code wiki / (root) / nx_tts_word.nx

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}