feat: per-channel meters, ceiling lamp, and pre-gain drive
Stage 6 begins. Add lock-free Meters (atomics shared audio->GUI) with a peak-with-decay ballistic, published once per block and gated on the editor being open. Editor draws a per-channel level + gain-reduction meter panel and a ceiling lamp fed by the output limiter. Compressor/Limiter expose gain_reduction_db() for this. Also add a smoothed per-channel pre-gain applied before each compressor (and before the All compressor), driving the signal into compression and on into the limiter for a compressed semi-distortion. Pairs with makeup for full per-channel input/output gain-staging. Compressor DSP untouched; 16 tests pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
//! Lock-free meter state shared from the audio thread to the editor.
|
||||
//!
|
||||
//! `process()` is the single writer (one store per value per block — decimated, not per sample);
|
||||
//! the editor is the single reader (once per frame). All access is wait-free via atomics, so the
|
||||
//! realtime thread never blocks. Values are plain scalars (no streaming history yet) — enough for
|
||||
//! the per-channel level + gain-reduction bars and the ceiling lamp.
|
||||
|
||||
use nih_plug::prelude::AtomicF32;
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
/// Metered channels: low, mid, high, then the 'All' aggregate — same order as the compressors.
|
||||
pub const NUM_CHANNELS: usize = 4;
|
||||
|
||||
pub struct Meters {
|
||||
/// Left output level per channel as a **linear** peak. Peak-with-decay.
|
||||
pub level_l: [AtomicF32; NUM_CHANNELS],
|
||||
/// Right output level per channel (== left for mono signals). Stored separately so the planned
|
||||
/// `|L|GR|R|` layout is a pure editor change; the current bars render `max(L, R)`.
|
||||
pub level_r: [AtomicF32; NUM_CHANNELS],
|
||||
/// Compressor gain reduction per channel in **dB (>= 0)**. Mono by design — detection is
|
||||
/// stereo-linked, so the same gain applies to both channels.
|
||||
pub gain_reduction_db: [AtomicF32; NUM_CHANNELS],
|
||||
/// Output limiter gain reduction in **dB (>= 0)** — drives the ceiling lamp.
|
||||
pub limiter_gr_db: AtomicF32,
|
||||
}
|
||||
|
||||
impl Default for Meters {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
level_l: std::array::from_fn(|_| AtomicF32::new(0.0)),
|
||||
level_r: std::array::from_fn(|_| AtomicF32::new(0.0)),
|
||||
gain_reduction_db: std::array::from_fn(|_| AtomicF32::new(0.0)),
|
||||
limiter_gr_db: AtomicF32::new(0.0),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Update a meter atomic with a new block value using peak-hold-with-decay: jump instantly to a
|
||||
/// louder value, ease back down by `decay_weight` (0..1, closer to 1 = slower fall). Keeps meters
|
||||
/// from flickering while staying responsive to transients.
|
||||
pub fn decay_store(meter: &AtomicF32, block_value: f32, decay_weight: f32) {
|
||||
let current = meter.load(Ordering::Relaxed);
|
||||
let next = if block_value > current {
|
||||
block_value
|
||||
} else {
|
||||
current * decay_weight + block_value * (1.0 - decay_weight)
|
||||
};
|
||||
meter.store(next, Ordering::Relaxed);
|
||||
}
|
||||
Reference in New Issue
Block a user