// organ.el — Neuron's I/O organ, in El. // // THE PRINCIPLE. El speaks. The engram stores geometry and does not speak. // Before this file the organ was a 939-line Swift program standing next to the // language (peripheral/src/periph.swift): Neuron's mouth and ears were a // separate binary, and "speak" meant "shell out to that binary, which shells // out to afplay." That is not a voice, it is a subprocess. The voice belongs to // the language and its runtime. // // THE SPLIT. Exactly two things here are not El, and they are the two things El // cannot express as arithmetic: // // the speaker — handing a buffer to the DAC and waiting for it to drain // the capture — asking the OS for samples off a mic or frames off a camera // // Those live in lang/runtime/el_audio_darwin.m and el_capture_darwin.m, as // their own translation units, declared in el_runtime.h. Everything ELSE that // the Swift did — WAV encode and decode, LPC autocorrelation, Levinson-Durbin, // formant extraction off the all-pole envelope, source-filter resynthesis, the // compact descriptors, the converse yield-or-hold decision — is arithmetic, and // arithmetic is El's. See organ_dsp.el for that half. // // WHERE THE VOICE COMES FROM. Not from this file, and not from a JSON manifest // on disk. A voice is GEOMETRY IN THE ENGRAM, and the organ goes and gets it by // asking the engram, the same way anything else asks the engram for anything: // a query against the graph, then read the numbers off the node that comes // back. organ_voice_fetch is that. The previous path, load_voice("...json"), // parsed a file — which quietly made the voice a build artifact instead of a // memory. If the region is not in the graph, the honest answer is an empty // result, not a default voice. // // WHAT THE ORGAN NEVER DOES. It never learns a word. Pronunciation, vocabulary // and phonemes are the language faculty's, already built as ingested geometry — // "the engram knows how to pronounce." The seam is synth_codes(codes, voice, // pmap): the codes and the phoneme map arrive from the language side as // geometry, and the organ's whole job is turning them into samples and getting // the samples out the speaker, plus the same trip in reverse for the senses. // // RAILS, all non-negotiable: // own-core — CoreAudio / AVFoundation / ImageIO, all shipped with the OS. // No cloud, no model, no heavy dependency. There is no network // call anywhere in the organ, by construction. // local-only — raw streams stay on the machine. What leaves a capture is a // DESCRIPTOR of a few dozen numbers, never the stream. // consent — two locks on the sensitive senses: a Neuron-level grant AND // the OS TCC permission. Camera and mic FAIL CLOSED without // both. The speaker is disclosed but not gated (see below). // disclosed — every device touch prints a [peripheral] line on stderr. // Nothing here is ever silent about being a device. // ── Disclosure ─────────────────────────────────────────────────────────────── // // stderr, not stdout: a program that announces "I am opening the microphone" on // stdout has corrupted its own output. And flushed immediately, so the line is // on the terminal BEFORE the device is touched — a disclosure that arrives // after the fact is a log, not a disclosure. fn organ_disclose(msg: String) -> Bool { eprintln(" [peripheral] " + msg) return true } // ── Consent, the Neuron-level lock ─────────────────────────────────────────── // // The OS has its own lock (TCC) and it is not enough on its own: TCC grants the // TERMINAL access to the microphone, once, more or less forever. That says the // user trusts the app. It does not say the user consents to THIS program // listening THIS time. So Neuron keeps its own grant, revocable, on the same // footing — and both must be open for a sensitive sense to work. // // Stored next to the organ rather than in the engram deliberately: consent must // be inspectable and revocable without a running graph, and a permission that // can only be revoked by the system it governs is not a permission. fn organ_consent_path() -> String { let home: String = env("PERIPH_HOME") if str_eq(home, "") { return "peripheral/.consent.json" } return home + "/.consent.json" } fn organ_consent_granted(device: String) -> Bool { let raw: String = fs_read(organ_consent_path()) if str_eq(raw, "") { return false } // A device is granted only on an explicit true. Anything unparseable, // missing or malformed reads as NOT granted — the failure direction for a // permission file is always closed. let key: String = "\"" + device + "\"" let at: Int = str_index_of(raw, key) if at < 0 { return false } let tail: String = str_slice(raw, at, str_len(raw)) let t: Int = str_index_of(tail, "true") let f: Int = str_index_of(tail, "false") if t < 0 { return false } if f < 0 { return true } // whichever token appears first after the key is this device's value if t < f { return true } return false } fn organ_consent_write(camera: Bool, mic: Bool) -> Bool { let c: String = "false" if camera { c = "true" } let m: String = "false" if mic { m = "true" } return fs_write(organ_consent_path(), "{\"camera\": " + c + ", \"mic\": " + m + "}\n") } fn organ_grant(device: String) -> Bool { let cam: Bool = organ_consent_granted("camera") let mic: Bool = organ_consent_granted("mic") if str_eq(device, "camera") { cam = true } if str_eq(device, "mic") { mic = true } let ok: Bool = organ_consent_write(cam, mic) organ_disclose("granted '" + device + "' (Neuron-level) — raw stream stays local, never egresses.") return ok } fn organ_revoke(device: String) -> Bool { let cam: Bool = organ_consent_granted("camera") let mic: Bool = organ_consent_granted("mic") if str_eq(device, "camera") { cam = false } if str_eq(device, "mic") { mic = false } let ok: Bool = organ_consent_write(cam, mic) organ_disclose("revoked '" + device + "' (Neuron-level).") return ok } fn organ_consent_status() -> String { let cam: String = "denied" if organ_consent_granted("camera") { cam = "granted" } let mic: String = "denied" if organ_consent_granted("mic") { mic = "granted" } return "camera=" + cam + " mic=" + mic } // Both locks, in order, with a disclosure for each outcome. Returns false and // says exactly which lock is shut — a refusal that does not say why is // indistinguishable from a bug. fn organ_may_listen() -> Bool { if organ_consent_granted("mic") == false { organ_disclose("CONSENT DENIED for 'mic' (Neuron-level). Run: organ grant mic") return false } if mic_available() == 0 { organ_disclose("CONSENT DENIED for 'mic' (OS/TCC), or no input device. Grant microphone access to this terminal in System Settings > Privacy.") return false } organ_disclose("consent OK (Neuron + OS) for 'mic' — local only, never egresses.") return true } fn organ_may_see() -> Bool { if organ_consent_granted("camera") == false { organ_disclose("CONSENT DENIED for 'camera' (Neuron-level). Run: organ grant camera") return false } if camera_available() == 0 { organ_disclose("CONSENT DENIED for 'camera' (OS/TCC), or no capture device. Grant camera access to this terminal in System Settings > Privacy.") return false } organ_disclose("consent OK (Neuron + OS) for 'camera' — local only, never egresses.") return true } // ── SPEAKER (efferent) ─────────────────────────────────────────────────────── // // Not consent-gated, and that is a deliberate asymmetry rather than an // oversight. The microphone and camera take information OFF the user without // them necessarily knowing; the speaker puts information INTO a room the user // is in, audibly, which is self-disclosing by its nature — you cannot secretly // speak aloud. So the speaker is DISCLOSED (every utterance announces itself on // stderr) but not gated. Gating it would mean Neuron needs permission to answer. fn organ_speak_samples(samples: [Int], sr: Int) -> Bool { let n: Int = native_list_len(samples) if n <= 0 { organ_disclose("SPEAKER: nothing to say (0 samples) — not touching the device.") return false } if speaker_available() == 0 { organ_disclose("SPEAKER: no audio output on this build (" + speaker_name() + ") — cannot speak.") return false } let secs: Int = n * 1000 / sr organ_disclose("SPEAKER: playing " + int_to_str(n) + " samples (" + int_to_str(secs) + " ms @ " + int_to_str(sr) + " Hz) ALOUD via " + speaker_name() + " (efferent).") let ok: Int = speaker_play_pcm16(samples, sr) if ok == 1 { organ_disclose("SPEAKER: done — Neuron spoke aloud.") return true } organ_disclose("SPEAKER: playback FAILED.") return false } fn organ_speak_wav(path: String) -> Bool { if speaker_available() == 0 { organ_disclose("SPEAKER: no audio output on this build — cannot speak.") return false } if fs_exists(path) == false { organ_disclose("SPEAKER: no such file: " + path) return false } organ_disclose("SPEAKER: playing '" + path + "' ALOUD via " + speaker_name() + " (efferent).") let ok: Int = speaker_play_wav(path) if ok == 1 { organ_disclose("SPEAKER: done — Neuron spoke aloud.") return true } organ_disclose("SPEAKER: playback FAILED.") return false } // ── The voice, fetched FROM THE ENGRAM ─────────────────────────────────────── // // This is the part that matters most and is easiest to get subtly wrong. A // voice is not a constant in code and it is not a JSON file next to the code — // it is a region of the graph, put there by having heard someone, and the organ // retrieves it the way anything retrieves a memory: by asking. // // The node content is the geometry, in the engram's own flat key=value form: // voice will | f0=137 f0_end=116 kf=1269 f1=500 f2=2093 f3=3531 ... // so the read is: query the graph, take the returned node, pull the numbers off // it. Nothing here opens a file. // // Returns [f0, f0_end, kf, f1, f2, f3], or an EMPTY list when the region is not // in the graph. Empty is the honest answer — a caller that gets no voice must // not be handed a plausible default and left unable to tell the difference // between "this is how they sound" and "I never heard them." // Read an unsigned integer that follows `key` in `s`. Stops at the first // non-digit, returns 0 when the key is absent. fn organ_int_after(s: String, key: String) -> Int { let at: Int = str_index_of(s, key) if at < 0 { return 0 } let i: Int = at + str_len(key) let n: Int = str_len(s) let v: Int = 0 let seen: Int = 0 while i < n { let c: Int = str_char_code(s, i) if c < 48 { i = n } else { if c > 57 { i = n } else { v = v * 10 + (c - 48) seen = seen + 1 i = i + 1 } } } if seen == 0 { return 0 } return v } // Ask the engram for a named voice region and read its geometry back. fn organ_voice_fetch(name: String) -> [Int] { let out: [Int] = native_list_empty() let marker: String = "voice " + name + " |" // The graph is asked by MEANING, not by id or by path. let hits: String = engram_search_json("voice " + name + " f0 formants", 12) let at: Int = str_index_of(hits, marker) if at < 0 { // Fall back to a scan of the resident graph before giving up: search is // geometric and a small graph may not rank the region first. let scan: String = engram_scan_nodes_json(500, 0) at = str_index_of(scan, marker) if at < 0 { organ_disclose("VOICE: no region for '" + name + "' in the engram — nothing to speak with.") return out } hits = scan } let win: String = str_slice(hits, at, at + 240) out = native_list_append(out, organ_int_after(win, "f0=")) out = native_list_append(out, organ_int_after(win, "f0_end=")) out = native_list_append(out, organ_int_after(win, "kf=")) out = native_list_append(out, organ_int_after(win, "f1=")) out = native_list_append(out, organ_int_after(win, "f2=")) out = native_list_append(out, organ_int_after(win, "f3=")) organ_disclose("VOICE: fetched '" + name + "' FROM THE ENGRAM — f0=" + int_to_str(native_list_get(out, 0)) + " f0_end=" + int_to_str(native_list_get(out, 1)) + " kf=" + int_to_str(native_list_get(out, 2)) + " f1=" + int_to_str(native_list_get(out, 3)) + " f2=" + int_to_str(native_list_get(out, 4)) + " f3=" + int_to_str(native_list_get(out, 5))) return out } // Put a measured voice INTO the engram as geometry. This is the afferent end of // the same wire: a voiceprint (organ_dsp.el's LPC analysis) becomes a node, and // from then on the voice is a memory rather than a measurement someone happened // to write down. `prov` carries the honesty: COARSE means one formant triple, no // coarticulation, no prosody — an impression, explicitly not a clone. fn organ_voice_ingest(name: String, f0: Int, f0_end: Int, kf: Int, f1: Int, f2: Int, f3: Int, src: String, prov: String) -> String { let hub: String = engram_node("voice-signature-set " + name + " grounding=measured src=" + src, "VoiceSet", 90) let body: String = "voice " + name + " | f0=" + int_to_str(f0) + " f0_end=" + int_to_str(f0_end) + " kf=" + int_to_str(kf) + " f1=" + int_to_str(f1) + " f2=" + int_to_str(f2) + " f3=" + int_to_str(f3) + " grounding=measured src=" + src + " prov=" + prov let vid: String = engram_node(body, "Voice", 90) engram_connect(hub, vid, 90, "has-signature") organ_disclose("VOICE: ingested '" + name + "' into the engram as geometry (node " + vid + ").") return vid } // Turn the fetched geometry into the voice slot-map the render consumes. Kept // separate from the fetch so the organ never invents a voice: if the fetch came // back empty this returns empty too, and the caller has to deal with it. // // The slot-map is built here rather than by calling the render's own // constructor, so the organ carries NO dependency on the language faculty's // modules — it only has to agree with them about a wire format, which is the // looser and more honest coupling. (The layout is the same key/value [String] // convention lang_get / surface_get / voice_get all read.) fn organ_voice_profile(name: String, g: [Int]) -> [String] { let r: [String] = native_list_empty() if native_list_len(g) < 6 { return r } r = native_list_append(r, "name") r = native_list_append(r, name) r = native_list_append(r, "f0") r = native_list_append(r, int_to_str(native_list_get(g, 0))) r = native_list_append(r, "f0_end") r = native_list_append(r, int_to_str(native_list_get(g, 1))) r = native_list_append(r, "kf") r = native_list_append(r, int_to_str(native_list_get(g, 2))) r = native_list_append(r, "dur") r = native_list_append(r, "1000") r = native_list_append(r, "tilt") r = native_list_append(r, "1000") r = native_list_append(r, "breath") r = native_list_append(r, "8") return r } // ── Scene geometry (afferent, camera) ──────────────────────────────────────── // // The image half of the afferent metabolism, and the same principle as the // audio descriptor: a frame is never handed on raw. The realizer returns a // small pixel grid; THIS computes the descriptor, in El, because averaging // pixels is arithmetic and arithmetic is not a device concern. // // Returns 15 numbers — [w, h, meanR, meanG, meanB, brightness_pm, and a 3x3 // luminance grid] — standing in for a multi-megapixel frame. The 3x3 grid is // the smallest thing that still says WHERE the light is, which is most of what // makes a scene comparable to another scene; a single brightness average would // make a lamp on the left indistinguishable from a lamp on the right. // // Luminance is Rec. 601 (0.299R + 0.587G + 0.114B), in integer per-mille, so // the descriptor is reproducible rather than subject to float drift. fn organ_image_descriptor() -> [Int] { let out: [Int] = native_list_empty() let frame: Any = camera_capture_rgb() if frame == 0 { return out } let w: Int = el_map_get(frame, "width") let h: Int = el_map_get(frame, "height") let gw: Int = el_map_get(frame, "grid_w") let gh: Int = el_map_get(frame, "grid_h") let px: [Int] = el_map_get(frame, "pixels") let np: Int = native_list_len(px) if np < 3 { return out } let count: Int = np / 3 let rsum: Int = 0 let gsum: Int = 0 let bsum: Int = 0 // 3x3 accumulators, row-major let cell: [Int] = native_list_empty() let cn: [Int] = native_list_empty() let z: Int = 0 while z < 9 { cell = native_list_append(cell, 0) cn = native_list_append(cn, 0) z = z + 1 } // El has no list-set, so the cells are summed into parallel scalars and // reassembled — nine explicit accumulators would be worse to read than one // pass per cell over a grid this small. let c0: Int = 0 let c1: Int = 0 let c2: Int = 0 let c3: Int = 0 let c4: Int = 0 let c5: Int = 0 let c6: Int = 0 let c7: Int = 0 let c8: Int = 0 let n0: Int = 0 let n1: Int = 0 let n2: Int = 0 let n3: Int = 0 let n4: Int = 0 let n5: Int = 0 let n6: Int = 0 let n7: Int = 0 let n8: Int = 0 let i: Int = 0 while i < count { let r: Int = native_list_get(px, i * 3) let g: Int = native_list_get(px, i * 3 + 1) let b: Int = native_list_get(px, i * 3 + 2) rsum = rsum + r gsum = gsum + g bsum = bsum + b let lum: Int = (299 * r + 587 * g + 114 * b) / 1000 let x: Int = i - (i / gw) * gw let y: Int = i / gw let cx: Int = x * 3 / gw let cy: Int = y * 3 / gh if cx > 2 { cx = 2 } if cy > 2 { cy = 2 } let idx: Int = cy * 3 + cx if idx == 0 { c0 = c0 + lum n0 = n0 + 1 } if idx == 1 { c1 = c1 + lum n1 = n1 + 1 } if idx == 2 { c2 = c2 + lum n2 = n2 + 1 } if idx == 3 { c3 = c3 + lum n3 = n3 + 1 } if idx == 4 { c4 = c4 + lum n4 = n4 + 1 } if idx == 5 { c5 = c5 + lum n5 = n5 + 1 } if idx == 6 { c6 = c6 + lum n6 = n6 + 1 } if idx == 7 { c7 = c7 + lum n7 = n7 + 1 } if idx == 8 { c8 = c8 + lum n8 = n8 + 1 } i = i + 1 } let rA: Int = rsum / count let gA: Int = gsum / count let bA: Int = bsum / count let bright: Int = (299 * rA + 587 * gA + 114 * bA) / 255 out = native_list_append(out, w) out = native_list_append(out, h) out = native_list_append(out, rA) out = native_list_append(out, gA) out = native_list_append(out, bA) out = native_list_append(out, bright) if n0 < 1 { n0 = 1 } if n1 < 1 { n1 = 1 } if n2 < 1 { n2 = 1 } if n3 < 1 { n3 = 1 } if n4 < 1 { n4 = 1 } if n5 < 1 { n5 = 1 } if n6 < 1 { n6 = 1 } if n7 < 1 { n7 = 1 } if n8 < 1 { n8 = 1 } out = native_list_append(out, c0 / n0) out = native_list_append(out, c1 / n1) out = native_list_append(out, c2 / n2) out = native_list_append(out, c3 / n3) out = native_list_append(out, c4 / n4) out = native_list_append(out, c5 / n5) out = native_list_append(out, c6 / n6) out = native_list_append(out, c7 / n7) out = native_list_append(out, c8 / n8) organ_disclose("FEAT(image): 15-number scene-geometry vs " + int_to_str(w * h * 3) + " pixel-channels — the descriptor travels, the frame does not.") return out } // ── Own-core tone ──────────────────────────────────────────────────────────── // // The smallest possible proof that the organ owns its medium end to end: a sine // with a gentle attack and release, computed here, played by us, no file and no // library anywhere in the path. fn organ_tone(hz: Int, ms: Int, sr: Int) -> [Int] { let n: Int = sr * ms / 1000 let out: [Int] = native_list_empty() let two_pi: Float = 6.283185307 let srf: Float = int_to_float(sr) let hzf: Float = int_to_float(hz) let i: Int = 0 // 20 ms of ramp at each end; a square-edged tone clicks, and a click is the // organ announcing that it does not understand envelopes. let ramp: Int = sr / 50 if ramp < 1 { ramp = 1 } while i < n { let t: Float = int_to_float(i) / srf let s: Float = math_sin(two_pi * hzf * t) let env: Int = 32767 if i < ramp { env = 32767 * i / ramp } let tail: Int = n - i if tail < ramp { env = 32767 * tail / ramp } let v: Int = float_to_int(s * 9000.0) * env / 32767 out = native_list_append(out, v) i = i + 1 } return out }