extends RefCounted ## Genera proceduralmente un loop musicale da circo (valzer calliope) in WAV. ## Nessun file audio esterno: tutto sintetizzato a runtime, con loop seamless. const SR := 22050 static func generate() -> AudioStreamWAV: var bpm := 132.0 var beat := 60.0 / bpm var bars := 8 var total_beats := bars * 3 var total_seconds := total_beats * beat var n := int(SR * total_seconds) var mix := PackedFloat32Array() mix.resize(n) # Melodia (calliope) — 8 battute in 3/4 var melody := [ [72, 1.0], [76, 1.0], [79, 1.0], [72, 1.0], [76, 1.0], [79, 1.0], [69, 1.0], [72, 1.0], [76, 1.0], [69, 1.0], [72, 1.0], [76, 1.0], [65, 1.0], [69, 1.0], [72, 1.0], [65, 1.0], [69, 1.0], [72, 1.0], [67, 1.0], [71, 1.0], [74, 1.0], [72, 2.0], [0, 1.0], ] # Accompagnamento oom-pah-pah: [basso, [accordo]] var chords := [ [48, [60, 64, 67]], [48, [60, 64, 67]], [45, [57, 60, 64]], [45, [57, 60, 64]], [41, [53, 57, 60]], [41, [53, 57, 60]], [43, [55, 59, 62]], [48, [60, 64, 67]], ] # Melodia var t := 0.0 for note in melody: var midi: int = note[0] var beats: float = note[1] var dur := beats * beat if midi > 0: _add_note(mix, t, dur, _freq(midi), 0.5, "calliope") t += dur # Accompagnamento t = 0.0 for bar in bars: var bass: int = chords[bar][0] var chord: Array = chords[bar][1] _add_note(mix, t, beat * 0.9, _freq(bass), 0.42, "bass") for b in [1, 2]: var ct: float = t + b * beat for m in chord: _add_note(mix, ct, beat * 0.8, _freq(int(m)), 0.16, "chord") t += 3.0 * beat # Fade in/out (30ms) per loop seamless senza click var fade := int(SR * 0.03) for i in fade: var g := float(i) / fade mix[i] *= g mix[n - 1 - i] *= g # Normalizzazione var peak := 0.0 for i in n: var a := absf(mix[i]) if a > peak: peak = a var norm := 1.0 if peak > 0.0: norm = 0.9 / peak var data := PackedByteArray() data.resize(n) for i in n: var v := int(clampf(mix[i] * norm * 127.0, -128.0, 127.0)) data[i] = v & 0xFF var stream := AudioStreamWAV.new() stream.format = AudioStreamWAV.FORMAT_8_BITS stream.mix_rate = SR stream.data = data stream.loop_mode = AudioStreamWAV.LOOP_FORWARD stream.loop_begin = 0 stream.loop_end = n return stream static func _freq(midi: int) -> float: return 440.0 * pow(2.0, (midi - 69) / 12.0) static func _square(t: float, f: float) -> float: return 1.0 if sin(t * f * TAU) > 0.0 else -1.0 static func _saw(t: float, f: float) -> float: return fmod(t * f, 1.0) * 2.0 - 1.0 static func _envelope(t: float, dur: float) -> float: var a := 0.02 var r := 0.06 var e := 1.0 if t < a: e = t / a var rem := dur - t if rem < r: e = minf(e, rem / r) return e static func _add_note(mix: PackedFloat32Array, start: float, dur: float, freq: float, amp: float, kind: String) -> void: var start_i := int(start * SR) var cnt := int(dur * SR) for i in cnt: var idx := start_i + i if idx < 0 or idx >= mix.size(): break var t := float(i) / SR var env := _envelope(t, dur) var s := 0.0 if kind == "calliope": var vib := 1.0 + 0.006 * sin(t * 2.0 * PI * 5.0) var f := freq * vib s = 0.5 * _square(t, f) + 0.3 * _saw(t, f * 1.004) + 0.2 * _square(t, f * 0.996) elif kind == "bass": s = 0.6 * _saw(t, freq) + 0.4 * _square(t, freq) elif kind == "chord": s = 0.5 * _square(t, freq) + 0.5 * _saw(t, freq) mix[idx] += s * env * amp