1
0
Fork 0

Add a pre-RM LPF to Crisp

This makes the effect more usable with high frequency inputs.
This commit is contained in:
Robbert van der Helm 2022-03-09 15:11:41 +01:00
parent 2811ab2996
commit 57ca8a5ccb

View file

@ -47,11 +47,13 @@ struct Crisp {
/// SIMD-ify this in the future. /// SIMD-ify this in the future.
prng: Pcg32iState, prng: Pcg32iState,
/// Resonant filters for low passing the input signal before RM'ing, to allow this to work with
/// inputs that already contain a lot of high freuqency content.
rm_input_lpf: [filter::Biquad<f32>; NUM_CHANNELS as usize],
/// Resonant filters for high passing the noise signal, to make it even brighter. /// Resonant filters for high passing the noise signal, to make it even brighter.
noise_hpf: [filter::Biquad<f32>; NUM_CHANNELS as usize], noise_hpf: [filter::Biquad<f32>; NUM_CHANNELS as usize],
} }
// TODO: Add a filter for the RM input
// TODO: Add more kinds of noise // TODO: Add more kinds of noise
#[derive(Params)] #[derive(Params)]
pub struct CrispParams { pub struct CrispParams {
@ -66,10 +68,16 @@ pub struct CrispParams {
#[id = "stereo"] #[id = "stereo"]
stereo_mode: EnumParam<StereoMode>, stereo_mode: EnumParam<StereoMode>,
/// The cutoff frequency for the high pass filter applied to the noise. /// The cutoff frequency for the low-pass filter applied to the input before RM'ing.
#[id = "rmlpff"]
rm_input_lpf_freq: FloatParam,
/// The Q frequency for the low-pass filter applied to the input before RM'ing.
#[id = "rmlpfq"]
rm_input_lpf_q: FloatParam,
/// The cutoff frequency for the high-pass filter applied to the noise.
#[id = "nzhpff"] #[id = "nzhpff"]
noise_hpf_freq: FloatParam, noise_hpf_freq: FloatParam,
/// The Q parameter for the high pass filter applied to the noise. /// The Q parameter for the high pass-filter applied to the noise.
#[id = "nzhpfq"] #[id = "nzhpfq"]
noise_hpf_q: FloatParam, noise_hpf_q: FloatParam,
@ -108,6 +116,7 @@ impl Default for Crisp {
sample_rate: 1.0, sample_rate: 1.0,
prng: INITIAL_PRNG_SEED, prng: INITIAL_PRNG_SEED,
rm_input_lpf: [filter::Biquad::default(); NUM_CHANNELS as usize],
noise_hpf: [filter::Biquad::default(); NUM_CHANNELS as usize], noise_hpf: [filter::Biquad::default(); NUM_CHANNELS as usize],
} }
} }
@ -126,11 +135,11 @@ impl Default for CrispParams {
mode: EnumParam::new("Mode", Mode::EvenCrispier), mode: EnumParam::new("Mode", Mode::EvenCrispier),
stereo_mode: EnumParam::new("Stereo Mode", StereoMode::Stereo), stereo_mode: EnumParam::new("Stereo Mode", StereoMode::Stereo),
noise_hpf_freq: FloatParam::new( rm_input_lpf_freq: FloatParam::new(
"Noise HPF Frequency", "RM LP Frequency",
1.0, 22_000.0,
FloatRange::Skewed { FloatRange::Skewed {
min: 1.0, min: 5.0,
max: 22_000.0, max: 22_000.0,
factor: FloatRange::skew_factor(-1.0), factor: FloatRange::skew_factor(-1.0),
}, },
@ -138,7 +147,7 @@ impl Default for CrispParams {
.with_smoother(SmoothingStyle::Logarithmic(100.0)) .with_smoother(SmoothingStyle::Logarithmic(100.0))
.with_unit(" Hz") .with_unit(" Hz")
.with_value_to_string(Arc::new(|value| { .with_value_to_string(Arc::new(|value| {
if value <= 1.0 { if value >= 22_000.0 {
String::from("Disabled") String::from("Disabled")
} else { } else {
format!("{:.0}", value) format!("{:.0}", value)
@ -146,13 +155,49 @@ impl Default for CrispParams {
})) }))
.with_string_to_value(Arc::new(|string| { .with_string_to_value(Arc::new(|string| {
if string == "Disabled" { if string == "Disabled" {
Some(1.0) Some(22_000.0)
} else {
string.trim().trim_end_matches(" Hz").parse().ok()
}
})),
rm_input_lpf_q: FloatParam::new(
"RM LP Resonance",
2.0f32.sqrt() / 2.0,
FloatRange::Skewed {
min: 2.0f32.sqrt() / 2.0,
max: 10.0,
factor: FloatRange::skew_factor(-1.0),
},
)
.with_smoother(SmoothingStyle::Logarithmic(100.0))
.with_value_to_string(formatters::f32_rounded(2)),
noise_hpf_freq: FloatParam::new(
"Noise HP Frequency",
1.0,
FloatRange::Skewed {
min: 5.0,
max: 22_000.0,
factor: FloatRange::skew_factor(-1.0),
},
)
.with_smoother(SmoothingStyle::Logarithmic(100.0))
.with_unit(" Hz")
.with_value_to_string(Arc::new(|value| {
if value <= 5.0 {
String::from("Disabled")
} else {
format!("{:.0}", value)
}
}))
.with_string_to_value(Arc::new(|string| {
if string == "Disabled" {
Some(5.0)
} else { } else {
string.trim().trim_end_matches(" Hz").parse().ok() string.trim().trim_end_matches(" Hz").parse().ok()
} }
})), })),
noise_hpf_q: FloatParam::new( noise_hpf_q: FloatParam::new(
"Noise HPF Resonance", "Noise HP Resonance",
2.0f32.sqrt() / 2.0, 2.0f32.sqrt() / 2.0,
FloatRange::Skewed { FloatRange::Skewed {
min: 2.0f32.sqrt() / 2.0, min: 2.0f32.sqrt() / 2.0,
@ -225,6 +270,9 @@ impl Plugin for Crisp {
// By using the same seeds each time bouncing can be made deterministic // By using the same seeds each time bouncing can be made deterministic
self.prng = INITIAL_PRNG_SEED; self.prng = INITIAL_PRNG_SEED;
for filter in &mut self.rm_input_lpf {
filter.reset();
}
for filter in &mut self.noise_hpf { for filter in &mut self.noise_hpf {
filter.reset(); filter.reset();
} }
@ -239,7 +287,7 @@ impl Plugin for Crisp {
let amount = self.params.amount.smoothed.next() * AMOUNT_GAIN_MULTIPLIER; let amount = self.params.amount.smoothed.next() * AMOUNT_GAIN_MULTIPLIER;
let output_gain = self.params.output_gain.smoothed.next(); let output_gain = self.params.output_gain.smoothed.next();
// Controls the HPF applied to the noise signal // Controls the pre-RM LPF and the HPF applied to the noise signal
self.maybe_update_filters(); self.maybe_update_filters();
// TODO: SIMD-ize this to process both channels at once // TODO: SIMD-ize this to process both channels at once
@ -248,15 +296,15 @@ impl Plugin for Crisp {
match self.params.stereo_mode.value() { match self.params.stereo_mode.value() {
StereoMode::Mono => { StereoMode::Mono => {
let noise = self.gen_noise(0); let noise = self.gen_noise(0);
for sample in channel_samples { for (channel_idx, sample) in channel_samples.into_iter().enumerate() {
*sample += self.do_ring_mod(*sample, noise) * amount; *sample += self.do_ring_mod(*sample, channel_idx, noise) * amount;
*sample *= output_gain; *sample *= output_gain;
} }
} }
StereoMode::Stereo => { StereoMode::Stereo => {
for (channel_idx, sample) in channel_samples.into_iter().enumerate() { for (channel_idx, sample) in channel_samples.into_iter().enumerate() {
let noise = self.gen_noise(channel_idx); let noise = self.gen_noise(channel_idx);
*sample += self.do_ring_mod(*sample, noise) * amount; *sample += self.do_ring_mod(*sample, channel_idx, noise) * amount;
*sample *= output_gain; *sample *= output_gain;
} }
} }
@ -274,8 +322,11 @@ impl Crisp {
self.noise_hpf[channel].process(noise) self.noise_hpf[channel].process(noise)
} }
/// Perform the RM step depending on the mode. /// Perform the RM step depending on the mode. This applies a low pass filter to the input
fn do_ring_mod(&self, sample: f32, noise: f32) -> f32 { /// before RM'ing.
fn do_ring_mod(&mut self, sample: f32, channel_idx: usize, noise: f32) -> f32 {
let sample = self.rm_input_lpf[channel_idx].process(sample);
// TODO: Avoid branching in the main loop, this just makes it a bit easier to prototype // TODO: Avoid branching in the main loop, this just makes it a bit easier to prototype
match self.params.mode.value() { match self.params.mode.value() {
Mode::Crispy => sample * noise, Mode::Crispy => sample * noise,
@ -286,15 +337,35 @@ impl Crisp {
/// Update the filter coefficients if needed. Should be called once per sample. /// Update the filter coefficients if needed. Should be called once per sample.
fn maybe_update_filters(&mut self) { fn maybe_update_filters(&mut self) {
if self.params.rm_input_lpf_freq.smoothed.is_smoothing()
|| self.params.rm_input_lpf_q.smoothed.is_smoothing()
{
self.update_rm_input_lpf();
}
if self.params.noise_hpf_freq.smoothed.is_smoothing() if self.params.noise_hpf_freq.smoothed.is_smoothing()
|| self.params.noise_hpf_q.smoothed.is_smoothing() || self.params.noise_hpf_q.smoothed.is_smoothing()
{ {
let frequency = self.params.noise_hpf_freq.smoothed.next(); self.update_noise_hpf();
let q = self.params.noise_hpf_q.smoothed.next(); }
let coefficients = filter::BiquadCoefficients::highpass(self.sample_rate, frequency, q); }
for filter in &mut self.noise_hpf {
filter.coefficients = coefficients; /// Update the filter coefficients if needed. Should be called explicitly from `initialize()`.
} fn update_rm_input_lpf(&mut self) {
let frequency = self.params.rm_input_lpf_freq.smoothed.next();
let q = self.params.rm_input_lpf_q.smoothed.next();
let coefficients = filter::BiquadCoefficients::lowpass(self.sample_rate, frequency, q);
for filter in &mut self.rm_input_lpf {
filter.coefficients = coefficients;
}
}
/// Update the filter coefficients if needed. Should be called explicitly from `initialize()`.
fn update_noise_hpf(&mut self) {
let frequency = self.params.noise_hpf_freq.smoothed.next();
let q = self.params.noise_hpf_q.smoothed.next();
let coefficients = filter::BiquadCoefficients::highpass(self.sample_rate, frequency, q);
for filter in &mut self.noise_hpf {
filter.coefficients = coefficients;
} }
} }
} }