Skip to content

Commit

Permalink
wip other parts
Browse files Browse the repository at this point in the history
  • Loading branch information
russellmcc committed Nov 27, 2024
1 parent e3304c5 commit 81d8565
Show file tree
Hide file tree
Showing 16 changed files with 375 additions and 10 deletions.
42 changes: 42 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions rust/reverb/component/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ rust-version = "1.79.0"
publish = false

[dependencies]
bitvec = "1.0.1"
conformal_component = "0.3.2"
rand = "0.8.5"
rand_xoshiro = "0.6.0"

[dev-dependencies]
assert_approx_eq = "1.1.0"
Expand Down
57 changes: 57 additions & 0 deletions rust/reverb/component/src/diffuser/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
use rand::Rng;

pub use crate::shuffler::CHANNELS;
use crate::{multi_channel_per_sample_delay::MultiChannelPerSampleDelay, shuffler::Shuffler};

#[derive(Debug, Clone)]
struct DiffuserBlock {
shuffler: Shuffler,
delay: MultiChannelPerSampleDelay<CHANNELS>,
}

impl DiffuserBlock {
fn new(rng: &mut impl Rng, max_delay: usize) -> Self {
// We ensure that each max_delay / CHANNELS section gets at least one channel of delay.
let mut delays = [0; CHANNELS];
for (i, delay) in delays.iter_mut().enumerate() {
let range_start = i * (max_delay / CHANNELS);
let range_end = range_start + (max_delay / CHANNELS);
*delay = rng.gen_range(range_start..range_end);
}

Self {
shuffler: Shuffler::new(rng),
delay: MultiChannelPerSampleDelay::new(delays),
}
}

fn process(&mut self, input: &[f32; CHANNELS]) -> [f32; CHANNELS] {
let ret = self.shuffler.shuffle(&self.delay.read());
self.delay.write(input);
ret
}
}

pub const BLOCKS: usize = 4;

pub struct Diffuser {
blocks: [DiffuserBlock; BLOCKS],
}
impl Diffuser {
pub fn new(rng: &mut impl Rng, max_delays: [usize; BLOCKS]) -> Self {
Self {
blocks: core::array::from_fn(|i| DiffuserBlock::new(rng, max_delays[i])),
}
}

pub fn process(&mut self, input: &[f32; CHANNELS]) -> [f32; CHANNELS] {
let mut output = *input;
for block in &mut self.blocks {
output = block.process(&output);
}
output
}
}

#[cfg(test)]
mod tests;
Git LFS file not shown
23 changes: 23 additions & 0 deletions rust/reverb/component/src/diffuser/tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#![allow(clippy::cast_possible_truncation)]

use super::*;
use rand::SeedableRng;
use rand_xoshiro::Xoshiro256PlusPlus;
use snapshots::assert_snapshot;

#[test]
fn impulse_response() {
const SNAPSHOT_LENGTH: usize = 48_000 * 2;
const SAMPLE_RATE: f32 = 48000.0;
const DELAYS_MS: [f32; BLOCKS] = [20.0, 40.0, 80.0, 160.0];
let mut diffuser = Diffuser::new(
&mut Xoshiro256PlusPlus::seed_from_u64(369),
DELAYS_MS.map(|d| (d / 1000.0 * SAMPLE_RATE).round() as usize),
);
let mut output = vec![0.0; SNAPSHOT_LENGTH];
output[0] = diffuser.process(&[1.0; CHANNELS])[0];
for output in output.iter_mut().skip(1) {
*output = diffuser.process(&[0.0; CHANNELS])[0];
}
assert_snapshot!("impulse_response", 48000, output);
}
3 changes: 3 additions & 0 deletions rust/reverb/component/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,12 @@ use conformal_component::parameters::{self, BufferStates, Flags, InfoRef, TypeSp
use conformal_component::pzip;
use conformal_component::{Component as ComponentTrait, ProcessingEnvironment, Processor};

mod diffuser;
mod multi_channel_feedback_loop;
mod multi_channel_per_sample_delay;
mod per_sample_delay;
mod reverb;
mod shuffler;

const PARAMETERS: [InfoRef<'static, &'static str>; 2] = [
InfoRef {
Expand Down
Git LFS file not shown
10 changes: 2 additions & 8 deletions rust/reverb/component/src/multi_channel_feedback_loop/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,9 @@ fn impulse_response() {
DELAYS_MS.map(|d| (d / 1000.0 * SAMPLE_RATE).round() as usize),
);
let mut output = vec![0.0; SNAPSHOT_LENGTH];
output[0] = feedback_loop
.process([1.0; CHANNELS], FEEDBACK)
.into_iter()
.sum::<f32>();
output[0] = feedback_loop.process([1.0; CHANNELS], FEEDBACK)[0];
for output in output.iter_mut().skip(1) {
*output = feedback_loop
.process([0.0; CHANNELS], FEEDBACK)
.into_iter()
.sum::<f32>();
*output = feedback_loop.process([0.0; CHANNELS], FEEDBACK)[0];
}
assert_snapshot!("impulse_response", 48000, output);
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ use crate::per_sample_delay::PerSampleDelay;
///
/// This is useful in feedback loops with channel mixing, since we have to operate
/// sample by sample.
#[derive(Debug, Clone)]
pub struct MultiChannelPerSampleDelay<const CHANNELS: usize> {
delays: [PerSampleDelay; CHANNELS],
}
Expand Down
7 changes: 6 additions & 1 deletion rust/reverb/component/src/per_sample_delay/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/// A delay line optimized to read and write one sample at a time.
#[derive(Debug)]
#[derive(Debug, Clone)]
pub struct PerSampleDelay {
buffer: Vec<f32>,

Expand All @@ -22,6 +22,11 @@ impl PerSampleDelay {
self.buffer[self.head] = input;
self.head = (self.head + 1) % self.buffer.len();
}

pub fn reset(&mut self) {
self.buffer.fill(0.0);
self.head = 0;
}
}

#[cfg(test)]
Expand Down
16 changes: 16 additions & 0 deletions rust/reverb/component/src/per_sample_delay/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,19 @@ fn delay_longer_than_buffer() {
1e-6
));
}

#[test]
fn reset() {
let mut delay = PerSampleDelay::new(3);
assert!(all_approx_eq(
process([1.0, 2.0, 3.0, 4.0, 5.0], &mut delay),
[0., 0., 0., 1., 2.],
1e-6
));
delay.reset();
assert!(all_approx_eq(
process([6.0, 7.0, 8.0, 9.0, 10.0], &mut delay),
[0., 0., 0., 6., 7.],
1e-6
));
}
86 changes: 86 additions & 0 deletions rust/reverb/component/src/reverb/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
use conformal_component::audio::{Buffer, BufferMut};
use conformal_component::ProcessingEnvironment;
use rand::{Rng, SeedableRng};
use rand_xoshiro::Xoshiro256PlusPlus;

pub use crate::diffuser::CHANNELS;
use crate::diffuser::{Diffuser, BLOCKS};
use crate::multi_channel_feedback_loop::MultiChannelFeedbackLoop;

pub struct Reverb {
diffuser: Diffuser,
feedback_loop: MultiChannelFeedbackLoop<CHANNELS>,
}

impl Reverb {
#[allow(clippy::cast_possible_truncation)]
pub fn new(env: &ProcessingEnvironment) -> Self {
const DIFFUSER_DELAYS_MS: [f32; BLOCKS] = [20.0, 40.0, 80.0, 160.0];
const FEEDBACK_LOOP_MIN_DELAY_MS: f32 = 100.0;
const FEEDBACK_LOOP_MAX_DELAY_MS: f32 = 200.0;

let min_feedback_delay_samples =
(FEEDBACK_LOOP_MIN_DELAY_MS / 1000.0 * env.sampling_rate).round() as usize;
let feedback_delay_range_samples = ((FEEDBACK_LOOP_MAX_DELAY_MS
- FEEDBACK_LOOP_MIN_DELAY_MS)
/ 1000.0
* env.sampling_rate)
.round() as usize;

let mut rng = Xoshiro256PlusPlus::seed_from_u64(369);

let fdn_delays = core::array::from_fn(|i| {
let min_for_block =
min_feedback_delay_samples + i * feedback_delay_range_samples / CHANNELS;
let max_for_block = min_for_block + feedback_delay_range_samples / CHANNELS;
rng.gen_range(min_for_block..max_for_block)
});

Self {
diffuser: Diffuser::new(
&mut rng,
DIFFUSER_DELAYS_MS.map(|d| (d / 1000.0 * env.sampling_rate).round() as usize),
),
feedback_loop: MultiChannelFeedbackLoop::new(fdn_delays),
}
}

pub fn process(&mut self, feedback: f32, input: &impl Buffer, output: &mut impl BufferMut) {
if input.num_channels() == 1 {
let input = input.channel(0);
let output = output.channel_mut(0);
for (input, output) in input.iter().zip(output.iter_mut()) {
let mc_input = core::array::from_fn(|i| if i == 0 { *input } else { 0.0 });
*output = self
.feedback_loop
.process(self.diffuser.process(&mc_input), feedback)[0];
}
} else if input.num_channels() == 2 {
let input_l = input.channel(0);
let input_r = input.channel(1);
for (i, (input_l, input_r)) in input_l.iter().zip(input_r.iter()).enumerate() {
{
let mc_input = core::array::from_fn(|i| {
if i == 0 {
*input_l
} else if i == 1 {
*input_r
} else {
0.0
}
});
let x = self
.feedback_loop
.process(self.diffuser.process(&mc_input), feedback);
output.channel_mut(0)[i] = x[0];
output.channel_mut(1)[i] = x[1];
}
}
} else {
panic!("Reverb only supports mono and stereo input");
}
}
}

#[cfg(test)]
mod tests;
Git LFS file not shown
23 changes: 23 additions & 0 deletions rust/reverb/component/src/reverb/tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
use super::*;
use conformal_component::{
audio::{BufferData, ChannelLayout},
ProcessingMode,
};
use snapshots::assert_snapshot;

#[test]
fn impulse_response() {
const SNAPSHOT_LENGTH: usize = 48_000 * 2;
const SAMPLE_RATE: f32 = 48000.0;
let mut reverb = Reverb::new(&ProcessingEnvironment {
sampling_rate: SAMPLE_RATE,
max_samples_per_process_call: SNAPSHOT_LENGTH,
channel_layout: ChannelLayout::Mono,
processing_mode: ProcessingMode::Realtime,
});
let mut impulse_vec = vec![0.0; SNAPSHOT_LENGTH];
impulse_vec[0] = 1.0;
let mut output = BufferData::new_mono(vec![0.0; SNAPSHOT_LENGTH]);
reverb.process(0.85, &BufferData::new_mono(impulse_vec), &mut output);
assert_snapshot!("impulse_response", 48000, output.channel(0).iter().copied());
}
Loading

0 comments on commit 81d8565

Please sign in to comment.