code wiki / _hdl_build / nx_asr_frontend.nx
nx_asr_frontend.nx source
↩ module page · 133 lines · 5074 B
1// nx_asr_frontend.nx -- THE DEEP, rung 1: the ASR acoustic FRONT-END (PCM frame -> spectral features), the
2// bottom layer every speech recognizer stands on. Self-contained radix-2 forward FFT (integer Q14, sibling of
3// nx_fft but written in the harness-safe idioms + no inverse) -> power spectrum -> a contiguous filterbank of
4// band energies + the dominant bin/band. NO float. HONESTLY just the front-end -- NOT recognition (acoustic
5// model HMM/CTC, language model, decoding = next rungs, grounded by knowledge/fetched/asr_*.raw). Linear
6// filterbank R1; mel-warp = R2. license_tier: ORIGINAL
7import "nx_syscalls.nx"
8const K_MAGIC_16384: i64 = 16384
9const K_MAGIC_16069: i64 = 16069
10const K_MAGIC_3196: i64 = 3196
11const K_MAGIC_15137: i64 = 15137
12const K_MAGIC_6270: i64 = 6270
13const K_MAGIC_13623: i64 = 13623
14const K_MAGIC_9102: i64 = 9102
15const K_MAGIC_11585: i64 = 11585
16
17// N=32 twiddle table: re=cos(2pi k/32), im=-sin(2pi k/32), Q14. negatives as 0-N (harness-safe).
18func af_tw(tr: *i64, ti: *i64) -> i64 {
19 tr[0]=K_MAGIC_16384; ti[0]=0
20 tr[1]=K_MAGIC_16069; ti[1]=0-K_MAGIC_3196
21 tr[2]=K_MAGIC_15137; ti[2]=0-K_MAGIC_6270
22 tr[3]=K_MAGIC_13623; ti[3]=0-K_MAGIC_9102
23 tr[4]=K_MAGIC_11585; ti[4]=0-K_MAGIC_11585
24 tr[5]=K_MAGIC_9102; ti[5]=0-K_MAGIC_13623
25 tr[6]=K_MAGIC_6270; ti[6]=0-K_MAGIC_15137
26 tr[7]=K_MAGIC_3196; ti[7]=0-K_MAGIC_16069
27 tr[8]=0; ti[8]=0-K_MAGIC_16384
28 tr[9]=0-K_MAGIC_3196; ti[9]=0-K_MAGIC_16069
29 tr[10]=0-K_MAGIC_6270; ti[10]=0-K_MAGIC_15137
30 tr[11]=0-K_MAGIC_9102; ti[11]=0-K_MAGIC_13623
31 tr[12]=0-K_MAGIC_11585; ti[12]=0-K_MAGIC_11585
32 tr[13]=0-K_MAGIC_13623; ti[13]=0-K_MAGIC_9102
33 tr[14]=0-K_MAGIC_15137; ti[14]=0-K_MAGIC_6270
34 tr[15]=0-K_MAGIC_16069; ti[15]=0-K_MAGIC_3196
35 return 0
36}
37func af_log2(n: i64) -> i64 { var l: i64 = 0; var v: i64 = n; while v > 1 { v = v / 2; l = l + 1 } return l }
38func af_bitrev(idx: i64, log2n: i64) -> i64 {
39 var r: i64 = 0
40 var v: i64 = idx
41 var b: i64 = 0
42 while b < log2n { r = r * 2; r = r + (v - (v / 2) * 2); v = v / 2; b = b + 1 }
43 return r
44}
45// in-place forward radix-2 FFT, n in {2,4,8,16,32}
46func af_fwd(re: *i64, im: *i64, n: i64) -> i64 {
47 let log2n: i64 = af_log2(n)
48 let tr: *i64 = sys_mmap(16 * 8) as *i64
49 let ti: *i64 = sys_mmap(16 * 8) as *i64
50 af_tw(tr, ti)
51 var i: i64 = 0
52 while i < n {
53 let j: i64 = af_bitrev(i, log2n)
54 if i < j {
55 let xr: i64 = re[i]; let xi: i64 = im[i]
56 re[i]=re[j]; im[i]=im[j]; re[j]=xr; im[j]=xi
57 }
58 i = i + 1
59 }
60 var m: i64 = 1
61 while m < n {
62 let m2: i64 = m * 2
63 let stride: i64 = 16 / m
64 var k: i64 = 0
65 while k < n {
66 var j: i64 = 0
67 while j < m {
68 let tw: i64 = j * stride
69 let wr: i64 = tr[tw]; let wi: i64 = ti[tw]
70 let xr: i64 = re[k + j + m]; let xi: i64 = im[k + j + m]
71 let t_r: i64 = (wr * xr - wi * xi) / K_MAGIC_16384
72 let t_i: i64 = (wr * xi + wi * xr) / K_MAGIC_16384
73 let u_r: i64 = re[k + j]; let u_i: i64 = im[k + j]
74 re[k + j] = u_r + t_r
75 im[k + j] = u_i + t_i
76 re[k + j + m] = u_r - t_r
77 im[k + j + m] = u_i - t_i
78 j = j + 1
79 }
80 k = k + m2
81 }
82 m = m2
83 }
84 return 0
85}
86
87// power spectrum of an N-sample real frame -> power[0..N)
88func nx_asr_power(pcm: *i64, n: i64, power: *i64) -> i64 {
89 let re: *i64 = sys_mmap(n * 8) as *i64
90 let im: *i64 = sys_mmap(n * 8) as *i64
91 var i: i64 = 0
92 while i < n { re[i] = pcm[i]; im[i] = 0; i = i + 1 }
93 af_fwd(re, im, n)
94 var k: i64 = 0
95 while k < n { power[k] = re[k] * re[k] + im[k] * im[k]; k = k + 1 }
96 return 0
97}
98// index of the max power bin in [1, N/2] (skip DC)
99func nx_asr_peak_bin(power: *i64, n: i64) -> i64 {
100 var best: i64 = 0 - 1
101 var bi: i64 = 0
102 let half: i64 = n / 2
103 var k: i64 = 1
104 while k <= half { if power[k] > best { best = power[k]; bi = k } k = k + 1 }
105 return bi
106}
107// linear filterbank: sum power bins [1..N/2] into `nfilt` contiguous bands -> energies (mel-warp = R2)
108func nx_asr_filterbank(power: *i64, n: i64, nfilt: i64, energies: *i64) -> i64 {
109 let half: i64 = n / 2
110 var f: i64 = 0
111 while f < nfilt { energies[f] = 0; f = f + 1 }
112 var k: i64 = 1
113 while k <= half {
114 var band: i64 = ((k - 1) * nfilt) / half
115 if band >= nfilt { band = nfilt - 1 }
116 energies[band] = energies[band] + power[k]
117 k = k + 1
118 }
119 return 0
120}
121func nx_asr_dom_band(energies: *i64, nfilt: i64) -> i64 {
122 var best: i64 = 0 - 1
123 var bi: i64 = 0
124 var f: i64 = 0
125 while f < nfilt { if energies[f] > best { best = energies[f]; bi = f } f = f + 1 }
126 return bi
127}
128func nx_asr_total_energy(energies: *i64, nfilt: i64) -> i64 {
129 var s: i64 = 0
130 var f: i64 = 0
131 while f < nfilt { s = s + energies[f]; f = f + 1 }
132 return s
133}