Skip to main content

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}