Skip to content

Commit

Permalink
send tracks
Browse files Browse the repository at this point in the history
  • Loading branch information
tesselode committed Dec 15, 2024
1 parent 5839c0b commit 8ff94f1
Show file tree
Hide file tree
Showing 20 changed files with 967 additions and 72 deletions.
2 changes: 1 addition & 1 deletion crates/kira/src/effect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ and [`Effect`] traits.
// pub mod eq_filter;
// pub mod filter;
pub mod panning_control;
// pub mod reverb;
pub mod reverb;
pub mod volume_control;

use crate::{frame::Frame, info::Info};
Expand Down
190 changes: 190 additions & 0 deletions crates/kira/src/effect/reverb.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
//! Adds reverberations to a sound. Useful for simulating room tones.
mod builder;
mod handle;

pub use builder::*;
pub use handle::*;

use crate::{
command::{read_commands_into_parameters, ValueChangeCommand},
command_writers_and_readers,
effect::Effect,
frame::Frame,
info::Info,
tween::Parameter,
Mix,
};
use all_pass::AllPassFilter;
use comb::CombFilter;

mod all_pass;
mod comb;

const NUM_COMB_FILTERS: usize = 8;
const NUM_ALL_PASS_FILTERS: usize = 4;
const GAIN: f32 = 0.015;
const STEREO_SPREAD: usize = 23;

#[derive(Debug)]
enum ReverbState {
Uninitialized,
Initialized {
comb_filters: [(CombFilter, CombFilter); NUM_COMB_FILTERS],
all_pass_filters: [(AllPassFilter, AllPassFilter); NUM_ALL_PASS_FILTERS],
},
}

// This code is based on Freeverb by Jezar at Dreampoint, found here:
// http://blog.bjornroche.com/2012/06/freeverb-original-public-domain-code-by.html
struct Reverb {
command_readers: CommandReaders,
feedback: Parameter,
damping: Parameter,
stereo_width: Parameter,
mix: Parameter<Mix>,
state: ReverbState,
}

impl Reverb {
/// Creates a new `Reverb` effect.
#[must_use]
fn new(settings: ReverbBuilder, command_readers: CommandReaders) -> Self {
Self {
command_readers,
feedback: Parameter::new(settings.feedback, 0.9),
damping: Parameter::new(settings.damping, 0.1),
stereo_width: Parameter::new(settings.stereo_width, 1.0),
mix: Parameter::new(settings.mix, Mix(0.5)),
state: ReverbState::Uninitialized,
}
}

fn init_filters(&mut self, sample_rate: u32) {
const REFERENCE_SAMPLE_RATE: u32 = 44100;

let adjust_buffer_size = |buffer_size: usize| -> usize {
let sample_rate_factor = (sample_rate as f64) / (REFERENCE_SAMPLE_RATE as f64);
((buffer_size as f64) * sample_rate_factor) as usize
};

self.state = ReverbState::Initialized {
comb_filters: [
(
CombFilter::new(adjust_buffer_size(1116)),
CombFilter::new(adjust_buffer_size(1116 + STEREO_SPREAD)),
),
(
CombFilter::new(adjust_buffer_size(1188)),
CombFilter::new(adjust_buffer_size(1188 + STEREO_SPREAD)),
),
(
CombFilter::new(adjust_buffer_size(1277)),
CombFilter::new(adjust_buffer_size(1277 + STEREO_SPREAD)),
),
(
CombFilter::new(adjust_buffer_size(1356)),
CombFilter::new(adjust_buffer_size(1356 + STEREO_SPREAD)),
),
(
CombFilter::new(adjust_buffer_size(1422)),
CombFilter::new(adjust_buffer_size(1422 + STEREO_SPREAD)),
),
(
CombFilter::new(adjust_buffer_size(1491)),
CombFilter::new(adjust_buffer_size(1491 + STEREO_SPREAD)),
),
(
CombFilter::new(adjust_buffer_size(1557)),
CombFilter::new(adjust_buffer_size(1557 + STEREO_SPREAD)),
),
(
CombFilter::new(adjust_buffer_size(1617)),
CombFilter::new(adjust_buffer_size(1617 + STEREO_SPREAD)),
),
],
all_pass_filters: [
(
AllPassFilter::new(adjust_buffer_size(556)),
AllPassFilter::new(adjust_buffer_size(556 + STEREO_SPREAD)),
),
(
AllPassFilter::new(adjust_buffer_size(441)),
AllPassFilter::new(adjust_buffer_size(441 + STEREO_SPREAD)),
),
(
AllPassFilter::new(adjust_buffer_size(341)),
AllPassFilter::new(adjust_buffer_size(341 + STEREO_SPREAD)),
),
(
AllPassFilter::new(adjust_buffer_size(225)),
AllPassFilter::new(adjust_buffer_size(225 + STEREO_SPREAD)),
),
],
}
}
}

impl Effect for Reverb {
fn init(&mut self, sample_rate: u32) {
self.init_filters(sample_rate);
}

fn on_change_sample_rate(&mut self, sample_rate: u32) {
self.init_filters(sample_rate);
}

fn on_start_processing(&mut self) {
read_commands_into_parameters!(self, feedback, damping, stereo_width, mix);
}

fn process(&mut self, input: &mut [Frame], dt: f64, info: &Info) {
for (i, frame) in input.iter_mut().enumerate() {
let info = info.for_single_frame(i);
if let ReverbState::Initialized {
comb_filters,
all_pass_filters,
} = &mut self.state
{
self.feedback.update(dt, &info);
self.damping.update(dt, &info);
self.stereo_width.update(dt, &info);
self.mix.update(dt, &info);

let feedback = self.feedback.value() as f32;
let damping = self.damping.value() as f32;
let stereo_width = self.stereo_width.value() as f32;

let mut output = Frame::ZERO;
let mono_input = (frame.left + frame.right) * GAIN;
// accumulate comb filters in parallel
for comb_filter in comb_filters {
output.left += comb_filter.0.process(mono_input, feedback, damping);
output.right += comb_filter.1.process(mono_input, feedback, damping);
}
// feed through all-pass filters in series
for all_pass_filter in all_pass_filters {
output.left = all_pass_filter.0.process(output.left);
output.right = all_pass_filter.1.process(output.right);
}
let wet_1 = stereo_width / 2.0 + 0.5;
let wet_2 = (1.0 - stereo_width) / 2.0;
let output = Frame::new(
output.left * wet_1 + output.right * wet_2,
output.right * wet_1 + output.left * wet_2,
);
let mix = self.mix.value().0;
*frame = output * mix.sqrt() + *frame * (1.0 - mix).sqrt()
} else {
panic!("Reverb should be initialized before the first process call")
}
}
}
}

command_writers_and_readers! {
set_feedback: ValueChangeCommand<f64>,
set_damping: ValueChangeCommand<f64>,
set_stereo_width: ValueChangeCommand<f64>,
set_mix: ValueChangeCommand<Mix>,
}
27 changes: 27 additions & 0 deletions crates/kira/src/effect/reverb/all_pass.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
const FEEDBACK: f32 = 0.5;

#[derive(Debug)]
pub struct AllPassFilter {
buffer: Vec<f32>,
current_index: usize,
}

impl AllPassFilter {
#[must_use]
pub fn new(buffer_size: usize) -> Self {
Self {
buffer: vec![0.0; buffer_size],
current_index: 0,
}
}

#[must_use]
pub fn process(&mut self, input: f32) -> f32 {
let buffer_output = self.buffer[self.current_index];
let output = -input + buffer_output;
self.buffer[self.current_index] = input + buffer_output * FEEDBACK;
self.current_index += 1;
self.current_index %= self.buffer.len();
output
}
}
98 changes: 98 additions & 0 deletions crates/kira/src/effect/reverb/builder.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
use crate::{
effect::{Effect, EffectBuilder},
Mix, Value,
};

use super::{command_writers_and_readers, Reverb, ReverbHandle};

/// Configures a reverb effect.
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct ReverbBuilder {
/// How much the room reverberates. A higher value will
/// result in a bigger sounding room. 1.0 gives an infinitely
/// reverberating room.
pub feedback: Value<f64>,
/// How quickly high frequencies disappear from the reverberation.
pub damping: Value<f64>,
/// The stereo width of the reverb effect (0.0 being fully mono,
/// 1.0 being fully stereo).
pub stereo_width: Value<f64>,
/// How much dry (unprocessed) signal should be blended
/// with the wet (processed) signal. `0.0` means
/// only the dry signal will be heard. `1.0` means
/// only the wet signal will be heard.
pub mix: Value<Mix>,
}

impl ReverbBuilder {
/// Creates a new [`ReverbBuilder`] with the default settings.
#[must_use]
pub fn new() -> Self {
Self::default()
}

/// Sets how much the room reverberates. A higher value will
/// result in a bigger sounding room. 1.0 gives an infinitely
/// reverberating room.
#[must_use = "This method consumes self and returns a modified ReverbBuilder, so the return value should be used"]
pub fn feedback(self, feedback: impl Into<Value<f64>>) -> Self {
Self {
feedback: feedback.into(),
..self
}
}

/// Sets how quickly high frequencies disappear from the reverberation.
#[must_use = "This method consumes self and returns a modified ReverbBuilder, so the return value should be used"]
pub fn damping(self, damping: impl Into<Value<f64>>) -> Self {
Self {
damping: damping.into(),
..self
}
}

/// Sets the stereo width of the reverb effect (0.0 being fully mono,
/// 1.0 being fully stereo).
#[must_use = "This method consumes self and returns a modified ReverbBuilder, so the return value should be used"]
pub fn stereo_width(self, stereo_width: impl Into<Value<f64>>) -> Self {
Self {
stereo_width: stereo_width.into(),
..self
}
}

/// Sets how much dry (unprocessed) signal should be blended
/// with the wet (processed) signal. `0.0` means only the dry
/// signal will be heard. `1.0` means only the wet signal will
/// be heard.
#[must_use = "This method consumes self and returns a modified ReverbBuilder, so the return value should be used"]
pub fn mix(self, mix: impl Into<Value<Mix>>) -> Self {
Self {
mix: mix.into(),
..self
}
}
}

impl Default for ReverbBuilder {
fn default() -> Self {
Self {
feedback: Value::Fixed(0.9),
damping: Value::Fixed(0.1),
stereo_width: Value::Fixed(1.0),
mix: Value::Fixed(Mix(0.5)),
}
}
}

impl EffectBuilder for ReverbBuilder {
type Handle = ReverbHandle;

fn build(self) -> (Box<dyn Effect>, Self::Handle) {
let (command_writers, command_readers) = command_writers_and_readers();
(
Box::new(Reverb::new(self, command_readers)),
ReverbHandle { command_writers },
)
}
}
27 changes: 27 additions & 0 deletions crates/kira/src/effect/reverb/comb.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#[derive(Debug)]
pub struct CombFilter {
filter_store: f32,
buffer: Vec<f32>,
current_index: usize,
}

impl CombFilter {
#[must_use]
pub fn new(buffer_size: usize) -> Self {
Self {
filter_store: 0.0,
buffer: vec![0.0; buffer_size],
current_index: 0,
}
}

#[must_use]
pub fn process(&mut self, input: f32, feedback: f32, damp: f32) -> f32 {
let output = self.buffer[self.current_index];
self.filter_store = output * (1.0 - damp) + self.filter_store * damp;
self.buffer[self.current_index] = input + self.filter_store * feedback;
self.current_index += 1;
self.current_index %= self.buffer.len();
output
}
}
31 changes: 31 additions & 0 deletions crates/kira/src/effect/reverb/handle.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
use crate::{command::handle_param_setters, Mix};

use super::CommandWriters;

/// Controls a reverb effect.
#[derive(Debug)]
pub struct ReverbHandle {
pub(super) command_writers: CommandWriters,
}

impl ReverbHandle {
handle_param_setters! {
/// Sets how much the room reverberates. A higher value will
/// result in a bigger sounding room. 1.0 gives an infinitely
/// reverberating room.
feedback: f64,

/// Sets how quickly high frequencies disappear from the reverberation.
damping: f64,

/// Sets the stereo width of the reverb effect (0.0 being fully mono,
/// 1.0 being fully stereo).
stereo_width: f64,

/// Sets how much dry (unprocessed) signal should be blended
/// with the wet (processed) signal. `0.0` means only the dry
/// signal will be heard. `1.0` means only the wet signal will
/// be heard.
mix: Mix,
}
}
Loading

0 comments on commit 8ff94f1

Please sign in to comment.