mirror of
https://github.com/storytold/soundcraft.git
synced 2026-10-24 10:36:46 -06:00
Keep delay-line storage when the compensated latency changes
Fixes #889 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
ab1849cbf8
commit
cc493b2d8b
+60
-10
@@ -248,21 +248,37 @@ struct Strip {
|
|||||||
/// at most this, whatever its plugins claim, so a bogus value can't stall offline renders.
|
/// at most this, whatever its plugins claim, so a bogus value can't stall offline renders.
|
||||||
const MAX_LATENCY: usize = 1 << 20;
|
const MAX_LATENCY: usize = 1 << 20;
|
||||||
|
|
||||||
/// A multichannel delay line (samples), resized only when the delay changes.
|
/// A multichannel delay line (samples). The rings only grow (keeping their contents), so a delay
|
||||||
|
/// change in the callback moves the read offset instead of reallocating and zeroing.
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct DelayLine {
|
struct DelayLine {
|
||||||
bufs: Vec<Vec<f32>>,
|
bufs: Vec<Vec<f32>>,
|
||||||
|
/// Write position in the rings.
|
||||||
idx: usize,
|
idx: usize,
|
||||||
|
/// Current delay (samples).
|
||||||
len: usize,
|
len: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DelayLine {
|
impl DelayLine {
|
||||||
fn set(&mut self, len: usize, ch: usize) {
|
fn set(&mut self, len: usize, ch: usize) {
|
||||||
let len = len.min(MAX_LATENCY);
|
let len = len.min(MAX_LATENCY);
|
||||||
if len != self.len || self.bufs.len() != ch {
|
self.len = len;
|
||||||
self.len = len;
|
let cap = self.bufs.first().map_or(0, Vec::len);
|
||||||
self.idx = 0;
|
if self.bufs.len() != ch {
|
||||||
self.bufs = vec![vec![0.0; len]; ch];
|
self.bufs.resize_with(ch, || vec![0.0; cap]);
|
||||||
|
}
|
||||||
|
let need = if len == 0 { 0 } else { len + 1 };
|
||||||
|
if need > cap {
|
||||||
|
let new_cap = need.next_power_of_two().min(MAX_LATENCY + 1);
|
||||||
|
let start = self.idx.min(cap);
|
||||||
|
for ring in &mut self.bufs {
|
||||||
|
let mut grown = Vec::with_capacity(new_cap);
|
||||||
|
grown.extend_from_slice(ring.get(start..).unwrap_or(&[]));
|
||||||
|
grown.extend_from_slice(ring.get(..start).unwrap_or(&[]));
|
||||||
|
grown.resize(new_cap, 0.0);
|
||||||
|
*ring = grown;
|
||||||
|
}
|
||||||
|
self.idx = cap;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn process(&mut self, io: &mut [Vec<f32>], frames: usize) {
|
fn process(&mut self, io: &mut [Vec<f32>], frames: usize) {
|
||||||
@@ -270,20 +286,29 @@ impl DelayLine {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let start = self.idx;
|
let start = self.idx;
|
||||||
|
let mut end = start;
|
||||||
for (c, ch) in io.iter_mut().enumerate() {
|
for (c, ch) in io.iter_mut().enumerate() {
|
||||||
let Some(ring) = self.bufs.get_mut(c) else { continue };
|
let Some(ring) = self.bufs.get_mut(c) else { continue };
|
||||||
let mut i = start;
|
let cap = ring.len();
|
||||||
|
if cap <= self.len {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let mut i = start % cap;
|
||||||
for x in ch.iter_mut().take(frames) {
|
for x in ch.iter_mut().take(frames) {
|
||||||
if let Some(r) = ring.get_mut(i) {
|
if let Some(w) = ring.get_mut(i) {
|
||||||
std::mem::swap(r, x);
|
*w = *x;
|
||||||
|
}
|
||||||
|
if let Some(r) = ring.get((i + cap - self.len) % cap) {
|
||||||
|
*x = *r;
|
||||||
}
|
}
|
||||||
i += 1;
|
i += 1;
|
||||||
if i >= self.len {
|
if i >= cap {
|
||||||
i = 0;
|
i = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
end = i;
|
||||||
}
|
}
|
||||||
self.idx = (start + frames) % self.len.max(1);
|
self.idx = end;
|
||||||
}
|
}
|
||||||
fn clear(&mut self) {
|
fn clear(&mut self) {
|
||||||
self.bufs.iter_mut().for_each(|b| b.fill(0.0));
|
self.bufs.iter_mut().for_each(|b| b.fill(0.0));
|
||||||
@@ -2303,6 +2328,31 @@ mod tests {
|
|||||||
s
|
s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn delay_line_change_keeps_buffers_and_history() {
|
||||||
|
// #889: a latency change must not reallocate or zero the delay line in the callback.
|
||||||
|
let mut d = DelayLine::default();
|
||||||
|
d.set(480, 2);
|
||||||
|
let ptrs: Vec<_> = d.bufs.iter().map(|b| b.as_ptr()).collect();
|
||||||
|
let mut io = vec![(1..=480).map(|v| v as f32).collect::<Vec<f32>>(); 2];
|
||||||
|
d.process(&mut io, 480);
|
||||||
|
assert!(io.iter().all(|c| c.iter().all(|&x| x == 0.0)));
|
||||||
|
// Shorter delay: the line keeps its storage and outputs what it already holds.
|
||||||
|
d.set(240, 2);
|
||||||
|
let after: Vec<_> = d.bufs.iter().map(|b| b.as_ptr()).collect();
|
||||||
|
assert_eq!(ptrs, after, "delay line reallocated on a shorter delay");
|
||||||
|
let mut io = vec![vec![0.0f32; 240]; 2];
|
||||||
|
d.process(&mut io, 240);
|
||||||
|
let want: Vec<f32> = (241..=480).map(|v| v as f32).collect();
|
||||||
|
for c in &io {
|
||||||
|
assert_eq!(c, &want, "held samples lost on a latency change");
|
||||||
|
}
|
||||||
|
// Back to the longer delay: still the same storage.
|
||||||
|
d.set(480, 2);
|
||||||
|
let after: Vec<_> = d.bufs.iter().map(|b| b.as_ptr()).collect();
|
||||||
|
assert_eq!(ptrs, after, "delay line reallocated going back to its size");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn stored_instrument_params_reach_the_plugin() {
|
fn stored_instrument_params_reach_the_plugin() {
|
||||||
// #152: a saved instrument level of -12 dB must render quieter than -6 dB.
|
// #152: a saved instrument level of -12 dB must render quieter than -6 dB.
|
||||||
|
|||||||
Reference in New Issue
Block a user