feat(audio): multitrack stem recording — Stage 2 (wire into the mixer)

Wires MultitrackRecorder into the live audio path, behind a recording_mode.

- config: RecordingMode { Mixed, Multitrack, Both } + AppConfig.recording_mode
  (serde-default Mixed, back-compat); CoreCommand::SetRecordingMode, sent at
  app startup from config.
- multitrack.rs: mic now arrives async via push_mic into an internal FIFO,
  drained one frame per end_cycle (mirrors recorder.rs) so the mic track tracks
  the cycle clock; added dir() accessor. mic is a plain WavWriter now.
- core: parallel `multitrack` slot + `is_multitrack` fast-path gate (exactly one
  of the mixed/multitrack recorders is active). SetRecording start branches on
  mode: Mixed → single-file Recorder (unchanged); Multitrack/Both → a per-session
  dir, MultitrackRecorder, and registers everyone already in the room (named,
  silence-aligned from t=0). The mixer taps each peer's RAW frame (pre-volume/
  mute/limiter) into stems and writes peer stems + mix (Both) + end_cycle per
  cycle; the capture thread pushes mic to whichever recorder; PeerJoined adds a
  late joiner's stem track. stop_recording finalizes both.

No UI yet to pick the mode (Stage 3) — defaults to Mixed, so behaviour is
unchanged until then; set recording_mode in config.json to exercise stems.
168 lib tests, clippy --all-targets clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-14 00:04:49 -04:00
co-authored by Claude Opus 4.8
parent fde6f8a680
commit f6520b79f7
5 changed files with 218 additions and 40 deletions
+4 -1
View File
@@ -1,4 +1,4 @@
use crate::config::NetworkMode;
use crate::config::{NetworkMode, RecordingMode};
use crate::network::PeerState;
use iroh::EndpointId;
@@ -29,6 +29,9 @@ pub enum CoreCommand {
/// Start/stop recording the call to a local WAV (your mic + the incoming
/// mix). No-op start if already recording / not in a call.
SetRecording(bool),
/// Set what a recording captures (mixed / per-peer stems / both). Takes
/// effect on the next recording start. Sent at startup from config.
SetRecordingMode(RecordingMode),
/// Broadcast a room text-chat message. No-op when not in a call.
SendChat(String),
/// Set the pixelpass binary location (config override, empty = use `$PATH`).