Make the oversampling in Soft Vacuum configurable
This commit is contained in:
parent
c6765d91ac
commit
4b5f52252a
|
@ -15,6 +15,7 @@
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
use std::f32::consts::PI;
|
use std::f32::consts::PI;
|
||||||
|
use std::sync::atomic::Ordering;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use nih_plug::prelude::*;
|
use nih_plug::prelude::*;
|
||||||
|
@ -26,12 +27,14 @@ mod oversampling;
|
||||||
/// oversampling.
|
/// oversampling.
|
||||||
const MAX_BLOCK_SIZE: usize = 32;
|
const MAX_BLOCK_SIZE: usize = 32;
|
||||||
|
|
||||||
/// The 2-logarithm of the oversampling amount to use. 4x oversampling corresponds to factor 2.
|
/// The 2-logarithm of the maximum oversampling amount to use. 16x oversampling corresponds to factor
|
||||||
// FIXME: Set this back to 2
|
/// 4.
|
||||||
const OVERSAMPLING_FACTOR: usize = 2;
|
const MAX_OVERSAMPLING_FACTOR: usize = 4;
|
||||||
const OVERSAMPLING_TIMES: usize = 2usize.pow(OVERSAMPLING_FACTOR as u32);
|
const MAX_OVERSAMPLING_TIMES: usize = oversampling_factor_to_times(MAX_OVERSAMPLING_FACTOR);
|
||||||
|
const MAX_OVERSAMPLED_BLOCK_SIZE: usize = MAX_BLOCK_SIZE * MAX_OVERSAMPLING_TIMES;
|
||||||
|
|
||||||
const MAX_OVERSAMPLED_BLOCK_SIZE: usize = MAX_BLOCK_SIZE * OVERSAMPLING_TIMES;
|
/// This corresponds to 4x oversampling.
|
||||||
|
const DEFAULT_OVERSAMPLING_FACTOR: usize = 2;
|
||||||
|
|
||||||
struct SoftVacuum {
|
struct SoftVacuum {
|
||||||
params: Arc<SoftVacuumParams>,
|
params: Arc<SoftVacuumParams>,
|
||||||
|
@ -65,34 +68,45 @@ struct SoftVacuumParams {
|
||||||
/// A linear dry/wet mix parameter.
|
/// A linear dry/wet mix parameter.
|
||||||
#[id = "dry_wet_ratio"]
|
#[id = "dry_wet_ratio"]
|
||||||
pub dry_wet_ratio: FloatParam,
|
pub dry_wet_ratio: FloatParam,
|
||||||
|
|
||||||
|
/// The current oversampling factor. This is the 2-logarithm of the oversampling amount. 0
|
||||||
|
/// corresponds to 1x/no oversampling, 1 to 2x oversampling, 2 to 4x, etc..
|
||||||
|
#[id = "oversampling_factor"]
|
||||||
|
pub oversampling_factor: IntParam,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for SoftVacuumParams {
|
impl Default for SoftVacuumParams {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
|
// This is set by the `oversampling_factor` parameter and is used by the smoothers of the
|
||||||
|
// other parmaeters so the oversampling amount always stays in sync
|
||||||
|
let oversampling_times = Arc::new(AtomicF32::new(oversampling_factor_to_times(
|
||||||
|
DEFAULT_OVERSAMPLING_FACTOR,
|
||||||
|
) as f32));
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
// Goes up to 200%, with the second half being nonlinear
|
// Goes up to 200%, with the second half being nonlinear
|
||||||
drive: FloatParam::new("Drive", 0.0, FloatRange::Linear { min: 0.0, max: 2.0 })
|
drive: FloatParam::new("Drive", 0.0, FloatRange::Linear { min: 0.0, max: 2.0 })
|
||||||
.with_unit("%")
|
.with_unit("%")
|
||||||
.with_smoother(
|
.with_smoother(SmoothingStyle::OversamplingAware(
|
||||||
SmoothingStyle::Linear(20.0)
|
oversampling_times.clone(),
|
||||||
.for_oversampling_factor(OVERSAMPLING_FACTOR as f32),
|
&SmoothingStyle::Linear(20.0),
|
||||||
)
|
))
|
||||||
.with_value_to_string(formatters::v2s_f32_percentage(0))
|
.with_value_to_string(formatters::v2s_f32_percentage(0))
|
||||||
.with_string_to_value(formatters::s2v_f32_percentage()),
|
.with_string_to_value(formatters::s2v_f32_percentage()),
|
||||||
warmth: FloatParam::new("Warmth", 0.0, FloatRange::Linear { min: 0.0, max: 1.0 })
|
warmth: FloatParam::new("Warmth", 0.0, FloatRange::Linear { min: 0.0, max: 1.0 })
|
||||||
.with_unit("%")
|
.with_unit("%")
|
||||||
.with_smoother(
|
.with_smoother(SmoothingStyle::OversamplingAware(
|
||||||
SmoothingStyle::Linear(10.0)
|
oversampling_times.clone(),
|
||||||
.for_oversampling_factor(OVERSAMPLING_FACTOR as f32),
|
&SmoothingStyle::Linear(10.0),
|
||||||
)
|
))
|
||||||
.with_value_to_string(formatters::v2s_f32_percentage(0))
|
.with_value_to_string(formatters::v2s_f32_percentage(0))
|
||||||
.with_string_to_value(formatters::s2v_f32_percentage()),
|
.with_string_to_value(formatters::s2v_f32_percentage()),
|
||||||
aura: FloatParam::new("Aura", 0.0, FloatRange::Linear { min: 0.0, max: PI })
|
aura: FloatParam::new("Aura", 0.0, FloatRange::Linear { min: 0.0, max: PI })
|
||||||
.with_unit("%")
|
.with_unit("%")
|
||||||
.with_smoother(
|
.with_smoother(SmoothingStyle::OversamplingAware(
|
||||||
SmoothingStyle::Linear(10.0)
|
oversampling_times.clone(),
|
||||||
.for_oversampling_factor(OVERSAMPLING_FACTOR as f32),
|
&SmoothingStyle::Linear(10.0),
|
||||||
)
|
))
|
||||||
// We're displaying the value as a percentage even though it goes from `[0, pi]`
|
// We're displaying the value as a percentage even though it goes from `[0, pi]`
|
||||||
.with_value_to_string({
|
.with_value_to_string({
|
||||||
let formatter = formatters::v2s_f32_percentage(0);
|
let formatter = formatters::v2s_f32_percentage(0);
|
||||||
|
@ -115,20 +129,47 @@ impl Default for SoftVacuumParams {
|
||||||
)
|
)
|
||||||
.with_unit(" dB")
|
.with_unit(" dB")
|
||||||
// The value does not go down to 0 so we can do logarithmic here
|
// The value does not go down to 0 so we can do logarithmic here
|
||||||
.with_smoother(
|
.with_smoother(SmoothingStyle::OversamplingAware(
|
||||||
SmoothingStyle::Logarithmic(10.0)
|
oversampling_times.clone(),
|
||||||
.for_oversampling_factor(OVERSAMPLING_FACTOR as f32),
|
&SmoothingStyle::Logarithmic(10.0),
|
||||||
)
|
))
|
||||||
.with_value_to_string(formatters::v2s_f32_gain_to_db(2))
|
.with_value_to_string(formatters::v2s_f32_gain_to_db(2))
|
||||||
.with_string_to_value(formatters::s2v_f32_gain_to_db()),
|
.with_string_to_value(formatters::s2v_f32_gain_to_db()),
|
||||||
dry_wet_ratio: FloatParam::new("Mix", 1.0, FloatRange::Linear { min: 0.0, max: 1.0 })
|
dry_wet_ratio: FloatParam::new("Mix", 1.0, FloatRange::Linear { min: 0.0, max: 1.0 })
|
||||||
.with_unit("%")
|
.with_unit("%")
|
||||||
.with_smoother(
|
.with_smoother(SmoothingStyle::OversamplingAware(
|
||||||
SmoothingStyle::Linear(10.0)
|
oversampling_times.clone(),
|
||||||
.for_oversampling_factor(OVERSAMPLING_FACTOR as f32),
|
&SmoothingStyle::Linear(10.0),
|
||||||
)
|
))
|
||||||
.with_value_to_string(formatters::v2s_f32_percentage(0))
|
.with_value_to_string(formatters::v2s_f32_percentage(0))
|
||||||
.with_string_to_value(formatters::s2v_f32_percentage()),
|
.with_string_to_value(formatters::s2v_f32_percentage()),
|
||||||
|
|
||||||
|
oversampling_factor: IntParam::new(
|
||||||
|
"Oversampling",
|
||||||
|
DEFAULT_OVERSAMPLING_FACTOR as i32,
|
||||||
|
IntRange::Linear {
|
||||||
|
min: 0,
|
||||||
|
max: MAX_OVERSAMPLING_FACTOR as i32,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.with_unit("x")
|
||||||
|
.with_callback(Arc::new(move |new_factor| {
|
||||||
|
oversampling_times.store(
|
||||||
|
oversampling_factor_to_times(new_factor as usize) as f32,
|
||||||
|
Ordering::Relaxed,
|
||||||
|
);
|
||||||
|
}))
|
||||||
|
.with_value_to_string(Arc::new(|value| {
|
||||||
|
// NIH-plug prevents `value` from being out of range and thus negative
|
||||||
|
let oversampling_times = oversampling_factor_to_times(value as usize);
|
||||||
|
|
||||||
|
oversampling_times.to_string()
|
||||||
|
}))
|
||||||
|
.with_string_to_value(Arc::new(|string| {
|
||||||
|
let oversampling_times: usize = string.parse().ok()?;
|
||||||
|
|
||||||
|
Some(oversampling_times_to_factor(oversampling_times) as i32)
|
||||||
|
})),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -185,14 +226,14 @@ impl Plugin for SoftVacuum {
|
||||||
|
|
||||||
self.hard_vacuum_processors
|
self.hard_vacuum_processors
|
||||||
.resize_with(num_channels, hard_vacuum::HardVacuum::default);
|
.resize_with(num_channels, hard_vacuum::HardVacuum::default);
|
||||||
// If the number of stages ever becomes configurable, then this needs to also change the
|
|
||||||
// existinginstances
|
|
||||||
self.oversamplers.resize_with(num_channels, || {
|
self.oversamplers.resize_with(num_channels, || {
|
||||||
oversampling::Lanczos3Oversampler::new(MAX_BLOCK_SIZE, OVERSAMPLING_FACTOR)
|
oversampling::Lanczos3Oversampler::new(MAX_BLOCK_SIZE, MAX_OVERSAMPLING_FACTOR)
|
||||||
});
|
});
|
||||||
|
|
||||||
if let Some(oversampler) = self.oversamplers.first() {
|
if let Some(oversampler) = self.oversamplers.first() {
|
||||||
context.set_latency_samples(oversampler.latency(OVERSAMPLING_FACTOR));
|
context.set_latency_samples(
|
||||||
|
oversampler.latency(self.params.oversampling_factor.value() as usize),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
true
|
true
|
||||||
|
@ -212,25 +253,29 @@ impl Plugin for SoftVacuum {
|
||||||
&mut self,
|
&mut self,
|
||||||
buffer: &mut Buffer,
|
buffer: &mut Buffer,
|
||||||
_aux: &mut AuxiliaryBuffers,
|
_aux: &mut AuxiliaryBuffers,
|
||||||
_context: &mut impl ProcessContext<Self>,
|
context: &mut impl ProcessContext<Self>,
|
||||||
) -> ProcessStatus {
|
) -> ProcessStatus {
|
||||||
// TODO: When the oversampling amount becomes dynamic, then we should update the latency here:
|
let oversampling_factor = self.params.oversampling_factor.value() as usize;
|
||||||
// context.set_latency_samples(self.oversampler.latency() as u32);
|
let oversampling_times = oversampling_factor_to_times(oversampling_factor);
|
||||||
|
|
||||||
|
// If the oversampling factor parameter is changed then the host needs to know about the new
|
||||||
|
// latency
|
||||||
|
if let Some(oversampler) = self.oversamplers.first() {
|
||||||
|
context.set_latency_samples(oversampler.latency(oversampling_factor));
|
||||||
|
}
|
||||||
|
|
||||||
// The Hard Vacuum algorithm makes use of slews, and the aura control amplifies this part.
|
// The Hard Vacuum algorithm makes use of slews, and the aura control amplifies this part.
|
||||||
// The oversampling rounds out the waveform and reduces those slews. This is a rough
|
// The oversampling rounds out the waveform and reduces those slews. This is a rough
|
||||||
// compensation to get the distortion to sound like it normally would. The alternative would
|
// compensation to get the distortion to sound like it normally would. The alternative would
|
||||||
// be to upsample the slews independently.
|
// be to upsample the slews independently.
|
||||||
// FIXME: Maybe just upsample the slew signal instead, that should be more accurate
|
// FIXME: Maybe just upsample the slew signal instead, that should be more accurate
|
||||||
let slew_oversampling_compensation_factor = (OVERSAMPLING_TIMES - 1) as f32 * 0.7;
|
let slew_oversampling_compensation_factor = (oversampling_times - 1) as f32 * 0.7;
|
||||||
|
|
||||||
for (_, block) in buffer.iter_blocks(MAX_BLOCK_SIZE) {
|
for (_, block) in buffer.iter_blocks(MAX_BLOCK_SIZE) {
|
||||||
let block_len = block.samples();
|
let block_len = block.samples();
|
||||||
let upsampled_block_len = block_len * OVERSAMPLING_TIMES;
|
let upsampled_block_len = block_len * oversampling_times;
|
||||||
|
|
||||||
// These are the parameters for the distortion algorithm
|
// These are the parameters for the distortion algorithm
|
||||||
// TODO: When the oversampling amount becomes dynamic, then the block size here needs to
|
|
||||||
// change depending on the oversampling amount
|
|
||||||
let mut drive = [0.0; MAX_OVERSAMPLED_BLOCK_SIZE];
|
let mut drive = [0.0; MAX_OVERSAMPLED_BLOCK_SIZE];
|
||||||
self.params
|
self.params
|
||||||
.drive
|
.drive
|
||||||
|
@ -264,7 +309,7 @@ impl Plugin for SoftVacuum {
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
.zip(self.hard_vacuum_processors.iter_mut()),
|
.zip(self.hard_vacuum_processors.iter_mut()),
|
||||||
) {
|
) {
|
||||||
oversampler.process(block_channel, OVERSAMPLING_FACTOR, |upsampled| {
|
oversampler.process(block_channel, oversampling_factor, |upsampled| {
|
||||||
assert!(upsampled.len() == upsampled_block_len);
|
assert!(upsampled.len() == upsampled_block_len);
|
||||||
|
|
||||||
for (sample_idx, sample) in upsampled.iter_mut().enumerate() {
|
for (sample_idx, sample) in upsampled.iter_mut().enumerate() {
|
||||||
|
@ -294,6 +339,15 @@ impl Plugin for SoftVacuum {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Used in the conversion for the oversampling amount parameter
|
||||||
|
const fn oversampling_factor_to_times(factor: usize) -> usize {
|
||||||
|
2usize.pow(factor as u32)
|
||||||
|
}
|
||||||
|
|
||||||
|
const fn oversampling_times_to_factor(times: usize) -> usize {
|
||||||
|
times.ilog2() as usize
|
||||||
|
}
|
||||||
|
|
||||||
impl ClapPlugin for SoftVacuum {
|
impl ClapPlugin for SoftVacuum {
|
||||||
const CLAP_ID: &'static str = "nl.robbertvanderhelm.soft-vacuum";
|
const CLAP_ID: &'static str = "nl.robbertvanderhelm.soft-vacuum";
|
||||||
const CLAP_DESCRIPTION: Option<&'static str> =
|
const CLAP_DESCRIPTION: Option<&'static str> =
|
||||||
|
|
Loading…
Reference in a new issue