No description
Find a file
atagen 79e4baedd0 4g: bus meters publishing + housekeeping
Closes the last gap before Phase 5's monitor TUI: per-app meter
events already publish on the meters topic via the registry watcher;
bus-level DSP meters now also publish.

4g — Bus meters
  headroom_core::meters::BusMetrics is an Arc<parking_lot::Mutex<...>>
  snapshot owned by the playback callback (try_lock; skip on
  contention) and read by the AGC controller on each 50 ms tick.
  Carries: compressor GR, limiter total/soft/hard GR, true peak. The
  AGC controller combines these with its ebur128 readings (momentary,
  short-term, integrated) and the current smoothed AGC target, then
  publishes a headroom_ipc::MeterTick on Topic::Meters.

  Publish cadence honours profile.meters.publish_hz, capped at the
  AGC tick rate (20 Hz). Lower publish_hz throttles to every Nth
  tick.

  Mode::I added to the AGC's EbuR128 so loudness_global() is
  available without a second ebur128 instance. Bounded cost — a
  histogram walk per call, <=20 Hz.

  LUFS values are sanitised to a -200.0 dB floor via
  finite_or_floor() — ebur128 returns -inf (not Err) for "no usable
  measurement yet," and non-finite f32 can't survive JSON
  serialisation (serde_json renders as null).

Housekeeping shipped alongside

  headroom-client moved from [dependencies] to [dev-dependencies] in
  headroom-core — it's only used inside ipc::server's tests. Verified
  by full clippy + test run; production builds no longer pull it in.

  Pre-existing clippy nits cleared (limiter.rs x5, app_level.rs,
  ipc/ops.rs, pw/filter.rs). All field_reassign_with_default or
  assign_op_pattern in test code; stage-6 commit ran clippy without
  --all-targets so these slipped through.

Verified

  178 tests passing (28 dsp + 48 dsp + 20 ipc + 106 core including
  +2 new meters tests + 4 client). Clippy clean at default level with
  -D warnings --all-targets.

  Smoke test: monitor meters subscription receives 20 Hz MeterTick
  events with the expected JSON shape (all fields finite).
2026-05-21 10:29:38 +10:00
crates 4g: bus meters publishing + housekeeping 2026-05-21 10:29:38 +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 stage 6: per-app 2026-05-20 23:49:58 +10:00
Cargo.toml stage 6: per-app 2026-05-20 23:49:58 +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.