Archived
c26b6aac82
El SDK CI - dev / build-and-test (pull_request) Failing after 4m18s
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.
571 lines
22 KiB
EmacsLisp
571 lines
22 KiB
EmacsLisp
// 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
|
|
}
|