Files
el/peripheral/src/organ_dsp.el
T
Neuron c26b6aac82
El SDK CI - dev / build-and-test (pull_request) Failing after 4m18s
organ: the rest of the peripheral moves into El
The speaker and the voice-fetch landed in the previous commit. This is the
remainder of the 939-line Swift program, ported, and the line it draws is
between DEVICE and ARITHMETIC rather than between languages.

Two things stay realizers, because they are the two things El cannot express
as arithmetic: handing a buffer to the DAC and waiting for it to drain
(el_audio_darwin.m), and asking the OS for samples off a mic or frames off a
camera (el_capture_darwin.m). Both are their own translation units declared
in el_runtime.h, never patches to el_runtime.c.

Everything else is El. WAV decode, LPC autocorrelation, Levinson-Durbin at
order 16, formant extraction off the all-pole envelope, source-filter
resynthesis, and the three descriptors are organ_dsp.el. Consent, disclosure
and the scene descriptor are organ.el. Barge-in, yield-or-hold, backchannel
and resume are organ_converse.el.

The organ never learns a word. Codes and phoneme geometry arrive from the
language side; the organ turns them into samples and gets the samples out the
speaker, and runs the same trip in reverse for the senses. No lexicon, no
grapheme-to-phoneme, by design.

Barge-in needed pause/resume and a real DAC position rather than a tick
counter, because "finish the buffer" is not barge-in and a queue holding
three buffers is a third of a second wrong about where it is. An injected
barge also had to fire once rather than stay true, which is otherwise a
livelock the moment a backchannel resumes.

Measured against the Swift on out/mic_room.wav: seconds, rms, peak, zcr,
centroid and F0 agree to every printed digit; formants F1-F5 and bandwidths
B1-B5 are identical. imitate cannot match bit-for-bit because the Swift
excites unvoiced frames with Double.random — two Swift runs correlate 0.957
with each other and El correlates 0.958 with Swift, so the port is as close
to the original as the original is to itself.

Verified end to end: consent fails closed on both locks, real mic capture
(16000 frames), real camera frame (1920x1080 -> 15 numbers), voiceprint,
imitate, hear-imitate, a voice learned by ear and fetched back out of the
engram, and all five converse paths with real audio. The binary contains
zero afplay/Swift strings and spawns no child process while speaking.
2026-08-16 16:42:41 -05:00

1020 lines
32 KiB
EmacsLisp

// organ_dsp.el the AFFERENT DSP organ, own-core, ported from peripheral/src/periph.swift.
//
// The mirror of speech.el: where speech.el RENDERS meaning out through a voice
// (efferent), this module HEARS it takes a raw 16-bit PCM RIFF/WAVE capture
// and metabolizes it into GEOMETRY: a compact 8-number audio descriptor, a
// voice-signature (F0 + formants F1-F5 by LPC), and an LPC analysis-resynthesis
// that speaks the heard voice back from its own signature.
//
// Everything here is float physics (math_sin/math_cos/math_sqrt), not the
// fixed-point integer path speech.el uses for synthesis the analysis side
// needs the dynamic range that autocorrelation and Levinson-Durbin demand.
//
// Binary I/O note: an El String truncates at the first NUL, so a WAV can never
// be read through fs_read(). We read it through fs_read_b64_chunk(), which
// hands back plain-ASCII base64 of a byte window, and decode that base64 HERE,
// in El, into a [Int] of byte values. Chunks are a multiple of 3 bytes so each
// base64 window decodes cleanly with no padding except the final one.
//
// Every function is prefixed dsp_ so nothing here can collide with speech.el
// (write_wav / wav_le16 / wav_le32 / sp_* / voice_* all live there and are NOT
// redefined). imitate returns [Int] samples hand them to speech.el's write_wav.
// ---------------------------------------------------------------------------
// Base64 -> bytes (own-core; the only way binary reaches El intact)
// ---------------------------------------------------------------------------
// Standard RFC 4648 alphabet A-Za-z0-9+/ -> 0..63. Padding '=' and any other
// character -> -1 (a sentinel; there is no exception handling in El).
fn dsp_b64_val(c: Int) -> Int {
if c >= 65 {
if c <= 90 {
return c - 65
}
}
if c >= 97 {
if c <= 122 {
return c - 71
}
}
if c >= 48 {
if c <= 57 {
return c + 4
}
}
if c == 43 {
return 62
}
if c == 47 {
return 63
}
return 0 - 1
}
// Read a whole file as a list of byte values 0..255. Empty list on failure.
fn dsp_read_bytes(path: String) -> [Int] {
let bytes: [Int] = native_list_empty()
let size: Int = fs_size(path)
if size <= 0 {
return bytes
}
let chunk: Int = 60000 // multiple of 3 -> no interior padding
let off: Int = 0
while off < size {
let s: String = fs_read_b64_chunk(path, off, chunk)
let sl: Int = str_len(s)
if sl < 4 {
return bytes
}
let i: Int = 0
while i + 3 < sl {
let c0: Int = dsp_b64_val(str_char_code(s, i))
let c1: Int = dsp_b64_val(str_char_code(s, i + 1))
let c2: Int = dsp_b64_val(str_char_code(s, i + 2))
let c3: Int = dsp_b64_val(str_char_code(s, i + 3))
if c0 < 0 {
return bytes
}
if c1 < 0 {
return bytes
}
let b0: Int = c0 * 4 + c1 / 16
bytes = native_list_append(bytes, b0)
if c2 >= 0 {
let lo1: Int = c1 - (c1 / 16) * 16
let b1: Int = lo1 * 16 + c2 / 4
bytes = native_list_append(bytes, b1)
if c3 >= 0 {
let lo2: Int = c2 - (c2 / 4) * 4
let b2: Int = lo2 * 64 + c3
bytes = native_list_append(bytes, b2)
}
}
i = i + 4
}
off = off + chunk
}
return bytes
}
fn dsp_rd16(b: [Int], o: Int) -> Int {
let b0: Int = native_list_get(b, o)
let b1: Int = native_list_get(b, o + 1)
return b0 + b1 * 256
}
fn dsp_rd32(b: [Int], o: Int) -> Int {
let b0: Int = native_list_get(b, o)
let b1: Int = native_list_get(b, o + 1)
let b2: Int = native_list_get(b, o + 2)
let b3: Int = native_list_get(b, o + 3)
return b0 + b1 * 256 + b2 * 65536 + b3 * 16777216
}
// Four bytes at o compared against a 4-char ASCII chunk id.
fn dsp_chunk_is(b: [Int], o: Int, id: String) -> Bool {
let k: Int = 0
while k < 4 {
let got: Int = native_list_get(b, o + k)
let want: Int = str_char_code(id, k)
if got != want {
return false
}
k = k + 1
}
return true
}
// ---------------------------------------------------------------------------
// readWavSamples 16-bit PCM RIFF/WAVE -> normalized samples.
// Walks chunks to find 'fmt ' and 'data', so JUNK/FLLR padding (which
// AVAudioRecorder emits) is stepped over rather than mistaken for audio.
// Channel 0 only if stereo.
//
// The returned list is PACKED: [ sr, ch, n, s0, s1, ... s(n-1) ] with the three
// header numbers carried as Floats (El has no tuples). Use dsp_wav_sr /
// dsp_wav_ch / dsp_wav_n / dsp_wav_pcm to open it. Empty list on failure.
// ---------------------------------------------------------------------------
fn dsp_read_wav(path: String) -> [Float] {
let out: [Float] = native_list_empty()
let d: [Int] = dsp_read_bytes(path)
let dn: Int = native_list_len(d)
if dn <= 44 {
return out
}
let ch: Int = 0
let sr: Int = 0
let bits: Int = 0
let o: Int = 12
while o + 8 <= dn {
let sz: Int = dsp_rd32(d, o + 4)
if dsp_chunk_is(d, o, "fmt ") {
if o + 24 <= dn {
ch = dsp_rd16(d, o + 10)
sr = dsp_rd32(d, o + 12)
bits = dsp_rd16(d, o + 22)
}
}
if dsp_chunk_is(d, o, "data") {
if bits != 16 {
return out
}
if ch <= 0 {
return out
}
// Faithful to periph.swift, including its `d.count - 1` bound and
// the `while i + 1 < end` test the last sample of a file whose
// data chunk runs to EOF is dropped there, so it is dropped here.
let start: Int = o + 8
let end: Int = start + sz
if end > dn - 1 {
end = dn - 1
}
let samples: [Float] = native_list_empty()
let step: Int = 2 * ch
let i: Int = start
let count: Int = 0
while i + 1 < end {
let v: Int = dsp_rd16(d, i)
if v >= 32768 {
v = v - 65536
}
let f: Float = int_to_float(v) / 32768.0
samples = native_list_append(samples, f)
count = count + 1
i = i + step
}
let srf: Float = int_to_float(sr)
let chf: Float = int_to_float(ch)
let nf: Float = int_to_float(count)
out = native_list_append(out, srf)
out = native_list_append(out, chf)
out = native_list_append(out, nf)
let j: Int = 0
while j < count {
let sv: Float = native_list_get(samples, j)
out = native_list_append(out, sv)
j = j + 1
}
return out
}
let adv: Int = 8 + sz + (sz - (sz / 2) * 2)
if adv <= 0 {
return out
}
o = o + adv
}
return out
}
fn dsp_wav_sr(w: [Float]) -> Int {
if native_list_len(w) < 3 {
return 0
}
let v: Float = native_list_get(w, 0)
return float_to_int(v)
}
fn dsp_wav_ch(w: [Float]) -> Int {
if native_list_len(w) < 3 {
return 0
}
let v: Float = native_list_get(w, 1)
return float_to_int(v)
}
fn dsp_wav_n(w: [Float]) -> Int {
if native_list_len(w) < 3 {
return 0
}
let v: Float = native_list_get(w, 2)
return float_to_int(v)
}
// The bare sample list, unpacked from the header-prefixed form.
fn dsp_wav_pcm(w: [Float]) -> [Float] {
let out: [Float] = native_list_empty()
let n: Int = dsp_wav_n(w)
let i: Int = 0
while i < n {
let v: Float = native_list_get(w, i + 3)
out = native_list_append(out, v)
i = i + 1
}
return out
}
// ---------------------------------------------------------------------------
// small float helpers (El has no unary minus on Float in every position, and
// no min/max builtin so they are written out)
// ---------------------------------------------------------------------------
fn dsp_fabs(v: Float) -> Float {
if v < 0.0 {
return 0.0 - v
}
return v
}
// ---------------------------------------------------------------------------
// computeAudio the compact audio descriptor.
// Returns the 8-number vector [seconds, sr, ch, rms, peak, zcr, centroid, f0].
// Empty list if the WAV cannot be read (no exceptions in El sentinels only).
// ---------------------------------------------------------------------------
fn dsp_compute_audio(path: String) -> [Float] {
let vec: [Float] = native_list_empty()
let w: [Float] = dsp_read_wav(path)
let n: Int = dsp_wav_n(w)
if n <= 0 {
return vec
}
let sr: Int = dsp_wav_sr(w)
let ch: Int = dsp_wav_ch(w)
let s: [Float] = dsp_wav_pcm(w)
let nf: Float = int_to_float(n)
let srf: Float = int_to_float(sr)
let seconds: Float = nf / srf
// energy / peak / zero crossings
let sumsq: Float = 0.0
let peak: Float = 0.0
let zc: Float = 0.0
let i: Int = 0
while i < n {
let v: Float = native_list_get(s, i)
sumsq = sumsq + v * v
let av: Float = dsp_fabs(v)
if av > peak {
peak = av
}
if i > 0 {
let pv: Float = native_list_get(s, i - 1)
let a: Bool = pv < 0.0
let b: Bool = v < 0.0
if a != b {
zc = zc + 1.0
}
}
i = i + 1
}
let rms: Float = math_sqrt(sumsq / nf)
let zcr: Float = zc / nf * srf // ~2*dominant freq for tonal
// Spectral centroid via a coarse 64-bin DFT on a centered 2048 window.
let ww: Int = 2048
if n < ww {
ww = n
}
let off: Int = (n - ww) / 2
if off < 0 {
off = 0
}
let two: Float = 2.0
let pi: Float = math_pi()
let num: Float = 0.0
let den: Float = 0.0
let bins: Int = 64
let binsf: Float = 128.0 // Double(2*bins)
let k: Int = 1
while k < bins {
let kf: Float = int_to_float(k)
let f: Float = kf * srf / binsf
let re: Float = 0.0
let im: Float = 0.0
let j: Int = 0
while j < ww {
let jf: Float = int_to_float(j)
let ang: Float = (0.0 - two) * pi * kf * jf / binsf
let xv: Float = native_list_get(s, off + j)
let cv: Float = math_cos(ang)
let sv: Float = math_sin(ang)
re = re + xv * cv
im = im + xv * sv
j = j + 1
}
let mag: Float = math_sqrt(re * re + im * im)
num = num + f * mag
den = den + mag
k = k + 1
}
let centroid: Float = 0.0
if den > 0.0 {
centroid = num / den
}
// F0 by autocorrelation over the plausible speech range 70-400 Hz.
let lag_min: Int = sr / 400
let lag_max: Int = sr / 70
if lag_max > n - 1 {
lag_max = n - 1
}
let bound: Int = off + ww
if bound > n {
bound = n
}
let best_lag: Int = 0
let best_corr: Float = 0.0
if lag_max > lag_min {
let lag: Int = lag_min
while lag <= lag_max {
let c: Float = 0.0
let p: Int = 0
while p + lag < bound {
let a1: Float = native_list_get(s, off + p)
let a2: Float = native_list_get(s, off + p + lag)
c = c + a1 * a2
p = p + 1
}
if c > best_corr {
best_corr = c
best_lag = lag
}
lag = lag + 1
}
}
let f0: Float = 0.0
if best_lag > 0 {
f0 = srf / int_to_float(best_lag)
}
vec = native_list_append(vec, seconds)
vec = native_list_append(vec, srf)
let chf: Float = int_to_float(ch)
vec = native_list_append(vec, chf)
vec = native_list_append(vec, rms)
vec = native_list_append(vec, peak)
vec = native_list_append(vec, zcr)
vec = native_list_append(vec, centroid)
vec = native_list_append(vec, f0)
return vec
}
// ---------------------------------------------------------------------------
// LPC core: hamming, autocorr, Levinson-Durbin, formant peak-pick, pitch.
// ---------------------------------------------------------------------------
fn dsp_hamming(x: [Float]) -> [Float] {
let n: Int = native_list_len(x)
if n < 2 {
return x
}
let out: [Float] = native_list_empty()
let pi: Float = math_pi()
let dn: Float = int_to_float(n - 1)
let i: Int = 0
while i < n {
let v: Float = native_list_get(x, i)
let ang: Float = 2.0 * pi * int_to_float(i) / dn
let cv: Float = math_cos(ang)
let wv: Float = 0.54 - 0.46 * cv
out = native_list_append(out, v * wv)
i = i + 1
}
return out
}
// r[lag] = sum_i x[i]*x[i-lag], lag = 0..p. Returns p+1 numbers.
fn dsp_autocorr(x: [Float], p: Int) -> [Float] {
let n: Int = native_list_len(x)
let r: [Float] = native_list_empty()
let lag: Int = 0
while lag <= p {
let acc: Float = 0.0
let i: Int = lag
while i < n {
let a: Float = native_list_get(x, i)
let b: Float = native_list_get(x, i - lag)
acc = acc + a * b
i = i + 1
}
r = native_list_append(r, acc)
lag = lag + 1
}
return r
}
// Levinson-Durbin -> LPC coeffs a[0..p] with A(z) = 1 + sum a[k] z^-k.
// Returns p+2 numbers: a[0..p] followed by the residual energy at index p+1.
fn dsp_levinson(r: [Float], p: Int) -> [Float] {
let a: [Float] = native_list_empty()
a = native_list_append(a, 1.0)
let z: Int = 1
while z <= p {
a = native_list_append(a, 0.0)
z = z + 1
}
let err: Float = native_list_get(r, 0)
if err <= 0.0 {
a = native_list_append(a, 0.0)
return a
}
let i: Int = 1
let stopped: Bool = false
while i <= p {
if stopped {
i = p + 1
} else {
let acc: Float = native_list_get(r, i)
if i > 1 {
let j: Int = 1
while j < i {
let aj: Float = native_list_get(a, j)
let rij: Float = native_list_get(r, i - j)
acc = acc + aj * rij
j = j + 1
}
}
let k: Float = (0.0 - acc) / err
// na = a, then na[i] = k, then na[j] = a[j] + k*a[i-j] for 1<=j<i
let na: [Float] = native_list_empty()
na = native_list_append(na, 1.0)
let m: Int = 1
while m <= p {
let v: Float = native_list_get(a, m)
if m == i {
v = k
}
if m < i {
let am: Float = native_list_get(a, m)
let aim: Float = native_list_get(a, i - m)
v = am + k * aim
}
na = native_list_append(na, v)
m = m + 1
}
a = na
err = err * (1.0 - k * k)
if err <= 0.0 {
stopped = true
}
i = i + 1
}
}
a = native_list_append(a, err)
return a
}
// Formant peaks from the LPC all-pole spectral envelope, 512 steps over 0..sr/2,
// kept in 150..5200 Hz, at most 5. Returns FLAT PAIRS [f1, bw1, f2, bw2, ...]
// with the crude -3 dB (peak/sqrt2) bandwidth estimate.
fn dsp_formants(a: [Float], sr: Int) -> [Float] {
let peaks: [Float] = native_list_empty()
let p: Int = native_list_len(a) - 1
if p < 1 {
return peaks
}
let steps: Int = 512
let stepsf: Float = 512.0
let srf: Float = int_to_float(sr)
let pi: Float = math_pi()
let mag: [Float] = native_list_empty()
let s: Int = 0
while s < steps {
let wq: Float = pi * int_to_float(s) / stepsf // 0..pi -> 0..sr/2
let re: Float = 0.0
let im: Float = 0.0
let k: Int = 0
while k <= p {
let ak: Float = native_list_get(a, k)
let ang: Float = wq * int_to_float(k)
let cv: Float = math_cos(ang)
let sv: Float = math_sin(ang)
re = re + ak * cv
im = im - ak * sv
k = k + 1
}
let d: Float = math_sqrt(re * re + im * im)
if d < 0.000000001 {
d = 0.000000001
}
mag = native_list_append(mag, 1.0 / d)
s = s + 1
}
let found: Int = 0
let t: Int = 1
while t < steps - 1 {
if found < 5 {
let m0: Float = native_list_get(mag, t - 1)
let m1: Float = native_list_get(mag, t)
let m2: Float = native_list_get(mag, t + 1)
let rise: Bool = m1 > m0
let fall: Bool = m1 >= m2
if rise {
if fall {
let f: Float = int_to_float(t) * srf / 2.0 / stepsf
if f > 150.0 {
if f < 5200.0 {
let thr: Float = m1 / 1.4142
let lo: Int = t
let scan_lo: Bool = true
while scan_lo {
if lo > 0 {
let mv: Float = native_list_get(mag, lo)
if mv > thr {
lo = lo - 1
} else {
scan_lo = false
}
} else {
scan_lo = false
}
}
let hi: Int = t
let scan_hi: Bool = true
while scan_hi {
if hi < steps - 1 {
let mv2: Float = native_list_get(mag, hi)
if mv2 > thr {
hi = hi + 1
} else {
scan_hi = false
}
} else {
scan_hi = false
}
}
let bw: Float = int_to_float(hi - lo) * srf / 2.0 / stepsf
peaks = native_list_append(peaks, f)
peaks = native_list_append(peaks, bw)
found = found + 1
}
}
}
}
}
t = t + 1
}
return peaks
}
// Autocorrelation pitch over 70-400 Hz with the 0.30 voicing threshold.
// Returns 0.0 for an unvoiced (or silent) frame.
fn dsp_pitch_of(frame: [Float], sr: Int) -> Float {
let n: Int = native_list_len(frame)
let lag_min: Int = sr / 400
let lag_max: Int = sr / 70
if lag_max > n - 1 {
lag_max = n - 1
}
if lag_max <= lag_min {
return 0.0
}
let r0: Float = 0.0
let i: Int = 0
while i < n {
let v: Float = native_list_get(frame, i)
r0 = r0 + v * v
i = i + 1
}
if r0 < 0.00001 {
return 0.0
}
let best_lag: Int = 0
let best: Float = 0.0
let lag: Int = lag_min
while lag <= lag_max {
let c: Float = 0.0
let j: Int = lag
while j < n {
let a: Float = native_list_get(frame, j)
let b: Float = native_list_get(frame, j - lag)
c = c + a * b
j = j + 1
}
if c > best {
best = c
best_lag = lag
}
lag = lag + 1
}
if best_lag > 0 {
let ratio: Float = best / r0
if ratio > 0.30 {
let srf: Float = int_to_float(sr)
return srf / int_to_float(best_lag)
}
}
return 0.0
}
// A copy of x[pos..pos+n) the El stand-in for Swift's Array(x[a..<b]).
fn dsp_slice(x: [Float], pos: Int, n: Int) -> [Float] {
let out: [Float] = native_list_empty()
let total: Int = native_list_len(x)
let i: Int = 0
while i < n {
if pos + i < total {
let v: Float = native_list_get(x, pos + i)
out = native_list_append(out, v)
}
i = i + 1
}
return out
}
// median == sorted()[count/2]. There is no list-set in El, so instead of
// sorting we find the value whose rank bracket contains index count/2
// numerically identical to the Swift expression, without a mutable buffer.
fn dsp_median(v: [Float]) -> Float {
let n: Int = native_list_len(v)
if n == 0 {
return 0.0
}
let target: Int = n / 2
let i: Int = 0
while i < n {
let x: Float = native_list_get(v, i)
let less: Int = 0
let eq: Int = 0
let j: Int = 0
while j < n {
let y: Float = native_list_get(v, j)
if y < x {
less = less + 1
}
if y == x {
eq = eq + 1
}
j = j + 1
}
if less <= target {
if target < less + eq {
return x
}
}
i = i + 1
}
return 0.0
}
fn dsp_min_of(v: [Float]) -> Float {
let n: Int = native_list_len(v)
if n == 0 {
return 0.0
}
let m: Float = native_list_get(v, 0)
let i: Int = 1
while i < n {
let x: Float = native_list_get(v, i)
if x < m {
m = x
}
i = i + 1
}
return m
}
fn dsp_max_of(v: [Float]) -> Float {
let n: Int = native_list_len(v)
if n == 0 {
return 0.0
}
let m: Float = native_list_get(v, 0)
let i: Int = 1
while i < n {
let x: Float = native_list_get(v, i)
if x > m {
m = x
}
i = i + 1
}
return m
}
// ---------------------------------------------------------------------------
// voiceprint the voice-signature: median F0 over voiced frames + the median
// of each formant F1..F5 (and its bandwidth), plus the F0 min/max range.
// FRAME = 400 (25 ms @ 16k), HOP = 160 (10 ms), LPC order 16.
//
// Returns PACKED: [ f0med, f0lo, f0hi, nf, f1, b1, f2, b2, ... ] where nf is
// how many formants were recovered (0..5). Empty list if unreadable.
// ---------------------------------------------------------------------------
fn dsp_voiceprint(path: String) -> [Float] {
let out: [Float] = native_list_empty()
let w: [Float] = dsp_read_wav(path)
let n: Int = dsp_wav_n(w)
let frame_len: Int = 400
let hop: Int = 160
let order: Int = 16
if n <= frame_len {
return out
}
let sr: Int = dsp_wav_sr(w)
let x: [Float] = dsp_wav_pcm(w)
let f0s: [Float] = native_list_empty()
// five formant banks + five bandwidth banks, flat lists each
let f1s: [Float] = native_list_empty()
let f2s: [Float] = native_list_empty()
let f3s: [Float] = native_list_empty()
let f4s: [Float] = native_list_empty()
let f5s: [Float] = native_list_empty()
let b1s: [Float] = native_list_empty()
let b2s: [Float] = native_list_empty()
let b3s: [Float] = native_list_empty()
let b4s: [Float] = native_list_empty()
let b5s: [Float] = native_list_empty()
let pos: Int = 0
while pos + frame_len <= n {
let raw: [Float] = dsp_slice(x, pos, frame_len)
let f0: Float = dsp_pitch_of(raw, sr)
if f0 > 0.0 {
f0s = native_list_append(f0s, f0)
let win: [Float] = dsp_hamming(raw)
let r: [Float] = dsp_autocorr(win, order)
let r0: Float = native_list_get(r, 0)
if r0 > 0.000001 {
let al: [Float] = dsp_levinson(r, order)
// strip the trailing residual energy: coeffs are a[0..order]
let a: [Float] = native_list_empty()
let ci: Int = 0
while ci <= order {
let av: Float = native_list_get(al, ci)
a = native_list_append(a, av)
ci = ci + 1
}
let fs: [Float] = dsp_formants(a, sr)
let nfs: Int = native_list_len(fs) / 2
if nfs > 0 {
let fv: Float = native_list_get(fs, 0)
let bv: Float = native_list_get(fs, 1)
f1s = native_list_append(f1s, fv)
b1s = native_list_append(b1s, bv)
}
if nfs > 1 {
let fv2: Float = native_list_get(fs, 2)
let bv2: Float = native_list_get(fs, 3)
f2s = native_list_append(f2s, fv2)
b2s = native_list_append(b2s, bv2)
}
if nfs > 2 {
let fv3: Float = native_list_get(fs, 4)
let bv3: Float = native_list_get(fs, 5)
f3s = native_list_append(f3s, fv3)
b3s = native_list_append(b3s, bv3)
}
if nfs > 3 {
let fv4: Float = native_list_get(fs, 6)
let bv4: Float = native_list_get(fs, 7)
f4s = native_list_append(f4s, fv4)
b4s = native_list_append(b4s, bv4)
}
if nfs > 4 {
let fv5: Float = native_list_get(fs, 8)
let bv5: Float = native_list_get(fs, 9)
f5s = native_list_append(f5s, fv5)
b5s = native_list_append(b5s, bv5)
}
}
}
pos = pos + hop
}
let f0med: Float = dsp_median(f0s)
let f0lo: Float = dsp_min_of(f0s)
let f0hi: Float = dsp_max_of(f0s)
out = native_list_append(out, f0med)
out = native_list_append(out, f0lo)
out = native_list_append(out, f0hi)
let nf: Int = 0
if native_list_len(f1s) > 0 {
nf = nf + 1
}
if native_list_len(f2s) > 0 {
nf = nf + 1
}
if native_list_len(f3s) > 0 {
nf = nf + 1
}
if native_list_len(f4s) > 0 {
nf = nf + 1
}
if native_list_len(f5s) > 0 {
nf = nf + 1
}
let nff: Float = int_to_float(nf)
out = native_list_append(out, nff)
if native_list_len(f1s) > 0 {
let m: Float = dsp_median(f1s)
let b: Float = dsp_median(b1s)
out = native_list_append(out, m)
out = native_list_append(out, b)
}
if native_list_len(f2s) > 0 {
let m2: Float = dsp_median(f2s)
let bb2: Float = dsp_median(b2s)
out = native_list_append(out, m2)
out = native_list_append(out, bb2)
}
if native_list_len(f3s) > 0 {
let m3: Float = dsp_median(f3s)
let bb3: Float = dsp_median(b3s)
out = native_list_append(out, m3)
out = native_list_append(out, bb3)
}
if native_list_len(f4s) > 0 {
let m4: Float = dsp_median(f4s)
let bb4: Float = dsp_median(b4s)
out = native_list_append(out, m4)
out = native_list_append(out, bb4)
}
if native_list_len(f5s) > 0 {
let m5: Float = dsp_median(f5s)
let bb5: Float = dsp_median(b5s)
out = native_list_append(out, m5)
out = native_list_append(out, bb5)
}
return out
}
// ---------------------------------------------------------------------------
// imitate LPC analysis-resynthesis. Per frame: autocorr + Levinson, gain =
// sqrt(residual energy), excitation = an energy-normalized glottal impulse
// train at F0 for voiced frames or white noise for unvoiced, run through the
// all-pole filter using the past-output state. The whole output is normalized
// to peak 0.9 and returned as int16 samples hand them to speech.el's
// write_wav(samples, sr, path).
// ---------------------------------------------------------------------------
fn dsp_imitate(path: String) -> [Int] {
let res: [Int] = native_list_empty()
let w: [Float] = dsp_read_wav(path)
let n: Int = dsp_wav_n(w)
let frame_len: Int = 400
let hop: Int = 160
let order: Int = 16
if n <= frame_len {
return res
}
let sr: Int = dsp_wav_sr(w)
let srf: Float = int_to_float(sr)
let x: [Float] = dsp_wav_pcm(w)
let out: [Float] = native_list_empty()
// past outputs, order deep
let state: [Float] = native_list_empty()
let si: Int = 0
while si < order {
state = native_list_append(state, 0.0)
si = si + 1
}
let phase: Float = 0.0
let last_f0: Float = 0.0
let nstate: Int = 22695 // LCG for the unvoiced source
let written: Int = 0
let pos: Int = 0
while pos + frame_len <= n {
let raw: [Float] = dsp_slice(x, pos, frame_len)
let win: [Float] = dsp_hamming(raw)
let r: [Float] = dsp_autocorr(win, order)
let f0: Float = dsp_pitch_of(raw, sr)
let r0: Float = native_list_get(r, 0)
if r0 < 0.0000001 {
// frame skipped in periph.swift -> those output samples stay zero
let z: Int = 0
while z < hop {
if written < n {
out = native_list_append(out, 0.0)
written = written + 1
}
z = z + 1
}
} else {
let al: [Float] = dsp_levinson(r, order)
let errv: Float = native_list_get(al, order + 1)
let ge: Float = errv
if ge < 0.0 {
ge = 0.0
}
let gain: Float = math_sqrt(ge)
let use_f0: Float = f0
if f0 <= 0.0 {
use_f0 = 0.0
if last_f0 > 0.0 {
use_f0 = last_f0
}
}
last_f0 = f0
let i: Int = 0
while i < hop {
if written < n {
let e: Float = 0.0
if use_f0 > 0.0 {
phase = phase + use_f0 / srf
if phase >= 1.0 {
phase = phase - 1.0
e = math_sqrt(srf / use_f0)
}
} else {
nstate = nstate * 1103515245 + 12345
nstate = nstate - (nstate / 2147483648) * 2147483648
if nstate < 0 {
nstate = 0 - nstate
}
let u: Float = int_to_float(nstate) / 2147483648.0
e = u * 2.0 - 1.0
}
let y: Float = gain * e
let k: Int = 1
while k <= order {
let ak: Float = native_list_get(al, k)
let sk: Float = native_list_get(state, k - 1)
y = y - ak * sk
k = k + 1
}
let ns: [Float] = native_list_empty()
ns = native_list_append(ns, y)
let m: Int = 0
while m < order - 1 {
let sv: Float = native_list_get(state, m)
ns = native_list_append(ns, sv)
m = m + 1
}
state = ns
out = native_list_append(out, y)
written = written + 1
}
i = i + 1
}
}
pos = pos + hop
}
// the tail past the last full frame stays silent, exactly as in periph.swift
while written < n {
out = native_list_append(out, 0.0)
written = written + 1
}
// normalize to peak 0.9, then to int16
let peak: Float = 0.0
let q: Int = 0
while q < n {
let v: Float = native_list_get(out, q)
let av: Float = dsp_fabs(v)
if av > peak {
peak = av
}
q = q + 1
}
let scale: Float = 1.0
if peak > 0.000000001 {
scale = 0.9 / peak
}
let t: Int = 0
while t < n {
let v2: Float = native_list_get(out, t)
let sv2: Float = v2 * scale * 32767.0
if sv2 > 32767.0 {
sv2 = 32767.0
}
if sv2 < 0.0 - 32767.0 {
sv2 = 0.0 - 32767.0
}
let iv: Int = float_to_int(sv2)
res = native_list_append(res, iv)
t = t + 1
}
return res
}