// Plucked string (Karplus-Strong), in tune at every pitch: the loop is one // period minus the averaging filter's half sample, with the fraction in a // first-order allpass; the excitation is a filtered noise burst minus its // copy at the pluck position (the comb a real pluck makes); the loop loss // gives the same T60 at every pitch. let MAX = 4096 let bright = param(0.6, 0, 1) let position = param(0.22, 0.05, 0.5) let t60 = param(3.0, 0.2, 12) let damp = param(0.15, 0, 0.9) let gain = param(0.6, 0, 1) var buf = [0.0; MAX] var ex = [0.0; MAX] var w = int(0) var len = int(1) var comb = int(1) var t = int(0) var ap = Allpass1{} var c = 0.0 var avg = 0.0 var lp = 0.0 var loss = 0.99 var burst = 0.0 var amp = 1.0 fn voice() { if trigger { // One period, minus the averaging filter's half sample and the // damping low-pass's phase delay at the fundamental. let w0 = TAU * freq / sample_rate let lp_delay = atan2(damp * sin(w0), 1.0 - damp * cos(w0)) / w0 let p = sample_rate / freq - 0.5 - lp_delay let d = p - floor(p) len = int(floor(p)) if d < 0.1 { len = len - 1 d = d + 1.0 } c = (1.0 - d) / (1.0 + d) comb = max(int(position * p), 1) loss = exp(-3.0 * log(10.0) / (freq * t60)) t = 0 amp = 1.0 } let noise = if t < len { rand() } else { 0.0 } burst = burst + (noise - burst) * (0.08 + 0.92 * bright * velocity) let b = if t < len { burst } else { 0.0 } ex[t] = b let input = if t < len + comb { b - ex[t - comb] } else { 0.0 } t = min(t + 1, MAX) let y = allpass1(ap, buf[w - len], c) let s = (y + avg) * 0.5 avg = y // Extra damping darkens the tail (a one-pole in the loop). lp = lp + (s - lp) * (1.0 - damp) let out = lp * loss + input buf[w] = out w = w + 1 if gate == 0.0 { amp = amp * exp(-1.0 / (0.15 * sample_rate)) if amp < 0.0001 { stop() } } out * amp * gain * (0.4 + 0.6 * velocity) }