Repository: irh/freeverb-rs Branch: main Commit: f6148740d5ed Files: 78 Total size: 77.5 KB Directory structure: gitextract_ujjsr6s2/ ├── .git-blame-ignore-revs ├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── crates/ │ ├── audio_module/ │ │ ├── Cargo.toml │ │ └── src/ │ │ ├── command.rs │ │ ├── lib.rs │ │ ├── module.rs │ │ ├── parameter.rs │ │ ├── processor.rs │ │ ├── string_converter.rs │ │ └── value_converter.rs │ ├── clib/ │ │ ├── .gitignore │ │ ├── Cargo.toml │ │ ├── build.sh │ │ ├── cbindgen.toml │ │ ├── src/ │ │ │ └── lib.rs │ │ └── test.cpp │ ├── freeverb/ │ │ ├── Cargo.toml │ │ └── src/ │ │ ├── all_pass.rs │ │ ├── comb.rs │ │ ├── delay_line.rs │ │ ├── float.rs │ │ ├── freeverb.rs │ │ ├── lib.rs │ │ └── tuning.rs │ └── freeverb_module/ │ ├── Cargo.toml │ └── src/ │ └── lib.rs └── examples/ ├── app_gtk/ │ ├── Cargo.toml │ └── src/ │ ├── audio_thread.rs │ ├── gtk_parameter_slider.rs │ ├── gtk_parameter_toggle.rs │ └── main.rs ├── app_juce/ │ ├── .gitignore │ ├── JuceLibraryCode/ │ │ ├── AppConfig.h │ │ ├── JuceHeader.h │ │ ├── ReadMe.txt │ │ ├── include_juce_audio_basics.cpp │ │ ├── include_juce_audio_basics.mm │ │ ├── include_juce_audio_devices.cpp │ │ ├── include_juce_audio_devices.mm │ │ ├── include_juce_audio_formats.cpp │ │ ├── include_juce_audio_formats.mm │ │ ├── include_juce_audio_processors.cpp │ │ ├── include_juce_audio_processors.mm │ │ ├── include_juce_audio_utils.cpp │ │ ├── include_juce_audio_utils.mm │ │ ├── include_juce_core.cpp │ │ ├── include_juce_core.mm │ │ ├── include_juce_cryptography.cpp │ │ ├── include_juce_cryptography.mm │ │ ├── include_juce_data_structures.cpp │ │ ├── include_juce_data_structures.mm │ │ ├── include_juce_events.cpp │ │ ├── include_juce_events.mm │ │ ├── include_juce_graphics.cpp │ │ ├── include_juce_graphics.mm │ │ ├── include_juce_gui_basics.cpp │ │ ├── include_juce_gui_basics.mm │ │ ├── include_juce_gui_extra.cpp │ │ ├── include_juce_gui_extra.mm │ │ ├── include_juce_opengl.cpp │ │ ├── include_juce_opengl.mm │ │ ├── include_juce_video.cpp │ │ └── include_juce_video.mm │ ├── Source/ │ │ ├── Main.cpp │ │ ├── MainComponent.cpp │ │ └── MainComponent.h │ └── freeverb.jucer └── wasm/ ├── .gitignore ├── Cargo.toml ├── README.md ├── package.json ├── public/ │ ├── freeverb-processor.js │ ├── index.html │ └── index.js └── src/ └── lib.rs ================================================ FILE CONTENTS ================================================ ================================================ FILE: .git-blame-ignore-revs ================================================ # .git-blame-ignore-revs # Rename /src -> /crates 990a498e2d963d18ce8cfa675674fa8c4173295e ================================================ FILE: .gitignore ================================================ **/target **/*.rs.bk /.vscode ================================================ FILE: Cargo.toml ================================================ [workspace] resolver = "2" members = [ "crates/*", "examples/app_gtk", "examples/wasm", ] [workspace.package] edition = "2024" authors = ["irh "] license = "MIT" readme = "README.md" repository = "https://github.com/irh/freeverb-rs" ================================================ FILE: LICENSE ================================================ Copyright (c) 2018 Ian Hobson Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # freeverb-rs A Rust implementation of the Freeverb algorithm. ## About Freeverb Freeverb was originally written in C++ by "Jezar at Dreampoint", and was released into the public domain in June 2000. It is now widely used in various incarnations in multiple software packages. - [Analysis of the Freeverb algorithm](https://ccrma.stanford.edu/~jos/pasp/Freeverb.html) - [More information and a link to original C++ source](https://freeverb3-vst.sourceforge.io/sites.shtml) ## About freeverb-rs This implementation of Freeverb in Rust is an almost direct conversion of the original source, created as a demonstration project for a [talk I gave about Rust at the Audio Developer Conference 2018](https://www.youtube.com/watch?v=Yom9E-67bdI). The code has been updated since then, so if you want to follow along with the talk then take a look at the `adc-2018` branch. A difference from the original implementation is that delay line buffers are dynamically allocated so that lengths can be adjusted for sample rates other than 44.1kHz. ## Repo structure [`crates/freeverb/`](./crates/freeverb) This contains the core implementation of Freeverb, with a simple interface. [`crates/clib`](./crates/clib) A static library that provides C bindings to the Freeverb processor, used by app_juce. [`crates/audio_module`](./crates/audio_module) This contains a very minimal generic module+parameter library [`crates/freeverb_module`](./crates/freeverb_module) The `freeverb` processor wrapped up as an `AudioModule`, currently only used by `app_gtk`. [`examples/app_gtk`](./examples/app_gtk) A very basic audio+GUI application that runs the Freeverb processor. You will need `gtk4` installed on your system for this to work. [`examples/app_juce`](./examples/app_juce) A very basic JUCE application that runs the Freeverb processor via a statically linked library. [`examples/wasm`](./examples/wasm) A library that provides a Wasm interface to the Freeverb processor. Also in the folder is a small web application that runs the Wasm processor. ================================================ FILE: crates/audio_module/Cargo.toml ================================================ [package] name = "audio_module" publish = false version = "0.1.0" authors = { workspace = true } edition = { workspace = true } [dependencies] ================================================ FILE: crates/audio_module/src/command.rs ================================================ pub enum Command { SetParameter(usize, f32), } pub trait CommandHandler { fn handle_command(&mut self, command: Command); } ================================================ FILE: crates/audio_module/src/lib.rs ================================================ mod command; mod module; mod parameter; mod processor; mod string_converter; mod value_converter; pub use { command::{Command, CommandHandler}, module::{AudioModule, ParameterProvider}, parameter::*, processor::AudioProcessor, string_converter::*, value_converter::*, }; ================================================ FILE: crates/audio_module/src/module.rs ================================================ use crate::{AudioProcessor, Parameter}; pub trait AudioModule: ParameterProvider { type Processor: AudioProcessor; fn create_processor(sample_rate: usize) -> Self::Processor; } pub trait ParameterProvider { fn parameter_count() -> usize; fn parameter(id: usize) -> Box; } ================================================ FILE: crates/audio_module/src/parameter.rs ================================================ use crate::{ string_converter::{DefaultStringConverter, StringConverter, float_string_converter}, value_converter::{DefaultValueConverter, ValueConverter, linear_value_converter}, }; pub enum ValueType { Float, Bool, } pub trait Parameter { fn name(&self) -> String; fn default_user_value(&self) -> f32; fn value_type(&self) -> ValueType { ValueType::Float } fn make_value_converter(&self) -> Box { Box::new(DefaultValueConverter {}) } fn make_string_converter(&self) -> Box { Box::new(DefaultStringConverter {}) } } pub struct BoolParameter { pub name: String, pub default_user_value: bool, } impl BoolParameter { pub fn new(name: &str) -> Self { Self { name: name.to_string(), default_user_value: false, } } pub fn default_user_value(mut self, default: bool) -> Self { self.default_user_value = default; self } } impl Parameter for BoolParameter { fn name(&self) -> String { self.name.clone() } fn default_user_value(&self) -> f32 { if self.default_user_value { 1.0 } else { 0.0 } } fn value_type(&self) -> ValueType { ValueType::Bool } } pub struct FloatParameter { pub name: String, pub unit: String, pub min_user_value: f32, pub max_user_value: f32, pub default_user_value: f32, pub value_converter_maker: fn(&FloatParameter) -> Box, pub string_converter_maker: fn(&FloatParameter) -> Box, } impl FloatParameter { pub fn new(name: &str) -> Self { Self { name: name.to_string(), unit: String::default(), min_user_value: 0.0, max_user_value: 1.0, default_user_value: 0.0, value_converter_maker: linear_value_converter, string_converter_maker: float_string_converter, } } pub fn unit(mut self, unit: &str) -> Self { self.unit = unit.to_string(); self } pub fn range(mut self, min: f32, max: f32) -> Self { self.min_user_value = min; self.max_user_value = max; self } pub fn default_user_value(mut self, default: f32) -> Self { self.default_user_value = default; self } pub fn value_converter( mut self, converter: fn(&FloatParameter) -> Box, ) -> Self { self.value_converter_maker = converter; self } pub fn string_converter( mut self, converter: fn(&FloatParameter) -> Box, ) -> Self { self.string_converter_maker = converter; self } } impl Parameter for FloatParameter { fn name(&self) -> String { self.name.clone() } fn default_user_value(&self) -> f32 { self.default_user_value } fn make_value_converter(&self) -> Box { (self.value_converter_maker)(self) } fn make_string_converter(&self) -> Box { (self.string_converter_maker)(self) } } ================================================ FILE: crates/audio_module/src/processor.rs ================================================ use crate::CommandHandler; pub trait AudioProcessor: CommandHandler + Send + Sync + 'static { fn process(&mut self, input: &[f32], output: &mut [f32], channels: u32); } ================================================ FILE: crates/audio_module/src/string_converter.rs ================================================ use crate::FloatParameter; pub trait StringConverter { fn to_string(&self, value: f32) -> String; } #[derive(Clone)] pub struct DefaultStringConverter {} impl StringConverter for DefaultStringConverter { fn to_string(&self, value: f32) -> String { format!("{:.0}", value) } } #[derive(Clone)] pub struct BoolStringConverter {} impl StringConverter for BoolStringConverter { fn to_string(&self, value: f32) -> String { if value == 0.0 { "off" } else { "on" }.to_string() } } #[derive(Clone)] pub struct FloatStringConverter { unit: String, } impl FloatStringConverter { pub fn new(unit: String) -> Self { Self { unit } } } impl StringConverter for FloatStringConverter { fn to_string(&self, value: f32) -> String { format!("{:.0} {1}", value, self.unit) } } #[derive(Clone)] pub struct PercentStringConverter {} impl StringConverter for PercentStringConverter { fn to_string(&self, value: f32) -> String { format!("{:.0} %", value * 100.0) } } pub fn float_string_converter(parameter: &FloatParameter) -> Box { Box::new(FloatStringConverter::new(parameter.unit.clone())) } pub fn percent_string_converter(_: &FloatParameter) -> Box { Box::new(PercentStringConverter {}) } ================================================ FILE: crates/audio_module/src/value_converter.rs ================================================ use crate::FloatParameter; pub trait ValueConverter { fn user_to_linear(&self, value: f32) -> f32; fn linear_to_user(&self, value: f32) -> f32; } pub struct DefaultValueConverter {} impl ValueConverter for DefaultValueConverter { fn user_to_linear(&self, value: f32) -> f32 { value } fn linear_to_user(&self, value: f32) -> f32 { value } } pub struct LinearValueConverter { pub min_user_value: f32, pub user_value_range: f32, } impl LinearValueConverter { pub fn new(min: f32, max: f32) -> Self { Self { min_user_value: min, user_value_range: max - min, } } } impl ValueConverter for LinearValueConverter { fn user_to_linear(&self, value: f32) -> f32 { (value - self.min_user_value) / self.user_value_range } fn linear_to_user(&self, value: f32) -> f32 { self.min_user_value + value * self.user_value_range } } pub struct LogValueConverter { pub log_min_user_value: f32, pub log_user_value_range: f32, } impl LogValueConverter { pub fn new(min: f32, max: f32) -> Self { Self { log_min_user_value: min.log2(), log_user_value_range: max.log2() - min.log2(), } } } impl ValueConverter for LogValueConverter { fn user_to_linear(&self, value: f32) -> f32 { (value.log2() - self.log_min_user_value) / self.log_user_value_range } fn linear_to_user(&self, value: f32) -> f32 { (self.log_min_user_value + value * self.log_user_value_range).exp2() } } pub fn linear_value_converter(parameter: &FloatParameter) -> Box { Box::new(LinearValueConverter::new( parameter.min_user_value, parameter.max_user_value, )) } pub fn log_value_converter(parameter: &FloatParameter) -> Box { Box::new(LogValueConverter::new( parameter.min_user_value, parameter.max_user_value, )) } ================================================ FILE: crates/clib/.gitignore ================================================ freeverb.hpp libfreeverb_clib.a ================================================ FILE: crates/clib/Cargo.toml ================================================ [package] name = "freeverb-clib" publish = false version = "0.1.0" authors = { workspace = true } edition = { workspace = true } [dependencies] freeverb = { path = "../freeverb" } [lib] crate-type = ["staticlib", "rlib"] ================================================ FILE: crates/clib/build.sh ================================================ cargo build --release cbindgen -d --lang c++ -o freeverb.hpp . clang++ --std=c++1z --stdlib=libc++ -L../target/release -lfreeverb_clib test.cpp ================================================ FILE: crates/clib/cbindgen.toml ================================================ namespace = "freeverb" ================================================ FILE: crates/clib/src/lib.rs ================================================ pub use freeverb::Freeverb; /// Create a Freeverb instance with a given sample rate /// /// The client is responsible for freeing the instance's memory when it's no longer required, /// see `destroy()`. #[unsafe(no_mangle)] pub extern "C" fn create(sample_rate: usize) -> *mut Freeverb { Box::into_raw(Box::new(Freeverb::::new(sample_rate))) } /// Destroy a Freeverb instance /// /// # Safety /// /// The instance must have been previously created using `create()`. #[unsafe(no_mangle)] pub unsafe extern "C" fn destroy(freeverb: *mut Freeverb) { if !freeverb.is_null() { unsafe { let _ = Box::from_raw(freeverb); } } else { panic!("") } } /// Process an audio buffer /// /// # Safety /// /// The input and output buffers must be (at least) sample_count f32s in size. #[unsafe(no_mangle)] pub unsafe extern "C" fn process( freeverb: &mut Freeverb, input_l: *const f32, input_r: *const f32, output_l: *mut f32, output_r: *mut f32, sample_count: usize, ) { unsafe { for i in 0..sample_count as isize { let out = freeverb.tick((*input_l.offset(i) as f64, *input_r.offset(i) as f64)); *output_l.offset(i) = out.0 as f32; *output_r.offset(i) = out.1 as f32; } } } #[unsafe(no_mangle)] pub extern "C" fn set_dampening(freeverb: &mut Freeverb, value: f64) { freeverb.set_dampening(value) } #[unsafe(no_mangle)] pub extern "C" fn set_freeze(freeverb: &mut Freeverb, value: bool) { freeverb.set_freeze(value) } #[unsafe(no_mangle)] pub extern "C" fn set_wet(freeverb: &mut Freeverb, value: f64) { freeverb.set_wet(value) } #[unsafe(no_mangle)] pub extern "C" fn set_width(freeverb: &mut Freeverb, value: f64) { freeverb.set_width(value) } #[unsafe(no_mangle)] pub extern "C" fn set_dry(freeverb: &mut Freeverb, value: f64) { freeverb.set_dry(value) } #[unsafe(no_mangle)] pub extern "C" fn set_room_size(freeverb: &mut Freeverb, value: f64) { freeverb.set_room_size(value) } ================================================ FILE: crates/clib/test.cpp ================================================ #include "freeverb.hpp" #include int main() { auto pFreeverb = freeverb::create(44100); freeverb::set_wet(pFreeverb, 1.0); freeverb::destroy(pFreeverb); } ================================================ FILE: crates/freeverb/Cargo.toml ================================================ [package] name = "freeverb" version = "0.2.0" edition = { workspace = true } authors = { workspace = true } readme = { workspace = true } repository = { workspace = true } license = { workspace = true } description = "A Rust implementation of the Freeverb algorithm" documentation = "https://docs.rs/freeverb" keywords = ["audio", "dsp", "effect", "reverb", "stereo"] [dependencies] ================================================ FILE: crates/freeverb/src/all_pass.rs ================================================ use crate::{delay_line::DelayLine, float::Float}; pub struct AllPass { delay_line: DelayLine, } impl AllPass { pub fn new(delay_length: usize) -> Self { Self { delay_line: DelayLine::new(delay_length), } } pub fn tick(&mut self, input: T) -> T { let delayed = self.delay_line.read(); let output = -input + delayed; // in the original version of freeverb this is a member which is never modified let feedback = T::from(0.5); self.delay_line .write_and_advance(input + delayed * feedback); output } } #[cfg(test)] mod tests { #[test] fn basic_ticking() { let mut allpass = super::AllPass::new(2); assert_eq!(allpass.tick(1.0), -1.0); assert_eq!(allpass.tick(0.0), 0.0); assert_eq!(allpass.tick(0.0), 1.0); assert_eq!(allpass.tick(0.0), 0.0); assert_eq!(allpass.tick(0.0), 0.5); assert_eq!(allpass.tick(0.0), 0.0); assert_eq!(allpass.tick(0.0), 0.25); } } ================================================ FILE: crates/freeverb/src/comb.rs ================================================ use crate::{delay_line::DelayLine, float::Float}; pub struct Comb { delay_line: DelayLine, feedback: T, filter_state: T, dampening: T, dampening_inverse: T, } impl Comb { pub fn new(delay_length: usize) -> Self { Self { delay_line: DelayLine::new(delay_length), feedback: T::from(0.5), filter_state: T::from(0.0), dampening: T::from(0.5), dampening_inverse: T::from(0.5), } } pub fn set_dampening(&mut self, value: T) { self.dampening = value; self.dampening_inverse = T::from(1.0) - value; } pub fn set_feedback(&mut self, value: T) { self.feedback = value; } pub fn tick(&mut self, input: T) -> T { let output = self.delay_line.read(); self.filter_state = output * self.dampening_inverse + self.filter_state * self.dampening; self.delay_line .write_and_advance(input + self.filter_state * self.feedback); output } } #[cfg(test)] mod tests { #[test] fn basic_ticking() { let mut comb = super::Comb::new(2); assert_eq!(comb.tick(1.0), 0.0); assert_eq!(comb.tick(0.0), 0.0); assert_eq!(comb.tick(0.0), 1.0); assert_eq!(comb.tick(0.0), 0.0); assert_eq!(comb.tick(0.0), 0.25); assert_eq!(comb.tick(0.0), 0.125); assert_eq!(comb.tick(0.0), 0.125); assert_eq!(comb.tick(0.0), 0.09375); } } ================================================ FILE: crates/freeverb/src/delay_line.rs ================================================ use crate::float::Float; pub struct DelayLine { buffer: Vec, index: usize, } impl DelayLine { pub fn new(length: usize) -> Self { Self { buffer: vec![T::from(0.0); length], index: 0, } } pub fn read(&self) -> T { self.buffer[self.index] } pub fn write_and_advance(&mut self, value: T) { self.buffer[self.index] = value; if self.index == self.buffer.len() - 1 { self.index = 0; } else { self.index += 1; } } } #[cfg(test)] mod tests { macro_rules! delay_line_test { ($name:ident, $length:expr) => { #[test] fn $name() { let mut line = super::DelayLine::new($length); for i in 0..$length { assert_eq!(line.read(), 0.0); line.write_and_advance(i as f32); } for i in 0..$length { assert_eq!(line.read(), i as f32); line.write_and_advance(0.0); } } }; } delay_line_test!(length_1, 1); delay_line_test!(length_3, 3); delay_line_test!(length_10, 10); } ================================================ FILE: crates/freeverb/src/float.rs ================================================ use std::{ fmt::Debug, ops::{Add, AddAssign, Div, Mul, Neg, Sub}, }; /// A trait for the floating point ops needed by the [Freeverb](crate::Freeverb) processor. pub trait Float: Add + Sub + Mul + Div + Neg + AddAssign + PartialEq + Default + From + Copy + Clone + Debug + Send + Sync + 'static { /// Converts the value into an `f32`. /// /// `f64` doesn't implement `Into` so an explicit method is needed here. fn to_f32(self) -> f32; } impl Float for f32 { fn to_f32(self) -> f32 { self } } impl Float for f64 { fn to_f32(self) -> f32 { self as f32 } } ================================================ FILE: crates/freeverb/src/freeverb.rs ================================================ use crate::{all_pass::AllPass, comb::Comb, float::Float, tuning::*}; /// A processor for the Freeverb reverb algorithm. /// /// 64-bit processing is enabled by default. /// 32-bit processing can be optionally enabled by using `f32` as the generic `T` parameter. pub struct Freeverb { combs: [(Comb, Comb); 8], allpasses: [(AllPass, AllPass); 4], wet_gains: (T, T), wet: T, width: T, dry: T, input_gain: T, dampening: T, room_size: T, frozen: bool, } impl Freeverb { /// Produces a new processor with the given sample rate. /// /// The algorithm's tuning constants were designed for a sample rate of 44.1kHz, /// with a note that they will 'probably be OK' for 48kHz, but would require scaling for other /// sample rates. In this implementation the constants are scaled when using any sample rate, /// including 48kHz. pub fn new(sr: usize) -> Self { let mut freeverb = Freeverb:: { combs: [ ( Comb::new(adjust_length(COMB_TUNING_L1, sr)), Comb::new(adjust_length(COMB_TUNING_R1, sr)), ), ( Comb::new(adjust_length(COMB_TUNING_L2, sr)), Comb::new(adjust_length(COMB_TUNING_R2, sr)), ), ( Comb::new(adjust_length(COMB_TUNING_L3, sr)), Comb::new(adjust_length(COMB_TUNING_R3, sr)), ), ( Comb::new(adjust_length(COMB_TUNING_L4, sr)), Comb::new(adjust_length(COMB_TUNING_R4, sr)), ), ( Comb::new(adjust_length(COMB_TUNING_L5, sr)), Comb::new(adjust_length(COMB_TUNING_R5, sr)), ), ( Comb::new(adjust_length(COMB_TUNING_L6, sr)), Comb::new(adjust_length(COMB_TUNING_R6, sr)), ), ( Comb::new(adjust_length(COMB_TUNING_L7, sr)), Comb::new(adjust_length(COMB_TUNING_R7, sr)), ), ( Comb::new(adjust_length(COMB_TUNING_L8, sr)), Comb::new(adjust_length(COMB_TUNING_R8, sr)), ), ], allpasses: [ ( AllPass::new(adjust_length(ALLPASS_TUNING_L1, sr)), AllPass::new(adjust_length(ALLPASS_TUNING_R1, sr)), ), ( AllPass::new(adjust_length(ALLPASS_TUNING_L2, sr)), AllPass::new(adjust_length(ALLPASS_TUNING_R2, sr)), ), ( AllPass::new(adjust_length(ALLPASS_TUNING_L3, sr)), AllPass::new(adjust_length(ALLPASS_TUNING_R3, sr)), ), ( AllPass::new(adjust_length(ALLPASS_TUNING_L4, sr)), AllPass::new(adjust_length(ALLPASS_TUNING_R4, sr)), ), ], wet_gains: (T::default(), T::default()), wet: T::default(), dry: T::default(), input_gain: T::default(), width: T::default(), dampening: T::default(), room_size: T::default(), frozen: false, }; freeverb.set_wet(T::from(1.0) / T::from(SCALE_WET)); freeverb.set_width(T::from(1.0)); freeverb.set_dampening(T::from(0.5)); freeverb.set_room_size(T::from(0.5)); freeverb.set_frozen(false); freeverb } /// Processes a single pair of values. /// /// The pair's values are the left/right channels of a single processing frame. /// /// To process a buffer of frames this function should be called repeatedly. pub fn tick(&mut self, input: (T, T)) -> (T, T) { let input_mixed = (input.0 + input.1) * T::from(FIXED_GAIN) * self.input_gain; let mut out = (T::from(0.0), T::from(0.0)); for combs in self.combs.iter_mut() { out.0 += combs.0.tick(input_mixed); out.1 += combs.1.tick(input_mixed); } for allpasses in self.allpasses.iter_mut() { out.0 = allpasses.0.tick(out.0); out.1 = allpasses.1.tick(out.1); } ( out.0 * self.wet_gains.0 + out.1 * self.wet_gains.1 + input.0 * self.dry, out.1 * self.wet_gains.0 + out.0 * self.wet_gains.1 + input.1 * self.dry, ) } /// Sets the processors dampening value. /// /// The value should be in the range `0..=1`. pub fn set_dampening(&mut self, value: T) { self.dampening = value * T::from(SCALE_DAMPENING); self.update_combs(); } /// Enables or disables the reverb's 'freeze' feature. /// /// This is called `set_mode` in the original implementation. pub fn set_freeze(&mut self, frozen: bool) { self.frozen = frozen; self.update_combs(); } /// Sets the amount of the 'dry' signal to include in the processor's output. /// /// The dry signal is the unmodified input, without any of the reverb's output. /// /// The value should be in the range `0..=1`. pub fn set_dry(&mut self, value: T) { self.dry = value; } /// Sets the amount of the 'wet' signal to include in the processor's output. /// /// The wet signal is the reverb's output, without any of the unmodified input. /// /// The value should be in the range `0..=1`. pub fn set_wet(&mut self, value: T) { self.wet = value * T::from(SCALE_WET); self.update_wet_gains(); } /// Sets the processor's stereo width. /// /// The value should be in the range `0..=1`. pub fn set_width(&mut self, value: T) { self.width = value; self.update_wet_gains(); } /// Sets the processor's 'room size'. /// /// The value should be in the range `0..=1`. pub fn set_room_size(&mut self, value: T) { self.room_size = value * T::from(SCALE_ROOM) + T::from(OFFSET_ROOM); self.update_combs(); } fn update_wet_gains(&mut self) { self.wet_gains = ( self.wet * (self.width / T::from(2.0) + T::from(0.5)), self.wet * ((T::from(1.0) - self.width) / T::from(2.0)), ) } fn set_frozen(&mut self, frozen: bool) { self.frozen = frozen; self.input_gain = if frozen { T::from(0.0) } else { T::from(1.0) }; self.update_combs(); } fn update_combs(&mut self) { let (feedback, dampening) = if self.frozen { (T::from(1.0), T::from(0.0)) } else { (self.room_size, self.dampening) }; for combs in self.combs.iter_mut() { combs.0.set_feedback(feedback); combs.1.set_feedback(feedback); combs.0.set_dampening(dampening); combs.1.set_dampening(dampening); } } } fn adjust_length(length: usize, sr: usize) -> usize { (length as f64 * sr as f64 / 44100.0) as usize } #[cfg(test)] mod tests { use super::*; #[test] fn check_adjust_length() { assert_eq!(adjust_length(1000, 44100), 1000); assert_eq!(adjust_length(100, 22050), 50); assert_eq!(adjust_length(2000, 88200), 4000); assert_eq!(adjust_length(4000, 96000), 8707); } #[test] fn check_output() { // Validate the processor's output against reference values take from the original // implementation. let mut freeverb = Freeverb::::new(44100); // Pass in the delta function. let delta = (1.0, 1.0); let silence = (0.0, 0.0); assert_eq!(freeverb.tick(delta), silence); // Process 3000 frames in total. for _ in 0..2999 { freeverb.tick(silence); } // Check that the output is within a small difference of the reference check_almost_equal(freeverb.tick(silence), (-0.0000064512, -0.014999998)); check_almost_equal(freeverb.tick(silence), (0.0074987095, 0.002340001)); check_almost_equal(freeverb.tick(silence), (0.0018747419, -0.0049695)); check_almost_equal(freeverb.tick(silence), (-0.0000000516, -0.0008064)); check_almost_equal(freeverb.tick(silence), (-0.03070501, 0.01296372)); check_almost_equal(freeverb.tick(silence), (-0.024516001, -0.000032256)); check_almost_equal(freeverb.tick(silence), (-0.0004032004, -0.03024645)); check_almost_equal(freeverb.tick(silence), (-0.0000806401, -0.0060492903)); } #[track_caller] fn check_almost_equal(output: (f32, f32), expected: (f32, f32)) { let difference = ((output.0 - expected.0).abs(), (output.1 - expected.1).abs()); let allowed_difference = 1.0e-10; if difference.0 > allowed_difference || difference.1 > allowed_difference { panic!( "Value is not almost equal to the expected output: output: {output:?} expected: {expected:?} allowed difference: {allowed_difference} difference: {difference:?} ", ) } } } ================================================ FILE: crates/freeverb/src/lib.rs ================================================ //! A Rust implementation of the Freeverb reverb algorithm. //! //! Freeverb was originally written in C++ by "Jezar at Dreampoint", and was released into the public //! domain in June 2000. It is now widely used in various incarnations in multiple software packages. //! //! - The orignal C++ source code can be found [here](https://freeverb3-vst.sourceforge.io). //! - For an analysis of the algorithm see //! [here](https://ccrma.stanford.edu/~jos/pasp/Freeverb.html). mod all_pass; mod comb; mod delay_line; mod float; mod freeverb; mod tuning; pub use self::{float::Float, freeverb::Freeverb}; ================================================ FILE: crates/freeverb/src/tuning.rs ================================================ pub const FIXED_GAIN: f32 = 0.015; pub const SCALE_WET: f32 = 3.0; pub const SCALE_DAMPENING: f32 = 0.4; pub const SCALE_ROOM: f32 = 0.28; pub const OFFSET_ROOM: f32 = 0.7; pub const STEREO_SPREAD: usize = 23; pub const COMB_TUNING_L1: usize = 1116; pub const COMB_TUNING_R1: usize = COMB_TUNING_L1 + STEREO_SPREAD; pub const COMB_TUNING_L2: usize = 1188; pub const COMB_TUNING_R2: usize = COMB_TUNING_L2 + STEREO_SPREAD; pub const COMB_TUNING_L3: usize = 1277; pub const COMB_TUNING_R3: usize = COMB_TUNING_L3 + STEREO_SPREAD; pub const COMB_TUNING_L4: usize = 1356; pub const COMB_TUNING_R4: usize = COMB_TUNING_L4 + STEREO_SPREAD; pub const COMB_TUNING_L5: usize = 1422; pub const COMB_TUNING_R5: usize = COMB_TUNING_L5 + STEREO_SPREAD; pub const COMB_TUNING_L6: usize = 1491; pub const COMB_TUNING_R6: usize = COMB_TUNING_L6 + STEREO_SPREAD; pub const COMB_TUNING_L7: usize = 1557; pub const COMB_TUNING_R7: usize = COMB_TUNING_L7 + STEREO_SPREAD; pub const COMB_TUNING_L8: usize = 1617; pub const COMB_TUNING_R8: usize = COMB_TUNING_L8 + STEREO_SPREAD; pub const ALLPASS_TUNING_L1: usize = 556; pub const ALLPASS_TUNING_R1: usize = ALLPASS_TUNING_L1 + STEREO_SPREAD; pub const ALLPASS_TUNING_L2: usize = 441; pub const ALLPASS_TUNING_R2: usize = ALLPASS_TUNING_L2 + STEREO_SPREAD; pub const ALLPASS_TUNING_L3: usize = 341; pub const ALLPASS_TUNING_R3: usize = ALLPASS_TUNING_L3 + STEREO_SPREAD; pub const ALLPASS_TUNING_L4: usize = 225; pub const ALLPASS_TUNING_R4: usize = ALLPASS_TUNING_L4 + STEREO_SPREAD; ================================================ FILE: crates/freeverb_module/Cargo.toml ================================================ [package] name = "freeverb_module" publish = false version = "0.1.0" authors = { workspace = true } edition = { workspace = true } [dependencies] audio_module = { path = "../audio_module" } freeverb = { path = "../freeverb" } num-derive = "0.4.2" num-traits = "0.2" ================================================ FILE: crates/freeverb_module/src/lib.rs ================================================ #[macro_use] extern crate num_derive; use { audio_module::{ AudioModule, AudioProcessor, BoolParameter, Command, CommandHandler, FloatParameter, Parameter, ParameterProvider, percent_string_converter, }, freeverb::{Float, Freeverb}, num_traits::FromPrimitive, }; #[derive(FromPrimitive)] pub enum Parameters { Dampening, Width, RoomSize, Freeze, Dry, Wet, } pub struct FreeverbProcessor { freeverb: Freeverb, } impl FreeverbProcessor { fn new(sample_rate: usize) -> Self { Self { freeverb: Freeverb::new(sample_rate), } } } impl CommandHandler for FreeverbProcessor { fn handle_command(&mut self, command: Command) { match command { Command::SetParameter(id, value) => match Parameters::from_usize(id).unwrap() { Parameters::Dampening => { self.freeverb.set_dampening(value.into()); } Parameters::Width => { self.freeverb.set_width(value.into()); } Parameters::RoomSize => { self.freeverb.set_room_size(value.into()); } Parameters::Freeze => { self.freeverb.set_freeze(value != 0.0); } Parameters::Dry => { self.freeverb.set_dry(value.into()); } Parameters::Wet => { self.freeverb.set_wet(value.into()); } }, } } } impl AudioProcessor for FreeverbProcessor { fn process(&mut self, input: &[f32], output: &mut [f32], channels: u32) { debug_assert_eq!(channels, 2); debug_assert_eq!(input.len(), output.len()); for i in (0..input.len()).step_by(2) { let result = self.freeverb.tick((input[i].into(), input[i + 1].into())); output[i] = result.0.to_f32(); output[i + 1] = result.1.to_f32(); } } } pub struct FreeverbModule {} impl AudioModule for FreeverbModule { type Processor = FreeverbProcessor; fn create_processor(sample_rate: usize) -> Self::Processor { FreeverbProcessor::new(sample_rate) } } impl ParameterProvider for FreeverbModule { fn parameter_count() -> usize { (0..usize::MAX) .take_while(|&x| Parameters::from_usize(x).is_some()) .count() } fn parameter(id: usize) -> Box { match Parameters::from_usize(id).unwrap() { Parameters::Dampening => Box::new( FloatParameter::new("Dampening") .string_converter(percent_string_converter) .default_user_value(0.5), ), Parameters::Width => Box::new( FloatParameter::new("Width") .string_converter(percent_string_converter) .default_user_value(0.5), ), Parameters::RoomSize => Box::new( FloatParameter::new("Room Size") .string_converter(percent_string_converter) .default_user_value(0.5), ), Parameters::Freeze => Box::new(BoolParameter::new("Freeze")), Parameters::Dry => Box::new( FloatParameter::new("Dry") .string_converter(percent_string_converter) .default_user_value(0.0), ), Parameters::Wet => Box::new( FloatParameter::new("Wet") .string_converter(percent_string_converter) .default_user_value(1.0), ), } } } ================================================ FILE: examples/app_gtk/Cargo.toml ================================================ [package] name = "freeverb-gtk" publish = false version = "0.1.0" authors = { workspace = true } edition = { workspace = true } [dependencies] audio_module = { path = "../../crates/audio_module" } freeverb_module = { path = "../../crates/freeverb_module" } audio_thread_priority = "0.34.0" cpal = "0.16.0" crossbeam-channel = "0.5.15" gtk = { version = "0.10.1", package = "gtk4", features = ["v4_12"] } ringbuf = "0.4.8" ================================================ FILE: examples/app_gtk/src/audio_thread.rs ================================================ use { audio_module::{AudioModule, AudioProcessor, Command, CommandHandler}, cpal::traits::{DeviceTrait, HostTrait, StreamTrait}, ringbuf::{ HeapRb, traits::{Consumer, Producer, Split}, }, }; #[allow(unused)] pub struct AudioStreams { pub output: cpal::Stream, pub input: cpal::Stream, } pub fn start_audio( command_receiver: crossbeam_channel::Receiver, sample_rate: usize, ) -> Result { let mut processor = Module::create_processor(sample_rate); const CHANNELS: usize = 2; const FRAMES_PER_BUFFER: usize = 128; const SAMPLES_PER_BUFFER: usize = FRAMES_PER_BUFFER * CHANNELS; let host = cpal::default_host(); let input_device = host .default_input_device() .expect("failed to find a default input device"); let output_device = host .default_output_device() .expect("failed to find a default output device"); let stream_config = cpal::StreamConfig { channels: CHANNELS as u16, sample_rate: cpal::SampleRate(sample_rate as u32), buffer_size: cpal::BufferSize::Fixed(FRAMES_PER_BUFFER as u32), }; let mut process_buffer = [0.0f32; SAMPLES_PER_BUFFER]; let ring_buffer = HeapRb::new(SAMPLES_PER_BUFFER * 2); let (mut to_output, mut from_input) = ring_buffer.split(); let input = input_device .build_input_stream( &stream_config, move |data: &[f32], _info: &cpal::InputCallbackInfo| { debug_assert!(data.len() == SAMPLES_PER_BUFFER); while let Ok(command) = command_receiver.try_recv() { processor.handle_command(command); } processor.process(data, &mut process_buffer, CHANNELS as u32); to_output.push_slice(&process_buffer); }, move |err| eprintln!("Error on audio input stream: {}", err), None, ) .expect("Failed to create input audio stream"); let output = output_device .build_output_stream( &stream_config, move |data: &mut [f32], _info: &cpal::OutputCallbackInfo| { // println!("output buffer"); debug_assert!(data.len() == SAMPLES_PER_BUFFER); let consumed = from_input.pop_slice(data); if consumed < SAMPLES_PER_BUFFER { println!("output underflowed"); } }, move |err| eprintln!("Error on audio output stream: {}", err), None, ) .expect("Failed to create input audio stream"); if let Err(error) = input.play() { eprintln!("Error while starting input audio stream: {}", error); return Err(()); } if let Err(error) = output.play() { eprintln!("Error while starting output audio stream: {}", error); return Err(()); } println!("Started audio i/o"); Ok(AudioStreams { input, output }) } ================================================ FILE: examples/app_gtk/src/gtk_parameter_slider.rs ================================================ use { audio_module::{Command, Parameter}, gtk::{Label, Orientation, PositionType, Scale, prelude::*}, }; pub fn make_slider( parameter: Box, id: usize, command_sender: crossbeam_channel::Sender, ) -> gtk::Box { let value_converter = parameter.make_value_converter(); let string_converter = parameter.make_string_converter(); let label = Label::new(Some(parameter.name().as_str())); let adjustment = gtk::Adjustment::builder() .lower(0.0) .upper(1.0) .value(value_converter.user_to_linear(parameter.default_user_value()) as f64) .step_increment(0.01) .page_increment(0.1) .build(); let scale = Scale::builder() .adjustment(&adjustment) .inverted(true) .value_pos(PositionType::Bottom) .orientation(Orientation::Vertical) .draw_value(true) .digits(2) .vexpand(true) .hexpand(true) .build(); scale.set_increments(0.01, 0.001); scale.set_format_value_func(move |_, x| { string_converter.to_string(value_converter.linear_to_user(x as f32)) }); scale.connect_value_changed(move |scale| { command_sender .send(Command::SetParameter(id, scale.value() as f32)) .unwrap(); }); let container = gtk::Box::builder() .orientation(Orientation::Vertical) .spacing(2) .build(); container.append(&label); container.append(&scale); container } ================================================ FILE: examples/app_gtk/src/gtk_parameter_toggle.rs ================================================ use { audio_module::{Command, Parameter}, gtk::{Align, Orientation, ToggleButton, prelude::*}, }; pub fn make_toggle( parameter: Box, id: usize, command_sender: crossbeam_channel::Sender, ) -> gtk::Box { let button = ToggleButton::with_label(parameter.name().as_str()); button.set_active(parameter.default_user_value() != 0.0); button.connect_toggled(move |button| { command_sender .send(Command::SetParameter( id, if button.is_active() { 1.0f32 } else { 0.0f32 }, )) .unwrap(); }); let container = gtk::Box::builder() .orientation(Orientation::Vertical) .spacing(2) .baseline_position(gtk::BaselinePosition::Center) // .vexpand(true) .valign(Align::Center) .build(); container.append(&button); container } ================================================ FILE: examples/app_gtk/src/main.rs ================================================ use { audio_module::{AudioModule, ValueType}, freeverb_module::FreeverbModule, gtk::{Application, ApplicationWindow, Orientation, prelude::*}, }; mod audio_thread; mod gtk_parameter_slider; mod gtk_parameter_toggle; fn main() { run_main::(); } fn run_main() { if gtk::init().is_err() { println!("Error initializing GTK"); return; } let (command_sender, command_receiver) = crossbeam_channel::bounded(1024); let sample_rate = 44100; let _audio_streams = audio_thread::start_audio::(command_receiver, sample_rate) .expect("Failed to start audio"); let app = Application::builder() .application_id("org.example.freeverb-rs") .build(); app.connect_activate(move |app| { let window = ApplicationWindow::builder() .application(app) .default_width(350) .default_height(300) .resizable(false) .title("freeverb-rs") .build(); let widgets = gtk::Box::builder() .orientation(Orientation::Horizontal) .spacing(4) .height_request(200) .vexpand(false) .build(); for id in 0..Module::parameter_count() { let parameter = Module::parameter(id); let widget = match parameter.value_type() { ValueType::Float => { gtk_parameter_slider::make_slider(parameter, id, command_sender.clone()) } ValueType::Bool => { gtk_parameter_toggle::make_toggle(parameter, id, command_sender.clone()) } }; widgets.append(&widget); } window.set_child(Some(&widgets)); window.present(); }); app.run(); } ================================================ FILE: examples/app_juce/.gitignore ================================================ Builds ================================================ FILE: examples/app_juce/JuceLibraryCode/AppConfig.h ================================================ /* IMPORTANT! This file is auto-generated each time you save your project - if you alter its contents, your changes may be overwritten! There's a section below where you can add your own custom code safely, and the Projucer will preserve the contents of that block, but the best way to change any of these definitions is by using the Projucer's project settings. Any commented-out settings will assume their default values. */ #pragma once //============================================================================== // [BEGIN_USER_CODE_SECTION] // (You can add your own code in this section, and the Projucer will not overwrite it) // [END_USER_CODE_SECTION] /* ============================================================================== In accordance with the terms of the JUCE 5 End-Use License Agreement, the JUCE Code in SECTION A cannot be removed, changed or otherwise rendered ineffective unless you have a JUCE Indie or Pro license, or are using JUCE under the GPL v3 license. End User License Agreement: www.juce.com/juce-5-licence ============================================================================== */ // BEGIN SECTION A #ifndef JUCE_DISPLAY_SPLASH_SCREEN #define JUCE_DISPLAY_SPLASH_SCREEN 1 #endif #ifndef JUCE_REPORT_APP_USAGE #define JUCE_REPORT_APP_USAGE 1 #endif // END SECTION A #define JUCE_USE_DARK_SPLASH_SCREEN 1 //============================================================================== #define JUCE_MODULE_AVAILABLE_juce_audio_basics 1 #define JUCE_MODULE_AVAILABLE_juce_audio_devices 1 #define JUCE_MODULE_AVAILABLE_juce_audio_formats 1 #define JUCE_MODULE_AVAILABLE_juce_audio_processors 1 #define JUCE_MODULE_AVAILABLE_juce_audio_utils 1 #define JUCE_MODULE_AVAILABLE_juce_core 1 #define JUCE_MODULE_AVAILABLE_juce_cryptography 1 #define JUCE_MODULE_AVAILABLE_juce_data_structures 1 #define JUCE_MODULE_AVAILABLE_juce_events 1 #define JUCE_MODULE_AVAILABLE_juce_graphics 1 #define JUCE_MODULE_AVAILABLE_juce_gui_basics 1 #define JUCE_MODULE_AVAILABLE_juce_gui_extra 1 #define JUCE_MODULE_AVAILABLE_juce_opengl 1 #define JUCE_MODULE_AVAILABLE_juce_video 1 #define JUCE_GLOBAL_MODULE_SETTINGS_INCLUDED 1 //============================================================================== // juce_audio_devices flags: #ifndef JUCE_USE_WINRT_MIDI //#define JUCE_USE_WINRT_MIDI 0 #endif #ifndef JUCE_ASIO //#define JUCE_ASIO 0 #endif #ifndef JUCE_WASAPI //#define JUCE_WASAPI 1 #endif #ifndef JUCE_WASAPI_EXCLUSIVE //#define JUCE_WASAPI_EXCLUSIVE 0 #endif #ifndef JUCE_DIRECTSOUND //#define JUCE_DIRECTSOUND 1 #endif #ifndef JUCE_ALSA //#define JUCE_ALSA 1 #endif #ifndef JUCE_JACK //#define JUCE_JACK 0 #endif #ifndef JUCE_BELA //#define JUCE_BELA 0 #endif #ifndef JUCE_USE_ANDROID_OBOE //#define JUCE_USE_ANDROID_OBOE 0 #endif #ifndef JUCE_USE_ANDROID_OPENSLES //#define JUCE_USE_ANDROID_OPENSLES 0 #endif #ifndef JUCE_DISABLE_AUDIO_MIXING_WITH_OTHER_APPS //#define JUCE_DISABLE_AUDIO_MIXING_WITH_OTHER_APPS 0 #endif //============================================================================== // juce_audio_formats flags: #ifndef JUCE_USE_FLAC //#define JUCE_USE_FLAC 1 #endif #ifndef JUCE_USE_OGGVORBIS //#define JUCE_USE_OGGVORBIS 1 #endif #ifndef JUCE_USE_MP3AUDIOFORMAT //#define JUCE_USE_MP3AUDIOFORMAT 0 #endif #ifndef JUCE_USE_LAME_AUDIO_FORMAT //#define JUCE_USE_LAME_AUDIO_FORMAT 0 #endif #ifndef JUCE_USE_WINDOWS_MEDIA_FORMAT //#define JUCE_USE_WINDOWS_MEDIA_FORMAT 1 #endif //============================================================================== // juce_audio_processors flags: #ifndef JUCE_PLUGINHOST_VST //#define JUCE_PLUGINHOST_VST 0 #endif #ifndef JUCE_PLUGINHOST_VST3 //#define JUCE_PLUGINHOST_VST3 0 #endif #ifndef JUCE_PLUGINHOST_AU //#define JUCE_PLUGINHOST_AU 0 #endif #ifndef JUCE_PLUGINHOST_LADSPA //#define JUCE_PLUGINHOST_LADSPA 0 #endif //============================================================================== // juce_audio_utils flags: #ifndef JUCE_USE_CDREADER //#define JUCE_USE_CDREADER 0 #endif #ifndef JUCE_USE_CDBURNER //#define JUCE_USE_CDBURNER 0 #endif //============================================================================== // juce_core flags: #ifndef JUCE_FORCE_DEBUG //#define JUCE_FORCE_DEBUG 0 #endif #ifndef JUCE_LOG_ASSERTIONS //#define JUCE_LOG_ASSERTIONS 0 #endif #ifndef JUCE_CHECK_MEMORY_LEAKS //#define JUCE_CHECK_MEMORY_LEAKS 1 #endif #ifndef JUCE_DONT_AUTOLINK_TO_WIN32_LIBRARIES //#define JUCE_DONT_AUTOLINK_TO_WIN32_LIBRARIES 0 #endif #ifndef JUCE_INCLUDE_ZLIB_CODE //#define JUCE_INCLUDE_ZLIB_CODE 1 #endif #ifndef JUCE_USE_CURL //#define JUCE_USE_CURL 0 #endif #ifndef JUCE_LOAD_CURL_SYMBOLS_LAZILY //#define JUCE_LOAD_CURL_SYMBOLS_LAZILY 0 #endif #ifndef JUCE_CATCH_UNHANDLED_EXCEPTIONS //#define JUCE_CATCH_UNHANDLED_EXCEPTIONS 1 #endif #ifndef JUCE_ALLOW_STATIC_NULL_VARIABLES //#define JUCE_ALLOW_STATIC_NULL_VARIABLES 1 #endif #ifndef JUCE_STRICT_REFCOUNTEDPOINTER #define JUCE_STRICT_REFCOUNTEDPOINTER 1 #endif //============================================================================== // juce_events flags: #ifndef JUCE_EXECUTE_APP_SUSPEND_ON_IOS_BACKGROUND_TASK //#define JUCE_EXECUTE_APP_SUSPEND_ON_IOS_BACKGROUND_TASK 0 #endif //============================================================================== // juce_graphics flags: #ifndef JUCE_USE_COREIMAGE_LOADER //#define JUCE_USE_COREIMAGE_LOADER 1 #endif #ifndef JUCE_USE_DIRECTWRITE //#define JUCE_USE_DIRECTWRITE 1 #endif #ifndef JUCE_DISABLE_COREGRAPHICS_FONT_SMOOTHING //#define JUCE_DISABLE_COREGRAPHICS_FONT_SMOOTHING 0 #endif //============================================================================== // juce_gui_basics flags: #ifndef JUCE_ENABLE_REPAINT_DEBUGGING //#define JUCE_ENABLE_REPAINT_DEBUGGING 0 #endif #ifndef JUCE_USE_XRANDR //#define JUCE_USE_XRANDR 1 #endif #ifndef JUCE_USE_XINERAMA //#define JUCE_USE_XINERAMA 1 #endif #ifndef JUCE_USE_XSHM //#define JUCE_USE_XSHM 1 #endif #ifndef JUCE_USE_XRENDER //#define JUCE_USE_XRENDER 0 #endif #ifndef JUCE_USE_XCURSOR //#define JUCE_USE_XCURSOR 1 #endif #ifndef JUCE_WIN_PER_MONITOR_DPI_AWARE //#define JUCE_WIN_PER_MONITOR_DPI_AWARE 1 #endif //============================================================================== // juce_gui_extra flags: #ifndef JUCE_WEB_BROWSER //#define JUCE_WEB_BROWSER 1 #endif #ifndef JUCE_ENABLE_LIVE_CONSTANT_EDITOR //#define JUCE_ENABLE_LIVE_CONSTANT_EDITOR 0 #endif //============================================================================== // juce_video flags: #ifndef JUCE_USE_CAMERA //#define JUCE_USE_CAMERA 0 #endif #ifndef JUCE_SYNC_VIDEO_VOLUME_WITH_OS_MEDIA_VOLUME //#define JUCE_SYNC_VIDEO_VOLUME_WITH_OS_MEDIA_VOLUME 1 #endif //============================================================================== #ifndef JUCE_STANDALONE_APPLICATION #if defined(JucePlugin_Name) && defined(JucePlugin_Build_Standalone) #define JUCE_STANDALONE_APPLICATION JucePlugin_Build_Standalone #else #define JUCE_STANDALONE_APPLICATION 1 #endif #endif ================================================ FILE: examples/app_juce/JuceLibraryCode/JuceHeader.h ================================================ /* IMPORTANT! This file is auto-generated each time you save your project - if you alter its contents, your changes may be overwritten! This is the header file that your files should include in order to get all the JUCE library headers. You should avoid including the JUCE headers directly in your own source files, because that wouldn't pick up the correct configuration options for your app. */ #pragma once #include "AppConfig.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if ! DONT_SET_USING_JUCE_NAMESPACE // If your code uses a lot of JUCE classes, then this will obviously save you // a lot of typing, but can be disabled by setting DONT_SET_USING_JUCE_NAMESPACE. using namespace juce; #endif #if ! JUCE_DONT_DECLARE_PROJECTINFO namespace ProjectInfo { const char* const projectName = "freeverb"; const char* const companyName = ""; const char* const versionString = "1.0.0"; const int versionNumber = 0x10000; } #endif ================================================ FILE: examples/app_juce/JuceLibraryCode/ReadMe.txt ================================================ Important Note!! ================ The purpose of this folder is to contain files that are auto-generated by the Projucer, and ALL files in this folder will be mercilessly DELETED and completely re-written whenever the Projucer saves your project. Therefore, it's a bad idea to make any manual changes to the files in here, or to put any of your own files in here if you don't want to lose them. (Of course you may choose to add the folder's contents to your version-control system so that you can re-merge your own modifications after the Projucer has saved its changes). ================================================ FILE: examples/app_juce/JuceLibraryCode/include_juce_audio_basics.cpp ================================================ /* IMPORTANT! This file is auto-generated each time you save your project - if you alter its contents, your changes may be overwritten! */ #include "AppConfig.h" #include ================================================ FILE: examples/app_juce/JuceLibraryCode/include_juce_audio_basics.mm ================================================ /* IMPORTANT! This file is auto-generated each time you save your project - if you alter its contents, your changes may be overwritten! */ #include "AppConfig.h" #include ================================================ FILE: examples/app_juce/JuceLibraryCode/include_juce_audio_devices.cpp ================================================ /* IMPORTANT! This file is auto-generated each time you save your project - if you alter its contents, your changes may be overwritten! */ #include "AppConfig.h" #include ================================================ FILE: examples/app_juce/JuceLibraryCode/include_juce_audio_devices.mm ================================================ /* IMPORTANT! This file is auto-generated each time you save your project - if you alter its contents, your changes may be overwritten! */ #include "AppConfig.h" #include ================================================ FILE: examples/app_juce/JuceLibraryCode/include_juce_audio_formats.cpp ================================================ /* IMPORTANT! This file is auto-generated each time you save your project - if you alter its contents, your changes may be overwritten! */ #include "AppConfig.h" #include ================================================ FILE: examples/app_juce/JuceLibraryCode/include_juce_audio_formats.mm ================================================ /* IMPORTANT! This file is auto-generated each time you save your project - if you alter its contents, your changes may be overwritten! */ #include "AppConfig.h" #include ================================================ FILE: examples/app_juce/JuceLibraryCode/include_juce_audio_processors.cpp ================================================ /* IMPORTANT! This file is auto-generated each time you save your project - if you alter its contents, your changes may be overwritten! */ #include "AppConfig.h" #include ================================================ FILE: examples/app_juce/JuceLibraryCode/include_juce_audio_processors.mm ================================================ /* IMPORTANT! This file is auto-generated each time you save your project - if you alter its contents, your changes may be overwritten! */ #include "AppConfig.h" #include ================================================ FILE: examples/app_juce/JuceLibraryCode/include_juce_audio_utils.cpp ================================================ /* IMPORTANT! This file is auto-generated each time you save your project - if you alter its contents, your changes may be overwritten! */ #include "AppConfig.h" #include ================================================ FILE: examples/app_juce/JuceLibraryCode/include_juce_audio_utils.mm ================================================ /* IMPORTANT! This file is auto-generated each time you save your project - if you alter its contents, your changes may be overwritten! */ #include "AppConfig.h" #include ================================================ FILE: examples/app_juce/JuceLibraryCode/include_juce_core.cpp ================================================ /* IMPORTANT! This file is auto-generated each time you save your project - if you alter its contents, your changes may be overwritten! */ #include "AppConfig.h" #include ================================================ FILE: examples/app_juce/JuceLibraryCode/include_juce_core.mm ================================================ /* IMPORTANT! This file is auto-generated each time you save your project - if you alter its contents, your changes may be overwritten! */ #include "AppConfig.h" #include ================================================ FILE: examples/app_juce/JuceLibraryCode/include_juce_cryptography.cpp ================================================ /* IMPORTANT! This file is auto-generated each time you save your project - if you alter its contents, your changes may be overwritten! */ #include "AppConfig.h" #include ================================================ FILE: examples/app_juce/JuceLibraryCode/include_juce_cryptography.mm ================================================ /* IMPORTANT! This file is auto-generated each time you save your project - if you alter its contents, your changes may be overwritten! */ #include "AppConfig.h" #include ================================================ FILE: examples/app_juce/JuceLibraryCode/include_juce_data_structures.cpp ================================================ /* IMPORTANT! This file is auto-generated each time you save your project - if you alter its contents, your changes may be overwritten! */ #include "AppConfig.h" #include ================================================ FILE: examples/app_juce/JuceLibraryCode/include_juce_data_structures.mm ================================================ /* IMPORTANT! This file is auto-generated each time you save your project - if you alter its contents, your changes may be overwritten! */ #include "AppConfig.h" #include ================================================ FILE: examples/app_juce/JuceLibraryCode/include_juce_events.cpp ================================================ /* IMPORTANT! This file is auto-generated each time you save your project - if you alter its contents, your changes may be overwritten! */ #include "AppConfig.h" #include ================================================ FILE: examples/app_juce/JuceLibraryCode/include_juce_events.mm ================================================ /* IMPORTANT! This file is auto-generated each time you save your project - if you alter its contents, your changes may be overwritten! */ #include "AppConfig.h" #include ================================================ FILE: examples/app_juce/JuceLibraryCode/include_juce_graphics.cpp ================================================ /* IMPORTANT! This file is auto-generated each time you save your project - if you alter its contents, your changes may be overwritten! */ #include "AppConfig.h" #include ================================================ FILE: examples/app_juce/JuceLibraryCode/include_juce_graphics.mm ================================================ /* IMPORTANT! This file is auto-generated each time you save your project - if you alter its contents, your changes may be overwritten! */ #include "AppConfig.h" #include ================================================ FILE: examples/app_juce/JuceLibraryCode/include_juce_gui_basics.cpp ================================================ /* IMPORTANT! This file is auto-generated each time you save your project - if you alter its contents, your changes may be overwritten! */ #include "AppConfig.h" #include ================================================ FILE: examples/app_juce/JuceLibraryCode/include_juce_gui_basics.mm ================================================ /* IMPORTANT! This file is auto-generated each time you save your project - if you alter its contents, your changes may be overwritten! */ #include "AppConfig.h" #include ================================================ FILE: examples/app_juce/JuceLibraryCode/include_juce_gui_extra.cpp ================================================ /* IMPORTANT! This file is auto-generated each time you save your project - if you alter its contents, your changes may be overwritten! */ #include "AppConfig.h" #include ================================================ FILE: examples/app_juce/JuceLibraryCode/include_juce_gui_extra.mm ================================================ /* IMPORTANT! This file is auto-generated each time you save your project - if you alter its contents, your changes may be overwritten! */ #include "AppConfig.h" #include ================================================ FILE: examples/app_juce/JuceLibraryCode/include_juce_opengl.cpp ================================================ /* IMPORTANT! This file is auto-generated each time you save your project - if you alter its contents, your changes may be overwritten! */ #include "AppConfig.h" #include ================================================ FILE: examples/app_juce/JuceLibraryCode/include_juce_opengl.mm ================================================ /* IMPORTANT! This file is auto-generated each time you save your project - if you alter its contents, your changes may be overwritten! */ #include "AppConfig.h" #include ================================================ FILE: examples/app_juce/JuceLibraryCode/include_juce_video.cpp ================================================ /* IMPORTANT! This file is auto-generated each time you save your project - if you alter its contents, your changes may be overwritten! */ #include "AppConfig.h" #include ================================================ FILE: examples/app_juce/JuceLibraryCode/include_juce_video.mm ================================================ /* IMPORTANT! This file is auto-generated each time you save your project - if you alter its contents, your changes may be overwritten! */ #include "AppConfig.h" #include ================================================ FILE: examples/app_juce/Source/Main.cpp ================================================ /* ============================================================================== This file was auto-generated! It contains the basic startup code for a JUCE application. ============================================================================== */ #include "../JuceLibraryCode/JuceHeader.h" #include "MainComponent.h" //============================================================================== class NewProjectApplication : public JUCEApplication { public: //============================================================================== NewProjectApplication() {} const String getApplicationName() override { return ProjectInfo::projectName; } const String getApplicationVersion() override { return ProjectInfo::versionString; } bool moreThanOneInstanceAllowed() override { return true; } //============================================================================== void initialise (const String& commandLine) override { // This method is where you should put your application's initialisation code.. mainWindow.reset (new MainWindow (getApplicationName())); } void shutdown() override { // Add your application's shutdown code here.. mainWindow = nullptr; // (deletes our window) } //============================================================================== void systemRequestedQuit() override { // This is called when the app is being asked to quit: you can ignore this // request and let the app carry on running, or call quit() to allow the app to close. quit(); } void anotherInstanceStarted (const String& commandLine) override { // When another instance of the app is launched while this one is running, // this method is invoked, and the commandLine parameter tells you what // the other instance's command-line arguments were. } //============================================================================== /* This class implements the desktop window that contains an instance of our MainComponent class. */ class MainWindow : public DocumentWindow { public: MainWindow (String name) : DocumentWindow (name, Desktop::getInstance().getDefaultLookAndFeel() .findColour (ResizableWindow::backgroundColourId), DocumentWindow::allButtons) { setUsingNativeTitleBar (true); setContentOwned (new MainComponent(), true); #if JUCE_IOS || JUCE_ANDROID setFullScreen (true); #else setResizable (true, true); centreWithSize (getWidth(), getHeight()); #endif setVisible (true); } void closeButtonPressed() override { // This is called when the user tries to close this window. Here, we'll just // ask the app to quit when this happens, but you can change this to do // whatever you need. JUCEApplication::getInstance()->systemRequestedQuit(); } /* Note: Be careful if you override any DocumentWindow methods - the base class uses a lot of them, so by overriding you might break its functionality. It's best to do all your work in your content component instead, but if you really have to override any DocumentWindow methods, make sure your subclass also calls the superclass's method. */ private: JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainWindow) }; private: std::unique_ptr mainWindow; }; //============================================================================== // This macro generates the main() routine that launches the app. START_JUCE_APPLICATION (NewProjectApplication) ================================================ FILE: examples/app_juce/Source/MainComponent.cpp ================================================ #include "MainComponent.h" //============================================================================== MainComponent::MainComponent() : freeverb_(nullptr, &freeverb::destroy) { auto setupSlider = [this](auto &slider, auto &label, const auto &text, const auto value) { addAndMakeVisible(slider); addAndMakeVisible(label); slider.setRange(0, 100); slider.addListener(this); slider.setTextValueSuffix("%"); slider.setNumDecimalPlacesToDisplay(1); slider.setValue(value); label.setText(text, dontSendNotification); label.attachToComponent(&slider, true); }; setupSlider(sliderDampening_, labelDampening_, "Dampening", 40.0); setupSlider(sliderWidth_, labelWidth_, "Width", 30.0); setupSlider(sliderRoomSize_, labelRoomSize_, "Room Size", 60.0); setupSlider(sliderDry_, labelDry_, "Dry", 50.0); setupSlider(sliderWet_, labelWet_, "Wet", 30.0); addAndMakeVisible(toggleFreeze_); toggleFreeze_.addListener(this); toggleFreeze_.setButtonText("Freeze"); setSize(600, 300); setAudioChannels(2, 2); } MainComponent::~MainComponent() { shutdownAudio(); } //============================================================================== void MainComponent::prepareToPlay(int samplesPerBlockExpected, double sampleRate) { freeverb_.reset(freeverb::create(sampleRate)); } void MainComponent::getNextAudioBlock(const AudioSourceChannelInfo &block) { const auto offset = block.startSample; const auto buffer = block.buffer; std::pair command; while (command_queue_.pop(command)) { switch (command.first) { case Parameter::Dampening: freeverb::set_dampening(freeverb_.get(), command.second); break; case Parameter::Width: freeverb::set_width(freeverb_.get(), command.second); break; case Parameter::RoomSize: freeverb::set_room_size(freeverb_.get(), command.second); break; case Parameter::Freeze: freeverb::set_freeze(freeverb_.get(), command.second != 0.0); break; case Parameter::Dry: freeverb::set_dry(freeverb_.get(), command.second); break; case Parameter::Wet: freeverb::set_wet(freeverb_.get(), command.second); break; } } freeverb::process(freeverb_.get(), buffer->getReadPointer(0, offset), buffer->getReadPointer(1, offset), buffer->getWritePointer(0, offset), buffer->getWritePointer(1, offset), block.numSamples); } void MainComponent::releaseResources() { freeverb_.release(); } //============================================================================== void MainComponent::paint(Graphics &g) { g.fillAll(getLookAndFeel().findColour(ResizableWindow::backgroundColourId)); } void MainComponent::resized() { const auto marginLeft = 120; const auto marginRight = 60; const auto height = 20; const auto verticalPadding = 5; auto y = verticalPadding; sliderDampening_.setBounds(marginLeft, y, getWidth() - marginLeft - marginRight, height); y += height + verticalPadding; sliderWidth_.setBounds(marginLeft, y, getWidth() - marginLeft - marginRight, height); y += height + verticalPadding; sliderRoomSize_.setBounds(marginLeft, y, getWidth() - marginLeft - marginRight, height); y += height + verticalPadding; toggleFreeze_.setBounds(marginLeft, y, 200, height); y += height + verticalPadding; sliderDry_.setBounds(marginLeft, y, getWidth() - marginLeft - marginRight, height); y += height + verticalPadding; sliderWet_.setBounds(marginLeft, y, getWidth() - marginLeft - marginRight, height); y += height + verticalPadding; } void MainComponent::buttonClicked(Button *button) { if (button == &toggleFreeze_) { command_queue_.push( std::make_pair(Parameter::Freeze, button->getToggleState() ? 1.0 : 0.0)); } } void MainComponent::sliderValueChanged(Slider *slider) { if (slider == &sliderDampening_) { command_queue_.push( std::make_pair(Parameter::Dampening, slider->getValue() / 100.0)); } else if (slider == &sliderWidth_) { command_queue_.push( std::make_pair(Parameter::Width, slider->getValue() / 100.0)); } else if (slider == &sliderRoomSize_) { command_queue_.push( std::make_pair(Parameter::RoomSize, slider->getValue() / 100.0)); } else if (slider == &sliderDry_) { command_queue_.push( std::make_pair(Parameter::Dry, slider->getValue() / 100.0)); } else if (slider == &sliderWet_) { command_queue_.push( std::make_pair(Parameter::Wet, slider->getValue() / 100.0)); } } ================================================ FILE: examples/app_juce/Source/MainComponent.h ================================================ #pragma once #include "../JuceLibraryCode/JuceHeader.h" #include "freeverb.hpp" #include #include enum class Parameter { Dampening, Width, RoomSize, Freeze, Dry, Wet, }; class MainComponent : public AudioAppComponent, public Slider::Listener, public Button::Listener { public: //============================================================================== MainComponent(); ~MainComponent(); //============================================================================== void prepareToPlay(int samplesPerBlockExpected, double sampleRate) override; void getNextAudioBlock(const AudioSourceChannelInfo &bufferToFill) override; void releaseResources() override; //============================================================================== void paint(Graphics &g) override; void resized() override; //============================================================================== void sliderValueChanged(Slider *slider) override; //============================================================================== void buttonClicked(Button *button) override; void buttonStateChanged(Button *button) override {} private: std::unique_ptr freeverb_; boost::lockfree::spsc_queue, boost::lockfree::capacity<128>> command_queue_; Slider sliderDampening_; Slider sliderWidth_; Slider sliderRoomSize_; Slider sliderDry_; Slider sliderWet_; Label labelDampening_; Label labelWidth_; Label labelRoomSize_; Label labelDry_; Label labelWet_; ToggleButton toggleFreeze_; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(MainComponent) }; ================================================ FILE: examples/app_juce/freeverb.jucer ================================================ ================================================ FILE: examples/wasm/.gitignore ================================================ node_modules public/freeverb_wasm.wasm ================================================ FILE: examples/wasm/Cargo.toml ================================================ [package] name = "freeverb-wasm" publish = false version = "0.1.0" authors = { workspace = true } edition = { workspace = true } [dependencies] freeverb-clib = { path = "../../crates/clib"} [lib] crate-type = ["cdylib"] ================================================ FILE: examples/wasm/README.md ================================================ # freeverb-wasm This is an example of taking the freeverb's C API and making it available as [WebAssembly](https://webassembly.org). To try out the example in a browser, `cd` into this folder, and run: ``` npm install npm run serve ``` ================================================ FILE: examples/wasm/package.json ================================================ { "scripts": { "build": "cargo build --target wasm32-unknown-unknown --release && cp ../../target/wasm32-unknown-unknown/release/freeverb_wasm.wasm public", "build-debug": "cargo build --target wasm32-unknown-unknown && cp ../../target/wasm32-unknown-unknown/debug/freeverb_wasm.wasm public", "serve": "npm run build && http-server public -o" }, "devDependencies": { "http-server": "^14.1.1" } } ================================================ FILE: examples/wasm/public/freeverb-processor.js ================================================ class FreeverbProcessor extends AudioWorkletProcessor { static get parameterDescriptors() { return [ { name: 'dampening', defaultValue: 0.5, minValue: 0.0, maxValue: 1.0, automationRate: 'k-rate' }, { name: 'width', defaultValue: 0.5, minValue: 0.0, maxValue: 1.0, automationRate: 'k-rate' }, { name: 'roomSize', defaultValue: 0.5, minValue: 0.0, maxValue: 1.0, automationRate: 'k-rate' }, { name: 'freeze', defaultValue: 0, minValue: 0, maxValue: 1, automationRate: 'k-rate' }, { name: 'dry', defaultValue: 0.5, minValue: 0.0, maxValue: 1.0, automationRate: 'k-rate' }, { name: 'wet', defaultValue: 0.5, minValue: 0.0, maxValue: 1.0, automationRate: 'k-rate' }, ]; } constructor(options) { super(); const wasmBuffer = options.processorOptions.wasmBuffer; const module = new WebAssembly.Module(options.processorOptions.wasmBuffer); this.wasm = new WebAssembly.Instance(module); const exports = this.wasm.exports; this.freeverb = exports.create(options.processorOptions.sampleRate); this.bufferInLPtr = exports.createBuffer(128); this.bufferInL = new Float32Array(exports.memory.buffer, this.bufferInLPtr, 128); this.bufferInRPtr = exports.createBuffer(128); this.bufferInR = new Float32Array(exports.memory.buffer, this.bufferInRPtr, 128); this.bufferOutLPtr = exports.createBuffer(128); this.bufferOutL = new Float32Array(exports.memory.buffer, this.bufferOutLPtr, 128); this.bufferOutRPtr = exports.createBuffer(128); this.bufferOutR = new Float32Array(exports.memory.buffer, this.bufferOutRPtr, 128); } process(inputs, outputs, parameters) { let freeverb = this.freeverb; if (!freeverb) { console.log("Missing freeverb"); return false; } const input = inputs[0]; const output = outputs[0]; if (input.length == 0 || output.length < 2) { console.log("Missing io (inputs: %i, outputs: %i)", input.length, output.length); return false; } const exports = this.wasm.exports; exports.set_dampening(freeverb, parameters.dampening[0]); exports.set_width(freeverb, parameters.width[0]); exports.set_room_size(freeverb, parameters.roomSize[0]); exports.set_freeze(freeverb, parameters.freeze[0]); exports.set_dry(freeverb, parameters.dry[0]); exports.set_wet(freeverb, parameters.wet[0]); if (input.length == 1) { this.bufferInL.set(input[0]); this.bufferInR.set(input[0]); } else { this.bufferInL.set(input[0]); this.bufferInR.set(input[1]); } exports.process( freeverb, this.bufferInLPtr, this.bufferInRPtr, this.bufferOutLPtr, this.bufferOutRPtr, 128); output[0].set(this.bufferOutL); output[1].set(this.bufferOutR); return true; } } registerProcessor('freeverb-processor', FreeverbProcessor); ================================================ FILE: examples/wasm/public/index.html ================================================ Freeverb
Active






================================================ FILE: examples/wasm/public/index.js ================================================ const audioContext = new AudioContext(); (async () => { const response = await fetch('./freeverb_wasm.wasm'); const wasmBuffer = await response.arrayBuffer(); await audioContext.audioWorklet.addModule('./freeverb-processor.js'); navigator.mediaDevices.getUserMedia({ audio: true, video: false }) .then(function(stream) { const mic = audioContext.createMediaStreamSource(stream); window.freeverb = new AudioWorkletNode(audioContext, "freeverb-processor", { outputChannelCount: [2], processorOptions: { wasmBuffer, sampleRate: audioContext.sampleRate, }, }); mic.connect(window.freeverb); window.freeverb.connect(audioContext.destination); audioContext.suspend(); }) .catch(function(error) { console.log("Failed to initialize audio:", error.name, "-", error.message); }); })(); let playing = false; const play = document.getElementById('play'); play.addEventListener('click', function() { if (playing) { audioContext.suspend(); playing = false; console.log("Suspending"); } else { audioContext.resume(); playing = true; console.log("Resuming"); } }); function setParameter(name, value) { window.freeverb.parameters.get(name).setValueAtTime(value, audioContext.currentTime); } const dampeningSlider = document.getElementById('dampening'); dampeningSlider.addEventListener('input', function() { setParameter('dampening', dampeningSlider.value / 100.0); }); const widthSlider = document.getElementById('width'); widthSlider.addEventListener('input', function() { setParameter('width', widthSlider.value / 100.0); }); const roomSizeSlider = document.getElementById('room-size'); roomSizeSlider.addEventListener('input', function() { setParameter('roomSize', roomSizeSlider.value / 100.0); }); const drySlider = document.getElementById('dry'); drySlider.addEventListener('input', function() { setParameter('dry', drySlider.value / 100.0); }); const wetSlider = document.getElementById('wet'); wetSlider.addEventListener('input', function() { setParameter('wet', wetSlider.value / 100.0); }); const freezeButton = document.getElementById('freeze'); freezeButton.addEventListener('input', function() { setParameter('freeze', freezeButton.checked ? 1 : 0); }); ================================================ FILE: examples/wasm/src/lib.rs ================================================ // Re-export the C API pub use freeverb_clib::*; // Provide a function that allocate #[unsafe(no_mangle)] pub extern "C" fn createBuffer(size: usize) -> *mut f32 { let mut buf = Vec::::with_capacity(size); let ptr = buf.as_mut_ptr(); std::mem::forget(buf); ptr }