code wiki / _hdl_build / nx_mdct.nx
nx_mdct.nx source
↩ module page · 120 lines · 6264 B
1// nx_mdct.nx -- R1 of the transform ladder: the Modified Discrete Cosine Transform (the lapped transform every
2// perceptual audio codec uses -- MP3/AAC/Opus-CELT). Built on the SAME sine absorbed into nx_audio_osc
3// (osc_sin_unit) -- one sine, no dup. Sine analysis+synthesis window (Princen-Bradley) -> Time-Domain Aliasing
4// Cancellation: overlap-add of consecutive half-overlapped blocks PERFECTLY reconstructs the signal. That PR
5// property is a pure math gate (verifiable without ears). No-float -> deterministic/bit-reproducible (the exceed).
6// Unlocks R2 = psychoacoustic masking + quantization = perceptual audio toward Opus. license_tier: ORIGINAL expect_exit: 0
7import "nx_audio_osc.nx" // osc_sin_unit -- the absorbed sine (window + cosine basis), dedup
8import "nx_syscalls.nx"
9const K_MAGIC_1100: i64 = 1100
10const K_MAGIC_2999: i64 = 2999
11const K_MAGIC_65536: i64 = 65536
12
13const OSCP: i64 = 65536
14const QPI: i64 = 16384 // pi/2 in osc units; cos(x) = sin(x + pi/2)
15
16func mp(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 }
17func mpn(v: i64) -> i64 {
18 let b: *u8 = sys_mmap(28); var x: i64=v
19 if x<0 { b[0]=45; sys_write(1,b,1); x=0-x }
20 if x==0 { b[0]=48; sys_write(1,b,1); return 0 }
21 var d: i64=0; var y: i64=x; while y>0 { d=d+1; y=y/10 }
22 var i: i64=d-1; y=x; while i>=0 { b[i]=(48+(y%10)) as u8; y=y/10; i=i-1 }
23 sys_write(1,b,d); return 0
24}
25func m_abs(v: i64) -> i64 { if v<0 { return 0-v } return v }
26
27// sine window w[n] = sin(pi/(2N)*(n+0.5)) over n in [0,2N); returns Q16. (w[n]^2 + w[n+N]^2 = 1 -> PR.)
28func mk_window(w: *i64, N: i64) -> i64 {
29 let two_n: i64 = 2*N
30 var n: i64 = 0
31 while n < two_n { w[n] = osc_sin_unit(((2*n+1) * OSCP) / (8*N)); n = n + 1 }
32 return 0
33}
34// MDCT cosine basis, Q16: cos[ (pi/N)*(n + 1/2 + N/2)*(k + 1/2) ]; osc angle = (a*b*OSCP)/(8N), a=2n+1+N, b=2k+1.
35func mbasis(n: i64, k: i64, N: i64) -> i64 {
36 let a: i64 = 2*n + 1 + N
37 let b: i64 = 2*k + 1
38 let th: i64 = (a * b * OSCP) / (8 * N)
39 return osc_sin_unit(th + QPI)
40}
41// forward MDCT: x[0..2N) (windowed inside) -> X[0..N)
42func mdct(x: *i64, X: *i64, w: *i64, N: i64) -> i64 {
43 let two_n: i64 = 2*N
44 var k: i64 = 0
45 while k < N {
46 var s: i64 = 0
47 var n: i64 = 0
48 while n < two_n { s = s + ((w[n] * x[n] * mbasis(n,k,N)) >> 24); n = n + 1 } // keep X[k] in Q8: preserves coefficient precision so cancellation at near-zero-basis positions can't amplify rounding
49 X[k] = s
50 k = k + 1
51 }
52 return 0
53}
54// inverse MDCT: X[0..N) -> y[0..2N) (synthesis-windowed, ready for overlap-add)
55func imdct(X: *i64, y: *i64, w: *i64, N: i64) -> i64 {
56 let two_n: i64 = 2*N
57 var n: i64 = 0
58 while n < two_n {
59 var s: i64 = 0
60 var k: i64 = 0
61 while k < N { s = s + (X[k] * mbasis(n,k,N)); k = k + 1 } // full-precision accumulate (Q16 cos)
62 let yt: i64 = (2 * s) / N
63 y[n] = (w[n] * yt) >> 40 // X is Q8 now (forward >>24), so the inverse normalization is >>40
64 n = n + 1
65 }
66 return 0
67}
68
69func main() -> i64 {
70 mp("nx_mdct (R1): MDCT + IMDCT, sine window, TDAC perfect-reconstruction (no-float, deterministic)\n" as *u8)
71 let N: i64 = 256
72 let two_n: i64 = 2*N
73 let w: *i64 = sys_mmap(two_n*8) as *i64
74 mk_window(w, N)
75
76 // PR test: a length-4N signal; process 3 half-overlapped blocks (at 0, N, 2N); overlap-add; the doubly-covered
77 // middle region [N, 3N) must reconstruct the input (TDAC). Edges [0,N) and [3N,4N) lack an overlap partner.
78 let L: i64 = 4*N
79 let x: *i64 = sys_mmap(L*8) as *i64
80 let out: *i64 = sys_mmap(L*8) as *i64
81 let X: *i64 = sys_mmap(N*8) as *i64
82 let y: *i64 = sys_mmap(two_n*8) as *i64
83 var i: i64 = 0
84 while i < L { x[i] = ((osc_sin_unit(i*673) * K_MAGIC_1100) >> 16) + ((osc_sin_unit(i*K_MAGIC_2999) * 350) >> 16); out[i] = 0; i = i + 1 } // smooth band-limited signal (what real audio is)
85
86 var p: i64 = 0
87 while p <= 2*N {
88 mdct(((x as i64) + p*8) as *i64, X, w, N) // unambiguous byte offset (x[p..])
89 imdct(X, y, w, N)
90 var n: i64 = 0
91 while n < two_n { out[p+n] = out[p+n] + y[n]; n = n + 1 }
92 p = p + N
93 }
94 mp(" sample check x[2N]=" as *u8); mpn(x[2*N]); mp(" out[2N]=" as *u8); mpn(out[2*N]); mp(" x[2N+40]=" as *u8); mpn(x[2*N+40]); mp(" out[2N+40]=" as *u8); mpn(out[2*N+40]); mp("\n" as *u8)
95
96 var mx: i64 = 0; var mxi: i64 = N
97 i = N
98 while i < 3*N { let e: i64 = m_abs(out[i] - x[i]); if e>mx {mx=e; mxi=i} i=i+1 }
99 mp(" PR max-err=" as *u8); mpn(mx); mp(" at i=" as *u8); mpn(mxi); mp(" (offset-from-N=" as *u8); mpn(mxi-N); mp(") x=" as *u8); mpn(x[mxi]); mp(" xprev=" as *u8); mpn(x[mxi-1]); mp("\n" as *u8)
100 // err in the deep-interior core only (exclude N/8 edge zones of the finite test)
101 var mxc: i64 = 0; i = N + N/8
102 while i < 3*N - N/8 { let e: i64 = m_abs(out[i] - x[i]); if e>mxc {mxc=e} i=i+1 }
103 mp(" PR core max-err (interior, finite-test edges excluded) =" as *u8); mpn(mxc); mp("\n" as *u8)
104 // window PR identity sanity: w[n]^2 + w[n+N]^2 == 1.0 (Q16) for the sine window
105 var widok: i64 = 1; i = 0
106 while i < N { let s: i64 = ((w[i]*w[i])>>16) + ((w[i+N]*w[i+N])>>16); if m_abs(s - K_MAGIC_65536) > 64 { widok = 0 } i = i + 1 }
107 mp(" window PR identity w^2+w'^2==1 (Q16): " as *u8); if widok==1 { mp("HOLDS\n" as *u8) } else { mp("FAILS\n" as *u8) }
108
109 // DIAGNOSTIC: decompose out[383] = block0.y[383] + blockN.y[127]; each block recomputed standalone.
110 mdct(x, X, w, N); imdct(X, y, w, N)
111 let y0_383: i64 = y[383]
112 mdct(((x as i64) + N*8) as *i64, X, w, N); imdct(X, y, w, N)
113 let yN_127: i64 = y[127]
114 mp(" decomp i=383: block0.y[383]=" as *u8); mpn(y0_383); mp(" + blockN.y[127]=" as *u8); mpn(yN_127); mp(" = " as *u8); mpn(y0_383+yN_127); mp(" vs x[383]=" as *u8); mpn(x[383]); mp(" (out[383]=" as *u8); mpn(out[383]); mp(")\n" as *u8)
115
116 mp(" verdict=" as *u8)
117 if mx < 60 { if widok == 1 { mp("GREEN (MDCT/IMDCT perfectly reconstruct via TDAC; no-float, deterministic) -- R1 DONE\n" as *u8); sys_exit(0); return 0 } }
118 mp("RED (recon err too high or window identity broke)\n" as *u8)
119 sys_exit(1); return 1
120}