PR-2 of ADR-0181. Lands the deterministic substrate only — no pack artifacts
(PR-3), no evals (PR-4), no Delta-CRDT wiring (PR-5), no teachers (PR-6).
Pipeline (spec §1): canonical signal → frame grid → acoustic lexer →
typed AudioIR → canonical event ordering → elliptic rotor lowering →
versor composition → AudioCompilationUnit (the future CRDT delta).
Modules (sensorium/audio/):
- types.py frozen AudioSignal/Token/Event/IR + AudioCompilationUnit.merge_key
- checksum.py layered sha256 chain (source→canonical→token→ir→manifest→projection)
- resample.py pure-numpy polyphase FIR (scipy absent; FIR taps are a PR-3 artifact)
- canonical.py mono/24kHz canonicalisation + provenance hashes
- frames.py 20ms/10ms deterministic frame grid (zero-padded tail)
- lexer.py quantized per-frame descriptors (energy/voicing/zcr/centroid/pYIN-style F0)
- parser.py runs → typed spans/events (pause/speech/prosody/turn/non-speech)
- operators.py elliptic-ONLY rotor registry; planes {6,7,8,10,11,13} square to -1
- compiler.py compile_events serialization barrier (ADR-0181 §2.1) + checksum chain
- trace.py trace-safe audio evidence (no PCM)
Correctness: v1 restricts to the six elliptic grade-2 planes (e_i e_j, i,j∈{1..4})
so every rotor and every composition is a unit versor — versor_condition < 1e-6
holds without weakening the threshold (CLAUDE.md §Non-Negotiable Field Invariant).
Non-elliptic blades (those touching e5) are rejected at OperatorSpec construction.
Tests (tests/test_audio_compiler.py, 13 passed): A-1 determinism, A-4 serialization
barrier order-sensitivity, A-5 versor condition, A-6 trace hygiene, IR-replay,
shape/dtype, elliptic-plane lawfulness. Smoke 67 + arch-invariants 40 green.
No core mutation: ingest/field/generate/vault/vocab untouched (ADR-0013).
unitize_versor is the only normalization, algebra-owned (CLAUDE.md §Normalization).
74 lines
2.7 KiB
Python
74 lines
2.7 KiB
Python
"""
|
|
sensorium/audio/canonical.py — canonical signal formation (spec §3).
|
|
|
|
Default: mono, 24 kHz, float32 internal-as-float64 compute. The original
|
|
source bytes are hashed for provenance (``source_sha256``); the canonical
|
|
float32 image is hashed as ``canonical_sha256``. Resampling, when needed, uses
|
|
the pinned polyphase FIR from the pack (PR-3); PR-2 supports the same-rate
|
|
passthrough path and resampling when explicit FIR taps are supplied.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import numpy as np
|
|
|
|
from sensorium.audio.checksum import sha256_array
|
|
from sensorium.audio.resample import needs_resample, resample_poly
|
|
from sensorium.audio.types import AudioSignal
|
|
|
|
CANONICAL_SAMPLE_RATE = 24_000
|
|
|
|
|
|
def to_mono(samples: np.ndarray) -> np.ndarray:
|
|
"""Deterministic mono downmix: average across channels if multi-channel.
|
|
|
|
Accepts (N,) mono, (N, C) or (C, N) interleaved/planar. Channel axis is
|
|
the smaller of the two dimensions (audio has more samples than channels).
|
|
"""
|
|
arr = np.asarray(samples, dtype=np.float64)
|
|
if arr.ndim == 1:
|
|
return arr
|
|
if arr.ndim != 2:
|
|
raise ValueError(f"expected 1-D or 2-D samples, got ndim={arr.ndim}")
|
|
channel_axis = 0 if arr.shape[0] < arr.shape[1] else 1
|
|
return arr.mean(axis=channel_axis)
|
|
|
|
|
|
def canonicalize(
|
|
samples: np.ndarray,
|
|
sample_rate: int,
|
|
*,
|
|
target_sr: int = CANONICAL_SAMPLE_RATE,
|
|
fir: np.ndarray | None = None,
|
|
start_ms: int = 0,
|
|
) -> AudioSignal:
|
|
"""Produce a canonical mono float32 ``AudioSignal`` with provenance hashes.
|
|
|
|
``source_sha256`` hashes the original input bytes exactly as received;
|
|
``canonical_sha256`` hashes the canonical float32 image. Resampling to
|
|
``target_sr`` requires explicit ``fir`` taps (the pinned pack artifact);
|
|
same-rate input is an exact passthrough.
|
|
"""
|
|
source_sha256 = sha256_array(np.asarray(samples, dtype=np.float32))
|
|
mono = to_mono(samples)
|
|
|
|
if needs_resample(sample_rate, target_sr):
|
|
if fir is None:
|
|
raise ValueError(
|
|
f"resampling {sample_rate}->{target_sr} requires explicit FIR taps "
|
|
"(pinned pack artifact, PR-3); none supplied"
|
|
)
|
|
from math import gcd
|
|
g = gcd(target_sr, sample_rate)
|
|
mono = resample_poly(mono, up=target_sr // g, down=sample_rate // g, fir=fir)
|
|
|
|
canonical = np.ascontiguousarray(mono, dtype=np.float32)
|
|
duration_ms = int(round(1000 * canonical.size / target_sr))
|
|
return AudioSignal(
|
|
samples=canonical,
|
|
sample_rate=target_sr,
|
|
start_ms=start_ms,
|
|
end_ms=start_ms + duration_ms,
|
|
source_sha256=source_sha256,
|
|
canonical_sha256=sha256_array(canonical),
|
|
)
|