No description
Find a file
atagen d52cd6db3b 8e: playback callback timing instrumentation + spike investigation
Adds a lock-free `PlaybackTiming` struct (atomics: call_count,
sum_us, max_us, spike_count, last_spike_us, last_spike_at_call)
shared between the bus filter's `playback_process` callback (RT
thread, writes) and the AGC controller (daemon thread, reads).
The audio thread wraps each inner call in
`Instant::now()` ... `state.timing.record(elapsed)` — wait-free,
no allocation. The AGC tick samples the snapshot once per second
and logs at WARN when new spikes have landed since the previous
sample, DEBUG otherwise. `#[global_allocator]` declaration in
`headroom-cli` now sits behind `cfg(debug_assertions)` so release
builds compile cleanly (assert_no_alloc strips `AllocDisabler`
under its default `disable_release` feature).

Spike investigation outcome

  PLAN §11 follow-up noted: ~240 μs steady state, ~2 ms BUSY
  spikes at ~10 s cadence. My ~3 min capture of a 1 kHz sine
  routed through processed (release build) showed:

  - Steady state ~2180 μs / call
  - Max climbed slowly: 2186 → 2222 → 2606 → 2655 → 2812 μs over
    ~1 min (1.3× steady-state, well within the per-quantum budget)
  - Callback rate ~4 Hz, implying the Mbox is negotiating a large
    quantum (~12k frames per call vs the 1024-frame baseline
    PLAN §4.7 measured). Per-frame DSP cost is identical to the
    original budget; the longer wall-clock is just the longer
    quantum

  No clear ~10 s-cadence outlier pattern reproduced. The system
  is comfortably inside budget (~2.2 ms / 250 ms quantum ≈ 1% of
  one core). Without an audible artefact or a reproducible
  failure mode I'm not chasing the original spike further; the
  instrumentation stays so future regressions are visible at
  WARN level. `SPIKE_THRESHOLD_US = 5000` is comfortably above
  steady-state at both small and large quanta, so only real
  outliers trip the log.

Verified

  185 tests pass; clippy clean at -D warnings --all-targets.
  Release build runs sine playback continuously for >3 min with
  no assert_no_alloc abort, no panic, no spike warning. Debug
  build (with assert_no_alloc active) likewise stable across
  thousands of audio callbacks (revalidated as part of the
  release-build comparison).
2026-05-21 16:42:46 +10:00
crates 8e: playback callback timing instrumentation + spike investigation 2026-05-21 16:42:46 +10:00
docs stage 2 2026-05-19 16:33:09 +10:00
profiles stage 2 2026-05-19 16:33:09 +10:00
.gitignore stage 2 2026-05-19 16:33:09 +10:00
Cargo.lock 8a: assert_no_alloc on audio-thread callbacks 2026-05-21 16:21:53 +10:00
Cargo.toml 5: monitor TUI + wire fill-ins 2026-05-21 13:35:27 +10:00
flake.lock stage 2 2026-05-19 16:33:09 +10:00
flake.nix stage 2 2026-05-19 16:33:09 +10:00
IPC.md stage 2 2026-05-19 16:33:09 +10:00
PLAN.md stage 6: per-app 2026-05-20 23:49:58 +10:00
README.md stage 6: per-app 2026-05-20 23:49:58 +10:00
rust-toolchain.toml stage 2 2026-05-19 16:33:09 +10:00

headroom

AGC + compressor + true-peak limiter daemon for PipeWire, in Rust.

Headroom puts a per-application audio safety net between noisy sources (browsers, voice chat, random video) and your speakers, while leaving the things you don't want compressed (music players, games, DAWs) untouched.

  • Hard 0.1 dBTP ceiling on the processed route, with proper inter-sample-peak handling, enforced inline so the contract holds regardless of control-plane state. Streams routed bypass ride the real sink directly and are not in scope of the contract — that's the trade-off that makes the per-app exclusion useful.
  • Per-app exclusion with profile-driven rules.
  • Single binary daemon + CLI, controlled over a Unix-domain socket with a documented JSON wire protocol (see IPC.md).
  • First-party Rust crate (headroom-client) for programmatic use; third-party clients (Qt panels, status bars, …) target the wire protocol directly.

See PLAN.md for the full design and roadmap.

Status

Pre-alpha. Wire protocol and crate scaffolding are in; daemon and filter are under construction.

Building

nix develop          # toolchain + pipewire dev libs + helpers
cargo build          # iterate
nix build            # final packaged headroom binary

License

GPL-3.0-or-later for the daemon and CLI. headroom-dsp and headroom-ipc are MPL-2.0 so they can be reused by non-GPL plugin hosts and clients.