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}