diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..a9d08b7 --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,6 @@ +[unstable] +unstable-options = true +bindeps = true + +[build] +artifact-dir = "out" \ No newline at end of file diff --git a/.gitignore b/.gitignore index ea8c4bf..064bd8a 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ /target +clap-host \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..bf9cf1f --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,27 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in library 'dsp'", + "cargo": { + "args": [ + "test", + "--no-run", + "--lib", + "--package=ebu-dsp" + ], + "filter": { + "name": "dsp", + "kind": "lib" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + } + ] +} \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 16bf05d..c757355 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,20 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" +[[package]] +name = "airfreshener" +version = "0.1.0" +dependencies = [ + "baseview", + "crossbeam", + "dsp", + "femtovg", + "image", + "nih_plug", + "parking_lot", + "serde", +] + [[package]] name = "anyhow" version = "1.0.100" @@ -321,7 +335,7 @@ dependencies = [ ] [[package]] -name = "ebu-dsp" +name = "dsp" version = "0.1.0" dependencies = [ "femtovg", @@ -330,17 +344,11 @@ dependencies = [ ] [[package]] -name = "ebu-plug-core" +name = "ebu-plugs" version = "0.1.0" dependencies = [ - "baseview", - "crossbeam", - "ebu-dsp", - "femtovg", - "image", - "nih_plug", - "parking_lot", - "serde", + "airfreshener", + "helpers", ] [[package]] @@ -469,6 +477,10 @@ dependencies = [ "web-sys", ] +[[package]] +name = "helpers" +version = "0.1.0" + [[package]] name = "hermit-abi" version = "0.1.19" diff --git a/Cargo.toml b/Cargo.toml index cef4c9d..4051255 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "ebu-plug-core" +name = "ebu-plugs" version = "0.1.0" edition = "2024" @@ -7,14 +7,10 @@ edition = "2024" crate-type = ["cdylib"] [dependencies] -ebu-dsp = { version = "0.1.0", path = "ebu-dsp" } -nih_plug = { git = "https://github.com/robbert-vdh/nih-plug", version = "0.0.0", default-features = false } -parking_lot = "0.12.5" -baseview = { git = "https://github.com/RustAudio/baseview", rev = "237d323c729f3aa99476ba3efa50129c5e86cad3", features = ["opengl"]} -crossbeam = "0.8.4" -femtovg = "0.19.3" -serde = { version = "1.0.228", features = ["derive"] } -image = { version = "0.25.9", default-features = false, features = ["png"] } +airfreshener = { path = "plug-airfreshener", lib = true, artifact = "cdylib" } + +[build-dependencies] +helpers = { path = "helpers" } [workspace] -members = ["ebu-dsp"] +members = ["dsp", "helpers", "plug-airfreshener"] diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..7f24ae0 --- /dev/null +++ b/build.rs @@ -0,0 +1,3 @@ +fn main() { + //helpers::copy_artifact_to_out_dir("airfreshener", "AirFreshener"); +} diff --git a/ebu-dsp/Cargo.toml b/dsp/Cargo.toml similarity index 93% rename from ebu-dsp/Cargo.toml rename to dsp/Cargo.toml index ded46df..65d9ac8 100644 --- a/ebu-dsp/Cargo.toml +++ b/dsp/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "ebu-dsp" +name = "dsp" version = "0.1.0" edition = "2024" diff --git a/ebu-dsp/src/amplitude.rs b/dsp/src/amplitude.rs similarity index 100% rename from ebu-dsp/src/amplitude.rs rename to dsp/src/amplitude.rs diff --git a/ebu-dsp/src/biquad.rs b/dsp/src/biquad.rs similarity index 100% rename from ebu-dsp/src/biquad.rs rename to dsp/src/biquad.rs diff --git a/ebu-dsp/src/comp.rs b/dsp/src/comp.rs similarity index 100% rename from ebu-dsp/src/comp.rs rename to dsp/src/comp.rs diff --git a/ebu-dsp/src/decibel.rs b/dsp/src/decibel.rs similarity index 100% rename from ebu-dsp/src/decibel.rs rename to dsp/src/decibel.rs diff --git a/ebu-dsp/src/eq.rs b/dsp/src/eq.rs similarity index 100% rename from ebu-dsp/src/eq.rs rename to dsp/src/eq.rs diff --git a/ebu-dsp/src/freq_split.rs b/dsp/src/freq_split.rs similarity index 100% rename from ebu-dsp/src/freq_split.rs rename to dsp/src/freq_split.rs diff --git a/ebu-dsp/src/gui/mod.rs b/dsp/src/gui/mod.rs similarity index 99% rename from ebu-dsp/src/gui/mod.rs rename to dsp/src/gui/mod.rs index e0a5a67..eee6035 100644 --- a/ebu-dsp/src/gui/mod.rs +++ b/dsp/src/gui/mod.rs @@ -33,7 +33,6 @@ impl SpriteSheet { scale_factor: Arc, frame_width: usize, frame_height: usize, - frames: usize, ) -> Self { let image = canvas .load_image_mem(data, ImageFlags::empty()) diff --git a/ebu-dsp/src/lib.rs b/dsp/src/lib.rs similarity index 100% rename from ebu-dsp/src/lib.rs rename to dsp/src/lib.rs diff --git a/ebu-dsp/src/meter.rs b/dsp/src/meter.rs similarity index 100% rename from ebu-dsp/src/meter.rs rename to dsp/src/meter.rs diff --git a/ebu-dsp/src/ring_buffer.rs b/dsp/src/ring_buffer.rs similarity index 100% rename from ebu-dsp/src/ring_buffer.rs rename to dsp/src/ring_buffer.rs diff --git a/ebu-dsp/src/smoother.rs b/dsp/src/smoother.rs similarity index 100% rename from ebu-dsp/src/smoother.rs rename to dsp/src/smoother.rs diff --git a/ebu-dsp/src/traits.rs b/dsp/src/traits.rs similarity index 100% rename from ebu-dsp/src/traits.rs rename to dsp/src/traits.rs diff --git a/helpers/Cargo.toml b/helpers/Cargo.toml new file mode 100644 index 0000000..cb9df2c --- /dev/null +++ b/helpers/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "helpers" +version = "0.1.0" +edition = "2024" + +[dependencies] diff --git a/helpers/src/lib.rs b/helpers/src/lib.rs new file mode 100644 index 0000000..65f01aa --- /dev/null +++ b/helpers/src/lib.rs @@ -0,0 +1,40 @@ +use std::{ + env, fs, + path::{Path, PathBuf}, +}; + +#[cfg(debug_assertions)] +const CONFIG: &str = "debug"; +#[cfg(not(debug_assertions))] +const CONFIG: &str = "release"; + +#[cfg(target_os = "macos")] +const OS_LIB_EXT: &str = "dylib"; +#[cfg(target_os = "windows")] +const OS_LIB_EXT: &str = "dll"; +#[cfg(target_os = "linux")] +const OS_LIB_EXT: &str = "so"; + +#[cfg(target_os = "macos")] +const OS_NAME: &str = "macOS"; +#[cfg(target_os = "windows")] +const OS_NAME: &str = "Windows"; +#[cfg(target_os = "linux")] +const OS_NAME: &str = "Linux"; + +pub fn built_lib_path(lib_name: &str) -> PathBuf { + Path::new(&env::var("CARGO_MANIFEST_DIR").unwrap_or("".to_owned())) + .join(format!("target/{CONFIG}/lib{lib_name}.{OS_LIB_EXT}")) +} + +pub fn copy_artifact_to_out_dir(lib_name: &str, pretty_name: &str) { + let src_path = built_lib_path(lib_name); + + let dst_path = Path::new(&env::var("CARGO_MANIFEST_DIR").unwrap_or("".to_owned())).join( + format!("target/build/{CONFIG}/{pretty_name}_{OS_NAME}.clap"), + ); + + fs::create_dir_all(dst_path.parent().unwrap()) + .expect("Failed to create artifact output directory"); + fs::copy(src_path, dst_path).expect("Failed to move artifact"); +} diff --git a/plug-airfreshener/Cargo.toml b/plug-airfreshener/Cargo.toml new file mode 100644 index 0000000..20fc442 --- /dev/null +++ b/plug-airfreshener/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "airfreshener" +version = "0.1.0" +edition = "2024" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +dsp = { version = "0.1.0", path = "../dsp" } +nih_plug = { git = "https://github.com/robbert-vdh/nih-plug", version = "0.0.0", default-features = false } +parking_lot = "0.12.5" +baseview = { git = "https://github.com/RustAudio/baseview", rev = "237d323c729f3aa99476ba3efa50129c5e86cad3", features = ["opengl"]} +crossbeam = "0.8.4" +femtovg = "0.19.3" +serde = { version = "1.0.228", features = ["derive"] } +image = { version = "0.25.9", default-features = false, features = ["png"] } \ No newline at end of file diff --git a/src/editor.rs b/plug-airfreshener/src/editor.rs similarity index 100% rename from src/editor.rs rename to plug-airfreshener/src/editor.rs diff --git a/src/gui.rs b/plug-airfreshener/src/gui.rs similarity index 91% rename from src/gui.rs rename to plug-airfreshener/src/gui.rs index eac355d..4374e6f 100644 --- a/src/gui.rs +++ b/plug-airfreshener/src/gui.rs @@ -2,16 +2,16 @@ use crate::parameters::PluginParams; use baseview::{ Event, EventStatus, MouseButton, MouseEvent, WindowEvent, WindowHandler, gl::GlContext, }; -use ebu_dsp::{ScaledPoint, ScaledRect, Sprite, SpriteSheet}; +use dsp::{ScaledPoint, ScaledRect, Sprite, SpriteSheet}; use femtovg::{Canvas, Color, FontId, Paint, Path, renderer::OpenGl}; use nih_plug::prelude::*; use std::sync::{Arc, atomic::Ordering}; -const DROID_SANS_FONT: &'static [u8] = include_bytes!("../assets/DroidSans.ttf"); +const DROID_SANS_FONT: &'static [u8] = include_bytes!("../../assets/DroidSans.ttf"); -const FRESHENER_IMAGE: &'static [u8] = include_bytes!("../assets/AirFreshener/sheet.png"); -const NOT_SO_FRESH_BG_IMAGE: &'static [u8] = include_bytes!("../assets/AirFreshener/bg0.png"); -const FRESH_DUMBLEDORE_BG_IMAGE: &'static [u8] = include_bytes!("../assets/AirFreshener/bg1.png"); +const FRESHENER_IMAGE: &'static [u8] = include_bytes!("../../assets/AirFreshener/sheet.png"); +const NOT_SO_FRESH_BG_IMAGE: &'static [u8] = include_bytes!("../../assets/AirFreshener/bg0.png"); +const FRESH_DUMBLEDORE_BG_IMAGE: &'static [u8] = include_bytes!("../../assets/AirFreshener/bg1.png"); const FRESHENER_FRAMES: usize = 256; const FRESHENER_FRAME_WIDTH: usize = 73; const FRESHENER_FRAME_HEIGHT: usize = 144; @@ -88,7 +88,6 @@ impl PluginGui { scaling_factor.clone(), FRESHENER_FRAME_WIDTH, FRESHENER_FRAME_HEIGHT, - FRESHENER_FRAMES, ); this.freshener_image_bounds = this .freshener_image @@ -100,6 +99,13 @@ impl PluginGui { ); this.not_so_fresh_image = Sprite::new(&mut canvas, NOT_SO_FRESH_BG_IMAGE, scaling_factor.clone()); + + let canvas_width = canvas.width(); + let editor_width = params.editor_state.size.load().0; + if canvas_width != editor_width { + this.scaling_factor + .store(canvas_width as f32 / editor_width as f32, Ordering::Release); + } this.canvas = Some(canvas); } unsafe { @@ -164,8 +170,10 @@ impl WindowHandler for PluginGui { let freshness = self.params.freshness.unmodulated_normalized_value(); let frame_index = (freshness * (FRESHENER_FRAMES - 1) as f32).floor() as usize; self.not_so_fresh_image.draw(canvas, 0.0, 0.0, 1.0); - self.fresh_dumbledore_image.draw(canvas, 0.0, 0.0, freshness); - self.freshener_image.draw(canvas, FRESHENER_SCREEN_X, FRESHENER_SCREEN_Y, frame_index); + self.fresh_dumbledore_image + .draw(canvas, 0.0, 0.0, freshness); + self.freshener_image + .draw(canvas, FRESHENER_SCREEN_X, FRESHENER_SCREEN_Y, frame_index); #[cfg(debug_assertions)] { diff --git a/plug-airfreshener/src/lib.rs b/plug-airfreshener/src/lib.rs new file mode 100644 index 0000000..42cd164 --- /dev/null +++ b/plug-airfreshener/src/lib.rs @@ -0,0 +1,218 @@ +mod editor; +mod gui; +mod parameters; +mod window; + +use dsp::{ + BiquadFilter, BiquadFilterState, Compressor, CompressorState, Decibel, Equalizer, + EqualizerState, FilterMode, FreqSplitter, FreqSplitterState, Lerp, Processor, Ratio, + SampleRate, +}; +use nih_plug::prelude::*; +use std::{num::NonZero, sync::Arc}; + +use crate::{parameters::PluginParams, window::EditorWindow}; + +struct AirFreshener { + params: Arc, + eqs: Vec, + compressors: Vec, + splitters: Vec, +} + +impl Default for AirFreshener { + fn default() -> Self { + Self { + params: Arc::new(PluginParams::default()), + eqs: Vec::new(), + compressors: Vec::new(), + splitters: Vec::new(), + } + } +} + +impl Plugin for AirFreshener { + const NAME: &'static str = "Air Freshener"; + const VENDOR: &'static str = "Simon Elberich"; + // You can use `env!("CARGO_PKG_HOMEPAGE")` to reference the homepage field from the + // `Cargo.toml` file here + const URL: &'static str = "https://youtu.be/dQw4w9WgXcQ"; + const EMAIL: &'static str = "info@example.com"; + + const VERSION: &'static str = env!("CARGO_PKG_VERSION"); + + // The first audio IO layout is used as the default. The other layouts may be selected either + // explicitly or automatically by the host or the user depending on the plugin API/backend. + const AUDIO_IO_LAYOUTS: &'static [AudioIOLayout] = &[ + AudioIOLayout { + main_input_channels: NonZeroU32::new(2), + main_output_channels: NonZeroU32::new(2), + + aux_input_ports: &[], + aux_output_ports: &[], + + // Individual ports and the layout as a whole can be named here. By default these names + // are generated as needed. This layout will be called 'Stereo', while the other one is + // given the name 'Mono' based no the number of input and output channels. + names: PortNames::const_default(), + }, + AudioIOLayout { + main_input_channels: NonZeroU32::new(1), + main_output_channels: NonZeroU32::new(1), + ..AudioIOLayout::const_default() + }, + ]; + + const MIDI_INPUT: MidiConfig = MidiConfig::None; + // Setting this to `true` will tell the wrapper to split the buffer up into smaller blocks + // whenever there are inter-buffer parameter changes. This way no changes to the plugin are + // required to support sample accurate automation and the wrapper handles all of the boring + // stuff like making sure transport and other timing information stays consistent between the + // splits. + const SAMPLE_ACCURATE_AUTOMATION: bool = true; + + // If the plugin can send or receive SysEx messages, it can define a type to wrap around those + // messages here. The type implements the `SysExMessage` trait, which allows conversion to and + // from plain byte buffers. + type SysExMessage = (); + // More advanced plugins can use this to run expensive background tasks. See the field's + // documentation for more information. `()` means that the plugin does not have any background + // tasks. + type BackgroundTask = (); + + fn params(&self) -> Arc { + self.params.clone() + } + + fn editor(&mut self, _async_executor: AsyncExecutor) -> Option> { + Some(Box::new(EditorWindow::new(self.params.clone()))) + } + + // This plugin doesn't need any special initialization, but if you need to do anything expensive + // then this would be the place. State is kept around when the host reconfigures the + // plugin. If we do need special initialization, we could implement the `initialize()` and/or + // `reset()` methods + + fn initialize( + &mut self, + audio_io_layout: &AudioIOLayout, + buffer_config: &BufferConfig, + _context: &mut impl InitContext, + ) -> bool { + //dsp::init(); + + let sample_rate = SampleRate(buffer_config.sample_rate as f64); + for _ in 0..audio_io_layout + .main_input_channels + .unwrap_or(NonZero::new(2).unwrap()) + .get() + { + self.eqs.push(Equalizer::from_state(EqualizerState { + nodes: [ + Some(BiquadFilter::from_state( + sample_rate, + BiquadFilterState { + gain_db: Decibel::from(8.0), + cutoff: 1900.0, + q: 0.5, + mode: FilterMode::HighShelf, + ..Default::default() + }, + )), + Some(BiquadFilter::from_state( + sample_rate, + BiquadFilterState { + gain_db: Decibel::from(-3.0), + cutoff: 2600.0, + mode: FilterMode::Peak, + ..Default::default() + }, + )), + None, + None, + None, + None, + ], + })); + + self.compressors.push(Compressor::from_state( + sample_rate, + CompressorState { + threshold: Decibel::from(-30.0), + ratio: Ratio(1.0, 0.9), + ..Default::default() + }, + )); + + self.splitters.push(FreqSplitter::from_state( + sample_rate, + FreqSplitterState { + split_frequency: 3050.0, + ..Default::default() + }, + )); + } + + true + } + + fn process( + &mut self, + buffer: &mut Buffer, + _aux: &mut AuxiliaryBuffers, + _context: &mut impl ProcessContext, + ) -> ProcessStatus { + _context.set_latency_samples(6); + for mut channel_samples in buffer.iter_samples() { + // Smoothing is optionally built into the parameters themselves + let freshness = self.params.freshness.smoothed.next() as f64; + + /*let frequency = self.params.frequency.smoothed.next() as f64; + let ratio = self.params.ratio.smoothed.next() as f64; + let threshold = self.params.threshold.smoothed.next() as f64; + + for compressor in &mut self.compressors { + compressor.set_ratio(Ratio(1.0, ratio)); + compressor.set_threshold(Decibel::from(threshold)); + } + for splitter in &mut self.splitters { + splitter.state.split_frequency = frequency; + } + for filter in &mut self.filters { + filter.state.cutoff = frequency; + } + */ + + let fresh_lerp_of_bel_air = freshness as f32; + for (channel, sample) in channel_samples.iter_mut().enumerate() { + *sample = sample.lerp(self.eqs[channel].process(*sample), fresh_lerp_of_bel_air); + let (low, high) = self.splitters[channel].process(*sample); + *sample = low + + high.lerp( + self.compressors[channel].process(high), + fresh_lerp_of_bel_air, + ); + } + } + + ProcessStatus::Normal + } + + // This can be used for cleaning up special resources like socket connections whenever the + // plugin is deactivated. Most plugins won't need to do anything here. + fn deactivate(&mut self) {} +} + +impl ClapPlugin for AirFreshener { + const CLAP_ID: &'static str = "com.moist-plugins-gmbh.gain"; + const CLAP_DESCRIPTION: Option<&'static str> = Some("A smoothed gain parameter example plugin"); + const CLAP_MANUAL_URL: Option<&'static str> = Some(Self::URL); + const CLAP_SUPPORT_URL: Option<&'static str> = None; + const CLAP_FEATURES: &'static [ClapFeature] = &[ + ClapFeature::AudioEffect, + ClapFeature::Stereo, + ClapFeature::Utility, + ]; +} + +nih_export_clap!(AirFreshener); diff --git a/src/parameters.rs b/plug-airfreshener/src/parameters.rs similarity index 100% rename from src/parameters.rs rename to plug-airfreshener/src/parameters.rs diff --git a/src/window.rs b/plug-airfreshener/src/window.rs similarity index 100% rename from src/window.rs rename to plug-airfreshener/src/window.rs diff --git a/rust-toolchain b/rust-toolchain new file mode 100644 index 0000000..07ade69 --- /dev/null +++ b/rust-toolchain @@ -0,0 +1 @@ +nightly \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index ea0077d..e69de29 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,218 +0,0 @@ -mod editor; -mod gui; -mod parameters; -mod window; - -use ebu_dsp::{ - BiquadFilter, BiquadFilterState, Compressor, CompressorState, Decibel, Equalizer, - EqualizerState, FilterMode, FreqSplitter, FreqSplitterState, Lerp, Processor, Ratio, - SampleRate, -}; -use nih_plug::prelude::*; -use std::{num::NonZero, sync::Arc}; - -use crate::{parameters::PluginParams, window::EditorWindow}; - -struct AirFreshener { - params: Arc, - eqs: Vec, - compressors: Vec, - splitters: Vec, -} - -impl Default for AirFreshener { - fn default() -> Self { - Self { - params: Arc::new(PluginParams::default()), - eqs: Vec::new(), - compressors: Vec::new(), - splitters: Vec::new(), - } - } -} - -impl Plugin for AirFreshener { - const NAME: &'static str = "Air Freshener"; - const VENDOR: &'static str = "Simon Elberich"; - // You can use `env!("CARGO_PKG_HOMEPAGE")` to reference the homepage field from the - // `Cargo.toml` file here - const URL: &'static str = "https://youtu.be/dQw4w9WgXcQ"; - const EMAIL: &'static str = "info@example.com"; - - const VERSION: &'static str = env!("CARGO_PKG_VERSION"); - - // The first audio IO layout is used as the default. The other layouts may be selected either - // explicitly or automatically by the host or the user depending on the plugin API/backend. - const AUDIO_IO_LAYOUTS: &'static [AudioIOLayout] = &[ - AudioIOLayout { - main_input_channels: NonZeroU32::new(2), - main_output_channels: NonZeroU32::new(2), - - aux_input_ports: &[], - aux_output_ports: &[], - - // Individual ports and the layout as a whole can be named here. By default these names - // are generated as needed. This layout will be called 'Stereo', while the other one is - // given the name 'Mono' based no the number of input and output channels. - names: PortNames::const_default(), - }, - AudioIOLayout { - main_input_channels: NonZeroU32::new(1), - main_output_channels: NonZeroU32::new(1), - ..AudioIOLayout::const_default() - }, - ]; - - const MIDI_INPUT: MidiConfig = MidiConfig::None; - // Setting this to `true` will tell the wrapper to split the buffer up into smaller blocks - // whenever there are inter-buffer parameter changes. This way no changes to the plugin are - // required to support sample accurate automation and the wrapper handles all of the boring - // stuff like making sure transport and other timing information stays consistent between the - // splits. - const SAMPLE_ACCURATE_AUTOMATION: bool = true; - - // If the plugin can send or receive SysEx messages, it can define a type to wrap around those - // messages here. The type implements the `SysExMessage` trait, which allows conversion to and - // from plain byte buffers. - type SysExMessage = (); - // More advanced plugins can use this to run expensive background tasks. See the field's - // documentation for more information. `()` means that the plugin does not have any background - // tasks. - type BackgroundTask = (); - - fn params(&self) -> Arc { - self.params.clone() - } - - fn editor(&mut self, _async_executor: AsyncExecutor) -> Option> { - Some(Box::new(EditorWindow::new(self.params.clone()))) - } - - // This plugin doesn't need any special initialization, but if you need to do anything expensive - // then this would be the place. State is kept around when the host reconfigures the - // plugin. If we do need special initialization, we could implement the `initialize()` and/or - // `reset()` methods - - fn initialize( - &mut self, - audio_io_layout: &AudioIOLayout, - buffer_config: &BufferConfig, - _context: &mut impl InitContext, - ) -> bool { - //ebu_dsp::init(); - - let sample_rate = SampleRate(buffer_config.sample_rate as f64); - for _ in 0..audio_io_layout - .main_input_channels - .unwrap_or(NonZero::new(2).unwrap()) - .get() - { - self.eqs.push(Equalizer::from_state(EqualizerState { - nodes: [ - Some(BiquadFilter::from_state( - sample_rate, - BiquadFilterState { - gain_db: Decibel::from(8.0), - cutoff: 1900.0, - q: 0.5, - mode: FilterMode::HighShelf, - ..Default::default() - }, - )), - Some(BiquadFilter::from_state( - sample_rate, - BiquadFilterState { - gain_db: Decibel::from(-3.0), - cutoff: 2600.0, - mode: FilterMode::Peak, - ..Default::default() - }, - )), - None, - None, - None, - None, - ], - })); - - self.compressors.push(Compressor::from_state( - sample_rate, - CompressorState { - threshold: Decibel::from(-30.0), - ratio: Ratio(1.0, 0.9), - ..Default::default() - }, - )); - - self.splitters.push(FreqSplitter::from_state( - sample_rate, - FreqSplitterState { - split_frequency: 3050.0, - ..Default::default() - }, - )); - } - - true - } - - fn process( - &mut self, - buffer: &mut Buffer, - _aux: &mut AuxiliaryBuffers, - _context: &mut impl ProcessContext, - ) -> ProcessStatus { - _context.set_latency_samples(6); - for mut channel_samples in buffer.iter_samples() { - // Smoothing is optionally built into the parameters themselves - let freshness = self.params.freshness.smoothed.next() as f64; - - /*let frequency = self.params.frequency.smoothed.next() as f64; - let ratio = self.params.ratio.smoothed.next() as f64; - let threshold = self.params.threshold.smoothed.next() as f64; - - for compressor in &mut self.compressors { - compressor.set_ratio(Ratio(1.0, ratio)); - compressor.set_threshold(Decibel::from(threshold)); - } - for splitter in &mut self.splitters { - splitter.state.split_frequency = frequency; - } - for filter in &mut self.filters { - filter.state.cutoff = frequency; - } - */ - - let fresh_lerp_of_bel_air = freshness as f32; - for (channel, sample) in channel_samples.iter_mut().enumerate() { - *sample = sample.lerp(self.eqs[channel].process(*sample), fresh_lerp_of_bel_air); - let (low, high) = self.splitters[channel].process(*sample); - *sample = low - + high.lerp( - self.compressors[channel].process(high), - fresh_lerp_of_bel_air, - ); - } - } - - ProcessStatus::Normal - } - - // This can be used for cleaning up special resources like socket connections whenever the - // plugin is deactivated. Most plugins won't need to do anything here. - fn deactivate(&mut self) {} -} - -impl ClapPlugin for AirFreshener { - const CLAP_ID: &'static str = "com.moist-plugins-gmbh.gain"; - const CLAP_DESCRIPTION: Option<&'static str> = Some("A smoothed gain parameter example plugin"); - const CLAP_MANUAL_URL: Option<&'static str> = Some(Self::URL); - const CLAP_SUPPORT_URL: Option<&'static str> = None; - const CLAP_FEATURES: &'static [ClapFeature] = &[ - ClapFeature::AudioEffect, - ClapFeature::Stereo, - ClapFeature::Utility, - ]; -} - -nih_export_clap!(AirFreshener);