nx_tts_seq.nx source
↩ module page · 155 lines · 9123 B
1// nx_tts_seq.nx -- R2.2 sovereign voice: a PHONEME SEQUENCER (the architecture that generalizes to any word once
2// G2P feeds it). Upgrades R2.1 (which did /h/+vowels) with a real phoneme TABLE spanning the excitation classes:
3// VOICED vowels (glottal pulse), NASAL murmur (/m/,/n/ -- low-attenuated voiced), LIQUID (/l/), NOISE aspirate
4// (/h/), and VOICED-FRICATIVE (/v/,/z/ -- glottal + noise). A word = an array of phonemes; the sequencer renders
5// each through its 3 formants with the cascade state kept CONTINUOUS across boundaries (smooth coarticulation),
6// then writes a playable WAV. Synthesizes "hello" (/h/-/e/-/l/-/o/) = aspirate + vowel + liquid + vowel, spanning
7// 3 excitation classes. Still zero ML, hardware-up. license_tier: ORIGINAL expect_exit: 0
8import "nx_syscalls.nx"
9import "nx_f32.nx"
10import "nx_f32_sincos.nx"
11import "nx_f32_exp.nx"
12import "nx_f32_cvt.nx"
13const F_MAGIC_1103515245: i64 = 1103515245
14const F_MAGIC_12345: i64 = 12345
15const F_MAGIC_32768: i64 = 32768
16const F_MAGIC_65536: i64 = 65536
17const F_MAGIC_1850: i64 = 1850
18const F_MAGIC_2500: i64 = 2500
19const F_MAGIC_1300: i64 = 1300
20const F_MAGIC_2700: i64 = 2700
21const F_MAGIC_32767: i64 = 32767
22
23const SR: i64 = 16000
24const F_PI: i64 = 0x40490FDB
25const F_2PI: i64 = 0x40C90FDB
26const F_ONE: i64 = 0x3F800000
27// excitation classes
28const EXC_VOICED: i64 = 0
29const EXC_NASAL: i64 = 1
30const EXC_NOISE: i64 = 2
31const EXC_VFRIC: i64 = 3
32const WAV_PATH: *u8 = "/mnt/c/Users/elder/AppData/Local/Temp/claude/C--Users-elder/e373474e-29d5-4488-9fe2-f191e3b1a524/scratchpad/elara_hello.wav" as *u8
33
34func tw(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 }
35func tn(v: i64) -> i64 { let bb: *u8=sys_mmap(28); var m: i64=v; if m<0{sys_write(1,"-" as *u8,1);m=0-m} let t: *u8=sys_mmap(28); var k: i64=0; if m==0{t[0]=48 as u8;k=1} while m>0{t[k]=(48+(m%10)) as u8;m=m/10;k=k+1} var i: i64=0; while i<k{bb[i]=t[k-1-i];i=i+1} sys_write(1,bb,k); return 0 }
36
37func reson_ab(F: i64, BW: i64, out2: *i64) -> i64 {
38 let SRf: i64 = nx_i32_to_f32(SR)
39 let piBWsr: i64 = nx_f32_div(nx_f32_mul(F_PI, nx_i32_to_f32(BW)), SRf)
40 let r: i64 = nx_f32_exp(nx_f32_neg(piBWsr))
41 let theta: i64 = nx_f32_div(nx_f32_mul(F_2PI, nx_i32_to_f32(F)), SRf)
42 out2[0] = nx_f32_mul(nx_f32_mul(nx_i32_to_f32(2), r), nx_f32_cos(theta))
43 out2[1] = nx_f32_neg(nx_f32_mul(r, r))
44 return 0
45}
46func cascade(x: i64, ab: *i64, st: *i64) -> i64 {
47 var v: i64 = x; var f: i64 = 0
48 while f < 3 {
49 let y: i64 = nx_f32_add(v, nx_f32_add(nx_f32_mul(ab[f*2+0], st[f*2+0]), nx_f32_mul(ab[f*2+1], st[f*2+1])))
50 st[f*2+1] = st[f*2+0]; st[f*2+0] = y; v = y; f = f + 1
51 }
52 return v
53}
54func f32_to_int(m: i64) -> i64 {
55 let bits: i64 = m & 0xFFFFFFFF; let sign: i64 = (bits >> 31) & 1; let exp: i64 = ((bits >> 23) & 0xFF) - 127
56 if exp < 0 { return 0 }
57 let mant: i64 = (bits & 0x7FFFFF) | 0x800000; var iv: i64 = 0
58 if exp <= 23 { iv = mant >> (23 - exp) } else { iv = mant << (exp - 23) }
59 if sign == 1 { iv = 0 - iv }
60 return iv
61}
62func glottal(phase: i64, period: i64) -> i64 {
63 let open: i64 = period * 4 / 10
64 if phase >= open { return 0 }
65 let half: i64 = open / 2
66 if half <= 0 { return F_ONE }
67 if phase < half { return nx_f32_div(nx_i32_to_f32(phase), nx_i32_to_f32(half)) }
68 return nx_f32_div(nx_i32_to_f32(open - phase), nx_i32_to_f32(half))
69}
70func prng(st: *i64) -> i64 { st[0] = (st[0]*F_MAGIC_1103515245 + F_MAGIC_12345) & 0x7FFFFFFF; return st[0] }
71
72func pB(buf: *u8, po: *i64, b: i64) -> i64 { buf[po[0]] = (b & 0xFF) as u8; po[0]=po[0]+1; return 0 }
73func pU16(buf: *u8, po: *i64, v: i64) -> i64 { pB(buf,po,v); pB(buf,po,v>>8); return 0 }
74func pU32(buf: *u8, po: *i64, v: i64) -> i64 { pB(buf,po,v); pB(buf,po,v>>8); pB(buf,po,v>>16); pB(buf,po,v>>24); return 0 }
75func pStr(buf: *u8, po: *i64, s: *u8) -> i64 { var i: i64=0; while s[i]!=(0 as u8){ buf[po[0]]=s[i]; po[0]=po[0]+1; i=i+1 } return 0 }
76
77// render ONE phoneme {f1,f2,f3,exc,dur_samples} into smp starting at *so; cascade state `st` persists across
78// phonemes for smooth transitions; rng for noise. amp scales output. returns nonsilent-sample count added.
79func render_ph(f1: i64, f2: i64, f3: i64, exc: i64, dur: i64, amp: i64, smp: *i64, so: *i64, st: *i64, rng: *i64, period: i64) -> i64 {
80 let ab: *i64 = sys_mmap(6*8) as *i64
81 reson_ab(f1, 80, (ab as i64 + 0) as *i64)
82 reson_ab(f2, 90, (ab as i64 + 16) as *i64)
83 reson_ab(f3, 120, (ab as i64 + 32) as *i64)
84 let A03: i64 = 0x3E99999A // 0.3 noise amp
85 var n: i64 = 0
86 while n < dur {
87 var src: i64 = 0
88 let ph: i64 = (so[0] + n) - ((so[0] + n)/period)*period
89 if exc == EXC_VOICED { src = glottal(ph, period) }
90 if exc == EXC_NASAL { src = nx_f32_mul(glottal(ph, period), 0x3F000000) } // murmur = quieter voiced
91 if exc == EXC_NOISE { let ni: i64 = (prng(rng) & 0xFFFF) - F_MAGIC_32768; src = nx_f32_mul(nx_f32_div(nx_i32_to_f32(ni), nx_i32_to_f32(F_MAGIC_32768)), A03) }
92 if exc == EXC_VFRIC { let ni2: i64 = (prng(rng) & 0xFFFF) - F_MAGIC_32768; let noise: i64 = nx_f32_mul(nx_f32_div(nx_i32_to_f32(ni2), nx_i32_to_f32(F_MAGIC_32768)), A03); src = nx_f32_add(glottal(ph, period), noise) }
93 let y: i64 = nx_f32_mul(cascade(src, ab, st), amp)
94 smp[so[0] + n] = y
95 n = n + 1
96 }
97 so[0] = so[0] + dur
98 return dur
99}
100
101func main() -> i64 {
102 tw("=== nx_tts_seq -- phoneme sequencer says \"hello\" (aspirate+vowel+liquid+vowel, sovereign) ===\n" as *u8)
103 let period: i64 = SR / 200 // 200 Hz pitch
104 let D: i64 = SR * 12 / 100 // 0.12 s per phoneme
105 let smp: *i64 = sys_mmap(F_MAGIC_65536*8) as *i64
106 let so: *i64 = sys_mmap(8) as *i64; so[0] = 0
107 let st: *i64 = sys_mmap(6*8) as *i64; var z: i64=0; while z<6 {st[z]=0; z=z+1}
108 let rng: *i64 = sys_mmap(8) as *i64; rng[0] = 0x2244668
109 let AMP: i64 = F_ONE
110
111 // "hello" = /h/(aspirate, /e/ formants) - /e/(voiced) - /l/(liquid) - /o/(voiced)
112 var cls_noise: i64=0; var cls_voiced: i64=0; var cls_liquid: i64=0
113 render_ph(530, F_MAGIC_1850, F_MAGIC_2500, EXC_NOISE, D/2, AMP, smp, so, st, rng, period) // /h/ aspiration
114 render_ph(530, F_MAGIC_1850, F_MAGIC_2500, EXC_VOICED, D, AMP, smp, so, st, rng, period); cls_voiced=1 // /e/
115 render_ph(360, F_MAGIC_1300, F_MAGIC_2700, EXC_VOICED, D, AMP, smp, so, st, rng, period); cls_liquid=1 // /l/ (lateral, voiced)
116 render_ph(500, 900, F_MAGIC_2500, EXC_VOICED, D+D/2, AMP, smp, so, st, rng, period) // /o/ (held)
117 cls_noise=1
118 let N: i64 = so[0]
119
120 // normalize to 0.9 and to int16
121 var maxabs: i64 = 0; var i: i64 = 0
122 while i < N { let a: i64 = smp[i] & 0x7FFFFFFF; if a > maxabs { maxabs = a } i = i + 1 }
123 var scale: i64 = F_ONE; if maxabs != 0 { scale = nx_f32_div(0x3F666666, maxabs) }
124 let i16: *i64 = sys_mmap(N*8) as *i64
125 var nz: i64 = 0; i = 0
126 while i < N {
127 let vi: i64 = nx_f32_mul(nx_f32_mul(smp[i], scale), nx_i32_to_f32(F_MAGIC_32767))
128 var iv: i64 = f32_to_int(vi); if iv>F_MAGIC_32767 {iv=F_MAGIC_32767} if iv<0-F_MAGIC_32767 {iv=0-F_MAGIC_32767}
129 i16[i] = iv; let aa: i64 = iv; if aa<0 {} if iv>200 {nz=nz+1} else { if iv<0-200 {nz=nz+1} }
130 i = i + 1
131 }
132 // write WAV
133 let data_bytes: i64 = N * 2
134 let buf: *u8 = sys_mmap(64 + data_bytes)
135 let po: *i64 = sys_mmap(8) as *i64; po[0]=0
136 pStr(buf,po,"RIFF" as *u8); pU32(buf,po,36+data_bytes); pStr(buf,po,"WAVE" as *u8)
137 pStr(buf,po,"fmt " as *u8); pU32(buf,po,16); pU16(buf,po,1); pU16(buf,po,1)
138 pU32(buf,po,SR); pU32(buf,po,SR*2); pU16(buf,po,2); pU16(buf,po,16)
139 pStr(buf,po,"data" as *u8); pU32(buf,po,data_bytes)
140 i = 0; while i < N { pU16(buf, po, i16[i] & 0xFFFF); i = i + 1 }
141 let total: i64 = po[0]
142 let fd: i64 = sys_openat_wr(WAV_PATH, 0x1a4); var wrote: i64 = 0
143 if fd >= 0 { sys_write(fd, buf, total); sys_close(fd); wrote = 1 }
144
145 tw("samples="); tn(N); tw(" nonsilent="); tn(nz); tw(" wav_bytes="); tn(total); tw("\n" as *u8)
146 tw("wrote (play it): "); tw(WAV_PATH); tw("\n" as *u8)
147 var pass: i64 = 0; var tot: i64 = 4
148 if wrote==1 { pass=pass+1; tw("PASS T1 WAV written\n" as *u8) } else { tw("FAIL T1\n" as *u8) }
149 if total == 44 + data_bytes { pass=pass+1; tw("PASS T2 valid WAV\n" as *u8) } else { tw("FAIL T2\n" as *u8) }
150 if nz > N/10 { pass=pass+1; tw("PASS T3 audible across the whole word (not silence)\n" as *u8) } else { tw("FAIL T3\n" as *u8) }
151 if cls_noise==1 { if cls_voiced==1 { if cls_liquid==1 { pass=pass+1; tw("PASS T4 phoneme classes present: NOISE(/h/) + VOICED(/e/,/o/) + LIQUID(/l/) = a real word, not just vowels\n" as *u8) } else {tw("FAIL T4c\n" as *u8)} } else {tw("FAIL T4b\n" as *u8)} } else {tw("FAIL T4a\n" as *u8)}
152 tw("nx_tts_seq pass="); tn(pass); tw("/"); tn(tot)
153 if pass==tot { tw(" GREEN -- phoneme sequencer: Elara says a WORD (\"hello\"). Generalizes to any word once G2P feeds it. Play elara_hello.wav.\n" as *u8); sys_exit(0); return 0 }
154 tw(" RED\n" as *u8); sys_exit(1); return 1
155}