synfx_dsp/interpolation.rs
1// Copyright (c) 2021-2022 Weird Constructor <weirdconstructor@gmail.com>
2// This file is a part of synfx-dsp. Released under GPL-3.0-or-later.
3// See README.md and COPYING for details.
4
5//! Various interpolation related functions.
6
7use crate::tanh_approx_drive;
8use crate::{f, Flt};
9
10/// Linear crossfade.
11///
12/// * `v1` - signal 1, range -1.0 to 1.0
13/// * `v2` - signal 2, range -1.0 to 1.0
14/// * `mix` - mix position, range 0.0 to 1.0, mid is at 0.5
15#[inline]
16pub fn crossfade<F: Flt>(v1: F, v2: F, mix: F) -> F {
17 v1 * (f::<F>(1.0) - mix) + v2 * mix
18}
19
20/// Linear crossfade with clipping the `v2` result.
21///
22/// This crossfade actually does clip the `v2` signal to the -1.0 to 1.0
23/// range. This is useful for Dry/Wet of plugins that might go beyond the
24/// normal signal range.
25///
26/// * `v1` - signal 1, range -1.0 to 1.0
27/// * `v2` - signal 2, range -1.0 to 1.0
28/// * `mix` - mix position, range 0.0 to 1.0, mid is at 0.5
29#[inline]
30pub fn crossfade_clip<F: Flt>(v1: F, v2: F, mix: F) -> F {
31 v1 * (f::<F>(1.0) - mix) + (v2 * mix).min(f::<F>(1.0)).max(f::<F>(-1.0))
32}
33
34/// Linear (f32) crossfade with driving the `v2` result through a tanh().
35///
36/// * `v1` - signal 1, range -1.0 to 1.0
37/// * `v2` - signal 2, range -1.0 to 1.0
38/// * `mix` - mix position, range 0.0 to 1.0, mid is at 0.5
39#[inline]
40pub fn crossfade_drive_tanh(v1: f32, v2: f32, mix: f32) -> f32 {
41 v1 * (1.0 - mix) + tanh_approx_drive(v2 * mix * 0.111, 0.95) * 0.9999
42}
43
44/// Constant power crossfade.
45///
46/// * `v1` - signal 1, range -1.0 to 1.0
47/// * `v2` - signal 2, range -1.0 to 1.0
48/// * `mix` - mix position, range 0.0 to 1.0, mid is at 0.5
49#[inline]
50pub fn crossfade_cpow(v1: f32, v2: f32, mix: f32) -> f32 {
51 let s1 = (mix * std::f32::consts::FRAC_PI_2).sin();
52 let s2 = ((1.0 - mix) * std::f32::consts::FRAC_PI_2).sin();
53 v1 * s2 + v2 * s1
54}
55
56const CROSS_LOG_MIN: f32 = -13.815510557964274; // (0.000001_f32).ln();
57const CROSS_LOG_MAX: f32 = 0.0; // (1.0_f32).ln();
58
59/// Logarithmic crossfade.
60///
61/// * `v1` - signal 1, range -1.0 to 1.0
62/// * `v2` - signal 2, range -1.0 to 1.0
63/// * `mix` - mix position, range 0.0 to 1.0, mid is at 0.5
64#[inline]
65pub fn crossfade_log(v1: f32, v2: f32, mix: f32) -> f32 {
66 let x = (mix * (CROSS_LOG_MAX - CROSS_LOG_MIN) + CROSS_LOG_MIN).exp();
67 crossfade(v1, v2, x)
68}
69
70/// Exponential crossfade.
71///
72/// * `v1` - signal 1, range -1.0 to 1.0
73/// * `v2` - signal 2, range -1.0 to 1.0
74/// * `mix` - mix position, range 0.0 to 1.0, mid is at 0.5
75#[inline]
76pub fn crossfade_exp(v1: f32, v2: f32, mix: f32) -> f32 {
77 crossfade(v1, v2, mix * mix)
78}
79
80/// Apply linear interpolation between the value a and b.
81///
82/// * `a` - value at x=0.0
83/// * `b` - value at x=1.0
84/// * `x` - value between 0.0 and 1.0 to blend between `a` and `b`.
85#[inline]
86pub fn lerp(x: f32, a: f32, b: f32) -> f32 {
87 (a * (1.0 - x)) + (b * x)
88}
89
90/// Apply 64bit linear interpolation between the value a and b.
91///
92/// * `a` - value at x=0.0
93/// * `b` - value at x=1.0
94/// * `x` - value between 0.0 and 1.0 to blend between `a` and `b`.
95#[inline]
96pub fn lerp64(x: f64, a: f64, b: f64) -> f64 {
97 (a * (1.0 - x)) + (b * x)
98}
99
100/// Hermite / Cubic interpolation of a buffer full of samples at the given _index_.
101/// _len_ is the buffer length to consider and wrap the index into. And _fract_ is the
102/// fractional part of the index.
103///
104/// This function is generic over f32 and f64. That means you can use your preferred float size.
105///
106/// Commonly used like this:
107///
108///```
109/// use synfx_dsp::cubic_interpolate;
110///
111/// let buf : [f32; 9] = [1.0, 0.9, 0.8, 0.7, 0.6, 0.5, 0.4, 0.3, 0.2];
112/// let pos = 3.3_f32;
113///
114/// let i = pos.floor() as usize;
115/// let f = pos.fract();
116///
117/// let res = cubic_interpolate(&buf[..], buf.len(), i, f);
118/// assert!((res - 0.67).abs() < 0.2_f32);
119///```
120#[inline]
121pub fn cubic_interpolate<F: Flt>(data: &[F], len: usize, index: usize, fract: F) -> F {
122 let index = index + len;
123 // Hermite interpolation, take from
124 // https://github.com/eric-wood/delay/blob/main/src/delay.rs#L52
125 //
126 // Thanks go to Eric Wood!
127 //
128 // For the interpolation code:
129 // MIT License, Copyright (c) 2021 Eric Wood
130 let xm1 = data[(index - 1) % len];
131 let x0 = data[index % len];
132 let x1 = data[(index + 1) % len];
133 let x2 = data[(index + 2) % len];
134
135 let c = (x1 - xm1) * f(0.5);
136 let v = x0 - x1;
137 let w = c + v;
138 let a = w + v + (x2 - x0) * f(0.5);
139 let b_neg = w + a;
140
141 let res = (((a * fract) - b_neg) * fract + c) * fract + x0;
142
143 // let rr2 =
144 // x0 + f::<F>(0.5) * fract * (
145 // x1 - xm1 + fract * (
146 // f::<F>(4.0) * x1
147 // + f::<F>(2.0) * xm1
148 // - f::<F>(5.0) * x0
149 // - x2
150 // + fract * (f::<F>(3.0) * (x0 - x1) - xm1 + x2)));
151
152 // eprintln!(
153 // "index={} fract={:6.4} xm1={:6.4} x0={:6.4} x1={:6.4} x2={:6.4} = {:6.4} <> {:6.4}",
154 // index, fract.to_f64().unwrap(), xm1.to_f64().unwrap(), x0.to_f64().unwrap(), x1.to_f64().unwrap(), x2.to_f64().unwrap(),
155 // res.to_f64().unwrap(),
156 // rr2.to_f64().unwrap()
157 // );
158
159 // eprintln!(
160 // "index={} fract={:6.4} xm1={:6.4} x0={:6.4} x1={:6.4} x2={:6.4} = {:6.4}",
161 // index, fract.to_f64().unwrap(), xm1.to_f64().unwrap(), x0.to_f64().unwrap(), x1.to_f64().unwrap(), x2.to_f64().unwrap(),
162 // res.to_f64().unwrap(),
163 // );
164
165 res
166}