makepad/platform/script/audio_aot/shaders/wavetable_pad.splash

79 lines
2.1 KiB
Text

// Wavetable morph pad: init() builds 8 band-limited frames additively
// (saw -> square -> thinner), three detuned unison oscillators read two
// neighbouring frames each and crossfade as a slow LFO sweeps the table.
let SIZE = 2048
let FRAMES = 8
let table = [0.0; 16384]
let morph = param(0.15, 0, 4)
let detune = param(0.12, 0, 1)
let attack = param(0.4, 0.001, 4)
let release = param(1.2, 0.01, 8)
var ph = [0.0; 3]
var lfo = 0.0
var amp = 0.0
var up = 0.0
var down = 0.0
var spread = 1.0
fn init() {
for f in 0..FRAMES {
let t = f / (FRAMES - 1)
for i in 0..SIZE {
let x = f32(i) / SIZE
let s = 0.0
for h in 1..17 {
let saw = 1.0 / h
let sq = if h % 2 == 1 { 1.0 / h } else { 0.0 }
let thin = if h % 3 == 0 { 0.0 } else { 1.0 / (h * h) }
let a = if t < 0.5 { mix(saw, sq, t * 2.0) } else { mix(sq, thin, t * 2.0 - 1.0) }
s = s + sin(TAU * x * h) * a
}
table[f * SIZE + i] = s * 0.5
}
}
}
fn frame_read(f, p) {
let pos = p * SIZE
let i = int(floor(pos))
let fr = pos - floor(pos)
let a = table[f * SIZE + (i & (SIZE - 1))]
let b = table[f * SIZE + ((i + 1) & (SIZE - 1))]
a + (b - a) * fr
}
fn osc(p, m) {
let f = int(floor(m))
let k = m - floor(m)
let f1 = min(f + 1, FRAMES - 1)
mix(frame_read(f, p), frame_read(f1, p), k)
}
fn block() {
up = 1.0 / (attack * sample_rate)
down = exp(-1.0 / (release * sample_rate))
spread = exp2(detune / 12.0)
}
fn voice() {
lfo = fract(lfo + morph / sample_rate)
let m = (0.5 - 0.5 * cos(TAU * lfo)) * (FRAMES - 1) * 0.999
let base = freq / sample_rate
ph[0] = fract(ph[0] + base)
ph[1] = fract(ph[1] + base * spread)
ph[2] = fract(ph[2] + base / spread)
let c = osc(ph[0], m)
let hi = osc(ph[1], m)
let lo = osc(ph[2], m)
if gate > 0.0 {
amp = min(amp + up, 1.0)
} else {
amp = amp * down
if amp < 0.0001 {
stop()
}
}
let g = amp * velocity * 0.25
vec2(c + hi * 0.8 + lo * 0.4, c + hi * 0.4 + lo * 0.8) * g
}