Skip to content

Commit

Permalink
Make slices automatable
Browse files Browse the repository at this point in the history
  • Loading branch information
Woyten committed Jul 19, 2024
1 parent 514c815 commit ef9c387
Show file tree
Hide file tree
Showing 2 changed files with 174 additions and 104 deletions.
62 changes: 62 additions & 0 deletions magnetron/src/automation.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
//! Core concepts for using time-dependent data and external parameters in audio processing pipelines.

use std::marker::PhantomData;

use crate::{
buffer::BufferWriter,
stage::{Stage, StageActivity},
Expand Down Expand Up @@ -261,6 +263,66 @@ where
}
}

pub struct AutomatableSlice<'a, T, O> {
automatables: &'a [T],
marker: PhantomData<O>,
}

impl<'a, T, O> AutomatableSlice<'a, T, O> {
pub fn new<C: CreationInfo, Q: QueryInfo>(automatables: &'a [T]) -> Self
where
T: Automatable<C>,
for<'b> T::Output: Automated<Q, Output<'b> = O>,
{
AutomatableSlice {
automatables,
marker: PhantomData,
}
}
}

impl<C: CreationInfo, T, O> Automatable<C> for AutomatableSlice<'_, T, O>
where
T: Automatable<C>,
{
type Output = AutomatedSlice<T::Output, O>;

fn create(&self, factory: &mut AutomationFactory<C>) -> Self::Output {
AutomatedSlice {
automateds: self
.automatables
.iter()
.map(|spec| spec.create(factory))
.collect(),
outputs: Vec::with_capacity(self.automatables.len()),
}
}
}

pub struct AutomatedSlice<T, O> {
automateds: Vec<T>,
outputs: Vec<O>,
}

impl<Q: QueryInfo, T, O> Automated<Q> for AutomatedSlice<T, O>
where
for<'a> T: Automated<Q, Output<'a> = O> + 'a,
{
type Output<'a> = &'a [O] where Self: 'a;

fn query(&mut self, render_window_secs: f64, context: Q::Context<'_>) -> Self::Output<'_> {
self.outputs.clear();

self.outputs.extend(
self.automateds
.iter_mut()
.map(|automated| automated.query(render_window_secs, context)),
);

&self.outputs
}
}

/// A concrete implementation of [`Automated`] yielding a time-dependent and context-dependent `f64`.
///
/// This type is used to retrieve the actual numerical values required in the implementation bodies of [`Stage`]s or other (nested) [`AutomatedValue`]s.
Expand Down
216 changes: 112 additions & 104 deletions microwave/src/magnetron/effects.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::f64::consts::TAU;
use std::{f64::consts::TAU, iter};

use magnetron::{
automation::{AutomatableParam, Automated, AutomationFactory},
automation::{AutomatableParam, AutomatableSlice, Automated, AutomationFactory},
buffer::BufferIndex,
stage::Stage,
};
Expand Down Expand Up @@ -138,118 +138,126 @@ impl<A: AutomatableParam> EffectSpec<A> {
} => {
let buffer_size = *buffer_size;

let mut allpasses: Vec<_> = allpasses
.iter()
.map(|delay_ms| {
(
AllPassDelay::new(buffer_size, 0.0),
AllPassDelay::new(buffer_size, 0.0),
factory.automate(delay_ms),
0.0,
)
})
.collect();
let mut combs: Vec<_> = combs
.iter()
.map(|(delay_ms_l, delay_ms_r)| {
(
CombFilter::new(
buffer_size,
OnePoleLowPass::new(0.0, 0.0).followed_by(0.0),
1.0,
),
CombFilter::new(
buffer_size,
OnePoleLowPass::new(0.0, 0.0).followed_by(0.0),
1.0,
),
factory.automate(delay_ms_l),
factory.automate(delay_ms_r),
0.0,
0.0,
)
})
.collect();

let mut gain = factory.automate(gain);
let (mut allpass_feedback, mut comb_feedback, mut cutoff_hz) =
factory.automate((allpass_feedback, comb_feedback, cutoff));
let mut out_levels = factory.automate(out_levels);

Stage::new(move |buffers, context| {
if buffers.reset() {
for allpass in &mut allpasses {
allpass.0.mute();
allpass.1.mute();
}
for comb in &mut combs {
comb.0.mute();
comb.1.mute();
}
}

let gain = gain.query(buffers.render_window_secs(), context);
let (allpass_feedback, comb_feedback, cutoff_hz) =
(&mut allpass_feedback, &mut comb_feedback, &mut cutoff_hz)
.query(buffers.render_window_secs(), context);
let out_levels = out_levels.query(buffers.render_window_secs(), context);

let sample_rate_hz = buffers.sample_width_secs().recip();
let delay_line_ms = buffers.sample_width_secs() * buffer_size as f64 * 1000.0;

for (allpass_l, allpass_r, delay_ms, delay) in &mut allpasses {
allpass_l.set_feedback(allpass_feedback);
allpass_r.set_feedback(allpass_feedback);

*delay =
delay_ms.query(buffers.render_window_secs(), context) / delay_line_ms;
}

for (comb_l, comb_r, delay_ms_l, delay_ms_r, delay_l, delay_r) in &mut combs {
let response_fn_l = &mut comb_l.response_fn();
response_fn_l.first().set_cutoff(cutoff_hz, sample_rate_hz);
*response_fn_l.second() = comb_feedback;

let response_fn_r = &mut comb_r.response_fn();
response_fn_r.first().set_cutoff(cutoff_hz, sample_rate_hz);
*response_fn_r.second() = comb_feedback;

*delay_l =
delay_ms_l.query(buffers.render_window_secs(), context) / delay_line_ms;
*delay_r =
delay_ms_r.query(buffers.render_window_secs(), context) / delay_line_ms;
}

buffers.read_2_write_2(
in_buffers,
out_buffers,
out_levels,
|signal_l, signal_r| {
let mut diffused_l = gain * signal_l;
let mut diffused_r = gain * signal_r;
let mut allpass_delays: Vec<_> = iter::repeat_with(|| {
(
AllPassDelay::new(buffer_size, 0.0),
AllPassDelay::new(buffer_size, 0.0),
)
})
.take(allpasses.len())
.collect();

let mut comb_filters: Vec<_> = iter::repeat_with(|| {
(
CombFilter::new(
buffer_size,
OnePoleLowPass::new(0.0, 0.0).followed_by(0.0),
1.0,
),
CombFilter::new(
buffer_size,
OnePoleLowPass::new(0.0, 0.0).followed_by(0.0),
1.0,
),
)
})
.take(combs.len())
.collect();

for (allpass_l, allpass_r, .., delay) in &mut allpasses {
diffused_l = allpass_l.process_sample_fract(*delay, diffused_l);
diffused_r = allpass_r.process_sample_fract(*delay, diffused_r);
factory
.automate((
gain,
(AutomatableSlice::new(allpasses), allpass_feedback),
(AutomatableSlice::new(combs), comb_feedback),
cutoff,
out_levels,
))
.into_stage(
move |buffers,
(
gain,
(allpasses, allpass_feedback),
(combs, comb_feedback),
cutoff_hz,
out_levels,
)| {
if buffers.reset() {
for allpass in &mut allpass_delays {
allpass.0.mute();
allpass.1.mute();
}
for comb in &mut comb_filters {
comb.0.mute();
comb.1.mute();
}
}

let mut reverbed_l = 0.0;
let mut reverbed_r = 0.0;
let sample_rate_hz = buffers.sample_width_secs().recip();
let delay_line_ms =
buffers.sample_width_secs() * buffer_size as f64 * 1000.0;

for (comb_l, comb_r, .., delay_l, delay_r) in &mut combs {
reverbed_l += comb_l.process_sample_fract(*delay_l, diffused_l);
reverbed_r += comb_r.process_sample_fract(*delay_r, diffused_r);
for (allpass_l, allpass_r) in &mut allpass_delays {
allpass_l.set_feedback(allpass_feedback);
allpass_r.set_feedback(allpass_feedback);
}

let normalization = combs.len() as f64;
for (comb_l, comb_r) in &mut comb_filters {
let response_fn_l = &mut comb_l.response_fn();
response_fn_l.first().set_cutoff(cutoff_hz, sample_rate_hz);
*response_fn_l.second() = comb_feedback;

let response_fn_r = &mut comb_r.response_fn();
response_fn_r.first().set_cutoff(cutoff_hz, sample_rate_hz);
*response_fn_r.second() = comb_feedback;
}

(
signal_l + reverbed_l / normalization,
signal_r + reverbed_r / normalization,
buffers.read_2_write_2(
in_buffers,
out_buffers,
out_levels,
|signal_l, signal_r| {
let mut diffused_l = gain * signal_l;
let mut diffused_r = gain * signal_r;

for ((allpass_l, allpass_r), delay) in
allpass_delays.iter_mut().zip(allpasses)
{
diffused_l = allpass_l.process_sample_fract(
*delay / delay_line_ms,
diffused_l,
);
diffused_r = allpass_r.process_sample_fract(
*delay / delay_line_ms,
diffused_r,
);
}

let mut reverbed_l = 0.0;
let mut reverbed_r = 0.0;

for ((comb_l, comb_r), (delay_l, delay_r)) in
comb_filters.iter_mut().zip(combs)
{
reverbed_l += comb_l.process_sample_fract(
*delay_l / delay_line_ms,
diffused_l,
);
reverbed_r += comb_r.process_sample_fract(
*delay_r / delay_line_ms,
diffused_r,
);
}

let normalization = combs.len() as f64;

(
signal_l + reverbed_l / normalization,
signal_r + reverbed_r / normalization,
)
},
)
},
)
})
}
EffectSpec::RotarySpeaker {
buffer_size,
Expand Down

0 comments on commit ef9c387

Please sign in to comment.