use crate::{
audio::AudioBuffers,
error::{Error, Result},
host::Vst3Host,
midi::MidiEvent,
plugin::{Plugin, PluginInfo},
};
use std::path::Path;
pub fn load_plugin<P: AsRef<Path>>(path: P) -> Result<Plugin> {
let mut host = Vst3Host::builder()
.sample_rate(44100.0)
.block_size(512)
.input_channels(2)
.output_channels(2)
.build()?;
host.load_plugin(path)
}
pub fn load_plugin_with_settings<P: AsRef<Path>>(
path: P,
sample_rate: f64,
block_size: usize,
) -> Result<Plugin> {
let mut host = Vst3Host::builder()
.sample_rate(sample_rate)
.block_size(block_size)
.input_channels(2)
.output_channels(2)
.build()?;
host.load_plugin(path)
}
pub fn load_plugin_isolated<P: AsRef<Path>>(path: P) -> Result<Plugin> {
let mut host = Vst3Host::builder()
.sample_rate(44100.0)
.block_size(512)
.input_channels(2)
.output_channels(2)
.with_process_isolation(true) .build()?;
host.load_plugin(path)
}
#[cfg(feature = "cpal-backend")]
pub fn play(plugin: Plugin) -> Result<crate::AudioHandle> {
let backend = crate::backends::CpalBackend::new()?;
let config = crate::audio::AudioConfig {
output_channels: 2,
input_channels: 0,
..Default::default()
};
crate::playback::play_with_backend(&backend, plugin, config)
}
#[cfg(feature = "cpal-backend")]
pub fn play_with_input(plugin: Plugin) -> Result<crate::AudioHandle> {
let backend = crate::backends::CpalBackend::new()?;
let config = crate::audio::AudioConfig {
input_channels: 2,
output_channels: 2,
..Default::default()
};
crate::playback::play_with_input_backend(&backend, plugin, config)
}
pub fn discover_plugins() -> Result<Vec<PluginInfo>> {
let mut host = Vst3Host::builder()
.scan_default_paths() .build()?;
host.discover_plugins()
}
pub fn discover_plugins_in<P: AsRef<Path>>(path: P) -> Result<Vec<PluginInfo>> {
let mut host = Vst3Host::builder().add_scan_path(path).build()?;
host.discover_plugins()
}
pub fn get_plugin_info<P: AsRef<Path>>(path: P) -> Result<PluginInfo> {
let path = path.as_ref();
if !path.exists() {
return Err(Error::PluginNotFound(path.display().to_string()));
}
let mut host = Vst3Host::builder().build()?;
let plugin = host.load_plugin(path)?;
Ok(plugin.info().clone())
}
pub fn is_valid_plugin<P: AsRef<Path>>(path: P) -> bool {
let path = path.as_ref();
if !path.exists() {
return false;
}
if let Some(extension) = path.extension() {
if extension.to_string_lossy().to_lowercase() == "vst3" {
return true;
}
}
false
}
const MAX_RENDER_SAMPLES: usize = (u32::MAX / 4) as usize;
fn render_frame_count(duration_secs: f64, sample_rate: f64, out_channels: usize) -> Result<usize> {
if !duration_secs.is_finite() || duration_secs < 0.0 {
return Err(Error::InvalidParameter(format!(
"duration must be finite and non-negative, got {duration_secs}"
)));
}
let frames = (duration_secs * sample_rate).round();
if !frames.is_finite() || frames < 0.0 {
return Err(Error::InvalidParameter(format!(
"duration {duration_secs}s at {sample_rate} Hz is not a renderable frame count"
)));
}
let max_frames = MAX_RENDER_SAMPLES / out_channels.max(1);
if frames > max_frames as f64 {
return Err(Error::InvalidParameter(format!(
"duration {duration_secs}s at {sample_rate} Hz is {frames} frames across \
{out_channels} channels, past the {max_frames}-frame render limit"
)));
}
Ok(frames as usize)
}
pub fn render_to_wav<P: AsRef<Path>>(
plugin: &mut Plugin,
duration_secs: f64,
midi: &[MidiEvent],
path: P,
) -> Result<()> {
let sample_rate = plugin.sample_rate();
let block = plugin.block_size().max(1);
let out_channels = plugin.output_channel_count().max(1);
let total_frames = render_frame_count(duration_secs, sample_rate, out_channels)?;
plugin.start_processing()?;
for &event in midi {
plugin.send_midi_event(event)?;
}
let mut channels: Vec<Vec<f32>> = vec![Vec::with_capacity(total_frames); out_channels];
let mut rendered = 0;
while rendered < total_frames {
let frames = block.min(total_frames - rendered);
let mut buffers = AudioBuffers::new(0, out_channels, frames, sample_rate);
plugin.process_audio(&mut buffers)?;
for (ch, dst) in channels.iter_mut().enumerate() {
if let Some(src) = buffers.outputs.get(ch) {
dst.extend_from_slice(&src[..frames.min(src.len())]);
}
}
rendered += frames;
}
plugin.stop_processing()?;
crate::audio::write_wav(path, &channels, sample_rate as u32)
}
pub fn render_to_wav_with_input<P: AsRef<Path>>(
plugin: &mut Plugin,
duration_secs: f64,
midi: &[MidiEvent],
source: &mut dyn crate::audio::InputSource,
path: P,
) -> Result<()> {
let sample_rate = plugin.sample_rate();
let block = plugin.block_size().max(1);
let out_channels = plugin.output_channel_count().max(1);
let in_channels = plugin.info().audio_inputs.max(1) as usize;
let total_frames = render_frame_count(duration_secs, sample_rate, out_channels)?;
plugin.start_processing()?;
for &event in midi {
plugin.send_midi_event(event)?;
}
let mut channels: Vec<Vec<f32>> = vec![Vec::with_capacity(total_frames); out_channels];
let mut rendered = 0;
while rendered < total_frames {
let frames = block.min(total_frames - rendered);
let mut buffers = AudioBuffers::new(in_channels, out_channels, frames, sample_rate);
source.fill(&mut buffers.inputs, frames, sample_rate);
plugin.process_audio(&mut buffers)?;
for (ch, dst) in channels.iter_mut().enumerate() {
if let Some(src) = buffers.outputs.get(ch) {
dst.extend_from_slice(&src[..frames.min(src.len())]);
}
}
rendered += frames;
}
plugin.stop_processing()?;
crate::audio::write_wav(path, &channels, sample_rate as u32)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_is_valid_plugin() {
assert!(!is_valid_plugin("/nonexistent/path.vst3"));
assert!(!is_valid_plugin("plugin.dll"));
assert!(!is_valid_plugin("plugin.so"));
}
#[test]
fn render_frame_count_rejects_unrenderable_durations() {
for bad in [
f64::INFINITY,
f64::NEG_INFINITY,
f64::NAN,
-1.0,
1.0e12,
f64::MAX,
] {
assert!(
render_frame_count(bad, 44_100.0, 2).is_err(),
"duration {bad} should be rejected"
);
}
}
#[test]
fn render_frame_count_accepts_ordinary_durations() {
assert_eq!(render_frame_count(0.0, 44_100.0, 2).unwrap(), 0);
assert_eq!(render_frame_count(2.0, 44_100.0, 2).unwrap(), 88_200);
assert_eq!(render_frame_count(0.5, 48_000.0, 6).unwrap(), 24_000);
let max_frames = MAX_RENDER_SAMPLES / 2;
let secs = max_frames as f64 / 44_100.0;
assert!(render_frame_count(secs, 44_100.0, 2).is_ok());
}
#[test]
fn test_host_creation() {
let host1 = Vst3Host::builder()
.sample_rate(44100.0)
.block_size(512)
.build();
assert!(host1.is_ok());
let host2 = Vst3Host::builder()
.sample_rate(48000.0)
.block_size(256)
.with_process_isolation(true)
.build();
assert!(host2.is_ok());
}
}