vst3_host/simple.rs
1//! Simplified API for common VST3 hosting tasks.
2//!
3//! This module provides convenience functions that make it easy to get started
4//! with VST3 plugin hosting without needing to understand all the configuration
5//! options and complex APIs.
6//!
7//! ## Quick Examples
8//!
9//! ### Load and play a plugin
10//! ```no_run
11//! use vst3_host::simple;
12//! use vst3_host::midi::MidiChannel;
13//!
14//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
15//! // Load a plugin with sensible defaults
16//! let mut plugin = simple::load_plugin("/path/to/synth.vst3")?;
17//!
18//! // Start processing audio
19//! plugin.start_processing()?;
20//!
21//! // Play a note
22//! plugin.send_midi_note(60, 127, MidiChannel::Ch1)?; // Middle C
23//! # Ok(())
24//! # }
25//! ```
26//!
27//! ### Discover plugins easily
28//! ```no_run
29//! use vst3_host::simple;
30//!
31//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
32//! // Find all plugins on the system
33//! let plugins = simple::discover_plugins()?;
34//!
35//! for plugin in plugins {
36//! println!("Found: {} by {}", plugin.name, plugin.vendor);
37//! }
38//! # Ok(())
39//! # }
40//! ```
41
42use crate::{
43 audio::AudioBuffers,
44 error::{Error, Result},
45 host::Vst3Host,
46 midi::MidiEvent,
47 plugin::{Plugin, PluginInfo},
48};
49use std::path::Path;
50
51/// Load a VST3 plugin with sensible defaults.
52///
53/// This function creates a host with default audio settings and loads the
54/// specified plugin. It's the quickest way to get started with plugin hosting.
55///
56/// # Default Settings
57/// - Sample rate: 44100 Hz
58/// - Block size: 512 samples
59/// - Input channels: 2 (stereo)
60/// - Output channels: 2 (stereo)
61/// - Process isolation: disabled (in-process). Use [`load_plugin_isolated`] to opt in.
62///
63/// # Arguments
64/// * `path` - Path to the VST3 plugin (.vst3 file or directory)
65///
66/// # Returns
67/// A loaded and configured plugin ready for audio processing.
68///
69/// # Examples
70/// ```no_run
71/// use vst3_host::simple;
72/// use vst3_host::midi::MidiChannel;
73///
74/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
75/// let mut plugin = simple::load_plugin("/Applications/Dexed.vst3")?;
76/// plugin.start_processing()?;
77/// plugin.send_midi_note(60, 100, MidiChannel::Ch1)?;
78/// # Ok(())
79/// # }
80/// ```
81pub fn load_plugin<P: AsRef<Path>>(path: P) -> Result<Plugin> {
82 let mut host = Vst3Host::builder()
83 .sample_rate(44100.0)
84 .block_size(512)
85 .input_channels(2)
86 .output_channels(2)
87 .build()?;
88
89 host.load_plugin(path)
90}
91
92/// Load a plugin with custom audio settings.
93///
94/// This provides a middle ground between the fully automatic `load_plugin()`
95/// and the full control of the host builder pattern.
96///
97/// # Arguments
98/// * `path` - Path to the VST3 plugin
99/// * `sample_rate` - Audio sample rate in Hz (typically 44100 or 48000)
100/// * `block_size` - Audio buffer size (typically 512 or 1024)
101///
102/// # Examples
103/// ```no_run
104/// use vst3_host::simple;
105///
106/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
107/// // Load with professional audio settings
108/// let mut plugin = simple::load_plugin_with_settings(
109/// "/path/to/plugin.vst3",
110/// 48000.0, // 48kHz sample rate
111/// 256 // Small buffer for low latency
112/// )?;
113/// # Ok(())
114/// # }
115/// ```
116pub fn load_plugin_with_settings<P: AsRef<Path>>(
117 path: P,
118 sample_rate: f64,
119 block_size: usize,
120) -> Result<Plugin> {
121 let mut host = Vst3Host::builder()
122 .sample_rate(sample_rate)
123 .block_size(block_size)
124 .input_channels(2)
125 .output_channels(2)
126 .build()?;
127
128 host.load_plugin(path)
129}
130
131/// Load a plugin with crash protection enabled.
132///
133/// This loads the plugin in a separate process, which prevents plugin crashes
134/// from affecting your application. Use this for untested or problematic plugins.
135///
136/// # Arguments
137/// * `path` - Path to the VST3 plugin
138///
139/// # Examples
140/// ```no_run
141/// use vst3_host::simple;
142///
143/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
144/// // Load a potentially unstable plugin safely
145/// let mut plugin = simple::load_plugin_isolated("/path/to/sketchy_plugin.vst3")?;
146/// # Ok(())
147/// # }
148/// ```
149pub fn load_plugin_isolated<P: AsRef<Path>>(path: P) -> Result<Plugin> {
150 let mut host = Vst3Host::builder()
151 .sample_rate(44100.0)
152 .block_size(512)
153 .input_channels(2)
154 .output_channels(2)
155 .with_process_isolation(true) // Force process isolation
156 .build()?;
157
158 host.load_plugin(path)
159}
160
161/// Load a plugin and immediately start playing it through the default audio device.
162///
163/// The quickest way to actually hear a synth: load, then `play`. The returned
164/// [`AudioHandle`](crate::AudioHandle) keeps audio running until dropped, and lets
165/// you control the plugin while it plays.
166///
167/// # Examples
168/// ```no_run
169/// use vst3_host::{simple, midi::MidiChannel};
170///
171/// # fn main() -> vst3_host::Result<()> {
172/// let plugin = simple::load_plugin("/path/to/synth.vst3")?;
173/// let audio = simple::play(plugin)?;
174/// audio.lock().send_midi_note(60, 100, MidiChannel::Ch1)?; // middle C
175/// std::thread::sleep(std::time::Duration::from_secs(2));
176/// # Ok(())
177/// # }
178/// ```
179#[cfg(feature = "cpal-backend")]
180pub fn play(plugin: Plugin) -> Result<crate::AudioHandle> {
181 let backend = crate::backends::CpalBackend::new()?;
182 let config = crate::audio::AudioConfig {
183 output_channels: 2,
184 input_channels: 0,
185 ..Default::default()
186 };
187 crate::playback::play_with_backend(&backend, plugin, config)
188}
189
190/// Host an effect plugin on live audio input: capture from the default input device, process
191/// through the plugin, and play the result on the default output device.
192///
193/// The instrument counterpart is [`play`]. Returns an [`AudioHandle`](crate::AudioHandle)
194/// that keeps the streams alive and lets you control the plugin; dropping it stops audio.
195#[cfg(feature = "cpal-backend")]
196pub fn play_with_input(plugin: Plugin) -> Result<crate::AudioHandle> {
197 let backend = crate::backends::CpalBackend::new()?;
198 let config = crate::audio::AudioConfig {
199 input_channels: 2,
200 output_channels: 2,
201 ..Default::default()
202 };
203 crate::playback::play_with_input_backend(&backend, plugin, config)
204}
205
206/// Discover all VST3 plugins in the standard system locations.
207///
208/// Scans the platform's standard VST3 directories and returns metadata for each
209/// plugin found. For progress reporting during a long scan, use
210/// [`Vst3Host::discover_plugins_with_callback`](crate::Vst3Host::discover_plugins_with_callback).
211///
212/// # Examples
213/// ```no_run
214/// use vst3_host::simple;
215///
216/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
217/// for info in simple::discover_plugins()? {
218/// println!("Found: {} by {}", info.name, info.vendor);
219/// }
220/// # Ok(())
221/// # }
222/// ```
223pub fn discover_plugins() -> Result<Vec<PluginInfo>> {
224 let mut host = Vst3Host::builder()
225 .scan_default_paths() // Enable scanning system directories
226 .build()?;
227
228 host.discover_plugins()
229}
230
231/// Discover plugins in a specific directory.
232///
233/// # Arguments
234/// * `path` - Directory to scan for VST3 plugins
235///
236/// # Examples
237/// ```no_run
238/// use vst3_host::simple;
239///
240/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
241/// let plugins = simple::discover_plugins_in("/my/custom/vst3/folder")?;
242/// println!("{} plugins found", plugins.len());
243/// # Ok(())
244/// # }
245/// ```
246pub fn discover_plugins_in<P: AsRef<Path>>(path: P) -> Result<Vec<PluginInfo>> {
247 let mut host = Vst3Host::builder().add_scan_path(path).build()?;
248
249 host.discover_plugins()
250}
251
252/// Get information about a specific plugin without loading it.
253///
254/// This is useful for checking plugin compatibility or displaying plugin
255/// information before deciding whether to load it.
256///
257/// # Arguments
258/// * `path` - Path to the VST3 plugin
259///
260/// # Returns
261/// Plugin information including name, vendor, version, and capabilities.
262///
263/// # Examples
264/// ```no_run
265/// use vst3_host::simple;
266///
267/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
268/// let info = simple::get_plugin_info("/path/to/plugin.vst3")?;
269/// println!("Plugin: {} v{} by {}", info.name, info.version, info.vendor);
270/// println!("Has GUI: {}", info.has_gui);
271/// println!("Audio I/O: {} in, {} out", info.audio_inputs, info.audio_outputs);
272/// # Ok(())
273/// # }
274/// ```
275pub fn get_plugin_info<P: AsRef<Path>>(path: P) -> Result<PluginInfo> {
276 let path = path.as_ref();
277
278 if !path.exists() {
279 return Err(Error::PluginNotFound(path.display().to_string()));
280 }
281
282 // Create a minimal host just for info gathering
283 let mut host = Vst3Host::builder().build()?;
284
285 // Load plugin to get info, then immediately drop it
286 let plugin = host.load_plugin(path)?;
287 Ok(plugin.info().clone())
288}
289
290/// Check if a plugin path is valid and loadable.
291///
292/// This performs basic validation without actually loading the plugin.
293/// Useful for filtering plugin lists or validating user input.
294///
295/// # Arguments
296/// * `path` - Path to check
297///
298/// # Returns
299/// `true` if the path appears to be a valid VST3 plugin, `false` otherwise.
300///
301/// # Examples
302/// ```no_run
303/// use vst3_host::simple;
304///
305/// if simple::is_valid_plugin("/path/to/plugin.vst3") {
306/// println!("Plugin path looks valid");
307/// } else {
308/// println!("Not a valid VST3 plugin path");
309/// }
310/// ```
311pub fn is_valid_plugin<P: AsRef<Path>>(path: P) -> bool {
312 let path = path.as_ref();
313
314 // Basic checks
315 if !path.exists() {
316 return false;
317 }
318
319 // Check for .vst3 extension
320 if let Some(extension) = path.extension() {
321 if extension.to_string_lossy().to_lowercase() == "vst3" {
322 return true;
323 }
324 }
325
326 false
327}
328
329/// Render a plugin offline to a 32-bit float WAV file.
330///
331/// Drives `process_audio` faster-than-realtime for `duration_secs` (at the plugin's
332/// configured sample rate and block size), starting/stopping processing for you, and writes
333/// the output to `path`. Any `midi` events are sent at the start — pass a held `NoteOn` to
334/// bounce an instrument, or an empty slice for an effect (feed input via the lower-level
335/// `process_audio` loop if you need to process a signal). No audio hardware is used.
336///
337/// ```no_run
338/// use vst3_host::{simple, midi::{MidiEvent, MidiChannel}};
339/// # fn main() -> vst3_host::Result<()> {
340/// let mut plugin = simple::load_plugin("/path/synth.vst3")?;
341/// let note = MidiEvent::NoteOn { channel: MidiChannel::Ch1, note: 60, velocity: 100 };
342/// simple::render_to_wav(&mut plugin, 2.0, &[note], "out.wav")?;
343/// # Ok(())
344/// # }
345/// ```
346pub fn render_to_wav<P: AsRef<Path>>(
347 plugin: &mut Plugin,
348 duration_secs: f64,
349 midi: &[MidiEvent],
350 path: P,
351) -> Result<()> {
352 if duration_secs < 0.0 {
353 return Err(Error::Other("duration must be non-negative".to_string()));
354 }
355 let sample_rate = plugin.sample_rate();
356 let block = plugin.block_size().max(1);
357 let out_channels = plugin.output_channel_count().max(1);
358 let total_frames = (duration_secs * sample_rate).round() as usize;
359
360 plugin.start_processing()?;
361 for &event in midi {
362 plugin.send_midi_event(event)?;
363 }
364
365 let mut channels: Vec<Vec<f32>> = vec![Vec::with_capacity(total_frames); out_channels];
366 let mut rendered = 0;
367 while rendered < total_frames {
368 let frames = block.min(total_frames - rendered);
369 let mut buffers = AudioBuffers::new(0, out_channels, frames, sample_rate);
370 plugin.process_audio(&mut buffers)?;
371 for (ch, dst) in channels.iter_mut().enumerate() {
372 if let Some(src) = buffers.outputs.get(ch) {
373 dst.extend_from_slice(&src[..frames.min(src.len())]);
374 }
375 }
376 rendered += frames;
377 }
378 plugin.stop_processing()?;
379
380 crate::audio::write_wav(path, &channels, sample_rate as u32)
381}
382
383/// Offline-render a plugin to a WAV while feeding its audio input from an [`InputSource`]
384/// (a generated test signal or a loaded file) — for auditioning/regression-testing effects
385/// with a known input. Like [`render_to_wav`] but with `input_channels` filled each block.
386///
387/// [`InputSource`]: crate::audio::InputSource
388pub fn render_to_wav_with_input<P: AsRef<Path>>(
389 plugin: &mut Plugin,
390 duration_secs: f64,
391 midi: &[MidiEvent],
392 source: &mut dyn crate::audio::InputSource,
393 path: P,
394) -> Result<()> {
395 if duration_secs < 0.0 {
396 return Err(Error::Other("duration must be non-negative".to_string()));
397 }
398 let sample_rate = plugin.sample_rate();
399 let block = plugin.block_size().max(1);
400 let out_channels = plugin.output_channel_count().max(1);
401 let in_channels = plugin.info().audio_inputs.max(1) as usize;
402 let total_frames = (duration_secs * sample_rate).round() as usize;
403
404 plugin.start_processing()?;
405 for &event in midi {
406 plugin.send_midi_event(event)?;
407 }
408
409 let mut channels: Vec<Vec<f32>> = vec![Vec::with_capacity(total_frames); out_channels];
410 let mut rendered = 0;
411 while rendered < total_frames {
412 let frames = block.min(total_frames - rendered);
413 let mut buffers = AudioBuffers::new(in_channels, out_channels, frames, sample_rate);
414 source.fill(&mut buffers.inputs, frames, sample_rate);
415 plugin.process_audio(&mut buffers)?;
416 for (ch, dst) in channels.iter_mut().enumerate() {
417 if let Some(src) = buffers.outputs.get(ch) {
418 dst.extend_from_slice(&src[..frames.min(src.len())]);
419 }
420 }
421 rendered += frames;
422 }
423 plugin.stop_processing()?;
424
425 crate::audio::write_wav(path, &channels, sample_rate as u32)
426}
427
428#[cfg(test)]
429mod tests {
430 use super::*;
431
432 #[test]
433 fn test_is_valid_plugin() {
434 // Test non-existent path
435 assert!(!is_valid_plugin("/nonexistent/path.vst3"));
436
437 // Test wrong extension
438 assert!(!is_valid_plugin("plugin.dll"));
439 assert!(!is_valid_plugin("plugin.so"));
440
441 // Would need actual plugin files to test positive cases
442 }
443
444 #[test]
445 fn test_host_creation() {
446 // Test that we can create hosts with different configurations
447 let host1 = Vst3Host::builder()
448 .sample_rate(44100.0)
449 .block_size(512)
450 .build();
451 assert!(host1.is_ok());
452
453 let host2 = Vst3Host::builder()
454 .sample_rate(48000.0)
455 .block_size(256)
456 .with_process_isolation(true)
457 .build();
458 assert!(host2.is_ok());
459 }
460}