79 lines
2.1 KiB
Text
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
|
|
}
|