diff --git a/plugins/crisp/src/lib.rs b/plugins/crisp/src/lib.rs index dc49b5c2..793075cb 100644 --- a/plugins/crisp/src/lib.rs +++ b/plugins/crisp/src/lib.rs @@ -38,8 +38,8 @@ struct Crisp { prng: Pcg32iState, } -// TODO: Filters -// TODO: Mono/stereo/mid-side switch +// TODO: Add a filter for the AM input +// TODO: Add more kinds of noise #[derive(Params)] pub struct CrispParams { /// On a range of `[0, 1]`, how much of the modulated sound to mix in. @@ -49,6 +49,9 @@ pub struct CrispParams { /// AMs the positive part of the waveform. #[id = "mode"] mode: EnumParam, + /// How to handle stereo signals. See [`StereoMode`]. + #[id = "stereo"] + stereo_mode: EnumParam, /// Output gain, as voltage gain. Displayed in decibels. #[id = "output"] @@ -68,6 +71,15 @@ enum Mode { EvenCrispierNegated, } +/// Controls how to handle stereo input. +#[derive(Enum, Debug, PartialEq)] +enum StereoMode { + /// Use the same noise for both channels. + Mono, + /// Use a different noise source per channel. + Stereo, +} + impl Default for Crisp { fn default() -> Self { Self { @@ -88,6 +100,7 @@ impl Default for CrispParams { .with_value_to_string(formatters::f32_percentage(0)) .with_string_to_value(formatters::from_f32_percentage()), mode: EnumParam::new("Mode", Mode::EvenCrispier), + stereo_mode: EnumParam::new("Stereo Mode", StereoMode::Stereo), output_gain: FloatParam::new( "Output", 1.0, @@ -147,17 +160,23 @@ impl Plugin for Crisp { let output_gain = self.params.output_gain.smoothed.next(); // TODO: SIMD-ize this to process both channels at once - for sample in channel_samples.into_iter() { - let noise = self.prng.next_f32() * 2.0 - 1.0; - // TODO: Avoid branching here later - let am_result = match self.params.mode.value() { - Mode::Crispy => *sample * noise, - Mode::EvenCrispier => sample.max(0.0) * noise, - Mode::EvenCrispierNegated => sample.max(0.0) * noise, - }; - - *sample += am_result * amount; - *sample *= output_gain; + // TODO: Avoid branching twice here. Modern branch predictors are pretty good at this + // though. + match self.params.stereo_mode.value() { + StereoMode::Mono => { + let noise = self.gen_noise(); + for sample in channel_samples { + *sample += self.do_am(*sample, noise) * amount; + *sample *= output_gain; + } + } + StereoMode::Stereo => { + for sample in channel_samples { + let noise = self.gen_noise(); + *sample += self.do_am(*sample, noise) * amount; + *sample *= output_gain; + } + } } } @@ -165,6 +184,23 @@ impl Plugin for Crisp { } } +impl Crisp { + /// Generate a new uniform noise sample. + fn gen_noise(&mut self) -> f32 { + self.prng.next_f32() * 2.0 - 1.0 + } + + /// Perform the AM step depending on the mode. + fn do_am(&self, sample: f32, noise: f32) -> f32 { + // TODO: Avoid branching in the main loop, this just makes it a bit easier to prototype + match self.params.mode.value() { + Mode::Crispy => sample * noise, + Mode::EvenCrispier => sample.max(0.0) * noise, + Mode::EvenCrispierNegated => sample.max(0.0) * noise, + } + } +} + impl ClapPlugin for Crisp { const CLAP_ID: &'static str = "nl.robbertvanderhelm.crisp"; const CLAP_DESCRIPTION: &'static str = "Adds a bright crispy top end to low bass sounds";