297ad2a83e
This will make it possible to run background tasks in a type safe way. Sadly, this does mean that every plugin now needs to define the type alias and constructor function since Rust does not yet support defaults for associated types.
174 lines
5.5 KiB
Rust
174 lines
5.5 KiB
Rust
use atomic_float::AtomicF32;
|
|
use nih_plug::prelude::*;
|
|
use nih_plug_vizia::ViziaState;
|
|
use std::sync::Arc;
|
|
|
|
mod editor;
|
|
|
|
/// The time it takes for the peak meter to decay by 12 dB after switching to complete silence.
|
|
const PEAK_METER_DECAY_MS: f64 = 150.0;
|
|
|
|
/// This is mostly identical to the gain example, minus some fluff, and with a GUI.
|
|
pub struct Gain {
|
|
params: Arc<GainParams>,
|
|
|
|
/// 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<AtomicF32>,
|
|
}
|
|
|
|
#[derive(Params)]
|
|
struct GainParams {
|
|
/// The editor state, saved together with the parameter state so the custom scaling can be
|
|
/// restored.
|
|
#[persist = "editor-state"]
|
|
editor_state: Arc<ViziaState>,
|
|
|
|
#[id = "gain"]
|
|
pub gain: FloatParam,
|
|
}
|
|
|
|
impl Default for Gain {
|
|
fn default() -> Self {
|
|
Self {
|
|
params: Arc::new(GainParams::default()),
|
|
|
|
peak_meter_decay_weight: 1.0,
|
|
peak_meter: Arc::new(AtomicF32::new(util::MINUS_INFINITY_DB)),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Default for GainParams {
|
|
fn default() -> Self {
|
|
Self {
|
|
editor_state: editor::default_state(),
|
|
|
|
gain: FloatParam::new(
|
|
"Gain",
|
|
util::db_to_gain(0.0),
|
|
FloatRange::Skewed {
|
|
min: util::db_to_gain(-30.0),
|
|
max: util::db_to_gain(30.0),
|
|
factor: FloatRange::gain_skew_factor(-30.0, 30.0),
|
|
},
|
|
)
|
|
.with_smoother(SmoothingStyle::Logarithmic(50.0))
|
|
.with_unit(" dB")
|
|
.with_value_to_string(formatters::v2s_f32_gain_to_db(2))
|
|
.with_string_to_value(formatters::s2v_f32_gain_to_db()),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Plugin for Gain {
|
|
const NAME: &'static str = "Gain GUI (VIZIA)";
|
|
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_INPUT_CHANNELS: u32 = 2;
|
|
const DEFAULT_OUTPUT_CHANNELS: u32 = 2;
|
|
|
|
const SAMPLE_ACCURATE_AUTOMATION: bool = true;
|
|
|
|
type AsyncExecutor = ();
|
|
fn async_executor(&self) -> Self::AsyncExecutor {}
|
|
|
|
fn params(&self) -> Arc<dyn Params> {
|
|
self.params.clone()
|
|
}
|
|
|
|
fn editor(&self) -> Option<Box<dyn Editor>> {
|
|
editor::create(
|
|
self.params.clone(),
|
|
self.peak_meter.clone(),
|
|
self.params.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 InitContext,
|
|
) -> bool {
|
|
// After `PEAK_METER_DECAY_MS` milliseconds of pure silence, the peak meter's value should
|
|
// have dropped by 12 dB
|
|
self.peak_meter_decay_weight = 0.25f64
|
|
.powf((buffer_config.sample_rate as f64 * PEAK_METER_DECAY_MS / 1000.0).recip())
|
|
as f32;
|
|
|
|
true
|
|
}
|
|
|
|
fn process(
|
|
&mut self,
|
|
buffer: &mut Buffer,
|
|
_aux: &mut AuxiliaryBuffers,
|
|
_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 *= 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.params.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-vizia";
|
|
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::Mono,
|
|
ClapFeature::Utility,
|
|
];
|
|
}
|
|
|
|
impl Vst3Plugin for Gain {
|
|
const VST3_CLASS_ID: [u8; 16] = *b"GainGuiVIIIZIAAA";
|
|
const VST3_CATEGORIES: &'static str = "Fx|Dynamics";
|
|
}
|
|
|
|
nih_export_clap!(Gain);
|
|
nih_export_vst3!(Gain);
|