summaryrefslogtreecommitdiff
path: root/src/audio.rs
blob: 4aaac9f2c67c9fa35f2fb8fbc4bed591d668d39f (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
use crate::{
    RetainPluginMainThread, RetainPluginShared, params::RetainParams,
    retain::retain_top_n_magnitudes, window_size::WindowSize, window_type::WindowType,
    windowed_fft::WindowedRealFft,
};
use clack_extensions::{
    audio_ports::{
        AudioPortFlags, AudioPortInfo, AudioPortInfoWriter, AudioPortType, PluginAudioPortsImpl,
    },
    latency::HostLatency,
    params::PluginAudioProcessorParams,
};
use clack_plugin::prelude::*;
use std::sync::Arc;

/// 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: Arc<RetainParams>,
    /// 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,
    /// Previous window type
    prev_window_type: Option<WindowType>,
    /// Previous window size
    prev_window_size: Option<WindowSize>,
}

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 = Arc::clone(&shared.params);

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

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

    fn process(
        &mut self,
        _process: Process,
        mut audio: Audio,
        events: Events,
    ) -> Result<ProcessStatus, PluginError> {
        // Ensure 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"))?;

        // Extract the buffer slices that we need, while making sure they are paired correctly and
        // check for either in-place or separate buffers.
        let mut output_buffers = [None, None];

        for (pair, buf) in output_channels.iter_mut().zip(&mut output_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)
                }
            }
        }

        // 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 self.prev_window_size != Some(window_size.clone()) {
            self.fft_left.window_size(window_size.clone());
            self.fft_right.window_size(window_size.clone());

            if let Some(latency) = self.shared.host.get_extension::<HostLatency>() {
                // SAFETY: should be safe as self.shared.host is a shared
                // version of the main thread handle
                // no reason to propagate it through all these layers
                let mut main = unsafe { self.shared.host.as_main_thread_unchecked() };

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

            self.prev_window_size = Some(window_size);
        }

        // update change in window type if needed
        let window_type = self.params.get_window_type();
        if self.prev_window_type != Some(window_type.clone()) {
            self.fft_left.window_function(&window_type);
            self.fft_right.window_function(&window_type);
            self.prev_window_type = Some(window_type);
        }

        // process the audio
        for event_batch in events.input.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();

            // this parameter is read-only and can only be changed in the UI
            let complement = self.params.get_complement();

            // process samples in place here
            if let [Some(left), Some(right)] = &mut output_buffers {
                let range = event_batch.sample_bounds();

                for sample in &mut left[range] {
                    if self.fft_left.push_back_input(*sample) {
                        self.fft_left.forward();

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

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

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

                for sample in &mut right[range] {
                    if self.fft_right.push_back_input(*sample) {
                        self.fft_right.forward();

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

                        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.
        // Request the on-main-thread callback, which we use to refresh the UI if it is open
        self.host.request_callback();

        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);
        }
    }
}