1
0
Fork 0

Redesign mapped block smoothing

The non-mapped version is now split off and the mapped version is much
better suited for array based modulation. Check the breaking changes
document and the new docstring for more information.
This commit is contained in:
Robbert van der Helm 2022-09-04 20:17:06 +02:00
parent 99437c6011
commit a21daef96b
2 changed files with 84 additions and 18 deletions

View file

@ -6,6 +6,16 @@ new and what's changed, this document lists all breaking changes in reverse
chronological order. If a new feature did not require any changes to existing
code then it will not be listed here.
## [2022-09-04]
- `Smoother::next_block_mapped()` and `Smoother::next_block_exact_mapped()` have
been redesigned. They now take an index of the element being generated and the
float representation of the smoothed value. This makes it easier to use them
for modulation, and it makes it possible to smoothly modulate integers and
other stepped parameters. Additionally, the mapping functions are now also
called for every produced value, even if the smoother has already finished
smoothing and is always producing the same value.
## [2022-08-19]
- `Plugin::DEFAULT_NUM_INPUTS` and `Plugin::DEFAULT_NUM_OUTPUTS` have been

View file

@ -324,24 +324,11 @@ impl<T: Smoothable> Smoother<T> {
///
/// Panics if `block_len > block_values.len()`.
pub fn next_block(&self, block_values: &mut [T], block_len: usize) {
self.next_block_exact_mapped(&mut block_values[..block_len], |x| x)
self.next_block_exact(&mut block_values[..block_len])
}
/// The same as [`next_block()`][Self::next_block()], but filling the entire slice.
pub fn next_block_exact(&self, block_values: &mut [T]) {
self.next_block_exact_mapped(block_values, |x| x)
}
/// The same as [`next_block()`][Self::next_block()], but with a function applied to each
/// produced value. Useful when applying modulation to a smoothed parameter. The mapping
/// function is only applied once for the final target value.
pub fn next_block_mapped(&self, block_values: &mut [T], block_len: usize, f: impl Fn(T) -> T) {
self.next_block_exact_mapped(&mut block_values[..block_len], f)
}
/// The same as [`next_block_exact()`][Self::next_block()], but with a function applied to each
/// produced value. Useful when applying modulation to a smoothed parameter.
pub fn next_block_exact_mapped(&self, block_values: &mut [T], f: impl Fn(T) -> T) {
// `self.next()` will yield the current value if the parameter is no longer smoothing, but
// it's a bit of a waste to continuesly call that if only the first couple or none of the
// values in `block_values` would require smoothing and the rest don't. Instead, we'll just
@ -354,9 +341,10 @@ impl<T: Smoothable> Smoother<T> {
// conditionals optimized out
let mut current = self.current.load(Ordering::Relaxed);
let target = self.target.to_f32();
block_values[..num_smoothed_values].fill_with(|| {
current = self.style.next(current, target, self.step_size);
f(T::from_f32(current))
T::from_f32(current)
});
// In `next()` the last step snaps the value to the target value. Since we're computing
@ -364,16 +352,84 @@ impl<T: Smoothable> Smoother<T> {
// instead to avoid the conditional.
if num_smoothed_values == steps_left {
current = target.to_f32();
block_values[num_smoothed_values - 1..].fill(f(self.target));
block_values[num_smoothed_values - 1..].fill(self.target);
} else {
block_values[num_smoothed_values..].fill(f(self.target));
block_values[num_smoothed_values..].fill(self.target);
}
self.current.store(current, Ordering::Relaxed);
self.steps_left
.fetch_sub(num_smoothed_values as i32, Ordering::Relaxed);
} else {
block_values.fill(f(self.target));
block_values.fill(self.target);
}
}
/// The same as [`next_block()`][Self::next_block()], but with a function applied to each
/// produced value. The mapping function takes an index in the block and a floating point
/// representation of the smoother's current value. This allows the modulation to be consistent
/// during smoothing. Additionally, the mapping function is always called even if the smoothing
/// is finished.
pub fn next_block_mapped(
&self,
block_values: &mut [T],
block_len: usize,
f: impl FnMut(usize, f32) -> T,
) {
self.next_block_exact_mapped(&mut block_values[..block_len], f)
}
/// The same as [`next_block_exact()`][Self::next_block()], but with a function applied to each
/// produced value. Useful when applying modulation to a smoothed parameter.
pub fn next_block_exact_mapped(
&self,
block_values: &mut [T],
mut f: impl FnMut(usize, f32) -> T,
) {
// This works exactly the same as `next_block_exact()`, except for the addition of the
// mapping function
let target = self.target.to_f32();
let steps_left = self.steps_left.load(Ordering::Relaxed) as usize;
let num_smoothed_values = block_values.len().min(steps_left);
if num_smoothed_values > 0 {
let mut current = self.current.load(Ordering::Relaxed);
for (idx, value) in block_values
.iter_mut()
.enumerate()
.take(num_smoothed_values)
{
current = self.style.next(current, target, self.step_size);
*value = f(idx, current);
}
if num_smoothed_values == steps_left {
current = target.to_f32();
for (idx, value) in block_values
.iter_mut()
.enumerate()
.skip(num_smoothed_values - 1)
{
*value = f(idx, target);
}
} else {
for (idx, value) in block_values
.iter_mut()
.enumerate()
.skip(num_smoothed_values)
{
*value = f(idx, target);
}
}
self.current.store(current, Ordering::Relaxed);
self.steps_left
.fetch_sub(num_smoothed_values as i32, Ordering::Relaxed);
} else {
for (idx, value) in block_values.iter_mut().enumerate() {
*value = f(idx, target);
}
}
}
}