├── .appveyor.yml ├── .gitignore ├── .travis.yml ├── Cargo.toml ├── LICENSE ├── README.md ├── appveyor_rust_install.ps1 ├── examples ├── dimension_expander │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── simple_host.rs └── sine_synth │ ├── Cargo.toml │ └── src │ └── lib.rs ├── osx_vst_bundler.sh └── src ├── api.rs ├── buffer.rs ├── channels.rs ├── editor.rs ├── event.rs ├── host.rs ├── interfaces.rs ├── lib.rs └── plugin.rs /.appveyor.yml: -------------------------------------------------------------------------------- 1 | # Appveyor configuration template for Rust 2 | # https://github.com/starkat99/appveyor-rust 3 | 4 | ## Operating System (VM environment) ## 5 | 6 | # Rust needs at least Visual Studio 2013 Appveyor OS for MSVC targets. 7 | os: Visual Studio 2015 8 | 9 | ## Build Matrix ## 10 | 11 | # This configuration will setup a build for each channel & target combination (12 windows 12 | # combinations in all). 13 | # 14 | # There are 3 channels: stable, beta, and nightly. 15 | # 16 | # Alternatively, the full version may be specified for the channel to build using that specific 17 | # version (e.g. channel: 1.5.0) 18 | # 19 | # The values for target are the set of windows Rust build targets. Each value is of the form 20 | # 21 | # ARCH-pc-windows-TOOLCHAIN 22 | # 23 | # Where ARCH is the target architecture, either x86_64 or i686, and TOOLCHAIN is the linker 24 | # toolchain to use, either msvc or gnu. See https://www.rust-lang.org/downloads.html#win-foot for 25 | # a description of the toolchain differences. 26 | # 27 | # Comment out channel/target combos you do not wish to build in CI. 28 | environment: 29 | matrix: 30 | 31 | ### MSVC Toolchains ### 32 | 33 | # Stable 64-bit MSVC 34 | - channel: stable 35 | target: x86_64-pc-windows-msvc 36 | # Stable 32-bit MSVC 37 | - channel: stable 38 | target: i686-pc-windows-msvc 39 | # Beta 64-bit MSVC 40 | - channel: beta 41 | target: x86_64-pc-windows-msvc 42 | # Beta 32-bit MSVC 43 | - channel: beta 44 | target: i686-pc-windows-msvc 45 | # Nightly 64-bit MSVC 46 | - channel: nightly 47 | target: x86_64-pc-windows-msvc 48 | # Nightly 32-bit MSVC 49 | - channel: nightly 50 | target: i686-pc-windows-msvc 51 | 52 | ### GNU Toolchains ### 53 | 54 | # Stable 64-bit GNU 55 | - channel: stable 56 | target: x86_64-pc-windows-gnu 57 | # Stable 32-bit GNU 58 | - channel: stable 59 | target: i686-pc-windows-gnu 60 | # Beta 64-bit GNU 61 | - channel: beta 62 | target: x86_64-pc-windows-gnu 63 | # Beta 32-bit GNU 64 | - channel: beta 65 | target: i686-pc-windows-gnu 66 | # Nightly 64-bit GNU 67 | - channel: nightly 68 | target: x86_64-pc-windows-gnu 69 | # Nightly 32-bit GNU 70 | - channel: nightly 71 | target: i686-pc-windows-gnu 72 | 73 | ### Allowed failures ### 74 | 75 | # See Appveyor documentation for specific details. In short, place any channel or targets you wish 76 | # to allow build failures on (usually nightly at least is a wise choice). This will prevent a build 77 | # or test failure in the matching channels/targets from failing the entire build. 78 | matrix: 79 | allow_failures: 80 | - channel: nightly 81 | - channel: beta 82 | 83 | # 32-bit MSVC isn't stablized yet, so you may optionally allow failures there (uncomment line): 84 | #- target: i686-pc-windows-msvc 85 | 86 | ## Install Script ## 87 | 88 | # This is the most important part of the Appveyor configuration. This installs the version of Rust 89 | # specified by the 'channel' and 'target' environment variables from the build matrix. By default, 90 | # Rust will be installed to C:\Rust for easy usage, but this path can be overridden by setting the 91 | # RUST_INSTALL_DIR environment variable. The URL to download rust distributions defaults to 92 | # https://static.rust-lang.org/dist/ but can overridden by setting the RUST_DOWNLOAD_URL environment 93 | # variable. 94 | # 95 | # For simple configurations, instead of using the build matrix, you can override the channel and 96 | # target environment variables with the -channel and -target script arguments. 97 | # 98 | # If no channel or target arguments or environment variables are specified, will default to stable 99 | # channel and x86_64-pc-windows-msvc target. 100 | # 101 | # The file appveyor_rust_install.ps1 must exist in the root directory of the repository. 102 | install: 103 | - ps: .\appveyor_rust_install.ps1 104 | 105 | # Alternative install command for simple configurations without build matrix (uncomment line and 106 | # comment above line): 107 | #- ps: .\appveyor_rust_install.ps1 -channel stable -target x86_64-pc-windows-msvc 108 | 109 | ## Build Script ## 110 | 111 | # 'cargo test' takes care of building for us, so disable Appveyor's build stage. This prevents 112 | # the "directory does not contain a project or solution file" error. 113 | build: false 114 | 115 | # Uses 'cargo test' to run tests. Alternatively, the project may call compiled programs directly or 116 | # perform other testing commands. Rust will automatically be placed in the PATH environment 117 | # variable. 118 | test_script: 119 | - cmd: cargo test --verbose 120 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled files 2 | *.o 3 | *.so 4 | *.rlib 5 | *.dll 6 | 7 | # Executables 8 | *.exe 9 | 10 | # Generated by Cargo 11 | /target/ 12 | /examples/*/target/ 13 | Cargo.lock 14 | 15 | # Vim 16 | [._]*.s[a-w][a-z] 17 | [._]s[a-w][a-z] 18 | *.un~ 19 | Session.vim 20 | .netrwhist 21 | *~ 22 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | after_success: | 3 | [ $TRAVIS_BRANCH = master ] && 4 | [ $TRAVIS_PULL_REQUEST = false ] && 5 | cargo doc && 6 | echo "" > target/doc/index.html && 7 | sudo pip install ghp-import && 8 | ghp-import -n target/doc && 9 | git push -fq https://${GH_TOKEN}@github.com/${TRAVIS_REPO_SLUG}.git gh-pages 10 | env: 11 | global: 12 | secure: ECN8bu5uqJIoQ8svymCF2Cbtw4AagUeAa2bfHJ4AkkwISwFl4IRs0twZA2BSx8p6rZbonWmQAOZzT/UeopQHqcJfOiO8yzmrVo/OJ5nET44s9GM5hVoMl1t1RwhVeWppw97u7A7cU+Vd3fYmh5FPcZ625Vkxe5nJ7PEmc43LIu4= 13 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "vst2" 3 | version = "0.0.1" 4 | authors = ["Marko Mijalkovic "] 5 | 6 | description = "VST 2.4 API implementation in rust. Create plugins or hosts." 7 | 8 | readme = "README.md" 9 | repository = "https://github.com/overdrivenpotato/rust-vst2" 10 | documentation = "http://overdrivenpotato.github.io/rust-vst2" 11 | 12 | license = "MIT" 13 | keywords = ["vst", "vst2", "plugin"] 14 | 15 | [dependencies] 16 | log = "0.3" 17 | num-traits = "0.1" 18 | libc = "0.2" 19 | bitflags = "0.8" 20 | libloading = "0.4" 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Marko Mijalkovic 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rust-vst2 2 | [![Travis Build][trav-img]][trav-url] 3 | [![Appveyor Build][appv-img]][appv-url] 4 | [![crates.io][crates-img]][crates-url] 5 | 6 | A library to help facilitate creating VST plugins in rust. 7 | 8 | This library is a work in progress and as such does not yet implement all 9 | opcodes. It is enough to create basic VST plugins without an editor interface. 10 | 11 | *Please note: This api may be subject to rapid changes and the current state of 12 | this library is not final.* 13 | 14 | ## Library Documentation 15 | * http://overdrivenpotato.github.io/rust-vst2 16 | 17 | ## TODO 18 | - Implement all opcodes 19 | - Proper editor support (possibly [conrod] + [sdl2]?) 20 | - Write more tests 21 | - Provide better examples 22 | 23 | ## Usage 24 | To create a plugin, simply create a type which implements `plugin::Plugin` and 25 | `std::default::Default`. Then call the macro `plugin_main!`, which will export 26 | the necessary functions and handle dealing with the rest of the API. 27 | 28 | ## Example Plugin 29 | A simple plugin that bears no functionality. The provided Cargo.toml has a 30 | crate-type directive which builds a dynamic library, usable by any VST host. 31 | 32 | `src/lib.rs` 33 | 34 | ```rust 35 | #[macro_use] 36 | extern crate vst2; 37 | 38 | use vst2::plugin::{Info, Plugin}; 39 | 40 | #[derive(Default)] 41 | struct BasicPlugin; 42 | 43 | impl Plugin for BasicPlugin { 44 | fn get_info(&self) -> Info { 45 | Info { 46 | name: "Basic Plugin".to_string(), 47 | unique_id: 1357, // Used by hosts to differentiate between plugins. 48 | 49 | ..Default::default() 50 | } 51 | } 52 | } 53 | 54 | plugin_main!(BasicPlugin); // Important! 55 | ``` 56 | 57 | `Cargo.toml` 58 | 59 | ```toml 60 | [package] 61 | name = "basic_vst" 62 | version = "0.0.1" 63 | authors = ["Author "] 64 | 65 | [dependencies] 66 | vst2 = "0.0.1" 67 | 68 | [lib] 69 | name = "basicvst" 70 | crate-type = ["dylib"] 71 | ``` 72 | 73 | [trav-img]: https://travis-ci.org/overdrivenpotato/rust-vst2.svg?branch=master 74 | [trav-url]: https://travis-ci.org/overdrivenpotato/rust-vst2 75 | [appv-img]: https://ci.appveyor.com/api/projects/status/4kg8efxas08b72bp?svg=true 76 | [appv-url]: https://ci.appveyor.com/project/overdrivenpotato/rust-vst2 77 | [crates-img]: https://img.shields.io/crates/v/vst2.svg 78 | [crates-url]: https://crates.io/crates/vst2 79 | [sdl2]: https://github.com/AngryLawyer/rust-sdl2 80 | [conrod]: https://github.com/PistonDevelopers/conrod 81 | 82 | 83 | #### Packaging on OS X 84 | 85 | On OS X VST plugins are packaged inside of loadable bundles. 86 | To package your VST as a loadable bundle you may use the `osx_vst_bundler.sh` script this library provides.  87 | 88 | Example:  89 | 90 | ``` 91 | ./osx_vst_bundler.sh Plugin target/release/plugin.dylib 92 | Creates a Plugin.vst bundle 93 | ``` 94 | -------------------------------------------------------------------------------- /appveyor_rust_install.ps1: -------------------------------------------------------------------------------- 1 | ##### Appveyor Rust Install Script ##### 2 | 3 | # https://github.com/starkat99/appveyor-rust 4 | 5 | # This is the most important part of the Appveyor configuration. This installs the version of Rust 6 | # specified by the "channel" and "target" environment variables from the build matrix. By default, 7 | # Rust will be installed to C:\Rust for easy usage, but this path can be overridden by setting the 8 | # RUST_INSTALL_DIR environment variable. The URL to download rust distributions defaults to 9 | # https://static.rust-lang.org/dist/ but can overridden by setting the RUST_DOWNLOAD_URL environment 10 | # variable. 11 | # 12 | # For simple configurations, instead of using the build matrix, you can override the channel and 13 | # target environment variables with the --channel and --target script arguments. 14 | # 15 | # If no channel or target arguments or environment variables are specified, will default to stable 16 | # channel and x86_64-pc-windows-msvc target. 17 | 18 | param([string]$channel=${env:channel}, [string]$target=${env:target}) 19 | 20 | # Initialize our parameters from arguments and environment variables, falling back to defaults 21 | if (!$channel) { 22 | $channel = "stable" 23 | } 24 | if (!$target) { 25 | $target = "x86_64-pc-windows-msvc" 26 | } 27 | 28 | $downloadUrl = "https://static.rust-lang.org/dist/" 29 | if ($env:RUST_DOWNLOAD_URL) { 30 | $downloadUrl = $env:RUST_DOWNLOAD_URL 31 | } 32 | 33 | $installDir = "C:\Rust" 34 | if ($env:RUST_INSTALL_DIR) { 35 | $installUrl = $env:RUST_INSTALL_DIR 36 | } 37 | 38 | if ($channel -eq "stable") { 39 | # Download manifest so we can find actual filename of installer to download. Needed for stable. 40 | echo "Downloading $channel channel manifest" 41 | $manifest = "${env:Temp}\channel-rust-${channel}" 42 | Start-FileDownload "${downloadUrl}channel-rust-${channel}" -FileName "$manifest" 43 | 44 | # Search the manifest lines for the correct filename based on target 45 | $match = Get-Content "$manifest" | Select-String -pattern "${target}.exe" -simplematch 46 | 47 | if (!$match -or !$match.line) { 48 | throw "Could not find $target in $channel channel manifest" 49 | } 50 | 51 | $installer = $match.line 52 | } else { 53 | # Otherwise download the file specified by channel directly. 54 | $installer = "rust-${channel}-${target}.exe" 55 | } 56 | 57 | # Download installer 58 | echo "Downloading ${downloadUrl}$installer" 59 | Start-FileDownload "${downloadUrl}$installer" -FileName "${env:Temp}\$installer" 60 | 61 | # Execute installer and wait for it to finish 62 | echo "Installing $installer to $installDir" 63 | &"${env:Temp}\$installer" /VERYSILENT /NORESTART /DIR="$installDir" | Write-Output 64 | 65 | # Add Rust to the path. 66 | $env:Path += ";${installDir}\bin;C:\MinGW\bin" 67 | 68 | echo "Installation of $channel Rust $target completed" 69 | 70 | # Test and display installed version information for rustc and cargo 71 | rustc -V 72 | cargo -V 73 | -------------------------------------------------------------------------------- /examples/dimension_expander/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "dimension_expander" 3 | version = "0.1.0" 4 | authors = ["Marko Mijalkovic "] 5 | 6 | [dependencies] 7 | vst2 = { git = "https://github.com/overdrivenpotato/rust-vst2" } 8 | time = "0.1" 9 | 10 | [lib] 11 | name = "dimension_expander" 12 | crate-type = ["dylib"] 13 | -------------------------------------------------------------------------------- /examples/dimension_expander/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] extern crate vst2; 2 | extern crate time; 3 | 4 | use vst2::plugin::{Category, Info, Plugin}; 5 | use vst2::buffer::AudioBuffer; 6 | 7 | use std::mem; 8 | use std::f64::consts::PI; 9 | use std::collections::VecDeque; 10 | 11 | /// Calculate the length in samples for a delay. Size ranges from 0.0 to 1.0. 12 | fn delay(index: usize, mut size: f32) -> isize { 13 | const SIZE_OFFSET: f32 = 0.06; 14 | const SIZE_MULT: f32 = 1_000.0; 15 | 16 | size += SIZE_OFFSET; 17 | 18 | // Spread ratio between delays 19 | const SPREAD: f32 = 0.3; 20 | 21 | let base = size * SIZE_MULT; 22 | let mult = (index as f32 * SPREAD) + 1.0; 23 | let offset = if index > 2 { base * SPREAD / 2.0 } else { 0.0 }; 24 | 25 | (base * mult + offset) as isize 26 | } 27 | 28 | /// A left channel and right channel sample. 29 | type SamplePair = (f32, f32); 30 | 31 | /// The Dimension Expander. 32 | struct DimensionExpander { 33 | buffers: Vec>, 34 | dry_wet: f32, 35 | size: f32, 36 | } 37 | 38 | impl Default for DimensionExpander { 39 | fn default() -> DimensionExpander { 40 | DimensionExpander::new(0.12, 0.66) 41 | } 42 | } 43 | 44 | impl DimensionExpander { 45 | fn new(size: f32, dry_wet: f32) -> DimensionExpander { 46 | const NUM_DELAYS: usize = 4; 47 | 48 | let mut buffers = Vec::new(); 49 | 50 | // Generate delay buffers 51 | for i in 0..NUM_DELAYS { 52 | let samples = delay(i, size); 53 | let mut buffer = VecDeque::with_capacity(samples as usize); 54 | 55 | // Fill in the delay buffers with empty samples 56 | for _ in 0..samples { 57 | buffer.push_back((0.0, 0.0)); 58 | } 59 | 60 | buffers.push(buffer); 61 | } 62 | 63 | DimensionExpander { 64 | buffers: buffers, 65 | dry_wet: dry_wet, 66 | size: size, 67 | } 68 | } 69 | 70 | /// Update the delay buffers with a new size value. 71 | fn resize(&mut self, n: f32) { 72 | let old_size = mem::replace(&mut self.size, n); 73 | 74 | for (i, buffer) in self.buffers.iter_mut().enumerate() { 75 | // Calculate the size difference between delays 76 | let old_delay = delay(i, old_size); 77 | let new_delay = delay(i, n); 78 | 79 | let diff = new_delay - old_delay; 80 | 81 | // Add empty samples if the delay was increased, remove if decreased 82 | if diff > 0 { 83 | for _ in 0..diff { 84 | buffer.push_back((0.0, 0.0)); 85 | } 86 | } else if diff < 0 { 87 | for _ in 0..-diff { 88 | let _ = buffer.pop_front(); 89 | } 90 | } 91 | } 92 | } 93 | } 94 | 95 | impl Plugin for DimensionExpander { 96 | fn get_info(&self) -> Info { 97 | Info { 98 | name: "Dimension Expander".to_string(), 99 | vendor: "overdrivenpotato".to_string(), 100 | unique_id: 243723071, 101 | version: 0001, 102 | inputs: 2, 103 | outputs: 2, 104 | parameters: 2, 105 | category: Category::Effect, 106 | 107 | ..Default::default() 108 | } 109 | } 110 | 111 | fn get_parameter(&self, index: i32) -> f32 { 112 | match index { 113 | 0 => self.size, 114 | 1 => self.dry_wet, 115 | _ => 0.0, 116 | } 117 | } 118 | 119 | fn get_parameter_text(&self, index: i32) -> String { 120 | match index { 121 | 0 => format!("{}", (self.size * 1000.0) as isize), 122 | 1 => format!("{:.1}%", self.dry_wet * 100.0), 123 | _ => "".to_string() 124 | } 125 | } 126 | 127 | fn get_parameter_name(&self, index: i32) -> String { 128 | match index { 129 | 0 => "Size", 130 | 1 => "Dry/Wet", 131 | _ => "", 132 | }.to_string() 133 | } 134 | 135 | fn set_parameter(&mut self, index: i32, val: f32) { 136 | match index { 137 | 0 => self.resize(val), 138 | 1 => self.dry_wet = val, 139 | _ => (), 140 | } 141 | } 142 | 143 | fn process(&mut self, buffer: AudioBuffer) { 144 | let (inputs, mut outputs) = buffer.split(); 145 | 146 | // Assume 2 channels 147 | if inputs.len() < 2 || outputs.len() < 2 { 148 | return; 149 | } 150 | 151 | // Iterate over inputs as (&f32, &f32) 152 | let stereo_in = match inputs.split_at(1) { 153 | (l, r) => l[0].iter().zip(r[0].iter()) 154 | }; 155 | 156 | // Iterate over outputs as (&mut f32, &mut f32) 157 | let stereo_out = match outputs.split_at_mut(1) { 158 | (l, r) => l[0].iter_mut().zip(r[0].iter_mut()) 159 | }; 160 | 161 | // Zip and process 162 | for ((left_in, right_in), (left_out, right_out)) in stereo_in.zip(stereo_out) { 163 | // Push the new samples into the delay buffers. 164 | for buffer in self.buffers.iter_mut() { 165 | buffer.push_back((*left_in, *right_in)); 166 | } 167 | 168 | let mut left_processed = 0.0; 169 | let mut right_processed = 0.0; 170 | 171 | // Recalculate time per sample 172 | let time_s = time::precise_time_ns() as f64 / 1_000_000_000.0; 173 | 174 | // Use buffer index to offset volume LFO 175 | for (n, buffer) in self.buffers.iter_mut().enumerate() { 176 | if let Some((left_old, right_old)) = buffer.pop_front() { 177 | const LFO_FREQ: f64 = 0.5; 178 | const WET_MULT: f32 = 0.66; 179 | 180 | let offset = 0.25 * (n % 4) as f64; 181 | 182 | // Sine wave volume LFO 183 | let lfo = ( 184 | (time_s * LFO_FREQ + offset) * PI * 2.0 185 | ).sin() as f32; 186 | 187 | let wet = self.dry_wet * WET_MULT; 188 | let mono = (left_old + right_old) / 2.0; 189 | 190 | // Flip right channel and keep left mono so that the result is 191 | // entirely stereo 192 | left_processed += mono * wet * lfo; 193 | right_processed += -mono * wet * lfo; 194 | } 195 | } 196 | 197 | // By only adding to the input, the output value always remains the same in mono 198 | *left_out = *left_in + left_processed; 199 | *right_out = *right_in + right_processed; 200 | } 201 | } 202 | } 203 | 204 | plugin_main!(DimensionExpander); 205 | -------------------------------------------------------------------------------- /examples/simple_host.rs: -------------------------------------------------------------------------------- 1 | extern crate vst2; 2 | 3 | use std::sync::{Arc, Mutex}; 4 | use std::path::Path; 5 | use std::error::Error; 6 | 7 | use vst2::host::{Host, PluginLoader}; 8 | use vst2::plugin::Plugin; 9 | 10 | struct SampleHost; 11 | 12 | impl Host for SampleHost { 13 | fn automate(&mut self, index: i32, value: f32) { 14 | println!("Parameter {} had its value changed to {}", index, value); 15 | } 16 | } 17 | 18 | fn main() { 19 | // This is an example of a plugin being loaded. Change this to the appropriate path. 20 | let path = Path::new( 21 | "/Library/Audio/Plug-Ins/VST/beyerdynamicVS.vst/Contents/MacOS/beyerdynamicVS" 22 | ); 23 | 24 | // Create the host 25 | let host = Arc::new(Mutex::new(SampleHost)); 26 | 27 | println!("Loading {}...", path.to_str().unwrap()); 28 | 29 | // Load the plugin 30 | let mut loader = PluginLoader::load(path, host.clone()) 31 | .unwrap_or_else(|e| panic!("Failed to load plugin: {}", 32 | e.description())); 33 | 34 | // Create an instance of the plugin 35 | let mut instance = loader.instance().unwrap(); 36 | 37 | // Get the plugin information 38 | let info = instance.get_info(); 39 | 40 | println!("Loaded '{}':\n\t\ 41 | Vendor: {}\n\t\ 42 | Presets: {}\n\t\ 43 | Parameters: {}\n\t\ 44 | VST ID: {}\n\t\ 45 | Version: {}\n\t\ 46 | Initial Delay: {} samples", 47 | info.name, 48 | info.vendor, 49 | info.presets, 50 | info.parameters, 51 | info.unique_id, 52 | info.version, 53 | info.initial_delay); 54 | 55 | // Initialize the instance 56 | instance.init(); 57 | println!("Initialized instance!"); 58 | 59 | println!("Closing instance..."); 60 | // Close the instance. This is not necessary as the instance is shut down when 61 | // it is dropped as it goes out of scope. 62 | // drop(instance); 63 | } 64 | -------------------------------------------------------------------------------- /examples/sine_synth/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sine_synth" 3 | version = "0.1.0" 4 | authors = ["Rob Saunders "] 5 | 6 | [dependencies] 7 | vst2 = { git = "https://github.com/overdrivenpotato/rust-vst2" } 8 | 9 | [lib] 10 | name = "sine_synth" 11 | crate-type = ["cdylib"] 12 | -------------------------------------------------------------------------------- /examples/sine_synth/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] extern crate vst2; 2 | 3 | use vst2::buffer::AudioBuffer; 4 | use vst2::plugin::{Category, Plugin, Info, CanDo}; 5 | use vst2::event::Event; 6 | use vst2::api::Supported; 7 | 8 | use std::f64::consts::PI; 9 | 10 | /// Convert the midi note into the equivalent frequency. 11 | /// 12 | /// This function assumes A4 is 440hz. 13 | fn midi_note_to_hz(note: u8) -> f64 { 14 | const A4: f64 = 440.0; 15 | 16 | (A4 / 32.0) * ((note as f64 - 9.0) / 12.0).exp2() 17 | } 18 | 19 | struct SineSynth { 20 | sample_rate: f64, 21 | time: f64, 22 | note_duration: f64, 23 | note: Option, 24 | } 25 | 26 | impl SineSynth { 27 | fn time_per_sample(&self) -> f64 { 28 | 1.0 / self.sample_rate 29 | } 30 | 31 | /// Process an incoming midi event. 32 | /// 33 | /// The midi data is split up like so: 34 | /// 35 | /// `data[0]`: Contains the status and the channel. Source: [source] 36 | /// `data[1]`: Contains the supplemental data for the message - so, if this was a NoteOn then 37 | /// this would contain the note. 38 | /// `data[2]`: Further supplemental data. Would be velocity in the case of a NoteOn message. 39 | /// 40 | /// [source]: http://www.midimountain.com/midi/midi_status.htm 41 | fn process_midi_event(&mut self, data: [u8; 3]) { 42 | match data[0] { 43 | 128 => self.note_off(data[1]), 44 | 144 => self.note_on(data[1]), 45 | _ => () 46 | } 47 | } 48 | 49 | fn note_on(&mut self, note: u8) { 50 | self.note_duration = 0.0; 51 | self.note = Some(note) 52 | } 53 | 54 | fn note_off(&mut self, note: u8) { 55 | if self.note == Some(note) { 56 | self.note = None 57 | } 58 | } 59 | } 60 | 61 | pub const TAU : f64 = PI * 2.0; 62 | 63 | impl Default for SineSynth { 64 | fn default() -> SineSynth { 65 | SineSynth { 66 | sample_rate: 44100.0, 67 | note_duration: 0.0, 68 | time: 0.0, 69 | note: None, 70 | } 71 | } 72 | } 73 | 74 | impl Plugin for SineSynth { 75 | fn get_info(&self) -> Info { 76 | Info { 77 | name: "SineSynth".to_string(), 78 | vendor: "DeathDisco".to_string(), 79 | unique_id: 6667, 80 | category: Category::Synth, 81 | inputs: 2, 82 | outputs: 2, 83 | parameters: 0, 84 | initial_delay: 0, 85 | ..Info::default() 86 | } 87 | } 88 | 89 | #[allow(unused_variables)] 90 | fn process_events(&mut self, events: Vec) { 91 | for event in events { 92 | match event { 93 | Event::Midi { data, .. } => self.process_midi_event(data), 94 | // More events can be handled here. 95 | _ => {} 96 | } 97 | } 98 | } 99 | 100 | fn set_sample_rate(&mut self, rate: f32) { 101 | self.sample_rate = rate as f64; 102 | } 103 | 104 | fn process(&mut self, buffer: AudioBuffer) { 105 | let (inputs, outputs) = buffer.split(); 106 | 107 | let samples = inputs 108 | .first() 109 | .map(|channel| channel.len()) 110 | .unwrap_or(0); 111 | 112 | let per_sample = self.time_per_sample(); 113 | 114 | for (input_buffer, output_buffer) in inputs.iter().zip(outputs) { 115 | let mut t = self.time; 116 | 117 | for (_, output_sample) in input_buffer.iter().zip(output_buffer) { 118 | if let Some(current_note) = self.note { 119 | let signal = (t * midi_note_to_hz(current_note) * TAU).sin(); 120 | 121 | // Apply a quick envelope to the attack of the signal to avoid popping. 122 | let attack = 0.5; 123 | let alpha = if self.note_duration < attack { 124 | self.note_duration / attack 125 | } else { 126 | 1.0 127 | }; 128 | 129 | *output_sample = (signal * alpha) as f32; 130 | 131 | t += per_sample; 132 | } else { 133 | *output_sample = 0.0; 134 | } 135 | } 136 | } 137 | 138 | self.time += samples as f64 * per_sample; 139 | self.note_duration += samples as f64 * per_sample; 140 | } 141 | 142 | fn can_do(&self, can_do: CanDo) -> Supported { 143 | match can_do { 144 | CanDo::ReceiveMidiEvent => Supported::Yes, 145 | _ => Supported::Maybe 146 | } 147 | } 148 | } 149 | 150 | plugin_main!(SineSynth); 151 | -------------------------------------------------------------------------------- /osx_vst_bundler.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Make sure we have the arguments we need 4 | if [[ -z $1 || -z $2 ]]; then 5 | echo "Generates a macOS bundle from a compiled dylib file" 6 | echo "Example:" 7 | echo -e "\t$0 Plugin target/release/plugin.dylib" 8 | echo -e "\tCreates a Plugin.vst bundle" 9 | else 10 | # Make the bundle folder 11 | mkdir -p "$1.vst/Contents/MacOS" 12 | 13 | # Create the PkgInfo 14 | echo "BNDL????" > "$1.vst/Contents/PkgInfo" 15 | 16 | #build the Info.Plist 17 | echo " 18 | 19 | 20 | 21 | CFBundleDevelopmentRegion 22 | English 23 | 24 | CFBundleExecutable 25 | $1 26 | 27 | CFBundleGetInfoString 28 | vst 29 | 30 | CFBundleIconFile 31 | 32 | 33 | CFBundleIdentifier 34 | com.rust-vst2.$1 35 | 36 | CFBundleInfoDictionaryVersion 37 | 6.0 38 | 39 | CFBundleName 40 | $1 41 | 42 | CFBundlePackageType 43 | BNDL 44 | 45 | CFBundleVersion 46 | 1.0 47 | 48 | CFBundleSignature 49 | $((RANDOM % 9999)) 50 | 51 | CSResourcesFileMapped 52 | 53 | 54 | 55 | " > "$1.vst/Contents/Info.plist" 56 | 57 | # move the provided library to the correct location 58 | cp "$2" "$1.vst/Contents/MacOS/$1" 59 | 60 | echo "Created bundle $1.vst" 61 | fi 62 | -------------------------------------------------------------------------------- /src/api.rs: -------------------------------------------------------------------------------- 1 | //! Structures and types for interfacing with the VST 2.4 API. 2 | use std::mem; 3 | 4 | use std::os::raw::c_void; 5 | 6 | use plugin::Plugin; 7 | use self::consts::*; 8 | 9 | /// Constant values 10 | #[allow(missing_docs)] // For obvious constants 11 | pub mod consts { 12 | 13 | pub const MAX_PRESET_NAME_LEN: usize = 24; 14 | pub const MAX_PARAM_STR_LEN: usize = 32; 15 | pub const MAX_LABEL: usize = 64; 16 | pub const MAX_SHORT_LABEL: usize = 8; 17 | pub const MAX_PRODUCT_STR_LEN: usize = 64; 18 | pub const MAX_VENDOR_STR_LEN: usize = 64; 19 | 20 | /// VST plugins are identified by a magic number. This corresponds to 0x56737450. 21 | pub const VST_MAGIC: i32 = ('V' as i32) << 24 | 22 | ('s' as i32) << 16 | 23 | ('t' as i32) << 8 | 24 | ('P' as i32) << 0 ; 25 | } 26 | 27 | /// `VSTPluginMain` function signature. 28 | pub type PluginMain = fn(callback: HostCallbackProc) -> *mut AEffect; 29 | 30 | /// Host callback function passed to plugin. Can be used to query host information from plugin side. 31 | pub type HostCallbackProc = fn(effect: *mut AEffect, opcode: i32, index: i32, value: isize, ptr: *mut c_void, opt: f32) -> isize; 32 | 33 | /// Dispatcher function used to process opcodes. Called by host. 34 | pub type DispatcherProc = fn(effect: *mut AEffect, opcode: i32, index: i32, value: isize, ptr: *mut c_void, opt: f32) -> isize; 35 | 36 | /// Process function used to process 32 bit floating point samples. Called by host. 37 | pub type ProcessProc = fn(effect: *mut AEffect, inputs: *mut *mut f32, outputs: *mut *mut f32, sample_frames: i32); 38 | 39 | /// Process function used to process 64 bit floating point samples. Called by host. 40 | pub type ProcessProcF64 = fn(effect: *mut AEffect, inputs: *mut *mut f64, outputs: *mut *mut f64, sample_frames: i32); 41 | 42 | /// Callback function used to set parameter values. Called by host. 43 | pub type SetParameterProc = fn(effect: *mut AEffect, index: i32, parameter: f32); 44 | 45 | /// Callback function used to get parameter values. Called by host. 46 | pub type GetParameterProc = fn(effect: *mut AEffect, index: i32) -> f32; 47 | 48 | /// Used with the VST API to pass around plugin information. 49 | #[allow(non_snake_case)] 50 | #[repr(C)] 51 | pub struct AEffect { 52 | /// Magic number. Must be `['V', 'S', 'T', 'P']`. 53 | pub magic: i32, 54 | 55 | /// Host to plug-in dispatcher. 56 | pub dispatcher: DispatcherProc, 57 | 58 | /// Accumulating process mode is deprecated in VST 2.4! Use `processReplacing` instead! 59 | pub _process: ProcessProc, 60 | 61 | /// Set value of automatable parameter. 62 | pub setParameter: SetParameterProc, 63 | 64 | /// Get value of automatable parameter. 65 | pub getParameter: GetParameterProc, 66 | 67 | 68 | /// Number of programs (Presets). 69 | pub numPrograms: i32, 70 | 71 | /// Number of parameters. All programs are assumed to have this many parameters. 72 | pub numParams: i32, 73 | 74 | /// Number of audio inputs. 75 | pub numInputs: i32, 76 | 77 | /// Number of audio outputs. 78 | pub numOutputs: i32, 79 | 80 | 81 | /// Bitmask made of values from api::flags. 82 | /// 83 | /// ```no_run 84 | /// use vst2::api::flags; 85 | /// let flags = flags::CAN_REPLACING | flags::CAN_DOUBLE_REPLACING; 86 | /// // ... 87 | /// ``` 88 | pub flags: i32, 89 | 90 | /// Reserved for host, must be 0. 91 | pub reserved1: isize, 92 | 93 | /// Reserved for host, must be 0. 94 | pub reserved2: isize, 95 | 96 | /// For algorithms which need input in the first place (Group delay or latency in samples). 97 | /// 98 | /// This value should be initially in a resume state. 99 | pub initialDelay: i32, 100 | 101 | 102 | /// Deprecated unused member. 103 | pub _realQualities: i32, 104 | 105 | /// Deprecated unused member. 106 | pub _offQualities: i32, 107 | 108 | /// Deprecated unused member. 109 | pub _ioRatio: f32, 110 | 111 | 112 | /// Void pointer usable by api to store object data. 113 | pub object: *mut c_void, 114 | 115 | /// User defined pointer. 116 | pub user: *mut c_void, 117 | 118 | /// Registered unique identifier (register it at Steinberg 3rd party support Web). This is used 119 | /// to identify a plug-in during save+load of preset and project. 120 | pub uniqueId: i32, 121 | 122 | /// Plug-in version (e.g. 1100 for v1.1.0.0). 123 | pub version: i32, 124 | 125 | /// Process audio samples in replacing mode. 126 | pub processReplacing: ProcessProc, 127 | 128 | /// Process double-precision audio samples in replacing mode. 129 | pub processReplacingF64: ProcessProcF64, 130 | 131 | /// Reserved for future use (please zero). 132 | pub future: [u8; 56] 133 | } 134 | 135 | impl AEffect { 136 | /// Return handle to Plugin object. Only works for plugins created using this library. 137 | pub unsafe fn get_plugin(&mut self) -> &mut Box { 138 | mem::transmute::<_, &mut Box>(self.object) 139 | } 140 | 141 | /// Drop the Plugin object. Only works for plugins created using this library. 142 | pub unsafe fn drop_plugin(&mut self) { 143 | // Possibly a simpler way of doing this..? 144 | drop(mem::transmute::<_, Box>>(self.object)) 145 | } 146 | } 147 | 148 | /// Information about a channel. Only some hosts use this information. 149 | #[repr(C)] 150 | pub struct ChannelProperties { 151 | /// Channel name. 152 | pub name: [u8; MAX_LABEL as usize], 153 | 154 | /// Flags found in `channel_flags` module. 155 | pub flags: i32, 156 | 157 | /// Type of speaker arrangement this channel is a part of. 158 | pub arrangement_type: SpeakerArrangementType, 159 | 160 | /// Name of channel (recommended: 6 characters + delimiter). 161 | pub short_name: [u8; MAX_SHORT_LABEL as usize], 162 | 163 | /// Reserved for future use. 164 | pub future: [u8; 48] 165 | } 166 | 167 | /// Tells the host how the channels are intended to be used in the plugin. Only useful for some 168 | /// hosts. 169 | #[repr(i32)] 170 | #[derive(Clone, Copy)] 171 | pub enum SpeakerArrangementType { 172 | /// User defined arrangement. 173 | Custom = -2, 174 | /// Empty arrangement. 175 | Empty = -1, 176 | 177 | /// Mono. 178 | Mono = 0, 179 | 180 | /// L R 181 | Stereo, 182 | /// Ls Rs 183 | StereoSurround, 184 | /// Lc Rc 185 | StereoCenter, 186 | /// Sl Sr 187 | StereoSide, 188 | /// C Lfe 189 | StereoCLfe, 190 | 191 | /// L R C 192 | Cinema30, 193 | /// L R S 194 | Music30, 195 | 196 | /// L R C Lfe 197 | Cinema31, 198 | /// L R Lfe S 199 | Music31, 200 | 201 | /// L R C S (LCRS) 202 | Cinema40, 203 | /// L R Ls Rs (Quadro) 204 | Music40, 205 | 206 | /// L R C Lfe S (LCRS + Lfe) 207 | Cinema41, 208 | /// L R Lfe Ls Rs (Quadro + Lfe) 209 | Music41, 210 | 211 | /// L R C Ls Rs 212 | Surround50, 213 | /// L R C Lfe Ls Rs 214 | Surround51, 215 | 216 | /// L R C Ls Rs Cs 217 | Cinema60, 218 | /// L R Ls Rs Sl Sr 219 | Music60, 220 | 221 | /// L R C Lfe Ls Rs Cs 222 | Cinema61, 223 | /// L R Lfe Ls Rs Sl Sr 224 | Music61, 225 | 226 | /// L R C Ls Rs Lc Rc 227 | Cinema70, 228 | /// L R C Ls Rs Sl Sr 229 | Music70, 230 | 231 | /// L R C Lfe Ls Rs Lc Rc 232 | Cinema71, 233 | /// L R C Lfe Ls Rs Sl Sr 234 | Music71, 235 | 236 | /// L R C Ls Rs Lc Rc Cs 237 | Cinema80, 238 | /// L R C Ls Rs Cs Sl Sr 239 | Music80, 240 | 241 | /// L R C Lfe Ls Rs Lc Rc Cs 242 | Cinema81, 243 | /// L R C Lfe Ls Rs Cs Sl Sr 244 | Music81, 245 | 246 | /// L R C Lfe Ls Rs Tfl Tfc Tfr Trl Trr Lfe2 247 | Surround102, 248 | } 249 | 250 | /// Used to specify whether functionality is supported. 251 | #[allow(missing_docs)] 252 | pub enum Supported { 253 | Yes, 254 | Maybe, 255 | No 256 | } 257 | 258 | impl Supported { 259 | /// Create a `Supported` value from an integer if possible. 260 | pub fn from(val: isize) -> Option { 261 | use self::Supported::*; 262 | 263 | match val { 264 | 1 => Some(Yes), 265 | 0 => Some(Maybe), 266 | -1 => Some(No), 267 | _ => None 268 | } 269 | } 270 | } 271 | 272 | impl Into for Supported { 273 | /// Convert to integer ordinal for interop with VST api. 274 | fn into(self) -> isize { 275 | use self::Supported::*; 276 | 277 | match self { 278 | Yes => 1, 279 | Maybe => 0, 280 | No => -1 281 | } 282 | } 283 | } 284 | 285 | /// Denotes in which thread the host is in. 286 | #[repr(i32)] 287 | pub enum ProcessLevel { 288 | /// Unsupported by host. 289 | Unknown = 0, 290 | 291 | /// GUI thread. 292 | User, 293 | /// Audio process thread. 294 | Realtime, 295 | /// Sequence thread (MIDI, etc). 296 | Prefetch, 297 | /// Offline processing thread (therefore GUI/user thread). 298 | Offline, 299 | } 300 | 301 | /// Language that the host is using. 302 | #[repr(i32)] 303 | #[allow(missing_docs)] 304 | pub enum HostLanguage { 305 | English = 1, 306 | German, 307 | French, 308 | Italian, 309 | Spanish, 310 | Japanese, 311 | } 312 | 313 | /// The file operation to perform. 314 | #[repr(i32)] 315 | pub enum FileSelectCommand { 316 | /// Load a file. 317 | Load = 0, 318 | /// Save a file. 319 | Save, 320 | /// Load multiple files simultaneously. 321 | LoadMultipleFiles, 322 | /// Choose a directory. 323 | SelectDirectory, 324 | } 325 | 326 | // TODO: investigate removing this. 327 | /// Format to select files. 328 | pub enum FileSelectType { 329 | /// Regular file selector. 330 | Regular 331 | } 332 | 333 | /// File type descriptor. 334 | #[repr(C)] 335 | pub struct FileType { 336 | /// Display name of file type. 337 | pub name: [u8; 128], 338 | 339 | /// OS X file type. 340 | pub osx_type: [u8; 8], 341 | /// Windows file type. 342 | pub win_type: [u8; 8], 343 | /// Unix file type. 344 | pub nix_type: [u8; 8], 345 | 346 | /// MIME type. 347 | pub mime_type_1: [u8; 128], 348 | /// Additional MIME type. 349 | pub mime_type_2: [u8; 128], 350 | } 351 | 352 | /// File selector descriptor used in `host::OpCode::OpenFileSelector`. 353 | #[repr(C)] 354 | pub struct FileSelect { 355 | /// The type of file selection to perform. 356 | pub command: FileSelectCommand, 357 | /// The file selector to open. 358 | pub select_type: FileSelectType, 359 | /// Unknown. 0 = no creator. 360 | pub mac_creator: i32, 361 | /// Number of file types. 362 | pub num_types: i32, 363 | /// List of file types to show. 364 | pub file_types: *mut FileType, 365 | 366 | /// File selector's title. 367 | pub title: [u8; 1024], 368 | /// Initial path. 369 | pub initial_path: *mut u8, 370 | /// Used when operation returns a single path. 371 | pub return_path: *mut u8, 372 | /// Size of the path buffer in bytes. 373 | pub size_return_path: i32, 374 | 375 | /// Used when operation returns multiple paths. 376 | pub return_multiple_paths: *mut *mut u8, 377 | /// Number of paths returned. 378 | pub num_paths: i32, 379 | 380 | /// Reserved by host. 381 | pub reserved: isize, 382 | /// Reserved for future use. 383 | pub future: [u8; 116] 384 | } 385 | 386 | /// A struct which contains events. 387 | #[repr(C)] 388 | pub struct Events { 389 | /// Number of events. 390 | pub num_events: i32, 391 | 392 | /// Reserved for future use. Should be 0. 393 | pub _reserved: isize, 394 | 395 | /// Variable-length array of pointers to `api::Event` objects. 396 | /// 397 | /// The VST standard specifies a variable length array of initial size 2. If there are more 398 | /// than 2 elements a larger array must be stored in this structure. 399 | pub events: [*mut Event; 2], 400 | } 401 | 402 | /// The type of event that has occured. See `api::Event.event_type`. 403 | #[repr(i32)] 404 | #[derive(Copy, Clone, Debug)] 405 | pub enum EventType { 406 | /// Midi event. See `api::MidiEvent`. 407 | Midi = 1, 408 | 409 | /// Deprecated. 410 | _Audio, 411 | /// Deprecated. 412 | _Video, 413 | /// Deprecated. 414 | _Parameter, 415 | /// Deprecated. 416 | _Trigger, 417 | 418 | /// System exclusive event. See `api::SysExEvent`. 419 | SysEx, 420 | } 421 | 422 | /// A VST event intended to be casted to a corresponding type. 423 | /// 424 | /// The event types are not all guaranteed to be the same size, so casting between them can be done 425 | /// via `mem::transmute()` while leveraging pointers, e.g. 426 | /// 427 | /// ``` 428 | /// # use vst2::api::{Event, EventType, MidiEvent, SysExEvent}; 429 | /// # let mut event: *mut Event = &mut unsafe { std::mem::zeroed() }; 430 | /// // let event: *const Event = ...; 431 | /// let midi_event: &MidiEvent = unsafe { std::mem::transmute(event) }; 432 | /// ``` 433 | #[repr(C)] 434 | #[derive(Copy, Clone)] 435 | pub struct Event { 436 | /// The type of event. This lets you know which event this object should be casted to. 437 | /// 438 | /// # Example 439 | /// 440 | /// ``` 441 | /// # use vst2::api::{Event, EventType, MidiEvent, SysExEvent}; 442 | /// # 443 | /// # // Valid for test 444 | /// # let mut event: *mut Event = &mut unsafe { std::mem::zeroed() }; 445 | /// # 446 | /// // let mut event: *mut Event = ... 447 | /// match unsafe { (*event).event_type } { 448 | /// EventType::Midi => { 449 | /// let midi_event: &MidiEvent = unsafe { 450 | /// std::mem::transmute(event) 451 | /// }; 452 | /// 453 | /// // ... 454 | /// } 455 | /// EventType::SysEx => { 456 | /// let sys_event: &SysExEvent = unsafe { 457 | /// std::mem::transmute(event) 458 | /// }; 459 | /// 460 | /// // ... 461 | /// } 462 | /// // ... 463 | /// # _ => {} 464 | /// } 465 | /// ``` 466 | pub event_type: EventType, 467 | 468 | /// Size of this structure; `mem::sizeof::()`. 469 | pub byte_size: i32, 470 | 471 | /// Number of samples into the current processing block that this event occurs on. 472 | /// 473 | /// E.g. if the block size is 512 and this value is 123, the event will occur on sample 474 | /// `samples[123]`. 475 | pub delta_frames: i32, 476 | 477 | /// Generic flags, none defined in VST api yet. 478 | pub _flags: i32, 479 | 480 | /// The `Event` type is cast appropriately, so this acts as reserved space. 481 | /// 482 | /// The actual size of the data may vary as this type is not guaranteed to be the same size as 483 | /// the other event types. 484 | pub _reserved: [u8; 16] 485 | } 486 | 487 | /// A midi event. 488 | #[repr(C)] 489 | pub struct MidiEvent { 490 | /// Should be `EventType::Midi`. 491 | pub event_type: EventType, 492 | 493 | /// Size of this structure; `mem::sizeof::()`. 494 | pub byte_size: i32, 495 | 496 | /// Number of samples into the current processing block that this event occurs on. 497 | /// 498 | /// E.g. if the block size is 512 and this value is 123, the event will occur on sample 499 | /// `samples[123]`. 500 | pub delta_frames: i32, 501 | 502 | /// See `flags::MidiFlags`. 503 | pub flags: i32, 504 | 505 | 506 | /// Length in sample frames of entire note if available, otherwise 0. 507 | pub note_length: i32, 508 | 509 | /// Offset in samples into note from start if available, otherwise 0. 510 | pub note_offset: i32, 511 | 512 | /// 1 to 3 midi bytes. TODO: Doc 513 | pub midi_data: [u8; 3], 514 | 515 | /// Reserved midi byte (0). 516 | pub _midi_reserved: u8, 517 | 518 | /// Detuning between -63 and +64 cents, for scales other than 'well-tempered'. e.g. 'microtuning' 519 | pub detune: i8, 520 | 521 | /// Note off velocity between 0 and 127. 522 | pub note_off_velocity: u8, 523 | 524 | /// Reserved for future use. Should be 0. 525 | pub _reserved1: u8, 526 | /// Reserved for future use. Should be 0. 527 | pub _reserved2: u8, 528 | } 529 | 530 | /// A midi system exclusive event. 531 | /// 532 | /// This event only contains raw byte data, and is up to the plugin to interpret it correctly. 533 | /// `plugin::CanDo` has a `ReceiveSysExEvent` variant which lets the host query the plugin as to 534 | /// whether this event is supported. 535 | #[repr(C)] 536 | pub struct SysExEvent { 537 | /// Should be `EventType::SysEx`. 538 | pub event_type: EventType, 539 | 540 | /// Size of this structure; `mem::sizeof::()`. 541 | pub byte_size: i32, 542 | 543 | /// Number of samples into the current processing block that this event occurs on. 544 | /// 545 | /// E.g. if the block size is 512 and this value is 123, the event will occur on sample 546 | /// `samples[123]`. 547 | pub delta_frames: i32, 548 | 549 | /// Generic flags, none defined in VST api yet. 550 | pub _flags: i32, 551 | 552 | 553 | /// Size of payload in bytes. 554 | pub data_size: i32, 555 | 556 | /// Reserved for future use. Should be 0. 557 | pub _reserved1: isize, 558 | 559 | /// Pointer to payload. 560 | pub system_data: *mut u8, 561 | 562 | /// Reserved for future use. Should be 0. 563 | pub _reserved2: isize, 564 | } 565 | 566 | /// Bitflags. 567 | pub mod flags { 568 | bitflags! { 569 | /// Flags for VST channels. 570 | pub flags Channel: i32 { 571 | /// Indicates channel is active. Ignored by host. 572 | const ACTIVE = 1, 573 | /// Indicates channel is first of stereo pair. 574 | const STEREO = 1 << 1, 575 | /// Use channel's specified speaker_arrangement instead of stereo flag. 576 | const SPEAKER = 1 << 2 577 | } 578 | } 579 | 580 | bitflags! { 581 | /// Flags for VST plugins. 582 | pub flags Plugin: i32 { 583 | /// Plugin has an editor. 584 | const HAS_EDITOR = 1 << 0, 585 | /// Plugin can process 32 bit audio. (Mandatory in VST 2.4). 586 | const CAN_REPLACING = 1 << 4, 587 | /// Plugin preset data is handled in formatless chunks. 588 | const PROGRAM_CHUNKS = 1 << 5, 589 | /// Plugin is a synth. 590 | const IS_SYNTH = 1 << 8, 591 | /// Plugin does not produce sound when all input is silence. 592 | const NO_SOUND_IN_STOP = 1 << 9, 593 | /// Supports 64 bit audio processing. 594 | const CAN_DOUBLE_REPLACING = 1 << 12 595 | } 596 | } 597 | 598 | bitflags!{ 599 | /// Cross platform modifier key flags. 600 | pub flags ModifierKey: u8 { 601 | /// Shift key. 602 | const SHIFT = 1 << 0, 603 | /// Alt key. 604 | const ALT = 1 << 1, 605 | /// Control on mac. 606 | const COMMAND = 1 << 2, 607 | /// Command on mac, ctrl on other. 608 | const CONTROL = 1 << 3, // Ctrl on PC, Apple on Mac 609 | } 610 | } 611 | 612 | bitflags! { 613 | /// MIDI event flags. 614 | pub flags MidiEvent: i32 { 615 | /// This event is played live (not in playback from a sequencer track). This allows the 616 | /// plugin to handle these flagged events with higher priority, especially when the 617 | /// plugin has a big latency as per `plugin::Info::initial_delay`. 618 | const REALTIME_EVENT = 1 << 0, 619 | } 620 | } 621 | } 622 | -------------------------------------------------------------------------------- /src/buffer.rs: -------------------------------------------------------------------------------- 1 | //! Buffers to safely work with audio samples. 2 | 3 | use std::iter::{Zip, IntoIterator}; 4 | use std::vec::IntoIter; 5 | use std::slice; 6 | 7 | use num_traits::Float; 8 | 9 | /// A buffer containing `ChannelBuffer` buffers for each input/output. 10 | pub struct AudioBuffer<'a, T: 'a + Float> { 11 | inputs: Vec<&'a mut [T]>, 12 | outputs: Vec<&'a mut [T]>, 13 | } 14 | 15 | /// Iterator over channel buffers for either inputs or outputs. 16 | pub type ChannelBufferIter<'a, T> = IntoIter<&'a mut [T]>; 17 | 18 | impl<'a, T: 'a + Float> AudioBuffer<'a, T> { 19 | /// Create an `AudioBuffer` from vectors of slices. 20 | /// 21 | /// Each vector item represents either an input or output, and contains an array of samples. Eg 22 | /// if inputs was a vector of size 2 containing slices of size 512, it would hold 2 inputs where 23 | /// each input holds 512 samples. 24 | pub fn new(inputs: Vec<&'a mut [T]>, outputs: Vec<&'a mut [T]>) -> AudioBuffer<'a, T> { 25 | AudioBuffer { 26 | inputs: inputs, 27 | outputs: outputs, 28 | } 29 | } 30 | 31 | /// Create an `AudioBuffer` from raw pointers. Only really useful for interacting with the VST 32 | /// API. 33 | pub unsafe fn from_raw(inputs_raw: *mut *mut T, outputs_raw: *mut *mut T, num_inputs: usize, num_outputs: usize, samples: usize) -> AudioBuffer<'a, T> { 34 | let inputs = 35 | // Create a slice of type &mut [*mut f32] 36 | slice::from_raw_parts_mut(inputs_raw, num_inputs).iter() 37 | // Convert to &mut [&mut [f32]] 38 | .map(|input| slice::from_raw_parts_mut(*input, samples)) 39 | // Collect into Vec<&mut [f32]> 40 | .collect(); 41 | 42 | let outputs = 43 | // Create a slice of type &mut [*mut f32] 44 | slice::from_raw_parts_mut(outputs_raw, num_outputs).iter() 45 | // Convert to &mut [&mut [f32]] 46 | .map(|output| slice::from_raw_parts_mut(*output, samples)) 47 | // Collect into Vec<&mut [f32]> 48 | .collect(); 49 | 50 | // Call constructor with vectors 51 | AudioBuffer::new(inputs, outputs) 52 | } 53 | 54 | /// Return a reference to all inputs. 55 | pub fn inputs(&'a mut self) -> &'a mut Vec<&'a mut [T]> { 56 | &mut self.inputs 57 | } 58 | 59 | /// Return a reference to all outputs. 60 | pub fn outputs(&'a mut self) -> &'a mut Vec<&'a mut [T]> { 61 | &mut self.outputs 62 | } 63 | 64 | /// Consume this buffer and split it into separate inputs and outputs. 65 | /// 66 | /// # Example 67 | /// 68 | /// ``` 69 | /// # use vst2::buffer::AudioBuffer; 70 | /// # let mut in1 = vec![0.0; 512]; 71 | /// # let (mut in2, mut out1, mut out2) = (in1.clone(), in1.clone(), in1.clone()); 72 | /// # 73 | /// # let buffer = AudioBuffer::new(vec![&mut in1, &mut in2], 74 | /// # vec![&mut out1, &mut out2]); 75 | /// let (mut inputs, mut outputs) = buffer.split(); 76 | /// let input: &mut [f32] = &mut inputs[0]; // First input 77 | /// ``` 78 | pub fn split(self) -> (Vec<&'a mut [T]>, Vec<&'a mut [T]>) { 79 | (self.inputs, self.outputs) 80 | } 81 | 82 | /// Zip together buffers. 83 | pub fn zip(self) -> Zip, ChannelBufferIter<'a, T>> { 84 | self.inputs.into_iter().zip(self.outputs.into_iter()) 85 | } 86 | } 87 | 88 | #[cfg(test)] 89 | mod tests { 90 | use buffer::AudioBuffer; 91 | 92 | /// Size of buffers used in tests. 93 | const SIZE: usize = 1024; 94 | 95 | /// Test that creating and zipping buffers works. 96 | /// 97 | /// This test creates a channel for 2 inputs and 2 outputs. The input channels are simply values 98 | /// from 0 to `SIZE-1` (e.g. [0, 1, 2, 3, 4, .. , SIZE - 1]) and the output channels are just 0. 99 | /// This test assures that when the buffers are zipped together, the input values do not change. 100 | #[test] 101 | fn buffer_zip() { 102 | let mut in1: Vec = (0..SIZE).map(|x| x as f32).collect(); 103 | let mut in2 = in1.clone(); 104 | 105 | let mut out1 = vec![0.0; SIZE]; 106 | let mut out2 = out1.clone(); 107 | 108 | let buffer = AudioBuffer::new(vec![&mut in1, &mut in2], 109 | vec![&mut out1, &mut out2]); 110 | 111 | for (input, output) in buffer.zip() { 112 | input.into_iter().zip(output.into_iter()) 113 | .fold(0, |acc, (input, output)| { 114 | assert_eq!(*input - acc as f32, 0.0); 115 | assert_eq!(*output, 0.0); 116 | acc + 1 117 | }); 118 | } 119 | } 120 | 121 | /// Test that creating buffers from raw pointers works. 122 | #[test] 123 | fn from_raw() { 124 | let mut in1: Vec = (0..SIZE).map(|x| x as f32).collect(); 125 | let mut in2 = in1.clone(); 126 | 127 | let mut out1 = vec![0.0; SIZE]; 128 | let mut out2 = out1.clone(); 129 | 130 | let buffer = unsafe { 131 | AudioBuffer::from_raw(vec![in1.as_mut_ptr(), in2.as_mut_ptr()].as_mut_ptr(), 132 | vec![out1.as_mut_ptr(), out2.as_mut_ptr()].as_mut_ptr(), 133 | 2, 2, SIZE) 134 | }; 135 | 136 | for (input, output) in buffer.zip() { 137 | input.into_iter().zip(output.into_iter()) 138 | .fold(0, |acc, (input, output)| { 139 | assert_eq!(*input - acc as f32, 0.0); 140 | assert_eq!(*output, 0.0); 141 | acc + 1 142 | }); 143 | } 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /src/channels.rs: -------------------------------------------------------------------------------- 1 | //! Meta data for dealing with input / output channels. Not all hosts use this so it is not 2 | //! necessary for plugin functionality. 3 | 4 | use api; 5 | use api::consts::{MAX_LABEL, MAX_SHORT_LABEL}; 6 | 7 | /// Information about an input / output channel. This isn't necessary for a channel to function but 8 | /// informs the host how the channel is meant to be used. 9 | pub struct ChannelInfo { 10 | name: String, 11 | short_name: String, 12 | active: bool, 13 | arrangement_type: SpeakerArrangementType 14 | } 15 | 16 | impl ChannelInfo { 17 | /// Construct a new `ChannelInfo` object. 18 | /// 19 | /// `name` is a user friendly name for this channel limited to `MAX_LABEL` characters. 20 | /// `short_name` is an optional field which provides a short name limited to `MAX_SHORT_LABEL`. 21 | /// `active` determines whether this channel is active. 22 | /// `arrangement_type` describes the arrangement type for this channel. 23 | pub fn new(name: String, 24 | short_name: Option, 25 | active: bool, 26 | arrangement_type: Option) 27 | -> ChannelInfo { 28 | ChannelInfo { 29 | name: name.clone(), 30 | 31 | short_name: 32 | if let Some(short_name) = short_name { 33 | short_name 34 | } else { 35 | name 36 | }, 37 | 38 | active: active, 39 | 40 | arrangement_type: arrangement_type.unwrap_or(SpeakerArrangementType::Custom) 41 | } 42 | } 43 | } 44 | 45 | impl Into for ChannelInfo { 46 | /// Convert to the VST api equivalent of this structure. 47 | fn into(self) -> api::ChannelProperties { 48 | api::ChannelProperties { 49 | name: { 50 | let mut label = [0; MAX_LABEL as usize]; 51 | for (b, c) in self.name.bytes().zip(label.iter_mut()) { 52 | *c = b; 53 | } 54 | label 55 | }, 56 | flags: { 57 | use api::flags::*; 58 | 59 | let mut flag = Channel::empty(); 60 | if self.active { flag = flag | ACTIVE } 61 | if self.arrangement_type.is_left_stereo() { flag = flag | STEREO } 62 | if self.arrangement_type.is_speaker_type() { flag = flag | SPEAKER } 63 | flag.bits() 64 | }, 65 | arrangement_type: self.arrangement_type.into(), 66 | short_name: { 67 | let mut label = [0; MAX_SHORT_LABEL as usize]; 68 | for (b, c) in self.short_name.bytes().zip(label.iter_mut()) { 69 | *c = b; 70 | } 71 | label 72 | }, 73 | future: [0; 48] 74 | } 75 | } 76 | } 77 | 78 | impl From for ChannelInfo { 79 | fn from(api: api::ChannelProperties) -> ChannelInfo { 80 | use api::flags::*; 81 | 82 | ChannelInfo { 83 | name: String::from_utf8_lossy(&api.name).to_string(), 84 | short_name: String::from_utf8_lossy(&api.short_name).to_string(), 85 | active: Channel::from_bits(api.flags).expect("Invalid bits in channel info") 86 | .intersects(ACTIVE), 87 | arrangement_type: SpeakerArrangementType::from(api), 88 | } 89 | } 90 | } 91 | 92 | /// Target for Speaker arrangement type. Can be a cinema configuration or music configuration. Both 93 | /// are technically identical but this provides extra information to the host. 94 | pub enum ArrangementTarget { 95 | /// Music arrangement. Technically identical to Cinema. 96 | Music, 97 | /// Cinematic arrangement. Technically identical to Music. 98 | Cinema, 99 | } 100 | 101 | /// An enum for all channels in a stereo configuration. 102 | pub enum StereoChannel { 103 | /// Left channel. 104 | Left, 105 | /// Right channel. 106 | Right 107 | } 108 | 109 | /// Possible stereo speaker configurations. 110 | #[allow(non_camel_case_types)] 111 | pub enum StereoConfig { 112 | /// Regular. 113 | L_R, 114 | /// Left surround, right surround. 115 | Ls_Rs, 116 | /// Left center, right center. 117 | Lc_Rc, 118 | /// Side left, side right. 119 | Sl_Sr, 120 | /// Center, low frequency effects. 121 | C_Lfe 122 | } 123 | 124 | /// Possible surround speaker configurations. 125 | #[allow(non_camel_case_types)] 126 | pub enum SurroundConfig { 127 | /// 3.0 surround sound. 128 | /// Cinema: L R C 129 | /// Music: L R S 130 | S3_0(ArrangementTarget), 131 | /// 3.1 surround sound. 132 | /// Cinema: L R C Lfe 133 | /// Music: L R Lfe S 134 | S3_1(ArrangementTarget), 135 | /// 4.0 surround sound. 136 | /// Cinema: L R C S (LCRS) 137 | /// Music: L R Ls Rs (Quadro) 138 | S4_0(ArrangementTarget), 139 | /// 4.1 surround sound. 140 | /// Cinema: L R C Lfe S (LCRS + Lfe) 141 | /// Music: L R Ls Rs (Quadro + Lfe) 142 | S4_1(ArrangementTarget), 143 | /// 5.0 surround sound. 144 | /// Cinema and music: L R C Ls Rs 145 | S5_0, 146 | /// 5.1 surround sound. 147 | /// Cinema and music: L R C Lfe Ls Rs 148 | S5_1, 149 | /// 6.0 surround sound. 150 | /// Cinema: L R C Ls Rs Cs 151 | /// Music: L R Ls Rs Sl Sr 152 | S6_0(ArrangementTarget), 153 | /// 6.1 surround sound. 154 | /// Cinema: L R C Lfe Ls Rs Cs 155 | /// Music: L R Ls Rs Sl Sr 156 | S6_1(ArrangementTarget), 157 | /// 7.0 surround sound. 158 | /// Cinema: L R C Ls Rs Lc Rc 159 | /// Music: L R C Ls Rs Sl Sr 160 | S7_0(ArrangementTarget), 161 | /// 7.1 surround sound. 162 | /// Cinema: L R C Lfe Ls Rs Lc Rc 163 | /// Music: L R C Lfe Ls Rs Sl Sr 164 | S7_1(ArrangementTarget), 165 | /// 8.0 surround sound. 166 | /// Cinema: L R C Ls Rs Lc Rc Cs 167 | /// Music: L R C Ls Rs Cs Sl Sr 168 | S8_0(ArrangementTarget), 169 | /// 8.1 surround sound. 170 | /// Cinema: L R C Lfe Ls Rs Lc Rc Cs 171 | /// Music: L R C Lfe Ls Rs Cs Sl Sr 172 | S8_1(ArrangementTarget), 173 | /// 10.2 surround sound. 174 | /// Cinema + Music: L R C Lfe Ls Rs Tfl Tfc Tfr Trl Trr Lfe2 175 | S10_2, 176 | } 177 | 178 | /// Type representing how a channel is used. Only useful for some hosts. 179 | pub enum SpeakerArrangementType { 180 | /// Custom arrangement not specified to host. 181 | Custom, 182 | /// Empty arrangement. 183 | Empty, 184 | /// Mono channel. 185 | Mono, 186 | /// Stereo channel. Contains type of stereo arrangement and speaker represented. 187 | Stereo(StereoConfig, StereoChannel), 188 | /// Surround channel. Contains surround arrangement and target (cinema or music). 189 | Surround(SurroundConfig), 190 | } 191 | 192 | impl Default for SpeakerArrangementType { 193 | fn default() -> SpeakerArrangementType { 194 | SpeakerArrangementType::Mono 195 | } 196 | } 197 | 198 | impl SpeakerArrangementType { 199 | /// Determine whether this channel is part of a surround speaker arrangement. 200 | pub fn is_speaker_type(&self) -> bool { 201 | if let SpeakerArrangementType::Surround(..) = *self { 202 | true 203 | } else { 204 | false 205 | } 206 | } 207 | 208 | /// Determine whether this channel is the left speaker in a stereo pair. 209 | pub fn is_left_stereo(&self) -> bool { 210 | if let SpeakerArrangementType::Stereo(_, StereoChannel::Left) = *self { 211 | true 212 | } else { 213 | false 214 | } 215 | } 216 | } 217 | 218 | impl Into for SpeakerArrangementType { 219 | /// Convert to VST API arrangement type. 220 | fn into(self) -> api::SpeakerArrangementType { 221 | use api::SpeakerArrangementType as Raw; 222 | use self::SpeakerArrangementType::*; 223 | use self::ArrangementTarget::{Music, Cinema}; 224 | 225 | match self { 226 | Custom => Raw::Custom, 227 | Empty => Raw::Empty, 228 | Mono => Raw::Mono, 229 | Stereo(conf, _) => match conf { // Stereo channels. 230 | StereoConfig::L_R => Raw::Stereo, 231 | StereoConfig::Ls_Rs => Raw::StereoSurround, 232 | StereoConfig::Lc_Rc => Raw::StereoCenter, 233 | StereoConfig::Sl_Sr => Raw::StereoSide, 234 | StereoConfig::C_Lfe => Raw::StereoCLfe 235 | }, 236 | Surround(conf) => match conf { // Surround channels. 237 | SurroundConfig::S3_0(Music) => Raw::Music30, 238 | SurroundConfig::S3_0(Cinema) => Raw::Cinema30, 239 | 240 | SurroundConfig::S3_1(Music) => Raw::Music31, 241 | SurroundConfig::S3_1(Cinema) => Raw::Cinema31, 242 | 243 | SurroundConfig::S4_0(Music) => Raw::Music40, 244 | SurroundConfig::S4_0(Cinema) => Raw::Cinema40, 245 | 246 | SurroundConfig::S4_1(Music) => Raw::Music41, 247 | SurroundConfig::S4_1(Cinema) => Raw::Cinema41, 248 | 249 | SurroundConfig::S5_0 => Raw::Surround50, 250 | SurroundConfig::S5_1 => Raw::Surround51, 251 | 252 | SurroundConfig::S6_0(Music) => Raw::Music60, 253 | SurroundConfig::S6_0(Cinema) => Raw::Cinema60, 254 | 255 | SurroundConfig::S6_1(Music) => Raw::Music61, 256 | SurroundConfig::S6_1(Cinema) => Raw::Cinema61, 257 | 258 | SurroundConfig::S7_0(Music) => Raw::Music70, 259 | SurroundConfig::S7_0(Cinema) => Raw::Cinema70, 260 | 261 | SurroundConfig::S7_1(Music) => Raw::Music71, 262 | SurroundConfig::S7_1(Cinema) => Raw::Cinema71, 263 | 264 | SurroundConfig::S8_0(Music) => Raw::Music80, 265 | SurroundConfig::S8_0(Cinema) => Raw::Cinema80, 266 | 267 | SurroundConfig::S8_1(Music) => Raw::Music81, 268 | SurroundConfig::S8_1(Cinema) => Raw::Cinema81, 269 | 270 | SurroundConfig::S10_2 => Raw::Surround102, 271 | } 272 | } 273 | } 274 | } 275 | 276 | /// Convert the VST API equivalent struct into something more usable. 277 | /// 278 | /// We implement `From` as `SpeakerArrangementType` contains extra info about 279 | /// stereo speakers found in the channel flags. 280 | impl From for SpeakerArrangementType { 281 | fn from(api: api::ChannelProperties) -> SpeakerArrangementType { 282 | use api::flags::*; 283 | 284 | use api::SpeakerArrangementType as Raw; 285 | use self::SpeakerArrangementType::*; 286 | use self::ArrangementTarget::{Music, Cinema}; 287 | use self::SurroundConfig::*; 288 | 289 | let stereo = if Channel::from_bits(api.flags).expect("Invalid Channel Flags") 290 | .intersects(STEREO) { 291 | StereoChannel::Left 292 | } else { 293 | StereoChannel::Right 294 | }; 295 | 296 | match api.arrangement_type { 297 | Raw::Custom => Custom, 298 | Raw::Empty => Empty, 299 | Raw::Mono => Mono, 300 | 301 | Raw::Stereo => Stereo(StereoConfig::L_R, stereo), 302 | Raw::StereoSurround => Stereo(StereoConfig::Ls_Rs, stereo), 303 | Raw::StereoCenter => Stereo(StereoConfig::Lc_Rc, stereo), 304 | Raw::StereoSide => Stereo(StereoConfig::Sl_Sr, stereo), 305 | Raw::StereoCLfe => Stereo(StereoConfig::C_Lfe, stereo), 306 | 307 | Raw::Music30 => Surround(S3_0(Music)), 308 | Raw::Cinema30 => Surround(S3_0(Cinema)), 309 | 310 | Raw::Music31 => Surround(S3_1(Music)), 311 | Raw::Cinema31 => Surround(S3_1(Cinema)), 312 | 313 | Raw::Music40 => Surround(S4_0(Music)), 314 | Raw::Cinema40 => Surround(S4_0(Cinema)), 315 | 316 | Raw::Music41 => Surround(S4_1(Music)), 317 | Raw::Cinema41 => Surround(S4_1(Cinema)), 318 | 319 | Raw::Surround50 => Surround(S5_0), 320 | Raw::Surround51 => Surround(S5_1), 321 | 322 | Raw::Music60 => Surround(S6_0(Music)), 323 | Raw::Cinema60 => Surround(S6_0(Cinema)), 324 | 325 | Raw::Music61 => Surround(S6_1(Music)), 326 | Raw::Cinema61 => Surround(S6_1(Cinema)), 327 | 328 | Raw::Music70 => Surround(S7_0(Music)), 329 | Raw::Cinema70 => Surround(S7_0(Cinema)), 330 | 331 | Raw::Music71 => Surround(S7_1(Music)), 332 | Raw::Cinema71 => Surround(S7_1(Cinema)), 333 | 334 | Raw::Music80 => Surround(S8_0(Music)), 335 | Raw::Cinema80 => Surround(S8_0(Cinema)), 336 | 337 | Raw::Music81 => Surround(S8_1(Music)), 338 | Raw::Cinema81 => Surround(S8_1(Cinema)), 339 | 340 | Raw::Surround102 => Surround(S10_2) 341 | } 342 | } 343 | } 344 | -------------------------------------------------------------------------------- /src/editor.rs: -------------------------------------------------------------------------------- 1 | //! All VST plugin editor related functionality. 2 | 3 | use std::os::raw::c_void; 4 | 5 | /// Implemented by plugin editors. 6 | #[allow(unused_variables)] 7 | pub trait Editor { 8 | /// Get the size of the editor window. 9 | fn size(&self) -> (i32, i32); 10 | 11 | /// Get the coordinates of the editor window. 12 | fn position(&self) -> (i32, i32); 13 | 14 | 15 | /// Editor idle call. Called by host. 16 | fn idle(&mut self) {} 17 | 18 | /// Called when the editor window is closed. 19 | fn close(&mut self) {} 20 | 21 | /// Called when the editor window is opened. `window` is a platform dependent window pointer 22 | /// (e.g. `HWND` on Windows, `WindowRef` on OSX, `Window` on X11/Linux). 23 | fn open(&mut self, window: *mut c_void); 24 | 25 | /// Return whether the window is currently open. 26 | fn is_open(&mut self) -> bool; 27 | 28 | 29 | /// Set the knob mode for this editor (if supported by host). 30 | /// 31 | /// Return true if the knob mode was set. 32 | fn set_knob_mode(&mut self, mode: KnobMode) -> bool { false } 33 | 34 | /// Recieve key up event. Return true if the key was used. 35 | fn key_up(&mut self, keycode: KeyCode) -> bool { false } 36 | 37 | /// Receive key down event. Return true if the key was used. 38 | fn key_down(&mut self, keycode: KeyCode) -> bool { false } 39 | } 40 | 41 | /// Rectangle used to specify dimensions of editor window. 42 | #[doc(hidden)] 43 | #[derive(Copy, Clone, Debug)] 44 | pub struct Rect { 45 | /// Y value in pixels of top side. 46 | pub top: i16, 47 | /// X value in pixels of left side. 48 | pub left: i16, 49 | /// Y value in pixels of bottom side. 50 | pub bottom: i16, 51 | /// X value in pixels of right side. 52 | pub right: i16 53 | } 54 | 55 | /// A platform independent key code. Includes modifier keys. 56 | #[derive(Copy, Clone, Debug)] 57 | pub struct KeyCode { 58 | /// ASCII character for key pressed (if applicable). 59 | pub character: char, 60 | /// Key pressed. See `enums::Key`. 61 | pub key: Key, 62 | /// Modifier key bitflags. See `enums::flags::modifier_key`. 63 | pub modifier: u8 64 | } 65 | 66 | /// Allows host to set how a parameter knob works. 67 | #[repr(usize)] 68 | #[derive(Copy, Clone, Debug)] 69 | #[allow(missing_docs)] 70 | pub enum KnobMode { 71 | Circular, 72 | CircularRelative, 73 | Linear 74 | } 75 | impl_clike!(KnobMode); 76 | 77 | /// Platform independent key codes. 78 | #[allow(missing_docs)] 79 | #[repr(usize)] 80 | #[derive(Debug, Copy, Clone)] 81 | pub enum Key { 82 | Back = 1, 83 | Tab, 84 | Clear, 85 | Return, 86 | Pause, 87 | Escape, 88 | Space, 89 | Next, 90 | End, 91 | Home, 92 | Left, 93 | Up, 94 | Right, 95 | Down, 96 | PageUp, 97 | PageDown, 98 | Select, 99 | Print, 100 | Enter, 101 | Snapshot, 102 | Insert, 103 | Delete, 104 | Help, 105 | Numpad0, 106 | Numpad1, 107 | Numpad2, 108 | Numpad3, 109 | Numpad4, 110 | Numpad5, 111 | Numpad6, 112 | Numpad7, 113 | Numpad8, 114 | Numpad9, 115 | Multiply, 116 | Add, 117 | Separator, 118 | Subtract, 119 | Decimal, 120 | Divide, 121 | F1, 122 | F2, 123 | F3, 124 | F4, 125 | F5, 126 | F6, 127 | F7, 128 | F8, 129 | F9, 130 | F10, 131 | F11, 132 | F12, 133 | Numlock, 134 | Scroll, 135 | Shift, 136 | Control, 137 | Alt, 138 | Equals 139 | } 140 | impl_clike!(Key); 141 | -------------------------------------------------------------------------------- /src/event.rs: -------------------------------------------------------------------------------- 1 | //! Interfaces to VST events. 2 | // TODO: Update and explain both host and plugin events 3 | 4 | use std::{mem, slice}; 5 | 6 | use api::flags::*; 7 | use api::{self, flags}; 8 | 9 | /// A VST event. 10 | pub enum Event<'a> { 11 | /// A midi event. 12 | /// 13 | /// These are sent to the plugin before `Plugin::processing()` or `Plugin::processing_f64()` is 14 | /// called. 15 | Midi { 16 | /// The raw midi data associated with this event. 17 | data: [u8; 3], 18 | 19 | /// Number of samples into the current processing block that this event occurs on. 20 | /// 21 | /// E.g. if the block size is 512 and this value is 123, the event will occur on sample 22 | /// `samples[123]`. 23 | // TODO: Don't repeat this value in all event types 24 | delta_frames: i32, 25 | 26 | /// This midi event was created live as opposed to being played back in the sequencer. 27 | /// 28 | /// This can give the plugin priority over this event if it introduces a lot of latency. 29 | live: bool, 30 | 31 | /// The length of the midi note associated with this event, if available. 32 | note_length: Option, 33 | 34 | /// Offset in samples into note from note start, if available. 35 | note_offset: Option, 36 | 37 | /// Detuning between -63 and +64 cents. 38 | detune: i8, 39 | 40 | /// Note off velocity between 0 and 127. 41 | note_off_velocity: u8, 42 | }, 43 | 44 | /// A system exclusive event. 45 | /// 46 | /// This is just a block of data and it is up to the plugin to interpret this. Generally used 47 | /// by midi controllers. 48 | SysEx { 49 | /// The SysEx payload. 50 | payload: &'a [u8], 51 | 52 | /// Number of samples into the current processing block that this event occurs on. 53 | /// 54 | /// E.g. if the block size is 512 and this value is 123, the event will occur on sample 55 | /// `samples[123]`. 56 | delta_frames: i32, 57 | }, 58 | 59 | /// A deprecated event. 60 | /// 61 | /// Passes the raw midi event structure along with this so that implementors can handle 62 | /// optionally handle this event. 63 | Deprecated(api::Event), 64 | } 65 | 66 | impl<'a> From for Event<'a> { 67 | fn from(event: api::Event) -> Event<'a> { 68 | use api::EventType::*; 69 | 70 | match event.event_type { 71 | Midi => { 72 | let event: api::MidiEvent = unsafe { mem::transmute(event) }; 73 | 74 | let length = if event.note_length > 0 { Some(event.note_length) } else { None }; 75 | let offset = if event.note_offset > 0 { Some(event.note_offset) } else { None }; 76 | let flags = flags::MidiEvent::from_bits(event.flags).unwrap(); 77 | 78 | Event::Midi { 79 | data: event.midi_data, 80 | delta_frames: event.delta_frames, 81 | live: flags.intersects(REALTIME_EVENT), 82 | note_length: length, 83 | note_offset: offset, 84 | detune: event.detune, 85 | note_off_velocity: event.note_off_velocity 86 | } 87 | } 88 | 89 | SysEx => Event::SysEx { 90 | payload: unsafe { 91 | // We can safely transmute the event pointer to a `SysExEvent` pointer as 92 | // event_type refers to a `SysEx` type. 93 | let event: &api::SysExEvent = mem::transmute(&event); 94 | slice::from_raw_parts(event.system_data, event.data_size as usize) 95 | }, 96 | 97 | delta_frames: event.delta_frames 98 | }, 99 | 100 | _ => Event::Deprecated(event), 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/host.rs: -------------------------------------------------------------------------------- 1 | //! Host specific structures. 2 | 3 | use std::path::Path; 4 | use std::sync::{Arc, Mutex}; 5 | use std::error::Error; 6 | use std::{fmt, mem, ptr, slice}; 7 | use std::ffi::CString; 8 | 9 | use libloading::Library; 10 | use std::os::raw::c_void; 11 | 12 | use interfaces; 13 | use plugin::{self, Plugin, Info, Category}; 14 | use api::{self, AEffect, PluginMain, Supported}; 15 | use api::consts::*; 16 | use buffer::AudioBuffer; 17 | use event::Event; 18 | use channels::ChannelInfo; 19 | 20 | #[repr(usize)] 21 | #[derive(Clone, Copy, Debug)] 22 | #[doc(hidden)] 23 | pub enum OpCode { 24 | /// [index]: parameter index 25 | /// [opt]: parameter value 26 | Automate = 0, 27 | /// [return]: host vst version (e.g. 2400 for VST 2.4) 28 | Version, 29 | /// [return]: current plugin ID (useful for shell plugins to figure out which plugin to load in 30 | /// `VSTPluginMain()`). 31 | CurrentId, 32 | /// No arguments. Give idle time to Host application, e.g. if plug-in editor is doing mouse 33 | /// tracking in a modal loop. 34 | Idle, 35 | /// Deprecated. 36 | _PinConnected = 4, 37 | 38 | /// Deprecated. 39 | _WantMidi = 6, // Not a typo 40 | /// [value]: request mask. see `VstTimeInfoFlags` 41 | /// [return]: `VstTimeInfo` pointer or null if not supported. 42 | GetTime, 43 | /// Inform host that the plugin has MIDI events ready to be processed. Should be called at the 44 | /// end of `Plugin::process`. 45 | /// [ptr]: `VstEvents*` the events to be processed. 46 | /// [return]: 1 if supported and processed OK. 47 | ProcessEvents, 48 | /// Deprecated. 49 | _SetTime, 50 | /// Deprecated. 51 | _TempoAt, 52 | /// Deprecated. 53 | _GetNumAutomatableParameters, 54 | /// Deprecated. 55 | _GetParameterQuantization, 56 | 57 | /// Notifies the host that the input/output setup has changed. This can allow the host to check 58 | /// numInputs/numOutputs or call `getSpeakerArrangement()`. 59 | /// [return]: 1 if supported. 60 | IOChanged, 61 | 62 | /// Deprecated. 63 | _NeedIdle, 64 | 65 | /// Request the host to resize the plugin window. 66 | /// [index]: new width. 67 | /// [value]: new height. 68 | SizeWindow, 69 | /// [return]: the current sample rate. 70 | GetSampleRate, 71 | /// [return]: the current block size. 72 | GetBlockSize, 73 | /// [return]: the input latency in samples. 74 | GetInputLatency, 75 | /// [return]: the output latency in samples. 76 | GetOutputLatency, 77 | 78 | /// Deprecated. 79 | _GetPreviousPlug, 80 | /// Deprecated. 81 | _GetNextPlug, 82 | /// Deprecated. 83 | _WillReplaceOrAccumulate, 84 | 85 | /// [return]: the current process level, see `VstProcessLevels` 86 | GetCurrentProcessLevel, 87 | /// [return]: the current automation state, see `VstAutomationStates` 88 | GetAutomationState, 89 | 90 | /// The plugin is ready to begin offline processing. 91 | /// [index]: number of new audio files. 92 | /// [value]: number of audio files. 93 | /// [ptr]: `AudioFile*` the host audio files. Flags can be updated from plugin. 94 | OfflineStart, 95 | /// Called by the plugin to read data. 96 | /// [index]: (bool) 97 | /// VST offline processing allows a plugin to overwrite existing files. If this value is 98 | /// true then the host will read the original file's samples, but if it is false it will 99 | /// read the samples which the plugin has written via `OfflineWrite` 100 | /// [value]: see `OfflineOption` 101 | /// [ptr]: `OfflineTask*` describing the task. 102 | /// [return]: 1 on success 103 | OfflineRead, 104 | /// Called by the plugin to write data. 105 | /// [value]: see `OfflineOption` 106 | /// [ptr]: `OfflineTask*` describing the task. 107 | OfflineWrite, 108 | /// Unknown. Used in offline processing. 109 | OfflineGetCurrentPass, 110 | /// Unknown. Used in offline processing. 111 | OfflineGetCurrentMetaPass, 112 | 113 | /// Deprecated. 114 | _SetOutputSampleRate, 115 | /// Deprecated. 116 | _GetOutputSpeakerArrangement, 117 | 118 | /// Get the vendor string. 119 | /// [ptr]: `char*` for vendor string, limited to `MAX_VENDOR_STR_LEN`. 120 | GetVendorString, 121 | /// Get the product string. 122 | /// [ptr]: `char*` for vendor string, limited to `MAX_PRODUCT_STR_LEN`. 123 | GetProductString, 124 | /// [return]: vendor-specific version 125 | GetVendorVersion, 126 | /// Vendor specific handling. 127 | VendorSpecific, 128 | 129 | /// Deprecated. 130 | _SetIcon, 131 | 132 | /// Check if the host supports a feature. 133 | /// [ptr]: `char*` can do string 134 | /// [return]: 1 if supported 135 | CanDo, 136 | /// Get the language of the host. 137 | /// [return]: `VstHostLanguage` 138 | GetLanguage, 139 | 140 | /// Deprecated. 141 | _OpenWindow, 142 | /// Deprecated. 143 | _CloseWindow, 144 | 145 | /// Get the current directory. 146 | /// [return]: `FSSpec` on OS X, `char*` otherwise 147 | GetDirectory, 148 | /// No arguments. TODO: Figure out what this does. 149 | UpdateDisplay, 150 | /// Tell the host that if needed, it should record automation data for a control. 151 | /// 152 | /// Typically called when the plugin editor begins changing a control. 153 | /// 154 | /// [index]: index of the control. 155 | /// [return]: true on success. 156 | BeginEdit, 157 | /// A control is no longer being changed. 158 | /// 159 | /// Typically called after the plugin editor is done. 160 | /// 161 | /// [index]: index of the control. 162 | /// [return]: true on success. 163 | EndEdit, 164 | /// Open the host file selector. 165 | /// [ptr]: `VstFileSelect*` 166 | /// [return]: true on success. 167 | OpenFileSelector, 168 | /// Close the host file selector. 169 | /// [ptr]: `VstFileSelect*` 170 | /// [return]: true on success. 171 | CloseFileSelector, 172 | 173 | /// Deprecated. 174 | _EditFile, 175 | /// Deprecated. 176 | /// [ptr]: char[2048] or sizeof (FSSpec). 177 | /// [return]: 1 if supported. 178 | _GetChunkFile, 179 | /// Deprecated. 180 | _GetInputSpeakerArrangement 181 | } 182 | impl_clike!(OpCode); 183 | 184 | /// Implemented by all VST hosts. 185 | #[allow(unused_variables)] 186 | pub trait Host { 187 | /// Automate a parameter; the value has been changed. 188 | fn automate(&mut self, index: i32, value: f32) {} 189 | 190 | /// Get the plugin ID of the currently loading plugin. 191 | /// 192 | /// This is only useful for shell plugins where this value will change the plugin returned. 193 | /// `TODO: implement shell plugins` 194 | fn get_plugin_id(&self) -> i32 { 195 | // TODO: Handle this properly 196 | 0 197 | } 198 | 199 | /// An idle call. 200 | /// 201 | /// This is useful when the plugin is doing something such as mouse tracking in the UI. 202 | fn idle(&self) {} 203 | 204 | /// Get vendor and product information. 205 | /// 206 | /// Returns a tuple in the form of `(version, vendor_name, product_name)`. 207 | fn get_info(&self) -> (isize, String, String) { 208 | (1, "vendor string".to_owned(), "product string".to_owned()) 209 | } 210 | 211 | /// Handle incoming events from the plugin. 212 | fn process_events(&mut self, events: Vec) {} 213 | } 214 | 215 | /// All possible errors that can occur when loading a VST plugin. 216 | #[derive(Debug)] 217 | pub enum PluginLoadError { 218 | /// Could not load given path. 219 | InvalidPath, 220 | 221 | /// Given path is not a VST plugin. 222 | NotAPlugin, 223 | 224 | /// Failed to create an instance of this plugin. 225 | /// 226 | /// This can happen for many reasons, such as if the plugin requires a different version of 227 | /// the VST API to be used, or due to improper licensing. 228 | InstanceFailed, 229 | 230 | /// The API version which the plugin used is not supported by this library. 231 | InvalidApiVersion, 232 | } 233 | 234 | impl fmt::Display for PluginLoadError { 235 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 236 | write!(f, "{}", self.description()) 237 | } 238 | } 239 | 240 | impl Error for PluginLoadError { 241 | fn description(&self) -> &str { 242 | use self::PluginLoadError::*; 243 | 244 | match *self { 245 | InvalidPath => "Could not open the requested path", 246 | NotAPlugin => "The given path does not contain a VST2.4 compatible library", 247 | InstanceFailed => "Failed to create a plugin instance", 248 | InvalidApiVersion => "The plugin API version is not compatible with this library" 249 | } 250 | } 251 | } 252 | 253 | /// Wrapper for an externally loaded VST plugin. 254 | /// 255 | /// The only functionality this struct provides is loading plugins, which can be done via the 256 | /// [`load`](#method.load) method. 257 | pub struct PluginLoader { 258 | main: PluginMain, 259 | lib: Arc, 260 | host: Arc>, 261 | } 262 | 263 | /// An instance of an externally loaded VST plugin. 264 | #[allow(dead_code)] // To keep `lib` around. 265 | pub struct PluginInstance { 266 | effect: *mut AEffect, 267 | lib: Arc, 268 | info: Info, 269 | } 270 | 271 | impl Drop for PluginInstance { 272 | fn drop(&mut self) { 273 | self.dispatch(plugin::OpCode::Shutdown, 0, 0, ptr::null_mut(), 0.0); 274 | } 275 | } 276 | 277 | 278 | impl PluginLoader { 279 | /// Load a plugin at the given path with the given host. 280 | /// 281 | /// Because of the possibility of multi-threading problems that can occur when using plugins, 282 | /// the host must be passed in via an `Arc>` object. This makes sure that even if the 283 | /// plugins are multi-threaded no data race issues can occur. 284 | /// 285 | /// Upon success, this method returns a [`PluginLoader`](.) object which you can use to call 286 | /// [`instance`](#method.instance) to create a new instance of the plugin. 287 | /// 288 | /// # Example 289 | /// 290 | /// ```no_run 291 | /// # use std::path::Path; 292 | /// # use std::sync::{Arc, Mutex}; 293 | /// # use vst2::host::{Host, PluginLoader}; 294 | /// # let path = Path::new("."); 295 | /// # struct MyHost; 296 | /// # impl MyHost { fn new() -> MyHost { MyHost } } 297 | /// # impl Host for MyHost { 298 | /// # fn automate(&mut self, _: i32, _: f32) {} 299 | /// # fn get_plugin_id(&self) -> i32 { 0 } 300 | /// # } 301 | /// // ... 302 | /// let host = Arc::new(Mutex::new(MyHost::new())); 303 | /// 304 | /// let mut plugin = PluginLoader::load(path, host.clone()).unwrap(); 305 | /// 306 | /// let instance = plugin.instance().unwrap(); 307 | /// // ... 308 | /// ``` 309 | /// 310 | /// # Linux/Windows 311 | /// * This should be a path to the library, typically ending in `.so`/`.dll`. 312 | /// * Possible full path: `/home/overdrivenpotato/.vst/u-he/Zebra2.64.so` 313 | /// * Possible full path: `C:\Program Files (x86)\VSTPlugins\iZotope Ozone 5.dll` 314 | /// 315 | /// # OS X 316 | /// * This should point to the mach-o file within the `.vst` bundle. 317 | /// * Plugin: `/Library/Audio/Plug-Ins/VST/iZotope Ozone 5.vst` 318 | /// * Possible full path: 319 | /// `/Library/Audio/Plug-Ins/VST/iZotope Ozone 5.vst/Contents/MacOS/PluginHooksVST` 320 | pub fn load(path: &Path, host: Arc>) -> Result, PluginLoadError> { 321 | // Try loading the library at the given path 322 | let lib = match Library::new(path) { 323 | Ok(l) => l, 324 | Err(_) => return Err(PluginLoadError::InvalidPath) 325 | }; 326 | 327 | Ok(PluginLoader { 328 | main: unsafe { 329 | // Search the library for the VSTAPI entry point 330 | match lib.get(b"VSTPluginMain") { 331 | Ok(s) => *s, 332 | _ => return Err(PluginLoadError::NotAPlugin) 333 | } 334 | }, 335 | lib: Arc::new(lib), 336 | host: host, 337 | }) 338 | } 339 | 340 | /// Call the VST entry point and retrieve a (possibly null) pointer. 341 | unsafe fn call_main(&mut self) -> *mut AEffect { 342 | LOAD_POINTER = Box::into_raw(Box::new(self.host.clone())) as *mut c_void; 343 | (self.main)(callback_wrapper::) 344 | } 345 | 346 | /// Try to create an instance of this VST plugin. 347 | /// 348 | /// If the instance is successfully created, a [`PluginInstance`](struct.PluginInstance.html) 349 | /// is returned. This struct implements the [`Plugin` trait](../plugin/trait.Plugin.html). 350 | pub fn instance(&mut self) -> Result { 351 | // Call the plugin main function. This also passes the plugin main function as the closure 352 | // could not return an error if the symbol wasn't found 353 | let effect = unsafe { self.call_main() }; 354 | 355 | if effect.is_null() { 356 | return Err(PluginLoadError::InstanceFailed); 357 | } 358 | 359 | unsafe { 360 | // Move the host to the heap and add it to the `AEffect` struct for future reference 361 | (*effect).reserved1 = Box::into_raw(Box::new(self.host.clone())) as isize; 362 | } 363 | 364 | let instance = PluginInstance::new( 365 | effect, 366 | self.lib.clone() 367 | ); 368 | 369 | let api_ver = instance.dispatch(plugin::OpCode::GetApiVersion, 0, 0, ptr::null_mut(), 0.0); 370 | if api_ver >= 2400 { 371 | Ok(instance) 372 | } else { 373 | trace!("Could not load plugin with api version {}", api_ver); 374 | Err(PluginLoadError::InvalidApiVersion) 375 | } 376 | } 377 | } 378 | 379 | impl PluginInstance { 380 | fn new(effect: *mut AEffect, lib: Arc) -> PluginInstance { 381 | use plugin::OpCode as op; 382 | 383 | let mut plug = PluginInstance { 384 | effect: effect, 385 | lib: lib, 386 | info: Default::default() 387 | }; 388 | 389 | unsafe { 390 | use api::flags::*; 391 | 392 | let effect: &AEffect = &*effect; 393 | let flags = Plugin::from_bits_truncate(effect.flags); 394 | 395 | plug.info = Info { 396 | name: plug.read_string(op::GetProductName, MAX_PRODUCT_STR_LEN), 397 | vendor: plug.read_string(op::GetVendorName, MAX_VENDOR_STR_LEN), 398 | 399 | presets: effect.numPrograms, 400 | parameters: effect.numParams, 401 | inputs: effect.numInputs, 402 | outputs: effect.numOutputs, 403 | 404 | unique_id: effect.uniqueId, 405 | version: effect.version, 406 | 407 | category: Category::from(plug.opcode(op::GetCategory)), 408 | 409 | initial_delay: effect.initialDelay, 410 | 411 | preset_chunks: flags.intersects(PROGRAM_CHUNKS), 412 | f64_precision: flags.intersects(CAN_DOUBLE_REPLACING), 413 | silent_when_stopped: flags.intersects(NO_SOUND_IN_STOP), 414 | }; 415 | } 416 | 417 | plug 418 | } 419 | 420 | /// Send a dispatch message to the plugin. 421 | fn dispatch(&self, 422 | opcode: plugin::OpCode, 423 | index: i32, 424 | value: isize, 425 | ptr: *mut c_void, 426 | opt: f32) 427 | -> isize { 428 | let dispatcher = unsafe { 429 | (*self.effect).dispatcher 430 | }; 431 | if (dispatcher as *mut u8).is_null() { 432 | panic!("Plugin was not loaded correctly."); 433 | } 434 | dispatcher(self.effect, opcode.into(), index, value, ptr, opt) 435 | } 436 | 437 | /// Send a lone opcode with no parameters. 438 | fn opcode(&self, opcode: plugin::OpCode) -> isize { 439 | self.dispatch(opcode, 0, 0, ptr::null_mut(), 0.0) 440 | } 441 | 442 | /// Like `dispatch`, except takes a `&str` to send via `ptr`. 443 | fn write_string(&self, 444 | opcode: plugin::OpCode, 445 | index: i32, 446 | value: isize, 447 | string: &str, 448 | opt: f32) 449 | -> isize { 450 | let string = CString::new(string).expect("Invalid string data"); 451 | self.dispatch(opcode, index, value, string.as_bytes().as_ptr() as *mut c_void, opt) 452 | } 453 | 454 | fn read_string(&self, opcode: plugin::OpCode, max: usize) -> String { 455 | self.read_string_param(opcode, 0, 0, 0.0, max) 456 | } 457 | 458 | fn read_string_param(&self, 459 | opcode: plugin::OpCode, 460 | index: i32, 461 | value: isize, 462 | opt: f32, 463 | max: usize) 464 | -> String { 465 | let mut buf = vec![0; max]; 466 | self.dispatch(opcode, index, value, buf.as_mut_ptr() as *mut c_void, opt); 467 | String::from_utf8_lossy(&buf).chars().take_while(|c| *c != '\0').collect() 468 | } 469 | } 470 | 471 | impl Plugin for PluginInstance { 472 | fn get_info(&self) -> plugin::Info { 473 | self.info.clone() 474 | } 475 | 476 | fn init(&mut self) { 477 | self.opcode(plugin::OpCode::Initialize); 478 | } 479 | 480 | 481 | fn change_preset(&mut self, preset: i32) { 482 | self.dispatch(plugin::OpCode::ChangePreset, 0, preset as isize, ptr::null_mut(), 0.0); 483 | } 484 | 485 | fn get_preset_num(&self) -> i32 { 486 | self.opcode(plugin::OpCode::GetCurrentPresetNum) as i32 487 | } 488 | 489 | fn set_preset_name(&mut self, name: String) { 490 | self.write_string(plugin::OpCode::SetCurrentPresetName, 0, 0, &name, 0.0); 491 | } 492 | 493 | fn get_preset_name(&self, preset: i32) -> String { 494 | self.read_string_param(plugin::OpCode::GetPresetName, preset, 0, 0.0, MAX_PRESET_NAME_LEN) 495 | } 496 | 497 | 498 | fn get_parameter_label(&self, index: i32) -> String { 499 | self.read_string_param(plugin::OpCode::GetParameterLabel, index, 0, 0.0, MAX_PARAM_STR_LEN) 500 | } 501 | 502 | fn get_parameter_text(&self, index: i32) -> String { 503 | self.read_string_param(plugin::OpCode::GetParameterDisplay, index, 0, 0.0, MAX_PARAM_STR_LEN) 504 | } 505 | 506 | fn get_parameter_name(&self, index: i32) -> String { 507 | self.read_string_param(plugin::OpCode::GetParameterName, index, 0, 0.0, MAX_PARAM_STR_LEN) 508 | } 509 | 510 | fn get_parameter(&self, index: i32) -> f32 { 511 | unsafe { 512 | ((*self.effect).getParameter)(self.effect, index) 513 | } 514 | } 515 | 516 | fn set_parameter(&mut self, index: i32, value: f32) { 517 | unsafe { 518 | ((*self.effect).setParameter)(self.effect, index, value) 519 | } 520 | } 521 | 522 | fn can_be_automated(&self, index: i32) -> bool { 523 | self.dispatch(plugin::OpCode::CanBeAutomated, index, 0, ptr::null_mut(), 0.0) > 0 524 | } 525 | 526 | fn string_to_parameter(&mut self, index: i32, text: String) -> bool { 527 | self.write_string(plugin::OpCode::StringToParameter, index, 0, &text, 0.0) > 0 528 | } 529 | 530 | 531 | fn set_sample_rate(&mut self, rate: f32) { 532 | self.dispatch(plugin::OpCode::SetSampleRate, 0, 0, ptr::null_mut(), rate); 533 | } 534 | 535 | fn set_block_size(&mut self, size: i64) { 536 | self.dispatch(plugin::OpCode::SetBlockSize, 0, size as isize, ptr::null_mut(), 0.0); 537 | } 538 | 539 | 540 | fn resume(&mut self) { 541 | self.dispatch(plugin::OpCode::StateChanged, 0, 1, ptr::null_mut(), 0.0); 542 | } 543 | 544 | fn suspend(&mut self) { 545 | self.dispatch(plugin::OpCode::StateChanged, 0, 0, ptr::null_mut(), 0.0); 546 | } 547 | 548 | 549 | fn vendor_specific(&mut self, index: i32, value: isize, ptr: *mut c_void, opt: f32) -> isize { 550 | self.dispatch(plugin::OpCode::VendorSpecific, index, value, ptr, opt) 551 | } 552 | 553 | 554 | fn can_do(&self, can_do: plugin::CanDo) -> Supported { 555 | let s: String = can_do.into(); 556 | Supported::from( 557 | self.write_string(plugin::OpCode::CanDo, 0, 0, &s, 0.0) 558 | ).expect("Invalid response received when querying plugin CanDo") 559 | } 560 | 561 | fn get_tail_size(&self) -> isize { 562 | self.opcode(plugin::OpCode::GetTailSize) 563 | } 564 | 565 | 566 | fn process(&mut self, buffer: AudioBuffer) { 567 | let (inputs, outputs) = buffer.split(); 568 | let frames = inputs[0].len(); 569 | let mut inputs: Vec<*mut f32> = inputs.into_iter().map(|s| s.as_mut_ptr()).collect(); 570 | let mut outputs: Vec<*mut f32> = outputs.into_iter().map(|s| s.as_mut_ptr()).collect(); 571 | 572 | unsafe { 573 | ((*self.effect).processReplacing) 574 | (self.effect, inputs.as_mut_ptr(), outputs.as_mut_ptr(), frames as i32) 575 | } 576 | } 577 | 578 | fn process_f64(&mut self, buffer: AudioBuffer) { 579 | let (inputs, outputs) = buffer.split(); 580 | let frames = inputs[0].len(); 581 | let mut inputs: Vec<*mut f64> = inputs.into_iter().map(|s| s.as_mut_ptr()).collect(); 582 | let mut outputs: Vec<*mut f64> = outputs.into_iter().map(|s| s.as_mut_ptr()).collect(); 583 | 584 | unsafe { 585 | ((*self.effect).processReplacingF64) 586 | (self.effect, inputs.as_mut_ptr(), outputs.as_mut_ptr(), frames as i32) 587 | } 588 | } 589 | 590 | fn process_events(&mut self, events: Vec) { 591 | interfaces::process_events( 592 | events, 593 | |ptr| { 594 | self.dispatch( 595 | plugin::OpCode::ProcessEvents, 596 | 0, 597 | 0, 598 | ptr, 599 | 0.0 600 | ); 601 | } 602 | ); 603 | } 604 | 605 | // TODO: Editor 606 | 607 | fn get_preset_data(&mut self) -> Vec { 608 | // Create a pointer that can be updated from the plugin. 609 | let mut ptr: *mut u8 = ptr::null_mut(); 610 | let len = self.dispatch(plugin::OpCode::GetData, 611 | 1 /*preset*/, 0, 612 | &mut ptr as *mut *mut u8 as *mut c_void, 0.0); 613 | let slice = unsafe { slice::from_raw_parts(ptr, len as usize) }; 614 | slice.to_vec() 615 | } 616 | 617 | fn get_bank_data(&mut self) -> Vec { 618 | // Create a pointer that can be updated from the plugin. 619 | let mut ptr: *mut u8 = ptr::null_mut(); 620 | let len = self.dispatch(plugin::OpCode::GetData, 621 | 0 /*bank*/, 0, 622 | &mut ptr as *mut *mut u8 as *mut c_void, 0.0); 623 | let slice = unsafe { slice::from_raw_parts(ptr, len as usize) }; 624 | slice.to_vec() 625 | } 626 | 627 | fn load_preset_data(&mut self, data: &[u8]) { 628 | self.dispatch(plugin::OpCode::SetData, 1, data.len() as isize, 629 | data.as_ptr() as *mut c_void, 0.0); 630 | } 631 | 632 | fn load_bank_data(&mut self, data: &[u8]) { 633 | self.dispatch(plugin::OpCode::SetData, 0, data.len() as isize, 634 | data.as_ptr() as *mut c_void, 0.0); 635 | } 636 | 637 | fn get_input_info(&self, input: i32) -> ChannelInfo { 638 | let mut props = unsafe { mem::uninitialized() }; 639 | let ptr = &mut props as *mut api::ChannelProperties as *mut c_void; 640 | 641 | self.dispatch(plugin::OpCode::GetInputInfo, input, 0, ptr, 0.0); 642 | 643 | ChannelInfo::from(props) 644 | } 645 | 646 | fn get_output_info(&self, output: i32) -> ChannelInfo { 647 | let mut props = unsafe { mem::uninitialized() }; 648 | let ptr = &mut props as *mut api::ChannelProperties as *mut c_void; 649 | 650 | self.dispatch(plugin::OpCode::GetOutputInfo, output, 0, ptr, 0.0); 651 | 652 | ChannelInfo::from(props) 653 | } 654 | } 655 | 656 | /// HACK: a pointer to store the host so that it can be accessed from the `callback_wrapper` 657 | /// function passed to the plugin. 658 | /// 659 | /// When the plugin is being loaded, a `Box>>` is transmuted to a *mut c_void pointer 660 | /// and placed here. When the plugin calls the callback during initialization, the host refers to 661 | /// this pointer to get a handle to the Host. After initialization, this pointer is invalidated and 662 | /// the host pointer is placed into a [reserved field] in the instance `AEffect` struct. 663 | /// 664 | /// The issue with this approach is that if 2 plugins are simultaneously loaded with 2 different 665 | /// host instances, this might fail as one host may receive a pointer to the other one. In practice 666 | /// this is a rare situation as you normally won't have 2 seperate host instances loading at once. 667 | /// 668 | /// [reserved field]: ../api/struct.AEffect.html#structfield.reserved1 669 | static mut LOAD_POINTER: *mut c_void = 0 as *mut c_void; 670 | 671 | /// Function passed to plugin to handle dispatching host opcodes. 672 | fn callback_wrapper(effect: *mut AEffect, opcode: i32, index: i32, 673 | value: isize, ptr: *mut c_void, opt: f32) -> isize { 674 | unsafe { 675 | // If the effect pointer is not null and the host pointer is not null, the plugin has 676 | // already been initialized 677 | if !effect.is_null() && (*effect).reserved1 != 0 { 678 | let reserved = (*effect).reserved1 as *const Arc>; 679 | let host = &*reserved; 680 | 681 | let host = &mut *host.lock().unwrap(); 682 | 683 | interfaces::host_dispatch(host, effect, opcode, index, value, ptr, opt) 684 | // In this case, the plugin is still undergoing initialization and so `LOAD_POINTER` is 685 | // dereferenced 686 | } else { 687 | // Used only during the plugin initialization 688 | let host = LOAD_POINTER as *const Arc>; 689 | let host = &*host; 690 | let host = &mut *host.lock().unwrap(); 691 | 692 | interfaces::host_dispatch(host, effect, opcode, index, value, ptr, opt) 693 | } 694 | } 695 | } 696 | -------------------------------------------------------------------------------- /src/interfaces.rs: -------------------------------------------------------------------------------- 1 | //! Function interfaces for VST 2.4 API. 2 | 3 | #![doc(hidden)] 4 | 5 | use std::{mem, slice}; 6 | 7 | use std::os::raw::{c_char, c_void}; 8 | 9 | use buffer::AudioBuffer; 10 | use api::consts::*; 11 | use api::{self, AEffect, ChannelProperties}; 12 | use editor::{Rect, KeyCode, Key, KnobMode}; 13 | use host::Host; 14 | use event::Event; 15 | 16 | /// Deprecated process function. 17 | pub fn process_deprecated(_effect: *mut AEffect, _inputs_raw: *mut *mut f32, _outputs_raw: *mut *mut f32, _samples: i32) { } 18 | 19 | /// VST2.4 replacing function. 20 | pub fn process_replacing(effect: *mut AEffect, inputs_raw: *mut *mut f32, outputs_raw: *mut *mut f32, samples: i32) { 21 | // Handle to the vst 22 | let mut plugin = unsafe { (*effect).get_plugin() }; 23 | 24 | let buffer = unsafe { 25 | AudioBuffer::from_raw(inputs_raw, 26 | outputs_raw, 27 | plugin.get_info().inputs as usize, 28 | plugin.get_info().outputs as usize, 29 | samples as usize) 30 | }; 31 | 32 | plugin.process(buffer); 33 | } 34 | 35 | /// VST2.4 replacing function with `f64` values. 36 | pub fn process_replacing_f64(effect: *mut AEffect, inputs_raw: *mut *mut f64, outputs_raw: *mut *mut f64, samples: i32) { 37 | let mut plugin = unsafe { (*effect).get_plugin() }; 38 | 39 | if plugin.get_info().f64_precision { 40 | let buffer = unsafe { 41 | AudioBuffer::from_raw(inputs_raw, 42 | outputs_raw, 43 | plugin.get_info().inputs as usize, 44 | plugin.get_info().outputs as usize, 45 | samples as usize) 46 | }; 47 | 48 | plugin.process_f64(buffer); 49 | } 50 | } 51 | 52 | /// VST2.4 set parameter function. 53 | pub fn set_parameter(effect: *mut AEffect, index: i32, value: f32) { 54 | unsafe { (*effect).get_plugin() }.set_parameter(index, value); 55 | } 56 | 57 | /// VST2.4 get parameter function. 58 | pub fn get_parameter(effect: *mut AEffect, index: i32) -> f32 { 59 | unsafe { (*effect).get_plugin() }.get_parameter(index) 60 | } 61 | 62 | /// Copy a string into a destination buffer. 63 | /// 64 | /// String will be cut at `max` characters. 65 | fn copy_string(dst: *mut c_void, src: &str, max: usize) { 66 | unsafe { 67 | use std::cmp::min; 68 | use libc::{c_void, memset, memcpy}; 69 | 70 | let dst = dst as *mut c_void; 71 | memset(dst, 0, max); 72 | memcpy(dst, src.as_ptr() as *const c_void, min(max, src.len())); 73 | } 74 | } 75 | 76 | /// VST2.4 dispatch function. This function handles dispatching all opcodes to the vst plugin. 77 | pub fn dispatch(effect: *mut AEffect, opcode: i32, index: i32, value: isize, ptr: *mut c_void, opt: f32) -> isize { 78 | use plugin::{CanDo, OpCode}; 79 | 80 | // Convert passed in opcode to enum 81 | let opcode = OpCode::from(opcode); 82 | // Plugin handle 83 | let mut plugin = unsafe { (*effect).get_plugin() }; 84 | 85 | match opcode { 86 | OpCode::Initialize => plugin.init(), 87 | OpCode::Shutdown => unsafe { 88 | (*effect).drop_plugin(); 89 | drop(mem::transmute::<*mut AEffect, Box>(effect)); 90 | }, 91 | 92 | OpCode::ChangePreset => plugin.change_preset(value as i32), 93 | OpCode::GetCurrentPresetNum => return plugin.get_preset_num() as isize, 94 | OpCode::SetCurrentPresetName => plugin.set_preset_name(read_string(ptr)), 95 | OpCode::GetCurrentPresetName => { 96 | let num = plugin.get_preset_num(); 97 | copy_string(ptr, &plugin.get_preset_name(num), MAX_PRESET_NAME_LEN); 98 | } 99 | 100 | OpCode::GetParameterLabel => copy_string(ptr, &plugin.get_parameter_label(index), MAX_PARAM_STR_LEN), 101 | OpCode::GetParameterDisplay => copy_string(ptr, &plugin.get_parameter_text(index), MAX_PARAM_STR_LEN), 102 | OpCode::GetParameterName => copy_string(ptr, &plugin.get_parameter_name(index), MAX_PARAM_STR_LEN), 103 | 104 | OpCode::SetSampleRate => plugin.set_sample_rate(opt), 105 | OpCode::SetBlockSize => plugin.set_block_size(value as i64), 106 | OpCode::StateChanged => { 107 | if value == 1 { 108 | plugin.resume(); 109 | } else { 110 | plugin.suspend(); 111 | } 112 | } 113 | 114 | OpCode::EditorGetRect => { 115 | if let Some(editor) = plugin.get_editor() { 116 | let size = editor.size(); 117 | let pos = editor.position(); 118 | 119 | unsafe { 120 | // Given a Rect** structure 121 | // TODO: Investigate whether we are given a valid Rect** pointer already 122 | *(ptr as *mut *mut c_void) = 123 | mem::transmute(Box::new(Rect { 124 | left: pos.0 as i16, // x coord of position 125 | top: pos.1 as i16, // y coord of position 126 | right: (pos.0 + size.0) as i16, // x coord of pos + x coord of size 127 | bottom: (pos.1 + size.1) as i16 // y coord of pos + y coord of size 128 | })); 129 | } 130 | } 131 | } 132 | OpCode::EditorOpen => { 133 | if let Some(editor) = plugin.get_editor() { 134 | editor.open(ptr); //ptr is raw window handle, eg HWND* on windows 135 | } 136 | } 137 | OpCode::EditorClose => { 138 | if let Some(editor) = plugin.get_editor() { 139 | editor.close(); 140 | } 141 | } 142 | 143 | OpCode::EditorIdle => { 144 | if let Some(editor) = plugin.get_editor() { 145 | editor.idle(); 146 | } 147 | } 148 | 149 | OpCode::GetData => { 150 | let chunks = if index == 0 { 151 | plugin.get_bank_data() 152 | } else { 153 | plugin.get_preset_data() 154 | }; 155 | 156 | let len = chunks.len() as isize; 157 | 158 | // u8 array to **void ptr 159 | // TODO: Release the allocated memory for the chunk in resume / suspend event 160 | unsafe { 161 | *(ptr as *mut *mut c_void) = 162 | chunks.into_boxed_slice().as_ptr() as *mut c_void; 163 | } 164 | 165 | return len; 166 | } 167 | OpCode::SetData => { 168 | let chunks = unsafe { slice::from_raw_parts(ptr as *mut u8, value as usize) }; 169 | if index == 0 { 170 | plugin.load_bank_data(chunks); 171 | } else { 172 | plugin.load_preset_data(chunks); 173 | } 174 | } 175 | 176 | OpCode::ProcessEvents => { 177 | let events: *const api::Events = ptr as *const api::Events; 178 | 179 | let events: Vec = unsafe { 180 | // Create a slice of type &mut [*mut Event] 181 | slice::from_raw_parts(&(*events).events[0], (*events).num_events as usize) 182 | // Deref and clone each event to get a slice 183 | .iter().map(|item| Event::from(**item)).collect() 184 | }; 185 | 186 | plugin.process_events(events); 187 | } 188 | OpCode::CanBeAutomated => return plugin.can_be_automated(index) as isize, 189 | OpCode::StringToParameter => return plugin.string_to_parameter(index, read_string(ptr)) as isize, 190 | 191 | OpCode::GetPresetName => copy_string(ptr, &plugin.get_preset_name(index), MAX_PRESET_NAME_LEN), 192 | 193 | OpCode::GetInputInfo => { 194 | if index >= 0 && index < plugin.get_info().inputs { 195 | unsafe { 196 | let ptr = mem::transmute::<_, *mut ChannelProperties>(ptr); 197 | *ptr = plugin.get_input_info(index).into(); 198 | } 199 | } 200 | } 201 | OpCode::GetOutputInfo => { 202 | if index >= 0 && index < plugin.get_info().outputs { 203 | unsafe { 204 | let ptr = mem::transmute::<_, *mut ChannelProperties>(ptr); 205 | *ptr = plugin.get_output_info(index).into(); 206 | } 207 | } 208 | } 209 | OpCode::GetCategory => { 210 | return plugin.get_info().category.into(); 211 | } 212 | 213 | OpCode::GetVendorName => copy_string(ptr, &plugin.get_info().vendor, MAX_VENDOR_STR_LEN), 214 | OpCode::GetProductName => copy_string(ptr, &plugin.get_info().name, MAX_PRODUCT_STR_LEN), 215 | OpCode::GetVendorVersion => return plugin.get_info().version as isize, 216 | OpCode::VendorSpecific => return plugin.vendor_specific(index, value, ptr, opt), 217 | OpCode::CanDo => { 218 | let can_do: CanDo = match read_string(ptr).parse() { 219 | Ok(c) => c, 220 | Err(e) => { warn!("{}", e); return 0; } 221 | }; 222 | return plugin.can_do(can_do).into(); 223 | } 224 | OpCode::GetTailSize => if plugin.get_tail_size() == 0 { return 1; } else { return plugin.get_tail_size() }, 225 | 226 | //OpCode::GetParamInfo => { /*TODO*/ } 227 | 228 | OpCode::GetApiVersion => return 2400, 229 | 230 | OpCode::EditorKeyDown => { 231 | if let Some(editor) = plugin.get_editor() { 232 | editor.key_down(KeyCode { 233 | character: index as u8 as char, 234 | key: Key::from(value), 235 | modifier: unsafe { mem::transmute::(opt) } as u8 236 | }); 237 | } 238 | } 239 | OpCode::EditorKeyUp => { 240 | if let Some(editor) = plugin.get_editor() { 241 | editor.key_up(KeyCode { 242 | character: index as u8 as char, 243 | key: Key::from(value), 244 | modifier: unsafe { mem::transmute::(opt) } as u8 245 | }); 246 | } 247 | } 248 | OpCode::EditorSetKnobMode => { 249 | if let Some(editor) = plugin.get_editor() { 250 | editor.set_knob_mode(KnobMode::from(value)); 251 | } 252 | } 253 | 254 | _ => { 255 | warn!("Unimplemented opcode ({:?})", opcode); 256 | trace!("Arguments; index: {}, value: {}, ptr: {:?}, opt: {}", index, value, ptr, opt); 257 | } 258 | } 259 | 260 | 0 261 | } 262 | 263 | pub fn host_dispatch(host: &mut Host, 264 | effect: *mut AEffect, 265 | opcode: i32, 266 | index: i32, 267 | value: isize, 268 | ptr: *mut c_void, 269 | opt: f32) -> isize { 270 | use host::OpCode; 271 | 272 | match OpCode::from(opcode) { 273 | OpCode::Version => return 2400, 274 | OpCode::Automate => host.automate(index, opt), 275 | 276 | OpCode::Idle => host.idle(), 277 | 278 | // ... 279 | 280 | OpCode::CanDo => { 281 | info!("Plugin is asking if host can: {}.", read_string(ptr)); 282 | } 283 | 284 | OpCode::GetVendorVersion => return host.get_info().0, 285 | OpCode::GetVendorString => copy_string(ptr, &host.get_info().1, MAX_VENDOR_STR_LEN), 286 | OpCode::GetProductString => copy_string(ptr, &host.get_info().2, MAX_PRODUCT_STR_LEN), 287 | OpCode::ProcessEvents => { 288 | let events: *const api::Events = ptr as *const api::Events; 289 | 290 | let events: Vec = unsafe { 291 | // Create a slice of type &mut [*mut Event] 292 | slice::from_raw_parts(&(*events).events[0], (*events).num_events as usize) 293 | // Deref and clone each event to get a slice 294 | .iter().map(|item| Event::from(**item)).collect() 295 | }; 296 | 297 | host.process_events(events); 298 | } 299 | 300 | unimplemented => { 301 | trace!("VST: Got unimplemented host opcode ({:?})", unimplemented); 302 | trace!("Arguments; effect: {:?}, index: {}, value: {}, ptr: {:?}, opt: {}", 303 | effect, index, value, ptr, opt); 304 | } 305 | } 306 | 0 307 | } 308 | 309 | // Read a string from the `ptr` buffer 310 | fn read_string(ptr: *mut c_void) -> String { 311 | use std::ffi::CStr; 312 | 313 | String::from_utf8_lossy( 314 | unsafe { CStr::from_ptr(ptr as *mut c_char).to_bytes() } 315 | ).into_owned() 316 | } 317 | 318 | /// Translate `Vec` into `&api::Events` and use via a callback. 319 | /// 320 | /// Both plugins and hosts can receive VST events, this simply translates the rust structure into 321 | /// the equivalent API structure and takes care of cleanup. 322 | pub fn process_events(events: Vec, callback: F) 323 | where F: FnOnce(*mut c_void) 324 | { 325 | use api::flags::REALTIME_EVENT; 326 | 327 | let len = events.len(); 328 | 329 | // The `api::Events` structure uses a variable length array which is difficult to represent in 330 | // rust. We begin by creating a vector with the appropriate byte size by calculating the header 331 | // and the variable length body seperately. 332 | let header_size = mem::size_of::() - (mem::size_of::<*mut api::Event>() * 2); 333 | let body_size = mem::size_of::<*mut api::Event>() * len; 334 | 335 | let mut send = vec![0u8; header_size + body_size]; 336 | 337 | let send_events: &mut [*mut api::Event] = unsafe { 338 | // The header is updated by casting the array to the `api::Events` type and specifying the 339 | // required fields. We create a slice from the position of the first event and the length 340 | // of the array. 341 | let ptr = send.as_mut_ptr() as *mut api::Events; 342 | (*ptr).num_events = len as i32; 343 | 344 | // A slice view of the body 345 | slice::from_raw_parts_mut(&mut (*ptr).events[0], len) 346 | }; 347 | 348 | // Each event is zipped with the target body array slot. Most of what's happening here is just 349 | // copying data but the key thing to notice is that each event is boxed and cast to 350 | // (*mut api::Event). This way we can let the callback handle the event, and then later create 351 | // the box again from the raw pointer so that it can be properly dropped. 352 | for (event, out) in events.iter().zip(send_events.iter_mut()) { 353 | *out = match *event { 354 | Event::Midi { data, delta_frames, live, 355 | note_length, note_offset, 356 | detune, note_off_velocity } => { 357 | Box::into_raw(Box::new(api::MidiEvent { 358 | event_type: api::EventType::Midi, 359 | byte_size: mem::size_of::() as i32, 360 | delta_frames: delta_frames, 361 | flags: if live { REALTIME_EVENT.bits() } else { 0 }, 362 | note_length: note_length.unwrap_or(0), 363 | note_offset: note_offset.unwrap_or(0), 364 | midi_data: data, 365 | _midi_reserved: 0, 366 | detune: detune, 367 | note_off_velocity: note_off_velocity, 368 | _reserved1: 0, 369 | _reserved2: 0 370 | })) as *mut api::Event 371 | } 372 | Event::SysEx { payload, delta_frames } => { 373 | Box::into_raw(Box::new(api::SysExEvent { 374 | event_type: api::EventType::SysEx, 375 | byte_size: mem::size_of::() as i32, 376 | delta_frames: delta_frames, 377 | _flags: 0, 378 | data_size: payload.len() as i32, 379 | _reserved1: 0, 380 | system_data: payload.as_ptr() as *const u8 as *mut u8, 381 | _reserved2: 0, 382 | })) as *mut api::Event 383 | } 384 | Event::Deprecated(e) => Box::into_raw(Box::new(e)) 385 | }; 386 | } 387 | 388 | // Allow the callback to use the pointer 389 | callback(send.as_mut_ptr() as *mut c_void); 390 | 391 | // Clean up the created events 392 | unsafe { 393 | for &mut event in send_events { 394 | match (*event).event_type { 395 | api::EventType::Midi => { 396 | drop(Box::from_raw(event as *mut api::MidiEvent)); 397 | } 398 | api::EventType::SysEx => { 399 | drop(Box::from_raw(event as *mut api::SysExEvent)); 400 | } 401 | _ => { 402 | drop(Box::from_raw(event)); 403 | } 404 | } 405 | } 406 | } 407 | } 408 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![warn(missing_docs)] 2 | 3 | //! rust-vst2 is a rust implementation of the VST2.4 API 4 | //! 5 | //! # Plugins 6 | //! All Plugins must implement the `Plugin` trait and `std::default::Default`. The `plugin_main!` 7 | //! macro must also be called in order to export the necessary functions for the plugin to function. 8 | //! 9 | //! ## `Plugin` Trait 10 | //! All methods in this trait have a default implementation except for the `get_info` method which 11 | //! must be implemented by the plugin. Any of the default implementations may be overriden for 12 | //! custom functionality; the defaults do nothing on their own. 13 | //! 14 | //! ## `plugin_main!` macro 15 | //! `plugin_main!` will export the necessary functions to create a proper VST plugin. This must be 16 | //! called with your VST plugin struct name in order for the vst to work. 17 | //! 18 | //! ## Example plugin 19 | //! A barebones VST plugin: 20 | //! 21 | //! ```no_run 22 | //! #[macro_use] 23 | //! extern crate vst2; 24 | //! 25 | //! use vst2::plugin::{Info, Plugin}; 26 | //! 27 | //! #[derive(Default)] 28 | //! struct BasicPlugin; 29 | //! 30 | //! impl Plugin for BasicPlugin { 31 | //! fn get_info(&self) -> Info { 32 | //! Info { 33 | //! name: "Basic Plugin".to_string(), 34 | //! unique_id: 1357, // Used by hosts to differentiate between plugins. 35 | //! 36 | //! ..Default::default() 37 | //! } 38 | //! } 39 | //! } 40 | //! 41 | //! plugin_main!(BasicPlugin); // Important! 42 | //! # fn main() {} // For `extern crate vst2` 43 | //! ``` 44 | //! 45 | //! # Hosts 46 | //! 47 | //! ## `Host` Trait 48 | //! All hosts must implement the [`Host` trait](host/trait.Host.html). To load a VST plugin, you 49 | //! need to wrap your host in an `Arc>` wrapper for thread safety reasons. Along with the 50 | //! plugin path, this can be passed to the [`PluginLoader::load`] method to create a plugin loader 51 | //! which can spawn plugin instances. 52 | //! 53 | //! ## Example Host 54 | //! ```no_run 55 | //! extern crate vst2; 56 | //! 57 | //! use std::sync::{Arc, Mutex}; 58 | //! use std::path::Path; 59 | //! 60 | //! use vst2::host::{Host, PluginLoader}; 61 | //! use vst2::plugin::Plugin; 62 | //! 63 | //! struct SampleHost; 64 | //! 65 | //! impl Host for SampleHost { 66 | //! fn automate(&mut self, index: i32, value: f32) { 67 | //! println!("Parameter {} had its value changed to {}", index, value); 68 | //! } 69 | //! } 70 | //! 71 | //! fn main() { 72 | //! let host = Arc::new(Mutex::new(SampleHost)); 73 | //! let path = Path::new("/path/to/vst"); 74 | //! 75 | //! let mut loader = PluginLoader::load(path, host.clone()).unwrap(); 76 | //! let mut instance = loader.instance().unwrap(); 77 | //! 78 | //! println!("Loaded {}", instance.get_info().name); 79 | //! 80 | //! instance.init(); 81 | //! println!("Initialized instance!"); 82 | //! 83 | //! println!("Closing instance..."); 84 | //! // Not necessary as the instance is shut down when it goes out of scope anyway. 85 | //! // drop(instance); 86 | //! } 87 | //! 88 | //! ``` 89 | //! 90 | //! [`PluginLoader::load`]: host/struct.PluginLoader.html#method.load 91 | //! 92 | 93 | extern crate libc; 94 | extern crate num_traits; 95 | extern crate libloading; 96 | #[macro_use] extern crate log; 97 | #[macro_use] extern crate bitflags; 98 | 99 | use std::{ptr, mem}; 100 | 101 | /// Implements `From` and `Into` for enums with `#[repr(usize)]`. Useful for interfacing with C 102 | /// enums. 103 | macro_rules! impl_clike { 104 | ($t:ty, $($c:ty) +) => { 105 | $( 106 | impl From<$c> for $t { 107 | fn from(v: $c) -> $t { 108 | use std::mem; 109 | unsafe { mem::transmute(v as usize) } 110 | } 111 | } 112 | 113 | impl Into<$c> for $t { 114 | fn into(self) -> $c { 115 | self as $c 116 | } 117 | } 118 | )* 119 | }; 120 | 121 | ($t:ty) => { 122 | impl_clike!($t, i8 i16 i32 i64 isize u8 u16 u32 u64 usize); 123 | } 124 | } 125 | 126 | pub mod buffer; 127 | pub mod api; 128 | pub mod editor; 129 | pub mod channels; 130 | pub mod event; 131 | pub mod host; 132 | pub mod plugin; 133 | mod interfaces; 134 | 135 | use api::{HostCallbackProc, AEffect}; 136 | use api::consts::VST_MAGIC; 137 | use plugin::{HostCallback, Plugin}; 138 | 139 | /// Exports the necessary symbols for the plugin to be used by a VST host. 140 | /// 141 | /// This macro takes a type which must implement the traits `plugin::Plugin` and 142 | /// `std::default::Default`. 143 | #[macro_export] 144 | macro_rules! plugin_main { 145 | ($t:ty) => { 146 | #[cfg(target_os = "macos")] 147 | #[no_mangle] 148 | pub extern "system" fn main_macho(callback: $crate::api::HostCallbackProc) -> *mut $crate::api::AEffect { 149 | VSTPluginMain(callback) 150 | } 151 | 152 | #[cfg(target_os = "windows")] 153 | #[allow(non_snake_case)] 154 | #[no_mangle] 155 | pub extern "system" fn MAIN(callback: $crate::api::HostCallbackProc) -> *mut $crate::api::AEffect { 156 | VSTPluginMain(callback) 157 | } 158 | 159 | #[allow(non_snake_case)] 160 | #[no_mangle] 161 | pub extern "C" fn VSTPluginMain(callback: $crate::api::HostCallbackProc) -> *mut $crate::api::AEffect { 162 | $crate::main::<$t>(callback) 163 | } 164 | } 165 | } 166 | 167 | /// Initializes a VST plugin and returns a raw pointer to an AEffect struct. 168 | #[doc(hidden)] 169 | pub fn main(callback: HostCallbackProc) -> *mut AEffect { 170 | // Create a Box containing a zeroed AEffect. This is transmuted into a *mut pointer so that it 171 | // can be passed into the HostCallback `wrap` method. The AEffect is then updated after the vst 172 | // object is created so that the host still contains a raw pointer to the AEffect struct. 173 | let effect = unsafe { mem::transmute(Box::new(mem::zeroed::())) }; 174 | 175 | let host = HostCallback::wrap(callback, effect); 176 | if host.vst_version() == 0 { // TODO: Better criteria would probably be useful here... 177 | return ptr::null_mut(); 178 | } 179 | 180 | trace!("Creating VST plugin instance..."); 181 | let mut plugin = T::new(host); 182 | let info = plugin.get_info().clone(); 183 | 184 | // Update AEffect in place 185 | unsafe { *effect = AEffect { 186 | magic: VST_MAGIC, 187 | dispatcher: interfaces::dispatch, // fn pointer 188 | 189 | _process: interfaces::process_deprecated, // fn pointer 190 | 191 | setParameter: interfaces::set_parameter, // fn pointer 192 | getParameter: interfaces::get_parameter, // fn pointer 193 | 194 | numPrograms: info.presets, 195 | numParams: info.parameters, 196 | numInputs: info.inputs, 197 | numOutputs: info.outputs, 198 | 199 | flags: { 200 | use api::flags::*; 201 | 202 | let mut flag = CAN_REPLACING; 203 | 204 | if info.f64_precision { 205 | flag = flag | CAN_DOUBLE_REPLACING; 206 | } 207 | 208 | if plugin.get_editor().is_some() { 209 | flag = flag | HAS_EDITOR; 210 | } 211 | 212 | if info.preset_chunks { 213 | flag = flag | PROGRAM_CHUNKS; 214 | } 215 | 216 | if let plugin::Category::Synth = info.category { 217 | flag = flag | IS_SYNTH; 218 | } 219 | 220 | if info.silent_when_stopped { 221 | flag = flag | NO_SOUND_IN_STOP; 222 | } 223 | 224 | flag.bits() 225 | }, 226 | 227 | reserved1: 0, 228 | reserved2: 0, 229 | 230 | initialDelay: info.initial_delay, 231 | 232 | _realQualities: 0, 233 | _offQualities: 0, 234 | _ioRatio: 0.0, 235 | 236 | object: mem::transmute(Box::new(Box::new(plugin) as Box)), 237 | user: ptr::null_mut(), 238 | 239 | uniqueId: info.unique_id, 240 | version: info.version, 241 | 242 | processReplacing: interfaces::process_replacing, // fn pointer 243 | processReplacingF64: interfaces::process_replacing_f64, //fn pointer 244 | 245 | future: [0u8; 56] 246 | }}; 247 | effect 248 | } 249 | 250 | #[cfg(test)] 251 | #[allow(private_no_mangle_fns)] // For `plugin_main!` 252 | mod tests { 253 | use std::ptr; 254 | 255 | use std::os::raw::c_void; 256 | 257 | use interfaces; 258 | use api::AEffect; 259 | use api::consts::VST_MAGIC; 260 | use plugin::{Info, Plugin}; 261 | 262 | #[derive(Default)] 263 | struct TestPlugin; 264 | 265 | impl Plugin for TestPlugin { 266 | fn get_info(&self) -> Info { 267 | Info { 268 | name: "Test Plugin".to_string(), 269 | vendor: "overdrivenpotato".to_string(), 270 | 271 | presets: 1, 272 | parameters: 1, 273 | 274 | unique_id: 5678, 275 | version: 1234, 276 | 277 | initial_delay: 123, 278 | 279 | ..Default::default() 280 | } 281 | } 282 | } 283 | 284 | plugin_main!(TestPlugin); 285 | 286 | fn pass_callback(_effect: *mut AEffect, _opcode: i32, _index: i32, _value: isize, _ptr: *mut c_void, _opt: f32) -> isize { 287 | 1 288 | } 289 | 290 | fn fail_callback(_effect: *mut AEffect, _opcode: i32, _index: i32, _value: isize, _ptr: *mut c_void, _opt: f32) -> isize { 291 | 0 292 | } 293 | 294 | #[cfg(target_os = "windows")] 295 | #[test] 296 | fn old_hosts() { 297 | assert_eq!(MAIN(fail_callback), ptr::null_mut()); 298 | } 299 | 300 | #[cfg(target_os = "macos")] 301 | #[test] 302 | fn old_hosts() { 303 | assert_eq!(main_macho(fail_callback), ptr::null_mut()); 304 | } 305 | 306 | #[test] 307 | fn host_callback() { 308 | assert_eq!(VSTPluginMain(fail_callback), ptr::null_mut()); 309 | } 310 | 311 | #[test] 312 | fn aeffect_created() { 313 | let aeffect = VSTPluginMain(pass_callback); 314 | assert!(!aeffect.is_null()); 315 | } 316 | 317 | #[test] 318 | fn plugin_drop() { 319 | static mut drop_test: bool = false; 320 | 321 | impl Drop for TestPlugin { 322 | fn drop(&mut self) { 323 | unsafe { drop_test = true; } 324 | } 325 | } 326 | 327 | let aeffect = VSTPluginMain(pass_callback); 328 | assert!(!aeffect.is_null()); 329 | 330 | unsafe { (*aeffect).drop_plugin() }; 331 | 332 | // Assert that the VST is shut down and dropped. 333 | assert!(unsafe { drop_test }); 334 | } 335 | 336 | #[test] 337 | fn plugin_no_drop() { 338 | let aeffect = VSTPluginMain(pass_callback); 339 | assert!(!aeffect.is_null()); 340 | 341 | // Make sure this doesn't crash. 342 | unsafe { (*aeffect).drop_plugin() }; 343 | } 344 | 345 | #[test] 346 | fn plugin_deref() { 347 | let aeffect = VSTPluginMain(pass_callback); 348 | assert!(!aeffect.is_null()); 349 | 350 | let plugin = unsafe { (*aeffect).get_plugin() }; 351 | // Assert that deref works correctly. 352 | assert!(plugin.get_info().name == "Test Plugin"); 353 | } 354 | 355 | #[test] 356 | fn aeffect_params() { 357 | // Assert that 2 function pointers are equal. 358 | macro_rules! assert_fn_eq { 359 | ($a:expr, $b:expr) => { 360 | assert_eq!($a as usize, $b as usize); 361 | } 362 | } 363 | 364 | let aeffect = unsafe { &mut *VSTPluginMain(pass_callback) }; 365 | 366 | assert_eq!(aeffect.magic, VST_MAGIC); 367 | assert_fn_eq!(aeffect.dispatcher, interfaces::dispatch); 368 | assert_fn_eq!(aeffect._process, interfaces::process_deprecated); 369 | assert_fn_eq!(aeffect.setParameter, interfaces::set_parameter); 370 | assert_fn_eq!(aeffect.getParameter, interfaces::get_parameter); 371 | assert_eq!(aeffect.numPrograms, 1); 372 | assert_eq!(aeffect.numParams, 1); 373 | assert_eq!(aeffect.numInputs, 2); 374 | assert_eq!(aeffect.numOutputs, 2); 375 | assert_eq!(aeffect.reserved1, 0); 376 | assert_eq!(aeffect.reserved2, 0); 377 | assert_eq!(aeffect.initialDelay, 123); 378 | assert_eq!(aeffect.uniqueId, 5678); 379 | assert_eq!(aeffect.version, 1234); 380 | assert_fn_eq!(aeffect.processReplacing, interfaces::process_replacing); 381 | assert_fn_eq!(aeffect.processReplacingF64, interfaces::process_replacing_f64); 382 | } 383 | } 384 | -------------------------------------------------------------------------------- /src/plugin.rs: -------------------------------------------------------------------------------- 1 | //! Plugin specific structures. 2 | 3 | use std::{mem, ptr}; 4 | 5 | use std::os::raw::c_void; 6 | 7 | use channels::ChannelInfo; 8 | use host::{self, Host}; 9 | use api::{AEffect, HostCallbackProc, Supported}; 10 | use api::consts::VST_MAGIC; 11 | use buffer::AudioBuffer; 12 | use editor::Editor; 13 | use event::Event; 14 | 15 | /// Plugin type. Generally either Effect or Synth. 16 | /// 17 | /// Other types are not necessary to build a plugin and are only useful for the host to categorize 18 | /// the plugin. 19 | #[repr(usize)] 20 | #[derive(Clone, Copy, Debug)] 21 | pub enum Category { 22 | /// Unknown / not implemented 23 | Unknown, 24 | /// Any effect 25 | Effect, 26 | /// VST instrument 27 | Synth, 28 | /// Scope, tuner, spectrum analyser, etc. 29 | Analysis, 30 | /// Dynamics, etc. 31 | Mastering, 32 | /// Panners, etc. 33 | Spacializer, 34 | /// Delays and Reverbs 35 | RoomFx, 36 | /// Dedicated surround processor. 37 | SurroundFx, 38 | /// Denoiser, etc. 39 | Restoration, 40 | /// Offline processing. 41 | OfflineProcess, 42 | /// Contains other plugins. 43 | Shell, 44 | /// Tone generator, etc. 45 | Generator 46 | } 47 | impl_clike!(Category); 48 | 49 | #[repr(usize)] 50 | #[derive(Clone, Copy, Debug)] 51 | #[doc(hidden)] 52 | pub enum OpCode { 53 | /// Called when plugin is initialized. 54 | Initialize, 55 | /// Called when plugin is being shut down. 56 | Shutdown, 57 | 58 | /// [value]: preset number to change to. 59 | ChangePreset, 60 | /// [return]: current preset number. 61 | GetCurrentPresetNum, 62 | /// [ptr]: char array with new preset name, limited to `consts::MAX_PRESET_NAME_LEN`. 63 | SetCurrentPresetName, 64 | /// [ptr]: char buffer for current preset name, limited to `consts::MAX_PRESET_NAME_LEN`. 65 | GetCurrentPresetName, 66 | 67 | /// [index]: parameter 68 | /// [ptr]: char buffer, limited to `consts::MAX_PARAM_STR_LEN` (e.g. "db", "ms", etc) 69 | GetParameterLabel, 70 | /// [index]: paramter 71 | /// [ptr]: char buffer, limited to `consts::MAX_PARAM_STR_LEN` (e.g. "0.5", "ROOM", etc). 72 | GetParameterDisplay, 73 | /// [index]: parameter 74 | /// [ptr]: char buffer, limited to `consts::MAX_PARAM_STR_LEN` (e.g. "Release", "Gain") 75 | GetParameterName, 76 | 77 | /// Deprecated. 78 | _GetVu, 79 | 80 | /// [opt]: new sample rate. 81 | SetSampleRate, 82 | /// [value]: new maximum block size. 83 | SetBlockSize, 84 | /// [value]: 1 when plugin enabled, 0 when disabled. 85 | StateChanged, 86 | 87 | /// [ptr]: Rect** receiving pointer to editor size. 88 | EditorGetRect, 89 | /// [ptr]: system dependent window pointer, eg HWND on Windows. 90 | EditorOpen, 91 | /// Close editor. No arguments. 92 | EditorClose, 93 | 94 | /// Deprecated. 95 | _EditorDraw, 96 | /// Deprecated. 97 | _EditorMouse, 98 | /// Deprecated. 99 | _EditorKey, 100 | 101 | /// Idle call from host. 102 | EditorIdle, 103 | 104 | /// Deprecated. 105 | _EditorTop, 106 | /// Deprecated. 107 | _EditorSleep, 108 | /// Deprecated. 109 | _EditorIdentify, 110 | 111 | /// [ptr]: pointer for chunk data address (void**). 112 | /// [index]: 0 for bank, 1 for program 113 | GetData, 114 | /// [ptr]: data (void*) 115 | /// [value]: data size in bytes 116 | /// [index]: 0 for bank, 1 for program 117 | SetData, 118 | 119 | /// [ptr]: VstEvents* TODO: Events 120 | ProcessEvents, 121 | /// [index]: param index 122 | /// [return]: 1=true, 0=false 123 | CanBeAutomated, 124 | /// [index]: param index 125 | /// [ptr]: parameter string 126 | /// [return]: true for success 127 | StringToParameter, 128 | 129 | /// Deprecated. 130 | _GetNumCategories, 131 | 132 | /// [index]: program name 133 | /// [ptr]: char buffer for name, limited to `consts::MAX_PRESET_NAME_LEN` 134 | /// [return]: true for success 135 | GetPresetName, 136 | 137 | /// Deprecated. 138 | _CopyPreset, 139 | /// Deprecated. 140 | _ConnectIn, 141 | /// Deprecated. 142 | _ConnectOut, 143 | 144 | /// [index]: input index 145 | /// [ptr]: `VstPinProperties` 146 | /// [return]: 1 if supported 147 | GetInputInfo, 148 | /// [index]: output index 149 | /// [ptr]: `VstPinProperties` 150 | /// [return]: 1 if supported 151 | GetOutputInfo, 152 | /// [return]: `PluginCategory` category. 153 | GetCategory, 154 | 155 | /// Deprecated. 156 | _GetCurrentPosition, 157 | /// Deprecated. 158 | _GetDestinationBuffer, 159 | 160 | /// [ptr]: `VstAudioFile` array 161 | /// [value]: count 162 | /// [index]: start flag 163 | OfflineNotify, 164 | /// [ptr]: `VstOfflineTask` array 165 | /// [value]: count 166 | OfflinePrepare, 167 | /// [ptr]: `VstOfflineTask` array 168 | /// [value]: count 169 | OfflineRun, 170 | 171 | /// [ptr]: `VstVariableIo` 172 | /// [use]: used for variable I/O processing (offline e.g. timestretching) 173 | ProcessVarIo, 174 | /// TODO: implement 175 | /// [value]: input `*mut VstSpeakerArrangement`. 176 | /// [ptr]: output `*mut VstSpeakerArrangement`. 177 | SetSpeakerArrangement, 178 | 179 | /// Deprecated. 180 | _SetBlocksizeAndSampleRate, 181 | 182 | /// Soft bypass (automatable). 183 | /// [value]: 1 = bypass, 0 = nobypass. 184 | SoftBypass, 185 | // [ptr]: buffer for effect name, limited to `kVstMaxEffectNameLen` 186 | GetEffectName, 187 | 188 | /// Deprecated. 189 | _GetErrorText, 190 | 191 | /// [ptr]: buffer for vendor name, limited to `consts::MAX_VENDOR_STR_LEN`. 192 | GetVendorName, 193 | /// [ptr]: buffer for product name, limited to `consts::MAX_PRODUCT_STR_LEN`. 194 | GetProductName, 195 | /// [return]: vendor specific version. 196 | GetVendorVersion, 197 | /// no definition, vendor specific. 198 | VendorSpecific, 199 | /// [ptr]: "Can do" string. 200 | /// [return]: 1 = yes, 0 = maybe, -1 = no. 201 | CanDo, 202 | /// [return]: tail size (e.g. reverb time). 0 is defualt, 1 means no tail. 203 | GetTailSize, 204 | 205 | /// Deprecated. 206 | _Idle, 207 | /// Deprecated. 208 | _GetIcon, 209 | /// Deprecated. 210 | _SetVewPosition, 211 | 212 | /// [index]: param index 213 | /// [ptr]: `*mut VstParamInfo` //TODO: Implement 214 | /// [return]: 1 if supported 215 | GetParamInfo, 216 | 217 | /// Deprecated. 218 | _KeysRequired, 219 | 220 | /// [return]: 2400 for vst 2.4. 221 | GetApiVersion, 222 | 223 | /// [index]: ASCII char. 224 | /// [value]: `Key` keycode. 225 | /// [opt]: `flags::modifier_key` bitmask. 226 | /// [return]: 1 if used. 227 | EditorKeyDown, 228 | /// [index]: ASCII char. 229 | /// [value]: `Key` keycode. 230 | /// [opt]: `flags::modifier_key` bitmask. 231 | /// [return]: 1 if used. 232 | EditorKeyUp, 233 | /// [value]: 0 = circular, 1 = circular relative, 2 = linear. 234 | EditorSetKnobMode, 235 | 236 | /// [index]: MIDI channel. 237 | /// [ptr]: `*mut MidiProgramName`. //TODO: Implement 238 | /// [return]: number of used programs, 0 = unsupported. 239 | GetMidiProgramName, 240 | /// [index]: MIDI channel. 241 | /// [ptr]: `*mut MidiProgramName`. //TODO: Implement 242 | /// [return]: index of current program. 243 | GetCurrentMidiProgram, 244 | /// [index]: MIDI channel. 245 | /// [ptr]: `*mut MidiProgramCategory`. //TODO: Implement 246 | /// [return]: number of used categories. 247 | GetMidiProgramCategory, 248 | /// [index]: MIDI channel. 249 | /// [return]: 1 if `MidiProgramName` or `MidiKeyName` has changed. //TODO: Implement 250 | HasMidiProgramsChanged, 251 | /// [index]: MIDI channel. 252 | /// [ptr]: `*mut MidiKeyName`. //TODO: Implement 253 | /// [return]: 1 = supported 0 = not. 254 | GetMidiKeyName, 255 | 256 | /// Called before a preset is loaded. 257 | BeginSetPreset, 258 | /// Called after a preset is loaded. 259 | EndSetPreset, 260 | 261 | /// [value]: inputs `*mut VstSpeakerArrangement` //TODO: Implement 262 | /// [ptr]: Outputs `*mut VstSpeakerArrangement` 263 | GetSpeakerArrangement, 264 | /// [ptr]: buffer for plugin name, limited to `consts::MAX_PRODUCT_STR_LEN`. 265 | /// [return]: next plugin's uniqueID. 266 | ShellGetNextPlugin, 267 | 268 | /// No args. Called once before start of process call. This indicates that the process call 269 | /// will be interrupted (e.g. Host reconfiguration or bypass when plugin doesn't support 270 | /// SoftBypass) 271 | StartProcess, 272 | /// No arguments. Called after stop of process call. 273 | StopProcess, 274 | /// [value]: number of samples to process. Called in offline mode before process. 275 | SetTotalSampleToProcess, 276 | /// [value]: pan law `PanLaw`. //TODO: Implement 277 | /// [opt]: gain. 278 | SetPanLaw, 279 | 280 | /// [ptr]: `*mut VstPatchChunkInfo`. //TODO: Implement 281 | /// [return]: -1 = bank cant be loaded, 1 = can be loaded, 0 = unsupported. 282 | BeginLoadBank, 283 | /// [ptr]: `*mut VstPatchChunkInfo`. //TODO: Implement 284 | /// [return]: -1 = bank cant be loaded, 1 = can be loaded, 0 = unsupported. 285 | BeginLoadPreset, 286 | 287 | /// [value]: 0 if 32 bit, anything else if 64 bit. 288 | SetPrecision, 289 | 290 | /// [return]: number of used MIDI Inputs (1-15). 291 | GetNumMidiInputs, 292 | /// [return]: number of used MIDI Outputs (1-15). 293 | GetNumMidiOutputs, 294 | } 295 | impl_clike!(OpCode); 296 | 297 | /// A structure representing static plugin information. 298 | #[derive(Clone, Debug)] 299 | pub struct Info { 300 | /// Plugin Name. 301 | pub name: String, 302 | 303 | /// Plugin Vendor. 304 | pub vendor: String, 305 | 306 | 307 | /// Number of different presets. 308 | pub presets: i32, 309 | 310 | /// Number of parameters. 311 | pub parameters: i32, 312 | 313 | 314 | /// Number of inputs. 315 | pub inputs: i32, 316 | 317 | /// Number of outputs. 318 | pub outputs: i32, 319 | 320 | 321 | /// Unique plugin ID. Can be registered with Steinberg to prevent conflicts with other plugins. 322 | /// 323 | /// This ID is used to identify a plugin during save and load of a preset and project. 324 | pub unique_id: i32, 325 | 326 | /// Plugin version (e.g. 0001 = `v0.0.0.1`, 1283 = `v1.2.8.3`). 327 | pub version: i32, 328 | 329 | /// Plugin category. Possible values are found in `enums::PluginCategory`. 330 | pub category: Category, 331 | 332 | /// Latency of the plugin in samples. 333 | /// 334 | /// This reports how many samples it takes for the plugin to create an output (group delay). 335 | pub initial_delay: i32, 336 | 337 | /// Indicates that preset data is handled in formatless chunks. 338 | /// 339 | /// If false, host saves and restores plugin by reading/writing parameter data. If true, it is 340 | /// up to the plugin to manage saving preset data by implementing the 341 | /// `{get, load}_{preset, bank}_chunks()` methods. Default is `false`. 342 | pub preset_chunks: bool, 343 | 344 | /// Indicates whether this plugin can process f64 based `AudioBuffer` buffers. 345 | /// 346 | /// Default is `true`. 347 | pub f64_precision: bool, 348 | 349 | /// If this is true, the plugin will not produce sound when the input is silence. 350 | /// 351 | /// Default is `false`. 352 | pub silent_when_stopped: bool, 353 | } 354 | 355 | impl Default for Info { 356 | fn default() -> Info { 357 | Info { 358 | name: "VST".to_string(), 359 | vendor: String::new(), 360 | 361 | presets: 1, // default preset 362 | parameters: 0, 363 | inputs: 2, // Stereo in,out 364 | outputs: 2, 365 | 366 | unique_id: 0, // This must be changed. 367 | version: 0001, // v0.0.0.1 368 | 369 | category: Category::Effect, 370 | 371 | initial_delay: 0, 372 | 373 | preset_chunks: false, 374 | f64_precision: true, 375 | silent_when_stopped: false, 376 | } 377 | } 378 | } 379 | 380 | /// Features which are optionally supported by a plugin. These are queried by the host at run time. 381 | #[derive(Debug)] 382 | #[allow(missing_docs)] 383 | pub enum CanDo { 384 | SendEvents, 385 | SendMidiEvent, 386 | ReceiveEvents, 387 | ReceiveMidiEvent, 388 | ReceiveTimeInfo, 389 | Offline, 390 | MidiProgramNames, 391 | Bypass, 392 | ReceiveSysExEvent, 393 | 394 | //Bitwig specific? 395 | MidiSingleNoteTuningChange, 396 | MidiKeyBasedInstrumentControl, 397 | 398 | Other(String) 399 | } 400 | 401 | use std::str::FromStr; 402 | impl FromStr for CanDo { 403 | type Err = String; 404 | 405 | fn from_str(s: &str) -> Result { 406 | use self::CanDo::*; 407 | 408 | Ok(match s { 409 | "sendVstEvents" => SendEvents, 410 | "sendVstMidiEvent" => SendMidiEvent, 411 | "receiveVstEvents" => ReceiveEvents, 412 | "receiveVstMidiEvent" => ReceiveMidiEvent, 413 | "receiveVstTimeInfo" => ReceiveTimeInfo, 414 | "offline" => Offline, 415 | "midiProgramNames" => MidiProgramNames, 416 | "bypass" => Bypass, 417 | 418 | "receiveVstSysexEvent" => ReceiveSysExEvent, 419 | "midiSingleNoteTuningChange" => MidiSingleNoteTuningChange, 420 | "midiKeyBasedInstrumentControl" => MidiKeyBasedInstrumentControl, 421 | otherwise => Other(otherwise.to_string()) 422 | }) 423 | } 424 | } 425 | 426 | impl Into for CanDo { 427 | fn into(self) -> String { 428 | use self::CanDo::*; 429 | 430 | match self { 431 | SendEvents => "sendVstEvents".to_string(), 432 | SendMidiEvent => "sendVstMidiEvent".to_string(), 433 | ReceiveEvents => "receiveVstEvents".to_string(), 434 | ReceiveMidiEvent => "receiveVstMidiEvent".to_string(), 435 | ReceiveTimeInfo => "receiveVstTimeInfo".to_string(), 436 | Offline => "offline".to_string(), 437 | MidiProgramNames => "midiProgramNames".to_string(), 438 | Bypass => "bypass".to_string(), 439 | 440 | ReceiveSysExEvent => "receiveVstSysexEvent".to_string(), 441 | MidiSingleNoteTuningChange => "midiSingleNoteTuningChange".to_string(), 442 | MidiKeyBasedInstrumentControl => "midiKeyBasedInstrumentControl".to_string(), 443 | Other(other) => other 444 | } 445 | } 446 | 447 | } 448 | 449 | /// Must be implemented by all VST plugins. 450 | /// 451 | /// All methods except `get_info` provide a default implementation which does nothing and can be 452 | /// safely overridden. 453 | #[allow(unused_variables)] 454 | pub trait Plugin { 455 | /// This method must return an `Info` struct. 456 | fn get_info(&self) -> Info; 457 | 458 | /// Called during initialization to pass a `HostCallback` to the plugin. 459 | /// 460 | /// This method can be overriden to set `host` as a field in the plugin struct. 461 | /// 462 | /// # Example 463 | /// 464 | /// ``` 465 | /// // ... 466 | /// # extern crate vst2; 467 | /// # #[macro_use] extern crate log; 468 | /// # use vst2::plugin::{Plugin, Info}; 469 | /// use vst2::plugin::HostCallback; 470 | /// 471 | /// # #[derive(Default)] 472 | /// struct ExamplePlugin { 473 | /// host: HostCallback 474 | /// } 475 | /// 476 | /// impl Plugin for ExamplePlugin { 477 | /// fn new(host: HostCallback) -> ExamplePlugin { 478 | /// ExamplePlugin { 479 | /// host: host 480 | /// } 481 | /// } 482 | /// 483 | /// fn init(&mut self) { 484 | /// info!("loaded with host vst version: {}", self.host.vst_version()); 485 | /// } 486 | /// 487 | /// // ... 488 | /// # fn get_info(&self) -> Info { 489 | /// # Info { 490 | /// # name: "Example Plugin".to_string(), 491 | /// # ..Default::default() 492 | /// # } 493 | /// # } 494 | /// } 495 | /// 496 | /// # fn main() {} 497 | /// ``` 498 | fn new(host: HostCallback) -> Self where Self: Sized + Default { 499 | Default::default() 500 | } 501 | 502 | /// Called when plugin is fully initialized. 503 | fn init(&mut self) { trace!("Initialized vst plugin."); } 504 | 505 | 506 | /// Set the current preset to the index specified by `preset`. 507 | fn change_preset(&mut self, preset: i32) { } 508 | 509 | /// Get the current preset index. 510 | fn get_preset_num(&self) -> i32 { 0 } 511 | 512 | /// Set the current preset name. 513 | fn set_preset_name(&mut self, name: String) { } 514 | 515 | /// Get the name of the preset at the index specified by `preset`. 516 | fn get_preset_name(&self, preset: i32) -> String { "".to_string() } 517 | 518 | 519 | /// Get parameter label for parameter at `index` (e.g. "db", "sec", "ms", "%"). 520 | fn get_parameter_label(&self, index: i32) -> String { "".to_string() } 521 | 522 | /// Get the parameter value for parameter at `index` (e.g. "1.0", "150", "Plate", "Off"). 523 | fn get_parameter_text(&self, index: i32) -> String { 524 | format!("{:.3}", self.get_parameter(index)) 525 | } 526 | 527 | /// Get the name of parameter at `index`. 528 | fn get_parameter_name(&self, index: i32) -> String { format!("Param {}", index) } 529 | 530 | /// Get the value of paramater at `index`. Should be value between 0.0 and 1.0. 531 | fn get_parameter(&self, index: i32) -> f32 { 0.0 } 532 | 533 | /// Set the value of parameter at `index`. `value` is between 0.0 and 1.0. 534 | fn set_parameter(&mut self, index: i32, value: f32) { } 535 | 536 | /// Return whether parameter at `index` can be automated. 537 | fn can_be_automated(&self, index: i32) -> bool { false } 538 | 539 | /// Use String as input for parameter value. Used by host to provide an editable field to 540 | /// adjust a parameter value. E.g. "100" may be interpreted as 100hz for parameter. Returns if 541 | /// the input string was used. 542 | fn string_to_parameter(&mut self, index: i32, text: String) -> bool { false } 543 | 544 | 545 | /// Called when sample rate is changed by host. 546 | fn set_sample_rate(&mut self, rate: f32) { } 547 | 548 | /// Called when block size is changed by host. 549 | fn set_block_size(&mut self, size: i64) { } 550 | 551 | 552 | /// Called when plugin is turned on. 553 | fn resume(&mut self) { } 554 | 555 | /// Called when plugin is turned off. 556 | fn suspend(&mut self) { } 557 | 558 | 559 | /// Vendor specific handling. 560 | fn vendor_specific(&mut self, index: i32, value: isize, ptr: *mut c_void, opt: f32) -> isize { 0 } 561 | 562 | 563 | /// Return whether plugin supports specified action. 564 | fn can_do(&self, can_do: CanDo) -> Supported { 565 | info!("Host is asking if plugin can: {:?}.", can_do); 566 | Supported::Maybe 567 | } 568 | 569 | /// Get the tail size of plugin when it is stopped. Used in offline processing as well. 570 | fn get_tail_size(&self) -> isize { 0 } 571 | 572 | 573 | /// Process an audio buffer containing `f32` values. 574 | /// 575 | /// # Example 576 | /// ```no_run 577 | /// # use vst2::plugin::{Info, Plugin}; 578 | /// # use vst2::buffer::AudioBuffer; 579 | /// # 580 | /// # struct ExamplePlugin; 581 | /// # impl Plugin for ExamplePlugin { 582 | /// # fn get_info(&self) -> Info { Default::default() } 583 | /// # 584 | /// // Processor that clips samples above 0.4 or below -0.4: 585 | /// fn process(&mut self, buffer: AudioBuffer){ 586 | /// let (inputs, mut outputs) = buffer.split(); 587 | /// 588 | /// for (channel, ibuf) in inputs.iter().enumerate() { 589 | /// for (i, sample) in ibuf.iter().enumerate() { 590 | /// outputs[channel][i] = if *sample > 0.4 { 591 | /// 0.4 592 | /// } else if *sample < -0.4 { 593 | /// -0.4 594 | /// } else { 595 | /// *sample 596 | /// }; 597 | /// } 598 | /// } 599 | /// } 600 | /// # } 601 | /// ``` 602 | fn process(&mut self, buffer: AudioBuffer) { 603 | // For each input and output 604 | for (input, output) in buffer.zip() { 605 | // For each input sample and output sample in buffer 606 | for (in_frame, out_frame) in input.into_iter().zip(output.into_iter()) { 607 | *out_frame = *in_frame; 608 | } 609 | } 610 | } 611 | 612 | /// Process an audio buffer containing `f64` values. 613 | /// 614 | /// # Example 615 | /// ```no_run 616 | /// # use vst2::plugin::{Info, Plugin}; 617 | /// # use vst2::buffer::AudioBuffer; 618 | /// # 619 | /// # struct ExamplePlugin; 620 | /// # impl Plugin for ExamplePlugin { 621 | /// # fn get_info(&self) -> Info { Default::default() } 622 | /// # 623 | /// // Processor that clips samples above 0.4 or below -0.4: 624 | /// fn process_f64(&mut self, buffer: AudioBuffer){ 625 | /// let (inputs, mut outputs) = buffer.split(); 626 | /// 627 | /// for (channel, ibuf) in inputs.iter().enumerate() { 628 | /// for (i, sample) in ibuf.iter().enumerate() { 629 | /// outputs[channel][i] = if *sample > 0.4 { 630 | /// 0.4 631 | /// } else if *sample < -0.4 { 632 | /// -0.4 633 | /// } else { 634 | /// *sample 635 | /// }; 636 | /// } 637 | /// } 638 | /// } 639 | /// # } 640 | /// ``` 641 | fn process_f64(&mut self, buffer: AudioBuffer) { 642 | // For each input and output 643 | for (input, output) in buffer.zip() { 644 | // For each input sample and output sample in buffer 645 | for (in_frame, out_frame) in input.into_iter().zip(output.into_iter()) { 646 | *out_frame = *in_frame; 647 | } 648 | } 649 | } 650 | 651 | /// Handle incoming events sent from the host. 652 | /// 653 | /// This is always called before the start of `process` or `process_f64`. 654 | fn process_events(&mut self, events: Vec) {} 655 | 656 | /// Return handle to plugin editor if supported. 657 | fn get_editor(&mut self) -> Option<&mut Editor> { None } 658 | 659 | 660 | /// If `preset_chunks` is set to true in plugin info, this should return the raw chunk data for 661 | /// the current preset. 662 | fn get_preset_data(&mut self) -> Vec { Vec::new() } 663 | 664 | /// If `preset_chunks` is set to true in plugin info, this should return the raw chunk data for 665 | /// the current plugin bank. 666 | fn get_bank_data(&mut self) -> Vec { Vec::new() } 667 | 668 | /// If `preset_chunks` is set to true in plugin info, this should load a preset from the given 669 | /// chunk data. 670 | fn load_preset_data(&mut self, data: &[u8]) {} 671 | 672 | /// If `preset_chunks` is set to true in plugin info, this should load a preset bank from the 673 | /// given chunk data. 674 | fn load_bank_data(&mut self, data: &[u8]) {} 675 | 676 | /// Get information about an input channel. Only used by some hosts. 677 | fn get_input_info(&self, input: i32) -> ChannelInfo { 678 | ChannelInfo::new(format!("Input channel {}", input), 679 | Some(format!("In {}", input)), 680 | true, None) 681 | } 682 | 683 | /// Get information about an output channel. Only used by some hosts. 684 | fn get_output_info(&self, output: i32) -> ChannelInfo { 685 | ChannelInfo::new(format!("Output channel {}", output), 686 | Some(format!("Out {}", output)), 687 | true, None) 688 | } 689 | } 690 | 691 | /// A reference to the host which allows the plugin to call back and access information. 692 | /// 693 | /// # Panics 694 | /// 695 | /// All methods in this struct will panic if the plugin has not yet been initialized. In practice, 696 | /// this can only occur if the plugin queries the host for information when `Default::default()` is 697 | /// called. 698 | /// 699 | /// ```should_panic 700 | /// # use vst2::plugin::{Info, Plugin, HostCallback}; 701 | /// struct ExamplePlugin; 702 | /// 703 | /// impl Default for ExamplePlugin { 704 | /// fn default() -> ExamplePlugin { 705 | /// // Will panic, don't do this. If needed, you can query 706 | /// // the host during initialization via Vst::new() 707 | /// let host: HostCallback = Default::default(); 708 | /// let version = host.vst_version(); 709 | /// 710 | /// // ... 711 | /// # ExamplePlugin 712 | /// } 713 | /// } 714 | /// # 715 | /// # impl Plugin for ExamplePlugin { 716 | /// # fn get_info(&self) -> Info { Default::default() } 717 | /// # } 718 | /// # fn main() { let plugin: ExamplePlugin = Default::default(); } 719 | /// ``` 720 | pub struct HostCallback { 721 | callback: Option, 722 | effect: *mut AEffect, 723 | } 724 | 725 | /// `HostCallback` implements `Default` so that the plugin can implement `Default` and have a 726 | /// `HostCallback` field. 727 | impl Default for HostCallback { 728 | fn default() -> HostCallback { 729 | HostCallback { 730 | callback: None, 731 | effect: ptr::null_mut(), 732 | } 733 | } 734 | } 735 | 736 | impl HostCallback { 737 | /// Wrap callback in a function to avoid using fn pointer notation. 738 | #[doc(hidden)] 739 | fn callback(&self, 740 | effect: *mut AEffect, 741 | opcode: host::OpCode, 742 | index: i32, 743 | value: isize, 744 | ptr: *mut c_void, 745 | opt: f32) 746 | -> isize { 747 | let callback = self.callback.unwrap_or_else(|| panic!("Host not yet initialized.")); 748 | callback(effect, opcode.into(), index, value, ptr, opt) 749 | } 750 | 751 | /// Check whether the plugin has been initialized. 752 | #[doc(hidden)] 753 | fn is_effect_valid(&self) -> bool { 754 | // Check whether `effect` points to a valid AEffect struct 755 | unsafe { *mem::transmute::<*mut AEffect, *mut i32>(self.effect) == VST_MAGIC } 756 | } 757 | 758 | /// Create a new Host structure wrapping a host callback. 759 | #[doc(hidden)] 760 | pub fn wrap(callback: HostCallbackProc, effect: *mut AEffect) -> HostCallback { 761 | HostCallback { 762 | callback: Some(callback), 763 | effect: effect, 764 | } 765 | } 766 | 767 | /// Get the VST API version supported by the host e.g. `2400 = VST 2.4`. 768 | pub fn vst_version(&self) -> i32 { 769 | self.callback(self.effect, host::OpCode::Version, 770 | 0, 0, ptr::null_mut(), 0.0) as i32 771 | } 772 | 773 | fn read_string(&self, opcode: host::OpCode, max: usize) -> String { 774 | self.read_string_param(opcode, 0, 0, 0.0, max) 775 | } 776 | 777 | fn read_string_param(&self, 778 | opcode: host::OpCode, 779 | index: i32, 780 | value: isize, 781 | opt: f32, 782 | max: usize) 783 | -> String { 784 | let mut buf = vec![0; max]; 785 | self.callback(self.effect, opcode, index, value, buf.as_mut_ptr() as *mut c_void, opt); 786 | String::from_utf8_lossy(&buf).chars().take_while(|c| *c != '\0').collect() 787 | } 788 | } 789 | 790 | impl Host for HostCallback { 791 | fn automate(&mut self, index: i32, value: f32) { 792 | if self.is_effect_valid() { // TODO: Investigate removing this check, should be up to host 793 | self.callback(self.effect, host::OpCode::Automate, 794 | index, 0, ptr::null_mut(), value); 795 | } 796 | } 797 | 798 | fn get_plugin_id(&self) -> i32 { 799 | self.callback(self.effect, host::OpCode::CurrentId, 800 | 0, 0, ptr::null_mut(), 0.0) as i32 801 | } 802 | 803 | fn idle(&self) { 804 | self.callback(self.effect, host::OpCode::Idle, 805 | 0, 0, ptr::null_mut(), 0.0); 806 | } 807 | 808 | fn get_info(&self) -> (isize, String, String) { 809 | use api::consts::*; 810 | let version = self.callback(self.effect, host::OpCode::CurrentId, 0, 0, ptr::null_mut(), 0.0) as isize; 811 | let vendor_name = self.read_string(host::OpCode::GetVendorString, MAX_VENDOR_STR_LEN); 812 | let product_name = self.read_string(host::OpCode::GetProductString, MAX_PRODUCT_STR_LEN); 813 | (version, vendor_name, product_name) 814 | } 815 | 816 | /// Send events to the host. 817 | /// 818 | /// This should only be called within [`process`] or [`process_f64`]. Calling `process_events` 819 | /// anywhere else is undefined behaviour and may crash some hosts. 820 | /// 821 | /// [`process`]: trait.Plugin.html#method.process 822 | /// [`process_f64`]: trait.Plugin.html#method.process_f64 823 | fn process_events(&mut self, events: Vec) { 824 | use interfaces; 825 | 826 | interfaces::process_events( 827 | events, 828 | |ptr| { 829 | self.callback( 830 | self.effect, 831 | host::OpCode::ProcessEvents, 832 | 0, 833 | 0, 834 | ptr, 835 | 0.0 836 | ); 837 | } 838 | ); 839 | } 840 | } 841 | 842 | #[cfg(test)] 843 | mod tests { 844 | use std::ptr; 845 | 846 | use plugin; 847 | 848 | /// Create a plugin instance. 849 | /// 850 | /// This is a macro to allow you to specify attributes on the created struct. 851 | macro_rules! make_plugin { 852 | ($($attr:meta) *) => { 853 | use std::os::raw::c_void; 854 | 855 | use main; 856 | use api::AEffect; 857 | use host::{Host, OpCode}; 858 | use plugin::{HostCallback, Info, Plugin}; 859 | 860 | $(#[$attr]) * 861 | struct TestPlugin { 862 | host: HostCallback 863 | } 864 | 865 | impl Plugin for TestPlugin { 866 | fn get_info(&self) -> Info { 867 | Info { 868 | name: "Test Plugin".to_string(), 869 | ..Default::default() 870 | } 871 | } 872 | 873 | fn new(host: HostCallback) -> TestPlugin { 874 | TestPlugin { 875 | host: host 876 | } 877 | } 878 | 879 | fn init(&mut self) { 880 | info!("Loaded with host vst version: {}", self.host.vst_version()); 881 | assert_eq!(2400, self.host.vst_version()); 882 | assert_eq!(9876, self.host.get_plugin_id()); 883 | // Callback will assert these. 884 | self.host.automate(123, 12.3); 885 | self.host.idle(); 886 | } 887 | } 888 | 889 | #[allow(dead_code)] 890 | fn instance() -> *mut AEffect { 891 | fn host_callback(_effect: *mut AEffect, 892 | opcode: i32, 893 | index: i32, 894 | _value: isize, 895 | _ptr: *mut c_void, 896 | opt: f32) 897 | -> isize { 898 | let opcode = OpCode::from(opcode); 899 | match opcode { 900 | OpCode::Automate => { 901 | assert_eq!(index, 123); 902 | assert_eq!(opt, 12.3); 903 | 0 904 | } 905 | OpCode::Version => 2400, 906 | OpCode::CurrentId => 9876, 907 | OpCode::Idle => 0, 908 | _ => 0 909 | } 910 | } 911 | 912 | main::(host_callback) 913 | } 914 | } 915 | } 916 | 917 | make_plugin!(derive(Default)); 918 | 919 | #[test] 920 | #[should_panic] 921 | fn null_panic() { 922 | make_plugin!(/* no `derive(Default)` */); 923 | 924 | impl Default for TestPlugin { 925 | fn default() -> TestPlugin { 926 | let plugin = TestPlugin { host: Default::default() }; 927 | 928 | // Should panic 929 | let version = plugin.host.vst_version(); 930 | info!("Loaded with host vst version: {}", version); 931 | 932 | plugin 933 | } 934 | } 935 | 936 | TestPlugin::default(); 937 | } 938 | 939 | #[test] 940 | fn host_callbacks() { 941 | let aeffect = instance(); 942 | (unsafe { (*aeffect).dispatcher })(aeffect, plugin::OpCode::Initialize.into(), 943 | 0, 0, ptr::null_mut(), 0.0); 944 | } 945 | } 946 | --------------------------------------------------------------------------------