beamer_core/plugin.rs
1//! Core plugin trait definitions.
2//!
3//! This module defines the two-phase plugin lifecycle:
4//!
5//! - **[`Descriptor`]** (unprepared state): Holds parameters, created before audio config is known.
6//! Transforms into a processor via [`Descriptor::prepare()`] when configuration arrives.
7//!
8//! - **[`Processor`]** (prepared state): Ready for audio processing with real sample rate
9//! and buffer configuration. Created by [`Descriptor::prepare()`], can return to unprepared
10//! state via [`Processor::unprepare()`] for sample rate changes.
11//!
12//! This design eliminates placeholder values by making it impossible to process audio
13//! until proper configuration is available.
14
15use crate::buffer::{AuxiliaryBuffers, Buffer};
16use crate::error::{PluginError, PluginResult};
17use crate::midi::{
18 KeyswitchInfo, Midi2Controller, MidiBuffer, MidiEvent, MpeInputDeviceSettings,
19 NoteExpressionTypeInfo, PhysicalUIMap,
20};
21use crate::midi_cc_config::MidiCcConfig;
22use crate::parameter_groups::ParameterGroups;
23use crate::parameter_store::ParameterStore;
24use crate::parameter_types::Parameters;
25use crate::process_context::ProcessContext;
26
27// =============================================================================
28// HasParameters Trait (Shared Parameter Access)
29// =============================================================================
30
31/// Trait for types that hold parameters.
32///
33/// This trait provides a common interface for parameter access, shared between
34/// [`Descriptor`] (unprepared state) and [`Processor`] (prepared state).
35/// Both traits require `HasParameters` as a supertrait.
36///
37/// # Derive Macro
38///
39/// Use `#[derive(HasParameters)]` to automatically implement this trait for structs
40/// with a `#[parameters]` field annotation:
41///
42/// ```ignore
43/// #[derive(Default, HasParameters)]
44/// pub struct GainPlugin {
45/// #[parameters]
46/// parameters: GainParameters,
47/// }
48///
49/// #[derive(HasParameters)]
50/// pub struct GainProcessor {
51/// #[parameters]
52/// parameters: GainParameters,
53/// }
54/// ```
55///
56/// This eliminates the boilerplate of implementing `parameters()`, `parameters_mut()`,
57/// and `set_parameters()` on both your Plugin and Processor types.
58pub trait HasParameters: Send + 'static {
59 /// The parameter collection type.
60 type Parameters: ParameterStore + ParameterGroups + crate::parameter_types::Parameters + Default;
61
62 /// Returns a reference to the parameters.
63 fn parameters(&self) -> &Self::Parameters;
64
65 /// Returns a mutable reference to the parameters.
66 fn parameters_mut(&mut self) -> &mut Self::Parameters;
67
68 /// Sets the parameters, replacing any existing values.
69 ///
70 /// This is used by the default [`Processor::unprepare()`] implementation
71 /// to transfer parameters from the processor back to the definition.
72 fn set_parameters(&mut self, params: Self::Parameters);
73}
74
75// =============================================================================
76// Plugin Setup Types - Composable Host Information
77// =============================================================================
78
79/// Internal: All information the host provides at initialization.
80///
81/// This is passed to `PluginSetup::extract()` so each setup type can
82/// extract only what it needs.
83#[derive(Clone, Debug)]
84pub struct HostSetup {
85 /// Sample rate in Hz (e.g., 44100.0, 48000.0, 96000.0)
86 pub sample_rate: f64,
87 /// Maximum number of samples per process() call
88 pub max_buffer_size: usize,
89 /// Bus layout information
90 pub layout: BusLayout,
91 /// Processing mode (realtime vs offline)
92 pub process_mode: ProcessMode,
93}
94
95impl HostSetup {
96 /// Create a new HostSetup with the given values.
97 pub fn new(sample_rate: f64, max_buffer_size: usize, layout: BusLayout, process_mode: ProcessMode) -> Self {
98 Self {
99 sample_rate,
100 max_buffer_size,
101 layout,
102 process_mode,
103 }
104 }
105}
106
107/// Trait for plugin setup requirements.
108///
109/// Plugins declare what host information they need via [`Descriptor::Setup`].
110/// The framework extracts only the requested data from the host.
111///
112/// # Composable Setup Types
113///
114/// Request exactly what you need using individual types or tuples:
115///
116/// ```ignore
117/// type Setup = (); // No setup needed
118/// type Setup = SampleRate; // Just sample rate
119/// type Setup = (SampleRate, MaxBufferSize); // Sample rate + buffer size
120/// type Setup = (SampleRate, MainOutputChannels); // Sample rate + channels
121/// ```
122///
123/// # Available Types
124///
125/// | Type | Value | Use Case |
126/// |------|-------|----------|
127/// | `()` | - | Stateless plugins (gain, pan) |
128/// | [`SampleRate`] | `f64` | Time-based DSP (delay, filter, envelope) |
129/// | [`MaxBufferSize`] | `usize` | FFT, lookahead buffers |
130/// | [`MainInputChannels`] | `u32` | Per-channel input processing |
131/// | [`MainOutputChannels`] | `u32` | Per-channel output state |
132/// | [`AuxInputCount`] | `usize` | Sidechain-aware processing |
133/// | [`AuxOutputCount`] | `usize` | Multi-bus output |
134/// | [`ProcessMode`] | enum | Quality settings for offline rendering |
135pub trait PluginSetup: Clone + Send + 'static {
136 /// Extract this setup from the host-provided information.
137 fn extract(host: &HostSetup) -> Self;
138}
139
140// =============================================================================
141// Unit Type - No Setup Needed
142// =============================================================================
143
144/// Use `()` for stateless plugins that don't depend on sample rate.
145///
146/// # Example
147///
148/// ```ignore
149/// impl Descriptor for GainPlugin {
150/// type Setup = ();
151/// fn prepare(self, _: ()) -> GainProcessor {
152/// GainProcessor { parameters: self.parameters }
153/// }
154/// }
155/// ```
156impl PluginSetup for () {
157 fn extract(_: &HostSetup) -> Self {}
158}
159
160// =============================================================================
161// Individual Setup Types
162// =============================================================================
163
164/// Sample rate in Hz.
165///
166/// Use for most plugins with time-dependent DSP: delays, filters,
167/// envelopes, parameter smoothing, etc. This is the most common choice.
168///
169/// # Example
170///
171/// ```ignore
172/// impl Descriptor for DelayPlugin {
173/// type Setup = SampleRate;
174/// fn prepare(self, sample_rate: SampleRate) -> DelayProcessor {
175/// let buffer_size = (MAX_DELAY_SECONDS * sample_rate.0) as usize;
176/// DelayProcessor {
177/// sample_rate: sample_rate.0,
178/// buffer: vec![0.0; buffer_size],
179/// }
180/// }
181/// }
182/// ```
183#[derive(Clone, Copy, Debug, PartialEq)]
184pub struct SampleRate(pub f64);
185
186impl SampleRate {
187 /// Get the sample rate in Hz.
188 #[inline]
189 pub fn hz(&self) -> f64 {
190 self.0
191 }
192}
193
194impl PluginSetup for SampleRate {
195 fn extract(host: &HostSetup) -> Self {
196 SampleRate(host.sample_rate)
197 }
198}
199
200/// Maximum buffer size in samples.
201///
202/// Use for plugins that need to pre-allocate processing buffers,
203/// like FFT-based processors or lookahead limiters.
204///
205/// # Example
206///
207/// ```ignore
208/// impl Descriptor for FftPlugin {
209/// type Setup = (SampleRate, MaxBufferSize);
210/// fn prepare(self, (sr, mbs): (SampleRate, MaxBufferSize)) -> FftProcessor {
211/// FftProcessor {
212/// sample_rate: sr.0,
213/// fft_buffer: vec![0.0; mbs.0],
214/// }
215/// }
216/// }
217/// ```
218#[derive(Clone, Copy, Debug, PartialEq)]
219pub struct MaxBufferSize(pub usize);
220
221impl PluginSetup for MaxBufferSize {
222 fn extract(host: &HostSetup) -> Self {
223 MaxBufferSize(host.max_buffer_size)
224 }
225}
226
227/// Number of channels on the main input bus.
228///
229/// Use for plugins that need per-channel input processing,
230/// like channel-specific EQ or surround processors.
231#[derive(Clone, Copy, Debug, PartialEq)]
232pub struct MainInputChannels(pub u32);
233
234impl PluginSetup for MainInputChannels {
235 fn extract(host: &HostSetup) -> Self {
236 MainInputChannels(host.layout.main_input_channels)
237 }
238}
239
240/// Number of channels on the main output bus.
241///
242/// Use for plugins that need per-channel output state allocation,
243/// like surround processors or multi-channel analyzers.
244///
245/// # Example
246///
247/// ```ignore
248/// impl Descriptor for SurroundPlugin {
249/// type Setup = (SampleRate, MainOutputChannels);
250/// fn prepare(self, (sr, channels): (SampleRate, MainOutputChannels)) -> SurroundProcessor {
251/// SurroundProcessor {
252/// sample_rate: sr.0,
253/// per_channel_state: vec![State::new(); channels.0 as usize],
254/// }
255/// }
256/// }
257/// ```
258#[derive(Clone, Copy, Debug, PartialEq)]
259pub struct MainOutputChannels(pub u32);
260
261impl PluginSetup for MainOutputChannels {
262 fn extract(host: &HostSetup) -> Self {
263 MainOutputChannels(host.layout.main_output_channels)
264 }
265}
266
267/// Number of auxiliary input buses.
268///
269/// Use for sidechain-aware plugins that need to know
270/// how many aux inputs are available.
271#[derive(Clone, Copy, Debug, PartialEq)]
272pub struct AuxInputCount(pub usize);
273
274impl PluginSetup for AuxInputCount {
275 fn extract(host: &HostSetup) -> Self {
276 AuxInputCount(host.layout.aux_input_count)
277 }
278}
279
280/// Number of auxiliary output buses.
281///
282/// Use for multi-bus output plugins.
283#[derive(Clone, Copy, Debug, PartialEq)]
284pub struct AuxOutputCount(pub usize);
285
286impl PluginSetup for AuxOutputCount {
287 fn extract(host: &HostSetup) -> Self {
288 AuxOutputCount(host.layout.aux_output_count)
289 }
290}
291
292/// Processing mode (realtime vs offline).
293///
294/// Use for plugins that want different quality settings
295/// for realtime vs offline rendering (e.g., higher quality
296/// oversampling when rendering offline).
297///
298/// # Example
299///
300/// ```ignore
301/// impl Descriptor for OversamplingPlugin {
302/// type Setup = (SampleRate, ProcessMode);
303/// fn prepare(self, (sr, mode): (SampleRate, ProcessMode)) -> OversamplingProcessor {
304/// let oversampling = match mode {
305/// ProcessMode::Realtime => 2,
306/// ProcessMode::Offline => 8,
307/// };
308/// OversamplingProcessor { sample_rate: sr.0, oversampling }
309/// }
310/// }
311/// ```
312#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
313pub enum ProcessMode {
314 /// Normal realtime playback.
315 #[default]
316 Realtime,
317 /// Offline rendering (bounce, export).
318 Offline,
319 /// Prefetch (used by some hosts for look-ahead).
320 Prefetch,
321}
322
323impl PluginSetup for ProcessMode {
324 fn extract(host: &HostSetup) -> Self {
325 host.process_mode
326 }
327}
328
329// =============================================================================
330// Tuple Implementations - Compose Multiple Setup Types
331// =============================================================================
332
333impl<A, B> PluginSetup for (A, B)
334where
335 A: PluginSetup,
336 B: PluginSetup,
337{
338 fn extract(host: &HostSetup) -> Self {
339 (A::extract(host), B::extract(host))
340 }
341}
342
343impl<A, B, C> PluginSetup for (A, B, C)
344where
345 A: PluginSetup,
346 B: PluginSetup,
347 C: PluginSetup,
348{
349 fn extract(host: &HostSetup) -> Self {
350 (A::extract(host), B::extract(host), C::extract(host))
351 }
352}
353
354impl<A, B, C, D> PluginSetup for (A, B, C, D)
355where
356 A: PluginSetup,
357 B: PluginSetup,
358 C: PluginSetup,
359 D: PluginSetup,
360{
361 fn extract(host: &HostSetup) -> Self {
362 (A::extract(host), B::extract(host), C::extract(host), D::extract(host))
363 }
364}
365
366impl<A, B, C, D, E> PluginSetup for (A, B, C, D, E)
367where
368 A: PluginSetup,
369 B: PluginSetup,
370 C: PluginSetup,
371 D: PluginSetup,
372 E: PluginSetup,
373{
374 fn extract(host: &HostSetup) -> Self {
375 (A::extract(host), B::extract(host), C::extract(host), D::extract(host), E::extract(host))
376 }
377}
378
379// =============================================================================
380// Bus Layout Information
381// =============================================================================
382
383/// Bus layout information for plugins that need channel configuration.
384///
385/// This is used internally to populate the individual setup types
386/// like [`MainInputChannels`], [`MainOutputChannels`], etc.
387#[derive(Clone, Debug, Default, PartialEq)]
388pub struct BusLayout {
389 /// Number of channels on the main input bus
390 pub main_input_channels: u32,
391 /// Number of channels on the main output bus
392 pub main_output_channels: u32,
393 /// Number of auxiliary input buses
394 pub aux_input_count: usize,
395 /// Number of auxiliary output buses
396 pub aux_output_count: usize,
397}
398
399impl BusLayout {
400 /// Create a stereo (2 in, 2 out) layout with no aux buses.
401 pub const fn stereo() -> Self {
402 Self {
403 main_input_channels: 2,
404 main_output_channels: 2,
405 aux_input_count: 0,
406 aux_output_count: 0,
407 }
408 }
409
410 /// Create a layout from a plugin's bus configuration.
411 pub fn from_plugin<P: Descriptor>(plugin: &P) -> Self {
412 Self {
413 main_input_channels: plugin
414 .input_bus_info(0)
415 .map(|b| b.channel_count)
416 .unwrap_or(2),
417 main_output_channels: plugin
418 .output_bus_info(0)
419 .map(|b| b.channel_count)
420 .unwrap_or(2),
421 aux_input_count: plugin.input_bus_count().saturating_sub(1),
422 aux_output_count: plugin.output_bus_count().saturating_sub(1),
423 }
424 }
425}
426
427// =============================================================================
428// Bus Configuration
429// =============================================================================
430
431/// Audio bus type.
432#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
433pub enum BusType {
434 /// Main audio bus (e.g., primary stereo input/output).
435 #[default]
436 Main,
437 /// Auxiliary bus (e.g., sidechain input).
438 Aux,
439}
440
441/// Information about an audio bus.
442#[derive(Debug, Clone)]
443pub struct BusInfo {
444 /// Display name for the bus (e.g., "Input", "Sidechain").
445 pub name: &'static str,
446 /// Bus type (main or auxiliary).
447 pub bus_type: BusType,
448 /// Number of channels in this bus.
449 pub channel_count: u32,
450 /// Whether the bus is active by default.
451 pub is_default_active: bool,
452}
453
454impl Default for BusInfo {
455 fn default() -> Self {
456 Self {
457 name: "Main",
458 bus_type: BusType::Main,
459 channel_count: 2,
460 is_default_active: true,
461 }
462 }
463}
464
465impl BusInfo {
466 /// Create a stereo main bus.
467 pub const fn stereo(name: &'static str) -> Self {
468 Self {
469 name,
470 bus_type: BusType::Main,
471 channel_count: 2,
472 is_default_active: true,
473 }
474 }
475
476 /// Create a mono main bus.
477 pub const fn mono(name: &'static str) -> Self {
478 Self {
479 name,
480 bus_type: BusType::Main,
481 channel_count: 1,
482 is_default_active: true,
483 }
484 }
485
486 /// Create an auxiliary bus (e.g., sidechain).
487 pub const fn aux(name: &'static str, channel_count: u32) -> Self {
488 Self {
489 name,
490 bus_type: BusType::Aux,
491 channel_count,
492 is_default_active: false,
493 }
494 }
495}
496
497// =============================================================================
498// Processor Trait
499// =============================================================================
500
501/// The prepared processor - ready for audio processing.
502///
503/// This trait defines the DSP (Digital Signal Processing) interface that
504/// plugin implementations must provide. It is designed to be format-agnostic,
505/// meaning the same implementation can be wrapped for VST3, CLAP, or other
506/// plugin formats.
507///
508/// A `Processor` is created by calling [`Descriptor::prepare()`] with the
509/// audio configuration. Unlike the old design where `setup()` was called
510/// after construction, here the processor is created with valid configuration
511/// from the start - no placeholder values.
512///
513/// # Lifecycle
514///
515/// ```text
516/// Descriptor::default() -> Descriptor (unprepared, holds parameters)
517/// |
518/// v Descriptor::prepare(config)
519/// |
520/// v
521/// Processor (prepared, ready for audio)
522/// |
523/// v Processor::unprepare()
524/// |
525/// v
526/// Descriptor (unprepared, parameters preserved)
527/// ```
528///
529/// # Thread Safety
530///
531/// Implementors must be `Send` because the plugin may be moved between threads.
532/// The `process` method is called on the audio thread and must be real-time safe:
533/// - No allocations
534/// - No locks (use lock-free structures)
535/// - No syscalls
536/// - No unbounded loops
537///
538/// # Note on HasParameters
539///
540/// The `Processor` trait requires [`HasParameters`] as a supertrait, which provides
541/// the `parameters()` and `parameters_mut()` methods. Use `#[derive(HasParameters)]` with a
542/// `#[parameters]` field annotation to implement this automatically.
543pub trait Processor: HasParameters {
544 /// The unprepared definition type that created this processor.
545 ///
546 /// Used by [`Processor::unprepare()`] to return to the unprepared state.
547 /// The Parameters type must match the definition's Parameters type.
548 type Descriptor: Descriptor<Processor = Self, Parameters = Self::Parameters>;
549
550 /// Process an audio buffer with transport context.
551 ///
552 /// This is the main DSP entry point, called on the audio thread for each
553 /// block of audio. The buffer provides input samples and mutable output
554 /// buffers for the main bus.
555 ///
556 /// # Arguments
557 ///
558 /// * `buffer` - Main audio bus (stereo/surround input and output)
559 /// * `aux` - Auxiliary buses (sidechain, aux sends) - ignore if not needed
560 /// * `context` - Processing context with sample rate, buffer size, and transport info
561 ///
562 /// # Real-Time Safety
563 ///
564 /// This method must be real-time safe. Do not allocate, lock mutexes,
565 /// or perform any operation with unbounded execution time.
566 ///
567 /// # Example: Simple Gain
568 ///
569 /// ```ignore
570 /// fn process(&mut self, buffer: &mut Buffer, _aux: &mut AuxiliaryBuffers, _context: &ProcessContext) {
571 /// let gain = self.parameters.gain();
572 /// for (input, output) in buffer.zip_channels() {
573 /// for (i, o) in input.iter().zip(output.iter_mut()) {
574 /// *o = *i * gain;
575 /// }
576 /// }
577 /// }
578 /// ```
579 ///
580 /// # Example: Tempo-Synced LFO
581 ///
582 /// ```ignore
583 /// fn process(&mut self, buffer: &mut Buffer, _aux: &mut AuxiliaryBuffers, context: &ProcessContext) {
584 /// // Calculate LFO rate synced to host tempo
585 /// let lfo_hz = context.transport.tempo
586 /// .map(|tempo| tempo / 60.0 / 4.0) // 1 cycle per 4 beats
587 /// .unwrap_or(2.0); // Fallback: 2 Hz
588 ///
589 /// let increment = (lfo_hz * 2.0 * std::f32::consts::PI) / context.sample_rate as f32;
590 ///
591 /// for (input, output) in buffer.zip_channels() {
592 /// for (i, o) in input.iter().zip(output.iter_mut()) {
593 /// let lfo = self.phase.sin();
594 /// *o = *i * (1.0 + lfo * 0.5);
595 /// self.phase += increment;
596 /// }
597 /// }
598 /// }
599 /// ```
600 ///
601 /// # Example: Sidechain Ducker
602 ///
603 /// ```ignore
604 /// fn process(&mut self, buffer: &mut Buffer, aux: &mut AuxiliaryBuffers, _context: &ProcessContext) {
605 /// let duck = aux.sidechain()
606 /// .map(|sc| (sc.rms(0) * 4.0).min(1.0))
607 /// .unwrap_or(0.0);
608 ///
609 /// buffer.copy_to_output();
610 /// buffer.apply_output_gain(1.0 - duck * 0.8);
611 /// }
612 /// ```
613 fn process(&mut self, buffer: &mut Buffer, aux: &mut AuxiliaryBuffers, context: &ProcessContext);
614
615 /// Return to the unprepared definition state.
616 ///
617 /// This is used when sample rate or buffer configuration changes.
618 /// The processor is consumed and returns the original definition with
619 /// parameters preserved. The wrapper can then call `prepare()` again
620 /// with the new configuration.
621 ///
622 /// # Default Implementation
623 ///
624 /// The default implementation creates a new `Descriptor::default()` and
625 /// transfers the parameters from this processor to it. This works for
626 /// most plugins where the Descriptor struct only holds parameters.
627 ///
628 /// Override this method if your Descriptor has additional state beyond
629 /// parameters that needs to be preserved across unprepare/prepare cycles.
630 ///
631 /// # Example (custom implementation)
632 ///
633 /// ```ignore
634 /// impl Processor for DelayProcessor {
635 /// type Descriptor = DelayPlugin;
636 ///
637 /// fn unprepare(self) -> DelayPlugin {
638 /// DelayPlugin {
639 /// parameters: self.parameters,
640 /// // DSP state (delay_lines, etc.) is discarded
641 /// }
642 /// }
643 /// }
644 /// ```
645 fn unprepare(mut self) -> Self::Descriptor
646 where
647 Self: Sized,
648 {
649 let params = std::mem::take(self.parameters_mut());
650 let mut definition = Self::Descriptor::default();
651 definition.set_parameters(params);
652 definition
653 }
654
655 // Note: `parameters()` and `parameters_mut()` are provided by the `HasParameters` supertrait.
656 // Use `#[derive(HasParameters)]` with a `#[parameters]` field annotation to implement them.
657
658 // =========================================================================
659 // Activation State
660 // =========================================================================
661
662 /// Called when the plugin is activated or deactivated.
663 ///
664 /// Activation typically happens when the user inserts the plugin into a
665 /// track or opens a project. Deactivation happens when removed or project
666 /// is closed.
667 ///
668 /// **Important:** When `active == true`, you should reset your DSP state
669 /// (clear delay lines, reset filter histories, zero envelopes, etc.).
670 /// Hosts call `setActive(false)` followed by `setActive(true)` to request
671 /// a full state reset.
672 ///
673 /// # Example
674 ///
675 /// ```ignore
676 /// fn set_active(&mut self, active: bool) {
677 /// if active {
678 /// // Reset DSP state on activation
679 /// self.delay_line.clear();
680 /// self.envelope.reset();
681 /// self.filter_state = FilterState::default();
682 /// }
683 /// }
684 /// ```
685 ///
686 /// Default implementation does nothing.
687 fn set_active(&mut self, _active: bool) {}
688
689 /// Get the tail length in samples.
690 ///
691 /// This indicates how many samples of audio "tail" the plugin produces
692 /// after input stops (e.g., reverb decay). Return 0 for no tail, or
693 /// `u32::MAX` for infinite tail.
694 ///
695 /// Default returns 0 (no tail).
696 fn tail_samples(&self) -> u32 {
697 0
698 }
699
700 /// Get the latency in samples.
701 ///
702 /// If the plugin introduces processing latency (e.g., lookahead limiters),
703 /// return the latency in samples here. The host can use this for delay
704 /// compensation.
705 ///
706 /// Default returns 0 (no latency).
707 fn latency_samples(&self) -> u32 {
708 0
709 }
710
711 /// Get the bypass ramp length in samples.
712 ///
713 /// When bypass is engaged or disengaged, this defines the crossfade
714 /// duration to avoid clicks. The host uses this value (combined with
715 /// `tail_samples()`) to determine how long to continue calling `process()`
716 /// after input stops.
717 ///
718 /// Return 0 for instant bypass (no crossfade), or a sample count for
719 /// smooth crossfading. Typical values:
720 /// - 64 samples (~1.3ms at 48kHz) - fast, suitable for most effects
721 /// - 256 samples (~5.3ms at 48kHz) - smoother, for sensitive material
722 /// - 512+ samples - very smooth, for reverbs/delays with long tails
723 ///
724 /// Default returns 64 samples.
725 ///
726 /// # Example
727 ///
728 /// ```ignore
729 /// fn bypass_ramp_samples(&self) -> u32 {
730 /// // Use 10ms crossfade based on current sample rate
731 /// (self.sample_rate * 0.01) as u32
732 /// }
733 /// ```
734 fn bypass_ramp_samples(&self) -> u32 {
735 64
736 }
737
738 // =========================================================================
739 // 64-bit Processing Support
740 // =========================================================================
741
742 /// Returns true if the plugin supports native 64-bit (double precision) processing.
743 ///
744 /// Override this to return `true` if your plugin implements `process_f64()` natively.
745 /// When false (default), the framework will automatically convert 64-bit host buffers
746 /// to 32-bit, call `process()`, and convert back.
747 ///
748 /// # Performance Considerations
749 ///
750 /// - For most plugins, f32 is sufficient and the default conversion is fine
751 /// - Implement native f64 only if your DSP algorithm benefits from double precision
752 /// (e.g., IIR filters with long decay, precision-sensitive synthesis)
753 /// - The conversion overhead is minimal (~few microseconds per buffer)
754 ///
755 /// Default returns `false`.
756 fn supports_double_precision(&self) -> bool {
757 false
758 }
759
760 /// Process an audio buffer at 64-bit (double) precision.
761 ///
762 /// This is the f64 equivalent of `process()`. Override this method AND
763 /// return `true` from `supports_double_precision()` to enable native
764 /// 64-bit processing.
765 ///
766 /// If `supports_double_precision()` returns `false`, this method is never
767 /// called - the framework converts to f32 and calls `process()` instead.
768 ///
769 /// # Default Implementation
770 ///
771 /// The default implementation converts f64→f32, calls `process()`, then
772 /// converts f32→f64. This allows any plugin to work in a 64-bit host
773 /// without modification.
774 ///
775 /// # Example: Native f64 Plugin
776 ///
777 /// ```ignore
778 /// fn supports_double_precision(&self) -> bool {
779 /// true
780 /// }
781 ///
782 /// fn process_f64(
783 /// &mut self,
784 /// buffer: &mut Buffer<f64>,
785 /// aux: &mut AuxiliaryBuffers<f64>,
786 /// context: &ProcessContext,
787 /// ) {
788 /// let gain = self.parameters.gain_linear() as f64;
789 /// for (input, output) in buffer.zip_channels() {
790 /// for (i, o) in input.iter().zip(output.iter_mut()) {
791 /// *o = *i * gain;
792 /// }
793 /// }
794 /// }
795 /// ```
796 fn process_f64(
797 &mut self,
798 buffer: &mut Buffer<f64>,
799 _aux: &mut AuxiliaryBuffers<f64>,
800 context: &ProcessContext,
801 ) {
802 // Default implementation: convert f64 → f32, process, convert back
803 //
804 // NOTE: This is a fallback implementation that allocates memory.
805 // In practice, this method is rarely called because:
806 // - The VST3 wrapper handles conversion with pre-allocated buffers
807 // (see `process_audio_f64_converted` in beamer-vst3/src/processor.rs)
808 // - Future format wrappers (CLAP, etc.) should also pre-allocate
809 //
810 // If you're implementing a custom wrapper, ensure you handle
811 // f64→f32 conversion with pre-allocated buffers for real-time safety.
812
813 let num_samples = buffer.num_samples();
814 let num_input_channels = buffer.num_input_channels();
815 let num_output_channels = buffer.num_output_channels();
816
817 // Allocate conversion buffers (VST3 wrapper uses pre-allocated buffers,
818 // this is only for the fallback default implementation)
819 let input_f32: Vec<Vec<f32>> = (0..num_input_channels)
820 .map(|ch| buffer.input(ch).iter().map(|&s| s as f32).collect())
821 .collect();
822 let mut output_f32: Vec<Vec<f32>> = (0..num_output_channels)
823 .map(|_| vec![0.0f32; num_samples])
824 .collect();
825
826 // Build f32 buffer slices
827 let input_slices: Vec<&[f32]> = input_f32.iter().map(|v| v.as_slice()).collect();
828 let output_slices: Vec<&mut [f32]> = output_f32
829 .iter_mut()
830 .map(|v| v.as_mut_slice())
831 .collect();
832
833 let mut buffer_f32 = Buffer::new(input_slices, output_slices, num_samples);
834
835 // For aux buffers, we use empty for now (full aux conversion is complex)
836 // The VST3 wrapper handles proper aux conversion
837 let mut aux_f32: AuxiliaryBuffers<f32> = AuxiliaryBuffers::empty();
838
839 // Process at f32
840 self.process(&mut buffer_f32, &mut aux_f32, context);
841
842 // Convert output back to f64
843 for (ch, output_samples) in output_f32.iter().enumerate().take(num_output_channels) {
844 let output_ch = buffer.output(ch);
845 for (i, sample) in output_samples.iter().enumerate() {
846 output_ch[i] = *sample as f64;
847 }
848 }
849 }
850
851 /// Save the plugin state to bytes.
852 ///
853 /// This is called when the DAW saves a project or preset. The returned
854 /// bytes should contain all state needed to restore the plugin to its
855 /// current configuration.
856 ///
857 /// The default implementation delegates to `Parameters::save_state()`,
858 /// which serializes all parameter values. Override this method if you
859 /// need to save additional state beyond parameters.
860 fn save_state(&self) -> PluginResult<Vec<u8>> {
861 Ok(self.parameters().save_state())
862 }
863
864 /// Load the plugin state from bytes.
865 ///
866 /// This is called when the DAW loads a project or preset. The data is
867 /// the same bytes returned from a previous `save_state` call.
868 ///
869 /// The default implementation delegates to `Parameters::load_state()`,
870 /// which restores all parameter values. Override this method if you
871 /// need to load additional state beyond parameters.
872 fn load_state(&mut self, data: &[u8]) -> PluginResult<()> {
873 self.parameters_mut()
874 .load_state(data)
875 .map_err(PluginError::StateError)
876 }
877
878 // =========================================================================
879 // MIDI Processing
880 // =========================================================================
881
882 /// Process MIDI events.
883 ///
884 /// Called during processing with any incoming MIDI events. Plugins can
885 /// transform events and add them to the output buffer, pass them through
886 /// unchanged, or consume them entirely.
887 ///
888 /// # Arguments
889 /// * `input` - Slice of incoming MIDI events (sorted by sample_offset)
890 /// * `output` - Buffer to write output MIDI events to
891 ///
892 /// # Real-Time Safety
893 ///
894 /// This method must be real-time safe. Do not allocate, lock mutexes,
895 /// or perform any operation with unbounded execution time.
896 ///
897 /// **Note:** Cloning a `SysEx` event allocates due to `Box<SysEx>`. SysEx
898 /// events are rare in typical use cases. If strict real-time safety is
899 /// required, override this method to handle SysEx specially.
900 ///
901 /// # Default Implementation
902 ///
903 /// The default implementation passes all events through unchanged.
904 fn process_midi(&mut self, input: &[MidiEvent], output: &mut MidiBuffer) {
905 for event in input {
906 output.push(event.clone());
907 }
908 }
909
910 /// Returns whether this plugin processes MIDI events.
911 ///
912 /// Override to return `true` if your plugin needs MIDI input/output.
913 /// This is used by the host to determine event bus configuration.
914 ///
915 /// Default returns `false`.
916 fn wants_midi(&self) -> bool {
917 false
918 }
919
920}
921
922// =============================================================================
923// Descriptor Trait
924// =============================================================================
925
926/// The unprepared plugin definition - holds parameters before audio config is known.
927///
928/// This is the primary trait that plugin authors implement to create a complete
929/// audio plugin. It holds parameters and configuration that doesn't depend on
930/// sample rate, and transforms into a [`Processor`] via [`Descriptor::prepare()`]
931/// when audio configuration becomes available.
932///
933/// # Two-Phase Lifecycle
934///
935/// ```text
936/// Descriptor::default() -> Descriptor (unprepared, holds parameters)
937/// |
938/// v Descriptor::prepare(config)
939/// |
940/// v
941/// Processor (prepared, ready for audio)
942/// |
943/// v Processor::unprepare()
944/// |
945/// v
946/// Descriptor (unprepared, parameters preserved)
947/// ```
948///
949/// # Example: Simple Gain (no setup)
950///
951/// ```ignore
952/// #[derive(Default, HasParameters)]
953/// pub struct GainPlugin {
954/// #[parameters]
955/// parameters: GainParameters,
956/// }
957///
958/// impl Descriptor for GainPlugin {
959/// type Setup = ();
960/// type Processor = GainProcessor;
961///
962/// fn prepare(self, _: ()) -> GainProcessor {
963/// GainProcessor { parameters: self.parameters }
964/// }
965/// }
966///
967/// #[derive(HasParameters)]
968/// pub struct GainProcessor {
969/// #[parameters]
970/// parameters: GainParameters,
971/// }
972///
973/// impl Processor for GainProcessor {
974/// type Descriptor = GainPlugin;
975///
976/// fn process(&mut self, buffer: &mut Buffer, _aux: &mut AuxiliaryBuffers, _context: &ProcessContext) {
977/// let gain = self.parameters.gain_linear();
978/// for (input, output) in buffer.zip_channels() {
979/// for (i, o) in input.iter().zip(output.iter_mut()) {
980/// *o = *i * gain;
981/// }
982/// }
983/// }
984///
985/// fn unprepare(self) -> GainPlugin {
986/// GainPlugin { parameters: self.parameters }
987/// }
988/// }
989/// ```
990///
991/// # Example: Delay (SampleRate)
992///
993/// ```ignore
994/// #[derive(Default, HasParameters)]
995/// pub struct DelayPlugin {
996/// #[parameters]
997/// parameters: DelayParameters,
998/// }
999///
1000/// impl Descriptor for DelayPlugin {
1001/// type Setup = SampleRate;
1002/// type Processor = DelayProcessor;
1003///
1004/// fn prepare(self, setup: SampleRate) -> DelayProcessor {
1005/// let buffer_size = (MAX_DELAY_SECONDS * setup.hz()) as usize;
1006/// DelayProcessor {
1007/// parameters: self.parameters,
1008/// sample_rate: setup.hz(), // Real value from start!
1009/// buffer: vec![0.0; buffer_size], // Correct allocation!
1010/// }
1011/// }
1012/// }
1013/// ```
1014///
1015/// # Note on HasParameters
1016///
1017/// The `Descriptor` trait requires [`HasParameters`] as a supertrait, which provides the
1018/// `parameters()` and `parameters_mut()` methods. Use `#[derive(HasParameters)]` with a
1019/// `#[parameters]` field annotation to implement this automatically.
1020pub trait Descriptor: HasParameters + Default {
1021 /// The setup information this plugin needs to prepare.
1022 ///
1023 /// Request exactly what you need:
1024 /// - `()`: No setup needed (gain, pan)
1025 /// - [`SampleRate`]: Time-based DSP (delay, filter, envelope) - most common
1026 /// - `(SampleRate, MaxBufferSize)`: FFT, lookahead
1027 /// - `(SampleRate, MainOutputChannels)`: Surround, per-channel state
1028 ///
1029 /// See [`PluginSetup`] for all available types.
1030 type Setup: PluginSetup;
1031
1032 /// The prepared processor type created by [`Descriptor::prepare()`].
1033 type Processor: Processor<Descriptor = Self, Parameters = Self::Parameters>;
1034
1035 /// Transform this definition into a prepared processor.
1036 ///
1037 /// This is called when audio configuration becomes available (in VST3,
1038 /// during `setupProcessing()`). The definition is consumed and transformed
1039 /// into a processor with valid configuration - no placeholder values.
1040 ///
1041 /// # Arguments
1042 ///
1043 /// * `setup` - The setup information (sample rate, buffer size, layout)
1044 ///
1045 /// # Returns
1046 ///
1047 /// A prepared processor ready for audio processing.
1048 fn prepare(self, setup: Self::Setup) -> Self::Processor;
1049
1050 // =========================================================================
1051 // Bus Configuration (static, known before prepare)
1052 // =========================================================================
1053
1054 /// Returns the number of audio input buses.
1055 ///
1056 /// Default returns 1 (single stereo input).
1057 fn input_bus_count(&self) -> usize {
1058 1
1059 }
1060
1061 /// Returns the number of audio output buses.
1062 ///
1063 /// Default returns 1 (single stereo output).
1064 fn output_bus_count(&self) -> usize {
1065 1
1066 }
1067
1068 /// Returns information about an input bus.
1069 ///
1070 /// Default returns a stereo main bus for index 0.
1071 fn input_bus_info(&self, index: usize) -> Option<BusInfo> {
1072 if index == 0 {
1073 Some(BusInfo::stereo("Input"))
1074 } else {
1075 None
1076 }
1077 }
1078
1079 /// Returns information about an output bus.
1080 ///
1081 /// Default returns a stereo main bus for index 0.
1082 fn output_bus_info(&self, index: usize) -> Option<BusInfo> {
1083 if index == 0 {
1084 Some(BusInfo::stereo("Output"))
1085 } else {
1086 None
1087 }
1088 }
1089
1090 /// Returns whether this plugin processes MIDI events.
1091 ///
1092 /// Override to return `true` if your plugin needs MIDI input/output.
1093 /// This is used by the host to determine event bus configuration.
1094 ///
1095 /// **Note:** This method is also on [`Processor`], but the Descriptor
1096 /// version is queried during bus configuration (before prepare).
1097 /// Both should return the same value.
1098 ///
1099 /// Default returns `false`.
1100 fn wants_midi(&self) -> bool {
1101 false
1102 }
1103
1104 // =========================================================================
1105 // MIDI Mapping (IMidiMapping)
1106 // =========================================================================
1107
1108 /// Get the parameter ID mapped to a MIDI CC.
1109 ///
1110 /// Override this to enable DAW MIDI learn for your parameters. When the
1111 /// DAW queries which parameter is assigned to a MIDI CC, this method is
1112 /// called.
1113 ///
1114 /// # Arguments
1115 /// * `bus_index` - MIDI bus index (usually 0)
1116 /// * `channel` - MIDI channel (0-15), or -1 to query all channels
1117 /// * `cc` - MIDI CC number (0-127)
1118 ///
1119 /// # Returns
1120 /// `Some(parameter_id)` if this CC is mapped to a parameter, `None` otherwise.
1121 ///
1122 /// # Example
1123 /// ```ignore
1124 /// fn midi_cc_to_parameter(&self, _bus: i32, _channel: i16, cc: u8) -> Option<u32> {
1125 /// match cc {
1126 /// cc::MOD_WHEEL => Some(PARAM_VIBRATO_DEPTH),
1127 /// cc::EXPRESSION => Some(PARAM_VOLUME),
1128 /// _ => None,
1129 /// }
1130 /// }
1131 /// ```
1132 fn midi_cc_to_parameter(&self, bus_index: i32, channel: i16, cc: u8) -> Option<u32> {
1133 let _ = (bus_index, channel, cc);
1134 None
1135 }
1136
1137 // =========================================================================
1138 // MIDI CC Configuration (VST3 IMidiMapping hidden parameters)
1139 // =========================================================================
1140
1141 /// Returns MIDI CC configuration for automatic host mapping.
1142 ///
1143 /// Override to enable MIDI CC/pitch bend/aftertouch reception via IMidiMapping.
1144 /// The framework will:
1145 /// 1. Create hidden parameters for each enabled controller
1146 /// 2. Handle IMidiMapping queries from the DAW
1147 /// 3. Convert parameter changes to MidiEvents in process_midi()
1148 /// 4. Provide direct CC value access via ProcessContext::midi_cc()
1149 ///
1150 /// This solves the VST3 MIDI input problem where most DAWs don't send
1151 /// `kLegacyMIDICCOutEvent` for input. Instead, they use the `IMidiMapping`
1152 /// interface to map MIDI controllers to parameters.
1153 ///
1154 /// # Example
1155 ///
1156 /// ```ignore
1157 /// impl Descriptor for MySynth {
1158 /// fn midi_cc_config(&self) -> Option<MidiCcConfig> {
1159 /// Some(MidiCcConfig::new()
1160 /// .with_pitch_bend()
1161 /// .with_mod_wheel()
1162 /// .with_aftertouch()
1163 /// .with_ccs(&[7, 10, 11, 64])) // Volume, Pan, Expression, Sustain
1164 /// }
1165 /// }
1166 ///
1167 /// // Access CC values during processing:
1168 /// fn process(&mut self, buffer: &mut Buffer, _aux: &mut AuxiliaryBuffers, context: &ProcessContext) {
1169 /// if let Some(cc) = context.midi_cc() {
1170 /// let pitch_bend = cc.pitch_bend(); // -1.0 to 1.0
1171 /// let mod_wheel = cc.mod_wheel(); // 0.0 to 1.0
1172 /// }
1173 /// }
1174 /// ```
1175 fn midi_cc_config(&self) -> Option<MidiCcConfig> {
1176 None
1177 }
1178
1179 // =========================================================================
1180 // MIDI Learn (IMidiLearn)
1181 // =========================================================================
1182
1183 /// Called by DAW when live MIDI CC input is received during learn mode.
1184 ///
1185 /// Override this to implement MIDI learn in your plugin UI. When the user
1186 /// enables "MIDI Learn" mode and moves a MIDI CC knob, the DAW calls this
1187 /// method so the plugin can map that CC to a parameter.
1188 ///
1189 /// # Arguments
1190 /// * `bus_index` - MIDI bus index (usually 0)
1191 /// * `channel` - MIDI channel (0-15)
1192 /// * `cc` - MIDI CC number that was moved
1193 ///
1194 /// # Returns
1195 /// `true` if the input was handled (learned), `false` otherwise.
1196 fn on_midi_learn(&mut self, bus_index: i32, channel: i16, cc: u8) -> bool {
1197 let _ = (bus_index, channel, cc);
1198 false
1199 }
1200
1201 // =========================================================================
1202 // MIDI 2.0 Mapping (IMidiMapping2)
1203 // =========================================================================
1204
1205 /// Get all MIDI 1.0 CC assignments for bulk query.
1206 ///
1207 /// Override to provide mappings for DAW queries. This is more efficient
1208 /// than individual `midi_cc_to_parameter` queries when there are many mappings.
1209 ///
1210 /// Default returns empty slice (no mappings).
1211 fn midi1_assignments(&self) -> &[Midi1Assignment] {
1212 &[]
1213 }
1214
1215 /// Get all MIDI 2.0 controller assignments for bulk query.
1216 ///
1217 /// Override to provide MIDI 2.0 Registered/Assignable controller mappings.
1218 ///
1219 /// Default returns empty slice (no mappings).
1220 fn midi2_assignments(&self) -> &[Midi2Assignment] {
1221 &[]
1222 }
1223
1224 // =========================================================================
1225 // MIDI 2.0 Learn (IMidiLearn2)
1226 // =========================================================================
1227
1228 /// Called when MIDI 1.0 CC input is received during learn mode.
1229 ///
1230 /// This is the MIDI 2.0 version of `on_midi_learn` with separate methods
1231 /// for MIDI 1.0 and MIDI 2.0 controllers.
1232 ///
1233 /// Default returns `false` (not handled).
1234 fn on_midi1_learn(&mut self, bus_index: i32, channel: u8, cc: u8) -> bool {
1235 let _ = (bus_index, channel, cc);
1236 false
1237 }
1238
1239 /// Called when MIDI 2.0 controller input is received during learn mode.
1240 ///
1241 /// Override to implement MIDI 2.0 controller learning.
1242 ///
1243 /// Default returns `false` (not handled).
1244 fn on_midi2_learn(&mut self, bus_index: i32, channel: u8, controller: Midi2Controller) -> bool {
1245 let _ = (bus_index, channel, controller);
1246 false
1247 }
1248
1249 // =========================================================================
1250 // Note Expression Controller (INoteExpressionController - VST3 SDK 3.5.0)
1251 // =========================================================================
1252
1253 /// Returns the number of supported note expression types.
1254 ///
1255 /// Override to advertise which note expressions your plugin supports
1256 /// (e.g., volume, pan, tuning for MPE instruments).
1257 ///
1258 /// Default returns 0 (no note expressions).
1259 fn note_expression_count(&self, bus_index: i32, channel: i16) -> usize {
1260 let _ = (bus_index, channel);
1261 0
1262 }
1263
1264 /// Returns information about a note expression type by index.
1265 ///
1266 /// Override to provide details about each supported expression type.
1267 ///
1268 /// Default returns None.
1269 fn note_expression_info(
1270 &self,
1271 bus_index: i32,
1272 channel: i16,
1273 index: usize,
1274 ) -> Option<NoteExpressionTypeInfo> {
1275 let _ = (bus_index, channel, index);
1276 None
1277 }
1278
1279 /// Converts a normalized note expression value to a display string.
1280 ///
1281 /// Override to provide custom formatting (e.g., "2.5 semitones" for tuning).
1282 ///
1283 /// Default returns the value as a percentage.
1284 fn note_expression_value_to_string(
1285 &self,
1286 bus_index: i32,
1287 channel: i16,
1288 type_id: u32,
1289 value: f64,
1290 ) -> String {
1291 let _ = (bus_index, channel, type_id);
1292 format!("{:.1}%", value * 100.0)
1293 }
1294
1295 /// Parses a string to a normalized note expression value.
1296 ///
1297 /// Override to support custom parsing.
1298 ///
1299 /// Default returns None (parsing not supported).
1300 fn note_expression_string_to_value(
1301 &self,
1302 bus_index: i32,
1303 channel: i16,
1304 type_id: u32,
1305 string: &str,
1306 ) -> Option<f64> {
1307 let _ = (bus_index, channel, type_id, string);
1308 None
1309 }
1310
1311 // =========================================================================
1312 // Keyswitch Controller (IKeyswitchController - VST3 SDK 3.5.0)
1313 // =========================================================================
1314
1315 /// Returns the number of keyswitches (articulations).
1316 ///
1317 /// Override for sample libraries and orchestral instruments that
1318 /// support keyswitching between articulations.
1319 ///
1320 /// Default returns 0 (no keyswitches).
1321 fn keyswitch_count(&self, bus_index: i32, channel: i16) -> usize {
1322 let _ = (bus_index, channel);
1323 0
1324 }
1325
1326 /// Returns information about a keyswitch by index.
1327 ///
1328 /// Override to provide keyswitch details for DAW expression maps.
1329 ///
1330 /// Default returns None.
1331 fn keyswitch_info(&self, bus_index: i32, channel: i16, index: usize) -> Option<KeyswitchInfo> {
1332 let _ = (bus_index, channel, index);
1333 None
1334 }
1335
1336 // =========================================================================
1337 // Physical UI Mapping (INoteExpressionPhysicalUIMapping - VST3 SDK 3.6.11)
1338 // =========================================================================
1339
1340 /// Returns mappings from physical UI controllers to note expressions.
1341 ///
1342 /// Override to define how MPE controllers (X-axis, Y-axis, Pressure)
1343 /// map to your plugin's note expression types.
1344 ///
1345 /// # Example
1346 /// ```ignore
1347 /// fn physical_ui_mappings(&self, _bus: i32, _channel: i16) -> &[PhysicalUIMap] {
1348 /// &[
1349 /// PhysicalUIMap::y_axis(note_expression::BRIGHTNESS),
1350 /// PhysicalUIMap::pressure(note_expression::EXPRESSION),
1351 /// ]
1352 /// }
1353 /// ```
1354 ///
1355 /// Default returns empty slice (no mappings).
1356 fn physical_ui_mappings(&self, bus_index: i32, channel: i16) -> &[PhysicalUIMap] {
1357 let _ = (bus_index, channel);
1358 &[]
1359 }
1360
1361 // =========================================================================
1362 // MPE Wrapper Support (IVst3WrapperMPESupport - VST3 SDK 3.6.12)
1363 // =========================================================================
1364
1365 /// Called to enable or disable MPE input processing.
1366 ///
1367 /// Override to handle MPE enable/disable notifications from wrappers.
1368 ///
1369 /// Default does nothing and returns true.
1370 fn enable_mpe_input_processing(&mut self, enabled: bool) -> bool {
1371 let _ = enabled;
1372 true
1373 }
1374
1375 /// Called when the MPE input device settings change.
1376 ///
1377 /// Override to receive MPE zone configuration from wrappers.
1378 ///
1379 /// Default does nothing and returns true.
1380 fn set_mpe_input_device_settings(&mut self, settings: MpeInputDeviceSettings) -> bool {
1381 let _ = settings;
1382 true
1383 }
1384}
1385
1386// =============================================================================
1387// MIDI Mapping Types
1388// =============================================================================
1389
1390/// Base assignment info for MIDI controller → parameter mapping.
1391#[derive(Debug, Clone, Copy)]
1392pub struct MidiControllerAssignment {
1393 /// Parameter ID this controller maps to.
1394 pub parameter_id: u32,
1395 /// MIDI bus index.
1396 pub bus_index: i32,
1397 /// MIDI channel (0-15).
1398 pub channel: u8,
1399}
1400
1401/// MIDI 1.0 CC assignment.
1402///
1403/// Maps a MIDI 1.0 Control Change to a parameter.
1404#[derive(Debug, Clone, Copy)]
1405pub struct Midi1Assignment {
1406 /// Base assignment info (parameter_id, bus, channel).
1407 pub assignment: MidiControllerAssignment,
1408 /// CC number (0-127).
1409 pub controller: u8,
1410}
1411
1412impl Midi1Assignment {
1413 /// Create a new MIDI 1.0 CC assignment.
1414 pub const fn new(parameter_id: u32, bus_index: i32, channel: u8, controller: u8) -> Self {
1415 Self {
1416 assignment: MidiControllerAssignment {
1417 parameter_id,
1418 bus_index,
1419 channel,
1420 },
1421 controller,
1422 }
1423 }
1424
1425 /// Create an assignment for the default bus and all channels.
1426 pub const fn simple(parameter_id: u32, controller: u8) -> Self {
1427 Self::new(parameter_id, 0, 0, controller)
1428 }
1429}
1430
1431/// MIDI 2.0 controller assignment.
1432///
1433/// Maps a MIDI 2.0 Registered/Assignable Controller to a parameter.
1434#[derive(Debug, Clone, Copy)]
1435pub struct Midi2Assignment {
1436 /// Base assignment info (parameter_id, bus, channel).
1437 pub assignment: MidiControllerAssignment,
1438 /// MIDI 2.0 controller identifier.
1439 pub controller: Midi2Controller,
1440}
1441
1442impl Midi2Assignment {
1443 /// Create a new MIDI 2.0 controller assignment.
1444 pub const fn new(
1445 parameter_id: u32,
1446 bus_index: i32,
1447 channel: u8,
1448 controller: Midi2Controller,
1449 ) -> Self {
1450 Self {
1451 assignment: MidiControllerAssignment {
1452 parameter_id,
1453 bus_index,
1454 channel,
1455 },
1456 controller,
1457 }
1458 }
1459
1460 /// Create an assignment for the default bus and all channels.
1461 pub const fn simple(parameter_id: u32, controller: Midi2Controller) -> Self {
1462 Self::new(parameter_id, 0, 0, controller)
1463 }
1464}