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 AU, VST3 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        // - Format wrappers (AU, VST3) handle conversion with pre-allocated buffers
807        //   (see respective processor implementations)
808        //
809        // If you're implementing a custom wrapper, ensure you handle
810        // f64→f32 conversion with pre-allocated buffers for real-time safety.
811
812        let num_samples = buffer.num_samples();
813        let num_input_channels = buffer.num_input_channels();
814        let num_output_channels = buffer.num_output_channels();
815
816        // Allocate conversion buffers (VST3 wrapper uses pre-allocated buffers,
817        // this is only for the fallback default implementation)
818        let input_f32: Vec<Vec<f32>> = (0..num_input_channels)
819            .map(|ch| buffer.input(ch).iter().map(|&s| s as f32).collect())
820            .collect();
821        let mut output_f32: Vec<Vec<f32>> = (0..num_output_channels)
822            .map(|_| vec![0.0f32; num_samples])
823            .collect();
824
825        // Build f32 buffer slices
826        let input_slices: Vec<&[f32]> = input_f32.iter().map(|v| v.as_slice()).collect();
827        let output_slices: Vec<&mut [f32]> = output_f32
828            .iter_mut()
829            .map(|v| v.as_mut_slice())
830            .collect();
831
832        let mut buffer_f32 = Buffer::new(input_slices, output_slices, num_samples);
833
834        // For aux buffers, we use empty for now (full aux conversion is complex)
835        // The VST3 wrapper handles proper aux conversion
836        let mut aux_f32: AuxiliaryBuffers<f32> = AuxiliaryBuffers::empty();
837
838        // Process at f32
839        self.process(&mut buffer_f32, &mut aux_f32, context);
840
841        // Convert output back to f64
842        for (ch, output_samples) in output_f32.iter().enumerate().take(num_output_channels) {
843            let output_ch = buffer.output(ch);
844            for (i, sample) in output_samples.iter().enumerate() {
845                output_ch[i] = *sample as f64;
846            }
847        }
848    }
849
850    /// Save the plugin state to bytes.
851    ///
852    /// This is called when the DAW saves a project or preset. The returned
853    /// bytes should contain all state needed to restore the plugin to its
854    /// current configuration.
855    ///
856    /// The default implementation delegates to `Parameters::save_state()`,
857    /// which serializes all parameter values. Override this method if you
858    /// need to save additional state beyond parameters.
859    fn save_state(&self) -> PluginResult<Vec<u8>> {
860        Ok(self.parameters().save_state())
861    }
862
863    /// Load the plugin state from bytes.
864    ///
865    /// This is called when the DAW loads a project or preset. The data is
866    /// the same bytes returned from a previous `save_state` call.
867    ///
868    /// The default implementation delegates to `Parameters::load_state()`,
869    /// which restores all parameter values. Override this method if you
870    /// need to load additional state beyond parameters.
871    fn load_state(&mut self, data: &[u8]) -> PluginResult<()> {
872        self.parameters_mut()
873            .load_state(data)
874            .map_err(PluginError::StateError)
875    }
876
877    // =========================================================================
878    // MIDI Processing
879    // =========================================================================
880
881    /// Process MIDI events.
882    ///
883    /// Called during processing with any incoming MIDI events. Plugins can
884    /// transform events and add them to the output buffer, pass them through
885    /// unchanged, or consume them entirely.
886    ///
887    /// # Arguments
888    /// * `input` - Slice of incoming MIDI events (sorted by sample_offset)
889    /// * `output` - Buffer to write output MIDI events to
890    ///
891    /// # Real-Time Safety
892    ///
893    /// This method must be real-time safe. Do not allocate, lock mutexes,
894    /// or perform any operation with unbounded execution time.
895    ///
896    /// **Note:** Cloning a `SysEx` event allocates due to `Box<SysEx>`. SysEx
897    /// events are rare in typical use cases. If strict real-time safety is
898    /// required, override this method to handle SysEx specially.
899    ///
900    /// # Default Implementation
901    ///
902    /// The default implementation passes all events through unchanged.
903    fn process_midi(&mut self, input: &[MidiEvent], output: &mut MidiBuffer) {
904        for event in input {
905            output.push(event.clone());
906        }
907    }
908
909    /// Returns whether this plugin processes MIDI events.
910    ///
911    /// Override to return `true` if your plugin needs MIDI input/output.
912    /// This is used by the host to determine event bus configuration.
913    ///
914    /// Default returns `false`.
915    fn wants_midi(&self) -> bool {
916        false
917    }
918
919}
920
921// =============================================================================
922// Descriptor Trait
923// =============================================================================
924
925/// The unprepared plugin definition - holds parameters before audio config is known.
926///
927/// This is the primary trait that plugin authors implement to create a complete
928/// audio plugin. It holds parameters and configuration that doesn't depend on
929/// sample rate, and transforms into a [`Processor`] via [`Descriptor::prepare()`]
930/// when audio configuration becomes available.
931///
932/// # Two-Phase Lifecycle
933///
934/// ```text
935/// Descriptor::default() -> Descriptor (unprepared, holds parameters)
936///                      |
937///                      v  Descriptor::prepare(config)
938///                      |
939///                      v
940///                Processor (prepared, ready for audio)
941///                      |
942///                      v  Processor::unprepare()
943///                      |
944///                      v
945///                 Descriptor (unprepared, parameters preserved)
946/// ```
947///
948/// # Example: Simple Gain (no setup)
949///
950/// ```ignore
951/// #[derive(Default, HasParameters)]
952/// pub struct GainPlugin {
953///     #[parameters]
954///     parameters: GainParameters,
955/// }
956///
957/// impl Descriptor for GainPlugin {
958///     type Setup = ();
959///     type Processor = GainProcessor;
960///
961///     fn prepare(self, _: ()) -> GainProcessor {
962///         GainProcessor { parameters: self.parameters }
963///     }
964/// }
965///
966/// #[derive(HasParameters)]
967/// pub struct GainProcessor {
968///     #[parameters]
969///     parameters: GainParameters,
970/// }
971///
972/// impl Processor for GainProcessor {
973///     type Descriptor = GainPlugin;
974///
975///     fn process(&mut self, buffer: &mut Buffer, _aux: &mut AuxiliaryBuffers, _context: &ProcessContext) {
976///         let gain = self.parameters.gain_linear();
977///         for (input, output) in buffer.zip_channels() {
978///             for (i, o) in input.iter().zip(output.iter_mut()) {
979///                 *o = *i * gain;
980///             }
981///         }
982///     }
983///
984///     fn unprepare(self) -> GainPlugin {
985///         GainPlugin { parameters: self.parameters }
986///     }
987/// }
988/// ```
989///
990/// # Example: Delay (SampleRate)
991///
992/// ```ignore
993/// #[derive(Default, HasParameters)]
994/// pub struct DelayPlugin {
995///     #[parameters]
996///     parameters: DelayParameters,
997/// }
998///
999/// impl Descriptor for DelayPlugin {
1000///     type Setup = SampleRate;
1001///     type Processor = DelayProcessor;
1002///
1003///     fn prepare(self, setup: SampleRate) -> DelayProcessor {
1004///         let buffer_size = (MAX_DELAY_SECONDS * setup.hz()) as usize;
1005///         DelayProcessor {
1006///             parameters: self.parameters,
1007///             sample_rate: setup.hz(),  // Real value from start!
1008///             buffer: vec![0.0; buffer_size],   // Correct allocation!
1009///         }
1010///     }
1011/// }
1012/// ```
1013///
1014/// # Note on HasParameters
1015///
1016/// The `Descriptor` trait requires [`HasParameters`] as a supertrait, which provides the
1017/// `parameters()` and `parameters_mut()` methods. Use `#[derive(HasParameters)]` with a
1018/// `#[parameters]` field annotation to implement this automatically.
1019pub trait Descriptor: HasParameters + Default {
1020    /// The setup information this plugin needs to prepare.
1021    ///
1022    /// Request exactly what you need:
1023    /// - `()`: No setup needed (gain, pan)
1024    /// - [`SampleRate`]: Time-based DSP (delay, filter, envelope) - most common
1025    /// - `(SampleRate, MaxBufferSize)`: FFT, lookahead
1026    /// - `(SampleRate, MainOutputChannels)`: Surround, per-channel state
1027    ///
1028    /// See [`PluginSetup`] for all available types.
1029    type Setup: PluginSetup;
1030
1031    /// The prepared processor type created by [`Descriptor::prepare()`].
1032    type Processor: Processor<Descriptor = Self, Parameters = Self::Parameters>;
1033
1034    /// Transform this definition into a prepared processor.
1035    ///
1036    /// This is called when audio configuration becomes available (in VST3,
1037    /// during `setupProcessing()`). The definition is consumed and transformed
1038    /// into a processor with valid configuration - no placeholder values.
1039    ///
1040    /// # Arguments
1041    ///
1042    /// * `setup` - The setup information (sample rate, buffer size, layout)
1043    ///
1044    /// # Returns
1045    ///
1046    /// A prepared processor ready for audio processing.
1047    fn prepare(self, setup: Self::Setup) -> Self::Processor;
1048
1049    // =========================================================================
1050    // Bus Configuration (static, known before prepare)
1051    // =========================================================================
1052
1053    /// Returns the number of audio input buses.
1054    ///
1055    /// Default returns 1 (single stereo input).
1056    fn input_bus_count(&self) -> usize {
1057        1
1058    }
1059
1060    /// Returns the number of audio output buses.
1061    ///
1062    /// Default returns 1 (single stereo output).
1063    fn output_bus_count(&self) -> usize {
1064        1
1065    }
1066
1067    /// Returns information about an input bus.
1068    ///
1069    /// Default returns a stereo main bus for index 0.
1070    fn input_bus_info(&self, index: usize) -> Option<BusInfo> {
1071        if index == 0 {
1072            Some(BusInfo::stereo("Input"))
1073        } else {
1074            None
1075        }
1076    }
1077
1078    /// Returns information about an output bus.
1079    ///
1080    /// Default returns a stereo main bus for index 0.
1081    fn output_bus_info(&self, index: usize) -> Option<BusInfo> {
1082        if index == 0 {
1083            Some(BusInfo::stereo("Output"))
1084        } else {
1085            None
1086        }
1087    }
1088
1089    /// Returns whether this plugin processes MIDI events.
1090    ///
1091    /// Override to return `true` if your plugin needs MIDI input/output.
1092    /// This is used by the host to determine event bus configuration.
1093    ///
1094    /// **Note:** This method is also on [`Processor`], but the Descriptor
1095    /// version is queried during bus configuration (before prepare).
1096    /// Both should return the same value.
1097    ///
1098    /// Default returns `false`.
1099    fn wants_midi(&self) -> bool {
1100        false
1101    }
1102
1103    // =========================================================================
1104    // MIDI Mapping (IMidiMapping)
1105    // =========================================================================
1106
1107    /// Get the parameter ID mapped to a MIDI CC.
1108    ///
1109    /// Override this to enable DAW MIDI learn for your parameters. When the
1110    /// DAW queries which parameter is assigned to a MIDI CC, this method is
1111    /// called.
1112    ///
1113    /// # Arguments
1114    /// * `bus_index` - MIDI bus index (usually 0)
1115    /// * `channel` - MIDI channel (0-15), or -1 to query all channels
1116    /// * `cc` - MIDI CC number (0-127)
1117    ///
1118    /// # Returns
1119    /// `Some(parameter_id)` if this CC is mapped to a parameter, `None` otherwise.
1120    ///
1121    /// # Example
1122    /// ```ignore
1123    /// fn midi_cc_to_parameter(&self, _bus: i32, _channel: i16, cc: u8) -> Option<u32> {
1124    ///     match cc {
1125    ///         cc::MOD_WHEEL => Some(PARAM_VIBRATO_DEPTH),
1126    ///         cc::EXPRESSION => Some(PARAM_VOLUME),
1127    ///         _ => None,
1128    ///     }
1129    /// }
1130    /// ```
1131    fn midi_cc_to_parameter(&self, bus_index: i32, channel: i16, cc: u8) -> Option<u32> {
1132        let _ = (bus_index, channel, cc);
1133        None
1134    }
1135
1136    // =========================================================================
1137    // MIDI CC Configuration (VST3 IMidiMapping hidden parameters)
1138    // =========================================================================
1139
1140    /// Returns MIDI CC configuration for automatic host mapping.
1141    ///
1142    /// Override to enable MIDI CC/pitch bend/aftertouch reception via IMidiMapping.
1143    /// The framework will:
1144    /// 1. Create hidden parameters for each enabled controller
1145    /// 2. Handle IMidiMapping queries from the DAW
1146    /// 3. Convert parameter changes to MidiEvents in process_midi()
1147    /// 4. Provide direct CC value access via ProcessContext::midi_cc()
1148    ///
1149    /// This solves the VST3 MIDI input problem where most DAWs don't send
1150    /// `kLegacyMIDICCOutEvent` for input. Instead, they use the `IMidiMapping`
1151    /// interface to map MIDI controllers to parameters.
1152    ///
1153    /// # Example
1154    ///
1155    /// ```ignore
1156    /// impl Descriptor for MySynth {
1157    ///     fn midi_cc_config(&self) -> Option<MidiCcConfig> {
1158    ///         Some(MidiCcConfig::new()
1159    ///             .with_pitch_bend()
1160    ///             .with_mod_wheel()
1161    ///             .with_aftertouch()
1162    ///             .with_ccs(&[7, 10, 11, 64]))  // Volume, Pan, Expression, Sustain
1163    ///     }
1164    /// }
1165    ///
1166    /// // Access CC values during processing:
1167    /// fn process(&mut self, buffer: &mut Buffer, _aux: &mut AuxiliaryBuffers, context: &ProcessContext) {
1168    ///     if let Some(cc) = context.midi_cc() {
1169    ///         let pitch_bend = cc.pitch_bend();  // -1.0 to 1.0
1170    ///         let mod_wheel = cc.mod_wheel();    // 0.0 to 1.0
1171    ///     }
1172    /// }
1173    /// ```
1174    fn midi_cc_config(&self) -> Option<MidiCcConfig> {
1175        None
1176    }
1177
1178    // =========================================================================
1179    // MIDI Learn (IMidiLearn)
1180    // =========================================================================
1181
1182    /// Called by DAW when live MIDI CC input is received during learn mode.
1183    ///
1184    /// Override this to implement MIDI learn in your plugin UI. When the user
1185    /// enables "MIDI Learn" mode and moves a MIDI CC knob, the DAW calls this
1186    /// method so the plugin can map that CC to a parameter.
1187    ///
1188    /// # Arguments
1189    /// * `bus_index` - MIDI bus index (usually 0)
1190    /// * `channel` - MIDI channel (0-15)
1191    /// * `cc` - MIDI CC number that was moved
1192    ///
1193    /// # Returns
1194    /// `true` if the input was handled (learned), `false` otherwise.
1195    fn on_midi_learn(&mut self, bus_index: i32, channel: i16, cc: u8) -> bool {
1196        let _ = (bus_index, channel, cc);
1197        false
1198    }
1199
1200    // =========================================================================
1201    // MIDI 2.0 Mapping (IMidiMapping2)
1202    // =========================================================================
1203
1204    /// Get all MIDI 1.0 CC assignments for bulk query.
1205    ///
1206    /// Override to provide mappings for DAW queries. This is more efficient
1207    /// than individual `midi_cc_to_parameter` queries when there are many mappings.
1208    ///
1209    /// Default returns empty slice (no mappings).
1210    fn midi1_assignments(&self) -> &[Midi1Assignment] {
1211        &[]
1212    }
1213
1214    /// Get all MIDI 2.0 controller assignments for bulk query.
1215    ///
1216    /// Override to provide MIDI 2.0 Registered/Assignable controller mappings.
1217    ///
1218    /// Default returns empty slice (no mappings).
1219    fn midi2_assignments(&self) -> &[Midi2Assignment] {
1220        &[]
1221    }
1222
1223    // =========================================================================
1224    // MIDI 2.0 Learn (IMidiLearn2)
1225    // =========================================================================
1226
1227    /// Called when MIDI 1.0 CC input is received during learn mode.
1228    ///
1229    /// This is the MIDI 2.0 version of `on_midi_learn` with separate methods
1230    /// for MIDI 1.0 and MIDI 2.0 controllers.
1231    ///
1232    /// Default returns `false` (not handled).
1233    fn on_midi1_learn(&mut self, bus_index: i32, channel: u8, cc: u8) -> bool {
1234        let _ = (bus_index, channel, cc);
1235        false
1236    }
1237
1238    /// Called when MIDI 2.0 controller input is received during learn mode.
1239    ///
1240    /// Override to implement MIDI 2.0 controller learning.
1241    ///
1242    /// Default returns `false` (not handled).
1243    fn on_midi2_learn(&mut self, bus_index: i32, channel: u8, controller: Midi2Controller) -> bool {
1244        let _ = (bus_index, channel, controller);
1245        false
1246    }
1247
1248    // =========================================================================
1249    // Note Expression Controller (INoteExpressionController - VST3 SDK 3.5.0)
1250    // =========================================================================
1251
1252    /// Returns the number of supported note expression types.
1253    ///
1254    /// Override to advertise which note expressions your plugin supports
1255    /// (e.g., volume, pan, tuning for MPE instruments).
1256    ///
1257    /// Default returns 0 (no note expressions).
1258    fn note_expression_count(&self, bus_index: i32, channel: i16) -> usize {
1259        let _ = (bus_index, channel);
1260        0
1261    }
1262
1263    /// Returns information about a note expression type by index.
1264    ///
1265    /// Override to provide details about each supported expression type.
1266    ///
1267    /// Default returns None.
1268    fn note_expression_info(
1269        &self,
1270        bus_index: i32,
1271        channel: i16,
1272        index: usize,
1273    ) -> Option<NoteExpressionTypeInfo> {
1274        let _ = (bus_index, channel, index);
1275        None
1276    }
1277
1278    /// Converts a normalized note expression value to a display string.
1279    ///
1280    /// Override to provide custom formatting (e.g., "2.5 semitones" for tuning).
1281    ///
1282    /// Default returns the value as a percentage.
1283    fn note_expression_value_to_string(
1284        &self,
1285        bus_index: i32,
1286        channel: i16,
1287        type_id: u32,
1288        value: f64,
1289    ) -> String {
1290        let _ = (bus_index, channel, type_id);
1291        format!("{:.1}%", value * 100.0)
1292    }
1293
1294    /// Parses a string to a normalized note expression value.
1295    ///
1296    /// Override to support custom parsing.
1297    ///
1298    /// Default returns None (parsing not supported).
1299    fn note_expression_string_to_value(
1300        &self,
1301        bus_index: i32,
1302        channel: i16,
1303        type_id: u32,
1304        string: &str,
1305    ) -> Option<f64> {
1306        let _ = (bus_index, channel, type_id, string);
1307        None
1308    }
1309
1310    // =========================================================================
1311    // Keyswitch Controller (IKeyswitchController - VST3 SDK 3.5.0)
1312    // =========================================================================
1313
1314    /// Returns the number of keyswitches (articulations).
1315    ///
1316    /// Override for sample libraries and orchestral instruments that
1317    /// support keyswitching between articulations.
1318    ///
1319    /// Default returns 0 (no keyswitches).
1320    fn keyswitch_count(&self, bus_index: i32, channel: i16) -> usize {
1321        let _ = (bus_index, channel);
1322        0
1323    }
1324
1325    /// Returns information about a keyswitch by index.
1326    ///
1327    /// Override to provide keyswitch details for DAW expression maps.
1328    ///
1329    /// Default returns None.
1330    fn keyswitch_info(&self, bus_index: i32, channel: i16, index: usize) -> Option<KeyswitchInfo> {
1331        let _ = (bus_index, channel, index);
1332        None
1333    }
1334
1335    // =========================================================================
1336    // Physical UI Mapping (INoteExpressionPhysicalUIMapping - VST3 SDK 3.6.11)
1337    // =========================================================================
1338
1339    /// Returns mappings from physical UI controllers to note expressions.
1340    ///
1341    /// Override to define how MPE controllers (X-axis, Y-axis, Pressure)
1342    /// map to your plugin's note expression types.
1343    ///
1344    /// # Example
1345    /// ```ignore
1346    /// fn physical_ui_mappings(&self, _bus: i32, _channel: i16) -> &[PhysicalUIMap] {
1347    ///     &[
1348    ///         PhysicalUIMap::y_axis(note_expression::BRIGHTNESS),
1349    ///         PhysicalUIMap::pressure(note_expression::EXPRESSION),
1350    ///     ]
1351    /// }
1352    /// ```
1353    ///
1354    /// Default returns empty slice (no mappings).
1355    fn physical_ui_mappings(&self, bus_index: i32, channel: i16) -> &[PhysicalUIMap] {
1356        let _ = (bus_index, channel);
1357        &[]
1358    }
1359
1360    // =========================================================================
1361    // MPE Wrapper Support (IVst3WrapperMPESupport - VST3 SDK 3.6.12)
1362    // =========================================================================
1363
1364    /// Called to enable or disable MPE input processing.
1365    ///
1366    /// Override to handle MPE enable/disable notifications from wrappers.
1367    ///
1368    /// Default does nothing and returns true.
1369    fn enable_mpe_input_processing(&mut self, enabled: bool) -> bool {
1370        let _ = enabled;
1371        true
1372    }
1373
1374    /// Called when the MPE input device settings change.
1375    ///
1376    /// Override to receive MPE zone configuration from wrappers.
1377    ///
1378    /// Default does nothing and returns true.
1379    fn set_mpe_input_device_settings(&mut self, settings: MpeInputDeviceSettings) -> bool {
1380        let _ = settings;
1381        true
1382    }
1383}
1384
1385// =============================================================================
1386// MIDI Mapping Types
1387// =============================================================================
1388
1389/// Base assignment info for MIDI controller → parameter mapping.
1390#[derive(Debug, Clone, Copy)]
1391pub struct MidiControllerAssignment {
1392    /// Parameter ID this controller maps to.
1393    pub parameter_id: u32,
1394    /// MIDI bus index.
1395    pub bus_index: i32,
1396    /// MIDI channel (0-15).
1397    pub channel: u8,
1398}
1399
1400/// MIDI 1.0 CC assignment.
1401///
1402/// Maps a MIDI 1.0 Control Change to a parameter.
1403#[derive(Debug, Clone, Copy)]
1404pub struct Midi1Assignment {
1405    /// Base assignment info (parameter_id, bus, channel).
1406    pub assignment: MidiControllerAssignment,
1407    /// CC number (0-127).
1408    pub controller: u8,
1409}
1410
1411impl Midi1Assignment {
1412    /// Create a new MIDI 1.0 CC assignment.
1413    pub const fn new(parameter_id: u32, bus_index: i32, channel: u8, controller: u8) -> Self {
1414        Self {
1415            assignment: MidiControllerAssignment {
1416                parameter_id,
1417                bus_index,
1418                channel,
1419            },
1420            controller,
1421        }
1422    }
1423
1424    /// Create an assignment for the default bus and all channels.
1425    pub const fn simple(parameter_id: u32, controller: u8) -> Self {
1426        Self::new(parameter_id, 0, 0, controller)
1427    }
1428}
1429
1430/// MIDI 2.0 controller assignment.
1431///
1432/// Maps a MIDI 2.0 Registered/Assignable Controller to a parameter.
1433#[derive(Debug, Clone, Copy)]
1434pub struct Midi2Assignment {
1435    /// Base assignment info (parameter_id, bus, channel).
1436    pub assignment: MidiControllerAssignment,
1437    /// MIDI 2.0 controller identifier.
1438    pub controller: Midi2Controller,
1439}
1440
1441impl Midi2Assignment {
1442    /// Create a new MIDI 2.0 controller assignment.
1443    pub const fn new(
1444        parameter_id: u32,
1445        bus_index: i32,
1446        channel: u8,
1447        controller: Midi2Controller,
1448    ) -> Self {
1449        Self {
1450            assignment: MidiControllerAssignment {
1451                parameter_id,
1452                bus_index,
1453                channel,
1454            },
1455            controller,
1456        }
1457    }
1458
1459    /// Create an assignment for the default bus and all channels.
1460    pub const fn simple(parameter_id: u32, controller: Midi2Controller) -> Self {
1461        Self::new(parameter_id, 0, 0, controller)
1462    }
1463}