diff --git a/fretcat_effects/src/common/iir_filter.rs b/fretcat_effects/src/common/iir_filter.rs new file mode 100644 index 0000000..ab3d28a --- /dev/null +++ b/fretcat_effects/src/common/iir_filter.rs @@ -0,0 +1,324 @@ +use std::f32::consts::{PI, SQRT_2, TAU}; + +use rustfft::num_complex::Complex32; +use serde::{Serialize, Deserialize}; + +#[derive(Debug, Copy, Clone, Default, Serialize, Deserialize, PartialEq)] +pub struct BiquadCoefs { + pub a1: f32, + pub a2: f32, + pub b0: f32, + pub b1: f32, + pub b2: f32, +} + +impl BiquadCoefs { + /// Returns settings for a Butterworth lowpass filter. + /// Cutoff is the -3 dB point of the filter in Hz. + pub fn butter_lowpass(sample_rate: f32, cutoff: f32) -> Self { + let f = (cutoff * PI / sample_rate).tan(); + let a0r = 1.0 / (1.0 + SQRT_2 * f + f * f); + let a1 = (2.0 * f * f - 2.0) * a0r; + let a2 = (1.0 - SQRT_2 * f + f * f) * a0r; + let b0 = f * f * a0r; + let b1 = 2.0 * b0; + let b2 = b0; + Self { a1, a2, b0, b1, b2 } + } + + /// Returns settings for a constant-gain bandpass resonator. + /// The center frequency is given in Hz. + /// Bandwidth is the difference in Hz between -3 dB points of the filter response. + /// The overall gain of the filter is independent of bandwidth. + pub fn resonator(sample_rate: f32, center: f32, bandwidth: f32) -> Self { + let r = (-PI * bandwidth / sample_rate).exp(); + let a1 = -2.0 * r * (TAU * center / sample_rate).cos(); + let a2 = r * r; + let b0 = (1.0 - r * r).sqrt() * 0.5; + let b1 = 0.0; + let b2 = -b0; + Self { a1, a2, b0, b1, b2 } + } + + /// Arbitrary biquad. + pub fn arbitrary(a1: f32, a2: f32, b0: f32, b1: f32, b2: f32) -> Self { + Self { a1, a2, b0, b1, b2 } + } + + /// Frequency response at frequency `omega` expressed as fraction of sampling rate. + pub fn response(&self, omega: f32) -> Complex32 { + let z1 = Complex32::from_polar(1.0, -TAU * omega); + let z2 = z1 * z1; + /// Complex64 with real component `x` and imaginary component zero. + fn re(x: f32) -> Complex32 { + Complex32::new(x, 0.0) + } + (re(self.b0) + re(self.b1) * z1 + re(self.b2) * z2) + / (re(1.0) + re(self.a1) * z1 + re(self.a2) * z2) + } +} + +/// 2nd order IIR filter implemented in normalized Direct Form I. +/// Setting: coefficients as tuple (a1, a2, b0, b1, b2). +/// - Input 0: input signal. +/// - Output 0: filtered signal. +#[derive(Debug, Default, Clone, Copy, Deserialize, Serialize, PartialEq)] +pub struct Biquad { + coefs: BiquadCoefs, + x1: f32, + x2: f32, + y1: f32, + y2: f32, + sample_rate: f32, +} + +impl Biquad { + pub fn new() -> Self { + Self { + sample_rate: 44100.0, + ..Default::default() + } + } + + pub fn with_coefs(coefs: BiquadCoefs) -> Self { + Self { + coefs, + sample_rate: 44100.0, + ..Default::default() + } + } + + pub fn coefs(&self) -> &BiquadCoefs { + &self.coefs + } + + pub fn set_coefs(&mut self, coefs: BiquadCoefs) { + self.coefs = coefs; + } + + pub fn set(&mut self, a1: f32, a2: f32, b0: f32, b1: f32, b2: f32) { + self.set_coefs(BiquadCoefs::arbitrary(a1, a2, b0, b1, b2)); + } + + pub fn reset(&mut self) { + self.x1 = 0.0; + self.x2 = 0.0; + self.y1 = 0.0; + self.y2 = 0.0; + } + + pub fn set_sample_rate(&mut self, sample_rate: f32) { + self.sample_rate = sample_rate; + } + + #[inline] + pub fn tick(&mut self, input: f32) -> f32 { + let x0 = input; + let y0 = self.coefs.b0 * x0 + self.coefs.b1 * self.x1 + self.coefs.b2 * self.x2 + - self.coefs.a1 * self.y1 + - self.coefs.a2 * self.y2; + self.x2 = self.x1; + self.x1 = x0; + self.y2 = self.y1; + self.y1 = y0; + y0 + + // Transposed Direct Form II would be: + // y0 = b0 * x0 + s1 + // s1 = s2 + b1 * x0 - a1 * y0 + // s2 = b2 * x0 - a2 * y0 + } +} + +/// Butterworth lowpass filter. +/// Setting: cutoff. +/// Number of inputs is `N`, either `U1` or `U2`. +/// - Input 0: input signal +/// - Input 1 (optional): cutoff frequency (Hz) +/// - Output 0: filtered signal +#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq)] +pub struct ButterLowpass { + biquad: Biquad, + sample_rate: f32, + cutoff: f32, +} + +impl ButterLowpass { + /// Create new Butterworth lowpass filter with initial `cutoff` frequency in Hz. + pub fn new(cutoff: f32) -> Self { + let mut node = ButterLowpass { + biquad: Biquad::new(), + sample_rate: 44100.0, + cutoff: 0.0, + }; + node.biquad.reset(); + node.set_cutoff(cutoff); + node + } + + pub fn set_cutoff(&mut self, cutoff: f32) { + self.biquad + .set_coefs(BiquadCoefs::butter_lowpass(self.sample_rate, cutoff)); + self.cutoff = cutoff; + } + + pub fn set(&mut self, setting: f32) { + self.set_cutoff(setting); + } + + pub fn reset(&mut self) { + self.biquad.reset(); + } + + pub fn set_sample_rate(&mut self, sample_rate: f32) { + self.sample_rate = sample_rate; + self.biquad.set_sample_rate(sample_rate); + self.set_cutoff(self.cutoff); + } + + pub fn sample_rate(&self) -> f32 { + self.sample_rate + } + + pub fn cutoff(&self) -> f32 { + self.cutoff + } + + #[inline] + pub fn tick(&mut self, input: f32) -> f32 { + self.biquad.tick(input) + } +} + +/// Constant-gain bandpass filter (resonator). +/// Filter gain is (nearly) independent of bandwidth. +/// Setting: (center, bandwidth). +/// Number of inputs is `N`, either `U1` or `U3`. +/// - Input 0: input signal +/// - Input 1 (optional): filter center frequency (peak) (Hz) +/// - Input 2 (optional): filter bandwidth (distance) between -3 dB points (Hz) +/// - Output 0: filtered signal +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)] +pub struct Resonator { + biquad: Biquad, + sample_rate: f32, + center: f32, + bandwidth: f32, +} + +impl Resonator { + /// Create new resonator bandpass. Initial `center` frequency and `bandwidth` are specified in Hz. + pub fn new(center: f32, bandwidth: f32) -> Self { + let mut node = Resonator { + biquad: Biquad::new(), + sample_rate: 44100.0, + center, + bandwidth, + }; + node.biquad.reset(); + node.set_center_bandwidth(center, bandwidth); + node + } + + pub fn set_center_bandwidth(&mut self, center: f32, bandwidth: f32) { + self.biquad + .set_coefs(BiquadCoefs::resonator(self.sample_rate, center, bandwidth)); + self.center = center; + self.bandwidth = bandwidth; + } + + pub fn center_bandwidth(&self) -> f32 { + self.center + } + + pub fn set(&mut self, center: f32, bandwidth: f32) { + self.set_center_bandwidth(center, bandwidth); + } + + pub fn reset(&mut self) { + self.biquad.reset(); + } + + pub fn set_sample_rate(&mut self, sample_rate: f32) { + self.sample_rate = sample_rate; + self.set_center_bandwidth(self.center, self.bandwidth); + } + + pub fn sample_rate(&self) -> f32 { + self.sample_rate + } + + #[inline] + pub fn tick( + &mut self, + input: f32, + ) -> f32 { + self.biquad.tick(input) + } +} + +/// DC blocking filter with cutoff frequency in Hz. +/// Setting: cutoff. +/// - Input 0: signal +/// - Output 0: zero centered signal +#[derive(Debug, Default, Clone, Serialize, Deserialize, Copy)] +pub struct DCBlock { + x1: f32, + y1: f32, + cutoff: f32, + coeff: f32, + sample_rate: f32, +} + +impl DCBlock { + /// Create new DC blocking filter with `cutoff` frequency specified in Hz. + pub fn new(cutoff: f32) -> Self { + let mut node = DCBlock { + cutoff, + ..Default::default() + }; + node.reset(); + node.set_sample_rate(44100.0); + node + } + + /// Set the cutoff frequency (in Hz). + pub fn set_cutoff(&mut self, cutoff: f32) { + self.cutoff = cutoff; + self.coeff = 1.0 - TAU / self.sample_rate * cutoff; + } + + pub fn cutoff(&self) -> f32 { + self.cutoff + } + + pub fn set(&mut self, setting: f32) { + self.set_cutoff(setting); + } + + pub fn reset(&mut self) { + self.x1 = 0.0; + self.y1 = 0.0; + } + + pub fn set_sample_rate(&mut self, sample_rate: f32) { + self.sample_rate = sample_rate; + self.set_cutoff(self.cutoff); + } + + pub fn sample_rate(&mut self) -> f32 { + self.sample_rate + } + + #[inline] + pub fn tick( + &mut self, + input: f32, + ) -> f32 { + let x = input; + let y0 = x - self.x1 + self.coeff * self.y1; + self.x1 = x; + self.y1 = y0; + y0 + } +} diff --git a/fretcat_effects/src/common/mod.rs b/fretcat_effects/src/common/mod.rs index 79ca633..05b73fe 100644 --- a/fretcat_effects/src/common/mod.rs +++ b/fretcat_effects/src/common/mod.rs @@ -7,7 +7,9 @@ mod comb; #[allow(unused)] mod delayline; #[allow(unused)] -mod filter; +mod svf_filter; +#[allow(unused)] +mod iir_filter; #[allow(unused)] mod reverb; #[allow(unused)] @@ -19,10 +21,11 @@ use std::ops::Range; pub use self::{ allpass::AllPass, - filter::{AudioFilter, FilterMode}, + svf_filter::{SvfFilter, FilterMode}, reverb::Freeverb, adsr::Envelope, - delay::Delay + delay::Delay, + iir_filter::*, }; diff --git a/fretcat_effects/src/common/filter.rs b/fretcat_effects/src/common/svf_filter.rs similarity index 95% rename from fretcat_effects/src/common/filter.rs rename to fretcat_effects/src/common/svf_filter.rs index fa2da86..07444ae 100644 --- a/fretcat_effects/src/common/filter.rs +++ b/fretcat_effects/src/common/svf_filter.rs @@ -16,7 +16,7 @@ pub enum FilterMode { } #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)] -pub struct AudioFilter { +pub struct SvfFilter { coeffs: SvfCoeffs, ic1eq: f32, ic2eq: f32, @@ -26,9 +26,9 @@ pub struct AudioFilter { mode: FilterMode, } -impl AudioFilter { +impl SvfFilter { pub fn new(mode: FilterMode, sample_rate: f32, cutoff: f32, q: f32) -> Self { - let coeffs = AudioFilter::get_coeffs(mode, sample_rate, cutoff, q); + let coeffs = SvfFilter::get_coeffs(mode, sample_rate, cutoff, q); Self { coeffs, @@ -67,7 +67,7 @@ impl AudioFilter { pub fn recalculate_coeffs(&mut self, cutoff: f32, q: f32, sample_rate: f32) { self.cutoff = cutoff; self.q = q; - self.coeffs = AudioFilter::get_coeffs(self.mode, sample_rate, cutoff, q); + self.coeffs = SvfFilter::get_coeffs(self.mode, sample_rate, cutoff, q); } pub fn set_cutoff(&mut self, cutoff: f32) { diff --git a/fretcat_effects/src/effects/delay/delay.rs b/fretcat_effects/src/effects/delay/delay.rs index 31571b8..51fd01d 100644 --- a/fretcat_effects/src/effects/delay/delay.rs +++ b/fretcat_effects/src/effects/delay/delay.rs @@ -3,16 +3,16 @@ use crate::prelude::*; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct MonoDelay { wet: f32, - delays: [Delay; 2] + delays: [Delay; 2], } impl PartialEq for MonoDelay { fn eq(&self, other: &Self) -> bool { - self.wet == other.wet && - self.delays[0].feedback() == other.delays[0].feedback() && - self.delays[1].feedback() == other.delays[1].feedback() && - self.delays[0].delay_time_secs() == other.delays[0].delay_time_secs() && - self.delays[1].delay_time_secs() == other.delays[1].delay_time_secs() + self.wet == other.wet + && self.delays[0].feedback() == other.delays[0].feedback() + && self.delays[1].feedback() == other.delays[1].feedback() + && self.delays[0].delay_time_secs() == other.delays[0].delay_time_secs() + && self.delays[1].delay_time_secs() == other.delays[1].delay_time_secs() } } @@ -36,9 +36,9 @@ impl MonoDelay { impl Default for MonoDelay { fn default() -> Self { - Self { + Self { wet: 0.5, - delays: [Delay::default(), Delay::default()] + delays: [Delay::default(), Delay::default()], } } } @@ -53,10 +53,8 @@ impl AudioEffect for MonoDelay { } input_buffer.process_individual(|left, right| { - nih_plug::util::permit_alloc(|| { - *left = ((1.0 - self.wet) * *left) + (self.wet * self.delays[0].tick(*left)); - *right = ((1.0 - self.wet) * *right) + (self.wet * self.delays[1].tick(*right)); - }); + *left = ((1.0 - self.wet) * *left) + (self.wet * self.delays[0].tick(*left)); + *right = ((1.0 - self.wet) * *right) + (self.wet * self.delays[1].tick(*right)); }); } @@ -79,7 +77,7 @@ struct DelayView { wet: f32, #[lens(ignore)] - handle: EffectHandle + handle: EffectHandle, } impl DelayView { @@ -88,8 +86,9 @@ impl DelayView { wet: handle.wet * 100.0, time: handle.delays[0].delay_time_secs() * 1000.0, feedback: handle.delays[0].feedback() * 100.0, - handle: handle.clone() - }.build(cx, |cx| { + handle: handle.clone(), + } + .build(cx, |cx| { HStack::new(cx, |cx| { NamedKnob::new(cx, "Time", Self::time, false, 20.0..1000.0) .on_changing(|ex, val| ex.emit(Message::Time(val))); @@ -124,4 +123,4 @@ impl View for DelayView { } }); } -} \ No newline at end of file +} diff --git a/fretcat_effects/src/effects/delay/twin_delay.rs b/fretcat_effects/src/effects/delay/twin_delay.rs index 4d54dc6..60ab579 100644 --- a/fretcat_effects/src/effects/delay/twin_delay.rs +++ b/fretcat_effects/src/effects/delay/twin_delay.rs @@ -38,10 +38,8 @@ impl AudioEffect for TwinDelay { } input_buffer.process_individual(|left, right| { - nih_plug::util::permit_alloc(|| { - *left = ((1.0 - self.wet_l) * *left) + (self.wet_l * self.delays[0].tick(*left)); - *right = ((1.0 - self.wet_r) * *right) + (self.wet_r * self.delays[1].tick(*right)); - }); + *left = ((1.0 - self.wet_l) * *left) + (self.wet_l * self.delays[0].tick(*left)); + *right = ((1.0 - self.wet_r) * *right) + (self.wet_r * self.delays[1].tick(*right)); }); } diff --git a/fretcat_effects/src/effects/distortion/bit_crusher.rs b/fretcat_effects/src/effects/distortion/bit_crusher.rs index 4262a6f..3f4187d 100644 --- a/fretcat_effects/src/effects/distortion/bit_crusher.rs +++ b/fretcat_effects/src/effects/distortion/bit_crusher.rs @@ -1,23 +1,74 @@ use crate::prelude::*; -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct BitCrusher { bit_rate: f32, + sample_rate: f32, } impl Default for BitCrusher { fn default() -> Self { - Self { bit_rate: 1.0 } + Self { + sample_rate: 44100.0, + bit_rate: 44100.0, + } + } +} + +impl BitCrusher { + #[inline] + pub fn sample_rate(&self) -> f32 { + self.sample_rate + } + + #[inline] + pub fn bit_rate(&self) -> f32 { + self.bit_rate + } + + #[inline] + pub fn set_sample_rate(&mut self, sample_rate: f32) { + self.sample_rate = sample_rate; + } + + #[inline] + pub fn set_bit_rate(&mut self, bit_rate: f32) { + self.bit_rate = bit_rate; + } + + #[inline] + fn step_size(&self) -> usize { + (self.sample_rate() / self.bit_rate()) as usize } } impl AudioEffect for BitCrusher { fn process(&mut self, input_buffer: &mut Frame, transport: &nih_plug::prelude::Transport) { + if self.sample_rate() != transport.sample_rate { + self.set_sample_rate(transport.sample_rate); + } + + let step_size = self.step_size(); + let mut sample_index = 0; + let buffer_size = input_buffer.len(); + + while sample_index < buffer_size { + let first_index = sample_index; + let limit_index = (sample_index + step_size).min(buffer_size); + + while sample_index < limit_index { + input_buffer.process_channel(|channel| { + let value = channel[first_index]; + channel[sample_index] = value; + }); + sample_index += 1; + } + } } fn view(&self, cx: &mut Context, handle: EffectHandle) { - BitCrusherView::new(cx, EffectHandle::::from(handle)); + BitCrusherView::new(cx, EffectHandle::::from(handle)).class("base-effect"); } fn height(&self) -> f32 { @@ -38,16 +89,13 @@ struct BitCrusherView { impl BitCrusherView { pub fn new(cx: &mut Context, handle: EffectHandle) -> Handle { Self { - bit_rate: handle.bit_rate, + bit_rate: (handle.bit_rate / handle.sample_rate) * 100.0, handle: handle.clone(), } .build(cx, |cx| { - LabeledKnob::new( - cx, - Self::bit_rate, - false, - 0.0..100.0, - ); + NamedKnob::new(cx, "Amount", Self::bit_rate, false, 0.0..100.0) + .on_changing(|ex, val| ex.emit(Message::Bit_rate(val))); + Label::new(cx, "BIT CRUSHER").class("effect-title"); }) } } @@ -57,11 +105,11 @@ impl View for BitCrusherView { Some("bit-crusher") } - fn event(&mut self, cx: &mut EventContext, event: &mut Event) { + fn event(&mut self, _: &mut EventContext, event: &mut Event) { event.map(|event, _| match event { Message::Bit_rate(val) => { self.bit_rate = *val; - self.handle.bit_rate = *val; + self.handle.bit_rate = (*val / 100.0) * self.handle.sample_rate; } }); } diff --git a/fretcat_effects/src/effects/distortion/fuzz.rs b/fretcat_effects/src/effects/distortion/fuzz.rs index 8daaa3b..bff2b1c 100644 --- a/fretcat_effects/src/effects/distortion/fuzz.rs +++ b/fretcat_effects/src/effects/distortion/fuzz.rs @@ -1,22 +1,84 @@ -use crate::prelude::*; +use std::f32::consts::PI; +use crate::prelude::*; -#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct Fuzz { - pub volume: f32, + pub drive: f32, + pub tone: f32, + pub boost: f32, + pub fuzziness: f32, + max_freq_hz: f32, + min_freq_hz: f32, + filter: [ButterLowpass; NUM_CHANNELS], + pre_filter: [DCBlock; NUM_CHANNELS], +} + +impl PartialEq for Fuzz { + fn eq(&self, other: &Self) -> bool { + self.drive == other.drive && self.tone == other.tone && self.boost == other.boost + } } impl Default for Fuzz { fn default() -> Self { - Self { volume: 1.0 } + let min_freq_hz = 1000.0; + Self { + drive: 1.0, + tone: min_freq_hz, + boost: 1.0, + fuzziness: 1.0, + max_freq_hz: 2000.0, + min_freq_hz, + filter: [ButterLowpass::new(min_freq_hz); NUM_CHANNELS], + pre_filter: [DCBlock::new(500.0); NUM_CHANNELS], + } } } impl AudioEffect for Fuzz { - fn process(&mut self, input_buffer: &mut Frame, _transport: &nih_plug::prelude::Transport) {} + fn process(&mut self, input_buffer: &mut Frame, _transport: &nih_plug::prelude::Transport) { + if self.filter[0].sample_rate() != _transport.sample_rate { + self.filter[0].set_sample_rate(_transport.sample_rate); + self.filter[1].set_sample_rate(_transport.sample_rate); + } + + if self.pre_filter[0].sample_rate() != _transport.sample_rate { + self.pre_filter[0].set_sample_rate(_transport.sample_rate); + self.pre_filter[1].set_sample_rate(_transport.sample_rate); + } + + input_buffer.process_individual(|left, right| { + *left = self.pre_filter[0].tick(*left); + *right = self.pre_filter[1].tick(*right); + + let offset_l = left.abs() * (self.fuzziness / 100.0); + let offset_r = right.abs() * (self.fuzziness / 100.0); + + *left += offset_l; + *right += offset_r; + + let gain = ((self.boost / 100.0) * 100.0) + 1.0; + + *left *= gain; + *right *= gain; + + let a = (((self.drive + 1.0) / 101.0) * (PI / 2.0)).sin(); + let k = 2.0 * a / (1.0 - a); + + let drive_l = (1.0 + k) * *left / (1.0 + k * left.abs()); + let drive_r = (1.0 + k) * *right / (1.0 + k * right.abs()); + + *left = drive_l; + *right = drive_r; + + *left = self.filter[0].tick(*left); + *right = self.filter[1].tick(*right); + }); + } fn view(&self, cx: &mut Context, handle: EffectHandle) { - FuzzView::new(cx, EffectHandle::::from(handle)); + FuzzView::new(cx, EffectHandle::::from(handle)).class("base-effect"); } fn height(&self) -> f32 { @@ -24,10 +86,16 @@ impl AudioEffect for Fuzz { } } -#[derive(Debug, Clone, Data, Message, Lens)] +#[derive(Debug, Clone, Data, Lens, Message)] pub struct FuzzView { #[msg] - pub volume: f32, + pub drive: f32, + #[msg] + pub tone: f32, + #[msg] + pub boost: f32, + #[msg] + pub fuzziness: f32, #[lens(ignore)] #[data(ignore)] @@ -37,20 +105,34 @@ pub struct FuzzView { impl FuzzView { pub fn new(cx: &mut Context, handle: EffectHandle) -> Handle { Self { - volume: handle.volume, - handle, + drive: handle.drive, + tone: handle.tone, + boost: handle.boost, + fuzziness: handle.fuzziness, + handle: handle.clone(), } .build(cx, |cx| { HStack::new(cx, |cx| { - HStack::new(cx, |cx| { - Knob::new(cx, 1.0, Self::volume, false) - .on_changing(|cx, val| cx.emit(Message::Volume(val))) - .class("volume-knob"); - Label::new(cx, "Output Gain"); - }) - .class("fuzz-knob-group"); - }) - .class("fuzz"); + NamedKnob::new(cx, "Drive", Self::drive, false, 0.0..100.0) + .on_changing(|ex, val| ex.emit(Message::Drive(val))) + .class("gain-knob"); + NamedKnob::new(cx, "Boost", Self::boost, false, 0.0..100.0) + .on_changing(|ex, val| ex.emit(Message::Boost(val))) + .class("volume-knob"); + NamedKnob::new(cx, "Fuzziness", Self::fuzziness, false, 0.0..100.0) + .on_changing(|ex, val| ex.emit(Message::Fuzziness(val))) + .class("fuzz-knob"); + NamedKnob::new( + cx, + "Tone", + Self::tone, + false, + handle.min_freq_hz..handle.max_freq_hz, + ) + .on_changing(|ex, val| ex.emit(Message::Tone(val))) + .class("tone-knob"); + Label::new(cx, "FUZZ").class("effect-title"); + }); }) } } @@ -62,10 +144,28 @@ impl View for FuzzView { fn event(&mut self, _cx: &mut EventContext, event: &mut Event) { event.map(|event, _| match event { - Message::Volume(val) => { - self.volume = *val; - self.handle.volume = *val; + Message::Drive(val) => { + self.drive = *val; + self.handle.drive = *val; + } + Message::Tone(val) => { + self.tone = *val; + self.handle.tone = *val; + self.handle.filter.iter_mut().for_each(|filter| { + filter.set_cutoff( + *val, + ); + }); + } + Message::Boost(val) => { + self.boost = *val; + self.handle.boost = *val; + } + Message::Fuzziness(val) => { + self.fuzziness = *val; + self.handle.fuzziness = *val; } }); } } + diff --git a/fretcat_effects/src/effects/distortion/overdrive.rs b/fretcat_effects/src/effects/distortion/overdrive.rs index 9a5bf09..ede25d5 100644 --- a/fretcat_effects/src/effects/distortion/overdrive.rs +++ b/fretcat_effects/src/effects/distortion/overdrive.rs @@ -2,21 +2,20 @@ use std::f32::consts::PI; use crate::prelude::*; -use crate::common::{AudioFilter, FilterMode}; - #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Overdrive { - pub gain: f32, - pub freq: f32, - pub volume: f32, + pub drive: f32, + pub tone: f32, + pub boost: f32, max_freq_hz: f32, min_freq_hz: f32, - filter: [AudioFilter; NUM_CHANNELS], + filter: [ButterLowpass; NUM_CHANNELS], + pre_filter: [DCBlock; NUM_CHANNELS], } impl PartialEq for Overdrive { fn eq(&self, other: &Self) -> bool { - self.gain == other.gain && self.freq == other.freq && self.volume == other.volume + self.drive == other.drive && self.tone == other.tone && self.boost == other.boost } } @@ -24,25 +23,46 @@ impl Default for Overdrive { fn default() -> Self { let min_freq_hz = 1000.0; Self { - gain: 1.0, - freq: min_freq_hz, - volume: 1.0, + drive: 1.0, + tone: min_freq_hz, + boost: 1.0, max_freq_hz: 2000.0, min_freq_hz, - filter: [AudioFilter::new(FilterMode::Lowpass, 44100.0, min_freq_hz, 1.0); 2], + filter: [ButterLowpass::new(min_freq_hz); NUM_CHANNELS], + pre_filter: [DCBlock::new(500.0); NUM_CHANNELS], } } } impl AudioEffect for Overdrive { fn process(&mut self, input_buffer: &mut Frame, _transport: &nih_plug::prelude::Transport) { + if self.filter[0].sample_rate() != _transport.sample_rate { + self.filter[0].set_sample_rate(_transport.sample_rate); + self.filter[1].set_sample_rate(_transport.sample_rate); + } + + if self.pre_filter[0].sample_rate() != _transport.sample_rate { + self.pre_filter[0].set_sample_rate(_transport.sample_rate); + self.pre_filter[1].set_sample_rate(_transport.sample_rate); + } + input_buffer.process_individual(|left, right| { - let clipping_fn = |sample: f32| (2.0 / PI) * f32::atan(sample); + *left = self.pre_filter[0].tick(*left); + *right = self.pre_filter[1].tick(*right); - let output_gain = db_to_gain_fast(self.volume); + let gain = ((self.boost / 100.0) * 100.0) + 1.0; - *left = clipping_fn(*left * db_to_gain_fast(self.gain)) * output_gain; - *right = clipping_fn(*right * db_to_gain_fast(self.gain)) * output_gain; + *left *= gain; + *right *= gain; + + let a = (((self.drive + 1.0) / 101.0) * (PI / 2.0)).sin(); + let k = 2.0 * a / (1.0 - a); + + let drive_l = (1.0 + k) * *left / (1.0 + k * left.abs()); + let drive_r = (1.0 + k) * *right / (1.0 + k * right.abs()); + + *left = drive_l; + *right = drive_r; *left = self.filter[0].tick(*left); *right = self.filter[1].tick(*right); @@ -61,11 +81,11 @@ impl AudioEffect for Overdrive { #[derive(Debug, Clone, Data, Lens, Message)] pub struct OverdriveView { #[msg] - pub gain: f32, + pub drive: f32, #[msg] - pub freq: f32, + pub tone: f32, #[msg] - pub volume: f32, + pub boost: f32, #[lens(ignore)] #[data(ignore)] @@ -75,28 +95,28 @@ pub struct OverdriveView { impl OverdriveView { pub fn new(cx: &mut Context, handle: EffectHandle) -> Handle { Self { - gain: handle.gain, - freq: handle.freq, - volume: handle.volume, + drive: handle.drive, + tone: handle.tone, + boost: handle.boost, handle: handle.clone(), } .build(cx, |cx| { HStack::new(cx, |cx| { - NamedKnob::new(cx, "Gain", Self::gain, false, 1.0..20.0) - .on_changing(|ex, val| ex.emit(Message::Gain(val))) + NamedKnob::new(cx, "Drive", Self::drive, false, 1.0..99.0) + .on_changing(|ex, val| ex.emit(Message::Drive(val))) .class("gain-knob"); + NamedKnob::new(cx, "Boost", Self::boost, false, 1.0..99.0) + .on_changing(|ex, val| ex.emit(Message::Boost(val))) + .class("volume-knob"); NamedKnob::new( cx, "Tone", - Self::freq, + Self::tone, false, handle.min_freq_hz..handle.max_freq_hz, ) - .on_changing(|ex, val| ex.emit(Message::Freq(val))) + .on_changing(|ex, val| ex.emit(Message::Tone(val))) .class("tone-knob"); - NamedKnob::new(cx, "Output Volume", Self::volume, false, -10.0..10.0) - .on_changing(|ex, val| ex.emit(Message::Volume(val))) - .class("volume-knob"); Label::new(cx, "DRIVE").class("effect-title"); }); }) @@ -110,22 +130,22 @@ impl View for OverdriveView { fn event(&mut self, _cx: &mut EventContext, event: &mut Event) { event.map(|event, _| match event { - Message::Gain(val) => { - self.gain = *val; - self.handle.gain = *val; + Message::Drive(val) => { + self.drive = *val; + self.handle.drive = *val; } - Message::Freq(val) => { - self.freq = *val; - self.handle.freq = *val; + Message::Tone(val) => { + self.tone = *val; + self.handle.tone = *val; self.handle.filter.iter_mut().for_each(|filter| { filter.set_cutoff( *val, ); }); } - Message::Volume(val) => { - self.volume = *val; - self.handle.volume = *val; + Message::Boost(val) => { + self.boost = *val; + self.handle.boost = *val; } }); } diff --git a/fretcat_effects/src/effects/dynamics/band_pass.rs b/fretcat_effects/src/effects/dynamics/band_pass.rs index db982bf..9f28ff2 100644 --- a/fretcat_effects/src/effects/dynamics/band_pass.rs +++ b/fretcat_effects/src/effects/dynamics/band_pass.rs @@ -2,7 +2,7 @@ use crate::prelude::*; #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct BandPass { - filter: [AudioFilter; 2], + filter: [SvfFilter; 2], min_freq_hz: f32, max_freq_hz: f32, } @@ -14,7 +14,7 @@ impl Default for BandPass { Self { min_freq_hz, max_freq_hz, - filter: [AudioFilter::new(crate::common::FilterMode::BandPass, 44100.0, (max_freq_hz + min_freq_hz) / 2.0, 1.0); 2] + filter: [SvfFilter::new(crate::common::FilterMode::BandPass, 44100.0, (max_freq_hz + min_freq_hz) / 2.0, 1.0); 2] } } } diff --git a/fretcat_effects/src/effects/dynamics/high_pass.rs b/fretcat_effects/src/effects/dynamics/high_pass.rs index 7d6f076..3190c47 100644 --- a/fretcat_effects/src/effects/dynamics/high_pass.rs +++ b/fretcat_effects/src/effects/dynamics/high_pass.rs @@ -2,7 +2,7 @@ use crate::prelude::*; #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct HighPass { - filter: [AudioFilter; 2], + filter: [SvfFilter; 2], min_freq_hz: f32, max_freq_hz: f32, } @@ -14,7 +14,7 @@ impl Default for HighPass { Self { min_freq_hz, max_freq_hz, - filter: [AudioFilter::new( + filter: [SvfFilter::new( crate::common::FilterMode::Highpass, 44100.0, max_freq_hz, diff --git a/fretcat_effects/src/effects/dynamics/low_pass.rs b/fretcat_effects/src/effects/dynamics/low_pass.rs index 77c7276..5200fd0 100644 --- a/fretcat_effects/src/effects/dynamics/low_pass.rs +++ b/fretcat_effects/src/effects/dynamics/low_pass.rs @@ -2,7 +2,7 @@ use crate::prelude::*; #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct LowPass { - filter: [AudioFilter; 2], + filter: [SvfFilter; 2], min_freq_hz: f32, max_freq_hz: f32, } @@ -14,7 +14,7 @@ impl Default for LowPass { Self { min_freq_hz, max_freq_hz, - filter: [AudioFilter::new( + filter: [SvfFilter::new( crate::common::FilterMode::Lowpass, 44100.0, min_freq_hz, diff --git a/fretcat_effects/src/effects/reverb/studioreverb.rs b/fretcat_effects/src/effects/reverb/studioreverb.rs index 5d3bd7f..4b97fd7 100644 --- a/fretcat_effects/src/effects/reverb/studioreverb.rs +++ b/fretcat_effects/src/effects/reverb/studioreverb.rs @@ -74,6 +74,7 @@ impl StudioReverbView { .on_changing(|ex, val| ex.emit(Message::Size(val))); NamedKnob::new(cx, "Wet", Self::wet, false, 0.0..100.0) .on_changing(|ex, val| ex.emit(Message::Wet(val))); + Label::new(cx, "STUDIO REVERB").class("effect-title"); }); }) } diff --git a/fretcat_effects/src/frame.rs b/fretcat_effects/src/frame.rs index 60ccd48..07f20f8 100644 --- a/fretcat_effects/src/frame.rs +++ b/fretcat_effects/src/frame.rs @@ -19,6 +19,12 @@ impl<'a> Frame<'a> { }); } + #[inline] + pub fn process_channel(&mut self, mut f: impl FnMut(&mut [f32])) { + (f)(self.left); + (f)(self.right); + } + #[inline] pub fn get_mut_left(&mut self) -> &mut [f32] { self.left diff --git a/fretcat_serialization/src/mapper.rs b/fretcat_serialization/src/mapper.rs index 8c46055..75101ef 100644 --- a/fretcat_serialization/src/mapper.rs +++ b/fretcat_serialization/src/mapper.rs @@ -1,4 +1,4 @@ -use fretcat_effects::effects::{Fuzz, Overdrive, AudioEffect, StudioReverb, Gain, LowPass, HighPass, BandPass, MonoDelay, TwinDelay}; +use fretcat_effects::effects::{Fuzz, Overdrive, AudioEffect, StudioReverb, Gain, LowPass, HighPass, BandPass, MonoDelay, TwinDelay, BitCrusher}; use fretcat_macros::EffectMapper; use std::sync::Arc; @@ -10,6 +10,7 @@ pub enum Mapper { Overdrive(Overdrive), Fuzz(Fuzz), Gain(Gain), + BitCrusher(BitCrusher), LowPass(LowPass), HighPass(HighPass), diff --git a/fretcat_styles/effects/bit-crusher.css b/fretcat_styles/effects/bit-crusher.css index 31dc8fc..0a50d64 100644 --- a/fretcat_styles/effects/bit-crusher.css +++ b/fretcat_styles/effects/bit-crusher.css @@ -1,23 +1,4 @@ -bit-crusher { - background-color: rgb(183, 17, 17); -} - -bit-crusher labeled-knob knob { - width: 60px; - height: 60px; - background-image: linear-gradient(135deg, #363636 0%, #303030 100%); - background-color: transparent; -} - -bit-crusher labeled-knob { - width: 150px; -} - -bit-crusher labeled-knob label { - font-family: Sparkler-Demo; - color: #e3e3e3; -} - -bit-crusher labeled-knob knob .knob-tick { - background-color: #e3e3e3; +bit-crusher .effect-title { + top: 37%; + left: 5%; } \ No newline at end of file diff --git a/fretcat_styles/effects/studio-reverb.css b/fretcat_styles/effects/studio-reverb.css index cae9252..cc2331a 100644 --- a/fretcat_styles/effects/studio-reverb.css +++ b/fretcat_styles/effects/studio-reverb.css @@ -1,37 +1,4 @@ -.studio-reverb .wet-knob { - width: 75px; - height: 75px; -} - -.studio-reverb .size-knob { - width: 100px; - height: 100px; -} - -.studio-reverb label { - color: whitesmoke; - font-family: "Sparkler-Demo"; - font-size: 30; -} - -.studio-reverb-knob-group .knob-head { - background-image: linear-gradient(180deg, rgb(0, 0, 0), rgb(24, 24, 24)); - border-width: 0px; - background-color: transparent; -} - -.studio-reverb-knob-group arctrack { - color: rgb(169, 58, 2); -} - -.studio-reverb-knob-group > knob { - box-shadow: -1px -1px 8px #110220; -} - -.studio-reverb-knob-group { - child-top: 2s; -} - -.studio-reverb-knob-group > knob { - border-radius: 100%; +studio-reverb .effect-title { + left: 43%; + top: 75%; } \ No newline at end of file diff --git a/fretcat_styles/fretcat-styles.css b/fretcat_styles/fretcat-styles.css index 4ec2abb..893ad85 100644 --- a/fretcat_styles/fretcat-styles.css +++ b/fretcat_styles/fretcat-styles.css @@ -441,28 +441,9 @@ sidebar button:hover { .effect-title:disabled { color: #404040; -}bit-crusher { - background-color: rgb(183, 17, 17); -} - -bit-crusher labeled-knob knob { - width: 60px; - height: 60px; - background-image: linear-gradient(135deg, #363636 0%, #303030 100%); - background-color: transparent; -} - -bit-crusher labeled-knob { - width: 150px; -} - -bit-crusher labeled-knob label { - font-family: Sparkler-Demo; - color: #e3e3e3; -} - -bit-crusher labeled-knob knob .knob-tick { - background-color: #e3e3e3; +}bit-crusher .effect-title { + top: 37%; + left: 5%; }.filter-knob .knob-name { transform: translate(0px, 35px); } @@ -535,42 +516,9 @@ band-pass .effect-title { .fuzz-knob-group > knob { border-radius: 100%; -}.studio-reverb .wet-knob { - width: 75px; - height: 75px; -} - -.studio-reverb .size-knob { - width: 100px; - height: 100px; -} - -.studio-reverb label { - color: whitesmoke; - font-family: "Sparkler-Demo"; - font-size: 30; -} - -.studio-reverb-knob-group .knob-head { - background-image: linear-gradient(180deg, rgb(0, 0, 0), rgb(24, 24, 24)); - border-width: 0px; - background-color: transparent; -} - -.studio-reverb-knob-group arctrack { - color: rgb(169, 58, 2); -} - -.studio-reverb-knob-group > knob { - box-shadow: -1px -1px 8px #110220; -} - -.studio-reverb-knob-group { - child-top: 2s; -} - -.studio-reverb-knob-group > knob { - border-radius: 100%; +}studio-reverb .effect-title { + left: 43%; + top: 75%; }twin-delay .effect-title { left: 45%; top: 85%;