// audio-surface.el - Native own-core additive-synthesis audio surface.
//
// The AUDIO efferent seam, native, no Python and no library. This renders real
// PCM .wav bytes from instrument SIGNATURES read from engram-sourced .sig data
// files (elp/faculty/sig/*.sig) - the partial amplitudes are NEVER literals in
// this source; they are parsed from the learned signature at run time. That is
// the whole proof: render-from-learned-signatures.
//
// EL has no float arithmetic operator (codegen emits raw int64 ops for + - * /
// on the shared 64-bit slot) and no float-arithmetic natives - so ALL synthesis
// math here is own-core INTEGER fixed-point. Angles use a quarter-wave sine
// table (scale 10000) from a fixed-point Taylor series; amplitudes are parsed to
// micro (scale 1e6) straight from the .sig text; frequencies are milliHz ints.
//
// Pipeline mirrors the two-stage projector (midi.py): plan_note(frame) reads a
// frame's meaning-geometry slot-map and derives (pitch, duration, amplitude);
// realize_audio SUPERPOSES the signature's partials (the compose op) and
// serialises RIFF/WAVE. Same frame -> midi OR audio.

// -- integer decimal + string helpers -----------------------------------------

fn str_to_int_el(s: String) -> Int {
    let n: Int = str_len(s)
    let i: Int = 0
    let v: Int = 0
    let neg: Bool = false
    while i < n {
        let c: Int = str_char_code(s, i)
        if c == 45 { let neg: Bool = true }
        if c >= 48 {
            if c < 58 {
                let v: Int = v * 10 + (c - 48)
            }
        }
        let i: Int = i + 1
    }
    if neg { return 0 - v }
    return v
}

fn parse_micro(s: String) -> Int {
    let dot: Int = str_index_of(s, ".")
    if dot < 0 {
        return str_to_int_el(s) * 1000000
    }
    let n: Int = str_len(s)
    let ipart: String = str_slice(s, 0, dot)
    let fpart: String = str_slice(s, dot + 1, n)
    let iv: Int = str_to_int_el(ipart)
    let fv: Int = 0
    let scale: Int = 100000
    let fn2: Int = str_len(fpart)
    let i: Int = 0
    while i < 6 {
        let d: Int = 0
        if i < fn2 {
            let d: Int = str_char_code(fpart, i) - 48
        }
        let fv: Int = fv + d * scale
        let scale: Int = scale / 10
        let i: Int = i + 1
    }
    return iv * 1000000 + fv
}

// -- signature (engram data file) loader ---------------------------------------

fn sig_load(path: String) -> [String] {
    let text: String = fs_read(path)
    return str_split(text, "\n")
}

fn sig_field(lines: [String], key: String) -> String {
    let pref: String = key + ": "
    let n: Int = native_list_len(lines)
    let plen: Int = str_len(pref)
    let i: Int = 0
    while i < n {
        let ln: String = native_list_get(lines, i)
        if str_starts_with(ln, pref) {
            return str_slice(ln, plen, str_len(ln))
        }
        let i: Int = i + 1
    }
    return ""
}

fn parse_micros(csv: String) -> [Int] {
    let parts: [String] = str_split(csv, ",")
    let n: Int = native_list_len(parts)
    let out: [Int] = native_list_empty()
    let i: Int = 0
    while i < n {
        let out: [Int] = native_list_append(out, parse_micro(native_list_get(parts, i)))
        let i: Int = i + 1
    }
    return out
}

// -- fixed-point sine (own-core, quarter-wave Taylor table, scale 10000) --------

fn sin_table() -> [Int] {
    let HP: Int = 1570796
    let t: [Int] = native_list_empty()
    let q: Int = 0
    while q < 257 {
        let x: Int = q * HP / 256
        let x2: Int = x * x / 1000000
        let x3: Int = x2 * x / 1000000
        let x5: Int = x3 * x2 / 1000000
        let x7: Int = x5 * x2 / 1000000
        let x9: Int = x7 * x2 / 1000000
        let s: Int = x - x3 / 6 + x5 / 120 - x7 / 5040 + x9 / 362880
        let t: [Int] = native_list_append(t, s / 100)
        let q: Int = q + 1
    }
    return t
}

fn sin_lookup(t: [Int], phase: Int) -> Int {
    let p: Int = phase % 1024
    if p < 0 { let p: Int = p + 1024 }
    let quad: Int = p / 256
    let r: Int = p % 256
    if quad == 0 { return native_list_get(t, r) }
    if quad == 1 { return native_list_get(t, 256 - r) }
    if quad == 2 { return 0 - native_list_get(t, r) }
    return 0 - native_list_get(t, 256 - r)
}

fn isqrt_int(n: Int) -> Int {
    if n <= 0 { return 0 }
    let x: Int = n
    let y: Int = (x + 1) / 2
    while y < x {
        let x: Int = y
        let y: Int = (x + n / x) / 2
    }
    return x
}

// freq_of_midi: equal-tempered frequency in milliHz. 440000 mHz at midi 69.
fn freq_of_midi(m: Int) -> Int {
    let f: Int = 440000
    if m > 69 {
        let k: Int = m - 69
        let i: Int = 0
        while i < k {
            let f: Int = f * 1059463 / 1000000
            let i: Int = i + 1
        }
        return f
    }
    if m < 69 {
        let k: Int = 69 - m
        let i: Int = 0
        while i < k {
            let f: Int = f * 1000000 / 1059463
            let i: Int = i + 1
        }
        return f
    }
    return f
}

// -- envelope (ADSR), scale 1000 -----------------------------------------------

fn adsr_env(i: Int, total: Int, atk_n: Int, dec_n: Int, sus_pm: Int, rel_n: Int) -> Int {
    if i < atk_n {
        if atk_n == 0 { return 1000 }
        return 1000 * i / atk_n
    }
    if i < atk_n + dec_n {
        if dec_n == 0 { return sus_pm }
        return 1000 - (1000 - sus_pm) * (i - atk_n) / dec_n
    }
    let rel_start: Int = total - rel_n
    if i < rel_start {
        return sus_pm
    }
    if rel_n == 0 { return 0 }
    let left: Int = total - i
    return sus_pm * left / rel_n
}

// -- note synthesis: SUPERPOSE the learned partials -> [Int] samples -----------
fn note_samples(freq_mHz: Int, dur_ms: Int, rate: Int, partials: [Int], sumP: Int, b_micro: Int, vib_rate: Int, vib_cents: Int, atk_ms: Int, dec_ms: Int, sus_pm: Int, rel_ms: Int, amp_pm: Int, table: [Int]) -> [Int] {
    let total: Int = dur_ms * rate / 1000
    let atk_n: Int = atk_ms * rate / 1000
    let dec_n: Int = dec_ms * rate / 1000
    let rel_n: Int = rel_ms * rate / 1000
    let np: Int = native_list_len(partials)
    let half_mhz: Int = rate * 1000 / 2
    let out: [Int] = native_list_empty()
    let i: Int = 0
    while i < total {
        let acc: Int = 0
        let k: Int = 0
        while k < np {
            let harm: Int = k + 1
            let amp_k: Int = native_list_get(partials, k)
            let factor: Int = 1000000
            if b_micro > 0 {
                let val: Int = 1000000 + b_micro * harm * harm
                let factor: Int = isqrt_int(val * 1000000)
            }
            let fn_mhz: Int = freq_mHz * harm
            let fn_mhz: Int = fn_mhz * factor / 1000000
            if vib_cents > 0 {
                if vib_rate > 0 {
                    let vphase: Int = i * vib_rate * 1024 / rate
                    let vs: Int = sin_lookup(table, vphase)
                    let vibf: Int = 1000000 + (vib_cents * vs * 833) / 10000
                    let fn_mhz: Int = fn_mhz * vibf / 1000000
                }
            }
            if fn_mhz <= half_mhz {
                let phase: Int = i * fn_mhz * 1024 / (rate * 1000)
                let sv: Int = sin_lookup(table, phase)
                let acc: Int = acc + sv * amp_k / 1000000
            }
            let k: Int = k + 1
        }
        let env: Int = adsr_env(i, total, atk_n, dec_n, sus_pm, rel_n)
        let s16: Int = acc * 2800000 / sumP
        let s16: Int = s16 * env / 1000
        let s16: Int = s16 * amp_pm / 1000
        if s16 > 32767 { let s16: Int = 32767 }
        if s16 < 0 - 32767 { let s16: Int = 0 - 32767 }
        let out: [Int] = native_list_append(out, s16)
        let i: Int = i + 1
    }
    return out
}

fn synth_from_sig(lines: [String], freq_mHz: Int, dur_ms: Int, amp_pm: Int, rate: Int, table: [Int]) -> [Int] {
    let partials: [Int] = parse_micros(sig_field(lines, "partials"))
    let np: Int = native_list_len(partials)
    let sumP: Int = 0
    let j: Int = 0
    while j < np {
        let pj: Int = native_list_get(partials, j)
        let sumP: Int = sumP + pj
        let j: Int = j + 1
    }
    if sumP <= 0 { let sumP: Int = 1000000 }
    let adsr: [String] = str_split(sig_field(lines, "adsr"), ",")
    let atk_ms: Int = parse_micro(native_list_get(adsr, 0)) / 1000
    let dec_ms: Int = parse_micro(native_list_get(adsr, 1)) / 1000
    let sus_pm: Int = parse_micro(native_list_get(adsr, 2)) / 1000
    let rel_ms: Int = parse_micro(native_list_get(adsr, 3)) / 1000
    let b_micro: Int = parse_micro(sig_field(lines, "inharmonicity_B"))
    let vib_rate: Int = str_to_int_el(sig_field(lines, "vibrato_rate_hz"))
    let vib_cents: Int = str_to_int_el(sig_field(lines, "vibrato_depth_cents"))
    return note_samples(freq_mHz, dur_ms, rate, partials, sumP, b_micro, vib_rate, vib_cents, atk_ms, dec_ms, sus_pm, rel_ms, amp_pm, table)
}

// -- byte-buffer helpers (own-core, no library) --------------------------------

fn put_tag(buf: String, pos: Int, s: String) -> String {
    let n: Int = str_len(s)
    let i: Int = 0
    while i < n {
        let buf: String = __str_set_char(buf, pos + i, str_char_code(s, i))
        let i: Int = i + 1
    }
    return buf
}

fn put_u32le(buf: String, pos: Int, v: Int) -> String {
    let buf: String = __str_set_char(buf, pos, v % 256)
    let buf: String = __str_set_char(buf, pos + 1, (v / 256) % 256)
    let buf: String = __str_set_char(buf, pos + 2, (v / 65536) % 256)
    let buf: String = __str_set_char(buf, pos + 3, (v / 16777216) % 256)
    return buf
}

fn put_u16le(buf: String, pos: Int, v: Int) -> String {
    let buf: String = __str_set_char(buf, pos, v % 256)
    let buf: String = __str_set_char(buf, pos + 1, (v / 256) % 256)
    return buf
}

// -- WAV serializer: own-core RIFF/WAVE, PCM mono 16-bit -----------------------

fn wav_write(path: String, samples: [Int], n: Int, rate: Int) -> Int {
    let data_len: Int = n * 2
    let total: Int = 44 + data_len
    let buf: String = __str_alloc(total)
    let buf: String = put_tag(buf, 0, "RIFF")
    let buf: String = put_u32le(buf, 4, 36 + data_len)
    let buf: String = put_tag(buf, 8, "WAVE")
    let buf: String = put_tag(buf, 12, "fmt ")
    let buf: String = put_u32le(buf, 16, 16)
    let buf: String = put_u16le(buf, 20, 1)
    let buf: String = put_u16le(buf, 22, 1)
    let buf: String = put_u32le(buf, 24, rate)
    let buf: String = put_u32le(buf, 28, rate * 2)
    let buf: String = put_u16le(buf, 32, 2)
    let buf: String = put_u16le(buf, 34, 16)
    let buf: String = put_tag(buf, 36, "data")
    let buf: String = put_u32le(buf, 40, data_len)
    let i: Int = 0
    while i < n {
        let v: Int = native_list_get(samples, i)
        if v < 0 { let v: Int = v + 65536 }
        let buf: String = __str_set_char(buf, 44 + i * 2, v % 256)
        let buf: String = __str_set_char(buf, 44 + i * 2 + 1, (v / 256) % 256)
        let i: Int = i + 1
    }
    let ok: Int = fs_write_bytes(path, buf, total)
    return ok
}

// -- plan: frame slot-map -> note atom (pitch, duration, amplitude) ------------

fn audio_frame(relation: String, polarity: String, confidence: String, importance: String, salience: String, subj_id: String) -> [String] {
    let f: [String] = native_list_empty()
    let f: [String] = native_list_append(f, "relation")
    let f: [String] = native_list_append(f, relation)
    let f: [String] = native_list_append(f, "polarity")
    let f: [String] = native_list_append(f, polarity)
    let f: [String] = native_list_append(f, "confidence")
    let f: [String] = native_list_append(f, confidence)
    let f: [String] = native_list_append(f, "importance")
    let f: [String] = native_list_append(f, importance)
    let f: [String] = native_list_append(f, "salience")
    let f: [String] = native_list_append(f, salience)
    let f: [String] = native_list_append(f, "subj_id")
    let f: [String] = native_list_append(f, subj_id)
    return f
}

fn degree_offset(deg: Int) -> Int {
    if deg == 0 { return 0 }
    if deg == 1 { return 2 }
    if deg == 2 { return 4 }
    if deg == 3 { return 5 }
    if deg == 4 { return 7 }
    if deg == 5 { return 9 }
    return 11
}

// returns [midi, dur_ms, amp_pm]
fn plan_note(frame: [String]) -> [Int] {
    let relation: String = surface_get(frame, "relation")
    let polarity: String = surface_get(frame, "polarity")
    let confidence: String = surface_get(frame, "confidence")
    let importance: String = surface_get(frame, "importance")
    let salience: String = surface_get(frame, "salience")
    let rn: Int = str_len(relation)
    let csum: Int = 0
    let i: Int = 0
    while i < rn {
        let cc: Int = str_char_code(relation, i)
        let csum: Int = csum + cc
        let i: Int = i + 1
    }
    let deg: Int = csum % 7
    let third: Int = 4
    if str_eq(polarity, "neg") { let third: Int = 3 }
    let sal_oct: Int = str_to_int_el(salience)
    let doff: Int = degree_offset(deg)
    let midi: Int = 60 + sal_oct * 12 + doff + third
    let conf_micro: Int = parse_micro(confidence)
    let dur_ms: Int = 200 + conf_micro / 1000
    let imp_micro: Int = parse_micro(importance)
    let amp_pm: Int = 400 + imp_micro / 2000
    let out: [Int] = native_list_empty()
    let out: [Int] = native_list_append(out, midi)
    let out: [Int] = native_list_append(out, dur_ms)
    let out: [Int] = native_list_append(out, amp_pm)
    return out
}

fn realize_audio(frames: [[String]], sig_lines: [String], path: String, rate: Int, table: [Int]) -> Int {
    let nf: Int = native_list_len(frames)
    let all: [Int] = native_list_empty()
    let count: Int = 0
    let fi: Int = 0
    while fi < nf {
        let frame: [String] = native_list_get(frames, fi)
        let plan: [Int] = plan_note(frame)
        let midi: Int = native_list_get(plan, 0)
        let dur_ms: Int = native_list_get(plan, 1)
        let amp_pm: Int = native_list_get(plan, 2)
        let freq: Int = freq_of_midi(midi)
        let note: [Int] = synth_from_sig(sig_lines, freq, dur_ms, amp_pm, rate, table)
        let nn: Int = native_list_len(note)
        let j: Int = 0
        while j < nn {
            let all: [Int] = native_list_append(all, native_list_get(note, j))
            let j: Int = j + 1
        }
        let count: Int = count + nn
        let fi: Int = fi + 1
    }
    let ok: Int = wav_write(path, all, count, rate)
    return count
}
