232 lines
8.3 KiB
Rust
232 lines
8.3 KiB
Rust
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<PluginParams>,
|
|
eqs: Vec<Equalizer>,
|
|
compressors: Vec<Compressor>,
|
|
splitters: Vec<FreqSplitter>,
|
|
}
|
|
|
|
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<dyn Params> {
|
|
self.params.clone()
|
|
}
|
|
|
|
fn editor(&mut self, _async_executor: AsyncExecutor<Self>) -> Option<Box<dyn Editor>> {
|
|
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<Self>,
|
|
) -> 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<Self>,
|
|
) -> 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);
|