1use crate::{
4 audio::{AudioBuffers, AudioBusLayout, AudioLevels, BusAudioBuffers},
5 error::{Error, Result},
6 midi::{
7 MidiChannel, MidiEvent, PluginEvent, PluginEventData, MAX_EVENT_PAYLOAD_BYTES,
8 MAX_EVENT_TEXT_UNITS,
9 },
10 parameters::{Parameter, ParameterUpdate},
11};
12use crossbeam_queue::ArrayQueue;
13use std::sync::{Arc, Mutex};
14
15#[derive(Clone)]
27pub struct OutputMidiConsumer {
28 queue: Arc<ArrayQueue<PluginEvent>>,
29}
30
31impl OutputMidiConsumer {
32 pub(crate) fn from_queue(queue: Arc<ArrayQueue<PluginEvent>>) -> Self {
33 Self { queue }
34 }
35
36 pub fn pop(&self) -> Option<MidiEvent> {
38 while let Some(event) = self.queue.pop() {
39 if let Some(midi) = event.to_midi() {
40 return Some(midi);
41 }
42 }
43 None
44 }
45
46 pub fn drain(&self) -> Vec<MidiEvent> {
50 let mut out = Vec::new();
51 while let Some(event) = self.pop() {
52 out.push(event);
53 }
54 out
55 }
56}
57
58#[derive(Clone)]
60pub struct OutputEventConsumer {
61 queue: Arc<ArrayQueue<PluginEvent>>,
62}
63
64impl OutputEventConsumer {
65 pub(crate) fn from_queue(queue: Arc<ArrayQueue<PluginEvent>>) -> Self {
66 Self { queue }
67 }
68
69 pub fn pop(&self) -> Option<PluginEvent> {
71 self.queue.pop()
72 }
73
74 pub fn drain(&self) -> Vec<PluginEvent> {
76 let mut out = Vec::new();
77 while let Some(event) = self.pop() {
78 out.push(event);
79 }
80 out
81 }
82}
83
84#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
86pub struct PluginInfo {
87 pub path: std::path::PathBuf,
89 pub name: String,
91 pub vendor: String,
93 pub version: String,
95 pub category: String,
97 pub uid: String,
99 pub audio_inputs: u32,
101 pub audio_outputs: u32,
103 pub has_midi_input: bool,
105 pub has_midi_output: bool,
107 pub has_gui: bool,
109}
110
111#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
117pub struct PluginPreset {
118 pub uid: String,
120 pub plugin_name: String,
122 pub state: Vec<u8>,
124}
125
126#[derive(Debug, Clone, PartialEq, Eq, Default, serde::Serialize, serde::Deserialize)]
139pub enum StateContext {
140 #[default]
145 Project,
146 Preset {
157 path: Option<String>,
167 },
168}
169
170impl StateContext {
171 pub fn preset() -> Self {
173 Self::Preset { path: None }
174 }
175
176 pub fn preset_from_path(path: impl AsRef<std::path::Path>) -> Self {
178 Self::Preset {
179 path: Some(path.as_ref().to_string_lossy().into_owned()),
180 }
181 }
182
183 pub fn file_path(&self) -> Option<&std::path::Path> {
185 match self {
186 Self::Project => None,
187 Self::Preset { path } => path.as_deref().map(std::path::Path::new),
188 }
189 }
190}
191
192pub(crate) struct StateSnapshot {
198 pub component: Vec<u8>,
199 pub controller: Option<Vec<u8>>,
200}
201
202const STATE_SNAPSHOT_MAGIC: &[u8; 16] = b"VST3HOST_STATE\0\0";
203const STATE_SNAPSHOT_VERSION: u32 = 1;
204const STATE_SNAPSHOT_HEADER_SIZE: usize = 16 + 4 + 4 + 4;
205const NO_CONTROLLER_STATE: u32 = u32::MAX;
206const MAX_STATE_SNAPSHOT_PAYLOAD_BYTES: usize =
209 crate::internal::com_implementations::MAX_STREAM_BYTES;
210pub(crate) const MAX_STATE_SNAPSHOT_BYTES: usize =
211 STATE_SNAPSHOT_HEADER_SIZE + MAX_STATE_SNAPSHOT_PAYLOAD_BYTES;
212
213pub(crate) fn encode_state_snapshot(snapshot: &StateSnapshot) -> Result<Vec<u8>> {
214 let component_len = u32::try_from(snapshot.component.len())
215 .map_err(|_| Error::Other("component state is too large".to_string()))?;
216 let controller_len = match snapshot.controller.as_ref() {
217 Some(state) => u32::try_from(state.len())
218 .map_err(|_| Error::Other("controller state is too large".to_string()))?,
219 None => NO_CONTROLLER_STATE,
220 };
221 let payload_size = snapshot
222 .component
223 .len()
224 .checked_add(snapshot.controller.as_ref().map_or(0, Vec::len))
225 .ok_or_else(|| Error::Other("plugin state size overflow".to_string()))?;
226 let total = STATE_SNAPSHOT_HEADER_SIZE
227 .checked_add(payload_size)
228 .ok_or_else(|| Error::Other("plugin state size overflow".to_string()))?;
229 if payload_size > MAX_STATE_SNAPSHOT_PAYLOAD_BYTES {
230 return Err(Error::Other(format!(
231 "combined plugin state payload is too large ({payload_size} bytes, maximum \
232 {MAX_STATE_SNAPSHOT_PAYLOAD_BYTES})"
233 )));
234 }
235
236 let mut out = Vec::with_capacity(total);
237 out.extend_from_slice(STATE_SNAPSHOT_MAGIC);
238 out.extend_from_slice(&STATE_SNAPSHOT_VERSION.to_le_bytes());
239 out.extend_from_slice(&component_len.to_le_bytes());
240 out.extend_from_slice(&controller_len.to_le_bytes());
241 out.extend_from_slice(&snapshot.component);
242 if let Some(controller) = snapshot.controller.as_ref() {
243 out.extend_from_slice(controller);
244 }
245 Ok(out)
246}
247
248pub(crate) fn decode_state_snapshot(data: &[u8]) -> Result<StateSnapshot> {
249 if !data.starts_with(STATE_SNAPSHOT_MAGIC) {
250 if data.len() > MAX_STATE_SNAPSHOT_PAYLOAD_BYTES {
251 return Err(Error::Other(format!(
252 "legacy component state is too large ({} bytes, maximum \
253 {MAX_STATE_SNAPSHOT_PAYLOAD_BYTES})",
254 data.len()
255 )));
256 }
257 return Ok(StateSnapshot {
258 component: data.to_vec(),
259 controller: None,
260 });
261 }
262 if data.len() < STATE_SNAPSHOT_HEADER_SIZE {
263 return Err(Error::Other(
264 "truncated vst3-host state snapshot header".to_string(),
265 ));
266 }
267 let version = read_snapshot_u32(&data[16..20]);
268 if version != STATE_SNAPSHOT_VERSION {
269 return Err(Error::Other(format!(
270 "unsupported vst3-host state snapshot version {version}"
271 )));
272 }
273 let component_len = read_snapshot_u32(&data[20..24]) as usize;
274 let encoded_controller_len = read_snapshot_u32(&data[24..28]);
275 let controller_len =
276 (encoded_controller_len != NO_CONTROLLER_STATE).then_some(encoded_controller_len as usize);
277 let payload_size = component_len
278 .checked_add(controller_len.unwrap_or(0))
279 .ok_or_else(|| Error::Other("plugin state size overflow".to_string()))?;
280 let expected = STATE_SNAPSHOT_HEADER_SIZE
281 .checked_add(payload_size)
282 .ok_or_else(|| Error::Other("plugin state size overflow".to_string()))?;
283 if expected != data.len() || expected > MAX_STATE_SNAPSHOT_BYTES {
284 return Err(Error::Other(format!(
285 "invalid vst3-host state snapshot size (header describes {expected} bytes, got {})",
286 data.len()
287 )));
288 }
289 let component_start = STATE_SNAPSHOT_HEADER_SIZE;
290 let component_end = component_start + component_len;
291 Ok(StateSnapshot {
292 component: data[component_start..component_end].to_vec(),
293 controller: controller_len.map(|len| data[component_end..component_end + len].to_vec()),
294 })
295}
296
297fn read_snapshot_u32(bytes: &[u8]) -> u32 {
298 u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]])
299}
300
301#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
306pub struct PluginUnit {
307 pub id: i32,
309 pub parent_id: i32,
311 pub name: String,
313 pub program_list_id: Option<i32>,
315 pub programs: Vec<String>,
317}
318
319#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
321pub struct ProgramPitchName {
322 pub midi_pitch: i16,
324 pub name: String,
326}
327
328#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
330pub enum AutomationState {
331 Off,
333 Read,
335 Write,
337 ReadWrite,
339}
340
341#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
349pub enum ParameterEditKind {
350 BeginGesture,
352 ValueChange,
355 EndGesture,
357}
358
359#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
365pub struct ParameterEdit {
366 pub id: u32,
368 pub kind: ParameterEditKind,
370 pub value: Option<f64>,
373}
374
375#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
377pub enum ProgressKind {
378 AsyncStateRestoration,
380 UiBackgroundTask,
382 Other(u32),
384}
385
386#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
391pub struct ProgressValue(u64);
392
393impl ProgressValue {
394 pub fn new(value: f64) -> Option<Self> {
396 (value.is_finite() && (0.0..=1.0).contains(&value)).then(|| Self(value.to_bits()))
397 }
398
399 pub fn get(self) -> f64 {
401 f64::from_bits(self.0)
402 }
403}
404
405#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
411pub struct ContextMenuItem {
412 pub item_id: u32,
414 pub name: String,
416 pub tag: i32,
418 pub flags: i32,
420}
421
422impl ContextMenuItem {
423 pub fn is_separator(&self) -> bool {
425 self.flags & vst3::Steinberg::Vst::IContextMenuItem_::Flags_::kIsSeparator as i32 != 0
426 }
427
428 pub fn is_disabled(&self) -> bool {
430 self.flags & vst3::Steinberg::Vst::IContextMenuItem_::Flags_::kIsDisabled as i32 != 0
431 }
432
433 pub fn is_checked(&self) -> bool {
435 self.flags & vst3::Steinberg::Vst::IContextMenuItem_::Flags_::kIsChecked as i32 != 0
436 }
437
438 pub fn is_group_start(&self) -> bool {
440 let group_start = vst3::Steinberg::Vst::IContextMenuItem_::Flags_::kIsGroupStart as i32;
441 self.flags & group_start == group_start
442 }
443
444 pub fn is_group_end(&self) -> bool {
446 let group_end = vst3::Steinberg::Vst::IContextMenuItem_::Flags_::kIsGroupEnd as i32;
447 self.flags & group_end == group_end
448 }
449}
450
451#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
457pub struct DataExchangeBlock {
458 pub queue_id: u32,
460 pub user_context_id: u32,
462 pub block_id: u32,
464 #[serde(with = "crate::process_isolation::state_codec")]
466 pub data: Vec<u8>,
467}
468
469#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
480pub enum HostNotification {
481 DirtyChanged(bool),
483 OpenEditorRequested {
485 name: Option<String>,
487 },
488 GroupEditStarted,
490 GroupEditFinished,
492 UnitSelectionChanged {
494 unit_id: i32,
496 },
497 ProgramListChanged {
499 list_id: i32,
501 program_index: Option<i32>,
503 },
504 UnitByBusChanged,
506 ProgressStarted {
508 id: u64,
510 kind: ProgressKind,
512 description: Option<String>,
514 },
515 ProgressUpdated {
517 id: u64,
519 value: ProgressValue,
521 },
522 ProgressFinished {
524 id: u64,
526 },
527 ContextMenuRequested {
533 menu_id: u64,
535 parameter_id: Option<u32>,
537 x: i32,
539 y: i32,
541 items: Vec<ContextMenuItem>,
543 },
544}
545
546impl HostNotification {
547 pub(crate) fn invalidates_unit_cache(&self) -> bool {
548 matches!(
549 self,
550 Self::ProgramListChanged { .. } | Self::UnitByBusChanged
551 )
552 }
553}
554
555#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
572pub struct RestartFlags(i32);
573
574impl RestartFlags {
575 pub(crate) fn from_bits(bits: i32) -> Self {
577 Self(bits)
578 }
579
580 pub fn bits(self) -> i32 {
582 self.0
583 }
584
585 pub fn is_empty(self) -> bool {
587 self.0 == 0
588 }
589
590 fn has(self, flag: i32) -> bool {
591 self.0 & flag != 0
592 }
593
594 pub fn param_values_changed(self) -> bool {
596 self.has(vst3::Steinberg::Vst::RestartFlags_::kParamValuesChanged)
597 }
598
599 pub fn reload_component(self) -> bool {
601 self.has(vst3::Steinberg::Vst::RestartFlags_::kReloadComponent)
602 }
603
604 pub fn param_titles_changed(self) -> bool {
606 self.has(vst3::Steinberg::Vst::RestartFlags_::kParamTitlesChanged)
607 }
608
609 pub fn latency_changed(self) -> bool {
611 self.has(vst3::Steinberg::Vst::RestartFlags_::kLatencyChanged)
612 }
613
614 pub fn io_changed(self) -> bool {
616 self.has(vst3::Steinberg::Vst::RestartFlags_::kIoChanged)
617 }
618
619 pub fn midi_cc_assignment_changed(self) -> bool {
621 self.has(vst3::Steinberg::Vst::RestartFlags_::kMidiCCAssignmentChanged)
622 }
623
624 pub fn note_expression_changed(self) -> bool {
626 self.has(vst3::Steinberg::Vst::RestartFlags_::kNoteExpressionChanged)
627 }
628
629 pub fn io_titles_changed(self) -> bool {
631 self.has(vst3::Steinberg::Vst::RestartFlags_::kIoTitlesChanged)
632 }
633
634 pub fn prefetchable_support_changed(self) -> bool {
636 self.has(vst3::Steinberg::Vst::RestartFlags_::kPrefetchableSupportChanged)
637 }
638
639 pub fn routing_info_changed(self) -> bool {
641 self.has(vst3::Steinberg::Vst::RestartFlags_::kRoutingInfoChanged)
642 }
643
644 pub fn keyswitch_changed(self) -> bool {
646 self.has(vst3::Steinberg::Vst::RestartFlags_::kKeyswitchChanged)
647 }
648
649 pub fn param_id_mapping_changed(self) -> bool {
651 self.has(vst3::Steinberg::Vst::RestartFlags_::kParamIDMappingChanged)
652 }
653}
654
655#[cfg(test)]
656mod restart_flag_tests {
657 use super::RestartFlags;
658 use vst3::Steinberg::Vst::RestartFlags_ as Flags;
659
660 #[test]
661 fn exposes_every_vst3_restart_flag() {
662 let bits = Flags::kReloadComponent
663 | Flags::kIoChanged
664 | Flags::kParamValuesChanged
665 | Flags::kLatencyChanged
666 | Flags::kParamTitlesChanged
667 | Flags::kMidiCCAssignmentChanged
668 | Flags::kNoteExpressionChanged
669 | Flags::kIoTitlesChanged
670 | Flags::kPrefetchableSupportChanged
671 | Flags::kRoutingInfoChanged
672 | Flags::kKeyswitchChanged
673 | Flags::kParamIDMappingChanged;
674 let flags = RestartFlags::from_bits(bits);
675 assert!(flags.reload_component());
676 assert!(flags.io_changed());
677 assert!(flags.param_values_changed());
678 assert!(flags.latency_changed());
679 assert!(flags.param_titles_changed());
680 assert!(flags.midi_cc_assignment_changed());
681 assert!(flags.note_expression_changed());
682 assert!(flags.io_titles_changed());
683 assert!(flags.prefetchable_support_changed());
684 assert!(flags.routing_info_changed());
685 assert!(flags.keyswitch_changed());
686 assert!(flags.param_id_mapping_changed());
687 }
688}
689
690#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, serde::Serialize, serde::Deserialize)]
694pub enum ProcessMode {
695 #[default]
697 Realtime,
698 Prefetch,
703 Offline,
705}
706
707#[allow(clippy::type_complexity)] pub struct Plugin {
710 pub(crate) info: PluginInfo,
712 pub(crate) compatibility: Vec<crate::discovery::ClassCompatibility>,
713 pub(crate) is_processing: bool,
714 pub(crate) sample_rate: f64,
716 pub(crate) block_size: usize,
718 pub(crate) audio_levels: Arc<Mutex<AudioLevels>>,
719 pub(crate) parameter_change_callback: Option<Box<dyn Fn(u32, f64) + Send + 'static>>,
720 pub(crate) audio_callback: Option<Box<dyn Fn(&AudioLevels) + Send + 'static>>,
721
722 pub(crate) internal: Option<Box<dyn PluginInternal>>,
724}
725
726pub(crate) trait PluginInternal: Send {
728 fn set_parameter(&mut self, id: u32, value: f64) -> Result<()>;
729 fn set_parameter_at(&mut self, id: u32, value: f64, _sample_offset: i32) -> Result<()> {
733 self.set_parameter(id, value)
734 }
735 fn queue_processor_parameter_at(
741 &mut self,
742 id: u32,
743 value: f64,
744 sample_offset: i32,
745 ) -> Result<()> {
746 self.set_parameter_at(id, value, sample_offset)
747 }
748 fn get_parameter(&self, id: u32) -> Result<f64>;
749 fn get_all_parameters(&self) -> Result<Vec<Parameter>>;
750 fn format_parameter(&self, id: u32, normalized: f64) -> Result<String>;
751 fn process(&mut self, buffers: &mut AudioBuffers) -> Result<()>;
752 fn audio_bus_layout(&self) -> Result<AudioBusLayout> {
754 Err(Error::Other(
755 "bus-aware audio processing is not supported for this plugin".to_string(),
756 ))
757 }
758 fn process_buses(&mut self, _buffers: &mut BusAudioBuffers) -> Result<()> {
760 Err(Error::Other(
761 "bus-aware audio processing is not supported for this plugin".to_string(),
762 ))
763 }
764 fn reconfigure(&mut self, _sample_rate: f64, _block_size: usize) -> Result<()> {
767 Err(Error::Other(
768 "runtime reconfigure is not supported for this plugin".to_string(),
769 ))
770 }
771 fn set_process_mode(&mut self, _mode: crate::plugin::ProcessMode) -> Result<()> {
774 Err(Error::Other(
775 "process mode switching is not supported for this plugin".to_string(),
776 ))
777 }
778 fn bus_arrangements(&self) -> Result<crate::audio::BusArrangements> {
780 Err(Error::Other(
781 "bus arrangement query is not supported for this plugin".to_string(),
782 ))
783 }
784 fn set_bus_arrangements(
787 &mut self,
788 _inputs: &[crate::audio::SpeakerArrangement],
789 _outputs: &[crate::audio::SpeakerArrangement],
790 ) -> Result<()> {
791 Err(Error::Other(
792 "bus arrangement negotiation is not supported for this plugin".to_string(),
793 ))
794 }
795 fn set_bus_active(
798 &mut self,
799 _media_type: crate::audio::MediaType,
800 _direction: crate::audio::BusDirection,
801 _bus_index: i32,
802 _active: bool,
803 ) -> Result<()> {
804 Err(Error::Other(
805 "bus activation is not supported for this plugin".to_string(),
806 ))
807 }
808 fn set_tempo(&mut self, _bpm: f64) -> Result<()> {
812 Err(Error::Other(
813 "runtime transport mutation is not supported for this plugin".to_string(),
814 ))
815 }
816 fn set_time_signature(&mut self, _numerator: i32, _denominator: i32) -> Result<()> {
820 Err(Error::Other(
821 "runtime transport mutation is not supported for this plugin".to_string(),
822 ))
823 }
824 fn set_playing(&mut self, _playing: bool) -> Result<()> {
827 Err(Error::Other(
828 "runtime transport mutation is not supported for this plugin".to_string(),
829 ))
830 }
831 fn send_midi_event(&mut self, event: MidiEvent) -> Result<()>;
832 fn send_midi_event_at(&mut self, event: MidiEvent, _sample_offset: i32) -> Result<()> {
836 self.send_midi_event(event)
837 }
838 fn send_plugin_event(&mut self, _event: PluginEvent) -> Result<()> {
840 Err(Error::Other(
841 "owned VST3 events are not supported for this plugin".to_string(),
842 ))
843 }
844 fn midi_panic(&mut self) -> Result<()> {
846 for i in 0..16 {
847 if let Some(channel) = MidiChannel::from_index(i) {
848 for controller in [123, 120, 121] {
849 self.send_midi_event(MidiEvent::ControlChange {
850 channel,
851 controller,
852 value: 0,
853 })?;
854 }
855 }
856 }
857 Ok(())
858 }
859 fn note_on(
862 &mut self,
863 _channel: MidiChannel,
864 _note: u8,
865 _velocity: u8,
866 _sample_offset: i32,
867 ) -> Result<crate::midi::NoteId> {
868 Err(Error::Other(
869 "per-note expression is not supported for this plugin".to_string(),
870 ))
871 }
872 fn note_off(&mut self, _id: crate::midi::NoteId, _sample_offset: i32) -> Result<()> {
874 Err(Error::Other(
875 "per-note expression is not supported for this plugin".to_string(),
876 ))
877 }
878 fn send_note_expression(
880 &mut self,
881 _id: crate::midi::NoteId,
882 _kind: crate::midi::NoteExpressionType,
883 _value: f64,
884 _sample_offset: i32,
885 ) -> Result<()> {
886 Err(Error::Other(
887 "per-note expression is not supported for this plugin".to_string(),
888 ))
889 }
890 fn note_expressions(
893 &self,
894 _bus: i32,
895 _channel: i16,
896 ) -> Result<Vec<crate::midi::NoteExpressionInfo>> {
897 Ok(Vec::new())
898 }
899 fn start_processing(&mut self) -> Result<()>;
900 fn stop_processing(&mut self) -> Result<()>;
901 fn has_editor(&self) -> bool;
902 fn open_editor(&mut self, parent: *mut std::ffi::c_void) -> Result<()>;
903 fn close_editor(&mut self) -> Result<()>;
904 fn get_editor_size(&self) -> Result<(i32, i32)>;
905 fn editor_can_resize(&self) -> bool {
907 false
908 }
909 fn resize_editor(&mut self, _width: i32, _height: i32) -> Result<(i32, i32)> {
912 Err(Error::Other("plugin editor is not resizable".to_string()))
913 }
914 fn set_editor_scale_factor(&mut self, _factor: f32) -> Result<bool> {
917 Ok(false)
918 }
919 fn service_run_loop(&mut self) {}
923 fn get_parameter_changes(&self) -> Vec<(u32, f64)>;
924 fn take_parameter_edits(&mut self) -> Vec<ParameterEdit> {
928 Vec::new()
929 }
930 fn take_host_notifications(&mut self) -> Vec<HostNotification> {
932 Vec::new()
933 }
934 fn take_data_exchange_blocks(&mut self) -> Vec<DataExchangeBlock> {
937 Vec::new()
938 }
939 fn execute_context_menu_item(&mut self, _menu_id: u64, _item_id: u32) -> Result<()> {
941 Err(Error::Other(
942 "plugin context menus are not supported".to_string(),
943 ))
944 }
945 fn dismiss_context_menu(&mut self, _menu_id: u64) -> Result<()> {
947 Err(Error::Other(
948 "plugin context menus are not supported".to_string(),
949 ))
950 }
951 fn take_restart_flags(&mut self) -> RestartFlags {
954 RestartFlags::default()
955 }
956 fn service_host_requests(&mut self) -> Result<RestartFlags> {
958 Ok(self.take_restart_flags())
959 }
960 fn take_output_events(&self) -> Vec<PluginEvent> {
963 Vec::new()
964 }
965 fn output_midi_handle(&self) -> Option<OutputMidiConsumer> {
968 None
969 }
970 fn output_event_handle(&self) -> Option<OutputEventConsumer> {
971 None
972 }
973 fn get_units(&self) -> Result<Vec<PluginUnit>> {
976 Ok(Vec::new())
977 }
978 fn select_program(&mut self, _unit_id: i32, _program_index: i32) -> Result<()> {
982 Err(Error::Other(
983 "program selection is not supported for this plugin".to_string(),
984 ))
985 }
986 fn selected_unit(&self) -> Result<Option<i32>> {
987 Ok(None)
988 }
989 fn select_unit(&mut self, _unit_id: i32) -> Result<()> {
990 Err(Error::Other(
991 "unit selection is not supported for this plugin".to_string(),
992 ))
993 }
994 fn program_pitch_names(
995 &self,
996 _program_list_id: i32,
997 _program_index: i32,
998 ) -> Result<Vec<ProgramPitchName>> {
999 Ok(Vec::new())
1000 }
1001 fn get_program_data(
1002 &self,
1003 _program_list_id: i32,
1004 _program_index: i32,
1005 ) -> Result<Option<Vec<u8>>> {
1006 Ok(None)
1007 }
1008 fn set_program_data(
1009 &mut self,
1010 _program_list_id: i32,
1011 _program_index: i32,
1012 _data: &[u8],
1013 ) -> Result<()> {
1014 Err(Error::Other(
1015 "program data is not supported for this plugin".to_string(),
1016 ))
1017 }
1018 fn get_unit_data(&self, _unit_id: i32) -> Result<Option<Vec<u8>>> {
1019 Ok(None)
1020 }
1021 fn set_unit_data(&mut self, _unit_id: i32, _data: &[u8]) -> Result<()> {
1022 Err(Error::Other(
1023 "unit data is not supported for this plugin".to_string(),
1024 ))
1025 }
1026 fn begin_host_edit(&mut self, _parameter_id: u32) -> Result<()> {
1027 Err(Error::Other(
1028 "host edit sessions are not supported for this plugin".to_string(),
1029 ))
1030 }
1031 fn end_host_edit(&mut self, _parameter_id: u32) -> Result<()> {
1032 Err(Error::Other(
1033 "host edit sessions are not supported for this plugin".to_string(),
1034 ))
1035 }
1036 fn send_midi_learn(&mut self, _bus: i32, _channel: i16, _controller: u16) -> Result<()> {
1037 Err(Error::Other(
1038 "MIDI learn is not supported for this plugin".to_string(),
1039 ))
1040 }
1041 fn set_automation_state(&mut self, _state: AutomationState) -> Result<()> {
1042 Err(Error::Other(
1043 "automation state is not supported for this plugin".to_string(),
1044 ))
1045 }
1046 fn remap_parameter_id(&self, _old_plugin_uid: &str, _old_param_id: u32) -> Result<Option<u32>> {
1048 Ok(None)
1049 }
1050 fn latency_samples(&self) -> u32 {
1052 0
1053 }
1054 fn tail_samples(&self) -> u32 {
1056 0
1057 }
1058 fn midi_cc_to_parameter(&self, _bus: i32, _channel: i16, _cc: u16) -> Option<u32> {
1061 None
1062 }
1063 fn save_state(&self) -> Result<Vec<u8>> {
1065 Err(Error::Other(
1066 "state save/restore is not supported".to_string(),
1067 ))
1068 }
1069 fn load_state_with_context(&mut self, _data: &[u8], _context: &StateContext) -> Result<()> {
1075 Err(Error::Other(
1076 "state save/restore is not supported".to_string(),
1077 ))
1078 }
1079 fn load_state(&mut self, data: &[u8]) -> Result<()> {
1082 self.load_state_with_context(data, &StateContext::Project)
1083 }
1084 fn helper_pid(&self) -> Option<u32> {
1086 None
1087 }
1088 fn recovery_count(&self) -> u64 {
1091 0
1092 }
1093 fn recover(&mut self) -> Result<()> {
1096 Err(Error::Other(
1097 "recovery is only supported for process-isolated plugins".to_string(),
1098 ))
1099 }
1100 fn take_editor_resize_request(&self) -> Option<(i32, i32)> {
1102 None
1103 }
1104 fn output_channel_count(&self) -> usize {
1106 2
1107 }
1108}
1109
1110impl Plugin {
1111 pub fn info(&self) -> &PluginInfo {
1113 &self.info
1114 }
1115
1116 pub fn class_compatibility(&self) -> &[crate::discovery::ClassCompatibility] {
1121 &self.compatibility
1122 }
1123
1124 pub fn replaced_class_ids(&self) -> &[String] {
1126 self.compatibility
1127 .iter()
1128 .find(|mapping| {
1129 crate::internal::utils::class_uid_matches(&mapping.new_class_id, &self.info.uid)
1130 })
1131 .map_or(&[], |mapping| mapping.old_class_ids.as_slice())
1132 }
1133
1134 pub fn sample_rate(&self) -> f64 {
1136 self.sample_rate
1137 }
1138
1139 pub fn block_size(&self) -> usize {
1141 self.block_size
1142 }
1143
1144 pub fn reconfigure(&mut self, sample_rate: f64, block_size: usize) -> Result<()> {
1153 if self.is_processing {
1154 return Err(Error::Other(
1155 "cannot reconfigure while processing; call stop_processing() first".to_string(),
1156 ));
1157 }
1158 if !(sample_rate.is_finite() && sample_rate > 0.0) {
1159 return Err(Error::InvalidParameter(format!(
1160 "sample rate must be finite and positive, got {sample_rate}"
1161 )));
1162 }
1163 if block_size == 0 || block_size > i32::MAX as usize {
1164 return Err(Error::InvalidParameter(format!(
1165 "block size must be in 1..={}, got {block_size}",
1166 i32::MAX
1167 )));
1168 }
1169
1170 self.internal
1171 .as_mut()
1172 .ok_or_else(|| Error::Other("Plugin not initialized".to_string()))?
1173 .reconfigure(sample_rate, block_size)?;
1174
1175 self.sample_rate = sample_rate;
1176 self.block_size = block_size;
1177 Ok(())
1178 }
1179
1180 pub fn set_process_mode(&mut self, mode: ProcessMode) -> Result<()> {
1188 if self.is_processing {
1189 return Err(Error::Other(
1190 "cannot set process mode while processing; call stop_processing() first"
1191 .to_string(),
1192 ));
1193 }
1194 self.internal
1195 .as_mut()
1196 .ok_or_else(|| Error::Other("Plugin not initialized".to_string()))?
1197 .set_process_mode(mode)
1198 }
1199
1200 pub fn bus_arrangements(&self) -> Result<crate::audio::BusArrangements> {
1203 self.internal
1204 .as_ref()
1205 .ok_or_else(|| Error::Other("Plugin not initialized".to_string()))?
1206 .bus_arrangements()
1207 }
1208
1209 pub fn set_bus_arrangements(
1218 &mut self,
1219 inputs: &[crate::audio::SpeakerArrangement],
1220 outputs: &[crate::audio::SpeakerArrangement],
1221 ) -> Result<()> {
1222 if self.is_processing {
1223 return Err(Error::Other(
1224 "cannot set bus arrangements while processing; call stop_processing() first"
1225 .to_string(),
1226 ));
1227 }
1228 self.internal
1229 .as_mut()
1230 .ok_or_else(|| Error::Other("Plugin not initialized".to_string()))?
1231 .set_bus_arrangements(inputs, outputs)
1232 }
1233
1234 pub fn set_bus_active(
1252 &mut self,
1253 media_type: crate::audio::MediaType,
1254 direction: crate::audio::BusDirection,
1255 bus_index: i32,
1256 active: bool,
1257 ) -> Result<()> {
1258 if self.is_processing {
1259 return Err(Error::Other(
1260 "cannot activate a bus while processing; call stop_processing() first".to_string(),
1261 ));
1262 }
1263 self.internal
1264 .as_mut()
1265 .ok_or_else(|| Error::Other("Plugin not initialized".to_string()))?
1266 .set_bus_active(media_type, direction, bus_index, active)
1267 }
1268
1269 pub fn get_parameters(&self) -> Result<Vec<Parameter>> {
1271 self.internal
1272 .as_ref()
1273 .ok_or_else(|| Error::Other("Plugin not initialized".to_string()))?
1274 .get_all_parameters()
1275 }
1276
1277 pub fn set_parameter(&mut self, id: u32, value: f64) -> Result<()> {
1279 if !(0.0..=1.0).contains(&value) {
1280 return Err(Error::InvalidParameter(format!(
1281 "Value {} is out of range [0.0, 1.0]",
1282 value
1283 )));
1284 }
1285
1286 self.internal
1287 .as_mut()
1288 .ok_or_else(|| Error::Other("Plugin not initialized".to_string()))?
1289 .set_parameter(id, value)?;
1290
1291 if let Some(ref callback) = self.parameter_change_callback {
1293 callback(id, value);
1294 }
1295
1296 Ok(())
1297 }
1298
1299 pub fn set_parameter_at(&mut self, id: u32, value: f64, sample_offset: i32) -> Result<()> {
1311 if !(0.0..=1.0).contains(&value) {
1312 return Err(Error::InvalidParameter(format!(
1313 "Value {} is out of range [0.0, 1.0]",
1314 value
1315 )));
1316 }
1317 self.internal
1318 .as_mut()
1319 .ok_or_else(|| Error::Other("Plugin not initialized".to_string()))?
1320 .set_parameter_at(id, value, sample_offset)
1321 }
1322
1323 pub(crate) fn queue_processor_parameter_at(
1327 &mut self,
1328 id: u32,
1329 value: f64,
1330 sample_offset: i32,
1331 ) -> Result<()> {
1332 if !value.is_finite() || !(0.0..=1.0).contains(&value) {
1333 return Err(Error::InvalidParameter(format!(
1334 "Value {} is out of range [0.0, 1.0]",
1335 value
1336 )));
1337 }
1338 self.internal
1339 .as_mut()
1340 .ok_or_else(|| Error::Other("Plugin not initialized".to_string()))?
1341 .queue_processor_parameter_at(id, value, sample_offset)
1342 }
1343
1344 pub fn set_tempo(&mut self, bpm: f64) -> Result<()> {
1351 if !(bpm.is_finite() && bpm > 0.0) {
1352 return Err(Error::InvalidParameter(format!(
1353 "tempo must be finite and positive, got {bpm}"
1354 )));
1355 }
1356 self.internal
1357 .as_mut()
1358 .ok_or_else(|| Error::Other("Plugin not initialized".to_string()))?
1359 .set_tempo(bpm)
1360 }
1361
1362 pub fn set_time_signature(&mut self, numerator: i32, denominator: i32) -> Result<()> {
1370 if numerator <= 0 {
1371 return Err(Error::InvalidParameter(format!(
1372 "time signature numerator must be positive, got {numerator}"
1373 )));
1374 }
1375 if !matches!(denominator, 1 | 2 | 4 | 8 | 16) {
1376 return Err(Error::InvalidParameter(format!(
1377 "time signature denominator must be one of 1, 2, 4, 8, 16, got {denominator}"
1378 )));
1379 }
1380 self.internal
1381 .as_mut()
1382 .ok_or_else(|| Error::Other("Plugin not initialized".to_string()))?
1383 .set_time_signature(numerator, denominator)
1384 }
1385
1386 pub fn set_playing(&mut self, playing: bool) -> Result<()> {
1395 self.internal
1396 .as_mut()
1397 .ok_or_else(|| Error::Other("Plugin not initialized".to_string()))?
1398 .set_playing(playing)
1399 }
1400
1401 pub fn get_units(&self) -> Result<Vec<PluginUnit>> {
1406 self.internal
1407 .as_ref()
1408 .ok_or_else(|| Error::Other("Plugin not initialized".to_string()))?
1409 .get_units()
1410 }
1411
1412 pub fn select_program(&mut self, unit_id: i32, program_index: i32) -> Result<()> {
1426 self.internal
1427 .as_mut()
1428 .ok_or_else(|| Error::Other("Plugin not initialized".to_string()))?
1429 .select_program(unit_id, program_index)
1430 }
1431
1432 pub fn selected_unit(&self) -> Result<Option<i32>> {
1434 self.internal
1435 .as_ref()
1436 .ok_or_else(|| Error::Other("Plugin not initialized".to_string()))?
1437 .selected_unit()
1438 }
1439
1440 pub fn select_unit(&mut self, unit_id: i32) -> Result<()> {
1442 self.internal
1443 .as_mut()
1444 .ok_or_else(|| Error::Other("Plugin not initialized".to_string()))?
1445 .select_unit(unit_id)
1446 }
1447
1448 pub fn program_pitch_names(
1450 &self,
1451 program_list_id: i32,
1452 program_index: i32,
1453 ) -> Result<Vec<ProgramPitchName>> {
1454 self.internal
1455 .as_ref()
1456 .ok_or_else(|| Error::Other("Plugin not initialized".to_string()))?
1457 .program_pitch_names(program_list_id, program_index)
1458 }
1459
1460 pub fn get_program_data(
1462 &self,
1463 program_list_id: i32,
1464 program_index: i32,
1465 ) -> Result<Option<Vec<u8>>> {
1466 self.internal
1467 .as_ref()
1468 .ok_or_else(|| Error::Other("Plugin not initialized".to_string()))?
1469 .get_program_data(program_list_id, program_index)
1470 }
1471
1472 pub fn set_program_data(
1474 &mut self,
1475 program_list_id: i32,
1476 program_index: i32,
1477 data: &[u8],
1478 ) -> Result<()> {
1479 self.internal
1480 .as_mut()
1481 .ok_or_else(|| Error::Other("Plugin not initialized".to_string()))?
1482 .set_program_data(program_list_id, program_index, data)
1483 }
1484
1485 pub fn get_unit_data(&self, unit_id: i32) -> Result<Option<Vec<u8>>> {
1487 self.internal
1488 .as_ref()
1489 .ok_or_else(|| Error::Other("Plugin not initialized".to_string()))?
1490 .get_unit_data(unit_id)
1491 }
1492
1493 pub fn set_unit_data(&mut self, unit_id: i32, data: &[u8]) -> Result<()> {
1495 self.internal
1496 .as_mut()
1497 .ok_or_else(|| Error::Other("Plugin not initialized".to_string()))?
1498 .set_unit_data(unit_id, data)
1499 }
1500
1501 pub fn begin_host_edit(&mut self, parameter_id: u32) -> Result<()> {
1503 self.internal
1504 .as_mut()
1505 .ok_or_else(|| Error::Other("Plugin not initialized".to_string()))?
1506 .begin_host_edit(parameter_id)
1507 }
1508
1509 pub fn end_host_edit(&mut self, parameter_id: u32) -> Result<()> {
1511 self.internal
1512 .as_mut()
1513 .ok_or_else(|| Error::Other("Plugin not initialized".to_string()))?
1514 .end_host_edit(parameter_id)
1515 }
1516
1517 pub fn send_midi_learn(&mut self, bus: i32, channel: i16, controller: u16) -> Result<()> {
1519 self.internal
1520 .as_mut()
1521 .ok_or_else(|| Error::Other("Plugin not initialized".to_string()))?
1522 .send_midi_learn(bus, channel, controller)
1523 }
1524
1525 pub fn set_automation_state(&mut self, state: AutomationState) -> Result<()> {
1527 self.internal
1528 .as_mut()
1529 .ok_or_else(|| Error::Other("Plugin not initialized".to_string()))?
1530 .set_automation_state(state)
1531 }
1532
1533 pub fn remap_parameter_id(
1540 &self,
1541 old_plugin_uid: &str,
1542 old_param_id: u32,
1543 ) -> Result<Option<u32>> {
1544 if crate::internal::utils::parse_class_uid(old_plugin_uid).is_none() {
1545 return Err(Error::InvalidParameter(
1546 "plugin UID must contain exactly 32 hexadecimal characters".to_string(),
1547 ));
1548 }
1549 self.internal
1550 .as_ref()
1551 .ok_or_else(|| Error::Other("Plugin not initialized".to_string()))?
1552 .remap_parameter_id(old_plugin_uid, old_param_id)
1553 }
1554
1555 pub fn latency_samples(&self) -> u32 {
1560 self.internal
1561 .as_ref()
1562 .map(|i| i.latency_samples())
1563 .unwrap_or(0)
1564 }
1565
1566 pub fn tail_samples(&self) -> u32 {
1571 self.internal
1572 .as_ref()
1573 .map(|i| i.tail_samples())
1574 .unwrap_or(0)
1575 }
1576
1577 pub fn midi_cc_to_parameter(&self, bus: i32, channel: i16, cc: u16) -> Option<u32> {
1586 if cc > 129 {
1589 return None;
1590 }
1591 self.internal
1592 .as_ref()?
1593 .midi_cc_to_parameter(bus, channel, cc)
1594 }
1595
1596 pub fn get_parameter(&self, id: u32) -> Result<f64> {
1598 self.internal
1599 .as_ref()
1600 .ok_or_else(|| Error::Other("Plugin not initialized".to_string()))?
1601 .get_parameter(id)
1602 }
1603
1604 pub fn format_parameter(&self, id: u32, normalized: f64) -> Result<String> {
1613 self.internal
1614 .as_ref()
1615 .ok_or_else(|| Error::Other("Plugin not initialized".to_string()))?
1616 .format_parameter(id, normalized)
1617 }
1618
1619 pub fn set_parameter_by_name(&mut self, name: &str, value: f64) -> Result<()> {
1621 let params = self.get_parameters()?;
1622 let param = params
1623 .iter()
1624 .find(|p| p.name == name)
1625 .ok_or_else(|| Error::InvalidParameter(format!("Parameter '{}' not found", name)))?;
1626
1627 self.set_parameter(param.id, value)
1628 }
1629
1630 pub fn find_parameter(&self, name: &str) -> Result<Parameter> {
1632 let params = self.get_parameters()?;
1633 params
1634 .into_iter()
1635 .find(|p| p.name == name)
1636 .ok_or_else(|| Error::InvalidParameter(format!("Parameter '{}' not found", name)))
1637 }
1638
1639 pub fn send_midi_note(&mut self, note: u8, velocity: u8, channel: MidiChannel) -> Result<()> {
1641 validate_note(note)?;
1642 validate_velocity(velocity)?;
1643
1644 let event = MidiEvent::NoteOn {
1645 channel,
1646 note,
1647 velocity,
1648 };
1649 self.send_midi_event(event)
1650 }
1651
1652 pub fn send_midi_note_off(&mut self, note: u8, channel: MidiChannel) -> Result<()> {
1654 validate_note(note)?;
1655
1656 let event = MidiEvent::NoteOff {
1657 channel,
1658 note,
1659 velocity: 0,
1660 };
1661 self.send_midi_event(event)
1662 }
1663
1664 pub fn send_midi_cc(&mut self, controller: u8, value: u8, channel: MidiChannel) -> Result<()> {
1666 validate_controller(controller)?;
1667 validate_cc_value(value)?;
1668
1669 let event = MidiEvent::ControlChange {
1670 channel,
1671 controller,
1672 value,
1673 };
1674 self.send_midi_event(event)
1675 }
1676
1677 pub fn send_midi_event(&mut self, event: MidiEvent) -> Result<()> {
1684 validate_midi_event(&event)?;
1685 self.internal
1686 .as_mut()
1687 .ok_or_else(|| Error::Other("Plugin not initialized".to_string()))?
1688 .send_midi_event(event)
1689 }
1690
1691 pub fn send_midi_event_at(&mut self, event: MidiEvent, sample_offset: i32) -> Result<()> {
1704 validate_midi_event(&event)?;
1705 self.internal
1706 .as_mut()
1707 .ok_or_else(|| Error::Other("Plugin not initialized".to_string()))?
1708 .send_midi_event_at(event, sample_offset)
1709 }
1710
1711 pub fn send_plugin_event(&mut self, event: PluginEvent) -> Result<()> {
1717 validate_plugin_event(&event)?;
1718 self.internal
1719 .as_mut()
1720 .ok_or_else(|| Error::Other("Plugin not initialized".to_string()))?
1721 .send_plugin_event(event)
1722 }
1723
1724 pub fn send_sysex(&mut self, bytes: Vec<u8>) -> Result<()> {
1726 self.send_plugin_event(PluginEvent::sysex(bytes))
1727 }
1728
1729 pub fn send_sysex_at(&mut self, bytes: Vec<u8>, sample_offset: i32) -> Result<()> {
1731 self.send_plugin_event(PluginEvent::sysex(bytes).at(sample_offset))
1732 }
1733
1734 pub fn note_on(
1746 &mut self,
1747 channel: MidiChannel,
1748 note: u8,
1749 velocity: u8,
1750 ) -> Result<crate::midi::NoteId> {
1751 self.note_on_at(channel, note, velocity, 0)
1752 }
1753
1754 pub fn note_on_at(
1759 &mut self,
1760 channel: MidiChannel,
1761 note: u8,
1762 velocity: u8,
1763 sample_offset: i32,
1764 ) -> Result<crate::midi::NoteId> {
1765 validate_note(note)?;
1766 validate_velocity(velocity)?;
1767 self.internal
1768 .as_mut()
1769 .ok_or_else(|| Error::Other("Plugin not initialized".to_string()))?
1770 .note_on(channel, note, velocity, sample_offset)
1771 }
1772
1773 pub fn note_off(&mut self, id: crate::midi::NoteId) -> Result<()> {
1775 self.note_off_at(id, 0)
1776 }
1777
1778 pub fn note_off_at(&mut self, id: crate::midi::NoteId, sample_offset: i32) -> Result<()> {
1780 self.internal
1781 .as_mut()
1782 .ok_or_else(|| Error::Other("Plugin not initialized".to_string()))?
1783 .note_off(id, sample_offset)
1784 }
1785
1786 pub fn send_note_expression(
1791 &mut self,
1792 id: crate::midi::NoteId,
1793 kind: crate::midi::NoteExpressionType,
1794 value: f64,
1795 ) -> Result<()> {
1796 self.send_note_expression_at(id, kind, value, 0)
1797 }
1798
1799 pub fn send_note_expression_at(
1801 &mut self,
1802 id: crate::midi::NoteId,
1803 kind: crate::midi::NoteExpressionType,
1804 value: f64,
1805 sample_offset: i32,
1806 ) -> Result<()> {
1807 if !(0.0..=1.0).contains(&value) {
1808 return Err(Error::InvalidParameter(format!(
1809 "note-expression value {value} out of range [0.0, 1.0]"
1810 )));
1811 }
1812 self.internal
1813 .as_mut()
1814 .ok_or_else(|| Error::Other("Plugin not initialized".to_string()))?
1815 .send_note_expression(id, kind, value, sample_offset)
1816 }
1817
1818 pub fn note_expressions(&self) -> Result<Vec<crate::midi::NoteExpressionInfo>> {
1822 self.internal
1823 .as_ref()
1824 .ok_or_else(|| Error::Other("Plugin not initialized".to_string()))?
1825 .note_expressions(0, 0)
1826 }
1827
1828 pub fn start_processing(&mut self) -> Result<()> {
1830 if self.is_processing {
1831 return Ok(());
1832 }
1833
1834 self.internal
1835 .as_mut()
1836 .ok_or_else(|| Error::Other("Plugin not initialized".to_string()))?
1837 .start_processing()?;
1838
1839 self.is_processing = true;
1840 Ok(())
1841 }
1842
1843 pub fn stop_processing(&mut self) -> Result<()> {
1845 if !self.is_processing {
1846 return Ok(());
1847 }
1848
1849 self.internal
1850 .as_mut()
1851 .ok_or_else(|| Error::Other("Plugin not initialized".to_string()))?
1852 .stop_processing()?;
1853
1854 self.is_processing = false;
1855 Ok(())
1856 }
1857
1858 pub fn process_audio(&mut self, buffers: &mut AudioBuffers) -> Result<()> {
1865 if !self.is_processing {
1866 return Err(Error::NotProcessing);
1867 }
1868
1869 self.internal
1870 .as_mut()
1871 .ok_or_else(|| Error::Other("Plugin not initialized".to_string()))?
1872 .process(buffers)?;
1873
1874 if let Ok(mut levels) = self.audio_levels.lock() {
1876 levels.update_from_buffers(&buffers.outputs);
1877
1878 if let Some(ref callback) = self.audio_callback {
1880 callback(&levels);
1881 }
1882 }
1883
1884 Ok(())
1885 }
1886
1887 pub fn audio_bus_layout(&self) -> Result<AudioBusLayout> {
1889 self.internal
1890 .as_ref()
1891 .ok_or_else(|| Error::Other("Plugin not initialized".to_string()))?
1892 .audio_bus_layout()
1893 }
1894
1895 pub fn create_bus_audio_buffers(&self, block_size: usize) -> Result<BusAudioBuffers> {
1900 if block_size == 0 {
1901 return Err(Error::Other(
1902 "bus audio block size must be greater than zero".to_string(),
1903 ));
1904 }
1905 Ok(BusAudioBuffers::new(
1906 &self.audio_bus_layout()?,
1907 block_size,
1908 self.sample_rate,
1909 ))
1910 }
1911
1912 pub fn process_bus_audio(&mut self, buffers: &mut BusAudioBuffers) -> Result<()> {
1919 if !self.is_processing {
1920 return Err(Error::NotProcessing);
1921 }
1922 self.internal
1923 .as_mut()
1924 .ok_or_else(|| Error::Other("Plugin not initialized".to_string()))?
1925 .process_buses(buffers)?;
1926 if let Ok(mut levels) = self.audio_levels.lock() {
1927 levels.update_from_bus_buffers(&buffers.outputs);
1928 if let Some(ref callback) = self.audio_callback {
1929 callback(&levels);
1930 }
1931 }
1932 Ok(())
1933 }
1934
1935 pub fn get_output_levels(&self) -> AudioLevels {
1941 self.audio_levels
1942 .lock()
1943 .unwrap_or_else(|poisoned| poisoned.into_inner())
1944 .clone()
1945 }
1946
1947 pub fn is_processing(&self) -> bool {
1949 self.is_processing
1950 }
1951
1952 pub fn on_parameter_change<F>(&mut self, callback: F)
1961 where
1962 F: Fn(u32, f64) + Send + 'static,
1963 {
1964 self.parameter_change_callback = Some(Box::new(callback));
1965 }
1966
1967 pub fn on_audio_process<F>(&mut self, callback: F)
1977 where
1978 F: Fn(&AudioLevels) + Send + 'static,
1979 {
1980 self.audio_callback = Some(Box::new(callback));
1981 }
1982
1983 pub fn has_editor(&self) -> bool {
1985 self.internal
1986 .as_ref()
1987 .map(|i| i.has_editor())
1988 .unwrap_or(false)
1989 }
1990
1991 pub fn open_editor(&mut self, parent: WindowHandle) -> Result<()> {
1993 self.internal
1994 .as_mut()
1995 .ok_or_else(|| Error::Other("Plugin not initialized".to_string()))?
1996 .open_editor(parent.0)
1997 }
1998
1999 pub fn service_run_loop(&mut self) {
2006 if let Some(internal) = self.internal.as_mut() {
2007 internal.service_run_loop();
2008 }
2009 }
2010
2011 pub fn close_editor(&mut self) -> Result<()> {
2013 self.internal
2014 .as_mut()
2015 .ok_or_else(|| Error::Other("Plugin not initialized".to_string()))?
2016 .close_editor()
2017 }
2018
2019 pub fn get_editor_size(&self) -> Result<(i32, i32)> {
2021 self.internal
2022 .as_ref()
2023 .ok_or_else(|| Error::Other("Plugin not initialized".to_string()))?
2024 .get_editor_size()
2025 }
2026
2027 pub fn editor_can_resize(&self) -> bool {
2033 self.internal
2034 .as_ref()
2035 .is_some_and(|internal| internal.editor_can_resize())
2036 }
2037
2038 pub fn resize_editor(&mut self, width: i32, height: i32) -> Result<(i32, i32)> {
2042 self.internal
2043 .as_mut()
2044 .ok_or_else(|| Error::Other("Plugin not initialized".to_string()))?
2045 .resize_editor(width, height)
2046 }
2047
2048 pub fn set_editor_scale_factor(&mut self, factor: f32) -> Result<bool> {
2052 self.internal
2053 .as_mut()
2054 .ok_or_else(|| Error::Other("Plugin not initialized".to_string()))?
2055 .set_editor_scale_factor(factor)
2056 }
2057
2058 pub fn update_parameters<F>(&mut self, f: F) -> Result<()>
2069 where
2070 F: FnOnce(&mut ParameterUpdate) -> Result<()>,
2071 {
2072 let mut update = ParameterUpdate::new(self);
2073 f(&mut update)?;
2074 update.apply()
2075 }
2076
2077 pub fn midi_panic(&mut self) -> Result<()> {
2079 self.internal
2080 .as_mut()
2081 .ok_or_else(|| Error::Other("Plugin not initialized".to_string()))?
2082 .midi_panic()
2083 }
2084
2085 pub fn get_parameter_changes(&self) -> Vec<(u32, f64)> {
2094 self.internal
2095 .as_ref()
2096 .map(|i| i.get_parameter_changes())
2097 .unwrap_or_default()
2098 }
2099
2100 pub fn take_parameter_edits(&mut self) -> Vec<ParameterEdit> {
2112 self.internal
2113 .as_mut()
2114 .map(|i| i.take_parameter_edits())
2115 .unwrap_or_default()
2116 }
2117
2118 pub fn take_host_notifications(&mut self) -> Vec<HostNotification> {
2120 self.internal
2121 .as_mut()
2122 .map(|i| i.take_host_notifications())
2123 .unwrap_or_default()
2124 }
2125
2126 pub fn take_data_exchange_blocks(&mut self) -> Vec<DataExchangeBlock> {
2133 self.internal
2134 .as_mut()
2135 .map(|i| i.take_data_exchange_blocks())
2136 .unwrap_or_default()
2137 }
2138
2139 pub fn execute_context_menu_item(&mut self, menu_id: u64, item_id: u32) -> Result<()> {
2146 self.internal
2147 .as_mut()
2148 .ok_or_else(|| Error::Other("Plugin not initialized".to_string()))?
2149 .execute_context_menu_item(menu_id, item_id)
2150 }
2151
2152 pub fn dismiss_context_menu(&mut self, menu_id: u64) -> Result<()> {
2154 self.internal
2155 .as_mut()
2156 .ok_or_else(|| Error::Other("Plugin not initialized".to_string()))?
2157 .dismiss_context_menu(menu_id)
2158 }
2159
2160 pub fn take_restart_flags(&mut self) -> RestartFlags {
2167 self.internal
2168 .as_mut()
2169 .map(|i| i.take_restart_flags())
2170 .unwrap_or_default()
2171 }
2172
2173 pub fn service_host_requests(&mut self) -> Result<RestartFlags> {
2179 self.internal
2180 .as_mut()
2181 .ok_or_else(|| Error::Other("Plugin not initialized".to_string()))?
2182 .service_host_requests()
2183 }
2184
2185 pub fn take_output_midi(&self) -> Vec<MidiEvent> {
2196 self.internal
2197 .as_ref()
2198 .map(|i| {
2199 i.take_output_events()
2200 .into_iter()
2201 .filter_map(|event| event.to_midi())
2202 .collect()
2203 })
2204 .unwrap_or_default()
2205 }
2206
2207 pub fn take_output_events(&self) -> Vec<crate::midi::OutputEvent> {
2209 self.internal
2210 .as_ref()
2211 .map(|i| i.take_output_events())
2212 .unwrap_or_default()
2213 }
2214
2215 pub fn output_midi_handle(&self) -> Option<OutputMidiConsumer> {
2221 self.internal.as_ref().and_then(|i| i.output_midi_handle())
2222 }
2223
2224 pub fn output_event_handle(&self) -> Option<OutputEventConsumer> {
2226 self.internal.as_ref().and_then(|i| i.output_event_handle())
2227 }
2228
2229 pub fn save_state(&self) -> Result<Vec<u8>> {
2245 self.internal
2246 .as_ref()
2247 .ok_or_else(|| Error::Other("Plugin not initialized".to_string()))?
2248 .save_state()
2249 }
2250
2251 pub fn load_state(&mut self, data: &[u8]) -> Result<()> {
2261 self.internal
2262 .as_mut()
2263 .ok_or_else(|| Error::Other("Plugin not initialized".to_string()))?
2264 .load_state(data)
2265 }
2266
2267 pub fn load_state_with_context(&mut self, data: &[u8], context: &StateContext) -> Result<()> {
2279 self.internal
2280 .as_mut()
2281 .ok_or_else(|| Error::Other("Plugin not initialized".to_string()))?
2282 .load_state_with_context(data, context)
2283 }
2284
2285 pub fn save_preset<P: AsRef<std::path::Path>>(&self, path: P) -> Result<()> {
2289 let info = self.info();
2290 let preset = PluginPreset {
2291 uid: info.uid.clone(),
2292 plugin_name: info.name.clone(),
2293 state: self.save_state()?,
2294 };
2295 let json = serde_json::to_vec_pretty(&preset)
2296 .map_err(|e| Error::Other(format!("serialize preset: {e}")))?;
2297 std::fs::write(path, json).map_err(|e| Error::Other(format!("write preset: {e}")))?;
2298 Ok(())
2299 }
2300
2301 pub fn load_preset<P: AsRef<std::path::Path>>(&mut self, path: P) -> Result<()> {
2308 let path = path.as_ref();
2309 let bytes = std::fs::read(path).map_err(|e| Error::Other(format!("read preset: {e}")))?;
2310 let preset: PluginPreset = serde_json::from_slice(&bytes)
2311 .map_err(|e| Error::Other(format!("parse preset: {e}")))?;
2312 if !self.accepts_state_class_id(&preset.uid)? {
2313 return Err(Error::Other(format!(
2314 "preset is for a different plugin ({}, expected {})",
2315 preset.plugin_name,
2316 self.info().name
2317 )));
2318 }
2319 self.load_state_with_context(&preset.state, &StateContext::preset_from_path(path))
2320 }
2321
2322 pub fn save_vstpreset<P: AsRef<std::path::Path>>(&self, path: P) -> Result<()> {
2331 let state = decode_state_snapshot(&self.save_state()?)?;
2332 let bytes = vstpreset::build(
2333 &self.info().uid,
2334 &state.component,
2335 state.controller.as_deref(),
2336 )?;
2337 std::fs::write(path, bytes).map_err(|e| Error::Other(format!("write vstpreset: {e}")))?;
2338 Ok(())
2339 }
2340
2341 pub fn load_vstpreset<P: AsRef<std::path::Path>>(&mut self, path: P) -> Result<()> {
2353 let path = path.as_ref();
2354 let bytes =
2355 std::fs::read(path).map_err(|e| Error::Other(format!("read vstpreset: {e}")))?;
2356 let parsed = vstpreset::parse(&bytes)?;
2357 if !self.accepts_state_class_id(&parsed.class_id)? {
2358 return Err(Error::Other(format!(
2359 "vstpreset is for a different plugin (class id {}, expected {})",
2360 parsed.class_id,
2361 self.info().uid
2362 )));
2363 }
2364 let state = encode_state_snapshot(&StateSnapshot {
2365 component: parsed.component_state,
2366 controller: parsed.controller_state,
2367 })?;
2368 self.load_state_with_context(&state, &StateContext::preset_from_path(path))
2369 }
2370
2371 fn accepts_state_class_id(&self, candidate: &str) -> Result<bool> {
2375 if crate::internal::utils::class_uid_matches(&self.info().uid, candidate) {
2376 return Ok(true);
2377 }
2378 Ok(self.compatibility.iter().any(|mapping| {
2379 crate::internal::utils::class_uid_matches(&mapping.new_class_id, &self.info().uid)
2380 && mapping
2381 .old_class_ids
2382 .iter()
2383 .any(|old| crate::internal::utils::class_uid_matches(old, candidate))
2384 }))
2385 }
2386
2387 pub fn isolation_pid(&self) -> Option<u32> {
2390 self.internal.as_ref().and_then(|i| i.helper_pid())
2391 }
2392
2393 pub fn recovery_count(&self) -> u64 {
2403 self.internal
2404 .as_ref()
2405 .map(|i| i.recovery_count())
2406 .unwrap_or(0)
2407 }
2408
2409 pub fn output_channel_count(&self) -> usize {
2414 self.internal
2415 .as_ref()
2416 .map(|i| i.output_channel_count())
2417 .unwrap_or(2)
2418 }
2419
2420 pub fn take_editor_resize_request(&self) -> Option<(i32, i32)> {
2427 self.internal
2428 .as_ref()
2429 .and_then(|i| i.take_editor_resize_request())
2430 }
2431
2432 pub fn recover(&mut self) -> Result<()> {
2444 self.internal
2445 .as_mut()
2446 .ok_or_else(|| Error::Other("Plugin not initialized".to_string()))?
2447 .recover()
2448 }
2449}
2450
2451const MIDI_DATA_MAX: u8 = 127;
2453
2454const MIDI_PITCH_BEND_MAX: u16 = 16383;
2456
2457fn validate_note(note: u8) -> Result<()> {
2458 if note > MIDI_DATA_MAX {
2459 return Err(Error::MidiError(format!("Invalid note number: {}", note)));
2460 }
2461 Ok(())
2462}
2463
2464fn validate_velocity(velocity: u8) -> Result<()> {
2465 if velocity > MIDI_DATA_MAX {
2466 return Err(Error::MidiError(format!("Invalid velocity: {}", velocity)));
2467 }
2468 Ok(())
2469}
2470
2471fn validate_controller(controller: u8) -> Result<()> {
2472 if controller > MIDI_DATA_MAX {
2473 return Err(Error::MidiError(format!(
2474 "Invalid controller number: {}",
2475 controller
2476 )));
2477 }
2478 Ok(())
2479}
2480
2481fn validate_cc_value(value: u8) -> Result<()> {
2482 if value > MIDI_DATA_MAX {
2483 return Err(Error::MidiError(format!("Invalid CC value: {}", value)));
2484 }
2485 Ok(())
2486}
2487
2488fn validate_pressure(pressure: u8) -> Result<()> {
2489 if pressure > MIDI_DATA_MAX {
2490 return Err(Error::MidiError(format!(
2491 "Invalid pressure value: {}",
2492 pressure
2493 )));
2494 }
2495 Ok(())
2496}
2497
2498fn validate_midi_event(event: &MidiEvent) -> Result<()> {
2509 match *event {
2510 MidiEvent::NoteOn { note, velocity, .. } | MidiEvent::NoteOff { note, velocity, .. } => {
2511 validate_note(note)?;
2512 validate_velocity(velocity)
2513 }
2514 MidiEvent::ControlChange {
2515 controller, value, ..
2516 } => {
2517 validate_controller(controller)?;
2518 validate_cc_value(value)
2519 }
2520 MidiEvent::ProgramChange { program, .. } => {
2521 if program > MIDI_DATA_MAX {
2522 return Err(Error::MidiError(format!(
2523 "Invalid program number: {}",
2524 program
2525 )));
2526 }
2527 Ok(())
2528 }
2529 MidiEvent::PitchBend { value, .. } => {
2530 if value > MIDI_PITCH_BEND_MAX {
2531 return Err(Error::MidiError(format!(
2532 "Invalid pitch bend value: {} (0-{})",
2533 value, MIDI_PITCH_BEND_MAX
2534 )));
2535 }
2536 Ok(())
2537 }
2538 MidiEvent::ChannelAftertouch { pressure, .. } => validate_pressure(pressure),
2539 MidiEvent::PolyAftertouch { note, pressure, .. } => {
2540 validate_note(note)?;
2541 validate_pressure(pressure)
2542 }
2543 }
2544}
2545
2546fn validate_plugin_event(event: &PluginEvent) -> Result<()> {
2547 if event.bus_index < 0 {
2548 return Err(Error::MidiError(format!(
2549 "Invalid event bus index: {}",
2550 event.bus_index
2551 )));
2552 }
2553 if event.sample_offset < 0 {
2554 return Err(Error::MidiError(format!(
2555 "Invalid event sample offset: {}",
2556 event.sample_offset
2557 )));
2558 }
2559 if !event.ppq_position.is_finite() {
2560 return Err(Error::MidiError(
2561 "Event PPQ position must be finite".to_string(),
2562 ));
2563 }
2564
2565 let validate_channel = |channel: i16| {
2566 if (0..16).contains(&channel) {
2567 Ok(())
2568 } else {
2569 Err(Error::MidiError(format!(
2570 "Invalid VST3 event channel: {channel}"
2571 )))
2572 }
2573 };
2574 let validate_pitch = |pitch: i16| {
2575 if (0..=127).contains(&pitch) {
2576 Ok(())
2577 } else {
2578 Err(Error::MidiError(format!(
2579 "Invalid VST3 event pitch: {pitch}"
2580 )))
2581 }
2582 };
2583 let validate_normalized = |name: &str, value: f64| {
2584 if value.is_finite() && (0.0..=1.0).contains(&value) {
2585 Ok(())
2586 } else {
2587 Err(Error::MidiError(format!(
2588 "{name} must be finite and normalized to [0.0, 1.0]"
2589 )))
2590 }
2591 };
2592
2593 match &event.data {
2594 PluginEventData::NoteOn {
2595 channel,
2596 pitch,
2597 tuning,
2598 velocity,
2599 length,
2600 ..
2601 } => {
2602 validate_channel(*channel)?;
2603 validate_pitch(*pitch)?;
2604 validate_normalized("note velocity", f64::from(*velocity))?;
2605 if !tuning.is_finite() || *length < 0 {
2606 return Err(Error::MidiError(
2607 "note tuning must be finite and length non-negative".to_string(),
2608 ));
2609 }
2610 Ok(())
2611 }
2612 PluginEventData::NoteOff {
2613 channel,
2614 pitch,
2615 velocity,
2616 tuning,
2617 ..
2618 } => {
2619 validate_channel(*channel)?;
2620 validate_pitch(*pitch)?;
2621 validate_normalized("note-off velocity", f64::from(*velocity))?;
2622 if !tuning.is_finite() {
2623 return Err(Error::MidiError(
2624 "note-off tuning must be finite".to_string(),
2625 ));
2626 }
2627 Ok(())
2628 }
2629 PluginEventData::Data { data_type, bytes } => {
2630 if *data_type != 0 {
2631 return Err(Error::MidiError(format!(
2632 "Unsupported VST3 data event type: {data_type}"
2633 )));
2634 }
2635 if bytes.len() > MAX_EVENT_PAYLOAD_BYTES {
2636 return Err(Error::MidiError(format!(
2637 "Event payload is {} bytes; maximum is {MAX_EVENT_PAYLOAD_BYTES}",
2638 bytes.len()
2639 )));
2640 }
2641 Ok(())
2642 }
2643 PluginEventData::PolyPressure {
2644 channel,
2645 pitch,
2646 pressure,
2647 ..
2648 } => {
2649 validate_channel(*channel)?;
2650 validate_pitch(*pitch)?;
2651 validate_normalized("poly pressure", f64::from(*pressure))
2652 }
2653 PluginEventData::NoteExpressionValue { value, .. } => {
2654 validate_normalized("note-expression value", *value)
2655 }
2656 PluginEventData::NoteExpressionText { text, .. }
2657 | PluginEventData::Chord { text, .. }
2658 | PluginEventData::Scale { text, .. } => {
2659 if text.len() > MAX_EVENT_TEXT_UNITS {
2660 return Err(Error::MidiError(format!(
2661 "Event text is {} UTF-16 units; maximum is {MAX_EVENT_TEXT_UNITS}",
2662 text.len()
2663 )));
2664 }
2665 Ok(())
2666 }
2667 PluginEventData::NoteExpressionIntValue { .. } => Ok(()),
2668 PluginEventData::LegacyMidiCcOut { .. } => Err(Error::MidiError(
2669 "Legacy MIDI CC events are plugin output only".to_string(),
2670 )),
2671 }
2672}
2673
2674pub struct WindowHandle(pub(crate) *mut std::ffi::c_void);
2676
2677impl WindowHandle {
2678 pub unsafe fn from_raw(handle: *mut std::ffi::c_void) -> Self {
2686 Self(handle)
2687 }
2688}
2689
2690unsafe impl Send for WindowHandle {}
2692
2693#[cfg(target_os = "macos")]
2694impl WindowHandle {
2695 pub unsafe fn from_nsview(view: *mut std::ffi::c_void) -> Self {
2704 Self(view)
2705 }
2706}
2707
2708#[cfg(target_os = "windows")]
2709impl WindowHandle {
2710 pub unsafe fn from_hwnd(hwnd: *mut std::ffi::c_void) -> Self {
2718 Self(hwnd)
2719 }
2720}
2721
2722#[cfg(target_os = "linux")]
2723impl WindowHandle {
2724 pub fn from_x11(window_id: u32) -> Self {
2729 Self(window_id as usize as *mut std::ffi::c_void)
2730 }
2731}
2732
2733mod vstpreset {
2745 use crate::error::{Error, Result};
2746
2747 const MAGIC: &[u8; 4] = b"VST3";
2748 const LIST_MAGIC: &[u8; 4] = b"List";
2749 const COMPONENT_CHUNK: &[u8; 4] = b"Comp";
2750 const CONTROLLER_CHUNK: &[u8; 4] = b"Cont";
2751 const VERSION: i32 = 1;
2752 const CLASS_ID_LEN: usize = 32;
2753 const HEADER_SIZE: usize = 4 + 4 + CLASS_ID_LEN + 8;
2754 const LIST_HEADER_SIZE: usize = 8;
2755 const ENTRY_SIZE: usize = 20;
2756
2757 pub(super) struct Parsed {
2759 pub class_id: String,
2761 pub component_state: Vec<u8>,
2763 pub controller_state: Option<Vec<u8>>,
2765 }
2766
2767 pub(super) fn build(
2769 class_id: &str,
2770 component_state: &[u8],
2771 controller_state: Option<&[u8]>,
2772 ) -> Result<Vec<u8>> {
2773 let class_bytes = class_id.as_bytes();
2774 if class_bytes.len() != CLASS_ID_LEN
2775 || !class_bytes.iter().all(|byte| byte.is_ascii_hexdigit())
2776 {
2777 return Err(Error::Other(format!(
2778 "vstpreset class id must be {CLASS_ID_LEN} ASCII hex chars, got {:?}",
2779 class_id
2780 )));
2781 }
2782
2783 let comp_offset = HEADER_SIZE as i64;
2784 let comp_size = component_state.len() as i64;
2785 let controller_offset = HEADER_SIZE
2786 .checked_add(component_state.len())
2787 .ok_or_else(|| Error::Other("vstpreset size overflow".to_string()))?;
2788 let list_offset = controller_offset
2789 .checked_add(controller_state.map_or(0, <[u8]>::len))
2790 .ok_or_else(|| Error::Other("vstpreset size overflow".to_string()))?;
2791 let entry_count = if controller_state.is_some() { 2 } else { 1 };
2792
2793 let mut out = Vec::with_capacity(list_offset + LIST_HEADER_SIZE + entry_count * ENTRY_SIZE);
2794 out.extend_from_slice(MAGIC);
2796 out.extend_from_slice(&VERSION.to_le_bytes());
2797 out.extend(class_bytes.iter().map(u8::to_ascii_uppercase));
2798 out.extend_from_slice(&(list_offset as i64).to_le_bytes());
2799 out.extend_from_slice(component_state);
2801 if let Some(controller) = controller_state {
2802 out.extend_from_slice(controller);
2803 }
2804 out.extend_from_slice(LIST_MAGIC);
2806 out.extend_from_slice(&(entry_count as i32).to_le_bytes());
2807 out.extend_from_slice(COMPONENT_CHUNK);
2808 out.extend_from_slice(&comp_offset.to_le_bytes());
2809 out.extend_from_slice(&comp_size.to_le_bytes());
2810 if let Some(controller) = controller_state {
2811 out.extend_from_slice(CONTROLLER_CHUNK);
2812 out.extend_from_slice(&(controller_offset as i64).to_le_bytes());
2813 out.extend_from_slice(&(controller.len() as i64).to_le_bytes());
2814 }
2815
2816 Ok(out)
2817 }
2818
2819 pub(super) fn parse(bytes: &[u8]) -> Result<Parsed> {
2821 if bytes.len() < HEADER_SIZE {
2822 return Err(Error::Other("vstpreset too short for header".to_string()));
2823 }
2824 if &bytes[0..4] != MAGIC {
2825 return Err(Error::Other(format!(
2826 "bad vstpreset magic: expected {:?}, got {:?}",
2827 MAGIC,
2828 &bytes[0..4]
2829 )));
2830 }
2831 let version = read_i32(&bytes[4..8]);
2832 if version != VERSION {
2833 return Err(Error::Other(format!(
2834 "unsupported vstpreset version {version} (expected {VERSION})"
2835 )));
2836 }
2837 let class_id = String::from_utf8(bytes[8..8 + CLASS_ID_LEN].to_vec())
2838 .map_err(|e| Error::Other(format!("vstpreset class id not UTF-8: {e}")))?;
2839 if !class_id.bytes().all(|byte| byte.is_ascii_hexdigit()) {
2840 return Err(Error::Other(
2841 "vstpreset class id is not 32 ASCII hex characters".to_string(),
2842 ));
2843 }
2844 let list_offset = read_i64(&bytes[8 + CLASS_ID_LEN..HEADER_SIZE]);
2845 if list_offset < HEADER_SIZE as i64 || list_offset as usize > bytes.len() {
2846 return Err(Error::Other(format!(
2847 "vstpreset chunk-list offset {list_offset} out of bounds (len {})",
2848 bytes.len()
2849 )));
2850 }
2851 let list = &bytes[list_offset as usize..];
2852 if list.len() < 8 || &list[0..4] != LIST_MAGIC {
2853 return Err(Error::Other(
2854 "vstpreset chunk list missing or malformed".to_string(),
2855 ));
2856 }
2857 let count = read_i32(&list[4..8]);
2858 if count < 0 {
2859 return Err(Error::Other("vstpreset negative entry count".to_string()));
2860 }
2861 let count = count as usize;
2862 let list_size = LIST_HEADER_SIZE
2863 .checked_add(
2864 count
2865 .checked_mul(ENTRY_SIZE)
2866 .ok_or_else(|| Error::Other("vstpreset entry count overflow".to_string()))?,
2867 )
2868 .ok_or_else(|| Error::Other("vstpreset list size overflow".to_string()))?;
2869 if list.len() < list_size {
2870 return Err(Error::Other(
2871 "vstpreset chunk-list entry truncated".to_string(),
2872 ));
2873 }
2874
2875 let body_end = list_offset as usize;
2876 let mut cursor = LIST_HEADER_SIZE;
2877 let mut component_state = None;
2878 let mut controller_state = None;
2879 let mut ranges: Vec<(usize, usize)> = Vec::with_capacity(count);
2880 for _ in 0..count {
2881 let id = &list[cursor..cursor + 4];
2882 let offset = read_i64(&list[cursor + 4..cursor + 12]);
2883 let size = read_i64(&list[cursor + 12..cursor + 20]);
2884 cursor += ENTRY_SIZE;
2885 if offset < HEADER_SIZE as i64 || size < 0 {
2886 return Err(Error::Other(
2887 "vstpreset chunk has invalid offset/size".to_string(),
2888 ));
2889 }
2890 let start = usize::try_from(offset)
2891 .map_err(|_| Error::Other("vstpreset chunk offset overflow".to_string()))?;
2892 let size = usize::try_from(size)
2893 .map_err(|_| Error::Other("vstpreset chunk size overflow".to_string()))?;
2894 let end = start
2895 .checked_add(size)
2896 .ok_or_else(|| Error::Other("vstpreset chunk size overflow".to_string()))?;
2897 if end > body_end {
2898 return Err(Error::Other(format!(
2899 "vstpreset chunk [{start}..{end}] is outside the payload body \
2900 [{HEADER_SIZE}..{body_end}]"
2901 )));
2902 }
2903 if ranges
2904 .iter()
2905 .any(|&(other_start, other_end)| start < other_end && other_start < end)
2906 {
2907 return Err(Error::Other("vstpreset chunk payloads overlap".to_string()));
2908 }
2909 ranges.push((start, end));
2910
2911 match id {
2912 id if id == COMPONENT_CHUNK => {
2913 if component_state.is_some() {
2914 return Err(Error::Other(
2915 "vstpreset has duplicate component chunks".to_string(),
2916 ));
2917 }
2918 component_state = Some(bytes[start..end].to_vec());
2919 }
2920 id if id == CONTROLLER_CHUNK => {
2921 if controller_state.is_some() {
2922 return Err(Error::Other(
2923 "vstpreset has duplicate controller chunks".to_string(),
2924 ));
2925 }
2926 controller_state = Some(bytes[start..end].to_vec());
2927 }
2928 _ => {}
2929 }
2930 }
2931 let component_state = component_state.ok_or_else(|| {
2932 Error::Other("vstpreset has no component (\"Comp\") chunk".to_string())
2933 })?;
2934 Ok(Parsed {
2935 class_id,
2936 component_state,
2937 controller_state,
2938 })
2939 }
2940
2941 fn read_i32(b: &[u8]) -> i32 {
2942 i32::from_le_bytes([b[0], b[1], b[2], b[3]])
2943 }
2944
2945 fn read_i64(b: &[u8]) -> i64 {
2946 i64::from_le_bytes([b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7]])
2947 }
2948}
2949
2950#[cfg(test)]
2951mod public_surface_tests {
2952 use super::*;
2953
2954 fn unloaded_plugin() -> Plugin {
2957 Plugin {
2958 info: PluginInfo {
2959 path: std::path::PathBuf::from("/none.vst3"),
2960 name: "None".to_string(),
2961 vendor: String::new(),
2962 version: String::new(),
2963 category: String::new(),
2964 uid: String::new(),
2965 audio_inputs: 0,
2966 audio_outputs: 2,
2967 has_midi_input: true,
2968 has_midi_output: false,
2969 has_gui: false,
2970 },
2971 compatibility: Vec::new(),
2972 is_processing: false,
2973 sample_rate: 44_100.0,
2974 block_size: 512,
2975 audio_levels: Arc::new(Mutex::new(AudioLevels::new(2))),
2976 parameter_change_callback: None,
2977 audio_callback: None,
2978 internal: None,
2979 }
2980 }
2981
2982 #[test]
2985 fn process_audio_while_stopped_is_the_allocation_free_variant() {
2986 let mut plugin = unloaded_plugin();
2987 let mut buffers = AudioBuffers::new(0, 2, 64, 44_100.0);
2988 let err = plugin
2989 .process_audio(&mut buffers)
2990 .expect_err("processing is stopped");
2991 assert!(
2992 matches!(err, Error::NotProcessing),
2993 "expected Error::NotProcessing, got {err:?}"
2994 );
2995 }
2996
2997 #[test]
3001 fn send_midi_event_rejects_out_of_range_fields() {
3002 let mut plugin = unloaded_plugin();
3003 let bad = [
3004 MidiEvent::NoteOn {
3005 channel: MidiChannel::Ch1,
3006 note: 255,
3007 velocity: 100,
3008 },
3009 MidiEvent::NoteOn {
3010 channel: MidiChannel::Ch1,
3011 note: 60,
3012 velocity: 255,
3013 },
3014 MidiEvent::NoteOff {
3015 channel: MidiChannel::Ch1,
3016 note: 128,
3017 velocity: 0,
3018 },
3019 MidiEvent::ControlChange {
3020 channel: MidiChannel::Ch1,
3021 controller: 200,
3022 value: 0,
3023 },
3024 MidiEvent::ControlChange {
3025 channel: MidiChannel::Ch1,
3026 controller: 1,
3027 value: 200,
3028 },
3029 MidiEvent::ProgramChange {
3030 channel: MidiChannel::Ch1,
3031 program: 200,
3032 },
3033 MidiEvent::PitchBend {
3034 channel: MidiChannel::Ch1,
3035 value: 16_384,
3036 },
3037 MidiEvent::ChannelAftertouch {
3038 channel: MidiChannel::Ch1,
3039 pressure: 200,
3040 },
3041 MidiEvent::PolyAftertouch {
3042 channel: MidiChannel::Ch1,
3043 note: 200,
3044 pressure: 1,
3045 },
3046 MidiEvent::PolyAftertouch {
3047 channel: MidiChannel::Ch1,
3048 note: 60,
3049 pressure: 200,
3050 },
3051 ];
3052 for event in bad {
3053 for err in [
3054 plugin.send_midi_event(event).expect_err("out of range"),
3055 plugin
3056 .send_midi_event_at(event, 0)
3057 .expect_err("out of range"),
3058 ] {
3059 assert!(
3060 matches!(err, Error::MidiError(_)),
3061 "expected a MidiError for {event:?}, got {err:?}"
3062 );
3063 }
3064 }
3065 }
3066
3067 #[test]
3070 fn send_midi_event_accepts_in_range_fields() {
3071 let mut plugin = unloaded_plugin();
3072 let ok = [
3073 MidiEvent::NoteOn {
3074 channel: MidiChannel::Ch1,
3075 note: 127,
3076 velocity: 127,
3077 },
3078 MidiEvent::ControlChange {
3079 channel: MidiChannel::Ch1,
3080 controller: 127,
3081 value: 127,
3082 },
3083 MidiEvent::ProgramChange {
3084 channel: MidiChannel::Ch1,
3085 program: 127,
3086 },
3087 MidiEvent::PitchBend {
3088 channel: MidiChannel::Ch1,
3089 value: 16_383,
3090 },
3091 MidiEvent::ChannelAftertouch {
3092 channel: MidiChannel::Ch1,
3093 pressure: 127,
3094 },
3095 MidiEvent::PolyAftertouch {
3096 channel: MidiChannel::Ch1,
3097 note: 127,
3098 pressure: 127,
3099 },
3100 ];
3101 for event in ok {
3102 let err = plugin.send_midi_event(event).expect_err("no plugin loaded");
3103 assert!(
3104 matches!(err, Error::Other(_)),
3105 "expected the uninitialized-plugin error for {event:?}, got {err:?}"
3106 );
3107 }
3108 }
3109
3110 #[test]
3113 fn note_on_rejects_out_of_range_note_and_velocity() {
3114 let mut plugin = unloaded_plugin();
3115 for (note, velocity) in [(128, 100), (60, 128), (255, 255)] {
3116 let err = plugin
3117 .note_on(MidiChannel::Ch1, note, velocity)
3118 .expect_err("out of range");
3119 assert!(
3120 matches!(err, Error::MidiError(_)),
3121 "expected a MidiError for note {note} velocity {velocity}, got {err:?}"
3122 );
3123 let err = plugin
3124 .note_on_at(MidiChannel::Ch1, note, velocity, 0)
3125 .expect_err("out of range");
3126 assert!(matches!(err, Error::MidiError(_)), "got {err:?}");
3127 }
3128 }
3129}
3130
3131#[cfg(test)]
3132mod output_midi_consumer_tests {
3133 use super::*;
3134
3135 fn note(n: u8) -> MidiEvent {
3136 MidiEvent::NoteOn {
3137 channel: MidiChannel::Ch1,
3138 note: n,
3139 velocity: 100,
3140 }
3141 }
3142
3143 #[test]
3144 fn drains_in_order_and_drops_oldest_when_full() {
3145 let q = Arc::new(ArrayQueue::new(2));
3146 let consumer = OutputMidiConsumer::from_queue(q.clone());
3147
3148 q.force_push(note(60).into());
3150 q.force_push(note(61).into());
3151 q.force_push(note(62).into()); assert_eq!(consumer.drain(), vec![note(61), note(62)]);
3154 assert_eq!(consumer.pop(), None);
3156 assert_eq!(consumer.drain(), vec![]);
3157 }
3158
3159 #[test]
3160 fn handle_is_send_and_shares_the_queue_across_threads() {
3161 let q = Arc::new(ArrayQueue::new(8));
3162 let consumer = OutputMidiConsumer::from_queue(q.clone());
3163 let producer = q.clone();
3165 std::thread::spawn(move || {
3166 producer.force_push(note(64).into());
3167 })
3168 .join()
3169 .unwrap();
3170 assert_eq!(consumer.pop(), Some(note(64)));
3171 }
3172}
3173
3174#[cfg(test)]
3175mod vstpreset_tests {
3176 use super::{
3177 decode_state_snapshot, encode_state_snapshot, vstpreset, StateSnapshot,
3178 STATE_SNAPSHOT_MAGIC,
3179 };
3180
3181 const TEST_CLASS_ID: &str = "0123456789ABCDEF0123456789ABCDEF";
3182
3183 #[test]
3184 fn build_parse_round_trip() {
3185 let state = b"opaque plugin state \x00\x01\x02\xff bytes".to_vec();
3186 let controller = b"controller-only state".to_vec();
3187 let bytes = vstpreset::build(TEST_CLASS_ID, &state, Some(&controller)).expect("build");
3188
3189 assert_eq!(&bytes[0..4], b"VST3");
3191 assert_eq!(
3192 i32::from_le_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]),
3193 1
3194 );
3195 assert_eq!(&bytes[8..40], TEST_CLASS_ID.as_bytes());
3196
3197 let parsed = vstpreset::parse(&bytes).expect("parse");
3198 assert_eq!(parsed.class_id, TEST_CLASS_ID);
3199 assert_eq!(parsed.component_state, state);
3200 assert_eq!(parsed.controller_state, Some(controller));
3201 }
3202
3203 #[test]
3204 fn round_trip_empty_state() {
3205 let bytes = vstpreset::build(TEST_CLASS_ID, &[], None).expect("build");
3206 let parsed = vstpreset::parse(&bytes).expect("parse");
3207 assert_eq!(parsed.class_id, TEST_CLASS_ID);
3208 assert!(parsed.component_state.is_empty());
3209 assert!(parsed.controller_state.is_none());
3210 }
3211
3212 #[test]
3213 fn round_trip_empty_controller_state() {
3214 let bytes = vstpreset::build(TEST_CLASS_ID, b"component", Some(&[])).expect("build");
3215 let parsed = vstpreset::parse(&bytes).expect("parse");
3216 assert_eq!(parsed.controller_state, Some(Vec::new()));
3217 }
3218
3219 #[test]
3220 fn build_rejects_wrong_length_class_id() {
3221 assert!(vstpreset::build("short", b"x", None).is_err());
3222 assert!(vstpreset::build("Z123456789ABCDEF0123456789ABCDEF", b"x", None).is_err());
3223 }
3224
3225 #[test]
3226 fn parse_rejects_bad_magic() {
3227 let mut bytes = vstpreset::build(TEST_CLASS_ID, b"x", None).expect("build");
3228 bytes[0] = b'X';
3229 assert!(vstpreset::parse(&bytes).is_err());
3230 }
3231
3232 #[test]
3233 fn parse_rejects_truncated_header() {
3234 assert!(vstpreset::parse(b"VST3").is_err());
3235 }
3236
3237 #[test]
3238 fn parse_rejects_out_of_bounds_list_offset() {
3239 let mut bytes = vstpreset::build(TEST_CLASS_ID, b"hello", None).expect("build");
3240 let bad = (bytes.len() as i64 + 100).to_le_bytes();
3242 bytes[40..48].copy_from_slice(&bad);
3243 assert!(vstpreset::parse(&bytes).is_err());
3244 }
3245
3246 #[test]
3247 fn parser_accepts_unknown_chunk_and_controller_before_component() {
3248 let comp = b"component";
3249 let cont = b"controller";
3250 let unknown = b"metadata";
3251 let body_len = comp.len() + cont.len() + unknown.len();
3252 let list_offset = 48 + body_len;
3253 let mut bytes = Vec::new();
3254 bytes.extend_from_slice(b"VST3");
3255 bytes.extend_from_slice(&1i32.to_le_bytes());
3256 bytes.extend_from_slice(TEST_CLASS_ID.as_bytes());
3257 bytes.extend_from_slice(&(list_offset as i64).to_le_bytes());
3258 bytes.extend_from_slice(comp);
3259 bytes.extend_from_slice(cont);
3260 bytes.extend_from_slice(unknown);
3261 bytes.extend_from_slice(b"List");
3262 bytes.extend_from_slice(&3i32.to_le_bytes());
3263 for (id, offset, state) in [
3264 (b"Cont", 48 + comp.len(), cont.as_slice()),
3265 (b"Info", 48 + comp.len() + cont.len(), unknown.as_slice()),
3266 (b"Comp", 48, comp.as_slice()),
3267 ] {
3268 bytes.extend_from_slice(id);
3269 bytes.extend_from_slice(&(offset as i64).to_le_bytes());
3270 bytes.extend_from_slice(&(state.len() as i64).to_le_bytes());
3271 }
3272 let parsed = vstpreset::parse(&bytes).expect("parse");
3273 assert_eq!(parsed.component_state, comp);
3274 assert_eq!(parsed.controller_state.as_deref(), Some(cont.as_slice()));
3275 }
3276
3277 #[test]
3278 fn parser_rejects_duplicate_or_overlapping_chunks() {
3279 let mut duplicate =
3280 vstpreset::build(TEST_CLASS_ID, b"component", Some(b"controller")).expect("build");
3281 let list_offset = i64::from_le_bytes(duplicate[40..48].try_into().unwrap()) as usize;
3282 duplicate[list_offset + 28..list_offset + 32].copy_from_slice(b"Comp");
3283 assert!(vstpreset::parse(&duplicate).is_err());
3284
3285 let mut overlap =
3286 vstpreset::build(TEST_CLASS_ID, b"component", Some(b"controller")).expect("build");
3287 let list_offset = i64::from_le_bytes(overlap[40..48].try_into().unwrap()) as usize;
3288 let comp_offset = i64::from_le_bytes(
3289 overlap[list_offset + 12..list_offset + 20]
3290 .try_into()
3291 .unwrap(),
3292 );
3293 overlap[list_offset + 32..list_offset + 40]
3294 .copy_from_slice(&(comp_offset + 1).to_le_bytes());
3295 assert!(vstpreset::parse(&overlap).is_err());
3296 }
3297
3298 #[test]
3299 fn state_snapshot_round_trip_and_legacy_component_compatibility() {
3300 let snapshot = StateSnapshot {
3301 component: b"component".to_vec(),
3302 controller: Some(b"controller".to_vec()),
3303 };
3304 let bytes = encode_state_snapshot(&snapshot).expect("encode");
3305 assert!(bytes.starts_with(STATE_SNAPSHOT_MAGIC));
3306 let decoded = decode_state_snapshot(&bytes).expect("decode");
3307 assert_eq!(decoded.component, snapshot.component);
3308 assert_eq!(decoded.controller, snapshot.controller);
3309
3310 let legacy = decode_state_snapshot(b"old raw component blob").expect("legacy");
3311 assert_eq!(legacy.component, b"old raw component blob");
3312 assert!(legacy.controller.is_none());
3313 }
3314
3315 #[test]
3316 fn state_snapshot_rejects_bad_lengths_and_versions() {
3317 let snapshot = StateSnapshot {
3318 component: b"component".to_vec(),
3319 controller: Some(b"controller".to_vec()),
3320 };
3321 let mut bytes = encode_state_snapshot(&snapshot).expect("encode");
3322 bytes[16..20].copy_from_slice(&2u32.to_le_bytes());
3323 assert!(decode_state_snapshot(&bytes).is_err());
3324
3325 let mut bytes = encode_state_snapshot(&snapshot).expect("encode");
3326 bytes[20..24].copy_from_slice(&u32::MAX.to_le_bytes());
3327 assert!(decode_state_snapshot(&bytes).is_err());
3328 }
3329}