1#![allow(clippy::unnecessary_cast)]
4
5use super::interfaces::{HostPlugFrame, PluginInstance, Vst3GuiInfo, protected_call};
6use super::midi::{EventBuffer, ParameterChanges};
7use super::port::{BusInfo, ParameterInfo};
8use super::state::{MemoryStream, Vst3PluginState, ibstream_ptr};
9use crate::audio::io::AudioIO;
10use crate::midi::io::MidiEvent;
11use std::ffi::{CString, c_void};
12use std::fmt;
13use std::path::Path;
14use std::sync::atomic::Ordering;
15use std::sync::{Arc, Mutex};
16use vst3::ComPtr;
17use vst3::ComWrapper;
18use vst3::Steinberg::Vst::ProcessModes_::kRealtime;
19use vst3::Steinberg::Vst::SymbolicSampleSizes_::kSample32;
20use vst3::Steinberg::Vst::{IEditControllerTrait, ViewType};
21use vst3::Steinberg::{FIDString, IPlugFrame, IPlugView, IPlugViewTrait, ViewRect, kResultOk};
22
23pub struct Vst3Processor {
24 path: String,
26 name: String,
27 plugin_id: String,
28
29 instance: PluginInstance,
31 _factory: super::interfaces::PluginFactory,
33
34 audio_inputs: Vec<Arc<AudioIO>>,
36 audio_outputs: Vec<Arc<AudioIO>>,
37 midi_input_ports: usize,
38 midi_output_ports: usize,
39 main_audio_inputs: usize,
40 main_audio_outputs: usize,
41 input_buses: Vec<BusInfo>,
42 output_buses: Vec<BusInfo>,
43
44 parameters: Vec<ParameterInfo>,
46 scalar_values: Arc<Mutex<Vec<f32>>>,
47 previous_values: Arc<Mutex<Vec<f32>>>,
48 max_samples_per_block: usize,
49 processing_started: bool,
50 sample_rate: f64,
51
52 gui_session: Arc<Mutex<Vst3GuiSession>>,
54}
55
56struct Vst3GuiSession {
57 view: Option<ComPtr<IPlugView>>,
58 plug_frame: Option<ComWrapper<HostPlugFrame>>,
59 ui_should_close: bool,
60 platform_type: Option<String>,
61}
62
63impl fmt::Debug for Vst3Processor {
64 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
65 f.debug_struct("Vst3Processor")
66 .field("path", &self.path)
67 .field("name", &self.name)
68 .field("plugin_id", &self.plugin_id)
69 .field("audio_inputs", &self.audio_inputs.len())
70 .field("audio_outputs", &self.audio_outputs.len())
71 .field("midi_input_ports", &self.midi_input_ports)
72 .field("midi_output_ports", &self.midi_output_ports)
73 .field("main_audio_inputs", &self.main_audio_inputs)
74 .field("main_audio_outputs", &self.main_audio_outputs)
75 .field("input_buses", &self.input_buses)
76 .field("output_buses", &self.output_buses)
77 .field("parameters", &self.parameters)
78 .field("max_samples_per_block", &self.max_samples_per_block)
79 .field("processing_started", &self.processing_started)
80 .finish()
81 }
82}
83
84impl Vst3Processor {
85 pub fn new_with_sample_rate(
87 sample_rate: f64,
88 buffer_size: usize,
89 plugin_path: &str,
90 audio_inputs: usize,
91 audio_outputs: usize,
92 ) -> Result<Self, String> {
93 let path_buf = Path::new(plugin_path);
94 let name = path_buf
95 .file_stem()
96 .or_else(|| path_buf.file_name())
97 .and_then(|s| s.to_str())
98 .unwrap_or("Unknown VST3")
99 .to_string();
100
101 let factory = super::interfaces::PluginFactory::from_module(path_buf)?;
103
104 let class_count = factory.count_classes();
105 if class_count == 0 {
106 return Err("No plugin classes found".to_string());
107 }
108
109 let mut class_info = None;
111 for i in 0..class_count {
112 if let Some(info) = factory.get_class_info(i)
113 && info.category.contains("Audio Module")
114 {
115 class_info = Some(info);
116 break;
117 }
118 }
119 let class_info = class_info
121 .or_else(|| factory.get_class_info(0))
122 .ok_or("Failed to get class info")?;
123
124 let mut instance = factory.create_instance(&class_info.cid)?;
125
126 instance.initialize(&factory)?;
128
129 let (plugin_input_buses, plugin_output_buses) = instance.audio_bus_counts();
130 let (plugin_main_in_channels, plugin_main_out_channels) =
131 instance.main_audio_channel_counts();
132 let (midi_input_ports, midi_output_ports) = instance.event_bus_counts();
133
134 let requested_inputs = if plugin_input_buses > 0 {
135 audio_inputs
136 .max(1)
137 .min(plugin_main_in_channels.max(1))
138 .min(i32::MAX as usize)
139 } else {
140 0
141 };
142 let requested_outputs = if plugin_output_buses > 0 {
143 audio_outputs
144 .max(1)
145 .min(plugin_main_out_channels.max(1))
146 .min(i32::MAX as usize)
147 } else {
148 0
149 };
150 let input_buses = if plugin_input_buses > 0 {
152 vec![BusInfo {
153 index: 0,
154 name: "Input".to_string(),
155 channel_count: requested_inputs.max(1),
156 is_active: true,
157 }]
158 } else {
159 vec![]
160 };
161
162 let output_buses = if plugin_output_buses > 0 {
163 vec![BusInfo {
164 index: 0,
165 name: "Output".to_string(),
166 channel_count: requested_outputs.max(1),
167 is_active: true,
168 }]
169 } else {
170 vec![]
171 };
172
173 let mut audio_input_ios = Vec::new();
175 for _ in 0..requested_inputs {
176 audio_input_ios.push(Arc::new(AudioIO::new(buffer_size)));
177 }
178
179 let mut audio_output_ios = Vec::new();
180 for _ in 0..requested_outputs {
181 audio_output_ios.push(Arc::new(AudioIO::new(buffer_size)));
182 }
183
184 instance.setup_processing(
187 sample_rate,
188 buffer_size as i32,
189 requested_inputs as i32,
190 requested_outputs as i32,
191 )?;
192 instance.set_active(true)?;
193
194 let processing_started = false;
195
196 let parameters = protected_call(|| instance.query_parameters()).unwrap_or_default();
199 let scalar_values = Arc::new(Mutex::new(
200 parameters.iter().map(|p| p.default_value as f32).collect(),
201 ));
202 let previous_values = Arc::new(Mutex::new(
203 parameters.iter().map(|p| p.default_value as f32).collect(),
204 ));
205 let plugin_id = format!("{:02X?}", class_info.cid);
206
207 let gui_session = Arc::new(Mutex::new(Vst3GuiSession {
208 view: None,
209 plug_frame: None,
210 ui_should_close: false,
211 platform_type: None,
212 }));
213
214 Ok(Self {
215 path: plugin_path.to_string(),
216 name,
217 plugin_id,
218 instance,
219 _factory: factory,
220 audio_inputs: audio_input_ios,
221 audio_outputs: audio_output_ios,
222 midi_input_ports,
223 midi_output_ports,
224 main_audio_inputs: requested_inputs,
225 main_audio_outputs: requested_outputs,
226 input_buses,
227 output_buses,
228 parameters,
229 scalar_values,
230 previous_values,
231 max_samples_per_block: buffer_size,
232 processing_started,
233 sample_rate,
234 gui_session,
235 })
236 }
237
238 pub fn path(&self) -> &str {
239 &self.path
240 }
241
242 pub fn name(&self) -> &str {
243 &self.name
244 }
245
246 pub fn plugin_id(&self) -> &str {
247 &self.plugin_id
248 }
249
250 pub fn audio_inputs(&self) -> &[Arc<AudioIO>] {
251 &self.audio_inputs
252 }
253
254 pub fn audio_outputs(&self) -> &[Arc<AudioIO>] {
255 &self.audio_outputs
256 }
257
258 pub fn main_audio_input_count(&self) -> usize {
259 self.main_audio_inputs
260 }
261
262 pub fn main_audio_output_count(&self) -> usize {
263 self.main_audio_outputs
264 }
265
266 pub fn midi_input_count(&self) -> usize {
267 self.midi_input_ports
268 }
269
270 pub fn midi_output_count(&self) -> usize {
271 self.midi_output_ports
272 }
273
274 pub fn setup_audio_ports(&self) {
275 for port in &self.audio_inputs {
276 port.setup();
277 }
278 for port in &self.audio_outputs {
279 port.setup();
280 }
281 }
282
283 pub fn process_with_audio_io(&self, frames: usize) {
284 for input in &self.audio_inputs {
286 input.process();
287 }
288
289 let processor = match &self.instance.audio_processor {
291 Some(proc) => proc,
292 None => {
293 self.process_silence();
294 return;
295 }
296 };
297
298 if self.process_vst3(processor, frames, &[]).is_err() {
300 self.process_silence();
301 }
302 }
303
304 #[allow(clippy::unnecessary_cast)]
306 pub fn process_with_midi(&self, frames: usize, input_events: &[MidiEvent]) -> Vec<MidiEvent> {
307 for input in &self.audio_inputs {
309 input.process();
310 }
311
312 let processor = match &self.instance.audio_processor {
314 Some(proc) => proc,
315 None => {
316 self.process_silence();
317 return Vec::new();
318 }
319 };
320
321 match self.process_vst3(processor, frames, input_events) {
323 Ok(output_buffer) => {
324 output_buffer.to_midi_events()
326 }
327 Err(_) => {
328 self.process_silence();
329 Vec::new()
330 }
331 }
332 }
333
334 fn process_vst3(
335 &self,
336 processor: &vst3::ComPtr<vst3::Steinberg::Vst::IAudioProcessor>,
337 frames: usize,
338 input_events: &[MidiEvent],
339 ) -> Result<EventBuffer, String> {
340 use vst3::Steinberg::Vst::IAudioProcessorTrait;
341 use vst3::Steinberg::Vst::*;
342
343 let input_guards: Vec<_> = self
345 .audio_inputs
346 .iter()
347 .map(|io| io.buffer.lock())
348 .collect();
349 let output_guards: Vec<_> = self
350 .audio_outputs
351 .iter()
352 .map(|io| io.buffer.lock())
353 .collect();
354
355 let mut input_channel_ptrs: Vec<*mut f32> = input_guards
356 .iter()
357 .map(|buf| buf.as_ptr() as *mut f32)
358 .collect();
359 let mut output_channel_ptrs: Vec<*mut f32> = output_guards
360 .iter()
361 .map(|buf| buf.as_ptr() as *mut f32)
362 .collect();
363
364 let max_input_frames = input_guards
365 .iter()
366 .map(|buf| buf.len())
367 .min()
368 .unwrap_or(frames);
369 let max_output_frames = output_guards
370 .iter()
371 .map(|buf| buf.len())
372 .min()
373 .unwrap_or(frames);
374 let num_frames = frames.min(max_input_frames).min(max_output_frames);
375 if num_frames == 0 {
376 return Ok(EventBuffer::new());
377 }
378
379 let mut input_buses = Vec::new();
380 if !self.input_buses.is_empty() && !input_channel_ptrs.is_empty() {
381 input_buses.push(AudioBusBuffers {
382 numChannels: input_channel_ptrs.len() as i32,
383 silenceFlags: 0,
384 __field0: AudioBusBuffers__type0 {
385 channelBuffers32: input_channel_ptrs.as_mut_ptr(),
386 },
387 });
388 }
389
390 let mut output_buses = Vec::new();
391 if !self.output_buses.is_empty() && !output_channel_ptrs.is_empty() {
392 output_buses.push(AudioBusBuffers {
393 numChannels: output_channel_ptrs.len() as i32,
394 silenceFlags: 0,
395 __field0: AudioBusBuffers__type0 {
396 channelBuffers32: output_channel_ptrs.as_mut_ptr(),
397 },
398 });
399 }
400
401 let mut process_context: ProcessContext = unsafe { std::mem::zeroed() };
403 process_context.sampleRate = self.sample_rate;
404 process_context.tempo = 120.0;
405 process_context.timeSigNumerator = 4;
406 process_context.timeSigDenominator = 4;
407 #[allow(clippy::unnecessary_cast)]
408 {
409 process_context.state = (ProcessContext_::StatesAndFlags_::kPlaying
410 | ProcessContext_::StatesAndFlags_::kTempoValid
411 | ProcessContext_::StatesAndFlags_::kTimeSigValid
412 | ProcessContext_::StatesAndFlags_::kContTimeValid
413 | ProcessContext_::StatesAndFlags_::kSystemTimeValid)
414 as u32;
415 }
416 let input_event_list = if self.midi_input_ports > 0 {
417 Some(ComWrapper::new(EventBuffer::from_midi_events(
418 input_events,
419 0,
420 )))
421 } else {
422 None
423 };
424 let midi_mapping = self
425 .instance
426 .edit_controller
427 .as_ref()
428 .and_then(|controller| controller.cast::<IMidiMapping>());
429 let input_parameter_changes = midi_mapping.as_ref().and_then(|mapping| {
430 ParameterChanges::from_midi_events(input_events, mapping, 0).map(ComWrapper::new)
431 });
432 let output_event_list = if self.midi_output_ports > 0 {
433 Some(ComWrapper::new(EventBuffer::new()))
434 } else {
435 None
436 };
437 let mut process_data = ProcessData {
438 processMode: kRealtime as i32,
439 symbolicSampleSize: kSample32 as i32,
440 numSamples: num_frames as i32,
441 numInputs: input_buses.len() as i32,
442 numOutputs: output_buses.len() as i32,
443 inputs: if input_buses.is_empty() {
444 std::ptr::null_mut()
445 } else {
446 input_buses.as_mut_ptr()
447 },
448 outputs: if output_buses.is_empty() {
449 std::ptr::null_mut()
450 } else {
451 output_buses.as_mut_ptr()
452 },
453 inputParameterChanges: input_parameter_changes
454 .as_ref()
455 .map(ParameterChanges::changes_ptr)
456 .unwrap_or(std::ptr::null_mut()),
457 outputParameterChanges: std::ptr::null_mut(),
458 inputEvents: input_event_list
459 .as_ref()
460 .map(EventBuffer::event_list_ptr)
461 .unwrap_or(std::ptr::null_mut()),
462 outputEvents: output_event_list
463 .as_ref()
464 .map(EventBuffer::event_list_ptr)
465 .unwrap_or(std::ptr::null_mut()),
466 processContext: &mut process_context,
467 };
468
469 let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| unsafe {
471 processor.process(&mut process_data)
472 }));
473
474 match result {
475 Ok(vst3::Steinberg::kResultOk) => {}
476 Ok(err_code) => {
477 return Err(format!("VST3 process failed with result: {}", err_code));
478 }
479 Err(_) => {
480 return Err("VST3 process panicked".to_string());
481 }
482 }
483
484 for output in &self.audio_outputs {
486 *output.finished.lock() = true;
487 }
488
489 Ok(output_event_list
490 .as_ref()
491 .map(|events| EventBuffer::from_midi_events(&events.to_midi_events(), 0))
492 .unwrap_or_default())
493 }
494
495 fn process_silence(&self) {
496 for output in &self.audio_outputs {
497 let out_buf = output.buffer.lock();
498 out_buf.fill(0.0);
499 *output.finished.lock() = true;
500 }
501 }
502
503 pub fn parameters(&self) -> &[ParameterInfo] {
504 &self.parameters
505 }
506
507 pub fn get_parameter_value(&self, param_id: u32) -> Option<f32> {
508 let idx = self.parameters.iter().position(|p| p.id == param_id)?;
509 Some(self.scalar_values.lock().unwrap()[idx])
510 }
511
512 pub fn set_parameter_value(&self, param_id: u32, normalized_value: f32) -> Result<(), String> {
513 let idx = self
514 .parameters
515 .iter()
516 .position(|p| p.id == param_id)
517 .ok_or("Parameter not found")?;
518
519 self.scalar_values.lock().unwrap()[idx] = normalized_value;
520
521 if let Some(controller) = &self.instance.edit_controller {
523 use vst3::Steinberg::Vst::IEditControllerTrait;
524 unsafe {
525 controller.setParamNormalized(param_id, normalized_value as f64);
526 }
527 }
528
529 Ok(())
530 }
531
532 pub fn snapshot_state(&self) -> Result<Vst3PluginState, String> {
534 use vst3::Steinberg::Vst::{IComponentTrait, IEditControllerTrait};
535
536 let instance = &self.instance;
537
538 let comp_stream = vst3::ComWrapper::new(MemoryStream::new());
540 unsafe {
541 let result = instance
542 .component
543 .getState(ibstream_ptr(&comp_stream) as *mut _);
544 if result != vst3::Steinberg::kResultOk {
545 return Err("Failed to get component state".to_string());
546 }
547 }
548
549 let ctrl_stream = vst3::ComWrapper::new(MemoryStream::new());
551 if let Some(controller) = &instance.edit_controller {
552 unsafe {
553 controller.getState(ibstream_ptr(&ctrl_stream) as *mut _);
554 }
555 }
556
557 Ok(Vst3PluginState {
558 plugin_id: self.plugin_id.clone(),
559 component_state: comp_stream.bytes(),
560 controller_state: ctrl_stream.bytes(),
561 })
562 }
563
564 pub fn restore_state(&self, state: &Vst3PluginState) -> Result<(), String> {
566 use vst3::Steinberg::Vst::{IComponentTrait, IEditControllerTrait};
567
568 if state.plugin_id != self.plugin_id {
569 return Err(format!(
570 "Plugin ID mismatch: expected '{}', got '{}'",
571 self.plugin_id, state.plugin_id
572 ));
573 }
574
575 let instance = &self.instance;
576
577 if !state.component_state.is_empty() {
579 let comp_stream =
580 vst3::ComWrapper::new(MemoryStream::from_bytes(&state.component_state));
581 unsafe {
582 let result = instance
583 .component
584 .setState(ibstream_ptr(&comp_stream) as *mut _);
585 if result != vst3::Steinberg::kResultOk {
586 return Err("Failed to set component state".to_string());
587 }
588 }
589 }
590
591 if !state.controller_state.is_empty()
593 && let Some(controller) = &instance.edit_controller
594 {
595 let ctrl_stream =
596 vst3::ComWrapper::new(MemoryStream::from_bytes(&state.controller_state));
597 unsafe {
598 controller.setState(ibstream_ptr(&ctrl_stream) as *mut _);
599 }
600
601 for (idx, param) in self.parameters.iter().enumerate() {
603 let value = unsafe { controller.getParamNormalized(param.id) };
604 self.scalar_values.lock().unwrap()[idx] = value as f32;
605 self.previous_values.lock().unwrap()[idx] = value as f32;
606 }
607 }
608
609 Ok(())
610 }
611
612 pub fn gui_info(&self) -> Result<Vst3GuiInfo, String> {
617 let controller = self
618 .instance
619 .edit_controller
620 .as_ref()
621 .ok_or("No edit controller available")?;
622 let view = unsafe { controller.createView(ViewType::kEditor) };
623 if view.is_null() {
624 return Ok(Vst3GuiInfo {
625 has_gui: false,
626 size: None,
627 });
628 }
629 unsafe {
631 let _ = ComPtr::<IPlugView>::from_raw(view);
632 }
633 Ok(Vst3GuiInfo {
634 has_gui: true,
635 size: None,
636 })
637 }
638
639 pub fn gui_create(&self, platform_type: &str) -> Result<(), String> {
640 let mut session = self.gui_session.lock().unwrap();
641 if session.view.is_some() {
642 return Ok(());
643 }
644 let controller = self
645 .instance
646 .edit_controller
647 .as_ref()
648 .ok_or("No edit controller available")?;
649 let view = unsafe { controller.createView(ViewType::kEditor) };
650 if view.is_null() {
651 return Err("Plugin does not provide an editor view".to_string());
652 }
653 let view =
654 unsafe { ComPtr::<IPlugView>::from_raw(view) }.ok_or("Failed to wrap IPlugView")?;
655
656 let platform_cstr =
657 CString::new(platform_type).map_err(|e| format!("Invalid platform type: {e}"))?;
658 let supported =
659 unsafe { view.isPlatformTypeSupported(platform_cstr.as_ptr() as FIDString) };
660 if supported != kResultOk {
661 return Err(format!("Platform type '{}' not supported", platform_type));
662 }
663
664 session.view = Some(view);
665 session.platform_type = Some(platform_type.to_string());
666 Ok(())
667 }
668
669 pub fn gui_get_size(&self) -> Result<(i32, i32), String> {
670 let session = self.gui_session.lock().unwrap();
671 let view = session.view.as_ref().ok_or("No GUI view created")?;
672 let mut rect = ViewRect {
673 left: 0,
674 top: 0,
675 right: 0,
676 bottom: 0,
677 };
678 let result = unsafe { view.getSize(&mut rect) };
679 if result != kResultOk {
680 return Err("Failed to get GUI size".to_string());
681 }
682 Ok((rect.right - rect.left, rect.bottom - rect.top))
683 }
684
685 pub fn gui_set_parent(&self, window: usize, platform_type: &str) -> Result<(), String> {
686 let mut session = self.gui_session.lock().unwrap();
687 let view = session.view.as_ref().ok_or("No GUI view created")?;
688
689 let plug_frame = ComWrapper::new(HostPlugFrame::new());
690 if let Some(frame_ptr) = plug_frame.to_com_ptr::<IPlugFrame>() {
691 unsafe {
692 let _ = view.setFrame(frame_ptr.into_raw());
693 }
694 }
695
696 let platform_cstr =
697 CString::new(platform_type).map_err(|e| format!("Invalid platform type: {e}"))?;
698 let result =
699 unsafe { view.attached(window as *mut c_void, platform_cstr.as_ptr() as FIDString) };
700 if result != kResultOk {
701 return Err(format!("Failed to attach GUI view: {:#x}", result));
702 }
703
704 session.plug_frame = Some(plug_frame);
705 Ok(())
706 }
707
708 pub fn gui_on_size(&self, width: i32, height: i32) -> Result<(), String> {
709 let session = self.gui_session.lock().unwrap();
710 let view = session.view.as_ref().ok_or("No GUI view created")?;
711 let mut rect = ViewRect {
712 left: 0,
713 top: 0,
714 right: width,
715 bottom: height,
716 };
717 unsafe {
718 let _ = view.onSize(&mut rect);
719 }
720 Ok(())
721 }
722
723 pub fn gui_show(&self) -> Result<(), String> {
724 let session = self.gui_session.lock().unwrap();
725 let view = session.view.as_ref().ok_or("No GUI view created")?;
726 unsafe {
727 let _ = view.onFocus(1);
728 }
729 Ok(())
730 }
731
732 pub fn gui_hide(&self) {
733 if let Ok(session) = self.gui_session.lock()
734 && let Some(view) = session.view.as_ref()
735 {
736 unsafe {
737 let _ = view.onFocus(0);
738 }
739 }
740 }
741
742 pub fn gui_destroy(&self) {
743 if let Ok(mut session) = self.gui_session.lock() {
744 if let Some(view) = session.view.take() {
745 unsafe {
746 let _ = view.setFrame(std::ptr::null_mut());
747 let _ = view.removed();
748 }
749 }
750 session.plug_frame.take();
751 session.platform_type.take();
752 }
753 }
754
755 pub fn ui_begin_session(&self) {
756 if let Ok(mut session) = self.gui_session.lock() {
757 session.ui_should_close = false;
758 }
759 }
760
761 pub fn ui_end_session(&self) {
762 if let Ok(mut session) = self.gui_session.lock() {
763 session.ui_should_close = false;
764 }
765 }
766
767 pub fn ui_should_close(&self) -> bool {
768 if let Ok(session) = self.gui_session.lock() {
769 return session.ui_should_close;
770 }
771 false
772 }
773
774 pub fn ui_take_param_updates(&self) -> Vec<(u32, f64)> {
775 let mut changes = Vec::new();
776 if let Ok(mut param_changes) = self.instance.parameter_changes.lock() {
777 std::mem::swap(&mut changes, &mut *param_changes);
778 }
779 changes
780 }
781
782 pub fn gui_check_resize(&self) -> Option<(i32, i32)> {
783 if let Ok(session) = self.gui_session.lock()
784 && let Some(ref frame) = session.plug_frame
785 && frame.resize_requested.swap(false, Ordering::Relaxed)
786 && let Ok(size) = frame.requested_size.lock()
787 {
788 return *size;
789 }
790 None
791 }
792
793 pub fn gui_on_main_thread(&self) {
794 super::interfaces::pump_host_run_loop();
795 }
796}
797
798impl Drop for Vst3Processor {
799 fn drop(&mut self) {
800 if self.processing_started {
801 self.instance.stop_processing();
802 }
803 let _ = self.instance.set_active(false);
804 let _ = self.instance.terminate();
805 }
806}
807
808pub fn list_plugins() -> Vec<super::host::Vst3PluginInfo> {
810 super::host::list_plugins()
811}