diff options
| -rw-r--r-- | Cargo.toml | 36 | ||||
| -rw-r--r-- | src/audio.rs | 163 | ||||
| -rw-r--r-- | src/gui.rs | 203 | ||||
| -rw-r--r-- | src/lib.rs | 89 | ||||
| -rw-r--r-- | src/params.rs | 352 | ||||
| -rw-r--r-- | src/window_size.rs | 77 |
6 files changed, 920 insertions, 0 deletions
diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..ccd8968 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,36 @@ +[package] +name = "retain" +version = "0.1.0" +edition = "2024" + +[lib] +crate-type = ["rlib", "cdylib"] + + +[dependencies] +clack-plugin = { path = "../clack/plugin" } +clack-extensions = { path = "../clack/extensions", features = [ + "audio-ports", + "clack-plugin", + "gui", + "note-ports", + "params", + "raw-window-handle_05", + "state", +] } +baseview = { git = "https://github.com/RustAudio/baseview.git", rev = "237d323c729f3aa99476ba3efa50129c5e86cad3", version = "0.1.0" } +egui-baseview = { git = "https://codeberg.org/BillyDM/egui-baseview.git", version = "0.7.0" } + +# to serialize parameters and save the plugin state +prost = "0.14.3" +prost-build = "0.14.3" + +[build-dependencies] +prost-build = "0.14.3" + +[profile.release] +opt-level = 3 +lto = "fat" +codegen-units = 1 +panic = "abort" +strip = "debuginfo" 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); + } + } +} diff --git a/src/gui.rs b/src/gui.rs new file mode 100644 index 0000000..8ab11b4 --- /dev/null +++ b/src/gui.rs @@ -0,0 +1,203 @@ +//! Contains all types and implementations related to the plugin's GUI + +use crate::{ + RetainPluginMainThread, RetainPluginShared, + params::{RetainParamsLocal, RetainParamsShared}, + window_size::WindowSize, +}; +use baseview::{Size, WindowHandle, WindowOpenOptions, WindowScalePolicy, gl::GlConfig}; +use clack_extensions::gui::*; +use clack_plugin::prelude::*; +use egui_baseview::{ + EguiWindow, GraphicsConfig, Queue, + egui::{self, ComboBox, Context, Slider}, +}; +use std::sync::Arc; + +/// The EGUI application state +struct AppState { + /// A handle to the shared params state + shared_params: Arc<RetainParamsShared>, + /// The local state of the parameters + local_params: RetainParamsLocal, +} + +impl AppState { + /// Initializes a new [`AppState`] from the given shared params state handle + pub fn new(shared_params: &Arc<RetainParamsShared>) -> Self { + Self { + local_params: RetainParamsLocal::new(shared_params), + shared_params: Arc::clone(shared_params), + } + } +} + +/// GUI state that can be accessed directly by the main thread +pub struct RetainPluginGui { + /// The handle to the baseview plugin window. + handle: WindowHandle, + /// The handle to the EGUI context + egui_context: Context, +} + +impl RetainPluginGui { + /// Creates a new GUI window, and embeds it into the given `parent`. + pub fn new(parent: Window<'_>, state: &RetainPluginShared) -> Self { + let settings = WindowOpenOptions { + title: "Retain".to_string(), + size: Size::new(1000.0, 500.0), + scale: WindowScalePolicy::SystemScaleFactor, + gl_config: Some(GlConfig::default()), + }; + + let (tx, rx) = std::sync::mpsc::channel(); + + let handle = EguiWindow::open_parented( + &parent, + settings, + GraphicsConfig::default(), + AppState::new(&state.params), + move |egui_ctx: &Context, _queue: &mut Queue, _state: &mut AppState| { + tx.send(egui_ctx.clone()).unwrap(); + }, + |egui_ctx: &Context, _queue: &mut Queue, state: &mut AppState| { + state.local_params.fetch_updates(&state.shared_params); + + egui::CentralPanel::default().show(egui_ctx, |ui| { + ui.heading("Retain"); + let mut order = state.local_params.get_order(); + + let slider = ui.add( + Slider::new(&mut order, 0..=100_000) + .text("Order") + .logarithmic(true), + ); + + if slider.changed() { + state.local_params.set_order(order); + state.local_params.push_updates(&state.shared_params); + } + + state.local_params.has_gesture = slider.is_pointer_button_down_on(); + state.local_params.push_gesture(&state.shared_params); + + let mut window_size = WindowSize::from(state.local_params.get_window_size()); + + ComboBox::from_label("Window Size") + .selected_text(window_size.as_str()) + .show_ui(ui, |ui| { + for size in WindowSize::into_iter() { + let display = size.as_str(); + + if ui + .selectable_value(&mut window_size, size, display) + .changed() + { + let u = window_size.value(); + + state.local_params.set_window_size(u); + state.local_params.push_updates(&state.shared_params); + } + } + }); + }); + }, + ); + + let egui_context = rx.recv().unwrap(); + + Self { + handle, + egui_context, + } + } + + /// Requests the UI to repaint itself, e.g. in response to events or parameter changes + pub fn request_repaint(&self) { + self.egui_context.request_repaint(); + } +} + +impl Drop for RetainPluginGui { + fn drop(&mut self) { + self.handle.close(); + } +} + +impl PluginGuiImpl for RetainPluginMainThread<'_> { + fn is_api_supported(&mut self, configuration: GuiConfiguration) -> bool { + configuration.api_type + == GuiApiType::default_for_current_platform().expect("Unsupported platform") + && !configuration.is_floating + } + + fn get_preferred_api(&mut self) -> Option<GuiConfiguration<'_>> { + Some(GuiConfiguration { + api_type: GuiApiType::default_for_current_platform().expect("Unsupported platform"), + is_floating: false, + }) + } + + fn create(&mut self, configuration: GuiConfiguration) -> Result<(), PluginError> { + if configuration.is_floating { + return Err(PluginError::Message( + "Invalid GUI configuration: this plugin does not support floating mode", + )); + } + + let supported_type = + GuiApiType::default_for_current_platform().expect("Unsupported platform"); + + if configuration.api_type != supported_type { + return Err(PluginError::Message( + "Invalid GUI configuration: unsupported API type", + )); + } + + Ok(()) + } + + fn destroy(&mut self) { + let _ = self.gui.take(); + } + + fn set_scale(&mut self, _scale: f64) -> Result<(), PluginError> { + Ok(()) + } + + fn get_size(&mut self) -> Option<GuiSize> { + Some(GuiSize { + width: 1000, + height: 500, + }) + } + + fn set_size(&mut self, _size: GuiSize) -> Result<(), PluginError> { + Ok(()) + } + + fn set_parent(&mut self, window: Window) -> Result<(), PluginError> { + self.gui = Some(RetainPluginGui::new(window, self.shared)); + Ok(()) + } + + fn set_transient(&mut self, _window: Window) -> Result<(), PluginError> { + Ok(()) + } + + fn show(&mut self) -> Result<(), PluginError> { + if let Some(gui) = &self.gui { + gui.request_repaint(); + } + + Ok(()) + } + + fn hide(&mut self) -> Result<(), PluginError> { + if let Some(gui) = &self.gui { + gui.request_repaint(); + } + + Ok(()) + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..82c1957 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,89 @@ +use crate::{ + audio::RetainPluginAudioProcessor, + gui::RetainPluginGui, + params::{RetainParamsLocal, RetainParamsShared}, +}; +use clack_extensions::{audio_ports::*, gui::PluginGui, params::*, state::PluginState}; +use clack_plugin::prelude::*; +use std::sync::Arc; + +mod audio; +mod gui; +mod params; +mod window_size; + +/// The type that represents our plugin in Clack. +/// +/// This is what implements the [`Plugin`] trait, where all the other subtypes are attached. +pub struct RetainPlugin; + +impl Plugin for RetainPlugin { + type AudioProcessor<'a> = RetainPluginAudioProcessor<'a>; + type Shared<'a> = RetainPluginShared; + type MainThread<'a> = RetainPluginMainThread<'a>; + + fn declare_extensions( + builder: &mut PluginExtensions<Self>, + _shared: Option<&RetainPluginShared>, + ) { + builder + .register::<PluginAudioPorts>() + .register::<PluginParams>() + .register::<PluginState>() + .register::<PluginGui>(); + } +} + +impl DefaultPluginFactory for RetainPlugin { + fn get_descriptor() -> PluginDescriptor { + use clack_plugin::plugin::features::*; + + PluginDescriptor::new("org.haemolacriaa.retain", "Retain") + .with_features([AUDIO_EFFECT, STEREO]) + } + + fn new_shared(_host: HostSharedHandle<'_>) -> Result<Self::Shared<'_>, PluginError> { + Ok(RetainPluginShared { + params: Arc::new(RetainParamsShared::new()), + }) + } + + fn new_main_thread<'a>( + _host: HostMainThreadHandle<'a>, + shared: &'a Self::Shared<'a>, + ) -> Result<Self::MainThread<'a>, PluginError> { + Ok(Self::MainThread { + shared, + params: RetainParamsLocal::new(&shared.params), + gui: None, + }) + } +} + +/// The plugin data that gets shared between the Main Thread and the Audio Thread. +pub struct RetainPluginShared { + /// The plugin's parameter values. + params: Arc<RetainParamsShared>, +} + +impl PluginShared<'_> for RetainPluginShared {} + +/// The data that belongs to the main thread of our plugin. +pub struct RetainPluginMainThread<'a> { + /// The local state of the parameters + params: RetainParamsLocal, + /// A reference to the plugin's shared data. + shared: &'a RetainPluginShared, + /// The plugin's GUI state and context + gui: Option<RetainPluginGui>, +} + +impl<'a> PluginMainThread<'a, RetainPluginShared> for RetainPluginMainThread<'a> { + fn on_main_thread(&mut self) { + if let Some(gui) = &self.gui { + gui.request_repaint(); + } + } +} + +clack_export_entry!(SinglePluginEntry<RetainPlugin>); diff --git a/src/params.rs b/src/params.rs new file mode 100644 index 0000000..b7e6676 --- /dev/null +++ b/src/params.rs @@ -0,0 +1,352 @@ +//! Contains all types and implementations related to parameter management. + +use crate::{RetainPluginMainThread, window_size::WindowSize}; +use clack_extensions::{params::*, state::PluginStateImpl}; +use clack_plugin::{ + events::{event_types::ParamValueEvent, spaces::CoreEventSpace}, + prelude::*, + stream::{InputStream, OutputStream}, + utils::Cookie, +}; +use prost::Message; +use std::{ + ffi::CStr, + fmt::Write as _, + io::{Read, Write as _}, + sync::atomic::{AtomicBool, AtomicUsize, Ordering}, +}; + +/// The default value of the order parameter. +const DEFAULT_ORDER: usize = 1000; +pub const DEFAULT_WINDOW_SIZE: WindowSize = WindowSize::Size128; + +/// A struct that manages the parameters for our plugin. +/// +/// This struct will be used both by the [`RetainPluginMainThread`] (which the host will use +/// to query the value of our parameters), and by the [`RetainPluginAudioProcessor`], which will +/// actually modulate the audio samples. +pub struct RetainParamsShared { + /// The current value of the order parameter. + order: AtomicUsize, + /// Window size of FFT + window_size: AtomicUsize, + /// Whether a gesture is currently active or not on the parameters + has_gesture: AtomicBool, +} + +impl RetainParamsShared { + /// The unique identifier for the order parameter. + pub const PARAM_ORDER_ID: ClapId = ClapId::new(1); + pub const PARAM_WINDOW_SIZE_ID: ClapId = ClapId::new(2); + + /// Initializes the shared parameter value. + pub fn new() -> Self { + Self { + order: AtomicUsize::new(DEFAULT_ORDER), + window_size: AtomicUsize::new(DEFAULT_WINDOW_SIZE.value()), + has_gesture: AtomicBool::new(false), + } + } +} + +/// A param gesture change type +#[derive(Copy, Clone, Eq, PartialEq)] +pub enum GestureChange { + /// User started changing a parameter + Begin, + /// User finished changing a parameter + End, + /// User both started and finished changing a parameter during the current block + Both, +} + +/// The local-side of parameter state. +/// +/// This state is local to the current thread (whether it is the main-thread, the UI or the audio +/// thread), it is not shared directly with the others. +/// +/// This allows us to both check for differences, and to only update parameter state when we want +/// to. +pub struct RetainParamsLocal { + /// The local value of the order parameter. + order: usize, + /// The local value of the window size paramter. + window_size: usize, + /// Whether the user is currently changing the parameter, that we know of + pub has_gesture: bool, +} + +impl RetainParamsLocal { + /// Initializes a new local state from the current shared state. + pub fn new(shared: &RetainParamsShared) -> Self { + Self { + order: shared.order.load(Ordering::Relaxed), + window_size: shared.window_size.load(Ordering::Relaxed), + has_gesture: shared.has_gesture.load(Ordering::Relaxed), + } + } + + /// Returns the current local order value. + #[inline] + pub fn get_order(&self) -> usize { + self.order + } + + /// Sets the current local order to `value`. + /// + /// It is clamped to the range `0..=100_000`. + #[inline] + pub fn set_order(&mut self, value: usize) { + self.order = value.clamp(0, 100_000); + } + + /// Returns the current window size value. + #[inline] + pub fn get_window_size(&self) -> usize { + self.window_size + } + + /// Sets the current local window size to `value`. + #[inline] + pub fn set_window_size(&mut self, value: usize) { + self.window_size = value; + } + + /// Fetch updates from the `shared` state. + /// + /// If any of the parameters have been updated, this returns `true`. + #[inline] + pub fn fetch_updates(&mut self, shared: &RetainParamsShared) -> bool { + let latest_order = shared.order.load(Ordering::Relaxed); + let latest_window_size = shared.window_size.load(Ordering::Relaxed); + + if latest_order != self.order || latest_window_size != self.order { + self.order = latest_order; + self.window_size = latest_window_size; + + true + } else { + false + } + } + + /// Pushes the local parameter values to the `shared` state. + /// + /// If the values were different and an actual update occurred, this returns `true`. + #[inline] + pub fn push_updates(&self, shared: &RetainParamsShared) -> bool { + let previous_order = shared.order.swap(self.order, Ordering::Relaxed); + let previous_window_size = shared.window_size.swap(self.window_size, Ordering::Relaxed); + + previous_order != self.order || previous_window_size != self.window_size + } + + /// Pushes the local gesture state to the `shared` state. + #[inline] + pub fn push_gesture(&self, shared: &RetainParamsShared) { + shared + .has_gesture + .store(self.has_gesture, Ordering::Relaxed); + } + + /// Fetches updates to the gesture state. + /// + /// If a gesture state changed occurred, it is returned. + /// If the gesture did not change from the last update, `None` is returned. + #[inline] + pub fn fetch_gesture( + &mut self, + shared: &RetainParamsShared, + has_ui_param_updates: bool, + ) -> Option<GestureChange> { + let previous_gesture = self.has_gesture; + + self.has_gesture = shared.has_gesture.load(Ordering::Relaxed); + + if previous_gesture == self.has_gesture { + return if has_ui_param_updates && !self.has_gesture { + Some(GestureChange::Both) + } else { + None + }; + } + + if previous_gesture { + Some(GestureChange::End) + } else { + Some(GestureChange::Begin) + } + } + + /// Handles incoming events. + /// + /// If the given event is a matching parameter change event, the order parameter will be + /// updated accordingly. + pub fn handle_event(&mut self, event: &UnknownEvent) { + if let Some(CoreEventSpace::ParamValue(event)) = event.as_core_event() { + if event.param_id() == RetainParamsShared::PARAM_ORDER_ID { + self.set_order(event.value() as usize); + } + + if event.param_id() == RetainParamsShared::PARAM_WINDOW_SIZE_ID { + self.set_window_size(event.value() as usize); + } + } + } + + /// Sends the value of the parameters to the host, via a [`ParamValueEvent`]. + pub fn send_param_events(&self, output_events: &mut OutputEvents) { + let order_event = ParamValueEvent::new( + 0, + RetainParamsShared::PARAM_ORDER_ID, + Pckn::match_all(), + self.order as f64, + Cookie::empty(), + ); + + let _ = output_events.try_push(order_event); + + let window_size_event = ParamValueEvent::new( + 0, + RetainParamsShared::PARAM_ORDER_ID, + Pckn::match_all(), + self.window_size as f64, + Cookie::empty(), + ); + + let _ = output_events.try_push(window_size_event); + } +} + +#[derive(Message)] +struct PluginState { + #[prost(uint64, tag = "0")] + order: u64, + #[prost(uint64, tag = "1")] + window_size: u64, +} + +impl PluginState { + fn copied(local_params: &RetainParamsLocal) -> Self { + Self { + order: local_params.get_order() as u64, + window_size: local_params.get_window_size() as u64, + } + } + + fn get_order(&self) -> u64 { + self.order + } + + fn get_window_size(&self) -> u64 { + self.window_size + } +} + +/// Implementation of the State extension. +impl PluginStateImpl for RetainPluginMainThread<'_> { + fn save(&mut self, output: &mut OutputStream) -> Result<(), PluginError> { + self.params.fetch_updates(&self.shared.params); + + let mut data = vec![]; + + let state = PluginState::copied(&self.params); + + state.encode(&mut data)?; + + output.write_all(&data)?; + + Ok(()) + } + + fn load(&mut self, input: &mut InputStream) -> Result<(), PluginError> { + let mut data = vec![]; + input.read_exact(&mut data)?; + + let data = PluginState::decode(&data[..])?; + + self.params.set_order(data.get_order() as usize); + self.params.set_window_size(data.get_window_size() as usize); + + self.params.push_updates(&self.shared.params); + + Ok(()) + } +} + +impl PluginMainThreadParams for RetainPluginMainThread<'_> { + fn count(&mut self) -> u32 { + 2 + } + + fn get_info(&mut self, param_index: u32, info: &mut ParamInfoWriter) { + if param_index != 0 { + return; + } + + info.set(&ParamInfo { + id: RetainParamsShared::PARAM_ORDER_ID, + flags: ParamInfoFlags::IS_AUTOMATABLE, + cookie: Cookie::default(), + name: b"Order", + module: b"", + min_value: 0.0, + max_value: 100_000.0, + default_value: DEFAULT_ORDER as f64, + }); + + info.set(&ParamInfo { + id: RetainParamsShared::PARAM_WINDOW_SIZE_ID, + flags: ParamInfoFlags::IS_AUTOMATABLE, + cookie: Cookie::default(), + name: b"Window Size", + module: b"", + min_value: 128.0, + max_value: usize::MAX as f64, + default_value: DEFAULT_WINDOW_SIZE.value() as f64, + }); + } + + fn get_value(&mut self, param_id: ClapId) -> Option<f64> { + match param_id { + RetainParamsShared::PARAM_ORDER_ID => Some(self.params.get_order() as f64), + RetainParamsShared::PARAM_WINDOW_SIZE_ID => Some(self.params.get_window_size() as f64), + _ => None, + } + } + + fn value_to_text( + &mut self, + param_id: ClapId, + value: f64, + writer: &mut ParamDisplayWriter, + ) -> std::fmt::Result { + if param_id == 1 { + write!(writer, "{0:.2} %", value * 100.0) + } else { + Err(std::fmt::Error) + } + } + + fn text_to_value(&mut self, param_id: ClapId, text: &CStr) -> Option<f64> { + let text = text.to_str().ok()?; + if param_id == 1 { + let text = text.strip_suffix('%').unwrap_or(text).trim(); + let percentage: f64 = text.parse().ok()?; + + Some(percentage / 100.0) + } else { + None + } + } + + fn flush( + &mut self, + input_parameter_changes: &InputEvents, + _output_parameter_changes: &mut OutputEvents, + ) { + for event in input_parameter_changes { + self.params.handle_event(event); + } + } +} diff --git a/src/window_size.rs b/src/window_size.rs new file mode 100644 index 0000000..0c1ef08 --- /dev/null +++ b/src/window_size.rs @@ -0,0 +1,77 @@ +#[derive(Debug, PartialEq)] +pub enum WindowSize { + Size128, + Size256, + Size512, + Size1024, + Size2048, + Size4096, + Size8192, + Size16384, + Size32768, + Custom(usize), +} + +impl From<usize> for WindowSize { + fn from(item: usize) -> Self { + match item { + 128 => Self::Size128, + 256 => Self::Size256, + 512 => Self::Size512, + 1024 => Self::Size1024, + 2048 => Self::Size2048, + 4096 => Self::Size4096, + 8192 => Self::Size8192, + 16384 => Self::Size16384, + 32768 => Self::Size32768, + _ => Self::Custom(item), + } + } +} + +impl WindowSize { + pub fn as_str(&self) -> &'static str { + match self { + Self::Size128 => "128", + Self::Size256 => "256", + Self::Size512 => "512", + Self::Size1024 => "1024", + Self::Size2048 => "2048", + Self::Size4096 => "4096", + Self::Size8192 => "8192", + Self::Size16384 => "16384", + Self::Size32768 => "32768", + Self::Custom(_) => "", + } + } + + pub fn value(&self) -> usize { + match self { + Self::Size128 => 128, + Self::Size256 => 256, + Self::Size512 => 512, + Self::Size1024 => 1024, + Self::Size2048 => 2048, + Self::Size4096 => 4096, + Self::Size8192 => 8192, + Self::Size16384 => 16384, + Self::Size32768 => 32768, + Self::Custom(x) => *x, + } + } + + pub fn into_iter() -> impl Iterator<Item = Self> { + [ + Self::Size128, + Self::Size256, + Self::Size512, + Self::Size1024, + Self::Size2048, + Self::Size4096, + Self::Size8192, + Self::Size16384, + Self::Size32768, + ] + .into_iter() + } +} |
