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).
53 lines
2 KiB
Python
53 lines
2 KiB
Python
"""
|
|
sensorium/audio/resample.py — deterministic polyphase FIR resampling (spec §3).
|
|
|
|
SciPy is intentionally NOT a dependency (it is absent from the runtime and
|
|
CLAUDE.md forbids broad infrastructure). This is a pure-numpy polyphase FIR
|
|
upsample→filter→downsample, equivalent in form to scipy.signal.resample_poly
|
|
with explicit FIR coefficients. The FIR taps are a pack artifact in PR-3
|
|
(`resample_fir_v1.npy`); this module only *applies* them, deterministically.
|
|
|
|
Replayability requirements (spec §3 / §7):
|
|
- odd-length symmetric FIR → zero-phase (group delay = (len-1)/2 samples).
|
|
- float64 internal compute; the caller casts to float32 at the boundary.
|
|
- same-rate input is an exact passthrough (no filtering, no drift).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from math import gcd
|
|
|
|
import numpy as np
|
|
|
|
|
|
def resample_poly(x: np.ndarray, up: int, down: int, fir: np.ndarray) -> np.ndarray:
|
|
"""Resample ``x`` by the rational factor ``up/down`` using explicit FIR taps.
|
|
|
|
The FIR must be a low-pass designed for the ``up`` insertion rate. An
|
|
odd-length symmetric FIR yields zero-phase output (the group delay is
|
|
removed by centering). Deterministic for fixed (x, up, down, fir).
|
|
"""
|
|
if up < 1 or down < 1:
|
|
raise ValueError(f"up/down must be >= 1, got up={up}, down={down}")
|
|
if fir.ndim != 1 or fir.size % 2 == 0:
|
|
raise ValueError("FIR must be a 1-D odd-length (symmetric) array")
|
|
|
|
g = gcd(up, down)
|
|
up, down = up // g, down // g
|
|
xf = np.asarray(x, dtype=np.float64)
|
|
|
|
# Upsample by zero-insertion.
|
|
upsampled = np.zeros(xf.size * up, dtype=np.float64)
|
|
upsampled[::up] = xf
|
|
|
|
# Zero-phase FIR via centered 'same' convolution, scaled by up to
|
|
# preserve amplitude through zero-insertion.
|
|
taps = np.asarray(fir, dtype=np.float64) * up
|
|
filtered = np.convolve(upsampled, taps, mode="same")
|
|
|
|
# Downsample by decimation.
|
|
return filtered[::down]
|
|
|
|
|
|
def needs_resample(sample_rate: int, target_sr: int) -> bool:
|
|
return sample_rate != target_sr
|