// speech.el - The native SPEECH render path + voice-by-imitation extractor. // // Speech = the AUDIO surface (surface_profile_audio) rendering LANGUAGE-meaning // through a VOICE signature. The realizer's language faculty supplies the words // (meaning -> sem_realize -> text); this module turns text -> phonemes (phonetics.el) // -> a formant-target track over time -> SUPERPOSES formant resonances over a // glottal source (own-core formant synthesis, the exact integer mirror of the // music additive superpose) -> own-core PCM/WAV. Two paths: // (1) RENDER: speak(text, voice) -> spoken WAV. // (2) IMITATE: voice_analyze(pcm) -> a voice signature grabbed BY EAR // (autocorrelation pitch + integer-DFT formant peaks), then render // any new meaning in that voice. An impression, not a corpus. // All integer/fixed-point (EL float arithmetic is unusable). // -- Own-core integer sine (Bhaskara I), phase 0..65535 = one cycle ----------- fn sp_sin(phase: Int) -> Int { let deg: Int = phase * 360 / 65536 let neg: Int = 0 if deg > 180 { deg = deg - 180 neg = 1 } let t: Int = deg * (180 - deg) let num: Int = 32767 * 4 * t let den: Int = 40500 - t let v: Int = num / den if neg == 1 { v = 0 - v } return v } fn sp_cos(phase: Int) -> Int { let p: Int = phase + 16384 p = p - (p / 65536) * 65536 return sp_sin(p) } // One formant resonance (Lorentzian peak), Q15. Peak 32767 at f=fc. fn sp_gain(f: Int, fc: Int, bw: Int) -> Int { let d: Int = f - fc let den: Int = d * d + bw * bw let num: Int = 32767 * bw * bw return num / den } fn sp_isqrt(n: Int) -> Int { if n <= 0 { return 0 } let x: Int = n let y: Int = (x + 1) / 2 while y < x { x = y y = (x + n / x) / 2 } return x } // -- WAV serializer (thin medium; the only non-DSP glue) --------------------- fn wav_le16(buf: String, off: Int, v: Int) -> String { let u: Int = v if u < 0 { u = u + 65536 } let lo: Int = u - (u / 256) * 256 let hi: Int = u / 256 let b: String = __str_set_char(buf, off, lo) b = __str_set_char(b, off + 1, hi) return b } fn wav_le32(buf: String, off: Int, v: Int) -> String { let b0: Int = v - (v / 256) * 256 let r1: Int = v / 256 let b1: Int = r1 - (r1 / 256) * 256 let r2: Int = r1 / 256 let b2: Int = r2 - (r2 / 256) * 256 let b3: Int = r2 / 256 let b: String = __str_set_char(buf, off, b0) b = __str_set_char(b, off + 1, b1) b = __str_set_char(b, off + 2, b2) b = __str_set_char(b, off + 3, b3) return b } fn wav_ascii(buf: String, off: Int, s: String) -> String { let n: Int = str_len(s) let i: Int = 0 let b: String = buf while i < n { let c: Int = str_char_code(s, i) b = __str_set_char(b, off + i, c) i = i + 1 } return b } fn write_wav(samples: [Int], sr: Int, path: String) -> Bool { let ns: Int = native_list_len(samples) let datalen: Int = ns * 2 let total: Int = 44 + datalen let buf: String = __str_alloc(total) buf = wav_ascii(buf, 0, "RIFF") buf = wav_le32(buf, 4, 36 + datalen) buf = wav_ascii(buf, 8, "WAVE") buf = wav_ascii(buf, 12, "fmt ") buf = wav_le32(buf, 16, 16) buf = wav_le16(buf, 20, 1) buf = wav_le16(buf, 22, 1) buf = wav_le32(buf, 24, sr) buf = wav_le32(buf, 28, sr * 2) buf = wav_le16(buf, 32, 2) buf = wav_le16(buf, 34, 16) buf = wav_ascii(buf, 36, "data") buf = wav_le32(buf, 40, datalen) let j: Int = 0 let off: Int = 44 while j < ns { let raw: Int = native_list_get(samples, j) buf = wav_le16(buf, off, raw) off = off + 2 j = j + 1 } return __fs_write_bytes(path, buf, total) } // One formant resonance as a float Lorentzian peak (own-core physics). fn fgain(f: Float, fc: Float, bw: Float) -> Float { let d: Float = f - fc return (bw * bw) / (d * d + bw * bw) } // His PITCH MELODY from measured prosody [f0_median, f0_min, f0_max, declination]. // A natural statement shape over the utterance: onset rise to the median, a // near-flat body (his declination is ~0.6 Hz/s), and a final fall toward f0_min. // Follows his melody + range, not a fixed 0.85 decline. gidx/total = position. fn prosody_f0(pros: [Int], gidx: Int, total: Int) -> Int { let med: Int = native_list_get(pros, 0) let lo: Int = native_list_get(pros, 1) let hi: Int = native_list_get(pros, 2) let p: Int = gidx * 1000 / total let f0: Int = med if p < 150 { f0 = lo + (med - lo) * p / 150 } else { if p > 700 { f0 = med + (lo - med) * (p - 700) / 300 } else { f0 = med } } if f0 < lo { f0 = lo } if f0 > hi { f0 = hi } return f0 } // -- The render: phoneme codes + voice signature -> normalized PCM samples ---- // Formant geometry per phoneme is READ FROM THE ENGRAM (pmap) via phon_geo — no // table in code. The optional ACCENT map (amap) composes a transform onto the // voice (voice (+) accent, separable): RP formant overrides read from the accent // manifold + a non-rhotic coda-R drop. Empty amap = base General-American. // Synthesis is FLOAT: a real phase accumulator + math_sin, superposition physics. fn synth_codes_accent(codes0: [String], voice: [String], pmap: [String], amap: [String], vset: [String], vmap: [String], prosody: [Int]) -> [Int] { let sr: Int = 16000 let srf: Float = 16000.0 let two_pi: Float = 6.283185307 let kf: Int = voice_get_int(voice, "kf") let f0s: Int = voice_get_int(voice, "f0") let f0e: Int = voice_get_int(voice, "f0_end") let durm: Int = voice_get_int(voice, "dur") if kf <= 0 { kf = 1000 } if durm <= 0 { durm = 1000 } let use_accent: Int = 0 if native_list_len(amap) > 0 { use_accent = 1 } let codes: [String] = codes0 if use_accent == 1 { if is_nonrhotic(amap) == 1 { codes = apply_rhoticity(codes0, vset) } } let nc: Int = native_list_len(codes) // pass 1: per-segment sample counts + total let segn: [Int] = native_list_empty() let total: Int = 0 let ci: Int = 0 while ci < nc { let code: String = native_list_get(codes, ci) let p: [Int] = phon_geo(pmap, code) let durms: Int = native_list_get(p, 8) let ns: Int = durms * 16 * durm / 1000 segn = native_list_append(segn, ns) total = total + ns ci = ci + 1 } if total <= 0 { total = 1 } // pass 2: synthesize let samples: [Int] = native_list_empty() let phasef: Float = 0.0 let gidx: Int = 0 let prevF1: Int = 500 * kf / 1000 let prevF2: Int = 1500 * kf / 1000 let prevF3: Int = 2500 * kf / 1000 let nstate: Int = 22695 let maxabs: Int = 1 let ci2: Int = 0 while ci2 < nc { let code: String = native_list_get(codes, ci2) let p: [Int] = phon_geo(pmap, code) let rf1: Int = native_list_get(p, 0) let rf2: Int = native_list_get(p, 1) let rf3: Int = native_list_get(p, 2) if use_accent == 1 { let ov: [Int] = accent_formants(amap, code) if native_list_len(ov) >= 3 { rf1 = native_list_get(ov, 0) rf2 = native_list_get(ov, 1) rf3 = native_list_get(ov, 2) } } // HIS measured vowel target overrides the generic/kf path (absolute Hz — // his formants already encode his vocal tract, so no kf scaling). let usekf: Int = 1 if native_list_len(vmap) > 0 { let hv: [Int] = vmap_get(vmap, code) if native_list_len(hv) >= 3 { rf1 = native_list_get(hv, 0) rf2 = native_list_get(hv, 1) rf3 = native_list_get(hv, 2) usekf = 0 } } let F1t: Int = rf1 * kf / 1000 let F2t: Int = rf2 * kf / 1000 let F3t: Int = rf3 * kf / 1000 if usekf == 0 { F1t = rf1 F2t = rf2 F3t = rf3 } let B1: Int = native_list_get(p, 3) let B2: Int = native_list_get(p, 4) let B3: Int = native_list_get(p, 5) let voiced: Int = native_list_get(p, 6) let ampv: Int = native_list_get(p, 9) let ns: Int = native_list_get(segn, ci2) let trans: Int = ns / 2 if trans > 560 { trans = 560 } if trans < 1 { trans = 1 } let k: Int = 0 while k < ns { let cF1: Int = F1t let cF2: Int = F2t let cF3: Int = F3t if k < trans { cF1 = prevF1 + (F1t - prevF1) * k / trans cF2 = prevF2 + (F2t - prevF2) * k / trans cF3 = prevF3 + (F3t - prevF3) * k / trans } let f0c: Int = f0s + (f0e - f0s) * gidx / total if native_list_len(prosody) >= 3 { f0c = prosody_f0(prosody, gidx, total) } if f0c < 40 { f0c = 40 } let env: Int = 32767 let ar: Int = 96 if k < ar { env = 32767 * k / ar } let tail: Int = ns - k if tail < ar { env = 32767 * tail / ar } let f0cf: Float = int_to_float(f0c) phasef = phasef + two_pi * f0cf / srf if phasef > two_pi { phasef = phasef - two_pi } let s: Int = 0 if voiced == 1 { let cF1f: Float = int_to_float(cF1) let cF2f: Float = int_to_float(cF2) let cF3f: Float = int_to_float(cF3) let B1f: Float = int_to_float(B1) let B2f: Float = int_to_float(B2) let B3f: Float = int_to_float(B3) let acc: Float = 0.0 let h: Int = 1 while h <= 50 { let hf: Float = int_to_float(h) let fhf: Float = hf * f0cf if fhf < 7900.0 { let sv: Float = math_sin(phasef * hf) let src: Float = 1.0 / hf let g1: Float = fgain(fhf, cF1f, B1f) let g2: Float = fgain(fhf, cF2f, B2f) let g3: Float = fgain(fhf, cF3f, B3f) let g: Float = g1 + g2 + g3 acc = acc + src * g * sv } h = h + 1 } s = float_to_int(acc * 4000.0) } else { if ampv > 0 { nstate = nstate * 1103515245 + 12345 nstate = nstate - (nstate / 2147483648) * 2147483648 if nstate < 0 { nstate = 0 - nstate } let nz: Int = nstate / 32768 - 32768 s = nz } } s = s * ampv / 100 s = s * env / 32767 samples = native_list_append(samples, s) let a: Int = s if a < 0 { a = 0 - a } if a > maxabs { maxabs = a } gidx = gidx + 1 k = k + 1 } prevF1 = F1t prevF2 = F2t prevF3 = F3t ci2 = ci2 + 1 } // normalize to int16 range (~22000 peak) let out: [Int] = native_list_empty() let ntot: Int = native_list_len(samples) let j: Int = 0 while j < ntot { let raw: Int = native_list_get(samples, j) let v: Int = raw * 22000 / maxabs out = native_list_append(out, v) j = j + 1 } return out } // GA convenience wrapper (no accent) — keeps the base render path. fn synth_codes(codes: [String], voice: [String], pmap: [String]) -> [Int] { let noacc: [String] = native_list_empty() let novset: [String] = native_list_empty() let novmap: [String] = native_list_empty() let nopros: [Int] = native_list_empty() return synth_codes_accent(codes, voice, pmap, noacc, novset, novmap, nopros) } // -- Voice-by-imitation: HEAR a PCM sample -> extract the voice signature ----- // Pitch by autocorrelation; vocal-tract scale (kf) from the F1 formant peak of a // heard sustained vowel /AA/ (nominal F1 = 730 Hz) via an integer DFT. The // analyzer sees ONLY the PCM samples — never the source signature numbers — so // recovery is genuinely by ear. fn voice_f0(samples: [Int], sr: Int) -> Int { let n: Int = native_list_len(samples) let start: Int = n / 4 let end: Int = n * 3 / 4 // bound the analysis window so accumulators can never overflow on long input if end - start > 6000 { end = start + 6000 } let minlag: Int = sr / 300 let maxlag: Int = sr / 75 let best: Int = 0 let bestlag: Int = minlag let lag: Int = minlag while lag <= maxlag { let sum: Int = 0 let i: Int = start while i < end { let ai: Int = native_list_get(samples, i) let bi: Int = native_list_get(samples, i + lag) sum = sum + ai * bi / 256 i = i + 2 } if sum > best { best = sum bestlag = lag } lag = lag + 1 } if bestlag < 1 { bestlag = 1 } return sr / bestlag } fn voice_peak_in_band(samples: [Int], sr: Int, flo: Int, fhi: Int) -> Int { let n: Int = native_list_len(samples) let start: Int = n / 4 let end: Int = n * 3 / 4 // bound the DFT window: re/im are accumulated /4096, and re*re must stay in // int64 — cap terms so (window/2)*(peak_term) squared cannot overflow. if end - start > 3000 { end = start + 3000 } let bestmag: Int = 0 let bestf: Int = flo let f: Int = flo while f <= fhi { let re: Int = 0 let im: Int = 0 let i: Int = start while i < end { let x: Int = native_list_get(samples, i) let ph: Int = i * f * 65536 / sr ph = ph - (ph / 65536) * 65536 let cq: Int = sp_cos(ph) let sq: Int = sp_sin(ph) re = re + x * cq / 4096 im = im + x * sq / 4096 i = i + 2 } let mag: Int = re * re + im * im if mag > bestmag { bestmag = mag bestf = f } f = f + 25 } return bestf } // Analyze a heard sustained /AA/ -> a full voice signature (by ear). fn voice_analyze(samples: [Int], sr: Int) -> [String] { let f0: Int = voice_f0(samples, sr) let f1: Int = voice_peak_in_band(samples, sr, 450, 1150) let kf: Int = 1000 * f1 / 730 let f0e: Int = f0 * 85 / 100 return voice_new("imitated", f0, f0e, kf, 1000, 1000, 8) }