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