Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 45 additions & 3 deletions crates/mix/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,9 @@ pub fn create_instance(id: &str, state: Option<&[u8]>, sample_rate: f32, max_blo
Some(NewInstance { plugin, refused_state })
}

/// Peak meter values for one strip (linear, per channel, max over the last block).
/// Peak meter values for one strip (linear, per channel). [`MixEngine`] measures one block; a
/// realtime host folds blocks together with [`StripMeter::accumulate`] so a reader polling less
/// often than blocks arrive sees the highest peak since it last looked, not just the latest block.
#[derive(Debug, Clone, Default, PartialEq)]
pub struct StripMeter {
/// First two channels (mono strips repeat channel 0), for stereo meters.
Expand Down Expand Up @@ -186,6 +188,33 @@ impl StripMeter {
self.plugin_failures = o.plugin_failures;
}

/// Fold another (later) measurement in: per-channel peaks and the gain reduction keep their
/// maximum, plugin failures take the later value (the count only grows). Allocation-free once
/// the channel count is stable, so it can run on the audio thread every block.
pub fn accumulate(&mut self, o: &StripMeter) {
if self.peaks.len() != o.peaks.len() {
// The strip's format changed (a document edit): earlier channels no longer line up.
self.peaks.clone_from(&o.peaks);
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
} else {
for (p, q) in self.peaks.iter_mut().zip(&o.peaks) {
*p = max_level(*p, *q);
}
}
for (p, q) in self.peak.iter_mut().zip(o.peak) {
*p = max_level(*p, q);
}
self.gain_reduction_db = max_level(self.gain_reduction_db, o.gain_reduction_db);
self.plugin_failures = o.plugin_failures;
}

/// Zero the levels (peaks and gain reduction) after a reader took them, keeping the channel
/// count and the plugin failure record. Never allocates.
pub fn reset_levels(&mut self) {
self.peak = [0.0; 2];
self.peaks.iter_mut().for_each(|p| *p = 0.0);
self.gain_reduction_db = 0.0;
}

fn measure(&mut self, bufs: &[Vec<f32>], frames: usize, gr: f32) {
if self.peaks.len() != bufs.len() {
self.peaks.resize(bufs.len(), 0.0);
Expand All @@ -199,6 +228,16 @@ impl StripMeter {
}
}

/// The larger of two meter levels; a NaN (from a misbehaving plugin) never wins or sticks.
fn max_level(a: f32, b: f32) -> f32 {
match (a.is_finite(), b.is_finite()) {
(true, true) => a.max(b),
(true, false) => a,
(false, true) => b,
(false, false) => 0.0,
}
}

/// A bus: its format and one buffer per channel.
struct BusBuf {
fmt: ChannelFormat,
Expand Down Expand Up @@ -544,8 +583,10 @@ impl MixEngine {
fx.reset();
}
}
self.meters.clear();
self.main_meter = StripMeter::default();
// Zeroed in place: rebuilding the entries would allocate on the audio thread at every
// play, stop and locate.
self.meters.values_mut().for_each(StripMeter::reset_levels);
self.main_meter.reset_levels();
}

/// Bring plugin instances in line with the session (creates/destroys as needed). Allocates;
Expand All @@ -560,6 +601,7 @@ impl MixEngine {
let mut cx = SlotCtx { sr: self.sample_rate, mb, external: self.external, adopt: &mut self.adopt, retired: &mut self.retired };
let gone: Vec<TrackId> = self.strips.keys().filter(|id| s.track(**id).is_none()).copied().collect();
for id in gone {
self.meters.remove(&id);
if let Some(mut st) = self.strips.remove(&id) {
for p in st.plugins.drain(..) {
cx.retire(p);
Expand Down
Loading
Loading