gain/
gain.rs

1use std::{
2    io::Write,
3    sync::{
4        Arc,
5        atomic::{AtomicU64, Ordering},
6    },
7};
8
9use clap_clap::prelude as clap;
10
11// A plugin must implement `Default` trait.  The plugin instance will be created
12// by the host with the call to `Gain::default()`.
13struct Gain {
14    gain: Arc<AtomicU64>,
15}
16
17impl Default for Gain {
18    fn default() -> Self {
19        Self {
20            gain: Arc::new(AtomicU64::new(1.0f64.to_bits())),
21        }
22    }
23}
24
25impl clap::Extensions<Self> for Gain {
26    fn audio_ports() -> Option<impl clap::AudioPorts<Self>> {
27        Some(clap::StereoPorts::<1, 1>)
28    }
29
30    fn params() -> Option<impl clap::Params<Self>> {
31        Some(GainParam)
32    }
33}
34
35struct GainParam;
36
37impl clap::Params<Gain> for GainParam {
38    fn count(_: &Gain) -> u32 {
39        1
40    }
41
42    fn get_info(_: &Gain, param_index: u32) -> Option<clap::ParamInfo> {
43        (param_index == 0).then(|| clap::ParamInfo {
44            id: 0.into(),
45            flags: clap::params::InfoFlags::RequiresProcess as u32
46                | clap::params::InfoFlags::Automatable as u32,
47            name: "Gain".to_string(),
48            module: "gain".to_string(),
49            min_value: 0.0,
50            max_value: 2.0,
51            default_value: 1.0,
52        })
53    }
54
55    fn get_value(plugin: &Gain, param_id: clap::ClapId) -> Option<f64> {
56        (param_id == 0.into()).then(|| f64::from_bits(plugin.gain.load(Ordering::Relaxed)))
57    }
58
59    fn value_to_text(
60        _: &Gain,
61        _: clap::ClapId,
62        value: f64,
63        mut out_buf: &mut [u8],
64    ) -> Result<(), clap::Error> {
65        Ok(write!(out_buf, "{value:.2}")?)
66    }
67
68    fn text_to_value(
69        _: &Gain,
70        _: clap::ClapId,
71        param_value_text: &str,
72    ) -> Result<f64, clap::Error> {
73        Ok(param_value_text.parse()?)
74    }
75
76    fn flush_inactive(_: &Gain, _: &clap::InputEvents, _: &clap::OutputEvents) {}
77
78    fn flush(
79        _: &<Gain as clap::Plugin>::AudioThread,
80        _: &clap::InputEvents,
81        _: &clap::OutputEvents,
82    ) {
83    }
84}
85
86impl clap::Plugin for Gain {
87    type AudioThread = AudioThread;
88
89    const ID: &'static str = "com.your-company.YourPlugin";
90    const NAME: &'static str = "Plugin Name";
91    const VENDOR: &'static str = "Vendor";
92    const URL: &'static str = "https://your-domain.com/your-plugin";
93    const MANUAL_URL: &'static str = "https://your-domain.com/your-plugin/manual";
94    const SUPPORT_URL: &'static str = "https://your-domain.com/support";
95    const VERSION: &'static str = "1.4.2";
96    const DESCRIPTION: &'static str = "The plugin description.";
97
98    fn features() -> impl Iterator<Item = &'static str> {
99        "fx stereo gain".split_whitespace()
100    }
101
102    fn init(&mut self, _: Arc<clap::Host>) -> Result<(), clap::Error> {
103        Ok(())
104    }
105
106    /// Start the audio thread.
107    fn activate(&mut self, _: f64, _: u32, _: u32) -> Result<AudioThread, clap::Error> {
108        Ok(AudioThread {
109            gain: self.gain.clone(),
110            smoothed: Smooth::default(),
111        })
112    }
113}
114
115struct AudioThread {
116    gain: Arc<AtomicU64>,
117    smoothed: Smooth,
118}
119
120impl clap::AudioThread<Gain> for AudioThread {
121    fn process(&mut self, process: &mut clap::Process) -> Result<clap::Status, clap::Error> {
122        let mut gain = f64::from_bits(self.gain.load(Ordering::Relaxed));
123
124        let nframes = process.frames_count();
125        let nev = process.in_events().size();
126        let mut ev_index = 0;
127        let mut next_ev_frame = if nev > 0 { 0 } else { nframes };
128
129        let mut i = 0;
130        while i < nframes {
131            while ev_index < nev && next_ev_frame == i {
132                {
133                    let in_events = process.in_events();
134                    let header = in_events.get(ev_index);
135                    if header.time() != i {
136                        next_ev_frame = header.time();
137                        break;
138                    }
139
140                    if let Ok(param_value) = header.param_value() {
141                        gain = param_value.value();
142                        self.gain.store(gain.to_bits(), Ordering::Release);
143                    }
144                }
145
146                ev_index += 1;
147
148                if ev_index == nev {
149                    next_ev_frame = nframes;
150                    break;
151                }
152            }
153
154            {
155                let i = i as usize;
156                let gain = gain as f32;
157
158                // Get the input signal from the main input port.
159                let in_l = process.audio_inputs(0).data32(0)[i];
160                let in_r = process.audio_inputs(0).data32(1)[i];
161
162                let smoothed = self.smoothed.tick(gain);
163                let out_l = in_l * smoothed;
164                let out_r = in_r * smoothed;
165
166                // Write the audio signal to the main output port.
167                process.audio_outputs(0).data32(0)[i] = out_l;
168                process.audio_outputs(0).data32(1)[i] = out_r;
169            }
170
171            i += 1;
172        }
173        Ok(clap::Continue)
174    }
175}
176
177// Export clap_entry symbols and build a plugin factory.
178clap::entry!(Gain);
179
180// A one-pole low pass filter to smooth out parameter changes.
181#[derive(Debug, Clone)]
182pub struct Smooth {
183    b0: f32,
184    a1: f32,
185    y1: f32,
186}
187
188impl Smooth {
189    fn tick(&mut self, sample: f32) -> f32 {
190        let y0 = sample * self.b0 - self.y1 * self.a1;
191        self.y1 = y0;
192        y0 * (1.0 + self.a1)
193    }
194}
195
196impl Default for Smooth {
197    fn default() -> Self {
198        Smooth {
199            b0: 1.0,
200            a1: -0.999,
201            y1: 0.0,
202        }
203    }
204}