feat: 3-bar meters, latching ceiling lamp, and rolling in/out/GR plot

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>
This commit is contained in:
Mikkeli Matlock
2026-06-23 09:53:06 +09:00
parent 9a54413bfa
commit 4c5165b0bc
3 changed files with 282 additions and 33 deletions
+35
View File
@@ -22,6 +22,16 @@ pub struct Meters {
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 {
@@ -31,10 +41,35 @@ impl Default for Meters {
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.