# Audio Compiler Spec — `audio_core_v1` **Companion to:** [ADR-0181](../adr/ADR-0181-audio-compiler-delta-crdt.md) **Status:** Proposed (PR-1 docs) **Scope:** the deterministic substrate (PR-2/PR-3) and its Delta-CRDT delta interface (PR-5). This spec fixes the typed intermediate representation, the operator/manifest format, the numeric determinism rules, and the `AudioCompilationUnit` → Delta-CRDT delta contract. It is implementation-facing; the *why* lives in ADR-0181, the *acceptance* lives in the eval plan. --- ## 1. Two-clock architecture A low-level **acoustic clock** measures signal facts; a higher-level **auditory grammar clock** emits typed events. The primary path is fully deterministic; learned systems are confined to auxiliary evidence lanes (PR-6). ```mermaid flowchart LR A[Waveform bytes / live stream] --> B[Canonicalizer
mono + fixed SR + checksums] B --> C[Frame grid
20 ms window / 10 ms hop] C --> D[Acoustic lexer
energy, voicing, onset,
pitch candidates, spectral bands, pauses] D --> E[Typed AudioIR parser
speech/pause spans, prosody arcs,
turn/overlap events, non-speech atoms, anchors] E --> F[Canonical ordering
quantization + stable serialization] F --> G[Operator registry
pack manifest + blade aliases + theta rules] G --> H[Rotor lowering] H --> I[Versor composition
unitize_versor + versor_condition] I --> J["(32,) float32 — one AudioCompilationUnit"] E --> K[Audio evidence trace
hashes, teacher provenance, pack IDs] J --> L[Thread-local arena
ADR-0180 §2.1] L --> M[Semilattice merge
keyed by content-addressed sha] ``` ## 2. Typed AudioIR The IR is built from **typed spans and events**, never from raw frames or mel bins. Transcript anchors may exist only as auxiliary content hypotheses, never as the sole meaning of the audio. ```python from __future__ import annotations from dataclasses import dataclass from typing import Literal import numpy as np @dataclass(frozen=True, slots=True) class AudioSignal: samples: np.ndarray # canonical mono float32 sample_rate: int # 24_000 start_ms: int end_ms: int source_sha256: str canonical_sha256: str @dataclass(frozen=True, slots=True) class PitchCandidate: cents_q: int # quantized cents prob_q: int # 0..255 @dataclass(frozen=True, slots=True) class AudioToken: kind: Literal[ "silence", "voiced", "unvoiced", "onset", "energy_bin", "pitch_candidates", "spectral_bin", ] start_hop: int end_hop: int value_q: tuple[int, ...] # canonical quantized payload @dataclass(frozen=True, slots=True) class AuditoryEvent: event_type: str start_hop: int end_hop: int attrs: tuple[tuple[str, int | str], ...] evidence_ids: tuple[str, ...] @dataclass(frozen=True, slots=True) class AudioIR: speech_spans: tuple[AuditoryEvent, ...] pause_spans: tuple[AuditoryEvent, ...] prosody_arcs: tuple[AuditoryEvent, ...] turn_events: tuple[AuditoryEvent, ...] non_speech_events: tuple[AuditoryEvent, ...] content_anchors: tuple[AuditoryEvent, ...] ir_sha256: str ``` ### 2.1 The compilation unit (the CRDT delta) ```python @dataclass(frozen=True, slots=True) class AudioCompilationUnit: canonical_sha256: str ir_sha256: str pack_id: str pack_manifest_sha256: str projection_sha256: str versor: np.ndarray # (32,) float32 versor_condition: float @property def merge_key(self) -> tuple[str, str, str]: # ADR-0181 §2.2 — content-addressed CRDT merge / dedup key. return (self.canonical_sha256, self.ir_sha256, self.projection_sha256) ``` `AudioCompilationUnit` is the single object the audio adapter writes into its thread-local arena (ADR-0180 §2.1). It carries no PCM (ADR-0181 §3.1 / ADR-0180 §1.5.5). ## 3. Canonical signal formation - Internal processing: **mono, 24 kHz, float**; original-source bytes preserved separately for provenance; a derived 16 kHz stream is produced **only** for teacher ASR (PR-6). - Resampling: **pinned polyphase FIR** (SciPy `resample_poly` semantics — zero-phase, odd-length symmetric filter). The FIR coefficients are generated **once**, stored as a pack artifact (`resample_fir_v1.npy`), and checksummed in the manifest. The runtime never relies on library defaults. ## 4. Acoustic lexer Operates on **measured facts**, not semantic guesses. Default frame 20 ms / hop 10 ms. Each frame yields quantized descriptors: RMS/log-energy bin, voiced/unvoiced flag, candidate F0 set (pYIN-style: multiple candidates with probabilities **before** Viterbi smoothing), onset strength bin, coarse spectral centroid/tilt bin, zero-crossing regime, pause classification. ## 5. Parser → typed events Promotes lexer output into typed spans/events. Preserves the distinction between "No.", "No?", shouted "No!", whispered "no", and silent hesitation. Non-speech atoms (laughter, alarm, impact, music, broadband noise) are first-class; "chaotic noise" is the fallback only when a more specific parse is impossible. ## 6. Operator registry (pack-local blade aliases) Because the `(32,)` boundary is fixed but no canonical *semantic* blade map is exposed, v1 uses **pack-local, versioned, checksummed blade aliases**. v1 uses **elliptic bivector operators only** (square = −1), so every rotor uses the numerically well-behaved `R = cos(θ/2) + B·sin(θ/2)`. Hyperbolic/boost-like operators are deferred. | Auditory atom family | Measured source | Alias | Default blade index | Theta rule | |---|---|---|---|---| | Speech present | voiced frames / harmonic ratio | `B_SPEECH` | 6 | `q(base + g1·voiced_ratio_q)` | | Short pause | pause duration | `B_PAUSE_SHORT` | 7 | `q(base + g2·dur_hops)` | | Long pause | pause duration | `B_PAUSE_LONG` | 8 | `q(base + g3·dur_hops)` | | Rising final contour | final F0 slope | `B_PITCH_RISE` | 9 | `q(base + g4·slope_q)` | | Falling final contour | final F0 slope | `B_PITCH_FALL` | 10 | `q(base + g5·abs(slope_q))` | | Emphasis / force | energy delta | `B_EMPHASIS` | 11 | `q(base + g6·delta_db_q)` | | Hesitation / uncertainty | filled pause + low-conf contour | `B_HESITATION` | 12 | `q(base + g7·hesitation_q)` | | Turn boundary | silent gap + local reset | `B_TURN` | 13 | `q(base + g8·boundary_q)` | | Overlap / interruption | simultaneous speech / abrupt cut | `B_OVERLAP` | 14 | `q(base + g9·overlap_q)` | | Alert-like event | salience / alarm morphology | `B_ALERT` | 15 | `q(base + g10·salience_q)` | | Laughter | periodic burst pattern | `B_LAUGH` | 16 | `q(base + g11·laugh_q)` | | Cry / distress | voicing + modulation profile | `B_DISTRESS` | 17 | `q(base + g12·distress_q)` | | Music / tonal bed | stable harmonic bed | `B_MUSIC` | 18 | `q(base + g13·tonal_q)` | | Chaotic broadband noise | flat/noisy spectrum | `B_NOISE` | 19 | `q(base + g14·noise_q)` | Indices are **reasonable defaults, not metaphysical claims** about Cl(4,1). The contract is that the mapping is explicit, versioned, checksummed, and frozen in the manifest. `B_OVERLAP` and `B_TURN` are the atoms that motivate per-stream arenas in ADR-0181 §2.3. ### 6.1 Minimal manifest (`packs/audio/audio_core_v1/manifest.toml`) ```toml pack_id = "audio_core_v1" modality = "audio" cl41_dim = 32 compiler_version = "0.1.0" basis_version = "audio-basis-v1" [canonical] sample_rate = 24000 channels = 1 frame_ms = 20 hop_ms = 10 output_dtype = "float32" internal_dtype = "float64" [resampling] algorithm = "polyphase_fir" fir_path = "resample_fir_v1.npy" fir_sha256 = "sha256:REPLACE_ME" padtype = "constant" cval = 0.0 [gating] gate_engaged = false checksum_verified = false versor_condition_max = 1.0e-6 [ordering] event_precedence = ["channel", "pause", "speech", "prosody", "turn", "non_speech", "content_anchor"] ``` ### 6.2 Operator row (`operators.jsonl`) ```json { "operator_id": "audio.prosody.question_contour.v1", "event_type": "prosody.question_contour", "blade_alias": "B_PITCH_RISE", "blade_index": 9, "rotor_kind": "elliptic", "base_theta_q": 64, "gain_rules": {"slope_q": 3, "final_energy_q": 1, "confidence_q": 1}, "theta_clip_q": 384, "version": "1" } ``` ## 7. Numeric determinism Rule: **quantize before semantics, normalize after composition.** Raw float measurements are too unstable to hash. Quantization regime (frozen in manifest): boundaries in hop units, log energy in 1 dB bins, F0 in 25-cent bins, pitch slope in coarse cents-per-100 ms bins, spectral shape in fixed ordinal bins, all confidences in uint8. After quantization, compute in float64, compose sparse rotors in canonical order, call algebra-owned `unitize_versor`, cast to float32 **only** at the output boundary. ```python import math import numpy as np EPS = 1e-12 def quantize_theta(theta: float, step: float = 1.0 / 1024.0) -> float: return round(theta / step) * step def build_elliptic_rotor(blade_index: int, theta: float) -> np.ndarray: out = np.zeros(32, dtype=np.float64) half = quantize_theta(theta) / 2.0 out[0] = math.cos(half) out[blade_index] = math.sin(half) return out def compile_events(events, registry, geometric_product, unitize_versor, versor_condition): # SERIALIZATION BARRIER (ADR-0181 §2.1): in-chunk composition is order-sensitive, # single-threaded, canonical order. The substrate never parallelizes this loop. v = np.zeros(32, dtype=np.float64) v[0] = 1.0 for ev in events: # must already be in canonical order spec = registry[ev.event_type] theta = spec.theta_from_event(ev) # deterministic, quantized inputs only r = build_elliptic_rotor(spec.blade_index, theta) v = geometric_product(v, r) v = unitize_versor(v) if versor_condition(v) >= 1e-6: raise ValueError("audio compilation failed versor check") return v.astype(np.float32) ``` `geometric_product`, `unitize_versor`, `versor_condition` are imported from `algebra/` — the audio compiler adds **no** new normalization function. ## 8. Repo-facing adapter (`sensorium/adapters/audio.py`) ```python from __future__ import annotations from dataclasses import dataclass import numpy as np @dataclass(frozen=True, slots=True) class AudioProjectionHead: compiler: "AudioCompiler" modality = ... # Modality.AUDIO @property def embedding_dim(self) -> int: return 32 def project(self, signal: "AudioSignal") -> np.ndarray: unit = self.compiler.compile(signal) out = unit.versor if out.shape != (32,): raise ValueError(f"expected (32,), got {out.shape}") if out.dtype != np.float32: raise TypeError(f"expected float32, got {out.dtype}") return out def project_batch(self, signals: list["AudioSignal"]) -> np.ndarray: return np.stack([self.project(s) for s in signals], axis=0) def verify_unitarity(self, signal: "AudioSignal") -> bool: return self.compiler.compile(signal).versor_condition < 1e-6 ``` The adapter is thin and pack-governed; it satisfies the `ProjectionHead` protocol in `sensorium/protocol.py` and is mounted as a `ModalityPack(modality_type=Modality.AUDIO, gate_engaged=False)` until the eval gates pass. ## 9. Delta-CRDT delta interface (PR-5) The audio adapter **never** writes the global `epistemic_state` (ADR-0180 §2.1). Instead: 1. `compile()` produces one `AudioCompilationUnit` per canonical chunk (the serialization barrier of §7 runs here). 2. The unit is written lock-free into the adapter's **thread-local arena**. Concurrent streams (overlap/interruption) each have their own arena (ADR-0181 §2.3). 3. The **Merge Kernel** (ADR-0180 §2.2, an explicitly-mounted component, not a daemon — ADR-0180 §1.5.5) folds pending units into the global Vault ordered by `unit.merge_key`. Duplicate keys are deduplicated (idempotence). 4. The kernel surfaces its pending-delta count in `TurnEvent` for replay evidence (ADR-0180 §1.5.5). The per-chunk Vault contribution is `(versor, provenance)` where provenance = `{merge_key, pack_id, pack_manifest_sha256}` — content-addressed, no PCM. ## 10. File plan (PR-2 … PR-6) ```text sensorium/audio/{__init__,types,canonical,checksum,resample,frames,lexer,parser,operators,compiler,trace,fixtures,teachers}.py sensorium/adapters/audio.py packs/audio/audio_core_v1/{manifest.toml,basis_map.json,operators.jsonl,atoms.jsonl,prototypes.jsonl,resample_fir_v1.npy,checksums.json} tests/test_audio_{signal,resample,lexer,parser,compiler,pack_manifest,sensorium_mount,trace,crdt_delta}.py evals/audio_sensorium/{fixtures/*.wav,manifest.json,expected_ir.jsonl,expected_projection_hashes.json} ```