├── .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 |
--------------------------------------------------------------------------------