-
Notifications
You must be signed in to change notification settings - Fork 49
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
20 changed files
with
967 additions
and
72 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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>, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 }, | ||
) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
} | ||
} |
Oops, something went wrong.