use atomic_float::AtomicF32; use nih_plug::prelude::*; use nih_plug_iced::IcedState; use std::pin::Pin; use std::sync::Arc; mod editor; /// This is mostly identical to the gain example, minus some fluff, and with a GUI. struct Gain { params: Pin>, editor_state: Arc, /// Needed to normalize the peak meter's response based on the sample rate. peak_meter_decay_weight: f32, /// The current data for the peak meter. This is stored as an [`Arc`] so we can share it between /// the GUI and the audio processing parts. If you have more state to share, then it's a good /// idea to put all of that in a struct behind a single `Arc`. /// /// This is stored as voltage gain. peak_meter: Arc, } #[derive(Params)] struct GainParams { #[id = "gain"] pub gain: FloatParam, } impl Default for Gain { fn default() -> Self { Self { params: Arc::pin(GainParams::default()), editor_state: editor::default_state(), peak_meter_decay_weight: 1.0, peak_meter: Arc::new(AtomicF32::new(util::MINUS_INFINITY_DB)), } } } impl Default for GainParams { fn default() -> Self { Self { gain: FloatParam::new( "Gain", 0.0, FloatRange::Linear { min: -30.0, max: 30.0, }, ) .with_smoother(SmoothingStyle::Linear(50.0)) .with_step_size(0.01) .with_unit(" dB"), } } } impl Plugin for Gain { const NAME: &'static str = "Gain GUI (iced)"; const VENDOR: &'static str = "Moist Plugins GmbH"; const URL: &'static str = "https://youtu.be/dQw4w9WgXcQ"; const EMAIL: &'static str = "info@example.com"; const VERSION: &'static str = "0.0.1"; const DEFAULT_NUM_INPUTS: u32 = 2; const DEFAULT_NUM_OUTPUTS: u32 = 2; const ACCEPTS_MIDI: bool = false; const SAMPLE_ACCURATE_AUTOMATION: bool = true; fn params(&self) -> Pin<&dyn Params> { self.params.as_ref() } fn editor(&self) -> Option> { editor::create( self.params.clone(), self.peak_meter.clone(), self.editor_state.clone(), ) } fn accepts_bus_config(&self, config: &BusConfig) -> bool { // This works with any symmetrical IO layout config.num_input_channels == config.num_output_channels && config.num_input_channels > 0 } fn initialize( &mut self, _bus_config: &BusConfig, buffer_config: &BufferConfig, _context: &mut impl ProcessContext, ) -> bool { // TODO: How do you tie this exponential decay to an actual time span? self.peak_meter_decay_weight = 0.9992f32.powf(44_100.0 / buffer_config.sample_rate); true } fn process( &mut self, buffer: &mut Buffer, _context: &mut impl ProcessContext, ) -> ProcessStatus { for channel_samples in buffer.iter_samples() { let mut amplitude = 0.0; let num_samples = channel_samples.len(); let gain = self.params.gain.smoothed.next(); for sample in channel_samples { *sample *= util::db_to_gain(gain); amplitude += *sample; } // To save resources, a plugin can (and probably should!) only perform expensive // calculations that are only displayed on the GUI while the GUI is open if self.editor_state.is_open() { amplitude = (amplitude / num_samples as f32).abs(); let current_peak_meter = self.peak_meter.load(std::sync::atomic::Ordering::Relaxed); let new_peak_meter = if amplitude > current_peak_meter { amplitude } else { current_peak_meter * self.peak_meter_decay_weight + amplitude * (1.0 - self.peak_meter_decay_weight) }; self.peak_meter .store(new_peak_meter, std::sync::atomic::Ordering::Relaxed) } } ProcessStatus::Normal } } impl ClapPlugin for Gain { const CLAP_ID: &'static str = "com.moist-plugins-gmbh.gain-gui-iced"; const CLAP_DESCRIPTION: &'static str = "A smoothed gain parameter example plugin"; const CLAP_FEATURES: &'static [&'static str] = &["audio_effect", "mono", "stereo", "tool"]; const CLAP_MANUAL_URL: &'static str = Self::URL; const CLAP_SUPPORT_URL: &'static str = Self::URL; } impl Vst3Plugin for Gain { const VST3_CLASS_ID: [u8; 16] = *b"GainGuiIcedAaAAa"; const VST3_CATEGORIES: &'static str = "Fx|Dynamics"; } nih_export_clap!(Gain); nih_export_vst3!(Gain);