fixing interpolation

This commit is contained in:
Weird Constructor 2022-07-19 20:47:46 +02:00
parent 649ce74aaf
commit 1fbc6741cb
6 changed files with 101 additions and 132 deletions

View file

@ -892,6 +892,48 @@ fn fclampc<F: Flt>(x: F, mi: f64, mx: f64) -> F {
x.max(f(mi)).min(f(mx)) 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.
///
/// Commonly used like this:
///
///```
/// use hexodsp::dsp::helpers::cubic_interpolate;
///
/// let buf [f32; 9] = [1.0, 0.2, 0.4, 0.5, 0.7, 0.9, 1.0, 0.3, 0.3];
/// let pos = 3.3_f32;
///
/// let i = pos.floor();
/// let f = pos.fract();
///
/// let res = cubic_interpolate(&buf[..], buf.len(), i, f);
/// assert_eq!((res - 0.4).abs() < 0.2);
///```
#[inline]
pub fn cubic_interpolate<F: Flt>(data: &[F], len: usize, index: usize, fract: F) -> F {
// Hermite interpolation, take from
// https://github.com/eric-wood/delay/blob/main/src/delay.rs#L52
//
// Thanks go to Eric Wood!
//
// For the interpolation code:
// MIT License, Copyright (c) 2021 Eric Wood
let xm1 = data[(index - 1) % len];
let x0 = data[index % len];
let x1 = data[(index + 1) % len];
let x2 = data[(index + 2) % len];
let c = (x1 - xm1) * f(0.5);
let v = x0 - x1;
let w = c + v;
let a = w + v + (x2 - x0) * f(0.5);
let b_neg = w + a;
(((a * fract) - b_neg) * fract + c) * fract + x0
}
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default)]
pub struct DelayBuffer<F: Flt> { pub struct DelayBuffer<F: Flt> {
data: Vec<F>, data: Vec<F>,
@ -1006,7 +1048,8 @@ impl<F: Flt> DelayBuffer<F> {
/// Fetch a sample from the delay buffer at the given offset. /// Fetch a sample from the delay buffer at the given offset.
/// ///
/// * `s_offs` - Sample offset in samples. /// * `s_offs` - Sample offset in samples into the past of the [DelayBuffer]
/// from the current write (or the "now") position.
#[inline] #[inline]
pub fn cubic_interpolate_at_s(&self, s_offs: F) -> F { pub fn cubic_interpolate_at_s(&self, s_offs: F) -> F {
let data = &self.data[..]; let data = &self.data[..];
@ -1016,26 +1059,7 @@ impl<F: Flt> DelayBuffer<F> {
let i = (self.wr + len) - offs; let i = (self.wr + len) - offs;
// Hermite interpolation, take from cubic_interpolate(data, len, i, f::<F>(1.0) - fract)
// https://github.com/eric-wood/delay/blob/main/src/delay.rs#L52
//
// Thanks go to Eric Wood!
//
// For the interpolation code:
// MIT License, Copyright (c) 2021 Eric Wood
let xm1 = data[(i + 1) % len];
let x0 = data[i % len];
let x1 = data[(i - 1) % len];
let x2 = data[(i - 2) % len];
let c = (x1 - xm1) * f(0.5);
let v = x0 - x1;
let w = c + v;
let a = w + v + (x2 - x0) * f(0.5);
let b_neg = w + a;
let fract = fract as F;
(((a * fract) - b_neg) * fract + c) * fract + x0
} }
#[inline] #[inline]

View file

@ -2,7 +2,7 @@
// This file is a part of HexoDSP. Released under GPL-3.0-or-later. // This file is a part of HexoDSP. Released under GPL-3.0-or-later.
// See README.md and COPYING for details. // See README.md and COPYING for details.
use super::helpers::Trigger; use super::helpers::{Trigger, cubic_interpolate};
use crate::dsp::{at, denorm, denorm_offs, inp, out}; //, inp, denorm, denorm_v, inp_dir, at}; use crate::dsp::{at, denorm, denorm_offs, inp, out}; //, inp, denorm, denorm_v, inp_dir, at};
use crate::dsp::{DspNode, LedPhaseVals, NodeContext, NodeId, ProcBuf, SAtom}; use crate::dsp::{DspNode, LedPhaseVals, NodeContext, NodeId, ProcBuf, SAtom};
use crate::nodes::{NodeAudioContext, NodeExecContext}; use crate::nodes::{NodeAudioContext, NodeExecContext};
@ -154,26 +154,7 @@ impl Sampl {
let f = self.phase.fract(); let f = self.phase.fract();
self.phase = j as f64 + f + sr_factor * speed; self.phase = j as f64 + f + sr_factor * speed;
// Hermite interpolation, take from cubic_interpolate(&sample_data[..], sd_len, i, (1.0 - f) as f32)
// https://github.com/eric-wood/delay/blob/main/src/delay.rs#L52
//
// Thanks go to Eric Wood!
//
// For the interpolation code:
// MIT License, Copyright (c) 2021 Eric Wood
let xm1 = sample_data[(i + 1) % sd_len];
let x0 = sample_data[i % sd_len];
let x1 = sample_data[(i - 1) % sd_len];
let x2 = sample_data[(i - 2) % sd_len];
let c = (x1 - xm1) * 0.5;
let v = x0 - x1;
let w = c + v;
let a = w + v + (x2 - x0) * 0.5;
let b_neg = w + a;
let f = (1.0 - f) as f32;
(((a * f) - b_neg) * f + c) * f + x0
} }
#[allow(clippy::many_single_char_names)] #[allow(clippy::many_single_char_names)]
@ -188,26 +169,7 @@ impl Sampl {
let f = self.phase.fract(); let f = self.phase.fract();
self.phase = (i % sd_len) as f64 + f + sr_factor * speed; self.phase = (i % sd_len) as f64 + f + sr_factor * speed;
// Hermite interpolation, take from cubic_interpolate(&sample_data[..], sd_len, i, f as f32)
// https://github.com/eric-wood/delay/blob/main/src/delay.rs#L52
//
// Thanks go to Eric Wood!
//
// For the interpolation code:
// MIT License, Copyright (c) 2021 Eric Wood
let xm1 = sample_data[(i - 1) % sd_len];
let x0 = sample_data[i % sd_len];
let x1 = sample_data[(i + 1) % sd_len];
let x2 = sample_data[(i + 2) % sd_len];
let c = (x1 - xm1) * 0.5;
let v = x0 - x1;
let w = c + v;
let a = w + v + (x2 - x0) * 0.5;
let b_neg = w + a;
let f = f as f32;
(((a * f) - b_neg) * f + c) * f + x0
} }
#[allow(clippy::float_cmp)] #[allow(clippy::float_cmp)]

View file

@ -38,35 +38,35 @@ fn check_node_allp() {
// starts with original signal * -0.7 // starts with original signal * -0.7
let mut v = vec![0.7; (2.0 * 44.1_f32).ceil() as usize]; let mut v = vec![0.7; (2.0 * 44.1_f32).ceil() as usize];
// silence for 1ms, which is the internal delay of the allpass // silence for 1ms, which is the internal delay of the allpass
v.append(&mut vec![0.0; (1.0 * 44.1_f32).floor() as usize - 2]); v.append(&mut vec![0.0; (1.0 * 44.1_f32).floor() as usize - 3]);
// allpass feedback of the original signal for 2ms: // allpass feedback of the original signal for 2ms:
// XXX: the smearing before and after the allpass is due to the // XXX: the smearing before and after the allpass is due to the
// cubic interpolation! // cubic interpolation!
v.append(&mut vec![-0.03748519, 0.37841395, 0.5260659]); v.append(&mut vec![-0.01606, 0.13158, 0.54748]);
v.append(&mut vec![0.51; (2.0 * 44.1_f32).ceil() as usize - 3]); v.append(&mut vec![0.51; (2.0 * 44.1_f32).ceil() as usize - 3]);
// 1ms allpass silence like before: // 1ms allpass silence like before:
v.append(&mut vec![0.54748523, 0.13158606, -0.016065884]); v.append(&mut vec![0.5260659, 0.37841395, -0.03748519]);
v.append(&mut vec![0.0; (1.0 * 44.1_f32).floor() as usize - 5]); v.append(&mut vec![0.0; (1.0 * 44.1_f32).floor() as usize - 6]);
// 2ms the previous 1.0 * 0.7 fed back into the filter, // 2ms the previous 1.0 * 0.7 fed back into the filter,
// including even more smearing due to cubic interpolation: // including even more smearing due to cubic interpolation:
v.append(&mut vec![ v.append(&mut vec![
-0.0019286226, -0.00035427228,
0.04086761, 0.006157537,
-0.1813516, -0.005423375,
-0.35157663, -0.1756484,
-0.36315754, -0.39786762,
-0.35664573, -0.3550714,
]); ]);
v.append(&mut vec![-0.357; (2.0 * 44.1_f32).floor() as usize - 5]); v.append(&mut vec![-0.357; (2.0 * 44.1_f32).floor() as usize - 5]);
v.append(&mut vec![ v.append(&mut vec![
-0.3550714, -0.35664573,
-0.39786762, -0.36315754,
-0.1756484, -0.35157663,
-0.005423375, -0.1813516,
0.006157537, 0.04086761,
-0.00035427228, -0.0019286226,
]); ]);
v.append(&mut vec![0.0; 10]); v.append(&mut vec![0.0; 10]);

View file

@ -36,45 +36,28 @@ fn check_node_comb_1() {
(0, 216), (0, 216),
(11, 221), (11, 221),
(22, 216), (22, 216),
(3370, 206), (3381, 184),
(3381, 248), (3391, 189),
(3391, 191), (3402, 195),
(6740, 185), (3413, 213),
(6751, 207), (3424, 198),
(6761, 195), (3435, 203),
(10131, 215), (6815, 188),
(10142, 210), (13587, 196),
(10153, 213), (13598, 210),
(10164, 201), (13609, 207),
(20338, 187), (13620, 193)
(20349, 184)
] ]
); );
pset_n_wait(&mut matrix, &mut node_exec, comb_1, "time", 0.030); pset_n_wait(&mut matrix, &mut node_exec, comb_1, "time", 0.030);
let fft = run_and_get_avg_fft4096_now(&mut node_exec, 180); let fft = run_and_get_avg_fft4096_now(&mut node_exec, 180);
assert_eq!( assert_eq!(fft, vec![(1001, 186), (3015, 186), (7031, 191)]);
fft,
vec![(1001, 206), (2993, 196), (3004, 219), (3994, 197), (6998, 211), (8000, 201)]
);
pset_n_wait(&mut matrix, &mut node_exec, comb_1, "g", 0.999); pset_n_wait(&mut matrix, &mut node_exec, comb_1, "g", 0.999);
let fft = run_and_get_avg_fft4096_now(&mut node_exec, 1000); let fft = run_and_get_avg_fft4096_now(&mut node_exec, 1000);
assert_eq!( assert_eq!(fft, vec![(0, 2008), (11, 1017)]);
fft,
vec![
(0, 2003),
(11, 1015),
(991, 1078),
(1001, 1837),
(2003, 1059),
(2993, 1420),
(3004, 1775),
(3994, 1297),
(4005, 1485)
]
);
} }
#[test] #[test]

View file

@ -68,16 +68,16 @@ fn check_node_delay_1() {
0.0, 0.0,
0.0, 0.0,
// delayed burst of sine for 100ms: // delayed burst of sine for 100ms:
0.047408286, 0.039102618,
-0.17181452, -0.16390327,
0.2669317, 0.27611724,
-0.22377986, -0.2608055,
0.000059626997, 0.060164057,
0.24652793, 0.20197779,
-0.30384338, -0.28871512,
0.2087649, 0.21515398,
-0.070256576, -0.081471935,
0.000003647874, 0.0023831273,
// silence afterwards: // silence afterwards:
0.0, 0.0,
0.0, 0.0,
@ -119,8 +119,8 @@ fn check_node_delay_2() {
vec![ vec![
// 10ms smoothing time for "inp" // 10ms smoothing time for "inp"
0.001133, // 30ms delaytime just mixing the 0.5: 0.001133, // 30ms delaytime just mixing the 0.5:
0.5, 0.5, 0.5, // the delayed smoothing ramp (10ms): 0.5, 0.5, 0.5, // the delayed smoothing ramp (10ms):
0.951113, // the delay + input signal: 0.9513626, // the delay + input signal:
1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0
] ]
); );
@ -172,15 +172,15 @@ fn check_node_delay_time_mod() {
let fft = run_and_get_fft4096_now(&mut node_exec, 110); let fft = run_and_get_fft4096_now(&mut node_exec, 110);
// Expect a sine sweep over a // Expect a sine sweep over a
// range of low frequencies: // range of low frequencies:
assert_eq!(fft[0], (86, 111)); assert_eq!(fft[0], (86, 112));
assert_eq!(fft[5], (237, 114)); assert_eq!(fft[5], (237, 112));
assert_eq!(fft[10], (517, 110)); assert_eq!(fft[10], (517, 111));
// Sweep upwards: // Sweep upwards:
run_for_ms(&mut node_exec, 300.0); run_for_ms(&mut node_exec, 300.0);
let fft = run_and_get_fft4096_now(&mut node_exec, 122); let fft = run_and_get_fft4096_now(&mut node_exec, 122);
assert_eq!(fft[0], (2498, 122)); assert_eq!(fft[0], (2509, 123));
assert_eq!(fft[7], (2681, 122)); assert_eq!(fft[7], (2821, 123));
// Sweep at mostly highest point: // Sweep at mostly highest point:
run_for_ms(&mut node_exec, 700.0); run_for_ms(&mut node_exec, 700.0);
@ -274,7 +274,7 @@ fn check_node_delay_fb() {
let idxs_big = collect_signal_changes(&res.0[..], 50); let idxs_big = collect_signal_changes(&res.0[..], 50);
// We expect the signal to be delayed by 20ms: // We expect the signal to be delayed by 20ms:
assert_eq!(idxs_big, vec![(221, 106), (442, 53)]); assert_eq!(idxs_big, vec![(220, 106), (440, 53)]);
} }
#[test] #[test]

View file

@ -153,7 +153,7 @@ fn check_node_sampl_reverse() {
matrix.set_param(dir_p, SAtom::setting(1)); matrix.set_param(dir_p, SAtom::setting(1));
let (rms, min, max) = run_and_get_l_rms_mimax(&mut node_exec, 50.0); let (rms, min, max) = run_and_get_l_rms_mimax(&mut node_exec, 50.0);
assert_rmsmima!((rms, min, max), (0.50059, -0.9997, 0.9997)); assert_rmsmima!((rms, min, max), (0.5003373, -0.9997, 0.9997));
let fft = run_and_get_fft4096(&mut node_exec, 800, 20.0); let fft = run_and_get_fft4096(&mut node_exec, 800, 20.0);
assert_eq!(fft[0], (441, 1023)); assert_eq!(fft[0], (441, 1023));
@ -164,11 +164,11 @@ fn check_node_sampl_reverse() {
matrix.set_param(freq_p, SAtom::param(-0.1)); matrix.set_param(freq_p, SAtom::param(-0.1));
let fft = run_and_get_fft4096(&mut node_exec, 800, 20.0); let fft = run_and_get_fft4096(&mut node_exec, 800, 20.0);
assert_eq!(fft[0], (215, 880)); assert_eq!(fft[0], (215, 881));
matrix.set_param(freq_p, SAtom::param(-0.2)); matrix.set_param(freq_p, SAtom::param(-0.2));
let fft = run_and_get_fft4096(&mut node_exec, 800, 20.0); let fft = run_and_get_fft4096(&mut node_exec, 800, 20.0);
assert_eq!(fft[0], (108, 986)); assert_eq!(fft[0], (108, 987));
matrix.set_param(freq_p, SAtom::param(-0.4)); matrix.set_param(freq_p, SAtom::param(-0.4));
let fft = run_and_get_fft4096(&mut node_exec, 800, 20.0); let fft = run_and_get_fft4096(&mut node_exec, 800, 20.0);
@ -176,7 +176,7 @@ fn check_node_sampl_reverse() {
matrix.set_param(freq_p, SAtom::param(-0.5)); matrix.set_param(freq_p, SAtom::param(-0.5));
let fft = run_and_get_fft4096(&mut node_exec, 800, 20.0); let fft = run_and_get_fft4096(&mut node_exec, 800, 20.0);
assert_eq!(fft[0], (11, 999)); assert_eq!(fft[0], (11, 1000));
matrix.set_param(freq_p, SAtom::param(0.2)); matrix.set_param(freq_p, SAtom::param(0.2));
let fft = run_and_get_fft4096(&mut node_exec, 800, 20.0); let fft = run_and_get_fft4096(&mut node_exec, 800, 20.0);
@ -617,8 +617,8 @@ fn check_node_sampl_rev_2() {
res.0, res.0,
5000, 5000,
vec![ vec![
0.9999773, 0.886596, 0.77321476, 0.6598335, 0.5464522, 0.43307102, 0.31968975, 0.0, 0.88664144, 0.7732602, 0.6598789, 0.54649764, 0.4331164, 0.31973514, 0.20635389,
0.20630851, 0.09292727 0.09297263
] ]
); );