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
|
//! Contains all types and implementations related to audio processing and the audio thread.
use crate::{
RetainPluginMainThread, RetainPluginShared, params::RetainParamsLocal,
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::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();
let prev_window_type = self.params.get_window_type().as_byte();
// Receive any param updates from the main thread and/or the GUI.
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();
}
}
// update change in window type if needed
let window_type = self.params.get_window_type();
if prev_window_type != window_type.as_byte() {
self.fft_left.window_function(window_type);
self.fft_right.window_function(window_type);
}
// 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();
let complement = self.params.get_complement();
// 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, complement);
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, 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.
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();
}
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);
}
}
}
|