nx_audio_sfx.nx source
↩ module page · 113 lines · 3515 B
1// nx_audio_sfx.nx -- SOVEREIGN AUDIO ENGINE: a game SFX library.
2//
3// Named, ready-to-use game sounds composed from the engine's primitives:
4// beep, coin (two-tone ding), laser (descending pitch sweep), explosion
5// (filtered noise burst). Includes a sovereign xorshift noise source (NOT an LCG
6// -- good bit mixing). These drop straight into econsim / any game. Reuses
7// ae_clamp (R0), adsr_apply (R3b), fx_lowpass (R5). license_tier: ORIGINAL.
8
9import "nx_syscalls.nx"
10import "nx_audio_mix.nx"
11import "nx_audio_synth.nx"
12import "nx_audio_fx.nx"
13const SFX_MAGIC_9000: i64 = 9000
14const SFX_MAGIC_1319: i64 = 1319
15const SFX_MAGIC_1400: i64 = 1400
16const SFX_MAGIC_12000: i64 = 12000
17const SFX_MAGIC_2654435761: i64 = 2654435761
18
19const SFX_PH: i64 = 65536
20
21// xorshift64 PRNG step (good mixing; never LCG low bits)
22func sfx_rng(st: *i64) -> i64 {
23 var x: i64 = st[0]
24 x = x ^ (x << 13)
25 x = x ^ (x >> 7)
26 x = x ^ (x << 17)
27 st[0] = x
28 return x
29}
30
31// white noise into out[0..n) in [-amp,amp]
32func sfx_noise(out: *i64, n: i64, amp: i64, seed: i64) -> i64 {
33 let st: *i64 = (sys_mmap(8)) as *i64
34 st[0] = seed
35 if st[0] == 0 { st[0] = 1 }
36 let span: i64 = 2 * amp + 1
37 var i: i64 = 0
38 while i < n {
39 var u: i64 = sfx_rng(st)
40 if u < 0 { u = 0 - u }
41 out[i] = (u % span) - amp
42 i = i + 1
43 }
44 return n
45}
46
47// square tone via phase accumulator
48func sfx_tone(out: *i64, n: i64, freq: i64, rate: i64, amp: i64) -> i64 {
49 let inc: i64 = (freq * SFX_PH) / rate
50 let half: i64 = SFX_PH / 2
51 var ph: i64 = 0
52 var i: i64 = 0
53 while i < n {
54 if ph < half { out[i] = amp } else { out[i] = 0 - amp }
55 ph = ph + inc
56 while ph >= SFX_PH { ph = ph - SFX_PH }
57 i = i + 1
58 }
59 return n
60}
61
62func sfx_beep(out: *i64, n: i64, rate: i64) -> i64 {
63 sfx_tone(out, n, 880, rate, SFX_MAGIC_9000)
64 adsr_apply(out, n, n / 10, n / 8, 200, n / 2, n / 4)
65 return n
66}
67
68// coin: two-tone ding (B5 -> E6), square, quick decay
69func sfx_coin(out: *i64, n: i64, rate: i64) -> i64 {
70 let q: i64 = n / 4
71 let inc1: i64 = (988 * SFX_PH) / rate
72 let inc2: i64 = (SFX_MAGIC_1319 * SFX_PH) / rate
73 let half: i64 = SFX_PH / 2
74 var ph: i64 = 0
75 var i: i64 = 0
76 while i < n {
77 var inc: i64 = inc1
78 if i >= q { inc = inc2 }
79 if ph < half { out[i] = SFX_MAGIC_9000 } else { out[i] = 0 - SFX_MAGIC_9000 }
80 ph = ph + inc
81 while ph >= SFX_PH { ph = ph - SFX_PH }
82 i = i + 1
83 }
84 adsr_apply(out, n, q / 4, q / 4, 220, n / 2, n / 4)
85 return n
86}
87
88// laser: descending pitch sweep (1400 -> 200 Hz), square
89func sfx_laser(out: *i64, n: i64, rate: i64) -> i64 {
90 let f_hi: i64 = SFX_MAGIC_1400
91 let f_lo: i64 = 200
92 let half: i64 = SFX_PH / 2
93 var ph: i64 = 0
94 var i: i64 = 0
95 while i < n {
96 let f: i64 = f_hi - ((f_hi - f_lo) * i) / n
97 let inc: i64 = (f * SFX_PH) / rate
98 if ph < half { out[i] = SFX_MAGIC_9000 } else { out[i] = 0 - SFX_MAGIC_9000 }
99 ph = ph + inc
100 while ph >= SFX_PH { ph = ph - SFX_PH }
101 i = i + 1
102 }
103 adsr_apply(out, n, n / 20, n / 8, 180, n / 2, n / 3)
104 return n
105}
106
107// explosion: filtered noise burst with sharp attack + long decay
108func sfx_explosion(out: *i64, n: i64, rate: i64) -> i64 {
109 sfx_noise(out, n, SFX_MAGIC_12000, SFX_MAGIC_2654435761)
110 fx_lowpass(out, n, 60) // muffle
111 adsr_apply(out, n, n / 40, n / 6, 120, n / 4, n / 2)
112 return n
113}