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) {} } #[cfg(not(feature = "vst3"))] 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, ]; } #[cfg(feature = "vst3")] impl Vst3Plugin for AirFreshener { const VST3_CLASS_ID: [u8; 16] = *b"AirFreshener____"; const VST3_SUBCATEGORIES: &'static [Vst3SubCategory] = &[Vst3SubCategory::Fx, Vst3SubCategory::Tools]; } #[cfg(not(feature = "vst3"))] nih_export_clap!(AirFreshener); #[cfg(feature = "vst3")] nih_export_vst3!(AirFreshener);