b7e2c580a8
El SDK CI - dev / build-and-test (pull_request) Successful in 6m28s
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.
234 lines
8.5 KiB
EmacsLisp
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
|
|
}
|