nx_tts_word.nx source
↩ module page · 151 lines · 7992 B
1// nx_tts_word.nx -- R2.1 of the sovereign voice: from vowels to a WORD. Two upgrades over nx_tts_formant:
2// (1) a TRIANGULAR GLOTTAL PULSE (open phase) instead of a bare impulse -> richer, far less buzzy voiced source;
3// (2) a NOISE-EXCITED CONSONANT -- /h/ aspiration = a sovereign PRNG white-noise source shaped by the following
4// vowel's formants -- so she says an actual word: "hi" = /h/ + /a->i/ diphthong. Voiced (periodic) + unvoiced
5// (aperiodic) excitation both present = real speech structure. Writes a playable WAV. Still zero ML, hardware-up.
6// license_tier: ORIGINAL expect_exit: 0
7import "nx_syscalls.nx"
8import "nx_f32.nx"
9import "nx_f32_sincos.nx"
10import "nx_f32_exp.nx"
11import "nx_f32_cvt.nx"
12const F_MAGIC_1103515245: i64 = 1103515245
13const F_MAGIC_12345: i64 = 12345
14const F_MAGIC_1150: i64 = 1150
15const F_MAGIC_2900: i64 = 2900
16const F_MAGIC_2250: i64 = 2250
17const F_MAGIC_32768: i64 = 32768
18const F_MAGIC_32767: i64 = 32767
19
20const SR: i64 = 16000
21const F_PI: i64 = 0x40490FDB
22const F_2PI: i64 = 0x40C90FDB
23const F_ONE: i64 = 0x3F800000
24const WAV_PATH: *u8 = "/mnt/c/Users/elder/AppData/Local/Temp/claude/C--Users-elder/e373474e-29d5-4488-9fe2-f191e3b1a524/scratchpad/elara_hi.wav" as *u8
25
26func tw(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 }
27func 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 }
28
29func reson_ab(F: i64, BW: i64, out2: *i64) -> i64 {
30 let SRf: i64 = nx_i32_to_f32(SR)
31 let piBWsr: i64 = nx_f32_div(nx_f32_mul(F_PI, nx_i32_to_f32(BW)), SRf)
32 let r: i64 = nx_f32_exp(nx_f32_neg(piBWsr))
33 let theta: i64 = nx_f32_div(nx_f32_mul(F_2PI, nx_i32_to_f32(F)), SRf)
34 out2[0] = nx_f32_mul(nx_f32_mul(nx_i32_to_f32(2), r), nx_f32_cos(theta))
35 out2[1] = nx_f32_neg(nx_f32_mul(r, r))
36 return 0
37}
38func cascade(x: i64, ab: *i64, st: *i64) -> i64 {
39 var v: i64 = x; var f: i64 = 0
40 while f < 3 {
41 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])))
42 st[f*2+1] = st[f*2+0]; st[f*2+0] = y; v = y; f = f + 1
43 }
44 return v
45}
46func f32_to_int(m: i64) -> i64 {
47 let bits: i64 = m & 0xFFFFFFFF; let sign: i64 = (bits >> 31) & 1; let exp: i64 = ((bits >> 23) & 0xFF) - 127
48 if exp < 0 { return 0 }
49 let mant: i64 = (bits & 0x7FFFFF) | 0x800000; var iv: i64 = 0
50 if exp <= 23 { iv = mant >> (23 - exp) } else { iv = mant << (exp - 23) }
51 if sign == 1 { iv = 0 - iv }
52 return iv
53}
54// triangular glottal pulse for phase within a pitch period (open phase ~40%); f32 in [0,1].
55func glottal(phase: i64, period: i64) -> i64 {
56 let open: i64 = period * 4 / 10
57 if phase >= open { return 0 }
58 let half: i64 = open / 2
59 if half <= 0 { return F_ONE }
60 if phase < half { return nx_f32_div(nx_i32_to_f32(phase), nx_i32_to_f32(half)) }
61 return nx_f32_div(nx_i32_to_f32(open - phase), nx_i32_to_f32(half))
62}
63func prng(st: *i64) -> i64 { st[0] = (st[0]*F_MAGIC_1103515245 + F_MAGIC_12345) & 0x7FFFFFFF; return st[0] }
64
65func pB(buf: *u8, po: *i64, b: i64) -> i64 { buf[po[0]] = (b & 0xFF) as u8; po[0]=po[0]+1; return 0 }
66func pU16(buf: *u8, po: *i64, v: i64) -> i64 { pB(buf,po,v); pB(buf,po,v>>8); return 0 }
67func 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 }
68func 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 }
69
70func main() -> i64 {
71 tw("=== nx_tts_word -- Elara says \"hi\" (glottal pulse + noise consonant, sovereign) ===\n" as *u8)
72 let F0: i64 = 200
73 let period: i64 = SR / F0
74 let h_len: i64 = SR * 8 / 100 // /h/ aspiration 0.08s (unvoiced noise)
75 let a_len: i64 = SR * 28 / 100 // /a/ 0.28s
76 let i_len: i64 = SR * 40 / 100 // /i/ 0.40s
77 let N: i64 = h_len + a_len + i_len
78
79 let ab_a: *i64 = sys_mmap(6*8) as *i64
80 reson_ab(800, 80, (ab_a as i64 + 0) as *i64); reson_ab(F_MAGIC_1150, 90, (ab_a as i64 + 16) as *i64); reson_ab(F_MAGIC_2900, 120, (ab_a as i64 + 32) as *i64)
81 let ab_i: *i64 = sys_mmap(6*8) as *i64
82 reson_ab(280, 70, (ab_i as i64 + 0) as *i64); reson_ab(F_MAGIC_2250, 100, (ab_i as i64 + 16) as *i64); reson_ab(F_MAGIC_2900, 120, (ab_i as i64 + 32) as *i64)
83
84 let st: *i64 = sys_mmap(6*8) as *i64; var z: i64=0; while z<6 {st[z]=0; z=z+1}
85 let rng: *i64 = sys_mmap(8) as *i64; rng[0] = 0x1234567
86 let smp: *i64 = sys_mmap(N*8) as *i64
87 let A03: i64 = 0x3E99999A // 0.3 aspiration amplitude
88
89 var maxabs: i64 = 0
90 var unv: i64 = 0 // count noticeable unvoiced (noise) samples in /h/
91 var n: i64 = 0
92 while n < N {
93 var src: i64 = 0
94 var ab: *i64 = ab_a
95 if n < h_len {
96 // /h/ : white noise (aperiodic) shaped by /a/ formants, low amplitude
97 let ni: i64 = (prng(rng) & 0xFFFF) - F_MAGIC_32768
98 src = nx_f32_mul(nx_f32_div(nx_i32_to_f32(ni), nx_i32_to_f32(F_MAGIC_32768)), A03)
99 } else {
100 // voiced: glottal pulse; /a/ then /i/ formants
101 let phase: i64 = n - (n/period)*period
102 src = glottal(phase, period)
103 if n >= h_len + a_len { ab = ab_i }
104 }
105 let y: i64 = cascade(src, ab, st)
106 smp[n] = y
107 let ay: i64 = y & 0x7FFFFFFF
108 if ay > maxabs { maxabs = ay }
109 if n < h_len { if ay > 0x3C23D70A { unv = unv + 1 } } // >0.01
110 n = n + 1
111 }
112
113 let i16buf: *i64 = sys_mmap(N*8) as *i64
114 var scale: i64 = F_ONE
115 if maxabs != 0 { scale = nx_f32_div(0x3F666666, maxabs) } // peak 0.9
116 var nz: i64 = 0; var voiced: i64 = 0
117 n = 0
118 while n < N {
119 let vi: i64 = nx_f32_mul(nx_f32_mul(smp[n], scale), nx_i32_to_f32(F_MAGIC_32767))
120 var iv: i64 = f32_to_int(vi)
121 if iv > F_MAGIC_32767 { iv = F_MAGIC_32767 }
122 if iv < 0-F_MAGIC_32767 { iv = 0-F_MAGIC_32767 }
123 i16buf[n] = iv
124 if iv > 200 { nz = nz + 1 } else { if iv < 0-200 { nz = nz + 1 } }
125 if n >= h_len { if iv > 200 { voiced = voiced + 1 } else { if iv < 0-200 { voiced = voiced + 1 } } }
126 n = n + 1
127 }
128
129 let data_bytes: i64 = N * 2
130 let buf: *u8 = sys_mmap(64 + data_bytes)
131 let po: *i64 = sys_mmap(8) as *i64; po[0]=0
132 pStr(buf,po,"RIFF" as *u8); pU32(buf,po,36+data_bytes); pStr(buf,po,"WAVE" as *u8)
133 pStr(buf,po,"fmt " as *u8); pU32(buf,po,16); pU16(buf,po,1); pU16(buf,po,1)
134 pU32(buf,po,SR); pU32(buf,po,SR*2); pU16(buf,po,2); pU16(buf,po,16)
135 pStr(buf,po,"data" as *u8); pU32(buf,po,data_bytes)
136 n = 0; while n < N { pU16(buf, po, i16buf[n] & 0xFFFF); n = n + 1 }
137 let total: i64 = po[0]
138 let fd: i64 = sys_openat_wr(WAV_PATH, 0x1a4); var wrote: i64 = 0
139 if fd >= 0 { sys_write(fd, buf, total); sys_close(fd); wrote = 1 }
140
141 tw("samples="); tn(N); tw(" /h/noise="); tn(unv); tw(" voiced="); tn(voiced); tw(" wav_bytes="); tn(total); tw("\n" as *u8)
142 tw("wrote: "); tw(WAV_PATH); tw("\n" as *u8)
143 var pass: i64 = 0; var tot: i64 = 4
144 if wrote==1 { pass=pass+1; tw("PASS T1 WAV written\n" as *u8) } else { tw("FAIL T1\n" as *u8) }
145 if total == 44 + data_bytes { pass=pass+1; tw("PASS T2 valid WAV\n" as *u8) } else { tw("FAIL T2\n" as *u8) }
146 if unv > h_len/4 { pass=pass+1; tw("PASS T3 unvoiced /h/ consonant present (noise excitation)\n" as *u8) } else { tw("FAIL T3\n" as *u8) }
147 if voiced > (a_len+i_len)/4 { pass=pass+1; tw("PASS T4 voiced vowels present (glottal-pulse excitation)\n" as *u8) } else { tw("FAIL T4\n" as *u8) }
148 tw("nx_tts_word pass="); tn(pass); tw("/"); tn(tot)
149 if pass==tot { tw(" GREEN -- consonant+vowel speech structure. Play elara_hi.wav (\"hi\").\n" as *u8); sys_exit(0); return 0 }
150 tw(" RED\n" as *u8); sys_exit(1); return 1
151}