66 lines
1.5 KiB
Text
66 lines
1.5 KiB
Text
// Mono lead/bass: a polyBLEP saw through a zero-delay (TPT) state-variable
|
|
// low-pass, with a filter envelope and an amp envelope.
|
|
let cutoff = param(900, 30, 16000)
|
|
let res = param(0.5, 0, 0.98)
|
|
let env_amt = param(3000, 0, 12000)
|
|
let decay = param(0.35, 0.01, 4)
|
|
|
|
var phase = 0.0
|
|
var amp = 0.0
|
|
var fenv = 0.0
|
|
var ic1 = 0.0
|
|
var ic2 = 0.0
|
|
var a1 = 0.0
|
|
var a2 = 0.0
|
|
var a3 = 0.0
|
|
var fall = 0.0
|
|
var rel = 0.0
|
|
|
|
// The 2-sample polynomial band-limited step residual.
|
|
fn blep(t, dt) {
|
|
if t < dt {
|
|
let x = t / dt
|
|
return x + x - x * x - 1.0
|
|
}
|
|
if t > 1.0 - dt {
|
|
let x = (t - 1.0) / dt
|
|
return x * x + x + x + 1.0
|
|
}
|
|
0.0
|
|
}
|
|
|
|
// Control rate: filter coefficients follow the envelope every 64 frames.
|
|
fn block() {
|
|
let fc = clamp(cutoff + env_amt * fenv, 20.0, sample_rate * 0.45)
|
|
let g = tan(PI * fc / sample_rate)
|
|
let k = 2.0 - 2.0 * res
|
|
a1 = 1.0 / (1.0 + g * (g + k))
|
|
a2 = g * a1
|
|
a3 = g * a2
|
|
fall = exp(-1.0 / (decay * sample_rate))
|
|
rel = exp(-1.0 / (0.06 * sample_rate))
|
|
}
|
|
|
|
fn voice() {
|
|
if trigger {
|
|
fenv = 1.0
|
|
}
|
|
let dt = freq / sample_rate
|
|
phase = fract(phase + dt)
|
|
let saw = 2.0 * phase - 1.0 - blep(phase, dt)
|
|
let v3 = saw - ic2
|
|
let v1 = a1 * ic1 + a2 * v3
|
|
let v2 = ic2 + a2 * ic1 + a3 * v3
|
|
ic1 = 2.0 * v1 - ic1
|
|
ic2 = 2.0 * v2 - ic2
|
|
fenv = fenv * fall
|
|
if gate > 0.0 {
|
|
amp = min(amp + 330.0 / sample_rate, 1.0)
|
|
} else {
|
|
amp = amp * rel
|
|
if amp < 0.0001 {
|
|
stop()
|
|
}
|
|
}
|
|
v2 * amp * velocity * 0.5
|
|
}
|