code wiki / (root) / nx_sound.nx

nx_sound.nx source

↩ module page · 155 lines · 5146 B

1// nx_sound.nx -- audio primitives (windowing + spectrogram). 2// 3// Building on V7 FFT. Ships the foundation for short-time spectral 4// analysis -- the math that powers speech, music, and any temporal 5// signal-frequency analysis. 6// 7// What this unlocks: 8// + spectrogram (time x frequency intensity map) 9// + voice activity detection (energy in voice band) 10// + pitch detection (harmonic structure) 11// + beat detection (periodic energy spikes) 12// + room-tone characterization 13// + noise vs signal discrimination 14// 15// All Q14 windows. Cosine table reused at 5-degree resolution; window 16// precision +-2% (sufficient for most analysis tasks). 17// 18// genealogy_id: harris_1978_windowing + allen_rabiner_1977_stft 19// lineage_id: windowed_short_time_fft 20 21// nx_safety_envelope: 22// intended_use: AUTO_APPLIED -- primitive-specific tuning queued 23// sil_target: SIL1 24// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail] 25// verdict: NOT_YET_EVALUATED 26 27import "syscalls.nx" 28import "nx_fft.nx" 29 30const NX_SOUND_Q: i64 = 16384 // Q14 31 32// ===== Cosine helper (full 0..359 degree range) ======================== 33 34func nx_sound_cos_q10(deg_x5: i64) -> i64 { 35 let cos_t: *i64 = (sys_mmap(36 * 8)) as *i64 36 cos_t[0]=1024; cos_t[1]=1020; cos_t[2]=1008; cos_t[3]=989 37 cos_t[4]=962; cos_t[5]=928; cos_t[6]=887; cos_t[7]=839 38 cos_t[8]=784; cos_t[9]=724; cos_t[10]=658; cos_t[11]=587 39 cos_t[12]=512; cos_t[13]=433; cos_t[14]=350; cos_t[15]=265 40 cos_t[16]=178; cos_t[17]=89; cos_t[18]=0; cos_t[19]=-89 41 cos_t[20]=-178;cos_t[21]=-265;cos_t[22]=-350;cos_t[23]=-433 42 cos_t[24]=-512;cos_t[25]=-587;cos_t[26]=-658;cos_t[27]=-724 43 cos_t[28]=-784;cos_t[29]=-839;cos_t[30]=-887;cos_t[31]=-928 44 cos_t[32]=-962;cos_t[33]=-989;cos_t[34]=-1008;cos_t[35]=-1020 45 var t: i64 = deg_x5 46 while t < 0 { t = t + 72 } 47 while t >= 72 { t = t - 72 } 48 if t < 36 { return cos_t[t] } 49 return -cos_t[t - 36] 50} 51 52// ===== Window functions ================================================= 53// 54// Hann window: w[n] = 0.5 * (1 - cos(2*pi*n / (N-1))) 55// In Q14: w[n] = (Q14 - cos_q14) / 2 56 57func nx_sound_hann_window(N: i64, win: *i64) -> i64 { 58 if N < 2 { return -1 } 59 var n: i64 = 0 60 while n < N { 61 // angle_deg = 360 * n / (N - 1) 62 let angle_deg: i64 = (360 * n) / (N - 1) 63 // round to nearest 5 degrees 64 let deg_x5: i64 = (angle_deg + 2) / 5 65 let c_q10: i64 = nx_sound_cos_q10(deg_x5) 66 let c_q14: i64 = c_q10 * 16 67 win[n] = (NX_SOUND_Q - c_q14) / 2 68 n = n + 1 69 } 70 return 0 71} 72 73// Hamming window: w[n] = 0.54 - 0.46 * cos(2*pi*n / (N-1)) 74// In Q14: 0.54 * 16384 = 8847; 0.46 * 16384 = 7537 75 76func nx_sound_hamming_window(N: i64, win: *i64) -> i64 { 77 if N < 2 { return -1 } 78 var n: i64 = 0 79 while n < N { 80 let angle_deg: i64 = (360 * n) / (N - 1) 81 let deg_x5: i64 = (angle_deg + 2) / 5 82 let c_q10: i64 = nx_sound_cos_q10(deg_x5) 83 // 0.46 * cos in Q14 = (7537 * c_q10) / 1024 84 let scaled_cos: i64 = (7537 * c_q10) / 1024 85 win[n] = 8847 - scaled_cos 86 n = n + 1 87 } 88 return 0 89} 90 91// ===== Apply window to signal ========================================== 92// 93// Multiplies each sample by window weight; divides by Q14 to keep scale. 94 95func nx_sound_apply_window(signal: *i64, win: *i64, N: i64) -> i64 { 96 var n: i64 = 0 97 while n < N { 98 signal[n] = (signal[n] * win[n]) / NX_SOUND_Q 99 n = n + 1 100 } 101 return 0 102} 103 104// ===== Signal energy (frame RMS scaled) ================================ 105// 106// Sum of squares; useful for voice activity detection. 107 108func nx_sound_frame_energy(signal: *i64, N: i64) -> i64 { 109 var acc: i64 = 0 110 var n: i64 = 0 111 while n < N { 112 acc = acc + signal[n] * signal[n] 113 n = n + 1 114 } 115 return acc / N 116} 117 118// ===== Short-time FFT (spectrogram) ==================================== 119// 120// Splits signal into N-sample frames hopping by `hop` samples, applies 121// Hann window, runs FFT, stores power spectrum. Output: spec_out 122// laid out as n_frames * N row-major. 123// 124// Returns number of frames produced. 125 126func nx_sound_stft(signal: *i64, sig_len: i64, N: i64, hop: i64, 127 spec_out: *i64) -> i64 { 128 let win: *i64 = (sys_mmap(N * 8)) as *i64 129 nx_sound_hann_window(N, win) 130 let frame_re: *i64 = (sys_mmap(N * 8)) as *i64 131 let frame_im: *i64 = (sys_mmap(N * 8)) as *i64 132 let power: *i64 = (sys_mmap(N * 8)) as *i64 133 134 var frame_idx: i64 = 0 135 var start: i64 = 0 136 while start + N <= sig_len { 137 var i: i64 = 0 138 while i < N { 139 frame_re[i] = signal[start + i] 140 frame_im[i] = 0 141 i = i + 1 142 } 143 nx_sound_apply_window(frame_re, win, N) 144 nx_fft_forward(frame_re, frame_im, N) 145 nx_fft_power_spectrum(frame_re, frame_im, N, power) 146 var k: i64 = 0 147 while k < N { 148 spec_out[frame_idx * N + k] = power[k] 149 k = k + 1 150 } 151 frame_idx = frame_idx + 1 152 start = start + hop 153 } 154 return frame_idx 155}