1
0
Fork 0

Modify oversampler to allow variable no of stages

This is needed to be able to add a control for the oversampler amount.
This commit is contained in:
Robbert van der Helm 2023-04-05 17:21:23 +02:00
parent 40db21277e
commit c6765d91ac
2 changed files with 83 additions and 51 deletions

View file

@ -192,7 +192,7 @@ impl Plugin for SoftVacuum {
}); });
if let Some(oversampler) = self.oversamplers.first() { if let Some(oversampler) = self.oversamplers.first() {
context.set_latency_samples(oversampler.latency() as u32); context.set_latency_samples(oversampler.latency(OVERSAMPLING_FACTOR));
} }
true true
@ -264,7 +264,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, |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() {

View file

@ -72,12 +72,14 @@ const LANZCOS3_KERNEL_LATENCY: usize = LANCZOS3_UPSAMPLING_KERNEL.len() / 2;
/// This only handles a single audio channel. Use multiple instances for multichannel audio. /// This only handles a single audio channel. Use multiple instances for multichannel audio.
#[derive(Debug)] #[derive(Debug)]
pub struct Lanczos3Oversampler { pub struct Lanczos3Oversampler {
/// The state used for each oversampling stage. /// The state used for each oversampling stage. Also contains stages that are not being used, so
/// the number of stages can change without allocating. The number of currently active
/// stages/the oversampling factor passed to [`process()`][Self::process()] determines how many
/// of these are actually used.
stages: Vec<Lanzcos3Stage>, stages: Vec<Lanzcos3Stage>,
/// The oversampler's latency. Precomputed since the number of stages cannot change at this /// The oversampler's latency. Precomputed for each possible number of active stages.
/// time. latencies: Vec<u32>,
latency: u32,
} }
/// A single oversampling stage. Contains the ring buffers and current position in that ringbuffer /// A single oversampling stage. Contains the ring buffers and current position in that ringbuffer
@ -110,18 +112,28 @@ struct Lanzcos3Stage {
} }
impl Lanczos3Oversampler { impl Lanczos3Oversampler {
/// Create a new oversampling for the specified oversampling factor, or the 2-logarithm of the /// Create a new oversampler that can oversample to up to the specified oversampling factor, or
/// oversampling amount. 1x oversampling (aka, do nothing) = 0, 2x oversampling = 1, 4x /// the 2-logarithm of the oversampling amount. 1x oversampling (aka, do nothing) = 0, 2x
/// oversampling = 3, etc. /// oversampling = 1, 4x oversampling = 3, etc. The actual amount of oversampling stages used is
pub fn new(maximum_block_size: usize, factor: usize) -> Self { /// passed to the `process()` function, and must be set to `max_factor` or lower.
let mut stages = Vec::with_capacity(factor); pub fn new(maximum_block_size: usize, max_factor: usize) -> Self {
for stage in 0..factor { let mut stages = Vec::with_capacity(max_factor);
for stage in 0..max_factor {
stages.push(Lanzcos3Stage::new(maximum_block_size, stage)) stages.push(Lanzcos3Stage::new(maximum_block_size, stage))
} }
let latency = stages.iter().map(|stage| stage.effective_latency()).sum(); // Since the number of active oversampling stages is passed to the process function, we also
// need to know the effective latencies of all possible oversampling settings in advance.
let latencies = stages
.iter()
.map(|stage| stage.effective_latency())
.scan(0, |total_latency, latency| {
*total_latency += latency;
Some(*total_latency)
})
.collect();
Self { stages, latency } Self { stages, latencies }
} }
/// Reset the oversampling filters to their initial states. /// Reset the oversampling filters to their initial states.
@ -131,20 +143,33 @@ impl Lanczos3Oversampler {
} }
} }
/// Get the latency in samples. Fractional latency is automatically avoided. /// Get the latency in samples for the given oversampling factor. Fractional latency is
pub fn latency(&self) -> u32 { /// automatically avoided.
self.latency
}
/// Upsample `block`, process the upsampled version using `f`, and then downsample it again and
/// write the results back to `block` with a [`latency()`][Self::latency()] sample delay.
/// ///
/// # Panics /// # Panics
/// ///
/// Panics if `block`'s length is longer than the maximum block size. /// Panics if `factor > max_factor`.
pub fn process(&mut self, block: &mut [f32], f: impl FnOnce(&mut [f32])) { pub fn latency(&self, factor: usize) -> u32 {
if factor == 0 {
0
} else {
self.latencies[factor - 1]
}
}
/// Upsample `block` using the specified oversampling factor, process the upsampled version
/// using `f`, and then downsample it again and write the results back to `block` with a
/// [`latency()`][Self::latency()] sample delay.
///
/// # Panics
///
/// Panics if `factor > max_factor`, or if `block`'s length is longer than the maximum block
/// size.
pub fn process(&mut self, block: &mut [f32], factor: usize, f: impl FnOnce(&mut [f32])) {
assert!(factor <= self.stages.len());
// This is the 1x oversampling case, this should also modify the block to be consistent // This is the 1x oversampling case, this should also modify the block to be consistent
if self.stages.is_empty() { if factor == 0 {
f(block); f(block);
return; return;
} }
@ -154,29 +179,31 @@ impl Lanczos3Oversampler {
"The block's size exceeds the maximum block size" "The block's size exceeds the maximum block size"
); );
let upsampled = self.upsample_from(block); let upsampled = self.upsample_from(block, factor);
f(upsampled); f(upsampled);
self.downsample_to(block) self.downsample_to(block, factor)
} }
/// Upsample `block` through all of the oversampling stages. Returns a reference to the /// Upsample `block` through `factor` oversampling stages. Returns a reference to the
/// oversampled output stored in the last `LancZos3Stage`'s scratch buffer **with the correct /// oversampled output stored in the last `LancZos3Stage`'s scratch buffer **with the correct
/// length**. This is a multiple of `block`'s length, which may be shorter than the entire /// length**. This is a multiple of `block`'s length, which may be shorter than the entire
/// scratch buffer's length if `block` is shorter than the configured maximum block length. /// scratch buffer's length if `block` is shorter than the configured maximum block length.
/// ///
/// # Panics /// # Panics
/// ///
/// Panics if `block`'s length is longer than the maximum block size, or if the number of /// Panics if `block`'s length is longer than the maximum block size, if the number of
/// oversampling stages is zero. This is already checked for in the process function. /// oversampling is smaller than `factor`, or if `factor` is zero. This is already checked for
fn upsample_from(&mut self, block: &[f32]) -> &mut [f32] { /// in the process function.
assert!(!self.stages.is_empty()); fn upsample_from(&mut self, block: &[f32], factor: usize) -> &mut [f32] {
assert_ne!(factor, 0);
assert!(factor <= self.stages.len());
// The first stage is upsampled from `block`, and everything after that is upsampled from // The first stage is upsampled from `block`, and everything after that is upsampled from
// the stage preceeding it // the stage preceeding it
self.stages[0].upsample_from(block); self.stages[0].upsample_from(block);
let mut previous_upsampled_block_len = block.len() * 2; let mut previous_upsampled_block_len = block.len() * 2;
for to_stage_idx in 1..self.stages.len() { for to_stage_idx in 1..factor {
// This requires splitting the vector so we can borrow the from-stage immutably and the // This requires splitting the vector so we can borrow the from-stage immutably and the
// to-stage mutably at the same time // to-stage mutably at the same time
let ([.., from], [to, ..]) = self.stages.split_at_mut(to_stage_idx) else { unreachable!() }; let ([.., from], [to, ..]) = self.stages.split_at_mut(to_stage_idx) else { unreachable!() };
@ -185,26 +212,27 @@ impl Lanczos3Oversampler {
previous_upsampled_block_len *= 2; previous_upsampled_block_len *= 2;
} }
&mut self.stages.last_mut().unwrap().scratch_buffer[..previous_upsampled_block_len] &mut self.stages[factor - 1].scratch_buffer[..previous_upsampled_block_len]
} }
/// Downsample starting from the last oversampling stage, writing the results from downsampling /// Downsample starting from the `factor`th oversampling stage, writing the results from
/// the first stage to `block`. `block`'s actual length is taken into account to compute the length of /// downsampling the first stage to `block`. `block`'s actual length is taken into account to
/// the oversampled blocks. /// compute the length of the oversampled blocks.
/// ///
/// # Panics /// # Panics
/// ///
/// Panics if `block`'s length is longer than the maximum block size, or if the number of /// Panics if `block`'s length is longer than the maximum block size, if the number of
/// oversampling stages is zero. This is already checked for in the process function. /// oversampling is smaller than `factor`, or if `factor` is zero. This is already checked for
fn downsample_to(&mut self, block: &mut [f32]) { /// in the process function.
assert!(!self.stages.is_empty()); fn downsample_to(&mut self, block: &mut [f32], factor: usize) {
assert_ne!(factor, 0);
assert!(factor <= self.stages.len());
// This is the reverse of `upsample_from`. Starting from the last stage, the oversampling // This is the reverse of `upsample_from`. Starting from the last stage, the oversampling
// stages are downsampled to the previous stage and then the first stage is downsampled to // stages are downsampled to the previous stage and then the first stage is downsampled to
// `block`. // `block`.
let num_stages = self.stages.len(); let mut next_downsampled_block_len = block.len() * 2usize.pow(factor as u32 - 1);
let mut next_downsampled_block_len = block.len() * 2usize.pow(num_stages as u32 - 1); for to_stage_idx in (1..factor).rev() {
for to_stage_idx in (1..self.stages.len()).rev() {
// This requires splitting the vector so we can borrow the from-stage immutably and the // This requires splitting the vector so we can borrow the from-stage immutably and the
// to-stage mutably at the same time // to-stage mutably at the same time
let ([.., to], [from, ..]) = self.stages.split_at_mut(to_stage_idx) else { unreachable!() }; let ([.., to], [from, ..]) = self.stages.split_at_mut(to_stage_idx) else { unreachable!() };
@ -498,16 +526,18 @@ mod tests {
let mut oversampler = let mut oversampler =
Lanczos3Oversampler::new(delta_impulse.len(), oversampling_factor); Lanczos3Oversampler::new(delta_impulse.len(), oversampling_factor);
let reported_latency = oversampler.latency(oversampling_factor) as usize;
assert!( assert!(
delta_impulse.len() > oversampler.latency() as usize, delta_impulse.len() > reported_latency,
"The delta impulse array is too small to test the latency at oversampling factor \ "The delta impulse array is too small to test the latency at oversampling factor \
{oversampling_factor}, this is an error with the test case" {oversampling_factor}, this is an error with the test case"
); );
oversampler.process(&mut delta_impulse, |_| ()); oversampler.process(&mut delta_impulse, oversampling_factor, |_| ());
let new_impulse_idx = argmax(delta_impulse); let new_impulse_idx = argmax(delta_impulse);
assert_eq!(new_impulse_idx as u32, oversampler.latency()); assert_eq!(new_impulse_idx, reported_latency);
// The latency should also not be fractional // The latency should also not be fractional
assert!(delta_impulse[new_impulse_idx] > delta_impulse[new_impulse_idx - 1]); assert!(delta_impulse[new_impulse_idx] > delta_impulse[new_impulse_idx - 1]);
@ -529,17 +559,19 @@ mod tests {
let mut output = input; let mut output = input;
let mut oversampler = Lanczos3Oversampler::new(output.len(), oversampling_factor); let mut oversampler = Lanczos3Oversampler::new(output.len(), oversampling_factor);
oversampler.process(&mut output, |upsampled| { oversampler.process(&mut output, oversampling_factor, |upsampled| {
for sample in upsampled { for sample in upsampled {
*sample *= GAIN; *sample *= GAIN;
} }
}); });
let latency = oversampler.latency() as usize; let reported_latency = oversampler.latency(oversampling_factor) as usize;
for (input_sample_idx, input_sample) in for (input_sample_idx, input_sample) in input
input.into_iter().enumerate().take(input.len() - latency) .into_iter()
.enumerate()
.take(input.len() - reported_latency)
{ {
let output_sample_idx = input_sample_idx + latency; let output_sample_idx = input_sample_idx + reported_latency;
let output_sample = output[output_sample_idx]; let output_sample = output[output_sample_idx];
// There can be quite a big difference between the input and output thanks to the // There can be quite a big difference between the input and output thanks to the