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))
}
/// 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)]
pub struct DelayBuffer<F: Flt> {
data: Vec<F>,
@ -1006,7 +1048,8 @@ impl<F: Flt> DelayBuffer<F> {
/// 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]
pub fn cubic_interpolate_at_s(&self, s_offs: F) -> F {
let data = &self.data[..];
@ -1016,26 +1059,7 @@ impl<F: Flt> DelayBuffer<F> {
let i = (self.wr + len) - offs;
// 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[(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
cubic_interpolate(data, len, i, f::<F>(1.0) - fract)
}
#[inline]

View file

@ -2,7 +2,7 @@
// This file is a part of HexoDSP. Released under GPL-3.0-or-later.
// 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::{DspNode, LedPhaseVals, NodeContext, NodeId, ProcBuf, SAtom};
use crate::nodes::{NodeAudioContext, NodeExecContext};
@ -154,26 +154,7 @@ impl Sampl {
let f = self.phase.fract();
self.phase = j as f64 + f + sr_factor * speed;
// 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 = 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
cubic_interpolate(&sample_data[..], sd_len, i, (1.0 - f) as f32)
}
#[allow(clippy::many_single_char_names)]
@ -188,26 +169,7 @@ impl Sampl {
let f = self.phase.fract();
self.phase = (i % sd_len) as f64 + f + sr_factor * speed;
// 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 = 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
cubic_interpolate(&sample_data[..], sd_len, i, f as f32)
}
#[allow(clippy::float_cmp)]

View file

@ -38,35 +38,35 @@ fn check_node_allp() {
// starts with original signal * -0.7
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
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:
// XXX: the smearing before and after the allpass is due to the
// 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]);
// 1ms allpass silence like before:
v.append(&mut vec![0.54748523, 0.13158606, -0.016065884]);
v.append(&mut vec![0.0; (1.0 * 44.1_f32).floor() as usize - 5]);
v.append(&mut vec![0.5260659, 0.37841395, -0.03748519]);
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,
// including even more smearing due to cubic interpolation:
v.append(&mut vec![
-0.0019286226,
0.04086761,
-0.1813516,
-0.35157663,
-0.36315754,
-0.35664573,
-0.00035427228,
0.006157537,
-0.005423375,
-0.1756484,
-0.39786762,
-0.3550714,
]);
v.append(&mut vec![-0.357; (2.0 * 44.1_f32).floor() as usize - 5]);
v.append(&mut vec![
-0.3550714,
-0.39786762,
-0.1756484,
-0.005423375,
0.006157537,
-0.00035427228,
-0.35664573,
-0.36315754,
-0.35157663,
-0.1813516,
0.04086761,
-0.0019286226,
]);
v.append(&mut vec![0.0; 10]);

View file

@ -36,45 +36,28 @@ fn check_node_comb_1() {
(0, 216),
(11, 221),
(22, 216),
(3370, 206),
(3381, 248),
(3391, 191),
(6740, 185),
(6751, 207),
(6761, 195),
(10131, 215),
(10142, 210),
(10153, 213),
(10164, 201),
(20338, 187),
(20349, 184)
(3381, 184),
(3391, 189),
(3402, 195),
(3413, 213),
(3424, 198),
(3435, 203),
(6815, 188),
(13587, 196),
(13598, 210),
(13609, 207),
(13620, 193)
]
);
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);
assert_eq!(
fft,
vec![(1001, 206), (2993, 196), (3004, 219), (3994, 197), (6998, 211), (8000, 201)]
);
assert_eq!(fft, vec![(1001, 186), (3015, 186), (7031, 191)]);
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);
assert_eq!(
fft,
vec![
(0, 2003),
(11, 1015),
(991, 1078),
(1001, 1837),
(2003, 1059),
(2993, 1420),
(3004, 1775),
(3994, 1297),
(4005, 1485)
]
);
assert_eq!(fft, vec![(0, 2008), (11, 1017)]);
}
#[test]

View file

@ -68,16 +68,16 @@ fn check_node_delay_1() {
0.0,
0.0,
// delayed burst of sine for 100ms:
0.047408286,
-0.17181452,
0.2669317,
-0.22377986,
0.000059626997,
0.24652793,
-0.30384338,
0.2087649,
-0.070256576,
0.000003647874,
0.039102618,
-0.16390327,
0.27611724,
-0.2608055,
0.060164057,
0.20197779,
-0.28871512,
0.21515398,
-0.081471935,
0.0023831273,
// silence afterwards:
0.0,
0.0,
@ -119,8 +119,8 @@ fn check_node_delay_2() {
vec![
// 10ms smoothing time for "inp"
0.001133, // 30ms delaytime just mixing the 0.5:
0.5, 0.5, 0.5, // the delayed smoothing ramp (10ms):
0.951113, // the delay + input signal:
0.5, 0.5, 0.5, // the delayed smoothing ramp (10ms):
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
]
);
@ -172,15 +172,15 @@ fn check_node_delay_time_mod() {
let fft = run_and_get_fft4096_now(&mut node_exec, 110);
// Expect a sine sweep over a
// range of low frequencies:
assert_eq!(fft[0], (86, 111));
assert_eq!(fft[5], (237, 114));
assert_eq!(fft[10], (517, 110));
assert_eq!(fft[0], (86, 112));
assert_eq!(fft[5], (237, 112));
assert_eq!(fft[10], (517, 111));
// Sweep upwards:
run_for_ms(&mut node_exec, 300.0);
let fft = run_and_get_fft4096_now(&mut node_exec, 122);
assert_eq!(fft[0], (2498, 122));
assert_eq!(fft[7], (2681, 122));
assert_eq!(fft[0], (2509, 123));
assert_eq!(fft[7], (2821, 123));
// Sweep at mostly highest point:
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);
// 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]

View file

@ -153,7 +153,7 @@ fn check_node_sampl_reverse() {
matrix.set_param(dir_p, SAtom::setting(1));
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);
assert_eq!(fft[0], (441, 1023));
@ -164,11 +164,11 @@ fn check_node_sampl_reverse() {
matrix.set_param(freq_p, SAtom::param(-0.1));
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));
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));
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));
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));
let fft = run_and_get_fft4096(&mut node_exec, 800, 20.0);
@ -617,8 +617,8 @@ fn check_node_sampl_rev_2() {
res.0,
5000,
vec![
0.9999773, 0.886596, 0.77321476, 0.6598335, 0.5464522, 0.43307102, 0.31968975,
0.20630851, 0.09292727
0.0, 0.88664144, 0.7732602, 0.6598789, 0.54649764, 0.4331164, 0.31973514, 0.20635389,
0.09297263
]
);