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}