summaryrefslogtreecommitdiff
path: root/src/audio.rs
diff options
context:
space:
mode:
authorozpv <39195175+ozpv@users.noreply.github.com>2026-05-15 15:49:19 -0500
committerGitHub <noreply@github.com>2026-05-15 15:49:19 -0500
commitcaed581a68f5f674ff4d3a798c241258b0412693 (patch)
tree1272b06a0705eb9e667dcc861a35c9eda4b2eb5e /src/audio.rs
parentf820003b0db3521f3d471a2c2f5e2aef2a281577 (diff)
commit
Diffstat (limited to 'src/audio.rs')
-rw-r--r--src/audio.rs163
1 files changed, 163 insertions, 0 deletions
diff --git a/src/audio.rs b/src/audio.rs
new file mode 100644
index 0000000..1552292
--- /dev/null
+++ b/src/audio.rs
@@ -0,0 +1,163 @@
+//! Contains all types and implementations related to audio processing and the audio thread.
+
+use crate::{
+ RetainPluginMainThread, RetainPluginShared,
+ params::{GestureChange, RetainParamsLocal, RetainParamsShared},
+};
+use clack_extensions::{audio_ports::*, 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,
+ /// Our handle to the host
+ host: HostAudioProcessorHandle<'a>,
+}
+
+impl<'a> PluginAudioProcessor<'a, RetainPluginShared, RetainPluginMainThread<'a>>
+ for RetainPluginAudioProcessor<'a>
+{
+ fn activate(
+ host: HostAudioProcessorHandle<'a>,
+ _main_thread: &mut RetainPluginMainThread,
+ shared: &'a RetainPluginShared,
+ _audio_config: PluginAudioConfiguration,
+ ) -> Result<Self, PluginError> {
+ // This is where we would allocate intermediate buffers and such if we needed them.
+ Ok(Self {
+ host,
+ shared,
+ params: RetainParamsLocal::new(&shared.params),
+ })
+ }
+
+ 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)
+ }
+ }
+ }
+
+ // Receive any param updates from the main thread and/or the GUI.
+ let has_ui_param_updates = self.params.fetch_updates(&self.shared.params);
+
+ // 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 order value after all parameter changes have been handled.
+ let order = self.params.get_order();
+
+ for buf in channel_buffers.iter_mut().flatten() {
+ for sample in buf.iter_mut() {
+ *sample = order as f32;
+ }
+ }
+ }
+
+ // 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);
+ }
+ }
+}