code wiki / (root) / nx_audio_synth.nx

nx_audio_synth.nx source

↩ module page · 59 lines · 2081 B

1// nx_audio_synth.nx -- SOVEREIGN AUDIO ENGINE rung R3b: sine + ADSR envelope. 2// 3// osc_sine: a clickless tone via a sovereign PARABOLIC sine approximation (no trig 4// lib) -- exact at 0/peak/trough, smooth between; a higher-precision table is a 5// later refinement (honest: ~few% THD, not a pure sinusoid). 6// adsr_apply: Attack/Decay/Sustain/Release amplitude shaping (Q8 gain) so notes 7// fade in/out instead of clicking -- what makes synths musical. 8// Reuses ae_clamp (R0). license_tier: ORIGINAL. 9 10import "nx_syscalls.nx" 11import "nx_audio_mix.nx" 12 13const SY_PHASE: i64 = 65536 // one cycle (Q16) 14 15// parabolic sine: lobe y = amp * 4x(H-x)/H^2, negated on the second half-cycle 16func osc_sine(out: *i64, n: i64, freq: i64, rate: i64, amp: i64) -> i64 { 17 let inc: i64 = (freq * SY_PHASE) / rate 18 let half: i64 = SY_PHASE / 2 19 let hh: i64 = half * half 20 var ph: i64 = 0 21 var i: i64 = 0 22 while i < n { 23 var x: i64 = ph 24 var sign: i64 = 1 25 if ph >= half { x = ph - half; sign = 0 - 1 } 26 let num: i64 = 4 * x * (half - x) 27 let y: i64 = (amp * num) / hh 28 out[i] = sign * y 29 ph = ph + inc 30 while ph >= SY_PHASE { ph = ph - SY_PHASE } 31 i = i + 1 32 } 33 return n 34} 35 36// apply an ADSR envelope (sample counts a/d/r; sustain level sl in Q8) in place 37func adsr_apply(buf: *i64, n: i64, a: i64, d: i64, sl: i64, slen: i64, r: i64) -> i64 { 38 var da: i64 = a; if da < 1 { da = 1 } 39 var dd: i64 = d; if dd < 1 { dd = 1 } 40 var dr: i64 = r; if dr < 1 { dr = 1 } 41 var i: i64 = 0 42 while i < n { 43 var g: i64 = 0 44 if i < a { g = (i * 256) / da } 45 else { 46 if i < a + d { g = 256 - ((i - a) * (256 - sl)) / dd } 47 else { 48 if i < a + d + slen { g = sl } 49 else { 50 if i < a + d + slen + r { g = sl - ((i - (a + d + slen)) * sl) / dr } 51 else { g = 0 } 52 } 53 } 54 } 55 buf[i] = ae_clamp((buf[i] * g) / 256) 56 i = i + 1 57 } 58 return n 59}