4c5165b0bc
Meters: per-channel |L | GR | R| layout (narrower bars). Ceiling lamp now latches on a limiter catch and holds, clearing after 3s or on click. Plot: per-channel selectable scrolling in/out/gain-reduction scope under the meters, fed by new raw block-peak atomics (separate from the decayed bar atomics). Histories for all four channels run continuously, so switching tabs keeps each channel's history. Scroll is time-based with a flow-speed selector (2/5/15/45 s window) so the window length is accurate regardless of frame rate, with peak-preserving downsampling between columns. reset() now zeroes the meters so transport restart shows silence rather than stale values. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
85 lines
3.9 KiB
Rust
85 lines
3.9 KiB
Rust
//! 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,
|
|
|
|
// --- Scrolling plot feed: instantaneous block peaks, NOT decayed. The editor samples these
|
|
// each frame into its own history ring. Per channel: input level (entering the compressor),
|
|
// output level, and gain reduction.
|
|
/// Mono input level per channel (linear peak, post pre-gain, pre-compressor).
|
|
pub plot_in: [AtomicF32; NUM_CHANNELS],
|
|
/// Mono output level per channel (linear peak, post-compressor).
|
|
pub plot_out: [AtomicF32; NUM_CHANNELS],
|
|
/// Gain reduction per channel in dB (>= 0).
|
|
pub plot_gr: [AtomicF32; NUM_CHANNELS],
|
|
}
|
|
|
|
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),
|
|
plot_in: std::array::from_fn(|_| AtomicF32::new(0.0)),
|
|
plot_out: std::array::from_fn(|_| AtomicF32::new(0.0)),
|
|
plot_gr: std::array::from_fn(|_| AtomicF32::new(0.0)),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Meters {
|
|
/// Zero every meter. Called from the plugin's `reset()` (transport restart / sample-rate
|
|
/// change) so the display starts from silence rather than stale values. Real-time safe.
|
|
pub fn clear(&self) {
|
|
for i in 0..NUM_CHANNELS {
|
|
self.level_l[i].store(0.0, Ordering::Relaxed);
|
|
self.level_r[i].store(0.0, Ordering::Relaxed);
|
|
self.gain_reduction_db[i].store(0.0, Ordering::Relaxed);
|
|
self.plot_in[i].store(0.0, Ordering::Relaxed);
|
|
self.plot_out[i].store(0.0, Ordering::Relaxed);
|
|
self.plot_gr[i].store(0.0, Ordering::Relaxed);
|
|
}
|
|
self.limiter_gr_db.store(0.0, Ordering::Relaxed);
|
|
}
|
|
}
|
|
|
|
/// Store an instantaneous value (no smoothing) — used for the scrolling-plot feed, which the
|
|
/// editor smooths/decimates on its own.
|
|
pub fn store_instant(meter: &AtomicF32, value: f32) {
|
|
meter.store(value, Ordering::Relaxed);
|
|
}
|
|
|
|
/// 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);
|
|
}
|