HasParameters for BlockAdapter {
impl DSPMeta for BlockAdapter {
type Sample = P::Sample;
+
+ fn set_samplerate(&mut self, samplerate: f32) {
+ self.0.set_samplerate(samplerate);
+ }
+
+ fn latency(&self) -> usize {
+ self.0.latency()
+ }
+
+ fn reset(&mut self) {
+ self.0.reset();
+ }
}
impl, const I: usize, const O: usize> DSPProcess for BlockAdapter {
@@ -123,13 +135,14 @@ pub struct SampleAdapter
where
P: DSPProcessBlock,
{
+ /// Inner block processor
+ pub inner: P,
/// Size of the buffers passed into the inner block processor.
pub buffer_size: usize,
input_buffer: AudioBufferBox,
input_filled: usize,
output_buffer: AudioBufferBox,
output_filled: usize,
- inner: P,
}
impl std::ops::Deref for SampleAdapter
diff --git a/crates/valib-core/src/math/fast.rs b/crates/valib-core/src/math/fast.rs
new file mode 100644
index 0000000..0f0f62f
--- /dev/null
+++ b/crates/valib-core/src/math/fast.rs
@@ -0,0 +1,99 @@
+use crate::Scalar;
+use numeric_literals::replace_float_literals;
+use simba::simd::SimdBool;
+
+/// Rational approximation of tanh(x) which is valid in the range -3..3
+///
+/// This approximation only includes the rational approximation part, and will diverge outside the
+/// bounds. In order to apply the tanh function over a bigger interval, consider clamping either the
+/// input or the output.
+///
+/// You should consider using [`tanh`] for a general-purpose faster tanh function, which uses
+/// branching.
+///
+/// Source:
+///
+/// # Arguments
+///
+/// * `x`: Input value (low-error range: -3..3)
+///
+/// returns: T
+#[replace_float_literals(T::from_f64(literal))]
+pub fn rational_tanh(x: T) -> T {
+ x * (27. + x * x) / (27. + 9. * x * x)
+}
+
+/// Fast approximation of tanh(x).
+///
+/// This approximation uses branching to clamp the output to -1..1 in order to be useful as a
+/// general-purpose approximation of tanh.
+///
+/// Source:
+///
+/// # Arguments
+///
+/// * `x`: Input value
+///
+/// returns: T
+pub fn tanh(x: T) -> T {
+ rational_tanh(x).simd_clamp(-T::one(), T::one())
+}
+
+/// Fast approximation of exp, with maximum error in -1..1 of 0.59%, and in -3.14..3.14 of 9.8%.
+///
+/// You should consider using [`exp`] for a better approximation which uses this function, but
+/// allows a greater range at the cost of branching.
+///
+/// Source:
+///
+/// # Arguments
+///
+/// * `x`: Input value
+///
+/// returns: T
+#[replace_float_literals(T::from_f64(literal))]
+pub fn fast_exp5(x: T) -> T {
+ (120. + x * (120. + x * (60. + x * (20. + x * (5. + x))))) * 0.0083333333
+}
+
+/// Fast approximation of exp, using [`fast_exp5`]. Uses branching to get a bigger range.
+///
+/// Maximum error in the 0..10.58 range is 0.45%.
+///
+/// Source:
+///
+/// # Arguments
+///
+/// * `x`:
+///
+/// returns: T
+#[replace_float_literals(T::from_f64(literal))]
+pub fn exp(x: T) -> T {
+ x.simd_lt(2.5).if_else2(
+ || T::simd_e() * fast_exp5(x - 1.),
+ (|| x.simd_lt(5.), || 33.115452 * fast_exp5(x - 3.5)),
+ || 403.42879 * fast_exp5(x - 6.),
+ )
+}
+
+/// Fast 2^x approximation, using [`exp`].
+///
+/// Maximum error in the 0..15.26 range is 0.45%.
+///
+/// Source:
+///
+/// # Arguments
+///
+/// * `x`:
+///
+/// returns: T
+///
+/// # Examples
+///
+/// ```
+///
+/// ```
+pub fn pow2(x: T) -> T {
+ let log_two = T::simd_ln_2();
+ exp(log_two * x)
+}
diff --git a/crates/valib-core/src/math/interpolation.rs b/crates/valib-core/src/math/interpolation.rs
index 14b6107..2cf2a0c 100644
--- a/crates/valib-core/src/math/interpolation.rs
+++ b/crates/valib-core/src/math/interpolation.rs
@@ -133,9 +133,12 @@ impl Interpolate for Linear {
#[derive(Debug, Copy, Clone)]
pub struct MappedLinear(pub F);
+pub type Sine = MappedLinear T>;
+
/// Returns an interpolator that performs sine interpolation.
-pub fn sine_interpolation() -> MappedLinear T> {
- MappedLinear(|t| T::simd_cos(t * T::simd_pi()))
+#[replace_float_literals(T::from_f64(literal))]
+pub fn sine_interpolation() -> Sine {
+ MappedLinear(|t| 0.5 - 0.5 * T::simd_cos(t * T::simd_pi()))
}
impl T> Interpolate for MappedLinear
diff --git a/crates/valib-core/src/math/mod.rs b/crates/valib-core/src/math/mod.rs
index df8a757..139b036 100644
--- a/crates/valib-core/src/math/mod.rs
+++ b/crates/valib-core/src/math/mod.rs
@@ -8,6 +8,7 @@ use simba::simd::{SimdBool, SimdComplexField};
use crate::Scalar;
+pub mod fast;
pub mod interpolation;
pub mod lut;
pub mod nr;
@@ -86,6 +87,7 @@ pub fn bilinear_prewarming_bounded(samplerate: T, wc: T) -> T {
#[inline]
pub fn smooth_min(t: T, a: T, b: T) -> T {
let r = (-a / t).simd_exp2() + (-b / t).simd_exp2();
+ // let r = fast::pow2(-a / t) + fast::pow2(-b / t);
-t * r.simd_log2()
}
diff --git a/crates/valib-core/src/math/snapshots/valib_core__math__interpolation__tests__valib_core_math_interpolation_MappedLinear_fn(f64) -_ f64_.snap b/crates/valib-core/src/math/snapshots/valib_core__math__interpolation__tests__valib_core_math_interpolation_MappedLinear_fn(f64) -_ f64_.snap
new file mode 100644
index 0000000..ad17605
--- /dev/null
+++ b/crates/valib-core/src/math/snapshots/valib_core__math__interpolation__tests__valib_core_math_interpolation_MappedLinear_fn(f64) -_ f64_.snap
@@ -0,0 +1,16 @@
+---
+source: crates/valib-core/src/math/interpolation.rs
+expression: "&actual as &[_]"
+---
+0.0
+0.146447
+0.5
+0.853553
+1.0
+1.0
+1.0
+1.0
+1.0
+1.0
+1.0
+1.0
diff --git a/crates/valib-core/src/util.rs b/crates/valib-core/src/util.rs
index f8827db..56150c7 100644
--- a/crates/valib-core/src/util.rs
+++ b/crates/valib-core/src/util.rs
@@ -7,7 +7,8 @@ use nalgebra::{
};
use num_traits::{AsPrimitive, Float, Zero};
use numeric_literals::replace_float_literals;
-use simba::simd::SimdValue;
+use simba::simd::{SimdComplexField, SimdValue};
+use std::collections::VecDeque;
/// Transmutes a slice into a slice of static arrays, putting the remainder of the slice not fitting
/// as a separate slice.
@@ -202,6 +203,27 @@ pub fn semitone_to_ratio(semi: T) -> T {
2.0.simd_powf(semi / 12.0)
}
+/// Compute the semitone equivalent change in pitch that would have resulted by multiplying the
+/// input ratio to a frequency value.
+///
+/// # Arguments
+///
+/// * `ratio`: Frequency ratio (unitless)
+///
+/// returns: T
+///
+/// # Examples
+///
+/// ```
+/// use valib_core::util::ratio_to_semitone;
+/// assert_eq!(0., ratio_to_semitone(1.));
+/// assert_eq!(12., ratio_to_semitone(2.));
+/// assert_eq!(-12., ratio_to_semitone(0.5));
+/// ```
+pub fn ratio_to_semitone(ratio: T) -> T {
+ T::from_f64(12.) * ratio.simd_log2()
+}
+
/// Create a new matrix referencing this one as storage. The resulting matrix will have the same
/// shape and same strides as the input one.
///
@@ -264,3 +286,32 @@ pub fn vector_view_mut>(
#[cfg(feature = "test-utils")]
pub mod tests;
+
+#[derive(Debug, Clone)]
+pub struct Rms {
+ data: VecDeque,
+ summed_squared: T,
+}
+
+impl Rms {
+ pub fn new(size: usize) -> Self {
+ Self {
+ data: (0..size).map(|_| T::zero()).collect(),
+ summed_squared: T::zero(),
+ }
+ }
+}
+
+impl Rms {
+ pub fn add_element(&mut self, value: T) -> T {
+ let v2 = value.simd_powi(2);
+ self.summed_squared -= self.data.pop_front().unwrap();
+ self.summed_squared += v2;
+ self.data.push_back(v2);
+ self.get_rms()
+ }
+
+ pub fn get_rms(&self) -> T {
+ self.summed_squared.simd_sqrt()
+ }
+}
diff --git a/crates/valib-filters/src/ladder.rs b/crates/valib-filters/src/ladder.rs
index b2b985a..ff3c82a 100644
--- a/crates/valib-filters/src/ladder.rs
+++ b/crates/valib-filters/src/ladder.rs
@@ -153,8 +153,7 @@ impl> Ladder {
/// let transistor_ladder = Ladder::<_, Transistor>>::new(48000.0, 440.0, 1.0);
/// ```
#[replace_float_literals(T::from_f64(literal))]
- pub fn new(samplerate: impl Into, cutoff: T, resonance: T) -> Self {
- let samplerate = T::from_f64(samplerate.into());
+ pub fn new(samplerate: T, cutoff: T, resonance: T) -> Self {
let mut this = Self {
inv_2fs: T::simd_recip(2.0 * samplerate),
samplerate,
@@ -369,7 +368,7 @@ mod tests {
bode: true,
series: &[Series {
label: "Frequency response",
- samplerate,
+ samplerate: samplerate as f32,
series: &responsef32,
color: &BLUE,
}],
diff --git a/crates/valib-filters/src/snapshots/valib_filters__ladder__tests__test_ladder_ir_valib_filters__ladder__OTA_valib_saturators__Tanh__cfalse_r0.1.snap b/crates/valib-filters/src/snapshots/valib_filters__ladder__tests__test_ladder_ir_valib_filters__ladder__OTA_valib_saturators__Tanh__cfalse_r0.1.snap
index d1128da..fc9ad91 100644
--- a/crates/valib-filters/src/snapshots/valib_filters__ladder__tests__test_ladder_ir_valib_filters__ladder__OTA_valib_saturators__Tanh__cfalse_r0.1.snap
+++ b/crates/valib-filters/src/snapshots/valib_filters__ladder__tests__test_ladder_ir_valib_filters__ladder__OTA_valib_saturators__Tanh__cfalse_r0.1.snap
@@ -11,53 +11,53 @@ expression: output.get_channel(0)
0.005
0.011
0.019
-0.03
-0.045
-0.063
-0.084
-0.108
-0.135
-0.164
-0.195
-0.228
-0.262
-0.297
-0.333
-0.369
-0.405
-0.441
-0.476
-0.51
-0.543
-0.574
-0.605
-0.634
-0.661
-0.686
-0.71
-0.733
-0.753
-0.772
-0.79
-0.806
-0.82
-0.834
-0.845
-0.856
-0.865
-0.874
-0.881
-0.888
-0.893
-0.898
+0.031
+0.046
+0.064
+0.085
+0.11
+0.137
+0.166
+0.198
+0.231
+0.266
+0.301
+0.337
+0.373
+0.409
+0.445
+0.48
+0.514
+0.547
+0.578
+0.609
+0.637
+0.664
+0.69
+0.713
+0.736
+0.756
+0.775
+0.792
+0.808
+0.822
+0.835
+0.847
+0.857
+0.867
+0.875
+0.882
+0.889
+0.894
+0.899
0.903
-0.906
-0.909
+0.907
+0.91
0.912
0.914
0.916
0.917
-0.918
+0.919
0.919
0.92
0.92
@@ -65,7 +65,7 @@ expression: output.get_channel(0)
0.921
0.921
0.921
-0.921
+0.92
0.92
0.92
0.92
diff --git a/crates/valib-filters/src/snapshots/valib_filters__ladder__tests__test_ladder_ir_valib_filters__ladder__OTA_valib_saturators__Tanh__cfalse_r0.5.snap b/crates/valib-filters/src/snapshots/valib_filters__ladder__tests__test_ladder_ir_valib_filters__ladder__OTA_valib_saturators__Tanh__cfalse_r0.5.snap
index 5ed47df..273ce55 100644
--- a/crates/valib-filters/src/snapshots/valib_filters__ladder__tests__test_ladder_ir_valib_filters__ladder__OTA_valib_saturators__Tanh__cfalse_r0.5.snap
+++ b/crates/valib-filters/src/snapshots/valib_filters__ladder__tests__test_ladder_ir_valib_filters__ladder__OTA_valib_saturators__Tanh__cfalse_r0.5.snap
@@ -11,39 +11,39 @@ expression: output.get_channel(0)
0.005
0.011
0.019
-0.03
-0.045
-0.063
-0.084
-0.108
-0.135
-0.164
-0.195
-0.228
-0.261
-0.296
-0.331
-0.367
-0.402
-0.436
-0.469
-0.501
-0.532
-0.561
-0.588
-0.613
-0.636
-0.657
-0.676
-0.692
-0.707
-0.72
-0.73
-0.739
-0.746
-0.751
-0.755
-0.757
+0.031
+0.046
+0.064
+0.085
+0.11
+0.137
+0.166
+0.198
+0.231
+0.265
+0.3
+0.335
+0.37
+0.405
+0.44
+0.473
+0.505
+0.535
+0.564
+0.591
+0.616
+0.639
+0.66
+0.678
+0.695
+0.709
+0.721
+0.732
+0.74
+0.747
+0.752
+0.756
+0.758
0.759
0.759
0.758
@@ -52,23 +52,23 @@ expression: output.get_channel(0)
0.75
0.746
0.742
-0.738
-0.733
-0.728
+0.737
+0.732
+0.727
0.723
0.718
0.713
-0.709
+0.708
0.704
-0.7
-0.696
-0.692
+0.699
+0.695
+0.691
0.688
0.685
0.682
0.679
-0.677
-0.675
+0.676
+0.674
0.673
0.671
0.67
@@ -86,15 +86,15 @@ expression: output.get_channel(0)
0.669
0.669
0.67
-0.67
+0.671
0.671
0.672
-0.672
+0.673
0.673
0.674
0.674
0.675
-0.675
+0.676
0.676
0.676
0.677
@@ -136,7 +136,6 @@ expression: output.get_channel(0)
0.678
0.678
0.678
-0.678
0.677
0.677
0.677
@@ -1026,3 +1025,4 @@ expression: output.get_channel(0)
0.678
0.678
0.678
+0.678
diff --git a/crates/valib-filters/src/snapshots/valib_filters__ladder__tests__test_ladder_ir_valib_filters__ladder__OTA_valib_saturators__Tanh__cfalse_r0.snap b/crates/valib-filters/src/snapshots/valib_filters__ladder__tests__test_ladder_ir_valib_filters__ladder__OTA_valib_saturators__Tanh__cfalse_r0.snap
index bcbb12a..3403312 100644
--- a/crates/valib-filters/src/snapshots/valib_filters__ladder__tests__test_ladder_ir_valib_filters__ladder__OTA_valib_saturators__Tanh__cfalse_r0.snap
+++ b/crates/valib-filters/src/snapshots/valib_filters__ladder__tests__test_ladder_ir_valib_filters__ladder__OTA_valib_saturators__Tanh__cfalse_r0.snap
@@ -11,67 +11,67 @@ expression: output.get_channel(0)
0.005
0.011
0.019
-0.03
-0.045
-0.063
-0.084
-0.108
-0.135
-0.164
-0.195
-0.228
-0.262
-0.298
-0.334
-0.37
-0.406
-0.442
-0.477
-0.512
-0.545
-0.578
-0.609
-0.639
-0.667
-0.694
-0.719
-0.743
-0.765
-0.786
-0.805
-0.823
-0.839
-0.854
-0.868
-0.881
-0.893
-0.903
-0.913
-0.922
-0.93
-0.937
-0.944
-0.95
-0.955
-0.96
-0.964
-0.968
+0.031
+0.046
+0.064
+0.085
+0.11
+0.137
+0.166
+0.198
+0.231
+0.266
+0.301
+0.337
+0.374
+0.41
+0.446
+0.481
+0.516
+0.549
+0.582
+0.613
+0.642
+0.671
+0.697
+0.722
+0.746
+0.768
+0.788
+0.807
+0.825
+0.841
+0.856
+0.87
+0.883
+0.894
+0.905
+0.914
+0.923
+0.931
+0.938
+0.945
+0.951
+0.956
+0.961
+0.965
+0.969
0.972
0.975
0.978
0.98
-0.982
-0.984
+0.983
+0.985
0.986
0.988
0.989
-0.99
+0.991
0.992
0.993
-0.993
+0.994
0.994
0.995
-0.995
+0.996
0.996
0.997
0.997
diff --git a/crates/valib-filters/src/snapshots/valib_filters__ladder__tests__test_ladder_ir_valib_filters__ladder__OTA_valib_saturators__Tanh__cfalse_r1.snap b/crates/valib-filters/src/snapshots/valib_filters__ladder__tests__test_ladder_ir_valib_filters__ladder__OTA_valib_saturators__Tanh__cfalse_r1.snap
index 527fefc..5239649 100644
--- a/crates/valib-filters/src/snapshots/valib_filters__ladder__tests__test_ladder_ir_valib_filters__ladder__OTA_valib_saturators__Tanh__cfalse_r1.snap
+++ b/crates/valib-filters/src/snapshots/valib_filters__ladder__tests__test_ladder_ir_valib_filters__ladder__OTA_valib_saturators__Tanh__cfalse_r1.snap
@@ -11,85 +11,85 @@ expression: output.get_channel(0)
0.005
0.011
0.019
-0.03
-0.045
-0.063
-0.084
-0.108
-0.134
-0.163
-0.194
-0.227
-0.26
-0.295
-0.329
-0.363
-0.397
-0.43
-0.461
-0.49
-0.518
-0.543
-0.566
-0.587
-0.605
-0.62
-0.632
-0.642
-0.65
-0.654
+0.031
+0.046
+0.064
+0.085
+0.11
+0.137
+0.166
+0.197
+0.23
+0.264
+0.298
+0.333
+0.367
+0.401
+0.433
+0.464
+0.494
+0.521
+0.546
+0.569
+0.589
+0.607
+0.622
+0.634
+0.644
+0.651
+0.655
0.657
0.657
0.656
0.652
-0.647
+0.646
0.64
0.632
-0.624
-0.614
-0.604
-0.593
-0.582
-0.571
-0.56
-0.549
-0.538
-0.528
-0.519
-0.51
-0.502
-0.495
-0.489
-0.483
+0.623
+0.613
+0.602
+0.592
+0.58
+0.569
+0.558
+0.548
+0.537
+0.527
+0.518
+0.509
+0.501
+0.494
+0.488
+0.482
0.478
0.474
0.471
0.469
-0.468
+0.467
0.467
0.467
0.467
0.469
0.47
-0.472
+0.473
0.475
0.478
0.481
-0.484
+0.485
0.488
0.491
0.495
0.498
-0.501
+0.502
0.505
0.508
0.511
-0.513
+0.514
0.516
-0.518
-0.52
+0.519
+0.521
0.522
-0.523
+0.524
0.525
0.526
0.526
@@ -114,7 +114,7 @@ expression: output.get_channel(0)
0.513
0.512
0.511
-0.511
+0.51
0.51
0.509
0.509
@@ -157,7 +157,7 @@ expression: output.get_channel(0)
0.514
0.514
0.514
-0.514
+0.513
0.513
0.513
0.513
diff --git a/crates/valib-filters/src/snapshots/valib_filters__ladder__tests__test_ladder_ir_valib_filters__ladder__OTA_valib_saturators__Tanh__ctrue_r0.1.snap b/crates/valib-filters/src/snapshots/valib_filters__ladder__tests__test_ladder_ir_valib_filters__ladder__OTA_valib_saturators__Tanh__ctrue_r0.1.snap
index 04eb70d..8a67d82 100644
--- a/crates/valib-filters/src/snapshots/valib_filters__ladder__tests__test_ladder_ir_valib_filters__ladder__OTA_valib_saturators__Tanh__ctrue_r0.1.snap
+++ b/crates/valib-filters/src/snapshots/valib_filters__ladder__tests__test_ladder_ir_valib_filters__ladder__OTA_valib_saturators__Tanh__ctrue_r0.1.snap
@@ -8,52 +8,52 @@ expression: output.get_channel(0)
0.0
0.0
0.002
-0.005
+0.006
0.011
0.02
-0.032
-0.048
-0.067
-0.089
-0.115
-0.144
-0.175
-0.209
-0.244
-0.281
-0.319
-0.358
-0.398
-0.437
-0.476
-0.514
-0.552
-0.588
-0.623
-0.657
-0.689
-0.719
-0.748
-0.775
-0.8
-0.823
-0.844
-0.864
-0.882
-0.898
-0.913
-0.926
-0.938
-0.949
-0.959
-0.967
-0.975
-0.981
-0.987
+0.033
+0.049
+0.068
+0.091
+0.117
+0.146
+0.178
+0.212
+0.248
+0.285
+0.324
+0.363
+0.402
+0.442
+0.481
+0.519
+0.557
+0.593
+0.628
+0.662
+0.694
+0.724
+0.752
+0.779
+0.803
+0.826
+0.847
+0.867
+0.885
+0.901
+0.915
+0.928
+0.94
+0.951
+0.96
+0.968
+0.976
+0.982
+0.988
0.992
0.996
-0.999
-1.002
+1.0
+1.003
1.005
1.007
1.009
@@ -79,7 +79,7 @@ expression: output.get_channel(0)
1.009
1.008
1.008
-1.008
+1.007
1.007
1.007
1.007
diff --git a/crates/valib-filters/src/snapshots/valib_filters__ladder__tests__test_ladder_ir_valib_filters__ladder__OTA_valib_saturators__Tanh__ctrue_r0.5.snap b/crates/valib-filters/src/snapshots/valib_filters__ladder__tests__test_ladder_ir_valib_filters__ladder__OTA_valib_saturators__Tanh__ctrue_r0.5.snap
index 1fbd9ec..c922b1b 100644
--- a/crates/valib-filters/src/snapshots/valib_filters__ladder__tests__test_ladder_ir_valib_filters__ladder__OTA_valib_saturators__Tanh__ctrue_r0.5.snap
+++ b/crates/valib-filters/src/snapshots/valib_filters__ladder__tests__test_ladder_ir_valib_filters__ladder__OTA_valib_saturators__Tanh__ctrue_r0.5.snap
@@ -10,74 +10,74 @@ expression: output.get_channel(0)
0.002
0.006
0.013
-0.023
-0.037
-0.055
-0.078
-0.105
-0.136
-0.17
-0.209
-0.25
-0.294
-0.34
-0.388
-0.438
-0.488
-0.538
-0.588
-0.638
-0.687
-0.734
-0.779
-0.822
-0.863
-0.901
-0.937
-0.969
-0.998
-1.024
-1.047
-1.068
-1.085
-1.099
-1.111
-1.12
-1.127
-1.131
-1.134
-1.135
-1.134
+0.024
+0.038
+0.057
+0.08
+0.107
+0.139
+0.175
+0.214
+0.256
+0.301
+0.348
+0.397
+0.446
+0.497
+0.548
+0.599
+0.648
+0.697
+0.744
+0.789
+0.832
+0.872
+0.91
+0.945
+0.976
+1.005
+1.031
+1.053
+1.072
+1.089
+1.103
+1.114
+1.122
+1.129
1.133
+1.135
+1.136
+1.135
+1.132
1.129
-1.125
-1.12
-1.114
-1.108
-1.101
-1.094
-1.087
-1.08
-1.073
-1.066
-1.059
-1.053
-1.046
-1.04
-1.035
-1.03
-1.025
-1.021
-1.017
-1.014
-1.011
+1.124
+1.119
+1.113
+1.107
+1.1
+1.093
+1.086
+1.078
+1.071
+1.064
+1.058
+1.051
+1.045
+1.039
+1.034
+1.029
+1.024
+1.02
+1.016
+1.013
+1.01
1.008
1.006
1.004
1.003
1.002
1.001
-1.001
+1.0
1.0
1.0
1.0
@@ -86,11 +86,11 @@ expression: output.get_channel(0)
1.002
1.003
1.004
-1.004
1.005
1.006
1.007
1.008
+1.008
1.009
1.01
1.011
@@ -103,7 +103,7 @@ expression: output.get_channel(0)
1.016
1.017
1.017
-1.017
+1.018
1.018
1.018
1.018
@@ -123,7 +123,7 @@ expression: output.get_channel(0)
1.018
1.018
1.018
-1.018
+1.017
1.017
1.017
1.017
@@ -170,7 +170,7 @@ expression: output.get_channel(0)
1.016
1.016
1.016
-1.016
+1.017
1.017
1.017
1.017
diff --git a/crates/valib-filters/src/snapshots/valib_filters__ladder__tests__test_ladder_ir_valib_filters__ladder__OTA_valib_saturators__Tanh__ctrue_r0.snap b/crates/valib-filters/src/snapshots/valib_filters__ladder__tests__test_ladder_ir_valib_filters__ladder__OTA_valib_saturators__Tanh__ctrue_r0.snap
index bcbb12a..3403312 100644
--- a/crates/valib-filters/src/snapshots/valib_filters__ladder__tests__test_ladder_ir_valib_filters__ladder__OTA_valib_saturators__Tanh__ctrue_r0.snap
+++ b/crates/valib-filters/src/snapshots/valib_filters__ladder__tests__test_ladder_ir_valib_filters__ladder__OTA_valib_saturators__Tanh__ctrue_r0.snap
@@ -11,67 +11,67 @@ expression: output.get_channel(0)
0.005
0.011
0.019
-0.03
-0.045
-0.063
-0.084
-0.108
-0.135
-0.164
-0.195
-0.228
-0.262
-0.298
-0.334
-0.37
-0.406
-0.442
-0.477
-0.512
-0.545
-0.578
-0.609
-0.639
-0.667
-0.694
-0.719
-0.743
-0.765
-0.786
-0.805
-0.823
-0.839
-0.854
-0.868
-0.881
-0.893
-0.903
-0.913
-0.922
-0.93
-0.937
-0.944
-0.95
-0.955
-0.96
-0.964
-0.968
+0.031
+0.046
+0.064
+0.085
+0.11
+0.137
+0.166
+0.198
+0.231
+0.266
+0.301
+0.337
+0.374
+0.41
+0.446
+0.481
+0.516
+0.549
+0.582
+0.613
+0.642
+0.671
+0.697
+0.722
+0.746
+0.768
+0.788
+0.807
+0.825
+0.841
+0.856
+0.87
+0.883
+0.894
+0.905
+0.914
+0.923
+0.931
+0.938
+0.945
+0.951
+0.956
+0.961
+0.965
+0.969
0.972
0.975
0.978
0.98
-0.982
-0.984
+0.983
+0.985
0.986
0.988
0.989
-0.99
+0.991
0.992
0.993
-0.993
+0.994
0.994
0.995
-0.995
+0.996
0.996
0.997
0.997
diff --git a/crates/valib-filters/src/snapshots/valib_filters__ladder__tests__test_ladder_ir_valib_filters__ladder__OTA_valib_saturators__Tanh__ctrue_r1.snap b/crates/valib-filters/src/snapshots/valib_filters__ladder__tests__test_ladder_ir_valib_filters__ladder__OTA_valib_saturators__Tanh__ctrue_r1.snap
index f6d8e7f..bf8ef22 100644
--- a/crates/valib-filters/src/snapshots/valib_filters__ladder__tests__test_ladder_ir_valib_filters__ladder__OTA_valib_saturators__Tanh__ctrue_r1.snap
+++ b/crates/valib-filters/src/snapshots/valib_filters__ladder__tests__test_ladder_ir_valib_filters__ladder__OTA_valib_saturators__Tanh__ctrue_r1.snap
@@ -7,141 +7,141 @@ expression: output.get_channel(0)
0.0
0.0
0.001
-0.002
+0.003
0.007
0.014
0.025
-0.04
-0.06
-0.085
-0.115
-0.149
-0.188
-0.232
-0.279
-0.33
-0.384
-0.44
-0.499
-0.559
-0.62
-0.682
-0.743
-0.804
-0.863
-0.92
-0.974
-1.025
-1.073
-1.116
-1.155
-1.19
-1.219
-1.244
-1.263
-1.278
-1.288
-1.294
-1.296
+0.041
+0.062
+0.087
+0.118
+0.153
+0.194
+0.238
+0.287
+0.339
+0.394
+0.452
+0.512
+0.573
+0.635
+0.698
+0.759
+0.82
+0.879
+0.936
+0.99
+1.041
+1.088
+1.13
+1.168
+1.201
+1.229
+1.252
+1.27
+1.284
+1.293
+1.297
+1.297
1.294
-1.288
-1.279
-1.268
-1.254
-1.238
-1.221
-1.202
-1.182
-1.162
-1.141
-1.121
-1.101
-1.081
-1.062
-1.044
-1.027
-1.012
-0.998
-0.985
-0.974
-0.964
-0.957
-0.95
-0.945
-0.942
-0.94
-0.94
+1.287
+1.278
+1.265
+1.251
+1.234
+1.216
+1.197
+1.177
+1.156
+1.135
+1.115
+1.094
+1.075
+1.056
+1.038
+1.022
+1.007
+0.993
+0.981
+0.97
+0.961
+0.954
+0.948
+0.943
+0.941
+0.939
+0.939
0.94
0.942
0.945
-0.948
-0.953
-0.958
-0.963
-0.969
-0.976
-0.982
-0.989
-0.995
-1.002
-1.008
-1.014
-1.019
-1.025
-1.03
-1.034
-1.038
-1.041
-1.044
-1.047
+0.949
+0.954
+0.959
+0.965
+0.971
+0.978
+0.984
+0.991
+0.997
+1.004
+1.01
+1.016
+1.021
+1.027
+1.031
+1.036
+1.039
+1.043
+1.045
+1.048
1.049
-1.05
1.051
1.052
1.052
1.052
+1.052
1.051
1.05
1.049
1.047
-1.046
+1.045
1.044
1.042
1.04
-1.038
-1.036
-1.034
-1.032
-1.03
+1.037
+1.035
+1.033
+1.031
+1.029
1.028
1.026
-1.025
+1.024
1.023
-1.022
1.021
1.02
1.019
1.018
-1.017
+1.018
1.017
1.017
1.016
1.016
+1.016
1.017
1.017
1.017
1.017
1.018
-1.018
+1.019
1.019
1.02
1.02
1.021
-1.021
1.022
-1.023
+1.022
1.023
1.024
+1.024
1.025
1.025
1.026
@@ -151,7 +151,7 @@ expression: output.get_channel(0)
1.027
1.027
1.027
-1.027
+1.028
1.028
1.028
1.028
@@ -167,7 +167,7 @@ expression: output.get_channel(0)
1.026
1.026
1.026
-1.026
+1.025
1.025
1.025
1.025
diff --git a/crates/valib-filters/src/snapshots/valib_filters__svf__tests__svf_hz.snap b/crates/valib-filters/src/snapshots/valib_filters__svf__tests__svf_hz.snap
index 073b805..d057a03 100644
--- a/crates/valib-filters/src/snapshots/valib_filters__svf__tests__svf_hz.snap
+++ b/crates/valib-filters/src/snapshots/valib_filters__svf__tests__svf_hz.snap
@@ -3,159 +3,159 @@ source: crates/valib-filters/src/svf.rs
expression: "&hz as &[_]"
---
1.0,0.0,0.0
-1.01,0.101,0.01
-1.04,0.208,0.042
-1.094,0.328,0.098
-1.179,0.471,0.189
-1.308,0.654,0.327
-1.504,0.903,0.542
-1.814,1.27,0.889
-2.312,1.85,1.48
-3.031,2.728,2.456
-3.332,3.333,3.334
-2.553,2.809,3.091
-1.756,2.108,2.53
-1.259,1.638,2.13
-0.952,1.334,1.869
-0.751,1.127,1.692
-0.611,0.979,1.567
-0.509,0.867,1.475
-0.433,0.78,1.405
-0.373,0.71,1.35
-0.326,0.652,1.306
-0.287,0.604,1.271
-0.256,0.563,1.241
-0.229,0.528,1.217
-0.207,0.497,1.196
-0.188,0.47,1.178
-0.171,0.446,1.162
-0.157,0.424,1.149
-0.144,0.405,1.137
-0.133,0.387,1.126
-0.123,0.371,1.117
-0.115,0.357,1.109
-0.107,0.343,1.102
-0.1,0.331,1.095
-0.094,0.319,1.089
-0.088,0.308,1.083
-0.083,0.298,1.079
-0.078,0.289,1.074
-0.073,0.28,1.07
-0.069,0.272,1.066
-0.066,0.264,1.063
-0.062,0.257,1.059
-0.059,0.25,1.056
-0.056,0.244,1.054
-0.054,0.237,1.051
-0.051,0.232,1.049
-0.049,0.226,1.047
-0.047,0.221,1.044
-0.045,0.216,1.043
-0.043,0.211,1.041
-0.041,0.206,1.039
-0.039,0.202,1.037
-0.038,0.198,1.036
-0.036,0.193,1.034
-0.035,0.19,1.033
-0.033,0.186,1.032
-0.032,0.182,1.031
-0.031,0.179,1.03
-0.03,0.175,1.029
-0.029,0.172,1.028
-0.028,0.169,1.027
-0.027,0.166,1.026
-0.026,0.163,1.025
-0.025,0.161,1.024
-0.024,0.158,1.023
-0.024,0.155,1.022
-0.023,0.153,1.022
-0.022,0.15,1.021
-0.021,0.148,1.02
-0.021,0.146,1.02
-0.02,0.143,1.019
-0.02,0.141,1.019
-0.019,0.139,1.018
-0.018,0.137,1.018
-0.018,0.135,1.017
-0.017,0.133,1.017
-0.017,0.131,1.016
-0.016,0.129,1.016
-0.016,0.128,1.015
-0.016,0.126,1.015
-0.015,0.124,1.015
-0.015,0.123,1.014
-0.014,0.121,1.014
-0.014,0.119,1.013
-0.014,0.118,1.013
-0.013,0.116,1.013
-0.013,0.115,1.012
-0.013,0.114,1.012
-0.012,0.112,1.012
-0.012,0.111,1.012
-0.012,0.109,1.011
-0.012,0.108,1.011
-0.011,0.107,1.011
-0.011,0.106,1.011
-0.011,0.104,1.01
-0.011,0.103,1.01
-0.01,0.102,1.01
-0.01,0.101,1.01
-0.01,0.1,1.009
-0.01,0.099,1.009
-0.009,0.098,1.009
-0.009,0.097,1.009
-0.009,0.096,1.009
-0.009,0.095,1.008
-0.009,0.094,1.008
-0.009,0.093,1.008
-0.008,0.092,1.008
-0.008,0.091,1.008
-0.008,0.09,1.008
-0.008,0.089,1.007
-0.008,0.088,1.007
-0.008,0.087,1.007
-0.007,0.086,1.007
-0.007,0.086,1.007
-0.007,0.085,1.007
-0.007,0.084,1.007
-0.007,0.083,1.007
+1.008,0.101,0.01
+1.034,0.207,0.041
+1.078,0.323,0.097
+1.145,0.458,0.183
+1.238,0.619,0.31
+1.362,0.817,0.49
+1.514,1.06,0.742
+1.667,1.334,1.067
+1.747,1.573,1.416
+1.666,1.667,1.667
+1.443,1.588,1.747
+1.184,1.421,1.706
+0.959,1.247,1.622
+0.783,1.096,1.536
+0.648,0.973,1.46
+0.545,0.872,1.397
+0.465,0.79,1.345
+0.401,0.723,1.302
+0.35,0.666,1.267
+0.309,0.618,1.237
+0.274,0.577,1.212
+0.245,0.541,1.191
+0.221,0.509,1.173
+0.2,0.481,1.158
+0.182,0.457,1.144
+0.167,0.435,1.132
+0.153,0.415,1.122
+0.141,0.396,1.113
+0.131,0.38,1.104
+0.121,0.365,1.097
+0.113,0.351,1.09
+0.105,0.338,1.084
+0.098,0.326,1.079
+0.092,0.315,1.074
+0.087,0.304,1.07
+0.082,0.295,1.066
+0.077,0.286,1.062
+0.073,0.277,1.059
+0.069,0.269,1.056
+0.065,0.262,1.053
+0.062,0.255,1.05
+0.059,0.248,1.048
+0.056,0.242,1.045
+0.053,0.236,1.043
+0.051,0.23,1.041
+0.048,0.224,1.039
+0.046,0.219,1.038
+0.044,0.214,1.036
+0.042,0.21,1.035
+0.041,0.205,1.033
+0.039,0.201,1.032
+0.037,0.196,1.03
+0.036,0.192,1.029
+0.035,0.189,1.028
+0.033,0.185,1.027
+0.032,0.181,1.026
+0.031,0.178,1.025
+0.03,0.175,1.024
+0.029,0.172,1.023
+0.028,0.169,1.023
+0.027,0.166,1.022
+0.026,0.163,1.021
+0.025,0.16,1.02
+0.024,0.157,1.02
+0.023,0.155,1.019
+0.023,0.152,1.019
+0.022,0.15,1.018
+0.021,0.147,1.017
+0.021,0.145,1.017
+0.02,0.143,1.016
+0.02,0.141,1.016
+0.019,0.139,1.015
+0.018,0.137,1.015
+0.018,0.135,1.015
+0.017,0.133,1.014
+0.017,0.131,1.014
+0.016,0.129,1.013
+0.016,0.127,1.013
+0.016,0.126,1.013
+0.015,0.124,1.012
+0.015,0.122,1.012
+0.014,0.121,1.012
+0.014,0.119,1.011
+0.014,0.118,1.011
+0.013,0.116,1.011
+0.013,0.115,1.011
+0.013,0.113,1.01
+0.012,0.112,1.01
+0.012,0.111,1.01
+0.012,0.109,1.01
+0.012,0.108,1.009
+0.011,0.107,1.009
+0.011,0.106,1.009
+0.011,0.104,1.009
+0.011,0.103,1.009
+0.01,0.102,1.008
+0.01,0.101,1.008
+0.01,0.1,1.008
+0.01,0.099,1.008
+0.009,0.098,1.008
+0.009,0.097,1.008
+0.009,0.096,1.007
+0.009,0.095,1.007
+0.009,0.094,1.007
+0.009,0.093,1.007
+0.008,0.092,1.007
+0.008,0.091,1.007
+0.008,0.09,1.007
+0.008,0.089,1.006
+0.008,0.088,1.006
+0.008,0.087,1.006
+0.007,0.086,1.006
+0.007,0.085,1.006
+0.007,0.085,1.006
+0.007,0.084,1.006
+0.007,0.083,1.006
0.007,0.082,1.006
-0.007,0.082,1.006
-0.006,0.081,1.006
-0.006,0.08,1.006
-0.006,0.079,1.006
-0.006,0.079,1.006
-0.006,0.078,1.006
-0.006,0.077,1.006
-0.006,0.076,1.006
+0.007,0.081,1.005
+0.006,0.081,1.005
+0.006,0.08,1.005
+0.006,0.079,1.005
+0.006,0.078,1.005
+0.006,0.078,1.005
+0.006,0.077,1.005
+0.006,0.076,1.005
0.006,0.076,1.005
0.006,0.075,1.005
0.006,0.074,1.005
-0.005,0.074,1.005
-0.005,0.073,1.005
-0.005,0.073,1.005
-0.005,0.072,1.005
-0.005,0.071,1.005
-0.005,0.071,1.005
-0.005,0.07,1.005
-0.005,0.07,1.005
-0.005,0.069,1.005
+0.005,0.074,1.004
+0.005,0.073,1.004
+0.005,0.072,1.004
+0.005,0.072,1.004
+0.005,0.071,1.004
+0.005,0.071,1.004
+0.005,0.07,1.004
+0.005,0.069,1.004
+0.005,0.069,1.004
0.005,0.068,1.004
0.005,0.068,1.004
0.005,0.067,1.004
0.004,0.067,1.004
0.004,0.066,1.004
0.004,0.066,1.004
-0.004,0.065,1.004
-0.004,0.065,1.004
-0.004,0.064,1.004
-0.004,0.064,1.004
-0.004,0.063,1.004
-0.004,0.063,1.004
-0.004,0.062,1.004
-0.004,0.062,1.004
-0.004,0.061,1.004
-0.004,0.061,1.004
+0.004,0.065,1.003
+0.004,0.065,1.003
+0.004,0.064,1.003
+0.004,0.064,1.003
+0.004,0.063,1.003
+0.004,0.063,1.003
+0.004,0.062,1.003
+0.004,0.062,1.003
+0.004,0.061,1.003
+0.004,0.061,1.003
0.004,0.06,1.003
0.004,0.06,1.003
0.004,0.059,1.003
@@ -167,18 +167,18 @@ expression: "&hz as &[_]"
0.003,0.057,1.003
0.003,0.056,1.003
0.003,0.056,1.003
-0.003,0.056,1.003
0.003,0.055,1.003
-0.003,0.055,1.003
-0.003,0.054,1.003
-0.003,0.054,1.003
-0.003,0.054,1.003
-0.003,0.053,1.003
-0.003,0.053,1.003
-0.003,0.052,1.003
-0.003,0.052,1.003
-0.003,0.052,1.003
-0.003,0.051,1.003
+0.003,0.055,1.002
+0.003,0.055,1.002
+0.003,0.054,1.002
+0.003,0.054,1.002
+0.003,0.054,1.002
+0.003,0.053,1.002
+0.003,0.053,1.002
+0.003,0.052,1.002
+0.003,0.052,1.002
+0.003,0.052,1.002
+0.003,0.051,1.002
0.003,0.051,1.002
0.003,0.051,1.002
0.003,0.05,1.002
@@ -199,24 +199,24 @@ expression: "&hz as &[_]"
0.002,0.045,1.002
0.002,0.045,1.002
0.002,0.045,1.002
-0.002,0.045,1.002
+0.002,0.044,1.002
0.002,0.044,1.002
0.002,0.044,1.002
0.002,0.044,1.002
0.002,0.043,1.002
0.002,0.043,1.002
-0.002,0.043,1.002
-0.002,0.043,1.002
-0.002,0.042,1.002
-0.002,0.042,1.002
-0.002,0.042,1.002
-0.002,0.041,1.002
-0.002,0.041,1.002
-0.002,0.041,1.002
-0.002,0.041,1.002
-0.002,0.04,1.002
-0.002,0.04,1.002
-0.002,0.04,1.002
+0.002,0.043,1.001
+0.002,0.043,1.001
+0.002,0.042,1.001
+0.002,0.042,1.001
+0.002,0.042,1.001
+0.002,0.041,1.001
+0.002,0.041,1.001
+0.002,0.041,1.001
+0.002,0.041,1.001
+0.002,0.04,1.001
+0.002,0.04,1.001
+0.002,0.04,1.001
0.002,0.04,1.001
0.002,0.039,1.001
0.002,0.039,1.001
@@ -294,18 +294,18 @@ expression: "&hz as &[_]"
0.001,0.025,1.001
0.001,0.025,1.001
0.001,0.025,1.001
-0.001,0.025,1.001
-0.001,0.024,1.001
-0.001,0.024,1.001
-0.001,0.024,1.001
-0.001,0.024,1.001
-0.001,0.024,1.001
-0.001,0.024,1.001
-0.001,0.024,1.001
-0.001,0.023,1.001
-0.001,0.023,1.001
-0.001,0.023,1.001
-0.001,0.023,1.001
+0.001,0.025,1.0
+0.001,0.024,1.0
+0.001,0.024,1.0
+0.001,0.024,1.0
+0.001,0.024,1.0
+0.001,0.024,1.0
+0.001,0.024,1.0
+0.001,0.024,1.0
+0.001,0.023,1.0
+0.001,0.023,1.0
+0.001,0.023,1.0
+0.001,0.023,1.0
0.001,0.023,1.0
0.001,0.023,1.0
0.001,0.022,1.0
diff --git a/crates/valib-filters/src/specialized.rs b/crates/valib-filters/src/specialized.rs
index 2930083..4d54004 100644
--- a/crates/valib-filters/src/specialized.rs
+++ b/crates/valib-filters/src/specialized.rs
@@ -7,6 +7,7 @@ use valib_core::Scalar;
use valib_saturators::Linear;
/// Specialized filter that removes DC offsets by applying a 5 Hz biquad highpass filter
+#[derive(Debug, Copy, Clone)]
pub struct DcBlocker(Biquad);
impl DcBlocker {
diff --git a/crates/valib-filters/src/svf.rs b/crates/valib-filters/src/svf.rs
index 745e6fe..70c6a12 100644
--- a/crates/valib-filters/src/svf.rs
+++ b/crates/valib-filters/src/svf.rs
@@ -112,7 +112,7 @@ impl Svf {
pub fn new(samplerate: T, fc: T, r: T) -> Self {
let mut this = Self {
s: [T::zero(); 2],
- r,
+ r: r + r,
fc,
g: T::zero(),
g1: T::zero(),
diff --git a/crates/valib-oscillators/src/lib.rs b/crates/valib-oscillators/src/lib.rs
index 76c665b..91f1305 100644
--- a/crates/valib-oscillators/src/lib.rs
+++ b/crates/valib-oscillators/src/lib.rs
@@ -8,18 +8,30 @@ use valib_core::dsp::DSPProcess;
use valib_core::Scalar;
pub mod blit;
+pub mod polyblep;
pub mod wavetable;
/// Tracks normalized phase for a given frequency. Phase is smooth even when frequency changes, so
/// it is suitable for driving oscillators.
#[derive(Debug, Clone, Copy)]
pub struct Phasor {
+ samplerate: T,
+ frequency: T,
phase: T,
step: T,
}
impl DSPMeta for Phasor {
type Sample = T;
+
+ fn set_samplerate(&mut self, samplerate: f32) {
+ self.samplerate = T::from_f64(samplerate as _);
+ self.set_frequency(self.frequency);
+ }
+
+ fn reset(&mut self) {
+ self.phase = T::zero();
+ }
}
#[profiling::all_functions]
@@ -27,7 +39,7 @@ impl DSPProcess<0, 1> for Phasor {
fn process(&mut self, _: [Self::Sample; 0]) -> [Self::Sample; 1] {
let p = self.phase;
let new_phase = self.phase + self.step;
- let gt = new_phase.simd_gt(T::one());
+ let gt = new_phase.simd_ge(T::one());
self.phase = (new_phase - T::one()).select(gt, new_phase);
[p]
}
@@ -43,13 +55,32 @@ impl Phasor {
///
/// returns: Phasor
#[replace_float_literals(T::from_f64(literal))]
- pub fn new(samplerate: T, freq: T) -> Self {
+ pub fn new(samplerate: T, frequency: T) -> Self {
Self {
+ samplerate,
+ frequency,
phase: 0.0,
- step: freq / samplerate,
+ step: frequency / samplerate,
}
}
+ pub fn phase(&self) -> T {
+ self.phase
+ }
+
+ pub fn set_phase(&mut self, phase: T) {
+ self.phase = phase.simd_fract();
+ }
+
+ pub fn with_phase(mut self, phase: T) -> Self {
+ self.set_phase(phase);
+ self
+ }
+
+ pub fn next_sample_resets(&self) -> T::SimdBool {
+ (self.phase + self.step).simd_ge(T::one())
+ }
+
/// Sets the frequency of this phasor. Phase is not reset, which means the phase remains
/// continuous.
/// # Arguments
@@ -58,7 +89,7 @@ impl Phasor {
/// * `freq`: New frequency
///
/// returns: ()
- pub fn set_frequency(&mut self, samplerate: T, freq: T) {
- self.step = freq / samplerate;
+ pub fn set_frequency(&mut self, freq: T) {
+ self.step = freq / self.samplerate;
}
}
diff --git a/crates/valib-oscillators/src/polyblep.rs b/crates/valib-oscillators/src/polyblep.rs
new file mode 100644
index 0000000..eb132e9
--- /dev/null
+++ b/crates/valib-oscillators/src/polyblep.rs
@@ -0,0 +1,200 @@
+use crate::Phasor;
+use num_traits::{one, zero, ConstOne, ConstZero};
+use std::marker::PhantomData;
+use valib_core::dsp::blocks::P1;
+use valib_core::dsp::{DSPMeta, DSPProcess};
+use valib_core::simd::SimdBool;
+use valib_core::util::lerp;
+use valib_core::Scalar;
+
+pub struct PolyBLEP {
+ pub amplitude: T,
+ pub phase: T,
+}
+
+impl PolyBLEP {
+ pub fn eval(&self, dt: T, phase: T) -> T {
+ let t = T::simd_fract(phase + self.phase);
+ let ret = t.simd_lt(dt).if_else(
+ || {
+ let t = t / dt;
+ t + t - t * t - one()
+ },
+ || {
+ t.simd_gt(one::() - dt).if_else(
+ || {
+ let t = (t - one()) / dt;
+ t * t + t + t + one()
+ },
+ || zero(),
+ )
+ },
+ );
+ self.amplitude * ret
+ }
+}
+
+pub trait PolyBLEPOscillator: DSPMeta {
+ fn bleps(&self) -> impl IntoIterator- >;
+ fn naive_eval(&mut self, phase: Self::Sample) -> Self::Sample;
+}
+
+pub struct PolyBLEPDriver {
+ pub phasor: Phasor,
+ pub blep: Osc,
+ samplerate: Osc::Sample,
+}
+
+impl PolyBLEPDriver {
+ pub fn new(samplerate: Osc::Sample, frequency: Osc::Sample, blep: Osc) -> Self {
+ Self {
+ phasor: Phasor::new(samplerate, frequency),
+ blep,
+ samplerate,
+ }
+ }
+
+ pub fn set_frequency(&mut self, frequency: Osc::Sample) {
+ self.phasor.set_frequency(frequency);
+ }
+}
+
+impl DSPProcess<0, 1> for PolyBLEPDriver {
+ fn process(&mut self, _: [Self::Sample; 0]) -> [Self::Sample; 1] {
+ let [phase] = self.phasor.process([]);
+ let mut y = self.blep.naive_eval(phase);
+ for blep in self.blep.bleps() {
+ y += blep.eval(self.phasor.step, phase);
+ }
+ [y]
+ }
+}
+
+impl DSPMeta for PolyBLEPDriver {
+ type Sample = Osc::Sample;
+
+ fn set_samplerate(&mut self, samplerate: f32) {
+ self.phasor.set_samplerate(samplerate);
+ self.blep.set_samplerate(samplerate);
+ }
+
+ fn latency(&self) -> usize {
+ self.blep.latency()
+ }
+
+ fn reset(&mut self) {
+ self.phasor.reset();
+ self.blep.reset();
+ }
+}
+
+#[derive(Debug, Copy, Clone)]
+pub struct SawBLEP(PhantomData);
+
+impl Default for SawBLEP {
+ fn default() -> Self {
+ Self(PhantomData)
+ }
+}
+
+impl DSPMeta for SawBLEP {
+ type Sample = T;
+}
+
+impl PolyBLEPOscillator for SawBLEP {
+ fn bleps(&self) -> impl IntoIterator
- > {
+ [PolyBLEP {
+ amplitude: -T::ONE,
+ phase: T::ZERO,
+ }]
+ }
+
+ fn naive_eval(&mut self, phase: Self::Sample) -> Self::Sample {
+ T::from_f64(2.0) * phase - T::one()
+ }
+}
+
+pub type Sawtooth = PolyBLEPDriver>;
+
+#[derive(Debug, Copy, Clone)]
+pub struct SquareBLEP {
+ pw: T,
+}
+
+impl SquareBLEP {
+ pub fn new(pulse_width: T) -> Self {
+ Self {
+ pw: pulse_width.simd_clamp(zero(), one()),
+ }
+ }
+}
+
+impl SquareBLEP {
+ pub fn set_pulse_width(&mut self, pw: T) {
+ self.pw = pw.simd_clamp(zero(), one());
+ }
+}
+
+impl DSPMeta for SquareBLEP {
+ type Sample = T;
+}
+
+impl PolyBLEPOscillator for SquareBLEP {
+ fn bleps(&self) -> impl IntoIterator
- > {
+ [
+ PolyBLEP {
+ amplitude: T::ONE,
+ phase: T::ZERO,
+ },
+ PolyBLEP {
+ amplitude: -T::ONE,
+ phase: T::one() - self.pw,
+ },
+ ]
+ }
+
+ fn naive_eval(&mut self, phase: Self::Sample) -> Self::Sample {
+ let dc_offset = lerp(self.pw, -T::ONE, T::ONE);
+ phase.simd_gt(self.pw).if_else(T::one, || -T::one()) + dc_offset
+ }
+}
+
+pub type Square = PolyBLEPDriver>;
+
+pub struct Triangle {
+ square: Square,
+ integrator: P1,
+}
+
+impl DSPMeta for Triangle {
+ type Sample = T;
+ fn set_samplerate(&mut self, samplerate: f32) {
+ self.square.set_samplerate(samplerate);
+ self.integrator.set_samplerate(samplerate);
+ }
+ fn reset(&mut self) {
+ self.square.reset();
+ self.integrator.reset();
+ }
+}
+
+impl DSPProcess<0, 1> for Triangle {
+ fn process(&mut self, []: [Self::Sample; 0]) -> [Self::Sample; 1] {
+ self.integrator.process(self.square.process([]))
+ }
+}
+
+impl Triangle {
+ pub fn new(samplerate: T, frequency: T, phase: T) -> Self {
+ let mut square =
+ PolyBLEPDriver::new(samplerate, frequency, SquareBLEP::new(T::from_f64(0.5)));
+ square.phasor.phase = phase;
+ let integrator = P1::new(samplerate, frequency);
+ Self { square, integrator }
+ }
+
+ pub fn set_frequency(&mut self, frequency: T) {
+ self.square.set_frequency(frequency);
+ self.integrator.set_fc(frequency);
+ }
+}
diff --git a/crates/valib-oversample/src/lib.rs b/crates/valib-oversample/src/lib.rs
index b2562e4..59b93be 100644
--- a/crates/valib-oversample/src/lib.rs
+++ b/crates/valib-oversample/src/lib.rs
@@ -169,6 +169,19 @@ impl ResampleStage {
}
impl ResampleStage {
+ /// Upsample a single sample of audio
+ ///
+ /// # Arguments
+ ///
+ /// * `s`: Input sample
+ ///
+ /// returns: [T; 2]
+ pub fn process(&mut self, s: T) -> [T; 2] {
+ let [x0] = self.filter.process([s + s]);
+ let [x1] = self.filter.process([T::zero()]);
+ [x0, x1]
+ }
+
/// Upsample the input buffer by a factor of 2.
///
/// The output slice should be twice the length of the input slice.
@@ -176,8 +189,7 @@ impl ResampleStage {
pub fn process_block(&mut self, input: &[T], output: &mut [T]) {
assert_eq!(input.len() * 2, output.len());
for (i, s) in input.iter().copied().enumerate() {
- let [x0] = self.filter.process([s + s]);
- let [x1] = self.filter.process([T::zero()]);
+ let [x0, x1] = self.process(s);
output[2 * i + 0] = x0;
output[2 * i + 1] = x1;
}
@@ -185,6 +197,19 @@ impl ResampleStage {
}
impl ResampleStage {
+ /// Downsample 2 samples of input audio, and output a single sample of audio.
+ ///
+ /// # Arguments
+ ///
+ /// * `[x0, x1]`: Inputs samples
+ ///
+ /// returns: T
+ pub fn process(&mut self, [x0, x1]: [T; 2]) -> T {
+ let [y] = self.filter.process([x0]);
+ let _ = self.filter.process([x1]);
+ y
+ }
+
/// Downsample the input buffer by a factor of 2.
///
/// The output slice should be twice the length of the input slice.
diff --git a/crates/valib-saturators/src/lib.rs b/crates/valib-saturators/src/lib.rs
index 387db9b..e99a9bf 100644
--- a/crates/valib-saturators/src/lib.rs
+++ b/crates/valib-saturators/src/lib.rs
@@ -14,6 +14,7 @@ use std::ops;
use clippers::DiodeClipperModel;
use valib_core::dsp::{DSPMeta, DSPProcess};
+use valib_core::math::fast;
use valib_core::Scalar;
pub mod adaa;
@@ -152,13 +153,14 @@ pub struct Tanh;
impl Saturator
for Tanh {
#[inline(always)]
fn saturate(&self, x: S) -> S {
- x.simd_tanh()
+ fast::tanh(x)
}
#[inline(always)]
#[replace_float_literals(S::from_f64(literal))]
fn sat_diff(&self, x: S) -> S {
- 1. - x.simd_tanh().simd_powi(2)
+ let tanh = fast::tanh(x);
+ 1. - tanh * tanh
}
}
diff --git a/crates/valib-voice/src/dynamic.rs b/crates/valib-voice/src/dynamic.rs
new file mode 100644
index 0000000..4fe8494
--- /dev/null
+++ b/crates/valib-voice/src/dynamic.rs
@@ -0,0 +1,282 @@
+use crate::monophonic::Monophonic;
+use crate::polyphonic::Polyphonic;
+use crate::{NoteData, Voice, VoiceManager};
+use std::fmt;
+use std::fmt::Formatter;
+use std::ops::Range;
+use std::sync::Arc;
+use valib_core::dsp::{DSPMeta, DSPProcess};
+
+#[derive(Debug)]
+enum Impl {
+ Monophonic(Monophonic),
+ Polyphonic(Polyphonic),
+}
+
+impl DSPMeta for Impl {
+ type Sample = V::Sample;
+
+ fn set_samplerate(&mut self, samplerate: f32) {
+ match self {
+ Impl::Monophonic(mono) => mono.set_samplerate(samplerate),
+ Impl::Polyphonic(poly) => poly.set_samplerate(samplerate),
+ }
+ }
+
+ fn latency(&self) -> usize {
+ match self {
+ Impl::Monophonic(mono) => mono.latency(),
+ Impl::Polyphonic(poly) => poly.latency(),
+ }
+ }
+
+ fn reset(&mut self) {
+ match self {
+ Impl::Monophonic(mono) => mono.reset(),
+ Impl::Polyphonic(poly) => poly.reset(),
+ }
+ }
+}
+
+pub struct DynamicVoice {
+ pitch_bend_st: Range,
+ poly_voice_capacity: usize,
+ create_voice: Arc) -> V>,
+ current_manager: Impl,
+ legato: bool,
+ samplerate: f32,
+}
+
+impl fmt::Debug for DynamicVoice {
+ fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
+ f.debug_struct("DynamicVoice")
+ .field("pitch_bend_st", &self.pitch_bend_st)
+ .field("poly_voice_capacity", &self.poly_voice_capacity)
+ .field("create_voice", &"Arc) -> V")
+ .field("current_manager", &self.current_manager)
+ .field("legato", &self.legato)
+ .field("samplerate", &self.samplerate)
+ .finish()
+ }
+}
+
+impl DynamicVoice {
+ pub fn new_mono(
+ samplerate: f32,
+ poly_voice_capacity: usize,
+ legato: bool,
+ create_voice: impl 'static + Send + Sync + Fn(f32, NoteData) -> V,
+ ) -> Self {
+ let create_voice = Arc::new(create_voice);
+ let mono = Monophonic::new(
+ samplerate,
+ {
+ let create_voice = create_voice.clone();
+ move |sr, nd| create_voice.clone()(sr, nd)
+ },
+ legato,
+ );
+ let pitch_bend_st = mono.pitch_bend_min_st..mono.pitch_bend_max_st;
+ Self {
+ pitch_bend_st,
+ poly_voice_capacity,
+ create_voice,
+ current_manager: Impl::Monophonic(mono),
+ legato,
+ samplerate,
+ }
+ }
+
+ pub fn new_poly(
+ samplerate: f32,
+ capacity: usize,
+ legato: bool,
+ create_voice: impl 'static + Send + Sync + Fn(f32, NoteData) -> V,
+ ) -> Self {
+ let create_voice = Arc::new(create_voice);
+ let poly = Polyphonic::new(samplerate, capacity, {
+ let create_voice = create_voice.clone();
+ move |sr, nd| create_voice.clone()(sr, nd)
+ });
+ let pitch_bend_st = poly.pitch_bend_st.clone();
+ Self {
+ pitch_bend_st,
+ poly_voice_capacity: capacity,
+ create_voice,
+ current_manager: Impl::Polyphonic(poly),
+ legato,
+ samplerate,
+ }
+ }
+
+ pub fn switch(&mut self, polyphonic: bool) {
+ let new = match self.current_manager {
+ Impl::Monophonic(..) if polyphonic => {
+ let create_voice = self.create_voice.clone();
+ let mut poly =
+ Polyphonic::new(self.samplerate, self.poly_voice_capacity, move |sr, nd| {
+ create_voice.clone()(sr, nd)
+ });
+ poly.pitch_bend_st = self.pitch_bend_st.clone();
+ Impl::Polyphonic(poly)
+ }
+ Impl::Polyphonic(..) if !polyphonic => {
+ let create_voice = self.create_voice.clone();
+ let mut mono = Monophonic::new(
+ self.samplerate,
+ move |sr, nd| create_voice.clone()(sr, nd),
+ self.legato,
+ );
+ mono.pitch_bend_min_st = self.pitch_bend_st.start;
+ mono.pitch_bend_max_st = self.pitch_bend_st.end;
+ Impl::Monophonic(mono)
+ }
+ _ => {
+ return;
+ }
+ };
+ self.current_manager = new;
+ }
+
+ pub fn is_monophonic(&self) -> bool {
+ matches!(self.current_manager, Impl::Monophonic(..))
+ }
+
+ pub fn is_polyphonic(&self) -> bool {
+ matches!(self.current_manager, Impl::Polyphonic(..))
+ }
+
+ pub fn legato(&self) -> bool {
+ self.legato
+ }
+
+ pub fn set_legato(&mut self, legato: bool) {
+ self.legato = legato;
+ if let Impl::Monophonic(ref mut mono) = self.current_manager {
+ mono.set_legato(legato);
+ }
+ }
+
+ pub fn clean_inactive_voices(&mut self) {
+ match self.current_manager {
+ Impl::Monophonic(ref mut mono) => mono.clean_voice_if_inactive(),
+ Impl::Polyphonic(ref mut poly) => poly.clean_inactive_voices(),
+ }
+ }
+}
+
+impl DSPMeta for DynamicVoice {
+ type Sample = V::Sample;
+
+ fn set_samplerate(&mut self, samplerate: f32) {
+ self.samplerate = samplerate;
+ self.current_manager.set_samplerate(samplerate);
+ }
+
+ fn latency(&self) -> usize {
+ self.current_manager.latency()
+ }
+
+ fn reset(&mut self) {
+ self.current_manager.reset();
+ }
+}
+
+impl VoiceManager for DynamicVoice {
+ type Voice = V;
+ type ID = as VoiceManager>::ID;
+
+ fn capacity(&self) -> usize {
+ self.poly_voice_capacity
+ }
+
+ fn get_voice(&self, id: Self::ID) -> Option<&Self::Voice> {
+ match self.current_manager {
+ Impl::Monophonic(ref mono) => mono.get_voice(()),
+ Impl::Polyphonic(ref poly) => poly.get_voice(id),
+ }
+ }
+
+ fn get_voice_mut(&mut self, id: Self::ID) -> Option<&mut Self::Voice> {
+ match self.current_manager {
+ Impl::Monophonic(ref mut mono) => mono.get_voice_mut(()),
+ Impl::Polyphonic(ref mut poly) => poly.get_voice_mut(id),
+ }
+ }
+
+ fn all_voices(&self) -> impl Iterator- {
+ 0..self.poly_voice_capacity
+ }
+
+ fn note_on(&mut self, note_data: NoteData) -> Self::ID {
+ match self.current_manager {
+ Impl::Monophonic(ref mut mono) => {
+ mono.note_on(note_data);
+ 0
+ }
+ Impl::Polyphonic(ref mut poly) => poly.note_on(note_data),
+ }
+ }
+
+ fn note_off(&mut self, id: Self::ID, release_velocity: f32) {
+ match self.current_manager {
+ Impl::Monophonic(ref mut mono) => {
+ mono.note_off((), release_velocity);
+ }
+ Impl::Polyphonic(ref mut poly) => {
+ poly.note_off(id, release_velocity);
+ }
+ }
+ }
+
+ fn choke(&mut self, id: Self::ID) {
+ match self.current_manager {
+ Impl::Monophonic(ref mut mono) => mono.choke(()),
+ Impl::Polyphonic(ref mut poly) => poly.choke(id),
+ }
+ }
+
+ fn panic(&mut self) {
+ match self.current_manager {
+ Impl::Monophonic(ref mut mono) => mono.panic(),
+ Impl::Polyphonic(ref mut poly) => poly.panic(),
+ }
+ }
+
+ fn pitch_bend(&mut self, amount: f64) {
+ match self.current_manager {
+ Impl::Monophonic(ref mut mono) => mono.pitch_bend(amount),
+ Impl::Polyphonic(ref mut poly) => poly.pitch_bend(amount),
+ }
+ }
+
+ fn aftertouch(&mut self, amount: f64) {
+ match self.current_manager {
+ Impl::Monophonic(ref mut mono) => mono.aftertouch(amount),
+ Impl::Polyphonic(ref mut poly) => poly.aftertouch(amount),
+ }
+ }
+
+ fn pressure(&mut self, id: Self::ID, pressure: f32) {
+ match self.current_manager {
+ Impl::Monophonic(ref mut mono) => mono.glide((), pressure),
+ Impl::Polyphonic(ref mut poly) => poly.glide(id, pressure),
+ }
+ }
+
+ fn glide(&mut self, id: Self::ID, semitones: f32) {
+ match self.current_manager {
+ Impl::Monophonic(ref mut mono) => mono.glide((), semitones),
+ Impl::Polyphonic(ref mut poly) => poly.glide(id, semitones),
+ }
+ }
+}
+
+impl> DSPProcess<0, 1> for DynamicVoice {
+ fn process(&mut self, []: [Self::Sample; 0]) -> [Self::Sample; 1] {
+ match self.current_manager {
+ Impl::Monophonic(ref mut mono) => mono.process([]),
+ Impl::Polyphonic(ref mut poly) => poly.process([]),
+ }
+ }
+}
diff --git a/crates/valib-voice/src/lib.rs b/crates/valib-voice/src/lib.rs
index 475af22..718f842 100644
--- a/crates/valib-voice/src/lib.rs
+++ b/crates/valib-voice/src/lib.rs
@@ -2,10 +2,12 @@
//! # Voice abstractions
//!
//! This crate provides abstractions around voice processing and voice management.
-use valib_core::dsp::DSPMeta;
+use valib_core::dsp::{BlockAdapter, DSPMeta, DSPProcessBlock, SampleAdapter};
use valib_core::simd::SimdRealField;
+use valib_core::util::{midi_to_freq, semitone_to_ratio};
use valib_core::Scalar;
+pub mod dynamic;
pub mod monophonic;
pub mod polyphonic;
#[cfg(feature = "resampled")]
@@ -20,11 +22,57 @@ pub trait Voice: DSPMeta {
/// Return a mutable reference to the voice's note data
fn note_data_mut(&mut self) -> &mut NoteData;
/// Release the note (corresponding to a note off)
- fn release(&mut self);
+ fn release(&mut self, release_velocity: f32);
/// Reuse the note (corresponding to a soft reset)
fn reuse(&mut self);
}
+impl + Voice, const I: usize, const O: usize> Voice
+ for SampleAdapter
+{
+ fn active(&self) -> bool {
+ self.inner.active()
+ }
+
+ fn note_data(&self) -> &NoteData {
+ self.inner.note_data()
+ }
+
+ fn note_data_mut(&mut self) -> &mut NoteData {
+ self.inner.note_data_mut()
+ }
+
+ fn release(&mut self, release_velocity: f32) {
+ self.inner.release(release_velocity);
+ }
+
+ fn reuse(&mut self) {
+ self.inner.reuse();
+ }
+}
+
+impl Voice for BlockAdapter {
+ fn active(&self) -> bool {
+ self.0.active()
+ }
+
+ fn note_data(&self) -> &NoteData {
+ self.0.note_data()
+ }
+
+ fn note_data_mut(&mut self) -> &mut NoteData {
+ self.0.note_data_mut()
+ }
+
+ fn release(&mut self, release_velocity: f32) {
+ self.0.release(release_velocity);
+ }
+
+ fn reuse(&mut self) {
+ self.0.reuse();
+ }
+}
+
/// Value representing velocity. The square root is precomputed to be used in voices directly.
#[derive(Debug, Copy, Clone)]
pub struct Velocity {
@@ -114,6 +162,8 @@ impl Gain {
pub struct NoteData {
/// Note frequency
pub frequency: T,
+ /// Frequency modulation (pitch bend, glide)
+ pub modulation_st: T,
/// Note velocity
pub velocity: Velocity,
/// Note gain
@@ -124,9 +174,35 @@ pub struct NoteData {
pub pressure: T,
}
+impl NoteData {
+ pub fn from_midi(midi_note: u8, velocity: f32) -> Self {
+ let frequency = midi_to_freq(midi_note);
+ let velocity = Velocity::new(T::from_f64(velocity as _));
+ let gain = Gain::from_linear(T::one());
+ let pan = T::zero();
+ let pressure = T::zero();
+ Self {
+ frequency,
+ modulation_st: T::zero(),
+ velocity,
+ gain,
+ pan,
+ pressure,
+ }
+ }
+
+ pub fn resolve_frequency(&self) -> T {
+ semitone_to_ratio(self.modulation_st) * self.frequency
+ }
+}
+
/// Trait for types which manage voices.
#[allow(unused_variables)]
-pub trait VoiceManager: DSPMeta {
+pub trait VoiceManager:
+ DSPMeta::Voice as DSPMeta>::Sample>
+{
+ /// Type of the inner voice.
+ type Voice: Voice;
/// Type for the voice ID.
type ID: Copy;
@@ -134,9 +210,9 @@ pub trait VoiceManager: DSPMeta {
fn capacity(&self) -> usize;
/// Get the voice by its ID
- fn get_voice(&self, id: Self::ID) -> Option<&V>;
+ fn get_voice(&self, id: Self::ID) -> Option<&Self::Voice>;
/// Get the voice mutably by its ID
- fn get_voice_mut(&mut self, id: Self::ID) -> Option<&mut V>;
+ fn get_voice_mut(&mut self, id: Self::ID) -> Option<&mut Self::Voice>;
/// Return true if the voice referred by the given ID is currently active
fn is_voice_active(&self, id: Self::ID) -> bool {
@@ -153,9 +229,9 @@ pub trait VoiceManager: DSPMeta {
}
/// Indicate a note on event, with the given note data to instanciate the voice.
- fn note_on(&mut self, note_data: NoteData) -> Self::ID;
+ fn note_on(&mut self, note_data: NoteData) -> Self::ID;
/// Indicate a note off event on the given voice ID.
- fn note_off(&mut self, id: Self::ID);
+ fn note_off(&mut self, id: Self::ID, release_velocity: f32);
/// Choke the voice, causing all processing on that voice to stop.
fn choke(&mut self, id: Self::ID);
/// Choke all the notes.
@@ -177,3 +253,148 @@ pub trait VoiceManager: DSPMeta {
/// Note gain
fn gain(&mut self, id: Self::ID, gain: f32) {}
}
+
+impl VoiceManager for BlockAdapter {
+ type Voice = V::Voice;
+ type ID = V::ID;
+
+ fn capacity(&self) -> usize {
+ self.0.capacity()
+ }
+
+ fn get_voice(&self, id: Self::ID) -> Option<&Self::Voice> {
+ self.0.get_voice(id)
+ }
+
+ fn get_voice_mut(&mut self, id: Self::ID) -> Option<&mut Self::Voice> {
+ self.0.get_voice_mut(id)
+ }
+
+ fn is_voice_active(&self, id: Self::ID) -> bool {
+ self.0.is_voice_active(id)
+ }
+
+ fn all_voices(&self) -> impl Iterator
- {
+ self.0.all_voices()
+ }
+
+ fn active(&self) -> usize {
+ self.0.active()
+ }
+
+ fn note_on(&mut self, note_data: NoteData) -> Self::ID {
+ self.0.note_on(note_data)
+ }
+
+ fn note_off(&mut self, id: Self::ID, release_velocity: f32) {
+ self.0.note_off(id, release_velocity)
+ }
+
+ fn choke(&mut self, id: Self::ID) {
+ self.0.choke(id)
+ }
+
+ fn panic(&mut self) {
+ self.0.panic()
+ }
+
+ fn pitch_bend(&mut self, amount: f64) {
+ self.0.pitch_bend(amount)
+ }
+
+ fn aftertouch(&mut self, amount: f64) {
+ self.0.aftertouch(amount)
+ }
+
+ fn pressure(&mut self, id: Self::ID, pressure: f32) {
+ self.0.pressure(id, pressure)
+ }
+
+ fn glide(&mut self, id: Self::ID, semitones: f32) {
+ self.0.glide(id, semitones)
+ }
+
+ fn pan(&mut self, id: Self::ID, pan: f32) {
+ self.0.pan(id, pan)
+ }
+
+ fn gain(&mut self, id: Self::ID, gain: f32) {
+ self.0.gain(id, gain)
+ }
+}
+
+impl + VoiceManager, const I: usize, const O: usize> VoiceManager
+ for SampleAdapter
+{
+ type Voice = V::Voice;
+ type ID = V::ID;
+
+ fn capacity(&self) -> usize {
+ self.inner.capacity()
+ }
+
+ fn get_voice(&self, id: Self::ID) -> Option<&Self::Voice> {
+ self.inner.get_voice(id)
+ }
+
+ fn get_voice_mut(&mut self, id: Self::ID) -> Option<&mut Self::Voice> {
+ self.inner.get_voice_mut(id)
+ }
+
+ fn is_voice_active(&self, id: Self::ID) -> bool {
+ self.inner.is_voice_active(id)
+ }
+
+ fn all_voices(&self) -> impl Iterator
- {
+ self.inner.all_voices()
+ }
+
+ fn active(&self) -> usize {
+ self.inner.active()
+ }
+
+ fn note_on(&mut self, note_data: NoteData) -> Self::ID {
+ self.inner.note_on(note_data)
+ }
+
+ fn note_off(&mut self, id: Self::ID, release_velocity: f32) {
+ self.inner.note_off(id, release_velocity)
+ }
+
+ fn choke(&mut self, id: Self::ID) {
+ self.inner.choke(id)
+ }
+
+ fn panic(&mut self) {
+ self.inner.panic()
+ }
+
+ fn pitch_bend(&mut self, amount: f64) {
+ self.inner.pitch_bend(amount)
+ }
+
+ fn aftertouch(&mut self, amount: f64) {
+ self.inner.aftertouch(amount)
+ }
+
+ fn pressure(&mut self, id: Self::ID, pressure: f32) {
+ self.inner.pressure(id, pressure)
+ }
+
+ fn glide(&mut self, id: Self::ID, semitones: f32) {
+ self.inner.glide(id, semitones)
+ }
+
+ fn pan(&mut self, id: Self::ID, pan: f32) {
+ self.inner.pan(id, pan)
+ }
+
+ fn gain(&mut self, id: Self::ID, gain: f32) {
+ self.inner.gain(id, gain)
+ }
+}
+
+/// Inner voice of the voice manager.
+pub type InnerVoice = ::Voice;
+/// Inner voice ID of the voice manager.
+pub type VoiceId = ::ID;
diff --git a/crates/valib-voice/src/monophonic.rs b/crates/valib-voice/src/monophonic.rs
index 86f43f5..b48e9a9 100644
--- a/crates/valib-voice/src/monophonic.rs
+++ b/crates/valib-voice/src/monophonic.rs
@@ -4,6 +4,9 @@
use crate::{NoteData, Voice, VoiceManager};
use num_traits::zero;
+use numeric_literals::replace_float_literals;
+use std::fmt;
+use std::fmt::Formatter;
use valib_core::dsp::buffer::{AudioBufferMut, AudioBufferRef};
use valib_core::dsp::{DSPMeta, DSPProcess, DSPProcessBlock};
use valib_core::util::lerp;
@@ -15,15 +18,33 @@ pub struct Monophonic {
pub pitch_bend_min_st: V::Sample,
/// Maximum pitch bend amount (semitones)
pub pitch_bend_max_st: V::Sample,
- create_voice: Box) -> V>,
+ create_voice: Box) -> V>,
voice: Option,
base_frequency: V::Sample,
- pitch_bend_st: V::Sample,
+ modulation_st: V::Sample,
released: bool,
legato: bool,
samplerate: f32,
}
+impl fmt::Debug for Monophonic {
+ fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
+ f.debug_struct("Monophonic")
+ .field(
+ "pitch_bend_st",
+ &(self.pitch_bend_min_st..self.pitch_bend_max_st),
+ )
+ .field("create_voice", &"Box) -> V")
+ .field("voice", &self.voice)
+ .field("base_frequency", &self.base_frequency)
+ .field("pitch_bend", &self.modulation_st)
+ .field("released", &self.released)
+ .field("legato", &self.legato)
+ .field("samplerate", &self.samplerate)
+ .finish()
+ }
+}
+
impl DSPMeta for Monophonic {
type Sample = V::Sample;
@@ -55,7 +76,7 @@ impl Monophonic {
/// returns: Monophonic
pub fn new(
samplerate: f32,
- create_voice: impl Fn(f32, NoteData) -> V + 'static,
+ create_voice: impl 'static + Send + Sync + Fn(f32, NoteData) -> V,
legato: bool,
) -> Self {
Self {
@@ -65,7 +86,7 @@ impl Monophonic {
voice: None,
released: false,
base_frequency: V::Sample::from_f64(440.),
- pitch_bend_st: zero(),
+ modulation_st: zero(),
legato,
samplerate,
}
@@ -80,9 +101,20 @@ impl Monophonic {
pub fn set_legato(&mut self, legato: bool) {
self.legato = legato;
}
+
+ pub fn clean_voice_if_inactive(&mut self) {
+ self.voice.take_if(|v| !v.active());
+ }
+
+ #[replace_float_literals(V::Sample::from_f64(literal))]
+ fn pitch_bend_st(&self, amt: V::Sample) -> V::Sample {
+ let t = 0.5 * amt + 0.5;
+ lerp(t, self.pitch_bend_min_st, self.pitch_bend_max_st)
+ }
}
-impl VoiceManager for Monophonic {
+impl VoiceManager for Monophonic {
+ type Voice = V;
type ID = ();
fn capacity(&self) -> usize {
@@ -109,9 +141,9 @@ impl VoiceManager for Monophonic {
}
}
- fn note_on(&mut self, note_data: NoteData) -> Self::ID {
+ fn note_on(&mut self, mut note_data: NoteData) -> Self::ID {
self.base_frequency = note_data.frequency;
- self.pitch_bend_st = zero();
+ note_data.modulation_st = self.modulation_st;
if let Some(voice) = &mut self.voice {
*voice.note_data_mut() = note_data;
if self.released || !self.legato {
@@ -122,9 +154,9 @@ impl VoiceManager for Monophonic {
}
}
- fn note_off(&mut self, _id: Self::ID) {
+ fn note_off(&mut self, _: Self::ID, release_velocity: f32) {
if let Some(voice) = &mut self.voice {
- voice.release();
+ voice.release(release_velocity);
}
}
@@ -137,11 +169,9 @@ impl VoiceManager for Monophonic {
}
fn pitch_bend(&mut self, amount: f64) {
- self.pitch_bend_st = lerp(
- V::Sample::from_f64(0.5 + amount / 2.),
- self.pitch_bend_min_st,
- self.pitch_bend_max_st,
- );
+ let mod_st = self.pitch_bend_st(V::Sample::from_f64(amount));
+ self.modulation_st = mod_st;
+ self.update_voice_pitchmod();
}
fn aftertouch(&mut self, amount: f64) {
@@ -155,8 +185,18 @@ impl VoiceManager for Monophonic {
voice.note_data_mut().pressure = V::Sample::from_f64(pressure as _);
}
}
+
fn glide(&mut self, _: Self::ID, semitones: f32) {
- self.pitch_bend_st = V::Sample::from_f64(semitones as _);
+ self.modulation_st = V::Sample::from_f64(semitones as _);
+ self.update_voice_pitchmod();
+ }
+}
+
+impl Monophonic {
+ fn update_voice_pitchmod(&mut self) {
+ if let Some(voice) = &mut self.voice {
+ voice.note_data_mut().modulation_st = self.modulation_st;
+ }
}
}
diff --git a/crates/valib-voice/src/polyphonic.rs b/crates/valib-voice/src/polyphonic.rs
index b9b1f89..d8e309c 100644
--- a/crates/valib-voice/src/polyphonic.rs
+++ b/crates/valib-voice/src/polyphonic.rs
@@ -1,16 +1,40 @@
//! # Polyphonic voice manager
//!
//! Provides a polyphonic voice manager with rotating voice allocation.
+
use crate::{NoteData, Voice, VoiceManager};
use num_traits::zero;
+use numeric_literals::replace_float_literals;
+use std::fmt;
+use std::fmt::Formatter;
+use std::ops::Range;
use valib_core::dsp::{DSPMeta, DSPProcess};
+use valib_core::util::lerp;
+use valib_core::Scalar;
/// Polyphonic voice manager with rotating voice allocation
pub struct Polyphonic {
- create_voice: Box) -> V>,
+ pub pitch_bend_st: Range,
+ create_voice: Box) -> V>,
voice_pool: Box<[Option]>,
+ active_voices: usize,
next_voice: usize,
samplerate: f32,
+ pitch_bend: V::Sample,
+}
+
+impl fmt::Debug for Polyphonic {
+ fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
+ f.debug_struct("Polyphonic")
+ .field(
+ "create_voice",
+ &"Box) -> V>",
+ )
+ .field("voice_pool", &"Box<[Option]>")
+ .field("next_voice", &self.next_voice)
+ .field("samplerate", &self.samplerate)
+ .finish()
+ }
}
impl Polyphonic {
@@ -26,15 +50,41 @@ impl Polyphonic {
pub fn new(
samplerate: f32,
voice_capacity: usize,
- create_voice: impl Fn(f32, NoteData) -> V + 'static,
+ create_voice: impl 'static + Send + Sync + Fn(f32, NoteData) -> V + 'static,
) -> Self {
Self {
+ pitch_bend_st: V::Sample::from_f64(-2.)..V::Sample::from_f64(2.),
create_voice: Box::new(create_voice),
next_voice: 0,
voice_pool: (0..voice_capacity).map(|_| None).collect(),
+ active_voices: 0,
samplerate,
+ pitch_bend: zero(),
}
}
+
+ /// Clean inactive voices to prevent them being processed for nothing.
+ pub fn clean_inactive_voices(&mut self) {
+ for slot in &mut self.voice_pool {
+ if slot.as_ref().is_some_and(|v| !v.active()) {
+ slot.take();
+ self.active_voices -= 1;
+ }
+ }
+ }
+
+ fn update_voices_pitchmod(&mut self) {
+ let mod_st = self.get_pitch_bend();
+ for voice in self.voice_pool.iter_mut().filter_map(|opt| opt.as_mut()) {
+ voice.note_data_mut().modulation_st = mod_st;
+ }
+ }
+
+ #[replace_float_literals(V::Sample::from_f64(literal))]
+ fn get_pitch_bend(&self) -> V::Sample {
+ let t = 0.5 * self.pitch_bend + 0.5;
+ lerp(t, self.pitch_bend_st.start, self.pitch_bend_st.end)
+ }
}
impl DSPMeta for Polyphonic {
@@ -61,7 +111,8 @@ impl DSPMeta for Polyphonic {
}
}
-impl VoiceManager for Polyphonic {
+impl VoiceManager for Polyphonic {
+ type Voice = V;
type ID = usize;
fn capacity(&self) -> usize {
@@ -81,31 +132,72 @@ impl VoiceManager for Polyphonic {
}
fn note_on(&mut self, note_data: NoteData) -> Self::ID {
- let id = self.next_voice;
- self.next_voice += 1;
-
- if let Some(voice) = &mut self.voice_pool[id] {
- *voice.note_data_mut() = note_data;
- voice.reuse();
+ if self.active_voices == self.capacity() {
+ // At capacity, we must steal a voice
+ let id = self.next_voice;
+
+ if let Some(voice) = &mut self.voice_pool[id] {
+ *voice.note_data_mut() = note_data;
+ voice.reuse();
+ } else {
+ self.voice_pool[id] = Some((self.create_voice)(self.samplerate, note_data));
+ }
+
+ self.next_voice = (self.next_voice + 1) % self.voice_pool.len();
+ id
} else {
+ // Find first available slot
+ while self.voice_pool[self.next_voice].is_some() {
+ self.next_voice = (self.next_voice + 1) % self.voice_pool.len();
+ }
+
+ let id = self.next_voice;
self.voice_pool[id] = Some((self.create_voice)(self.samplerate, note_data));
+ self.next_voice = (self.next_voice + 1) % self.voice_pool.len();
+ self.active_voices += 1;
+ id
}
-
- id
}
- fn note_off(&mut self, id: Self::ID) {
+ fn note_off(&mut self, id: Self::ID, release_velocity: f32) {
if let Some(voice) = &mut self.voice_pool[id] {
- voice.release();
+ voice.release(release_velocity);
}
}
fn choke(&mut self, id: Self::ID) {
self.voice_pool[id] = None;
+ self.active_voices -= 1;
}
fn panic(&mut self) {
self.voice_pool.fill_with(|| None);
+ self.active_voices = 0;
+ }
+
+ fn pitch_bend(&mut self, amount: f64) {
+ self.pitch_bend = V::Sample::from_f64(amount);
+ self.update_voices_pitchmod();
+ }
+
+ fn aftertouch(&mut self, amount: f64) {
+ let pressure = V::Sample::from_f64(amount);
+ for voice in self.voice_pool.iter_mut().filter_map(|x| x.as_mut()) {
+ voice.note_data_mut().pressure = pressure;
+ }
+ }
+
+ fn pressure(&mut self, id: Self::ID, pressure: f32) {
+ if let Some(voice) = &mut self.voice_pool[id] {
+ voice.note_data_mut().pressure = V::Sample::from_f64(pressure as _);
+ }
+ }
+
+ fn glide(&mut self, id: Self::ID, semitones: f32) {
+ let mod_st = V::Sample::from_f64(semitones as _);
+ if let Some(voice) = &mut self.voice_pool[id] {
+ voice.note_data_mut().modulation_st = mod_st;
+ }
}
}
diff --git a/crates/valib-voice/src/upsample.rs b/crates/valib-voice/src/upsample.rs
index 71384b4..e3d26b8 100644
--- a/crates/valib-voice/src/upsample.rs
+++ b/crates/valib-voice/src/upsample.rs
@@ -1,6 +1,7 @@
//! # Upsampled voices
//!
//! Provides upsampling for DSP process which are generators (0 input channels).
+use crate::{NoteData, Voice};
use num_traits::zero;
use valib_core::dsp::buffer::{AudioBufferBox, AudioBufferMut, AudioBufferRef};
use valib_core::dsp::{DSPMeta, DSPProcessBlock};
@@ -15,6 +16,28 @@ pub struct UpsampledVoice {
num_active_stages: usize,
}
+impl Voice for UpsampledVoice