TowerGameProto/examples_dd3d/demo_music_visualizer.gd

176 lines
5.4 KiB
GDScript

@tool
extends VBoxContainer
@export_range(1, 128) var bars_count := 32
var transform: Transform3D:
get:
return %AudioVisualizer.global_transform
@export_exp_easing("inout") var motion_smoothing := 0.025
@export_range(0, 0.5) var bar_thickness := 0.065
@export_range(0, 10) var bars_separation := 0.325
@export_exp_easing("inout") var color_offset_speed := 0.4
@export var colors: Gradient = null
var MusicAnalyzerBus := &"MusicAnalyzer"
var MasterBus := &"Master"
var MAX_HZ := 16000.0
var MIN_HZ := 20.0
var MIN_DB := 60.0
var spectrum: AudioEffectSpectrumAnalyzerInstance = null
var smoothed_energy: Array[float] = []
var color_offset := 0.0
var _on_data_loaded_callback = null
func _ready():
var bus = AudioServer.get_bus_index(MusicAnalyzerBus)
if bus == -1:
print("'MusicVisualizer' audio bus not found.\nSet 'VisualizerAudioBus.tres' as the default bus to use the audio visualizer.")
spectrum = AudioServer.get_bus_effect_instance(bus, 0)
%MuteMaster.button_pressed = AudioServer.is_bus_mute(AudioServer.get_bus_index(MasterBus))
%VolumeSlider.value = db_to_linear(AudioServer.get_bus_volume_db(AudioServer.get_bus_index(MasterBus)))
if OS.has_feature('web'):
motion_smoothing = motion_smoothing * 1.5
_on_data_loaded_callback = JavaScriptBridge.create_callback(_on_data_loaded)
# Retrieve the 'gd_callbacks' object
var gdcallbacks: JavaScriptObject = JavaScriptBridge.get_interface("gd_callbacks")
# Assign the callbacks
gdcallbacks.dataLoaded = _on_data_loaded_callback
func _process(_delta):
if %MusicPlayer.playing:
draw_spectrum()
func _pressed():
var open_file = func(filepath: String):
print("Opening '%s'" % filepath)
var file = FileAccess.open(filepath, FileAccess.READ)
var data = file.get_buffer(file.get_length())
open_stream(filepath.get_extension(), data)
if DisplayServer.has_feature(DisplayServer.FEATURE_NATIVE_DIALOG):
DisplayServer.file_dialog_show("Select audio file", "", "", true, DisplayServer.FILE_DIALOG_MODE_OPEN_FILE, ["*.mp3"],
func (status: bool, selected: PackedStringArray, _fileter: int):
if status and selected.size():
open_file.call(selected[0])
)
elif OS.has_feature('web'):
JavaScriptBridge.eval("loadData()")
else:
var fd := FileDialog.new()
add_child(fd)
fd.title = "Select audio file"
fd.access = FileDialog.ACCESS_FILESYSTEM
fd.file_mode = FileDialog.FILE_MODE_OPEN_FILE
fd.current_dir = OS.get_system_dir(OS.SYSTEM_DIR_DOWNLOADS)
fd.add_filter("*.mp3")
fd.popup_centered_ratio(0.8)
fd.file_selected.connect(func(path: String):
open_file.call(path)
)
fd.visibility_changed.connect(func():
if not fd.visible:
fd.queue_free()
)
func _on_data_loaded(data: Array) -> void:
# Make sure there is something
if (data.size() == 0):
return
var file_name: String = data[0]
print("Opening '%s'" % file_name)
var arr: PackedByteArray = JavaScriptBridge.eval("gd_callbacks.dataLoadedResult;")
open_stream(file_name.get_extension(), arr)
func open_stream(file_ext: String, data: PackedByteArray):
var stream: AudioStream = null
if file_ext == "mp3":
stream = AudioStreamMP3.new()
stream.data = data
if not stream.data:
print("Failed to load MP3!")
return
if not stream:
print("Failed to load music!")
return
%MusicPlayer.stream = stream
%MusicPlayer.bus = MusicAnalyzerBus
%MusicPlayer.play()
# Debugging frequencies
for ih in range(1, bars_count + 1):
var _hz: float = log_freq(ih / float(bars_count), MIN_HZ, MAX_HZ)
#print("%.0f hz %.2f" % [_hz, ih / float(bars_count)])
func draw_spectrum():
var _s1 = DebugDraw3D.scoped_config().set_thickness(bar_thickness).set_center_brightness(0.9)
var prev_hz = MIN_HZ
smoothed_energy.resize(bars_count)
var xf := transform
var y := xf.basis.y
var h := y.length()
var x := xf.basis.x
var z := xf.basis.z
var origin := xf.origin - (x * bars_count + (x * bars_separation) * (bars_count - 1)) * 0.5
var sum := 0.0
for ih in range(1, bars_count + 1):
var i := ih - 1
var hz: float = log_freq(ih / float(bars_count), MIN_HZ, MAX_HZ)
var magnitude: float = spectrum.get_magnitude_for_frequency_range(prev_hz, hz, AudioEffectSpectrumAnalyzerInstance.MAGNITUDE_AVERAGE).length()
var energy: float = clampf((MIN_DB + linear_to_db(magnitude)) / MIN_DB, 0, 1)
var e: float = lerp(smoothed_energy[i], energy, clampf(get_process_delta_time() / motion_smoothing if motion_smoothing else 1.0, 0, 1))
smoothed_energy[i] = e
var height: float = e * h
sum += e
var s := x * bars_separation
var a := origin + x * i + s * i + (z * 0.5)
var b := origin + x * (i + 1) + s * i + (z * -0.5) + xf.basis.y.normalized() * clampf(height, 0.001, h)
var c := Color.HOT_PINK
if colors:
c = colors.sample(wrapf(float(ih) / bars_count + color_offset, 0, 1))
c.s = clamp(c.s - smoothed_energy[i] * 0.3, 0, 1.0)
DebugDraw3D.draw_box_ab(a, b, y, c)
prev_hz = hz
color_offset = wrapf(color_offset + sum / smoothed_energy.size() * clampf(get_process_delta_time() / color_offset_speed if color_offset_speed else 1.0, 0, 1), 0, 1)
func log10(val: float) -> float:
return log(val) / 2.302585
func log_freq(pos: float, min_hz: float, max_hz: float) -> float:
return pow(10, log10(min_hz) + (log10(max_hz) - log10(min_hz)) * pos)
func _on_volume_slider_value_changed(value):
AudioServer.set_bus_volume_db(AudioServer.get_bus_index(MasterBus), linear_to_db(value))
func _on_mute_master_toggled(toggled_on):
AudioServer.set_bus_mute(AudioServer.get_bus_index(MasterBus), toggled_on)