153 lines
4.9 KiB
GDScript
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)
|