diff --git a/src/dsp/helpers.rs b/src/dsp/helpers.rs index 4491216..b8526fd 100644 --- a/src/dsp/helpers.rs +++ b/src/dsp/helpers.rs @@ -892,7 +892,6 @@ fn fclampc(x: F, mi: f64, mx: f64) -> F { x.max(f(mi)).min(f(mx)) } - /// Hermite / Cubic interpolation of a buffer full of samples at the given _index_. /// _len_ is the buffer length to consider and wrap the index into. And _fract_ is the /// fractional part of the index. @@ -913,6 +912,7 @@ fn fclampc(x: F, mi: f64, mx: f64) -> F { ///``` #[inline] pub fn cubic_interpolate(data: &[F], len: usize, index: usize, fract: F) -> F { + let index = index + len; // Hermite interpolation, take from // https://github.com/eric-wood/delay/blob/main/src/delay.rs#L52 // @@ -1031,11 +1031,21 @@ impl DelayBuffer { let offs = s_offs.floor().to_usize().unwrap_or(0) % len; let fract = s_offs.fract(); - let i = (self.wr + len) - offs; + // one extra offset, because feed() advances self.wr to the next writing position! + let i = (self.wr + len) - (offs + 1); let x0 = data[i % len]; let x1 = data[(i - 1) % len]; - x0 + fract * (x1 - x0) + let res = x0 + fract * (x1 - x0); + //d// eprintln!( + //d// "INTERP: {:6.4} x0={:6.4} x1={:6.4} fract={:6.4} => {:6.4}", + //d// s_offs.to_f64().unwrap_or(0.0), + //d// x0.to_f64().unwrap(), + //d// x1.to_f64().unwrap(), + //d// fract.to_f64().unwrap(), + //d// res.to_f64().unwrap(), + //d// ); + res } /// Fetch a sample from the delay buffer at the given time. @@ -1057,23 +1067,33 @@ impl DelayBuffer { let offs = s_offs.floor().to_usize().unwrap_or(0) % len; let fract = s_offs.fract(); - let i = (self.wr + len) - offs; - - cubic_interpolate(data, len, i, f::(1.0) - fract) + let i = (self.wr + len) - (offs + 2); + let res = cubic_interpolate(data, len, i, f::(1.0) - fract); +// eprintln!( +// "cubic at={} ({:6.4}) res={:6.4}", +// i % len, +// s_offs.to_f64().unwrap(), +// res.to_f64().unwrap() +// ); + res } #[inline] pub fn nearest_at(&self, delay_time_ms: F) -> F { let len = self.data.len(); let offs = ((delay_time_ms * self.srate) / f(1000.0)).floor().to_usize().unwrap_or(0) % len; - let idx = ((self.wr + len) - offs) % len; + // (offs + 1) one extra offset, because feed() advances + // self.wr to the next writing position! + let idx = ((self.wr + len) - (offs + 1)) % len; self.data[idx] } #[inline] pub fn at(&self, delay_sample_count: usize) -> F { let len = self.data.len(); - let idx = ((self.wr + len) - delay_sample_count) % len; + // (delay_sample_count + 1) one extra offset, because feed() advances self.wr to + // the next writing position! + let idx = ((self.wr + len) - (delay_sample_count + 1)) % len; self.data[idx] } } diff --git a/src/dsp/node_sampl.rs b/src/dsp/node_sampl.rs index e5d5708..7964a26 100644 --- a/src/dsp/node_sampl.rs +++ b/src/dsp/node_sampl.rs @@ -148,8 +148,8 @@ impl Sampl { return 0.0; } - let j = self.phase.floor() as usize % sd_len; - let i = ((sd_len - 1) - j) + sd_len; + let j = self.phase.floor() as usize; + let i = ((sd_len - 1) - j); let f = self.phase.fract(); self.phase = j as f64 + f + sr_factor * speed; @@ -165,7 +165,7 @@ impl Sampl { return 0.0; } - let i = self.phase.floor() as usize + sd_len; + let i = self.phase.floor() as usize; let f = self.phase.fract(); self.phase = (i % sd_len) as f64 + f + sr_factor * speed; diff --git a/tests/delay_buffer.rs b/tests/delay_buffer.rs new file mode 100644 index 0000000..c305ada --- /dev/null +++ b/tests/delay_buffer.rs @@ -0,0 +1,182 @@ +// Copyright (c) 2021 Weird Constructor +// This file is a part of HexoDSP. Released under GPL-3.0-or-later. +// See README.md and COPYING for details. + +mod common; +use common::*; + +#[test] +fn check_delaybuffer_linear_interpolation() { + let mut buf = crate::helpers::DelayBuffer::new(); + + buf.feed(0.0); + buf.feed(0.1); + buf.feed(0.2); + buf.feed(0.3); + buf.feed(0.4); + buf.feed(0.5); + buf.feed(0.6); + buf.feed(0.7); + buf.feed(0.8); + buf.feed(0.9); + buf.feed(1.0); + + let mut samples_out = vec![]; + let mut pos = 0.0; + let pos_inc = 0.5; + for _ in 0..20 { + samples_out.push(buf.linear_interpolate_at_s(pos)); + pos += pos_inc; + } + + assert_vec_feq!( + samples_out, + vec![ + 1.0, 0.95, 0.9, 0.85, 0.8, 0.75, 0.7, 0.65, 0.6, 0.55, 0.5, 0.45, 0.4, 0.35000002, 0.3, + 0.25, 0.2, 0.15, 0.1, 0.05 + ] + ); + + let mut samples_out = vec![]; + let mut pos = 0.0; + let pos_inc = 0.2; + for _ in 0..30 { + samples_out.push(buf.linear_interpolate_at_s(pos)); + pos += pos_inc; + } + + assert_vec_feq!( + samples_out, + vec![ + 1.0, 0.98, 0.96, 0.94, 0.91999996, 0.9, 0.88, 0.85999995, 0.84, 0.82, 0.8, 0.78, 0.76, + 0.73999995, 0.71999997, 0.6999999, 0.67999995, 0.65999997, 0.6399999, 0.61999995, + 0.59999996, 0.58, 0.56, 0.54, 0.52000004, 0.50000006, 0.48000008, 0.4600001, + 0.44000012, 0.42000014 + ] + ); +} + +#[test] +fn check_delaybuffer_nearest() { + let mut buf = crate::helpers::DelayBuffer::new(); + + buf.feed(0.0); + buf.feed(0.1); + buf.feed(0.2); + buf.feed(0.3); + buf.feed(0.4); + buf.feed(0.5); + buf.feed(0.6); + buf.feed(0.7); + buf.feed(0.8); + buf.feed(0.9); + buf.feed(1.0); + + let mut samples_out = vec![]; + let mut pos = 0.0; + let pos_inc = 0.5; + for _ in 0..20 { + samples_out.push(buf.at(pos as usize)); + pos += pos_inc; + } + + assert_vec_feq!( + samples_out, + vec![ + 1.0, 1.0, 0.9, 0.9, 0.8, 0.8, 0.7, 0.7, 0.6, 0.6, 0.5, 0.5, 0.4, 0.4, 0.3, 0.3, 0.2, + 0.2, 0.1, 0.1 + ] + ); + + let mut samples_out = vec![]; + let mut pos = 0.0; + let pos_inc = 0.2; + for _ in 0..30 { + samples_out.push(buf.at(pos as usize)); + pos += pos_inc; + } + + assert_vec_feq!( + samples_out, + vec![ + 1.0, 1.0, 1.0, 1.0, 1.0, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.8, 0.8, 0.8, 0.8, 0.7, 0.7, + 0.7, 0.7, 0.7, 0.6, 0.6, 0.6, 0.6, 0.6, 0.5, 0.5, 0.5, 0.5, 0.5 + ] + ); +} + +#[test] +fn check_cubic_interpolate() { + use crate::helpers::cubic_interpolate; + let data = [1.0, 0.9, 0.8, 0.7, 0.6, 0.5, 0.4, 0.3, 0.2, 0.1, 0.0]; + + let mut samples_out = vec![]; + let mut pos = 0.0_f32; + let pos_inc = 0.1_f32; + for _ in 0..30 { + let i = pos.floor() as usize; + let f = pos.fract(); + samples_out.push(cubic_interpolate(&data[..], data.len(), i, f)); + pos += pos_inc; + } + assert_vec_feq!( + samples_out, + vec![ + 1.0, 1.03455, 1.0504, 1.05085, 1.0392, 1.01875, 0.99279994, 0.9646499, 0.9375999, + 0.91494995, 0.9, 0.89, 0.87999994, 0.86999995, 0.85999995, 0.84999996, 0.84, 0.83, + 0.82, 0.80999994, 0.8, 0.79, 0.78000003, 0.77000004, 0.76, 0.75, 0.74, 0.73, 0.72, + 0.71000004 + ] + ); +} + +#[test] +fn check_delaybuffer_cubic_interpolation() { + let mut buf = crate::helpers::DelayBuffer::new(); + + buf.feed(0.0); + buf.feed(0.1); + buf.feed(0.2); + buf.feed(0.3); + buf.feed(0.4); + buf.feed(0.5); + buf.feed(0.6); + buf.feed(0.7); + buf.feed(0.8); + buf.feed(0.9); + buf.feed(1.0); + + let mut samples_out = vec![]; + let mut pos = 0.0; + let pos_inc = 0.5; + for _ in 0..20 { + samples_out.push(buf.cubic_interpolate_at_s(pos)); + pos += pos_inc; + } + + assert_vec_feq!( + samples_out, + vec![ + 1.0, 0.95, 0.9, 0.85, 0.8, 0.75, 0.7, 0.65, 0.6, 0.55, 0.5, 0.45, 0.4, 0.35000002, 0.3, + 0.25, 0.2, 0.15, 0.1, 0.05 + ] + ); + + let mut samples_out = vec![]; + let mut pos = 0.0; + let pos_inc = 0.2; + for _ in 0..30 { + samples_out.push(buf.cubic_interpolate_at_s(pos)); + pos += pos_inc; + } + + assert_vec_feq!( + samples_out, + vec![ + 1.0, 0.98, 0.96, 0.94, 0.91999996, 0.9, 0.88, 0.85999995, 0.84, 0.82, 0.8, 0.78, 0.76, + 0.73999995, 0.71999997, 0.6999999, 0.67999995, 0.65999997, 0.6399999, 0.61999995, + 0.59999996, 0.58, 0.56, 0.54, 0.52000004, 0.50000006, 0.48000008, 0.4600001, + 0.44000012, 0.42000014 + ] + ); +}