code wiki / _hdl_build / nx_perceptual.nx
nx_perceptual.nx source
↩ module page · 105 lines · 5981 B
1// nx_perceptual.nx -- R2 of the codec ladder: a PERCEPTUAL audio quantizer on the MDCT (the MP3/AAC/Opus idea).
2// Composes nx_mdct (R1) for the transform. Groups MDCT bins into widening (Bark-like) critical bands, derives a
3// per-band MASKING threshold (band energy + simple spreading + an absolute-hearing floor), then allocates the
4// quantizer step per band so the quantization NOISE sits AT the mask -- coarse where the signal hides it, fine
5// where it doesn't. GATEABLE WITHOUT EARS via the noise-to-mask ratio: noise[b] <= mask[b] in every band =
6// perceptually transparent. TEETH: uniform quantization that reaches the SAME transparency needs many more bits
7// -- so perceptual transparency is provably cheaper. No-float (integer sqrt). license_tier: ORIGINAL expect_exit: 0
8import "nx_mdct.nx" // mk_window, mdct (R1, perfect-reconstruction)
9import "nx_audio_osc.nx" // osc_sin_unit (build the test tones)
10import "nx_syscalls.nx"
11const K_MAGIC_3840: i64 = 3840
12const K_MAGIC_9000: i64 = 9000
13const K_MAGIC_11520: i64 = 11520
14const K_MAGIC_2600: i64 = 2600
15const K_MAGIC_19200: i64 = 19200
16
17const N: i64 = 256
18const SMR: i64 = 16 // signal-to-mask ratio: a masker hides noise ~SMR x weaker (~12 dB). [config, not magic: psychoacoustics corpus]
19const ATH: i64 = 250000 // absolute-threshold-of-hearing floor (flat approximation; real ATH is frequency-dependent)
20
21func pp(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 }
22func ppn(v: i64) -> i64 {
23 let b: *u8 = sys_mmap(28); var x: i64=v
24 if x<0 { b[0]=45; sys_write(1,b,1); x=0-x }
25 if x==0 { b[0]=48; sys_write(1,b,1); return 0 }
26 var d: i64=0; var y: i64=x; while y>0 { d=d+1; y=y/10 }
27 var i: i64=d-1; y=x; while i>=0 { b[i]=(48+(y%10)) as u8; y=y/10; i=i-1 }
28 sys_write(1,b,d); return 0
29}
30func isqrt(n: i64) -> i64 { if n < 2 { return n } var x: i64=n; var y: i64=(x+1)/2; while y < x { x=y; y=(x + n/x)/2 } return x }
31func bitlen(v: i64) -> i64 { var a: i64=v; if a<0 {a=0-a} var b: i64=2; while a>0 { b=b+1; a=a>>1 } return b } // sign + magnitude
32// quantize X with step st -> integer level (round to nearest)
33func quantize(X: i64, st: i64) -> i64 { if st < 1 { return X } if X>=0 { return (X + st/2)/st } return 0 - ((0-X + st/2)/st) }
34
35func main() -> i64 {
36 pp("nx_perceptual (R2): MDCT perceptual quantizer; gate = noise-to-mask ratio + bits-vs-uniform\n" as *u8)
37 let two_n: i64 = 2*N
38 let w: *i64 = sys_mmap(two_n*8) as *i64
39 mk_window(w, N)
40 let x: *i64 = sys_mmap(two_n*8) as *i64
41 let X: *i64 = sys_mmap(N*8) as *i64
42
43 // test frame: three tones of very different loudness -> band energies vary widely (where perceptual coding wins)
44 var i: i64 = 0
45 while i < two_n {
46 let a: i64 = (osc_sin_unit(i*K_MAGIC_3840) * K_MAGIC_9000) >> 16 // strong (bin ~30)
47 let b: i64 = (osc_sin_unit(i*K_MAGIC_11520) * K_MAGIC_2600) >> 16 // medium (bin ~90)
48 let c: i64 = (osc_sin_unit(i*K_MAGIC_19200) * 500) >> 16 // weak (bin ~150)
49 x[i] = a + b + c
50 i = i + 1
51 }
52 mdct(x, X, w, N)
53
54 // widening (Bark-like) critical bands covering the N bins
55 let nb: i64 = 11
56 let bw: *i64 = sys_mmap(nb*8) as *i64
57 bw[0]=8; bw[1]=8; bw[2]=8; bw[3]=8; bw[4]=16; bw[5]=16; bw[6]=16; bw[7]=32; bw[8]=32; bw[9]=48; bw[10]=64
58 let bs: *i64 = sys_mmap((nb+1)*8) as *i64
59 bs[0]=0; var bi: i64=0; while bi<nb { bs[bi+1]=bs[bi]+bw[bi]; bi=bi+1 }
60
61 // band energy E[b] = sum X^2
62 let E: *i64 = sys_mmap(nb*8) as *i64
63 bi=0; while bi<nb { var e: i64=0; var k: i64=bs[bi]; while k<bs[bi+1] { e = e + (X[k]*X[k]) >> 8; k=k+1 } E[bi]=e; bi=bi+1 }
64 // mask T[b] = (spread energy)/SMR + ATH ; spreading = self + half of each neighbour
65 let T: *i64 = sys_mmap(nb*8) as *i64
66 bi=0; while bi<nb {
67 var sp: i64 = E[bi]
68 if bi>0 { sp = sp + E[bi-1]/2 }
69 if bi<nb-1 { sp = sp + E[bi+1]/2 }
70 T[bi] = sp/SMR + ATH
71 bi=bi+1
72 }
73
74 // PERCEPTUAL: step[b] sized so band quant-noise (M*step^2/12) ~ mask T[b]; quantize; measure noise + bits
75 var bits_p: i64 = 0; var transparent: i64 = 1
76 bi=0
77 while bi<nb {
78 let M: i64 = bw[bi]
79 var st: i64 = isqrt((12*T[bi]) / M); if st < 1 { st = 1 }
80 var nz: i64 = 0; var k: i64 = bs[bi]
81 while k < bs[bi+1] { let q: i64 = quantize(X[k], st); bits_p = bits_p + bitlen(q); let dq: i64 = q*st; let e: i64 = X[k]-dq; nz = nz + ((e*e)>>8); k=k+1 }
82 if nz > T[bi] { transparent = 0 }
83 bi=bi+1
84 }
85
86 // UNIFORM at transparency (neg-control / teeth): one step = the SMALLEST perceptual step (binds everywhere) -> bits
87 var st_u: i64 = 0
88 bi=0; while bi<nb { let M: i64=bw[bi]; var st: i64=isqrt((12*T[bi])/M); if st<1 {st=1} if st_u==0 {st_u=st} else { if st<st_u {st_u=st} } bi=bi+1 }
89 var bits_u: i64 = 0; i=0; while i<N { let q: i64 = quantize(X[i], st_u); bits_u = bits_u + bitlen(q); i=i+1 }
90
91 let raw_bits: i64 = N*16
92 pp(" perceptual: transparent(noise<=mask all bands)=" as *u8); ppn(transparent); pp(" bits=" as *u8); ppn(bits_p); pp("\n" as *u8)
93 pp(" uniform-at-transparency: bits=" as *u8); ppn(bits_u); pp(" (step=" as *u8); ppn(st_u); pp(")\n" as *u8)
94 pp(" raw 16-bit coeffs: bits=" as *u8); ppn(raw_bits); pp("\n" as *u8)
95 pp(" >> perceptual transparency costs " as *u8); ppn((bits_p*100)/bits_u); pp("% of uniform's bits (lower=win); " as *u8); ppn((raw_bits*100)/bits_p); pp("/100 x vs raw\n" as *u8)
96
97 var ok: i64 = 1
98 if transparent != 1 { ok = 0 }
99 if bits_p >= bits_u { ok = 0 } // perceptual must be cheaper than uniform-transparency
100 if bits_p >= raw_bits { ok = 0 } // and cheaper than raw
101 pp(" verdict=" as *u8)
102 if ok == 1 { pp("GREEN (perceptual quant reaches transparency at fewer bits than uniform -- masking model earns its keep) -- R2 core DONE\n" as *u8); sys_exit(0); return 0 }
103 pp("RED\n" as *u8)
104 sys_exit(1); return 1
105}