Files
music-spinner/MusicVisualizor.gd
2026-02-10 06:09:33 +01:00

153 lines
4.9 KiB
GDScript

extends Control
# === User-tweakable exports ===
@export var music_bus := "MusicBus"
@export var num_points := 128 # base samples taken from analyzer
@export var samples_per_segment := 3 # higher -> smoother interpolation
@export var line_width := 3.0
@export var line_color := Color(0.2, 0.8, 1.0)
@export var fill_color := Color(0.2, 0.8, 1.0, 0.18)
@export var base_amplitude_scale := 0.8 # baseline (fraction of Control height)
@export var auto_scale := true # automatically adjust scale to avoid clipping
@export var auto_target_fraction := 0.85 # fraction of half-height we want peak to occupy
@export var auto_max_multiplier := 3.0
@export var scale_smoothness := 0.08 # smoothing for auto scale changes
@export var value_smoothness := 0.18 # smoothing for spectrum magnitudes
@export var spectrum_gain := 5.0 # multiplier on analyzer magnitude
@export var baseline_smoothness := 0.05 # smoothing for baseline centering
# === Internal ===
@onready var analyzer = AudioServer.get_bus_effect_instance(
AudioServer.get_bus_index(music_bus), 0
)
var values: Array = []
var current_amplitude_scale: float = base_amplitude_scale
var baseline: float = 0.0
func _ready() -> void:
values.resize(num_points)
for i in range(num_points):
values[i] = 0.0
func _process(delta: float) -> void:
if not analyzer:
return
# --- sample spectrum and smooth values ---
for i in range(num_points):
var freq_low: float = lerp(20.0, 20000.0, float(i) / num_points)
var freq_high: float = lerp(20.0, 20000.0, float(i + 1) / num_points)
var mag_v: float = analyzer.get_magnitude_for_frequency_range(freq_low, freq_high).length() * spectrum_gain
values[i] = lerp(values[i], mag_v, value_smoothness)
# --- update auto scale (smoothed) ---
_update_auto_scale()
# --- update dynamic baseline for centering ---
_update_baseline()
queue_redraw()
func _update_auto_scale() -> void:
if not auto_scale:
current_amplitude_scale = lerp(current_amplitude_scale, base_amplitude_scale, scale_smoothness)
return
var peak: float = 0.0
for v in values:
if v > peak:
peak = v
var eps: float = 1e-6
var half_h: float = size.y * 0.5
var peak_pixels: float = peak * size.y * base_amplitude_scale
var target_pixels: float = half_h * auto_target_fraction
var required_mul: float = (target_pixels / (peak_pixels + eps))
required_mul = clamp(required_mul, 1.0 / auto_max_multiplier, auto_max_multiplier)
var new_scale: float = base_amplitude_scale * required_mul
current_amplitude_scale = lerp(current_amplitude_scale, new_scale, scale_smoothness)
func _update_baseline() -> void:
var avg_mag: float = 0.0
for v in values:
avg_mag += v
avg_mag /= num_points
baseline = lerp(baseline, avg_mag, baseline_smoothness)
# --- Catmull-Rom interpolation for smooth curves ---
func _catmull_rom_interpolate(raw_points: PackedVector2Array, samples_per_segment: int) -> PackedVector2Array:
var n: int = raw_points.size()
if n < 2:
return raw_points.duplicate()
var out := PackedVector2Array()
for i in range(n - 1):
var p0: Vector2 = raw_points[clamp(i - 1, 0, n - 1)]
var p1: Vector2 = raw_points[i]
var p2: Vector2 = raw_points[i + 1]
var p3: Vector2 = raw_points[clamp(i + 2, 0, n - 1)]
for s in range(samples_per_segment):
var t: float = float(s) / samples_per_segment
var t2: float = t * t
var t3: float = t2 * t
var x: float = 0.5 * ( (2.0 * p1.x) +
(-p0.x + p2.x) * t +
(2.0*p0.x - 5.0*p1.x + 4.0*p2.x - p3.x) * t2 +
(-p0.x + 3.0*p1.x - 3.0*p2.x + p3.x) * t3 )
var y: float = 0.5 * ( (2.0 * p1.y) +
(-p0.y + p2.y) * t +
(2.0*p0.y - 5.0*p1.y + 4.0*p2.y - p3.y) * t2 +
(-p0.y + 3.0*p1.y - 3.0*p2.y + p3.y) * t3 )
out.append(Vector2(x, y))
out.append(raw_points[n - 1])
return out
func _draw() -> void:
if values.is_empty():
return
var mid_y: float = size.y * 0.5
var step_x: float = size.x / float(num_points - 1)
# raw top points (before interpolation)
var raw := PackedVector2Array()
for i in range(num_points):
var x: float = i * step_x
var y: float = mid_y - (values[i] - baseline) * size.y * current_amplitude_scale
raw.append(Vector2(x, y))
# smooth the curve
var top_points: PackedVector2Array = _catmull_rom_interpolate(raw, samples_per_segment)
# mirrored bottom points (reverse order)
var bottom_points: PackedVector2Array = PackedVector2Array()
for i in range(top_points.size() - 1, -1, -1):
var p: Vector2 = top_points[i]
bottom_points.append(Vector2(p.x, 2.0 * mid_y - p.y))
# build filled polygon
var poly: PackedVector2Array = PackedVector2Array()
poly.append(Vector2(0, mid_y))
for p in top_points:
poly.append(p)
poly.append(Vector2(size.x, mid_y))
for p in bottom_points:
poly.append(p)
# draw filled area
draw_polygon(poly, [fill_color])
# draw top and bottom outlines
if top_points.size() >= 2:
draw_polyline(top_points, line_color, line_width, true)
draw_polyline(bottom_points, line_color, line_width, true)