added BowStri oscillator
This commit is contained in:
parent
bce7dfebe9
commit
24ea58eda0
4 changed files with 275 additions and 2 deletions
|
@ -25,6 +25,11 @@ pub struct BiquadCoefs {
|
|||
// https://github.com/VCVRack/Befaco/blob/v1/src/ChowDSP.hpp#L339
|
||||
// more coeffs from there ^^^^^^^^^^^^^ ?
|
||||
impl BiquadCoefs {
|
||||
#[inline]
|
||||
pub fn new(b0: f32, b1: f32, b2: f32, a1: f32, a2: f32) -> Self {
|
||||
Self { b0, b1, b2, a1, a2 }
|
||||
}
|
||||
|
||||
/// Returns settings for a Butterworth lowpass filter.
|
||||
/// Cutoff is the -3 dB point of the filter in Hz.
|
||||
#[inline]
|
||||
|
@ -113,6 +118,13 @@ impl Biquad {
|
|||
Default::default()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn new_with(b0: f32, b1: f32, b2: f32, a1: f32, a2: f32) -> Self {
|
||||
let mut s = Self::new();
|
||||
s.set_coefs(BiquadCoefs::new(b0, b1, b2, a1, a2));
|
||||
s
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn coefs(&self) -> &BiquadCoefs {
|
||||
&self.coefs
|
||||
|
|
|
@ -969,10 +969,18 @@ impl<F: Flt> DelayBuffer<F> {
|
|||
/// Fetch a sample from the delay buffer at the given time.
|
||||
///
|
||||
/// * `delay_time_ms` - Delay time in milliseconds.
|
||||
#[inline]
|
||||
pub fn linear_interpolate_at(&self, delay_time_ms: F) -> F {
|
||||
self.linear_interpolate_at_s((delay_time_ms * self.srate) / f(1000.0))
|
||||
}
|
||||
|
||||
/// Fetch a sample from the delay buffer at the given offset.
|
||||
///
|
||||
/// * `s_offs` - Sample offset in samples.
|
||||
#[inline]
|
||||
pub fn linear_interpolate_at_s(&self, s_offs: F) -> F {
|
||||
let data = &self.data[..];
|
||||
let len = data.len();
|
||||
let s_offs = (delay_time_ms * self.srate) / f(1000.0);
|
||||
let offs = s_offs.floor().to_usize().unwrap_or(0) % len;
|
||||
let fract = s_offs.fract();
|
||||
|
||||
|
@ -988,9 +996,16 @@ impl<F: Flt> DelayBuffer<F> {
|
|||
/// * `delay_time_ms` - Delay time in milliseconds.
|
||||
#[inline]
|
||||
pub fn cubic_interpolate_at(&self, delay_time_ms: F) -> F {
|
||||
self.cubic_interpolate_at_s((delay_time_ms * self.srate) / f(1000.0))
|
||||
}
|
||||
|
||||
/// Fetch a sample from the delay buffer at the given offset.
|
||||
///
|
||||
/// * `s_offs` - Sample offset in samples.
|
||||
#[inline]
|
||||
pub fn cubic_interpolate_at_s(&self, s_offs: F) -> F {
|
||||
let data = &self.data[..];
|
||||
let len = data.len();
|
||||
let s_offs = (delay_time_ms * self.srate) / f(1000.0);
|
||||
let offs = s_offs.floor().to_usize().unwrap_or(0) % len;
|
||||
let fract = s_offs.fract();
|
||||
|
||||
|
@ -1201,6 +1216,47 @@ impl<F: Flt> OnePoleLPF<F> {
|
|||
}
|
||||
}
|
||||
|
||||
// Fixed one pole with setable pole and gain.
|
||||
// Implementation taken from tubonitaub / alec-deason
|
||||
// from https://github.com/alec-deason/virtual_modular/blob/4025f1ef343c2eb9cd74eac07b5350c1e7ec9c09/src/simd_graph.rs#L4292
|
||||
// under MIT License
|
||||
#[derive(Debug, Copy, Clone, Default)]
|
||||
pub struct FixedOnePole {
|
||||
b0: f32,
|
||||
a1: f32,
|
||||
y1: f32,
|
||||
gain: f32,
|
||||
}
|
||||
|
||||
impl FixedOnePole {
|
||||
pub fn new(pole: f32, gain: f32) -> Self {
|
||||
let b0 =
|
||||
if pole > 0.0 { 1.0 - pole }
|
||||
else { 1.0 + pole };
|
||||
|
||||
Self {
|
||||
b0,
|
||||
a1: -pole,
|
||||
y1: 0.0,
|
||||
gain
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reset(&mut self) {
|
||||
self.y1 = 0.0;
|
||||
}
|
||||
|
||||
pub fn set_gain(&mut self, gain: f32) { self.gain = gain; }
|
||||
|
||||
pub fn process(&mut self, input: f32) -> f32 {
|
||||
let output =
|
||||
self.b0 * self.gain * input
|
||||
- self.a1 * self.y1;
|
||||
self.y1 = output;
|
||||
output
|
||||
}
|
||||
}
|
||||
|
||||
// one pole hp from valley rack free:
|
||||
// https://github.com/ValleyAudio/ValleyRackFree/blob/v1.0/src/Common/DSP/OnePoleFilters.cpp
|
||||
#[inline]
|
||||
|
|
|
@ -52,6 +52,8 @@ mod node_mux9;
|
|||
mod node_cqnt;
|
||||
#[allow(non_upper_case_globals)]
|
||||
mod node_quant;
|
||||
#[allow(non_upper_case_globals)]
|
||||
mod node_bowstri;
|
||||
|
||||
pub mod biquad;
|
||||
pub mod tracker;
|
||||
|
@ -121,6 +123,7 @@ use node_rndwk::RndWk;
|
|||
use node_mux9::Mux9;
|
||||
use node_cqnt::CQnt;
|
||||
use node_quant::Quant;
|
||||
use node_bowstri::BowStri;
|
||||
|
||||
pub const MIDI_MAX_FREQ : f32 = 13289.75;
|
||||
|
||||
|
@ -822,6 +825,13 @@ macro_rules! node_list {
|
|||
{6 0 dist setting(0) fa_distort 0 3}
|
||||
{7 1 ovrsmpl setting(1) fa_vosc_ovrsmpl 0 1}
|
||||
[0 sig],
|
||||
bowstri => BowStri UIType::Generic UICategory::Osc
|
||||
(0 freq n_pit d_pit r_fq f_freq stp_d -1.0, 0.5647131, 440.0)
|
||||
(1 det n_det d_det r_det f_det stp_f -0.2, 0.2, 0.0)
|
||||
(2 vel n_id n_id r_id f_def stp_d 0.0, 1.0, 0.5)
|
||||
(3 force n_id n_id r_id f_def stp_d 0.0, 1.0, 0.5)
|
||||
(4 pos n_id n_id r_id f_def stp_d 0.0, 1.0, 0.5)
|
||||
[0 sig],
|
||||
out => Out UIType::Generic UICategory::IOUtil
|
||||
(0 ch1 n_id d_id r_id f_def stp_d -1.0, 1.0, 0.0)
|
||||
(1 ch2 n_id d_id r_id f_def stp_d -1.0, 1.0, 0.0)
|
||||
|
|
195
src/dsp/node_bowstri.rs
Normal file
195
src/dsp/node_bowstri.rs
Normal file
|
@ -0,0 +1,195 @@
|
|||
// Copyright (c) 2021 Weird Constructor <weirdconstructor@gmail.com>
|
||||
// This file is a part of HexoDSP. Released under GPL-3.0-or-later.
|
||||
// See README.md and COPYING for details.
|
||||
|
||||
use crate::nodes::{NodeAudioContext, NodeExecContext};
|
||||
use crate::dsp::{
|
||||
NodeId, SAtom, ProcBuf, denorm_offs, denorm,
|
||||
out, inp, DspNode, LedPhaseVals, NodeContext
|
||||
};
|
||||
use crate::dsp::helpers::{FixedOnePole, DelayBuffer};
|
||||
use crate::dsp::biquad::{Biquad, BiquadCoefs};
|
||||
|
||||
// Bowed String instrument oscillator
|
||||
// Bowed string model, a la Smith (1986),
|
||||
// after McIntyre, Schumacher, Woodhouse (1983).
|
||||
//
|
||||
// This is a digital waveguide model, making its use possibly subject to
|
||||
// patents held by Stanford University, Yamaha, and others.
|
||||
//
|
||||
// Implementation taken from tubonitaub / alec-deason
|
||||
// from https://github.com/alec-deason/virtual_modular/blob/4025f1ef343c2eb9cd74eac07b5350c1e7ec9c09/src/simd_graph.rs#L3926
|
||||
// or
|
||||
// under MIT License
|
||||
//
|
||||
// Which is a reimplementation of this implementation:
|
||||
// https://github.com/thestk/stk/blob/38970124ecda9d78a74a375426ed5fb9c09840a2/src/Bowed.cpp#L32
|
||||
// By Perry R. Cook and Gary P. Scavone, 1995--2019.
|
||||
// Contributions by Esteban Maestre, 2011.
|
||||
#[derive(Debug, Clone)]
|
||||
struct BowedString {
|
||||
srate: f32,
|
||||
nut_to_bow: DelayBuffer<f32>,
|
||||
bow_to_bridge: DelayBuffer<f32>,
|
||||
string_filter: FixedOnePole,
|
||||
body_filters: [Biquad; 6],
|
||||
}
|
||||
|
||||
impl BowedString {
|
||||
pub fn new() -> Self {
|
||||
let mut s = Self {
|
||||
srate: 44100.0,
|
||||
nut_to_bow: DelayBuffer::new(),
|
||||
bow_to_bridge: DelayBuffer::new(),
|
||||
string_filter: FixedOnePole::new(0.0, 0.0),
|
||||
body_filters: [
|
||||
Biquad::new_with(1.0, 1.5667, 0.3133, -0.5509, -0.3925),
|
||||
Biquad::new_with(1.0, -1.9537, 0.9542, -1.6357, 0.8697),
|
||||
Biquad::new_with(1.0, -1.6683, 0.8852, -1.7674, 0.8735),
|
||||
Biquad::new_with(1.0, -1.8585, 0.9653, -1.8498, 0.9516),
|
||||
Biquad::new_with(1.0, -1.9299, 0.9621, -1.9354, 0.9590),
|
||||
Biquad::new_with(1.0, -1.9800, 0.9888, -1.9867, 0.9923),
|
||||
],
|
||||
};
|
||||
s.set_sample_rate(s.srate);
|
||||
s
|
||||
}
|
||||
|
||||
pub fn set_sample_rate(&mut self, sample_rate: f32) {
|
||||
self.srate = sample_rate;
|
||||
self.string_filter =
|
||||
FixedOnePole::new(
|
||||
0.75 - (0.2 * (22050.0 / sample_rate)),
|
||||
0.95);
|
||||
}
|
||||
|
||||
pub fn reset(&mut self) {
|
||||
self.nut_to_bow.reset();
|
||||
self.bow_to_bridge.reset();
|
||||
self.string_filter.reset();
|
||||
|
||||
for f in self.body_filters.iter_mut() {
|
||||
f.reset();
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn process(&mut self,
|
||||
freq: f32, bow_velocity: f32, bow_force: f32, pos: f32
|
||||
) -> f32
|
||||
{
|
||||
let total_l = self.srate / freq.max(20.0);
|
||||
let bow_position = ((pos + 1.0) / 2.0).clamp(0.01, 0.99);
|
||||
|
||||
let bow_nut_l = total_l * (1.0 - bow_position);
|
||||
let bow_bridge_l = total_l * bow_position;
|
||||
|
||||
let nut = -self.nut_to_bow.linear_interpolate_at_s(bow_nut_l);
|
||||
let brid = self.bow_to_bridge.linear_interpolate_at_s(bow_bridge_l);
|
||||
let bridge = -self.string_filter.process(brid);
|
||||
|
||||
let dv = bow_velocity - (nut + bridge);
|
||||
|
||||
let phat =
|
||||
((dv + 0.001) * bow_force + 0.75)
|
||||
.powf(-4.0)
|
||||
.clamp(0.0, 0.98);
|
||||
|
||||
self.bow_to_bridge.feed(nut + phat*dv);
|
||||
self.nut_to_bow.feed(bridge + phat*dv);
|
||||
|
||||
let mut output = bridge;
|
||||
for f in self.body_filters.iter_mut() {
|
||||
output = f.tick(output);
|
||||
}
|
||||
|
||||
output
|
||||
}
|
||||
}
|
||||
|
||||
/// A sine oscillator
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct BowStri {
|
||||
bstr: Box<BowedString>,
|
||||
}
|
||||
|
||||
const TWOPI : f32 = 2.0 * std::f32::consts::PI;
|
||||
|
||||
impl BowStri {
|
||||
pub fn new(_nid: &NodeId) -> Self {
|
||||
Self {
|
||||
bstr: Box::new(BowedString::new()),
|
||||
}
|
||||
}
|
||||
pub const freq : &'static str =
|
||||
"BowStri freq\nFrequency of the bowed string oscillator.\n\nRange: (-1..1)\n";
|
||||
pub const det : &'static str =
|
||||
"BowStri det\nDetune the oscillator in semitones and cents. \
|
||||
the input of this value is rounded to semitones on coarse input. \
|
||||
Fine input lets you detune in cents (rounded). \
|
||||
A signal sent to this port is not rounded.\n\
|
||||
Note: The signal input allows detune +-10 octaves.\
|
||||
\nRange: (Knob -0.2 .. 0.2) / (Signal -1.0 .. 1.0)\n";
|
||||
pub const vel : &'static str =
|
||||
"BowStri vel\n\n\nRange: (-1..1)\n";
|
||||
pub const force : &'static str =
|
||||
"BowStri force\n\n\nRange: (-1..1)\n";
|
||||
pub const pos : &'static str =
|
||||
"BowStri pos\n\n\nRange: (-1..1)\n";
|
||||
pub const sig : &'static str =
|
||||
"BowStri sig\nOscillator signal output.\n\nRange: (-1..1)\n";
|
||||
|
||||
pub const DESC : &'static str =
|
||||
r#"Bowed String Oscillator
|
||||
|
||||
This is an oscillator that simulates a bowed string.
|
||||
"#;
|
||||
|
||||
pub const HELP : &'static str =
|
||||
r#"BowStri - A Bowed String Oscillator
|
||||
|
||||
"#;
|
||||
}
|
||||
|
||||
impl DspNode for BowStri {
|
||||
fn outputs() -> usize { 1 }
|
||||
|
||||
fn set_sample_rate(&mut self, srate: f32) {
|
||||
self.bstr.set_sample_rate(srate);
|
||||
}
|
||||
|
||||
fn reset(&mut self) {
|
||||
self.bstr.reset();
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn process<T: NodeAudioContext>(
|
||||
&mut self, ctx: &mut T, _ectx: &mut NodeExecContext,
|
||||
_nctx: &NodeContext,
|
||||
_atoms: &[SAtom], inputs: &[ProcBuf],
|
||||
outputs: &mut [ProcBuf], ctx_vals: LedPhaseVals)
|
||||
{
|
||||
let o = out::BowStri::sig(outputs);
|
||||
let freq = inp::BowStri::freq(inputs);
|
||||
let det = inp::BowStri::det(inputs);
|
||||
let vel = inp::BowStri::vel(inputs);
|
||||
let force = inp::BowStri::force(inputs);
|
||||
let pos = inp::BowStri::pos(inputs);
|
||||
|
||||
let mut last_val = 0.0;
|
||||
for frame in 0..ctx.nframes() {
|
||||
let freq = denorm_offs::BowStri::freq(freq, det.read(frame), frame);
|
||||
|
||||
let out =
|
||||
self.bstr.process(
|
||||
freq,
|
||||
denorm::BowStri::vel(vel, frame),
|
||||
denorm::BowStri::force(force, frame),
|
||||
denorm::BowStri::pos(pos, frame));
|
||||
last_val = out;
|
||||
o.write(frame, out);
|
||||
}
|
||||
|
||||
ctx_vals[0].set(last_val);
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue