# Audio Capture / Playback Spike

Chanora proof-of-concept. **Not product code.**

| Field | Value |
|---|---|
| PoC name | `audio-capture-playback-spike` |
| PoC plan | [`docs/architecture/proof-of-concept-plan.md`](../../docs/architecture/proof-of-concept-plan.md) §2 |
| Purpose | Prove platform audio capture + playback behaviour |
| Exit criterion (plan) | "Capture/playback works on at least one desktop and one mobile target" |
| Authority | DEC-011 (platform-native first), audio family in SysDes / SRS |

## Honest status

**Partial pass.**

- **Linux desktop**: verified. cpal opens both the default input and
  the default output device, capture streams build (or report a typed
  error when no hardware-backed input is available), and playback
  drives a full PCM stream end-to-end through the platform audio
  stack (ALSA → PipeWire on this host).
- **Mobile (Android / iOS)**: **not verified**. cpal supports Oboe
  (Android) and AVAudioEngine (iOS) but this PoC has not been built
  or run on either. The exit criterion's mobile half remains an open
  item for a follow-up spike — see "Gaps" below.

## What it proves

- `AudioCapture::record_to_wav(path, duration)` — opens the default
  input device, reads frames in any supported sample format, down-mixes
  to mono, writes 16-bit-PCM WAV.
- `AudioPlayback::play_wav(path)` — opens the default output device,
  picks a stream config matching the file's sample rate, blocks until
  the file drains.
- `synth_sine_wav` — produces a deterministic 440 Hz sine WAV so the
  playback path is exercisable on hosts without a usable input device.
- A typed `AudioError` covering the practical failure modes (no
  device, config mismatch, stream-build failure, WAV I/O, unsupported
  format). Production code (`chanora_audio`) will widen this when DSP
  + Opus + jitter buffer surfaces land.
- Headless-friendly behaviour: if capture cannot build a stream, the
  CLI falls back to synth and the playback path is still exercised.
  This is the same shape production code would use for a
  "device test" UI.

## Layout

```text
audio-capture-playback-spike/
  src/
    lib.rs               # crate root + re-exports
    capture.rs           # AudioCapture, AudioError, sample-format adapters
    playback.rs          # AudioPlayback
    synth.rs             # synth_sine_wav (deterministic test signal)
    main.rs              # audio-capture-playback-cli
  tests/
    audio.rs             # 2 tests
  Cargo.toml
```

## Reproduce (Linux desktop)

Requires Rust stable, ALSA dev headers (cpal builds against ALSA on
Linux), and a working audio stack (PipeWire / Pulse / pure ALSA).

```bash
cargo test
cargo run --bin audio-capture-playback-cli -- roundtrip
```

CLI modes:

| Mode | What it does |
|---|---|
| `capture` | Records 1 s of audio from the default input device to `${TMPDIR}/chanora_audio_spike_capture.wav`. |
| `synth` | Writes a 440 Hz / 500 ms sine wave to `${TMPDIR}/chanora_audio_spike_synth.wav`. |
| `playback` | Plays the synth WAV through the default output device. |
| `roundtrip` (default) | Tries `capture`; on any failure, falls back to `synth`; then `playback`. |

### PipeWire-only Linux hosts

On hosts where the only audio server is PipeWire (no `pulseaudio`
package), `cpal` still uses ALSA underneath and needs a
`pcm.!default` alias to the PipeWire PCM plugin. The package
`pipewire-alsa` (or your distro equivalent) plus a one-line
`~/.asoundrc` is the standard fix:

```text
pcm.!default { type pipewire }
ctl.!default { type pipewire }
```

## Gaps (the mobile half of the exit criterion)

| Target | What's missing | Notes |
|---|---|---|
| Android | Build & run cpal-oboe path on an emulator or device | Needs NDK + Gradle wiring + emulator. cpal supports it; this spike has not built it. |
| iOS | Build & run cpal-AVAudioEngine path on a device | Needs macOS + Xcode + a developer account. |

These should be closed by a follow-up spike (or by promotion into the
product `chanora_audio` crate scaffold) before the audio half of the
PoC matrix is considered complete.

## Scope boundaries

- **No DSP.** HPF / NS / AEC / AGC / mixing / Opus encode-decode /
  jitter buffer all belong to `chanora_audio` and are out of scope.
- **No bit-exact loopback assertion.** This PoC verifies *that* a
  capture stream can be opened and *that* a playback stream can drive
  a file to completion. It does not verify capture-to-playback signal
  integrity.
- **No latency measurement.**
- **No device-permission flows.** Mobile microphone consent UI is
  product-level.
- **No echo-cancellation evidence.** Different PoC.

## Verification log

See `VERIFICATION.md` in this directory.
