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).
This commit is contained in:
parent
fcf421b94c
commit
79e4baedd0
9 changed files with 309 additions and 70 deletions
|
|
@ -624,10 +624,12 @@ mod tests {
|
|||
fn try_set_config_applies_scalar_changes() {
|
||||
let sr = 48_000.0;
|
||||
let mut l = Limiter::new(LimiterConfig::default(), sr);
|
||||
let mut cfg = LimiterConfig::default();
|
||||
cfg.ceiling_dbtp = -3.0;
|
||||
cfg.release_ms = 200.0;
|
||||
cfg.hold_ms = 10.0;
|
||||
let cfg = LimiterConfig {
|
||||
ceiling_dbtp: -3.0,
|
||||
release_ms: 200.0,
|
||||
hold_ms: 10.0,
|
||||
..LimiterConfig::default()
|
||||
};
|
||||
assert_eq!(l.try_set_config(cfg), SetConfigOutcome::Applied);
|
||||
assert!((l.ceiling_dbtp() - -3.0).abs() < 1e-6);
|
||||
let active = l.config();
|
||||
|
|
@ -640,8 +642,10 @@ mod tests {
|
|||
let sr = 48_000.0;
|
||||
let mut l = Limiter::new(LimiterConfig::default(), sr);
|
||||
// Start with soft on. Disable it.
|
||||
let mut cfg = LimiterConfig::default();
|
||||
cfg.soft = None;
|
||||
let mut cfg = LimiterConfig {
|
||||
soft: None,
|
||||
..LimiterConfig::default()
|
||||
};
|
||||
assert_eq!(l.try_set_config(cfg), SetConfigOutcome::Applied);
|
||||
assert!(l.config().soft.is_none());
|
||||
assert!(l.effective_soft_ceiling_dbtp().is_none());
|
||||
|
|
@ -664,8 +668,10 @@ mod tests {
|
|||
fn try_set_config_rejects_oversample_change() {
|
||||
let sr = 48_000.0;
|
||||
let mut l = Limiter::new(LimiterConfig::default(), sr);
|
||||
let mut cfg = LimiterConfig::default();
|
||||
cfg.oversample = 8;
|
||||
let cfg = LimiterConfig {
|
||||
oversample: 8,
|
||||
..LimiterConfig::default()
|
||||
};
|
||||
assert_eq!(l.try_set_config(cfg), SetConfigOutcome::StructuralChange);
|
||||
// Limiter unchanged.
|
||||
assert_eq!(l.config().oversample, LimiterConfig::default().oversample);
|
||||
|
|
@ -675,8 +681,11 @@ mod tests {
|
|||
fn try_set_config_rejects_lookahead_change() {
|
||||
let sr = 48_000.0;
|
||||
let mut l = Limiter::new(LimiterConfig::default(), sr);
|
||||
let mut cfg = LimiterConfig::default();
|
||||
cfg.lookahead_ms = 5.0; // resizes delay + peak buffer
|
||||
let cfg = LimiterConfig {
|
||||
// resizes delay + peak buffer
|
||||
lookahead_ms: 5.0,
|
||||
..LimiterConfig::default()
|
||||
};
|
||||
assert_eq!(l.try_set_config(cfg), SetConfigOutcome::StructuralChange);
|
||||
}
|
||||
|
||||
|
|
@ -684,8 +693,10 @@ mod tests {
|
|||
fn try_set_config_rejects_fir_taps_change() {
|
||||
let sr = 48_000.0;
|
||||
let mut l = Limiter::new(LimiterConfig::default(), sr);
|
||||
let mut cfg = LimiterConfig::default();
|
||||
cfg.fir_taps = 63;
|
||||
let cfg = LimiterConfig {
|
||||
fir_taps: 63,
|
||||
..LimiterConfig::default()
|
||||
};
|
||||
assert_eq!(l.try_set_config(cfg), SetConfigOutcome::StructuralChange);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue