Grand Diomande Research · Full HTML Reader

Phase 1 – Engine Bring-up (Weeks 1-6)

Goal: Ship a macOS binary that owns the CoreAudio device, renders audio in a lock-free callback (no allocations once prepared), plays two deck buffers through crossfader + EQ + limiter, and produces real latency/jitter metrics. Everything else can be stubs.

Embodied Trajectory Systems research note experiment writeup candidate score 28 .md

Full Public Reader

Phase 1 – Engine Bring-up (Weeks 1-6)

Goal: Ship a macOS binary that owns the CoreAudio device, renders audio in a lock-free callback (no allocations once prepared), plays two deck buffers through crossfader + EQ + limiter, and produces real latency/jitter metrics. Everything else can be stubs.

---

Week 1 — Driver & Callback Skeleton

### 1.1 Audio I/O backend
- Add `cpal` dependency; create `DevicePicker` that negotiates `f32` stereo 48 kHz stream with the default output device.
- Implement `CoreAudioBackend::prepare()` to store stream config and device handles, `start()` to spawn the stream.
- Provide `LinuxBackend`/`WasapiBackend` stubs behind `cfg` (return `Unsupported` errors) to keep the crate building on other OSes.

### 1.2 Render callback contract
- Define `AudioBlock { channels, frames, data: *mut f32 }` and `AudioCallback` trait with `fn render(&mut self, AudioBlock)`.
- Wire the cpal stream so the callback simply forwards the mutable slice to `EngineCore::render()`.
- Configure QoS: set audio thread to `UserInteractive` priority via coreaudio APIs; pin control thread to another core.

### 1.3 Channel ring & double buffering
- Introduce `ChannelRing<RenderBatch>` (SPSC) using `rtrb`; producer pushes `RenderBatch { left: const f32, right: const f32, len }` per block.
- Consumer (audio thread) pops and copies into the device buffer; drop oldest batch if ring is full (increment `overruns`).
- Add `EngineTelemetry` struct containing `overruns`, `callback_ns`, `blocks_rendered`.

Deliverables: Binary opens default device and outputs silence; unit test verifies ring operations do not allocate after creation.

---

Week 2 — Memory Arena & Node Graph Skeleton

### 2.1 Graph arena
- Implement `GraphArena` with `Vec<f32>` backing store and `alloc_slice<T>` helper returning `&mut [T]` without heap growth after `prepare()`.
- Expose `GraphArena::prepare(block_size, sample_rate)` / `reset()` and verify usage via tests.

### 2.2 Node descriptors
- Create `NodeDescriptor` enum (`DeckPlayer`, `Crossfader`, `Eq3`, `Limiter`, `TestTone`).
- Build `GraphBuilder` that accepts descriptors + connections, computes topological order, and preallocates node contexts in the arena.

### 2.3 Process loop
- Implement `Node::process(ctx)` methods (initially just zero output buffers).
- `EngineCore::render()` walks nodes in order and writes to `OutputBus` slices.

Deliverables: Integration test verifying no heap allocations in `render()` (instrument using custom global allocator); CLI example printing arena usage.

---

Week 3 — Deck Players & Crossfader

### 3.1 Deck playback
- Implement `DeckPlayer` reading from `Arc<[f32]>` buffers with position cursor, looping flag, and `DeckCommand` ring (play/pause/seek).
- Add `AudioLoader` helper to decode WAV → f32 interleaved buffers (use `hound` or `lewton`).

### 3.2 Crossfade envelope
- Implement equal-power crossfader with `ParamSmoother` (linear or exponential ramp) to avoid zipper noise.
- Provide CLI to load two sample WAVs and sweep crossfader via keyboard input for manual QA.

### 3.3 Test tone
- Add `TestToneNode` generating sine for acceptance harness.

Deliverables: `cargo run -p audio-engine --example ab_sine` crossfading between two tones; unit test verifying crossfade output stays within ±0.5 dB of inputs.

---

Week 4 — EQ & Limiter

### 4.1 Per-deck EQ stub
- Add simple three-band shelving EQ using biquad filters; parameters smoothed with `ParamSmoother`.
- Store coefficients/state in arena; expose `set_eq(deck, band, gain_db)` API.

### 4.2 Master limiter
- Implement soft-knee limiter with small lookahead (e.g., 256 samples) and record gain reduction per block.
- Add `LimiterMetrics` to telemetry.

### 4.3 Monitoring hooks
- Publish telemetry to control thread via `ChannelRing<EngineTelemetry>`.

Deliverables: Example `deck_playback` loading two WAVs, crossfading through EQ + limiter with audible results; manual verification ensures limiter prevents clipping.

---

Week 5 — Control Thread Integration

### 5.1 Command queues
- Implement SPSC rings for `DeckCommand`, `ParamCommand`, and `TransportCommand`.
- Control thread drains command queues every block, updates node parameters, and pushes render batches.

### 5.2 Engine controller API
- Provide `EngineController` struct with methods: `load_deck`, `set_crossfader`, `set_eq`, `set_limiter`, `start`, `stop`, `set_gain`.
- Manage deck states (stopped → primed → playing) and ensure operations are O(1).

Deliverables: Integration test that loads two short buffers, sequences commands, and asserts ring never underflows; CLI `engine_cli` for manual crossfade testing.

---

Week 6 — Acceptance & Profiling

### 6.1 Latency harness
- Implement optional input stream capturing loopback audio; add `LatencyMeter` correlating click track to compute round-trip latency/jitter.
- Export JSON report with min/mean/max callback duration, round-trip latency, jitter.

### 6.2 Benchmark script
- Create `scripts/profile_audio_engine.sh` that runs the engine for 5 minutes, captures telemetry, and stores metrics under `logs/benchmarks/YYYYMMDD/`.

### 6.3 Documentation & QA
- Update `docs/phase/phase-1.md` with step-by-step instructions, CLI usage, known limitations (macOS only, no time-stretch yet).
- Review code for `unsafe` usage; ensure callback path is `#[inline(always)]` where appropriate.

Deliverables:
- Latency report demonstrating <10 ms round-trip, zero overruns during 5-minute soak.
- Callback telemetry plots (Plotly or similar) stored with benchmark logs.
- Sign-off checklist completed: driver init, node graph, command handling, telemetry, docs.

---

## Definition of Done
- Engine grabs CoreAudio, renders crossfading decks with EQ + limiter without allocations/locks inside callback.
- Latency harness verifies <10 ms round-trip and acceptable jitter.
- Engine telemetry (overruns, limiter GR, callback duration) exposed to higher layers.
- Documentation and CLI examples allow other contributors to reproduce Phase 1 setup in minutes.

Promotion Decision

Attach run IDs, datasets, metrics, and reproduction commands.

Source Anchor

projects/Documentation/02-projects/echelon/phases/phase-1.md

Detected Structure

Method · Evaluation · Figures · Code Anchors