Files
el/elp/src/speech-ingest.el
T
bigmerge b7e2c580a8
El SDK CI - dev / build-and-test (pull_request) Successful in 6m28s
Add native speech synthesis and voice-imitation faculty
speech.el: formant/glottal integer DSP synthesis + voice-analyze-by-
imitation. voice-profile.el / voice-ingest.el: voice-profile plumbing.
accent.el: British-RP as an ingested transform-geometry (explicitly marked
provisional/citation-pending by its own comments). organ-read.el:
engram read-through for the speech organ. Includes demo/test drivers and
non-personal reference data (British-RP phonetics/lexicon derived data,
a public-domain LibriVox RP reference recording).

Deliberately excludes elp/data/live/ (raw recorded voice + face-photo
samples of the repo owner) and the will-*.{json,psv} derived voiceprint
files — personal biometric data that shouldn't be committed to a shared
repo without an explicit decision from the owner. Also excludes this
worktree's elp/src/surface-profile.el, which diverges from the copy in
other worktrees (agent-aaf04b0a9714c4070, main) — needs manual
reconciliation before landing, left out here to avoid silently picking a
version.
2026-08-15 14:27:52 -05:00

234 lines
8.5 KiB
EmacsLisp

// speech-ingest.el - The native LOAD step of the ingest organ, for the SPEECH
// primitives. Reads the acoustic-phonetics SOURCE (elp/data/phonetics.psv) and
// the pronunciation lexicon SOURCE (elp/data/lexicon.psv) and emits a PHONEME
// MANIFOLD into the engram: one node per phoneme (faithful, provenance-tagged
// content) + is_a edges to phoneme-class nodes (a discrete manifold, not islands).
// The render then PULLS phoneme geometry back from the engram via phon_geo
// zero phonetic numbers in code. Source -> manifold -> merge; the same output
// the polymorphic ingest organ will produce and subsume.
// -- small parsing helpers ---------------------------------------------------
fn sp_map_get(pairs: [String], key: String) -> String {
let n: Int = native_list_len(pairs)
let i: Int = 0
while i < n - 1 {
let k: String = native_list_get(pairs, i)
if str_eq(k, key) {
return native_list_get(pairs, i + 1)
}
let i = i + 2
}
return ""
}
// read the unsigned integer that follows `key` inside string s (e.g. key "F1=")
fn parse_uint_from(s: String, key: String) -> Int {
let idx: Int = str_index_of(s, key)
if idx < 0 {
return 0
}
let start: Int = idx + str_len(key)
let n: Int = str_len(s)
let i: Int = start
let val: Int = 0
while i < n {
let c: Int = str_char_code(s, i)
if c >= 48 {
if c <= 57 {
val = val * 10 + (c - 48)
i = i + 1
} else {
i = n
}
} else {
i = n
}
}
return val
}
fn clean_word(w: String) -> String {
let low: String = str_to_lower(w)
let n: Int = str_len(low)
let out: String = ""
let i: Int = 0
while i < n {
let c: Int = str_char_code(low, i)
if c >= 97 {
if c <= 122 {
out = out + str_char_at(low, i)
}
}
i = i + 1
}
return out
}
// -- INGEST: acoustic-phonetics source -> phoneme manifold in the engram ------
// Returns the symbol -> node-id index (pmap) the render reads geometry through.
fn ingest_phonetics(path: String) -> [String] {
let content: String = fs_read(path)
let lines: [String] = str_split(content, "\n")
let nl: Int = native_list_len(lines)
let pmap: [String] = native_list_empty()
let classmap: [String] = native_list_empty()
let li: Int = 0
while li < nl {
let line: String = native_list_get(lines, li)
let ll: Int = str_len(line)
let skip: Int = 0
if ll < 5 {
skip = 1
}
if skip == 0 {
let first: Int = str_char_code(line, 0)
if first == 35 {
skip = 1
}
}
if skip == 0 {
let f: [String] = str_split(line, "|")
let nf: Int = native_list_len(f)
if nf >= 12 {
let sym: String = native_list_get(f, 0)
let f1: String = native_list_get(f, 1)
let f2: String = native_list_get(f, 2)
let f3: String = native_list_get(f, 3)
let b1: String = native_list_get(f, 4)
let b2: String = native_list_get(f, 5)
let b3: String = native_list_get(f, 6)
let vo: String = native_list_get(f, 7)
let na: String = native_list_get(f, 8)
let du: String = native_list_get(f, 9)
let am: String = native_list_get(f, 10)
let cls: String = native_list_get(f, 11)
let cont: String = "phoneme " + sym + " | f1=" + f1 + " f2=" + f2 + " f3=" + f3 + " bw1=" + b1 + " bw2=" + b2 + " bw3=" + b3 + " voiced=" + vo + " nasal=" + na + " dur=" + du + " amp=" + am + " class=" + cls + " src=PetersonBarney1952-Hillenbrand1995"
let id: String = engram_node(cont, "Phoneme", 80)
pmap = native_list_append(pmap, sym)
pmap = native_list_append(pmap, cont)
// manifold edge: phoneme is_a class
let cid: String = sp_map_get(classmap, cls)
if str_eq(cid, "") {
cid = engram_node("phoneme-class " + cls + " src=acoustic-phonetics", "PhonemeClass", 80)
classmap = native_list_append(classmap, cls)
classmap = native_list_append(classmap, cid)
}
engram_connect(id, cid, 80, "is_a")
}
}
li = li + 1
}
return pmap
}
// -- INGEST: pronunciation lexicon source -> word nodes ----------------------
fn ingest_lexicon(path: String) -> [String] {
let content: String = fs_read(path)
let lines: [String] = str_split(content, "\n")
let nl: Int = native_list_len(lines)
let lmap: [String] = native_list_empty()
let li: Int = 0
while li < nl {
let line: String = native_list_get(lines, li)
let ll: Int = str_len(line)
let skip: Int = 0
if ll < 3 {
skip = 1
}
if skip == 0 {
let first: Int = str_char_code(line, 0)
if first == 35 {
skip = 1
}
}
if skip == 0 {
let f: [String] = str_split(line, "|")
let nf: Int = native_list_len(f)
if nf >= 2 {
let word: String = native_list_get(f, 0)
let seq: String = native_list_get(f, 1)
let id: String = engram_node("word " + word + " phonemes " + seq + " src=lexicon", "Pronunciation", 80)
lmap = native_list_append(lmap, word)
lmap = native_list_append(lmap, seq)
}
}
li = li + 1
}
return lmap
}
// -- READ geometry back from the engram (the render's afferent lookup) --------
// phon_geo(sym) -> [F1,F2,F3,B1,B2,B3,voiced,nasal,dur,amp], parsed from the
// ingested phoneme node's content. NO formant numbers live in this code.
fn phon_geo(pmap: [String], sym: String) -> [Int] {
let id: String = sp_map_get(pmap, sym)
if str_eq(id, "") {
id = sp_map_get(pmap, "AX")
}
let out: [Int] = native_list_empty()
if str_eq(id, "") {
let out = native_list_append(out, 500)
let out = native_list_append(out, 1500)
let out = native_list_append(out, 2500)
let out = native_list_append(out, 80)
let out = native_list_append(out, 100)
let out = native_list_append(out, 150)
let out = native_list_append(out, 1)
let out = native_list_append(out, 0)
let out = native_list_append(out, 80)
let out = native_list_append(out, 80)
return out
}
let j: String = id
let out = native_list_append(out, parse_uint_from(j, "f1="))
let out = native_list_append(out, parse_uint_from(j, "f2="))
let out = native_list_append(out, parse_uint_from(j, "f3="))
let out = native_list_append(out, parse_uint_from(j, "bw1="))
let out = native_list_append(out, parse_uint_from(j, "bw2="))
let out = native_list_append(out, parse_uint_from(j, "bw3="))
let out = native_list_append(out, parse_uint_from(j, "voiced="))
let out = native_list_append(out, parse_uint_from(j, "nasal="))
let out = native_list_append(out, parse_uint_from(j, "dur="))
let out = native_list_append(out, parse_uint_from(j, "amp="))
return out
}
// word -> phoneme codes, read from the ingested lexicon node.
fn word_phonemes(lmap: [String], word: String) -> [String] {
let id: String = sp_map_get(lmap, word)
if str_eq(id, "") {
let r: [String] = native_list_empty()
let r = native_list_append(r, "AX")
return r
}
return str_split(id, " ")
}
// realized text -> flat phoneme-code sequence (SIL between words + at ends).
fn text_phonemes(lmap: [String], text: String) -> [String] {
let words: [String] = str_split(text, " ")
let nw: Int = native_list_len(words)
let seq: [String] = native_list_empty()
let seq = native_list_append(seq, "SIL")
let wi: Int = 0
while wi < nw {
let raw: String = native_list_get(words, wi)
let w: String = clean_word(raw)
if str_eq(w, "") {
wi = wi + 1
} else {
let ph: [String] = word_phonemes(lmap, w)
let np: Int = native_list_len(ph)
let pi: Int = 0
while pi < np {
let code: String = native_list_get(ph, pi)
seq = native_list_append(seq, code)
pi = pi + 1
}
seq = native_list_append(seq, "SIL")
wi = wi + 1
}
}
return seq
}