summaryrefslogtreecommitdiff
path: root/src/audio.rs
blob: 71be7d3cf528e8be1fda407b72e1017431a9e3d5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
//! Contains all types and implementations related to audio processing and the audio thread.

use crate::{
    RetainPluginMainThread, RetainPluginShared,
    params::{GestureChange, RetainParamsLocal, RetainParamsShared},
    retain::retain_top_n_magnitudes,
    windowed_fft::WindowedRealFft,
};
use clack_extensions::{
    audio_ports::{
        AudioPortFlags, AudioPortInfo, AudioPortInfoWriter, AudioPortType, PluginAudioPortsImpl,
    },
    latency::HostLatency,
    params::PluginAudioProcessorParams,
};
use clack_plugin::{
    events::event_types::{ParamGestureBeginEvent, ParamGestureEndEvent},
    prelude::*,
};

/// Our plugin's audio processor. It lives in the audio thread.
///
/// It receives parameter events, and process a stereo audio signal by operating on the given audio
/// buffer.
pub struct RetainPluginAudioProcessor<'a> {
    /// The local state of the parameters
    params: RetainParamsLocal,
    /// A reference to the plugin's shared data.
    shared: &'a RetainPluginShared<'a>,
    /// Our handle to the host
    host: HostAudioProcessorHandle<'a>,
    /// Fft for the left channel
    fft_left: WindowedRealFft,
    /// Fft for the right channel
    fft_right: WindowedRealFft,
}

impl<'a> PluginAudioProcessor<'a, RetainPluginShared<'a>, RetainPluginMainThread<'a>>
    for RetainPluginAudioProcessor<'a>
{
    fn activate(
        host: HostAudioProcessorHandle<'a>,
        _main_thread: &mut RetainPluginMainThread,
        shared: &'a RetainPluginShared<'a>,
        _audio_config: PluginAudioConfiguration,
    ) -> Result<Self, PluginError> {
        let params = RetainParamsLocal::new(&shared.params);

        let fft_left = WindowedRealFft::new(params.get_window_size().into());
        let fft_right = WindowedRealFft::new(params.get_window_size().into());

        // This is where we would allocate intermediate buffers and such if we needed them.
        Ok(Self {
            params,
            shared,
            host,
            fft_left,
            fft_right,
        })
    }

    fn process(
        &mut self,
        _process: Process,
        mut audio: Audio,
        events: Events,
    ) -> Result<ProcessStatus, PluginError> {
        // First, we have to make a few sanity checks.
        // We want at least a single input/output port pair, which contains channels of `f32`
        // audio sample data.
        let mut port_pair = audio
            .port_pair(0)
            .ok_or(PluginError::Message("No input/output ports found"))?;

        let mut output_channels = port_pair
            .channels()?
            .into_f32()
            .ok_or(PluginError::Message("Expected f32 input/output"))?;

        let mut channel_buffers = [None, None];

        // Extract the buffer slices that we need, while making sure they are paired correctly and
        // check for either in-place or separate buffers.
        for (pair, buf) in output_channels.iter_mut().zip(&mut channel_buffers) {
            *buf = match pair {
                ChannelPair::InputOnly(_) | ChannelPair::OutputOnly(_) => None,
                ChannelPair::InPlace(b) => Some(b),
                ChannelPair::InputOutput(i, o) => {
                    o.copy_from_slice(i);
                    Some(o)
                }
            }
        }

        let prev_window_size = self.params.get_window_size();

        // Receive any param updates from the main thread and/or the GUI.
        let has_ui_param_updates = self.params.fetch_updates(&self.shared.params);

        // update window size and latency if it has changed
        // updates fft window sizes only if necessary
        let window_size = self.params.get_window_size();
        if prev_window_size != window_size {
            self.fft_left.window_size(window_size.into());
            self.fft_right.window_size(window_size.into());

            if let Some(latency) = self.shared.host.get_extension::<HostLatency>() {
                // should be safe
                let mut main = unsafe { self.shared.host.as_main_thread_unchecked() };

                latency.changed(&mut main);
                // self.shared.host.request_restart();
            }
        }

        // Now let's process the audio, while splitting the processing in batches between each
        // sample-accurate event.
        for event_batch in events.input.batch() {
            // Process all param events in this batch
            for event in event_batch.events() {
                self.params.handle_event(event);
            }

            // Get the parameters after all changes have been handled.
            let order = self.params.get_order();

            if order == 0 {
                for channel in channel_buffers.iter_mut().flatten() {
                    for sample in channel.iter_mut() {
                        *sample = 0.0;
                    }
                }

                continue;
            }

            // process samples in place here
            if let [Some(left), Some(right)] = &mut channel_buffers {
                for sample in left.iter_mut() {
                    if self.fft_left.push_back_input(*sample) {
                        self.fft_left.forward();

                        retain_top_n_magnitudes(self.fft_left.get_spectrum(), order);

                        self.fft_left.inverse();
                        self.fft_left.clear_input();
                    }

                    *sample = self.fft_left.pop_front_output();
                }

                for sample in right.iter_mut() {
                    if self.fft_right.push_back_input(*sample) {
                        self.fft_right.forward();

                        retain_top_n_magnitudes(self.fft_right.get_spectrum(), order);

                        self.fft_right.inverse();
                        self.fft_right.clear_input();
                    }

                    *sample = self.fft_right.pop_front_output();
                }
            }
        }

        // Publish any parameter changes we may have received back to the GUI.
        if self.params.push_updates(&self.shared.params) {
            // Request the on-main-thread callback, which we use to refresh the UI if it is open
            self.host.request_callback();
        }

        // Fetch the latest gesture status
        let current_gesture = self
            .params
            .fetch_gesture(&self.shared.params, has_ui_param_updates);

        // Send a Gesture Begin event, if we need to do so
        if let Some(GestureChange::Begin | GestureChange::Both) = current_gesture {
            let _ = events.output.try_push(ParamGestureBeginEvent::new(
                0,
                RetainParamsShared::PARAM_ORDER_ID,
            ));
        }

        // If the UI sent us param updates, send them to the Host
        if has_ui_param_updates {
            self.params.send_param_events(events.output);
        }

        // Send a Gesture End event, if we need to do so
        if let Some(GestureChange::End | GestureChange::Both) = current_gesture {
            let _ = events.output.try_push(ParamGestureEndEvent::new(
                audio.frames_count(),
                RetainParamsShared::PARAM_ORDER_ID,
            ));
        }

        Ok(ProcessStatus::ContinueIfNotQuiet)
    }
}

impl PluginAudioPortsImpl for RetainPluginMainThread<'_> {
    fn count(&mut self, _is_input: bool) -> u32 {
        1
    }

    fn get(&mut self, index: u32, _is_input: bool, writer: &mut AudioPortInfoWriter) {
        if index == 0 {
            writer.set(&AudioPortInfo {
                id: ClapId::new(0),
                name: b"main",
                channel_count: 2,
                flags: AudioPortFlags::IS_MAIN,
                port_type: Some(AudioPortType::STEREO),
                in_place_pair: None,
            });
        }
    }
}

impl PluginAudioProcessorParams for RetainPluginAudioProcessor<'_> {
    fn flush(
        &mut self,
        input_parameter_changes: &InputEvents,
        _output_parameter_changes: &mut OutputEvents,
    ) {
        for event in input_parameter_changes {
            self.params.handle_event(event);
        }
    }
}