beamer_core/
plugin.rs

1//! Core plugin trait definitions.
2
3use crate::buffer::{AuxiliaryBuffers, Buffer};
4use crate::error::PluginResult;
5use crate::midi::{
6    KeyswitchInfo, Midi2Controller, MidiBuffer, MidiEvent, MpeInputDeviceSettings,
7    NoteExpressionTypeInfo, PhysicalUIMap,
8};
9use crate::params::Parameters;
10use crate::process_context::ProcessContext;
11
12// =============================================================================
13// Bus Configuration
14// =============================================================================
15
16/// Audio bus type.
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
18pub enum BusType {
19    /// Main audio bus (e.g., primary stereo input/output).
20    #[default]
21    Main,
22    /// Auxiliary bus (e.g., sidechain input).
23    Aux,
24}
25
26/// Information about an audio bus.
27#[derive(Debug, Clone)]
28pub struct BusInfo {
29    /// Display name for the bus (e.g., "Input", "Sidechain").
30    pub name: &'static str,
31    /// Bus type (main or auxiliary).
32    pub bus_type: BusType,
33    /// Number of channels in this bus.
34    pub channel_count: u32,
35    /// Whether the bus is active by default.
36    pub is_default_active: bool,
37}
38
39impl Default for BusInfo {
40    fn default() -> Self {
41        Self {
42            name: "Main",
43            bus_type: BusType::Main,
44            channel_count: 2,
45            is_default_active: true,
46        }
47    }
48}
49
50impl BusInfo {
51    /// Create a stereo main bus.
52    pub const fn stereo(name: &'static str) -> Self {
53        Self {
54            name,
55            bus_type: BusType::Main,
56            channel_count: 2,
57            is_default_active: true,
58        }
59    }
60
61    /// Create a mono main bus.
62    pub const fn mono(name: &'static str) -> Self {
63        Self {
64            name,
65            bus_type: BusType::Main,
66            channel_count: 1,
67            is_default_active: true,
68        }
69    }
70
71    /// Create an auxiliary bus (e.g., sidechain).
72    pub const fn aux(name: &'static str, channel_count: u32) -> Self {
73        Self {
74            name,
75            bus_type: BusType::Aux,
76            channel_count,
77            is_default_active: false,
78        }
79    }
80}
81
82// =============================================================================
83// AudioProcessor Trait
84// =============================================================================
85
86/// Core trait for audio processing logic.
87///
88/// This trait defines the DSP (Digital Signal Processing) interface that
89/// plugin implementations must provide. It is designed to be format-agnostic,
90/// meaning the same implementation can be wrapped for VST3, CLAP, or other
91/// plugin formats.
92///
93/// # Thread Safety
94///
95/// Implementors must be `Send` because the plugin may be moved between threads.
96/// The `process` method is called on the audio thread and must be real-time safe:
97/// - No allocations
98/// - No locks (use lock-free structures)
99/// - No syscalls
100/// - No unbounded loops
101pub trait AudioProcessor: Send {
102    /// Called when audio processing setup changes.
103    ///
104    /// This is called before audio processing begins, whenever the sample rate
105    /// or maximum block size changes. Use this to initialize buffers, filters,
106    /// or other sample-rate dependent state.
107    ///
108    /// # Arguments
109    /// * `sample_rate` - The sample rate in Hz (e.g., 44100.0, 48000.0)
110    /// * `max_buffer_size` - Maximum number of samples per process call
111    fn setup(&mut self, sample_rate: f64, max_buffer_size: usize);
112
113    /// Process an audio buffer with transport context.
114    ///
115    /// This is the main DSP entry point, called on the audio thread for each
116    /// block of audio. The buffer provides input samples and mutable output
117    /// buffers for the main bus.
118    ///
119    /// # Arguments
120    ///
121    /// * `buffer` - Main audio bus (stereo/surround input and output)
122    /// * `aux` - Auxiliary buses (sidechain, aux sends) - ignore if not needed
123    /// * `context` - Processing context with sample rate, buffer size, and transport info
124    ///
125    /// # Real-Time Safety
126    ///
127    /// This method must be real-time safe. Do not allocate, lock mutexes,
128    /// or perform any operation with unbounded execution time.
129    ///
130    /// # Example: Simple Gain
131    ///
132    /// ```ignore
133    /// fn process(&mut self, buffer: &mut Buffer, _aux: &mut AuxiliaryBuffers, _context: &ProcessContext) {
134    ///     let gain = self.params.gain();
135    ///     for (input, output) in buffer.zip_channels() {
136    ///         for (i, o) in input.iter().zip(output.iter_mut()) {
137    ///             *o = *i * gain;
138    ///         }
139    ///     }
140    /// }
141    /// ```
142    ///
143    /// # Example: Tempo-Synced LFO
144    ///
145    /// ```ignore
146    /// fn process(&mut self, buffer: &mut Buffer, _aux: &mut AuxiliaryBuffers, context: &ProcessContext) {
147    ///     // Calculate LFO rate synced to host tempo
148    ///     let lfo_hz = context.transport.tempo
149    ///         .map(|tempo| tempo / 60.0 / 4.0)  // 1 cycle per 4 beats
150    ///         .unwrap_or(2.0);                   // Fallback: 2 Hz
151    ///
152    ///     let increment = (lfo_hz * 2.0 * std::f32::consts::PI) / context.sample_rate as f32;
153    ///
154    ///     for (input, output) in buffer.zip_channels() {
155    ///         for (i, o) in input.iter().zip(output.iter_mut()) {
156    ///             let lfo = self.phase.sin();
157    ///             *o = *i * (1.0 + lfo * 0.5);
158    ///             self.phase += increment;
159    ///         }
160    ///     }
161    /// }
162    /// ```
163    ///
164    /// # Example: Sidechain Ducker
165    ///
166    /// ```ignore
167    /// fn process(&mut self, buffer: &mut Buffer, aux: &mut AuxiliaryBuffers, _context: &ProcessContext) {
168    ///     let duck = aux.sidechain()
169    ///         .map(|sc| (sc.rms(0) * 4.0).min(1.0))
170    ///         .unwrap_or(0.0);
171    ///
172    ///     buffer.copy_to_output();
173    ///     buffer.apply_output_gain(1.0 - duck * 0.8);
174    /// }
175    /// ```
176    fn process(&mut self, buffer: &mut Buffer, aux: &mut AuxiliaryBuffers, context: &ProcessContext);
177
178    /// Called when the plugin is activated or deactivated.
179    ///
180    /// Activation typically happens when the user inserts the plugin into a
181    /// track or opens a project. Deactivation happens when removed or project
182    /// is closed.
183    ///
184    /// Default implementation does nothing.
185    fn set_active(&mut self, _active: bool) {}
186
187    /// Get the tail length in samples.
188    ///
189    /// This indicates how many samples of audio "tail" the plugin produces
190    /// after input stops (e.g., reverb decay). Return 0 for no tail, or
191    /// `u32::MAX` for infinite tail.
192    ///
193    /// Default returns 0 (no tail).
194    fn tail_samples(&self) -> u32 {
195        0
196    }
197
198    /// Get the latency in samples.
199    ///
200    /// If the plugin introduces processing latency (e.g., lookahead limiters),
201    /// return the latency in samples here. The host can use this for delay
202    /// compensation.
203    ///
204    /// Default returns 0 (no latency).
205    fn latency_samples(&self) -> u32 {
206        0
207    }
208
209    /// Get the bypass ramp length in samples.
210    ///
211    /// When bypass is engaged or disengaged, this defines the crossfade
212    /// duration to avoid clicks. The host uses this value (combined with
213    /// `tail_samples()`) to determine how long to continue calling `process()`
214    /// after input stops.
215    ///
216    /// Return 0 for instant bypass (no crossfade), or a sample count for
217    /// smooth crossfading. Typical values:
218    /// - 64 samples (~1.3ms at 48kHz) - fast, suitable for most effects
219    /// - 256 samples (~5.3ms at 48kHz) - smoother, for sensitive material
220    /// - 512+ samples - very smooth, for reverbs/delays with long tails
221    ///
222    /// Default returns 64 samples.
223    ///
224    /// # Example
225    ///
226    /// ```ignore
227    /// fn bypass_ramp_samples(&self) -> u32 {
228    ///     // Use 10ms crossfade based on current sample rate
229    ///     (self.sample_rate * 0.01) as u32
230    /// }
231    /// ```
232    fn bypass_ramp_samples(&self) -> u32 {
233        64
234    }
235
236    // =========================================================================
237    // 64-bit Processing Support
238    // =========================================================================
239
240    /// Returns true if the plugin supports native 64-bit (double precision) processing.
241    ///
242    /// Override this to return `true` if your plugin implements `process_f64()` natively.
243    /// When false (default), the framework will automatically convert 64-bit host buffers
244    /// to 32-bit, call `process()`, and convert back.
245    ///
246    /// # Performance Considerations
247    ///
248    /// - For most plugins, f32 is sufficient and the default conversion is fine
249    /// - Implement native f64 only if your DSP algorithm benefits from double precision
250    ///   (e.g., IIR filters with long decay, precision-sensitive synthesis)
251    /// - The conversion overhead is minimal (~few microseconds per buffer)
252    ///
253    /// Default returns `false`.
254    fn supports_double_precision(&self) -> bool {
255        false
256    }
257
258    /// Process an audio buffer at 64-bit (double) precision.
259    ///
260    /// This is the f64 equivalent of `process()`. Override this method AND
261    /// return `true` from `supports_double_precision()` to enable native
262    /// 64-bit processing.
263    ///
264    /// If `supports_double_precision()` returns `false`, this method is never
265    /// called - the framework converts to f32 and calls `process()` instead.
266    ///
267    /// # Default Implementation
268    ///
269    /// The default implementation converts f64→f32, calls `process()`, then
270    /// converts f32→f64. This allows any plugin to work in a 64-bit host
271    /// without modification.
272    ///
273    /// # Example: Native f64 Plugin
274    ///
275    /// ```ignore
276    /// fn supports_double_precision(&self) -> bool {
277    ///     true
278    /// }
279    ///
280    /// fn process_f64(
281    ///     &mut self,
282    ///     buffer: &mut Buffer<f64>,
283    ///     aux: &mut AuxiliaryBuffers<f64>,
284    ///     context: &ProcessContext,
285    /// ) {
286    ///     let gain = self.params.gain_linear() as f64;
287    ///     for (input, output) in buffer.zip_channels() {
288    ///         for (i, o) in input.iter().zip(output.iter_mut()) {
289    ///             *o = *i * gain;
290    ///         }
291    ///     }
292    /// }
293    /// ```
294    fn process_f64(
295        &mut self,
296        buffer: &mut Buffer<f64>,
297        _aux: &mut AuxiliaryBuffers<f64>,
298        context: &ProcessContext,
299    ) {
300        // Default implementation: convert f64 → f32, process, convert back
301        //
302        // NOTE: This is a fallback implementation that allocates memory.
303        // In practice, this method is rarely called because:
304        // - The VST3 wrapper handles conversion with pre-allocated buffers
305        //   (see `process_audio_f64_converted` in beamer-vst3/src/processor.rs)
306        // - Future format wrappers (CLAP, etc.) should also pre-allocate
307        //
308        // If you're implementing a custom wrapper, ensure you handle
309        // f64→f32 conversion with pre-allocated buffers for real-time safety.
310
311        let num_samples = buffer.num_samples();
312        let num_input_channels = buffer.num_input_channels();
313        let num_output_channels = buffer.num_output_channels();
314
315        // Allocate conversion buffers (VST3 wrapper uses pre-allocated buffers,
316        // this is only for the fallback default implementation)
317        let input_f32: Vec<Vec<f32>> = (0..num_input_channels)
318            .map(|ch| buffer.input(ch).iter().map(|&s| s as f32).collect())
319            .collect();
320        let mut output_f32: Vec<Vec<f32>> = (0..num_output_channels)
321            .map(|_| vec![0.0f32; num_samples])
322            .collect();
323
324        // Build f32 buffer slices
325        let input_slices: Vec<&[f32]> = input_f32.iter().map(|v| v.as_slice()).collect();
326        let output_slices: Vec<&mut [f32]> = output_f32
327            .iter_mut()
328            .map(|v| v.as_mut_slice())
329            .collect();
330
331        let mut buffer_f32 = Buffer::new(input_slices, output_slices, num_samples);
332
333        // For aux buffers, we use empty for now (full aux conversion is complex)
334        // The VST3 wrapper handles proper aux conversion
335        let mut aux_f32: AuxiliaryBuffers<f32> = AuxiliaryBuffers::empty();
336
337        // Process at f32
338        self.process(&mut buffer_f32, &mut aux_f32, context);
339
340        // Convert output back to f64
341        for (ch, output_samples) in output_f32.iter().enumerate().take(num_output_channels) {
342            let output_ch = buffer.output(ch);
343            for (i, sample) in output_samples.iter().enumerate() {
344                output_ch[i] = *sample as f64;
345            }
346        }
347    }
348
349    /// Save the plugin state to bytes.
350    ///
351    /// This is called when the DAW saves a project or preset. The returned
352    /// bytes should contain all state needed to restore the plugin to its
353    /// current configuration.
354    ///
355    /// Default returns an empty vector.
356    fn save_state(&self) -> PluginResult<Vec<u8>> {
357        Ok(Vec::new())
358    }
359
360    /// Load the plugin state from bytes.
361    ///
362    /// This is called when the DAW loads a project or preset. The data is
363    /// the same bytes returned from a previous `save_state` call.
364    ///
365    /// Default does nothing.
366    fn load_state(&mut self, _data: &[u8]) -> PluginResult<()> {
367        Ok(())
368    }
369
370    // =========================================================================
371    // Bus Configuration
372    // =========================================================================
373
374    /// Returns the number of audio input buses.
375    ///
376    /// Default returns 1 (single stereo input).
377    fn input_bus_count(&self) -> usize {
378        1
379    }
380
381    /// Returns the number of audio output buses.
382    ///
383    /// Default returns 1 (single stereo output).
384    fn output_bus_count(&self) -> usize {
385        1
386    }
387
388    /// Returns information about an input bus.
389    ///
390    /// Default returns a stereo main bus for index 0.
391    fn input_bus_info(&self, index: usize) -> Option<BusInfo> {
392        if index == 0 {
393            Some(BusInfo::stereo("Input"))
394        } else {
395            None
396        }
397    }
398
399    /// Returns information about an output bus.
400    ///
401    /// Default returns a stereo main bus for index 0.
402    fn output_bus_info(&self, index: usize) -> Option<BusInfo> {
403        if index == 0 {
404            Some(BusInfo::stereo("Output"))
405        } else {
406            None
407        }
408    }
409
410    // =========================================================================
411    // MIDI Processing
412    // =========================================================================
413
414    /// Process MIDI events.
415    ///
416    /// Called during processing with any incoming MIDI events. Plugins can
417    /// transform events and add them to the output buffer, pass them through
418    /// unchanged, or consume them entirely.
419    ///
420    /// # Arguments
421    /// * `input` - Slice of incoming MIDI events (sorted by sample_offset)
422    /// * `output` - Buffer to write output MIDI events to
423    ///
424    /// # Real-Time Safety
425    ///
426    /// This method must be real-time safe. Do not allocate, lock mutexes,
427    /// or perform any operation with unbounded execution time.
428    ///
429    /// **Note:** Cloning a `SysEx` event allocates due to `Box<SysEx>`. SysEx
430    /// events are rare in typical use cases. If strict real-time safety is
431    /// required, override this method to handle SysEx specially.
432    ///
433    /// # Default Implementation
434    ///
435    /// The default implementation passes all events through unchanged.
436    fn process_midi(&mut self, input: &[MidiEvent], output: &mut MidiBuffer) {
437        for event in input {
438            output.push(event.clone());
439        }
440    }
441
442    /// Returns whether this plugin processes MIDI events.
443    ///
444    /// Override to return `true` if your plugin needs MIDI input/output.
445    /// This is used by the host to determine event bus configuration.
446    ///
447    /// Default returns `false`.
448    fn wants_midi(&self) -> bool {
449        false
450    }
451}
452
453// =============================================================================
454// Plugin Trait
455// =============================================================================
456
457/// Main plugin trait combining audio processing and parameters.
458///
459/// This is the primary trait that plugin authors implement to create a complete
460/// audio plugin. It combines [`AudioProcessor`] for DSP with a [`Parameters`]
461/// collection for host communication.
462///
463/// # Example
464///
465/// ```ignore
466/// use beamer_core::{Plugin, AudioProcessor, Buffer, AuxiliaryBuffers, Parameters, ProcessContext};
467///
468/// pub struct MyGain {
469///     params: MyGainParams,
470/// }
471///
472/// impl AudioProcessor for MyGain {
473///     fn setup(&mut self, _sample_rate: f64, _max_buffer_size: usize) {}
474///
475///     fn process(&mut self, buffer: &mut Buffer, _aux: &mut AuxiliaryBuffers, _context: &ProcessContext) {
476///         let gain = self.params.gain_linear();
477///         for (input, output) in buffer.zip_channels() {
478///             for (i, o) in input.iter().zip(output.iter_mut()) {
479///                 *o = *i * gain;
480///             }
481///         }
482///     }
483/// }
484///
485/// impl Plugin for MyGain {
486///     type Params = MyGainParams;
487///
488///     fn params(&self) -> &Self::Params {
489///         &self.params
490///     }
491///
492///     fn create() -> Self {
493///         Self { params: MyGainParams::new() }
494///     }
495/// }
496/// ```
497pub trait Plugin: AudioProcessor {
498    /// The parameter collection type for this plugin.
499    type Params: Parameters + crate::params::Units + crate::param_types::Params;
500
501    /// Returns a reference to the plugin's parameters.
502    ///
503    /// The VST3 wrapper uses this to communicate parameter values with the host.
504    fn params(&self) -> &Self::Params;
505
506    /// Returns a mutable reference to the plugin's parameters.
507    ///
508    /// Used by the framework for operations like resetting smoothers after
509    /// loading state. Most plugins can use the default implementation.
510    fn params_mut(&mut self) -> &mut Self::Params;
511
512    /// Creates a new instance of the plugin with default state.
513    ///
514    /// Called by the host when instantiating the plugin.
515    fn create() -> Self
516    where
517        Self: Sized;
518
519    // =========================================================================
520    // MIDI Mapping (IMidiMapping)
521    // =========================================================================
522
523    /// Get the parameter ID mapped to a MIDI CC.
524    ///
525    /// Override this to enable DAW MIDI learn for your parameters. When the
526    /// DAW queries which parameter is assigned to a MIDI CC, this method is
527    /// called.
528    ///
529    /// # Arguments
530    /// * `bus_index` - MIDI bus index (usually 0)
531    /// * `channel` - MIDI channel (0-15), or -1 to query all channels
532    /// * `cc` - MIDI CC number (0-127)
533    ///
534    /// # Returns
535    /// `Some(param_id)` if this CC is mapped to a parameter, `None` otherwise.
536    ///
537    /// # Example
538    /// ```ignore
539    /// fn midi_cc_to_param(&self, _bus: i32, _channel: i16, cc: u8) -> Option<u32> {
540    ///     match cc {
541    ///         cc::MOD_WHEEL => Some(PARAM_VIBRATO_DEPTH),
542    ///         cc::EXPRESSION => Some(PARAM_VOLUME),
543    ///         _ => None,
544    ///     }
545    /// }
546    /// ```
547    fn midi_cc_to_param(&self, bus_index: i32, channel: i16, cc: u8) -> Option<u32> {
548        let _ = (bus_index, channel, cc);
549        None
550    }
551
552    // =========================================================================
553    // MIDI Learn (IMidiLearn)
554    // =========================================================================
555
556    /// Called by DAW when live MIDI CC input is received during learn mode.
557    ///
558    /// Override this to implement MIDI learn in your plugin UI. When the user
559    /// enables "MIDI Learn" mode and moves a MIDI CC knob, the DAW calls this
560    /// method so the plugin can map that CC to a parameter.
561    ///
562    /// # Arguments
563    /// * `bus_index` - MIDI bus index (usually 0)
564    /// * `channel` - MIDI channel (0-15)
565    /// * `cc` - MIDI CC number that was moved
566    ///
567    /// # Returns
568    /// `true` if the input was handled (learned), `false` otherwise.
569    fn on_midi_learn(&mut self, bus_index: i32, channel: i16, cc: u8) -> bool {
570        let _ = (bus_index, channel, cc);
571        false
572    }
573
574    // =========================================================================
575    // MIDI 2.0 Mapping (IMidiMapping2)
576    // =========================================================================
577
578    /// Get all MIDI 1.0 CC assignments for bulk query.
579    ///
580    /// Override to provide mappings for DAW queries. This is more efficient
581    /// than individual `midi_cc_to_param` queries when there are many mappings.
582    ///
583    /// Default returns empty slice (no mappings).
584    fn midi1_assignments(&self) -> &[Midi1Assignment] {
585        &[]
586    }
587
588    /// Get all MIDI 2.0 controller assignments for bulk query.
589    ///
590    /// Override to provide MIDI 2.0 Registered/Assignable controller mappings.
591    ///
592    /// Default returns empty slice (no mappings).
593    fn midi2_assignments(&self) -> &[Midi2Assignment] {
594        &[]
595    }
596
597    // =========================================================================
598    // MIDI 2.0 Learn (IMidiLearn2)
599    // =========================================================================
600
601    /// Called when MIDI 1.0 CC input is received during learn mode.
602    ///
603    /// This is the MIDI 2.0 version of `on_midi_learn` with separate methods
604    /// for MIDI 1.0 and MIDI 2.0 controllers.
605    ///
606    /// Default returns `false` (not handled).
607    fn on_midi1_learn(&mut self, bus_index: i32, channel: u8, cc: u8) -> bool {
608        let _ = (bus_index, channel, cc);
609        false
610    }
611
612    /// Called when MIDI 2.0 controller input is received during learn mode.
613    ///
614    /// Override to implement MIDI 2.0 controller learning.
615    ///
616    /// Default returns `false` (not handled).
617    fn on_midi2_learn(&mut self, bus_index: i32, channel: u8, controller: Midi2Controller) -> bool {
618        let _ = (bus_index, channel, controller);
619        false
620    }
621
622    // =========================================================================
623    // Note Expression Controller (INoteExpressionController - VST3 SDK 3.5.0)
624    // =========================================================================
625
626    /// Returns the number of supported note expression types.
627    ///
628    /// Override to advertise which note expressions your plugin supports
629    /// (e.g., volume, pan, tuning for MPE instruments).
630    ///
631    /// Default returns 0 (no note expressions).
632    fn note_expression_count(&self, bus_index: i32, channel: i16) -> usize {
633        let _ = (bus_index, channel);
634        0
635    }
636
637    /// Returns information about a note expression type by index.
638    ///
639    /// Override to provide details about each supported expression type.
640    ///
641    /// Default returns None.
642    fn note_expression_info(
643        &self,
644        bus_index: i32,
645        channel: i16,
646        index: usize,
647    ) -> Option<NoteExpressionTypeInfo> {
648        let _ = (bus_index, channel, index);
649        None
650    }
651
652    /// Converts a normalized note expression value to a display string.
653    ///
654    /// Override to provide custom formatting (e.g., "2.5 semitones" for tuning).
655    ///
656    /// Default returns the value as a percentage.
657    fn note_expression_value_to_string(
658        &self,
659        bus_index: i32,
660        channel: i16,
661        type_id: u32,
662        value: f64,
663    ) -> String {
664        let _ = (bus_index, channel, type_id);
665        format!("{:.1}%", value * 100.0)
666    }
667
668    /// Parses a string to a normalized note expression value.
669    ///
670    /// Override to support custom parsing.
671    ///
672    /// Default returns None (parsing not supported).
673    fn note_expression_string_to_value(
674        &self,
675        bus_index: i32,
676        channel: i16,
677        type_id: u32,
678        string: &str,
679    ) -> Option<f64> {
680        let _ = (bus_index, channel, type_id, string);
681        None
682    }
683
684    // =========================================================================
685    // Keyswitch Controller (IKeyswitchController - VST3 SDK 3.5.0)
686    // =========================================================================
687
688    /// Returns the number of keyswitches (articulations).
689    ///
690    /// Override for sample libraries and orchestral instruments that
691    /// support keyswitching between articulations.
692    ///
693    /// Default returns 0 (no keyswitches).
694    fn keyswitch_count(&self, bus_index: i32, channel: i16) -> usize {
695        let _ = (bus_index, channel);
696        0
697    }
698
699    /// Returns information about a keyswitch by index.
700    ///
701    /// Override to provide keyswitch details for DAW expression maps.
702    ///
703    /// Default returns None.
704    fn keyswitch_info(&self, bus_index: i32, channel: i16, index: usize) -> Option<KeyswitchInfo> {
705        let _ = (bus_index, channel, index);
706        None
707    }
708
709    // =========================================================================
710    // Physical UI Mapping (INoteExpressionPhysicalUIMapping - VST3 SDK 3.6.11)
711    // =========================================================================
712
713    /// Returns mappings from physical UI controllers to note expressions.
714    ///
715    /// Override to define how MPE controllers (X-axis, Y-axis, Pressure)
716    /// map to your plugin's note expression types.
717    ///
718    /// # Example
719    /// ```ignore
720    /// fn physical_ui_mappings(&self, _bus: i32, _channel: i16) -> &[PhysicalUIMap] {
721    ///     &[
722    ///         PhysicalUIMap::y_axis(note_expression::BRIGHTNESS),
723    ///         PhysicalUIMap::pressure(note_expression::EXPRESSION),
724    ///     ]
725    /// }
726    /// ```
727    ///
728    /// Default returns empty slice (no mappings).
729    fn physical_ui_mappings(&self, bus_index: i32, channel: i16) -> &[PhysicalUIMap] {
730        let _ = (bus_index, channel);
731        &[]
732    }
733
734    // =========================================================================
735    // MPE Wrapper Support (IVst3WrapperMPESupport - VST3 SDK 3.6.12)
736    // =========================================================================
737
738    /// Called to enable or disable MPE input processing.
739    ///
740    /// Override to handle MPE enable/disable notifications from wrappers.
741    ///
742    /// Default does nothing and returns true.
743    fn enable_mpe_input_processing(&mut self, enabled: bool) -> bool {
744        let _ = enabled;
745        true
746    }
747
748    /// Called when the MPE input device settings change.
749    ///
750    /// Override to receive MPE zone configuration from wrappers.
751    ///
752    /// Default does nothing and returns true.
753    fn set_mpe_input_device_settings(&mut self, settings: MpeInputDeviceSettings) -> bool {
754        let _ = settings;
755        true
756    }
757}
758
759// =============================================================================
760// MIDI Mapping Types
761// =============================================================================
762
763/// Base assignment info for MIDI controller → parameter mapping.
764#[derive(Debug, Clone, Copy)]
765pub struct MidiControllerAssignment {
766    /// Parameter ID this controller maps to.
767    pub param_id: u32,
768    /// MIDI bus index.
769    pub bus_index: i32,
770    /// MIDI channel (0-15).
771    pub channel: u8,
772}
773
774/// MIDI 1.0 CC assignment.
775///
776/// Maps a MIDI 1.0 Control Change to a parameter.
777#[derive(Debug, Clone, Copy)]
778pub struct Midi1Assignment {
779    /// Base assignment info (param_id, bus, channel).
780    pub assignment: MidiControllerAssignment,
781    /// CC number (0-127).
782    pub controller: u8,
783}
784
785impl Midi1Assignment {
786    /// Create a new MIDI 1.0 CC assignment.
787    pub const fn new(param_id: u32, bus_index: i32, channel: u8, controller: u8) -> Self {
788        Self {
789            assignment: MidiControllerAssignment {
790                param_id,
791                bus_index,
792                channel,
793            },
794            controller,
795        }
796    }
797
798    /// Create an assignment for the default bus and all channels.
799    pub const fn simple(param_id: u32, controller: u8) -> Self {
800        Self::new(param_id, 0, 0, controller)
801    }
802}
803
804/// MIDI 2.0 controller assignment.
805///
806/// Maps a MIDI 2.0 Registered/Assignable Controller to a parameter.
807#[derive(Debug, Clone, Copy)]
808pub struct Midi2Assignment {
809    /// Base assignment info (param_id, bus, channel).
810    pub assignment: MidiControllerAssignment,
811    /// MIDI 2.0 controller identifier.
812    pub controller: Midi2Controller,
813}
814
815impl Midi2Assignment {
816    /// Create a new MIDI 2.0 controller assignment.
817    pub const fn new(
818        param_id: u32,
819        bus_index: i32,
820        channel: u8,
821        controller: Midi2Controller,
822    ) -> Self {
823        Self {
824            assignment: MidiControllerAssignment {
825                param_id,
826                bus_index,
827                channel,
828            },
829            controller,
830        }
831    }
832
833    /// Create an assignment for the default bus and all channels.
834    pub const fn simple(param_id: u32, controller: Midi2Controller) -> Self {
835        Self::new(param_id, 0, 0, controller)
836    }
837}