176 lines
5.4 KiB
GDScript3
176 lines
5.4 KiB
GDScript3
|
@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)
|