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