first commit
This commit is contained in:
163
scripts/synth.gd
Normal file
163
scripts/synth.gd
Normal file
@@ -0,0 +1,163 @@
|
||||
extends RefCounted
|
||||
## Kleiner Synthesizer. Erzeugt PCM-Puffer und daraus AudioStreamWAV.
|
||||
## Alles wird beim Start berechnet, zur Laufzeit kostet es nichts mehr.
|
||||
|
||||
const RATE := 22050
|
||||
const TABLE_BITS := 11
|
||||
const TABLE_SIZE := 1 << TABLE_BITS
|
||||
const TABLE_MASK := TABLE_SIZE - 1
|
||||
|
||||
static var _sine: PackedFloat32Array
|
||||
|
||||
static func _table() -> PackedFloat32Array:
|
||||
if _sine.is_empty():
|
||||
_sine.resize(TABLE_SIZE)
|
||||
for i in TABLE_SIZE:
|
||||
_sine[i] = sin(TAU * float(i) / float(TABLE_SIZE))
|
||||
return _sine
|
||||
|
||||
## Sinus über eine Phase in Umdrehungen (0..1). Schneller als sin().
|
||||
static func osc(phase: float) -> float:
|
||||
var t := _table()
|
||||
return t[int(phase * TABLE_SIZE) & TABLE_MASK]
|
||||
|
||||
static func buffer(seconds: float) -> PackedFloat32Array:
|
||||
var b := PackedFloat32Array()
|
||||
b.resize(int(seconds * RATE))
|
||||
return b
|
||||
|
||||
## Ton mit Obertönen und exponentiellem Abfall.
|
||||
static func add_tone(buf: PackedFloat32Array, start: float, freq: float, dur: float,
|
||||
amp: float, decay: float, harmonics: Array = [1.0], attack := 0.005,
|
||||
vibrato := 0.0, vib_hz := 5.0) -> void:
|
||||
var i0 := int(start * RATE)
|
||||
var n := int(dur * RATE)
|
||||
var step := freq / float(RATE)
|
||||
for i in n:
|
||||
var idx := i0 + i
|
||||
if idx < 0 or idx >= buf.size():
|
||||
continue
|
||||
var t := float(i) / float(RATE)
|
||||
var env := exp(-t * decay)
|
||||
if t < attack:
|
||||
env *= t / attack
|
||||
var ph := float(i) * step
|
||||
if vibrato > 0.0:
|
||||
# Integrierte Phasenauslenkung. Die Phase selbst zu skalieren
|
||||
# würde die Tonhöhe über die Zeit davonlaufen lassen.
|
||||
ph += vibrato * freq / (TAU * vib_hz) * (1.0 + osc(t * vib_hz - 0.25))
|
||||
var v := 0.0
|
||||
var h := 1.0
|
||||
for a in harmonics:
|
||||
v += float(a) * osc(ph * h)
|
||||
h += 1.0
|
||||
buf[idx] += v * env * amp
|
||||
|
||||
## Weiches Pad: langsam an, langsam aus.
|
||||
static func add_pad(buf: PackedFloat32Array, start: float, freq: float, dur: float,
|
||||
amp: float, fade: float) -> void:
|
||||
var i0 := int(start * RATE)
|
||||
var n := int(dur * RATE)
|
||||
var step := freq / float(RATE)
|
||||
for i in n:
|
||||
var idx := i0 + i
|
||||
if idx < 0 or idx >= buf.size():
|
||||
continue
|
||||
var t := float(i) / float(RATE)
|
||||
var env: float = clampf(t / fade, 0.0, 1.0) * clampf((dur - t) / fade, 0.0, 1.0)
|
||||
env = env * env * (3.0 - 2.0 * env)
|
||||
var p := float(i) * step
|
||||
var v := osc(p) + 0.30 * osc(p * 2.0) + 0.09 * osc(p * 3.0)
|
||||
# Leichtes Schweben durch minimale Verstimmung
|
||||
v += 0.26 * osc(p * 1.003)
|
||||
buf[idx] += v * env * amp * 0.5
|
||||
|
||||
## Gefiltertes Rauschen mit Hüllkurve.
|
||||
static func add_noise(buf: PackedFloat32Array, start: float, dur: float, amp: float,
|
||||
decay: float, lp: float, rng: RandomNumberGenerator, attack := 0.002) -> void:
|
||||
var i0 := int(start * RATE)
|
||||
var n := int(dur * RATE)
|
||||
var y := 0.0
|
||||
for i in n:
|
||||
var idx := i0 + i
|
||||
if idx < 0 or idx >= buf.size():
|
||||
continue
|
||||
var t := float(i) / float(RATE)
|
||||
var env := exp(-t * decay)
|
||||
if t < attack:
|
||||
env *= t / attack
|
||||
y += (rng.randf_range(-1.0, 1.0) - y) * lp
|
||||
buf[idx] += y * env * amp
|
||||
|
||||
## Ton mit echtem Frequenzwobbeln. Die Phase wird aufsummiert, nicht
|
||||
## skaliert - nur so bleibt die Tonhöhe da, wo sie hingehört.
|
||||
static func add_wobble(buf: PackedFloat32Array, start: float, freq: float, dur: float,
|
||||
amp: float, depth: float, rate: float, harm2 := 0.0) -> void:
|
||||
var i0 := int(start * RATE)
|
||||
var n := int(dur * RATE)
|
||||
var ph := 0.0
|
||||
for i in n:
|
||||
var idx := i0 + i
|
||||
if idx < 0 or idx >= buf.size():
|
||||
continue
|
||||
var t := float(i) / float(RATE)
|
||||
ph += freq * (1.0 + depth * osc(t * rate)) / float(RATE)
|
||||
buf[idx] += (osc(ph) + harm2 * osc(ph * 2.0)) * amp
|
||||
|
||||
## Rauschen durch einen schmalen Resonanzfilter - klingt metallisch,
|
||||
## ohne die Härte eines reinen Sinus.
|
||||
static func add_resonant_noise(buf: PackedFloat32Array, start: float, dur: float,
|
||||
freq: float, r: float, amp: float, rng: RandomNumberGenerator,
|
||||
decay := 0.0) -> void:
|
||||
var i0 := int(start * RATE)
|
||||
var n := int(dur * RATE)
|
||||
var b1 := 2.0 * r * cos(TAU * freq / float(RATE))
|
||||
var b2 := -r * r
|
||||
var y1 := 0.0
|
||||
var y2 := 0.0
|
||||
var gain := (1.0 - r) * 1.6
|
||||
for i in n:
|
||||
var idx := i0 + i
|
||||
if idx < 0 or idx >= buf.size():
|
||||
continue
|
||||
var y := gain * rng.randf_range(-1.0, 1.0) + b1 * y1 + b2 * y2
|
||||
y2 = y1
|
||||
y1 = y
|
||||
var env := 1.0 if decay <= 0.0 else exp(-float(i) / float(RATE) * decay)
|
||||
buf[idx] += y * amp * env
|
||||
|
||||
## Legt den Ausklang auf den Anfang, damit die Schleife nahtlos wird.
|
||||
static func seamless(buf: PackedFloat32Array, loop_len: float) -> PackedFloat32Array:
|
||||
var n := int(loop_len * RATE)
|
||||
var tail: int = mini(buf.size() - n, n)
|
||||
var out := buf.slice(0, n)
|
||||
for i in tail:
|
||||
var w := 1.0 - float(i) / float(tail)
|
||||
out[i] += buf[n + i] * w
|
||||
return out
|
||||
|
||||
## Begrenzt sanft und wandelt in rohes PCM. Läuft auch in einem Thread.
|
||||
static func to_pcm(buf: PackedFloat32Array) -> PackedByteArray:
|
||||
var peak := 0.0
|
||||
for v in buf:
|
||||
peak = maxf(peak, absf(v))
|
||||
var gain: float = 0.92 / peak if peak > 0.001 else 1.0
|
||||
var data := PackedByteArray()
|
||||
data.resize(buf.size() * 2)
|
||||
for i in buf.size():
|
||||
var v: float = clampf(buf[i] * gain, -1.0, 1.0)
|
||||
data.encode_s16(i * 2, int(v * 32767.0))
|
||||
return data
|
||||
|
||||
## Baut den abspielbaren Stream. Nur im Hauptthread aufrufen.
|
||||
static func to_stream(pcm: PackedByteArray, loop := false) -> AudioStreamWAV:
|
||||
var w := AudioStreamWAV.new()
|
||||
w.format = AudioStreamWAV.FORMAT_16_BITS
|
||||
w.mix_rate = RATE
|
||||
w.stereo = false
|
||||
w.data = pcm
|
||||
if loop:
|
||||
w.loop_mode = AudioStreamWAV.LOOP_FORWARD
|
||||
w.loop_begin = 0
|
||||
w.loop_end = pcm.size() / 2
|
||||
return w
|
||||
Reference in New Issue
Block a user