From c19e6596bb9e524c2c0e06418b5e8c2b04d4a222 Mon Sep 17 00:00:00 2001 From: Mike Hilgendorf Date: Sat, 25 Apr 2020 00:50:17 -0700 Subject: [PATCH 01/11] pass over a lint --- com/macros/support/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/com/macros/support/src/lib.rs b/com/macros/support/src/lib.rs index 76727a5..31bc8ab 100644 --- a/com/macros/support/src/lib.rs +++ b/com/macros/support/src/lib.rs @@ -1,4 +1,5 @@ #![allow(clippy::ptr_arg)] +#![allow(clippy::implicit_hasher)] extern crate proc_macro; pub mod aggr_co_class; From e83af35834bcdd347c06ae09fc47c5c23ee4557c Mon Sep 17 00:00:00 2001 From: Mike Hilgendorf Date: Sat, 25 Apr 2020 15:43:46 -0700 Subject: [PATCH 02/11] added github actions --- .github/workflows/ci.yml | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..b1be839 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,38 @@ +on: + push: + branches: + - master + pull_request: + +jobs: + check: + runs-on: ubuntu-latest + name: formatting + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + toolchain: stable + profile: minimal + components: rustfmt, clippy + - uses: actions-rs/cargo@v1 + with: + command: fmt + args: --all -- --check + - uses: actions-rs/cargo@v1 + with: + command: clippy + args: --all-targets --all-features -- -D warnings + + test: + runs-on: [macos-latest, windows-latest, ubuntu-latest] + name: test + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v + with: + toolchain: stable + profile: minimal + - uses: actions-rs/cargo@v1 + with: + command: test + args: --all \ No newline at end of file From 926d8e3e2f43a165fc4376d04ab05ad5920457e6 Mon Sep 17 00:00:00 2001 From: Mike Hilgendorf Date: Sat, 25 Apr 2020 15:47:12 -0700 Subject: [PATCH 03/11] wrong syntax for OS --- .github/workflows/ci.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b1be839..57efae1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,8 +25,11 @@ jobs: args: --all-targets --all-features -- -D warnings test: - runs-on: [macos-latest, windows-latest, ubuntu-latest] + runs-on: ${{ matrix.os }} name: test + strategy: + matrix: + os: [macOS-latest, windows-2019, ubuntu-latest] - uses: actions/checkout@v2 - uses: actions-rs/toolchain@v with: From bfd9960a73a75adfd9042e6a02066230ef42b239 Mon Sep 17 00:00:00 2001 From: Mike Hilgendorf Date: Sat, 25 Apr 2020 15:49:18 -0700 Subject: [PATCH 04/11] missing steps --- .github/workflows/ci.yml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 57efae1..277f819 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,11 +25,9 @@ jobs: args: --all-targets --all-features -- -D warnings test: - runs-on: ${{ matrix.os }} + runs-on: [macOS-latest, windows-2019, ubuntu-latest] name: test - strategy: - matrix: - os: [macOS-latest, windows-2019, ubuntu-latest] + steps: - uses: actions/checkout@v2 - uses: actions-rs/toolchain@v with: From b1d740884875d1162a0875c5eb84796bb9b579f6 Mon Sep 17 00:00:00 2001 From: Mike Hilgendorf Date: Sat, 25 Apr 2020 15:52:38 -0700 Subject: [PATCH 05/11] removed travis --- .travis.yml | 25 ------------------------- 1 file changed, 25 deletions(-) delete mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index ccbbca0..0000000 --- a/.travis.yml +++ /dev/null @@ -1,25 +0,0 @@ -language: rust -jobs: - include: - - stage: test - os: linux - before_script: - - rustup toolchain install stable - - rustup component add --toolchain stable clippy-preview || cargo install --git https://github.com/rust-lang/rust-clippy/ --force clippy - - rustup component add rustfmt - script: - - cargo build --verbose - - cargo test --verbose - - cargo fmt --all -- --check - - cargo +stable clippy --all-targets --all-features -- -D warnings -A clippy::unreadable_literal -A clippy::needless_range_loop -A clippy::float_cmp -A clippy::comparison-chain -A clippy::needless-doctest-main -A clippy::missing-safety-doc - - stage: test - os: osx - before_script: - - rustup toolchain install stable - - rustup component add --toolchain stable clippy-preview || cargo install --git https://github.com/rust-lang/rust-clippy/ --force clippy - - rustup component add rustfmt - script: - - cargo build --verbose - - cargo test --verbose - - cargo fmt --all -- --check - - cargo +stable clippy --all-targets --all-features -- -D warnings -A clippy::unreadable_literal -A clippy::needless_range_loop -A clippy::float_cmp -A clippy::comparison-chain -A clippy::needless-doctest-main -A clippy::missing-safety-doc \ No newline at end of file From 85474fbd300407aadaf89048ce349079ff0017be Mon Sep 17 00:00:00 2001 From: Mike Hilgendorf Date: Sat, 25 Apr 2020 15:57:34 -0700 Subject: [PATCH 06/11] runner names --- .github/workflows/ci.yml | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 277f819..84ca129 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,25 +7,29 @@ on: jobs: check: runs-on: ubuntu-latest - name: formatting + name: formatting and linting steps: - - uses: actions/checkout@v2 - - uses: actions-rs/toolchain@v1 + - name: checkout + uses: actions/checkout@v2 + - name: provision + uses: actions-rs/toolchain@v1 with: toolchain: stable profile: minimal components: rustfmt, clippy - - uses: actions-rs/cargo@v1 + - name: cargo fmt + uses: actions-rs/cargo@v1 with: command: fmt args: --all -- --check - - uses: actions-rs/cargo@v1 + - name: cargo clippy + uses: actions-rs/cargo@v1 with: command: clippy args: --all-targets --all-features -- -D warnings test: - runs-on: [macOS-latest, windows-2019, ubuntu-latest] + runs-on: [macos-latest, windows-latest, ubuntu-latest] name: test steps: - uses: actions/checkout@v2 From ead82e4022febbf586f8d10c1189dcdf7283177d Mon Sep 17 00:00:00 2001 From: Mike Hilgendorf Date: Sat, 25 Apr 2020 15:59:32 -0700 Subject: [PATCH 07/11] do I need a matrix? --- .github/workflows/ci.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 84ca129..7e695dd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,7 +29,10 @@ jobs: args: --all-targets --all-features -- -D warnings test: - runs-on: [macos-latest, windows-latest, ubuntu-latest] + strategy: + matrix: + os: [macos-latest, windows-latest, ubuntu-latest] + runs-on: ${{ matrix.os }} name: test steps: - uses: actions/checkout@v2 From c0b9011afd89c9224fbd2410c4a3cc00977873eb Mon Sep 17 00:00:00 2001 From: Mike Hilgendorf Date: Sat, 25 Apr 2020 16:01:04 -0700 Subject: [PATCH 08/11] whoops --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7e695dd..02901f4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,7 +36,7 @@ jobs: name: test steps: - uses: actions/checkout@v2 - - uses: actions-rs/toolchain@v + - uses: actions-rs/toolchain@v1 with: toolchain: stable profile: minimal From 4f8266416b50c671e255f4eda71fa54cf853d715 Mon Sep 17 00:00:00 2001 From: Mike Hilgendorf Date: Sat, 25 Apr 2020 16:59:33 -0700 Subject: [PATCH 09/11] MacOS Build, passthru example, cleanup * Added MacOS packaging script and support in Makefile.toml * Fixed passthru example to pass validator tests * Moved passthru from workspace crate to example * removed old LICENSE.txt file * added build instructions to the readme --- .gitignore | 1 + Cargo.toml | 12 +- LICENSE.txt | 12 - Makefile.toml | 31 +- .../support/src/co_class/class_factory.rs | 2 +- examples/again/src/lib.rs | 13 + examples/passthru.rs | 389 ++++++++++++++++++ examples/passthru/Cargo.toml | 19 - examples/passthru/src/lib.rs | 292 ------------- osx-bundler.sh | 51 +++ readme.md | 17 + 11 files changed, 505 insertions(+), 334 deletions(-) delete mode 100644 LICENSE.txt create mode 100644 examples/passthru.rs delete mode 100644 examples/passthru/Cargo.toml delete mode 100644 examples/passthru/src/lib.rs create mode 100755 osx-bundler.sh diff --git a/.gitignore b/.gitignore index 9b430e1..e7d9874 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ Cargo.lock .idea .vscode *.iml +validator diff --git a/Cargo.toml b/Cargo.toml index 42ce5de..675877e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,5 +11,15 @@ edition = "2018" vst3-com = { path = "./com" } bitflags = "1.2.0" +[dev-dependencies] +log = "0.4" +simple_logger = "1.6.0" +lazy_static = "1.4.0" +widestring = "0.4.0" + [workspace] -members = ["examples/passthru", "examples/again", "com", "com/macros", "com/macros/support"] +members = ["examples/again", "com", "com/macros", "com/macros/support"] + +[[example]] +name = "passthru" +crate-type = ["cdylib"] diff --git a/LICENSE.txt b/LICENSE.txt deleted file mode 100644 index 51974c9..0000000 --- a/LICENSE.txt +++ /dev/null @@ -1,12 +0,0 @@ -This crate is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . \ No newline at end of file diff --git a/Makefile.toml b/Makefile.toml index 4b0a354..3ddfbee 100644 --- a/Makefile.toml +++ b/Makefile.toml @@ -2,34 +2,47 @@ workspace = false script = [] +[tasks.passthru.mac] +script = [ + "cargo build --example passthru", + "./osx-bundler.sh passthru target/debug/examples/libpassthru.dylib" +] + [tasks.passthru.linux] workspace = false script = [ - "cargo build --package passthru", - "mkdir -p target/debug/plugin.vst3/Contents/x86_64-linux", - "mkdir -p target/debug/plugin.vst3/Contents/Resources", - "cp target/debug/libpassthru.so target/debug/plugin.vst3/Contents/x86_64-linux/plugin.so" + "cargo build --example passthru", + "mkdir -p target/debug/passthru.vst3/Contents/x86_64-linux", + "mkdir -p target/debug/passthru.vst3/Contents/Resources", + "cp target/debug/examples/libpassthru.so target/debug/passthru.vst3/Contents/x86_64-linux/passthru.so" ] [tasks.passthru.windows] workspace = false script_runner = "@shell" script = [ - "cargo build --package passthru", - "cp target/debug/passthru.dll target/debug/passthru.vst3" + "cargo build --example passthru", + "cp target/debug/examples/passthru.dll target/debug/passthru.vst3" ] [tasks.again] workspace = false script = [] +[tasks.again.mac] +workspace = false +script = [ + "cargo build --package again", + "./osx-bundler.sh again target/debug/libagain.dylib" +] + [tasks.again.linux] workspace = false script = [ "cargo build --package again", - "mkdir -p target/debug/plugin.vst3/Contents/x86_64-linux", - "mkdir -p target/debug/plugin.vst3/Contents/Resources", - "cp target/debug/libagain.so target/debug/plugin.vst3/Contents/x86_64-linux/plugin.so" + "mkdir -p target/debug/again.vst3/Contents/x86_64-linux", + "mkdir -p target/debug/again.vst3/Contents/Resources", + "cp target/debug/libagain.so target/debug/again.vst3/Contents/x86_64-linux/again.so" ] [tasks.again.windows] diff --git a/com/macros/support/src/co_class/class_factory.rs b/com/macros/support/src/co_class/class_factory.rs index d080246..b98c57c 100644 --- a/com/macros/support/src/co_class/class_factory.rs +++ b/com/macros/support/src/co_class/class_factory.rs @@ -45,7 +45,7 @@ pub fn generate(struct_item: &ItemStruct) -> HelperTokenStream { // Bringing trait into scope to access IUnknown methods. use vst3_com::interfaces::iunknown::IUnknown; - if aggr != std::ptr::null_mut() { + if !aggr.is_null() { return vst3_com::sys::CLASS_E_NOAGGREGATION; } diff --git a/examples/again/src/lib.rs b/examples/again/src/lib.rs index ab37194..decc9ca 100644 --- a/examples/again/src/lib.rs +++ b/examples/again/src/lib.rs @@ -1006,6 +1006,19 @@ pub extern "system" fn ModuleExit() -> bool { true } +#[no_mangle] +#[allow(non_snake_case)] +pub extern "system" fn bundleEntry(_: *mut c_void) -> bool { + true +} + +#[no_mangle] +#[allow(non_snake_case)] +pub extern "system" fn bundleExit() -> bool { + info!("Module exited"); + true +} + static mut INIT_LOGGER: bool = false; #[no_mangle] diff --git a/examples/passthru.rs b/examples/passthru.rs new file mode 100644 index 0000000..b33880d --- /dev/null +++ b/examples/passthru.rs @@ -0,0 +1,389 @@ +//! Author: Mike Hilgendorf +//! +//! Bare minimum plugin that copies input to output, doesn't +//! save its own state, and doesn't have any parameters. +#![allow(clippy::collapsible_if)] +#![allow(clippy::missing_safety_doc)] +use log::*; +use std::{ + os::raw::{c_char, c_short, c_void}, + ptr::{copy_nonoverlapping, null_mut}, +}; +use vst3_com::{sys::GUID, IID}; +use vst3_sys::{ + base::{ + kInvalidArgument, kResultFalse, kResultOk, tresult, FIDString, IPluginBase, IPluginFactory, + TBool, + }, + vst::{ + AudioBusBuffers, BusDirection, BusDirections, BusFlags, BusInfo, IAudioPresentationLatency, + IAudioProcessor, IAutomationState, IComponent, IEditController, MediaTypes, ParameterInfo, + ProcessData, ProcessSetup, RoutingInfo, TChar, + }, + VST3, +}; +use widestring::U16CString; + +unsafe fn strcpy(src: &str, dst: *mut c_char) { + copy_nonoverlapping(src.as_ptr() as *const c_void as *const _, dst, src.len()); +} + +unsafe fn wstrcpy(src: &str, dst: *mut c_short) { + let src = U16CString::from_str(src).unwrap(); + let mut src = src.into_vec(); + src.push(0); + copy_nonoverlapping(src.as_ptr() as *const c_void as *const _, dst, src.len()); +} + +#[VST3(implements( + IComponent, + IPluginBase, + IEditController, + IAudioProcessor, + IAutomationState, + IAudioPresentationLatency +))] +pub struct PassthruPlugin {} +pub struct PassthruController {} +impl PassthruPlugin { + const CID: GUID = GUID { + data: [ + 0x93, 0x68, 0x4f, 0x1a, 0x46, 0x11, 0x91, 0x01, 0x00, 0x00, 0xb4, 0x39, 0xe5, 0x64, + 0x8a, 0xda, + ], + }; + pub fn new() -> Box { + PassthruPlugin::allocate() + } +} + +#[VST3(implements(IPluginFactory))] +pub struct Factory {} + +impl IEditController for PassthruPlugin { + unsafe fn set_component_state(&mut self, _state: *mut c_void) -> tresult { + info!("set_component_state"); + kResultOk + } + unsafe fn set_state(&mut self, _state: *mut c_void) -> tresult { + info!("set_state"); + kResultOk + } + unsafe fn get_state(&mut self, _state: *mut c_void) -> tresult { + info!("get_state"); + kResultOk + } + unsafe fn get_parameter_count(&self) -> i32 { + info!("get_parameter_count"); + 0 + } + unsafe fn get_parameter_info(&self, _: i32, _: *mut ParameterInfo) -> tresult { + info!("get_parameter_info"); + kResultFalse + } + unsafe fn get_param_string_by_value( + &self, + _id: u32, + _value_normalized: f64, + _string: *mut TChar, + ) -> tresult { + info!("get_param_string_by_value"); + kResultFalse + } + unsafe fn get_param_value_by_string( + &self, + _id: u32, + _string: *mut TChar, + _value_normalized: *mut f64, + ) -> tresult { + info!("get_param_value_by_string"); + kResultFalse + } + unsafe fn normalized_param_to_plain(&self, _id: u32, _value_normalized: f64) -> f64 { + info!("normalized_param_to_plain"); + 0.0 + } + unsafe fn plain_param_to_normalized(&self, _id: u32, _plain_value: f64) -> f64 { + info!("plain_param_to_normalized"); + 0.0 + } + unsafe fn get_param_normalized(&self, _id: u32) -> f64 { + info!("get_param_normalized"); + 0.0 + } + unsafe fn set_param_normalized(&mut self, _id: u32, _value: f64) -> tresult { + info!("set_param_normalized"); + kResultOk + } + unsafe fn set_component_handler(&mut self, _handler: *mut c_void) -> tresult { + info!("set_component_handler"); + kResultOk + } + unsafe fn create_view(&self, _name: FIDString) -> *mut c_void { + info!("Called: AGainController::create_view()"); + null_mut() + } +} +impl IAudioProcessor for PassthruPlugin { + unsafe fn set_bus_arrangements( + &self, + _inputs: *mut u64, + _num_ins: i32, + _outputs: *mut u64, + _num_outs: i32, + ) -> i32 { + kResultFalse + } + + unsafe fn get_bus_arrangements(&self, dir: i32, idx: i32, arr: *mut u64) -> i32 { + info!("get_bus(): dir: {}, idx: {}, arr: {:016b}", dir, idx, *arr); + let arr = &mut *arr; + if (*arr == 0x0) || (*arr == 0x1) || (*arr == 0x3) { + kResultOk + } else { + *arr = 0x03; + kResultOk + } + } + + unsafe fn can_process_sample_size(&self, _symbolic_sample_size: i32) -> i32 { + kResultOk + } + + unsafe fn get_latency_sample(&self) -> u32 { + 0 + } + unsafe fn setup_processing(&mut self, _setup: *mut ProcessSetup) -> tresult { + kResultOk + } + unsafe fn set_processing(&self, _state: TBool) -> tresult { + kResultOk + } + unsafe fn process(&mut self, data: *mut ProcessData) -> tresult { + let data = &*data; + let num_samples = data.num_samples as usize; + if data.inputs.is_null() || data.outputs.is_null() { + return kResultOk; + } + let inputs: &mut AudioBusBuffers = &mut *data.inputs; + let outputs: &mut AudioBusBuffers = &mut *data.outputs; + let num_channels = inputs.num_channels as usize; + let input_ptr = std::slice::from_raw_parts(inputs.buffers, num_channels); + let output_ptr = std::slice::from_raw_parts(outputs.buffers, num_channels); + let sample_size = if data.symbolic_sample_size == 0 { 4 } else { 8 }; + for (i, o) in input_ptr.iter().zip(output_ptr.iter()) { + copy_nonoverlapping(*i, *o, num_samples * sample_size); + } + kResultOk + } + unsafe fn get_tail_samples(&self) -> u32 { + 0 + } +} + +impl IAudioPresentationLatency for PassthruPlugin { + unsafe fn set_audio_presentation_latency_sample( + &self, + _dir: BusDirection, + _bus_idx: i32, + _latency_samples: u32, + ) -> tresult { + kResultOk + } +} + +impl IAutomationState for PassthruPlugin { + unsafe fn set_automation_state(&self, _state: i32) -> tresult { + kResultOk + } +} + +impl IPluginBase for PassthruPlugin { + unsafe fn initialize(&mut self, _host_context: *mut c_void) -> tresult { + kResultOk + } + unsafe fn terminate(&mut self) -> tresult { + kResultOk + } +} + +impl IComponent for PassthruPlugin { + unsafe fn get_controller_class_id(&self, _tuid: *mut IID) -> tresult { + kResultOk + } + + unsafe fn set_io_mode(&self, _mode: i32) -> tresult { + kResultOk + } + + unsafe fn get_bus_count(&self, type_: i32, _dir: i32) -> i32 { + if type_ == MediaTypes::kAudio as i32 { + 1 + } else { + 0 + } + } + + unsafe fn get_bus_info(&self, type_: i32, dir: i32, _idx: i32, info: *mut BusInfo) -> tresult { + if type_ == MediaTypes::kAudio as i32 { + let info = &mut *info; + if dir == BusDirections::kInput as i32 { + info.direction = dir; + info.bus_type = MediaTypes::kAudio as i32; + info.channel_count = 2; + info.flags = BusFlags::kDefaultActive.bits(); + wstrcpy("Audio Input", info.name.as_mut_ptr()); + } else { + info.direction = dir; + info.bus_type = MediaTypes::kAudio as i32; + info.channel_count = 2; + info.flags = BusFlags::kDefaultActive.bits(); + wstrcpy("Audio Output", info.name.as_mut_ptr()); + } + kResultOk + } else { + kInvalidArgument + } + } + + unsafe fn get_routing_info( + &self, + _in_info: *mut RoutingInfo, + _out_info: *mut RoutingInfo, + ) -> i32 { + kResultFalse + } + + unsafe fn activate_bus(&mut self, _type_: i32, _dir: i32, _idx: i32, _state: TBool) -> tresult { + kResultOk + } + + unsafe fn set_active(&self, _state: TBool) -> tresult { + kResultOk + } + + unsafe fn set_state(&mut self, _state: *mut c_void) -> tresult { + kResultOk + } + + unsafe fn get_state(&mut self, _state: *mut c_void) -> tresult { + kResultOk + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Factory implementation + +impl Factory { + fn new() -> Box { + info!("instantiating factory..."); + Self::allocate() + } +} + +impl IPluginFactory for Factory { + unsafe fn get_factory_info(&self, info: *mut vst3_sys::base::PFactoryInfo) -> i32 { + let info = &mut *info; + strcpy("rust.audio", info.vendor.as_mut_ptr()); + strcpy("https://rust.audio", info.url.as_mut_ptr()); + strcpy("mailto://mike@hilgendorf.audio", info.email.as_mut_ptr()); + info.flags = 8; + kResultOk + } + + unsafe fn count_classes(&self) -> i32 { + 1 + } + unsafe fn get_class_info(&self, idx: i32, info: *mut vst3_sys::base::PClassInfo) -> i32 { + match idx { + 0 => { + let info = &mut *info; + info.cardinality = 0x7FFF_FFFF; + info.cid = PassthruPlugin::CID; + strcpy("Audio Module Class", info.category.as_mut_ptr()); + strcpy("Pass Through", info.name.as_mut_ptr()); + } + _ => { + info!("invalid class info ID {}", idx); + return kInvalidArgument; + } + } + kResultOk + } + unsafe fn create_instance( + &self, + cid: *mut vst3_com::sys::GUID, + _riid: *mut vst3_com::sys::GUID, + obj: *mut *mut core::ffi::c_void, + ) -> i32 { + let iid = *cid; + match iid { + PassthruPlugin::CID => { + let ptr = Box::into_raw(PassthruPlugin::new()) as *mut c_void; + *obj = ptr; + kResultOk + } + _ => kResultFalse, + } + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +pub fn init() { + if let Err(e) = simple_logger::init() { + println!("{:?}", e); + } + info!("plugin library loaded"); +} + +#[no_mangle] +#[allow(non_snake_case)] +pub unsafe extern "system" fn GetPluginFactory() -> *mut c_void { + info!("calling plugin factory"); + Box::into_raw(Factory::new()) as *mut c_void +} + +#[cfg(target_os = "linux")] +#[no_mangle] +#[allow(non_snake_case)] +pub extern "system" fn ModuleEntry(_: *mut c_void) -> bool { + init(); + true +} + +#[cfg(target_os = "linux")] +#[no_mangle] +#[allow(non_snake_case)] +pub extern "system" fn ModuleExit() -> bool { + true +} + +#[cfg(target_os = "macos")] +#[no_mangle] +#[allow(non_snake_case)] +pub extern "system" fn bundleEntry() -> bool { + init(); + true +} + +#[cfg(target_os = "linux")] +#[no_mangle] +#[allow(non_snake_case)] +pub extern "system" fn bundleExit() -> bool { + true +} + +#[cfg(target_os = "windows")] +#[no_mangle] +#[allow(non_snake_case)] +pub extern "system" fn InitDll() -> bool { + init(); + true +} + +#[cfg(target_os = "windows")] +#[no_mangle] +#[allow(non_snake_case)] +pub extern "system" fn ExitDll() -> bool { + true +} diff --git a/examples/passthru/Cargo.toml b/examples/passthru/Cargo.toml deleted file mode 100644 index 5c6526e..0000000 --- a/examples/passthru/Cargo.toml +++ /dev/null @@ -1,19 +0,0 @@ -[package] -name = "passthru" -version = "0.1.0" -authors = ["Mike Hilgendorf "] -edition = "2018" -description = """ -Example/smoke test to verify the API bindings work, and to demonstrate -how the APIs are intended to be used without macros. -""" - -[lib] -crate-type = ["cdylib"] - -[dependencies] -vst3-sys = { path = "../../"} -vst3-com = { path = "../../com" } -log = "0.4" -simple_logger = "1.6.0" -lazy_static = "1.4.0" \ No newline at end of file diff --git a/examples/passthru/src/lib.rs b/examples/passthru/src/lib.rs deleted file mode 100644 index bb420ee..0000000 --- a/examples/passthru/src/lib.rs +++ /dev/null @@ -1,292 +0,0 @@ -#![allow(unused_unsafe)] -use lazy_static::lazy_static; -use log::*; -use std::os::raw::{c_char, c_void}; -use std::ptr::copy_nonoverlapping as memcpy; -use std::sync::Mutex; -use vst3_com::sys::GUID; -use vst3_com::IID; -use vst3_sys::base::{ - kInvalidArgument, kResultOk, tresult, IPluginBase, IPluginFactory, IUnknown, TBool, -}; -use vst3_sys::vst::{ - BusDirection, BusDirections, BusFlags, BusInfo, IAudioPresentationLatency, IAudioProcessor, - IAutomationState, IComponent, MediaTypes, ProcessData, ProcessSetup, RoutingInfo, -}; -use vst3_sys::VST3; - -#[VST3(implements( - IAudioProcessor, - IAudioPresentationLatency, - IAutomationState, - IPluginBase -))] -pub struct PassthruPlugin {} -pub struct PassthruController {} -impl PassthruPlugin { - const CID: GUID = GUID { - data: [ - 0x93, 0x68, 0x4f, 0x1a, 0x46, 0x11, 0x91, 0x01, 0x0, 0, 0xb4, 0x39, 0xe5, 0x64, 0x8a, - 0xda, - ], - }; - pub fn new() -> Box { - PassthruPlugin::allocate() - } -} -#[VST3(implements(IPluginFactory))] -pub struct Factory {} - -impl IAudioProcessor for PassthruPlugin { - unsafe fn set_bus_arrangements( - &self, - _inputs: *mut u64, - _num_ins: i32, - _outputs: *mut u64, - _num_outs: i32, - ) -> i32 { - unimplemented!() - } - - unsafe fn get_bus_arrangements(&self, _dir: i32, _index: i32, _arr: *mut u64) -> i32 { - unimplemented!() - } - - unsafe fn can_process_sample_size(&self, _symbolic_sample_size: i32) -> i32 { - unimplemented!() - } - - unsafe fn get_latency_sample(&self) -> u32 { - 0 - } - unsafe fn setup_processing(&mut self, _setup: *mut ProcessSetup) -> tresult { - kResultOk - } - unsafe fn set_processing(&self, _state: TBool) -> tresult { - kResultOk - } - unsafe fn process(&mut self, _data: *mut ProcessData) -> tresult { - kResultOk - } - unsafe fn get_tail_samples(&self) -> u32 { - 0 - } -} - -impl IAudioPresentationLatency for PassthruPlugin { - unsafe fn set_audio_presentation_latency_sample( - &self, - _dir: BusDirection, - _bus_idx: i32, - _latency_samples: u32, - ) -> tresult { - kResultOk - } -} - -impl IAutomationState for PassthruPlugin { - unsafe fn set_automation_state(&self, _state: i32) -> tresult { - kResultOk - } -} - -impl IPluginBase for PassthruPlugin { - unsafe fn initialize(&mut self, _host_context: *mut c_void) -> tresult { - kResultOk - } - unsafe fn terminate(&mut self) -> tresult { - kResultOk - } -} - -impl IComponent for PassthruPlugin { - unsafe fn get_controller_class_id(&self, _tuid: *mut IID) -> tresult { - kResultOk - } - - unsafe fn set_io_mode(&self, _mode: i32) -> tresult { - kResultOk - } - - unsafe fn get_bus_count(&self, type_: i32, _dir: i32) -> i32 { - if type_ == MediaTypes::kAudio as i32 { - 1 - } else { - 0 - } - } - - unsafe fn get_bus_info(&self, type_: i32, dir: i32, _idx: i32, info: *mut BusInfo) -> tresult { - if type_ == MediaTypes::kAudio as i32 { - let info = unsafe { &mut *info }; - if dir == BusDirections::kInput as i32 { - info.direction = dir; - info.bus_type = MediaTypes::kAudio as i32; - info.channel_count = 2; - info.flags = BusFlags::kDefaultActive.bits(); - } else { - info.direction = dir; - info.bus_type = MediaTypes::kAudio as i32; - info.channel_count = 2; - info.flags = BusFlags::kDefaultActive.bits(); - } - kResultOk - } else { - kInvalidArgument - } - } - - unsafe fn get_routing_info( - &self, - _in_info: *mut RoutingInfo, - _out_info: *mut RoutingInfo, - ) -> i32 { - unimplemented!() - } - - unsafe fn activate_bus(&mut self, _type_: i32, _dir: i32, _idx: i32, _state: TBool) -> tresult { - kResultOk - } - - unsafe fn set_active(&self, _state: TBool) -> tresult { - kResultOk - } - - unsafe fn set_state(&mut self, _state: *mut c_void) -> tresult { - kResultOk - } - - unsafe fn get_state(&mut self, _state: *mut c_void) -> tresult { - kResultOk - } -} - -//todo: IComponent -//todo: IContextMenuTarget -//todo: IEditController -//todo: IEditController2 -//todo: IMidiMapping -//todo: IEditControllerHostEditing -//todo: IInterAppAudioConnectionNotification -//todo: IInterAppAudioPresetManager -//todo: IConnectionPoint -//todo: IMidiLearn -//todo: INoteExpressionController -//todo: IKeyswitchController -//todo: INoteExpressionPhysicalUIMapping -//todo: IPrefetchableSupport -//todo: IXmlRepresentationController -//todo: IUnitInfo -//todo: IProgramListData -//todo: IUnitData -//todo: IPlugView -//todo: IPlugViewContentScaleSupport - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// Factory implementation - -impl Factory { - fn new() -> Box { - info!("instantiating factory..."); - Self::allocate() - } -} - -unsafe fn strcpy(src: &str, dst: *mut c_char) { - memcpy(src.as_ptr() as *const c_void as *const _, dst, src.len()); -} - -impl IPluginFactory for Factory { - unsafe fn get_factory_info(&self, info: *mut vst3_sys::base::PFactoryInfo) -> i32 { - let info = &mut *info; - strcpy("rust.audio", info.vendor.as_mut_ptr()); - strcpy("https://rust.audio", info.url.as_mut_ptr()); - strcpy("mailto://mike@hilgendorf.audio", info.email.as_mut_ptr()); - info.flags = 8; - kResultOk - } - - unsafe fn count_classes(&self) -> i32 { - 1 - } - unsafe fn get_class_info(&self, idx: i32, info: *mut vst3_sys::base::PClassInfo) -> i32 { - match idx { - 0 => { - let info = &mut *info; - info.cardinality = 0x7FFFFFFF; - info.cid = PassthruPlugin::CID; - strcpy("Audio Module Class", info.category.as_mut_ptr()); - strcpy("Pass Through", info.name.as_mut_ptr()); - } - _ => { - info!("invalid class info ID {}", idx); - return kInvalidArgument; - } - } - kResultOk - } - unsafe fn create_instance( - &self, - cid: *mut vst3_com::sys::GUID, - riid: *mut vst3_com::sys::GUID, - ppv: *mut *mut core::ffi::c_void, - ) -> i32 { - //todo: figure out why this method fails in the validator - let cid = *&*cid; - let cmp = PassthruPlugin::CID; - - info!("creating instance of {:?}", cid); - if cid == cmp { - let instance = PassthruPlugin::new(); - instance.add_ref(); - let hr = instance.query_interface(riid, ppv); - instance.release(); - core::mem::forget(instance); - return hr; - } else { - warn!("CID not found"); - } - kResultOk - } -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// entry point and wrapping code to satisfy the borrow checker -// todo: cleanup singleton instance so this is less hacky - -struct FactoryWrapper { - factory: Box, -} -unsafe impl Send for FactoryWrapper {} -unsafe impl Sync for FactoryWrapper {} -lazy_static! { - static ref WRAPPER: Mutex = Mutex::new(FactoryWrapper { - factory: Factory::new() - }); -} - -#[no_mangle] -#[allow(non_snake_case)] -pub unsafe extern "system" fn GetPluginFactory() -> *mut c_void { - info!("calling plugin factory"); - let factory = &mut *WRAPPER.lock().unwrap().factory; - factory.add_ref(); - factory as *mut _ as *mut _ -} - -#[no_mangle] -#[allow(non_snake_case)] -pub extern "system" fn ModuleEntry(_: *mut c_void) -> bool { - if let Err(e) = simple_logger::init() { - println!("{:?}", e); - } - info!("Module entered"); - true -} - -#[no_mangle] -#[allow(non_snake_case)] -pub extern "system" fn ModuleExit() -> bool { - info!("Module exited"); - true -} diff --git a/osx-bundler.sh b/osx-bundler.sh new file mode 100755 index 0000000..ddb7c1f --- /dev/null +++ b/osx-bundler.sh @@ -0,0 +1,51 @@ +#!/bin/bash -e +# Make sure we have the arguments we need +if [[ -z $1 || -z $2 ]]; then + echo "Generates a macOS bundle from a compiled dylib file" + echo "Example:" + echo -e "\t$0 Plugin target/release/plugin.dylib" + echo -e "\tCreates a Plugin.vst3 bundle" +else + # Make the bundle folder + mkdir -p "target/debug/$1.vst3/Contents/MacOS" + + # Create the PkgInfo + echo "BNDL????" > "target/debug/$1.vst3/Contents/PkgInfo" + + #build the Info.Plist + echo " + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + $1 + CFBundleGetInfoString + vst3 + CFBundleIconFile + + CFBundleIdentifier + com.rust-vst.$1 + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $1 + CFBundlePackageType + BNDL + CFBundleVersion + 1.0 + CFBundleSignature + $((RANDOM % 9999)) + CSResourcesFileMapped + + +" > "target/debug/$1.vst3/Contents/Info.plist" + + # move the provided library to the correct location + cp "$2" "target/debug/$1.vst3/Contents/MacOS/$1" + + echo "Created bundle target/debug/$1.vst3" +fi + + diff --git a/readme.md b/readme.md index 7c18095..14b7db2 100644 --- a/readme.md +++ b/readme.md @@ -4,6 +4,23 @@ A port of the VST3 API in pure Rust. We do not distribute the SDK nor try and wrap it in clean abstractions, just port compatiable bindings to the API which is based on COM. The full SDK can be found at sdk.steinberg.net or cloned from github [here](https://github.com/steinbergmedia/vst3sdk). +## Building Examples + +The examples can be built using [cargo-make](https://github.com/sagiegurari/cargo-make). + +``` +cargo install --force cargo-make +``` + +The two current examples are `again` and `passthru`. + +``` +cargo make again +cargo make passthru +``` + +Provided is a script to package a vst3 plugin as a MacOS bundle, which requires an `Info.plist` file and `PkgInfo` to be included in the vst3 plugin directory. + ## Completeness and Contributions Currently this crate is missing definitions of some of the constants found in the SDK, and help covering them would be greatly appreciated. If you find something missing from the SDK please submit a PR to add it. You can also grep for `todo` to find gaps. From ef0f44bfa42c75a6405d99e7a545315fada3a933 Mon Sep 17 00:00:00 2001 From: Mike Hilgendorf Date: Sat, 25 Apr 2020 17:04:53 -0700 Subject: [PATCH 10/11] added github actions --- .github/workflows/ci.yml | 46 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..02901f4 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,46 @@ +on: + push: + branches: + - master + pull_request: + +jobs: + check: + runs-on: ubuntu-latest + name: formatting and linting + steps: + - name: checkout + uses: actions/checkout@v2 + - name: provision + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + profile: minimal + components: rustfmt, clippy + - name: cargo fmt + uses: actions-rs/cargo@v1 + with: + command: fmt + args: --all -- --check + - name: cargo clippy + uses: actions-rs/cargo@v1 + with: + command: clippy + args: --all-targets --all-features -- -D warnings + + test: + strategy: + matrix: + os: [macos-latest, windows-latest, ubuntu-latest] + runs-on: ${{ matrix.os }} + name: test + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + toolchain: stable + profile: minimal + - uses: actions-rs/cargo@v1 + with: + command: test + args: --all \ No newline at end of file From 439487740528de3dfcfc4f6f2f531f4b419ab895 Mon Sep 17 00:00:00 2001 From: Mike Hilgendorf Date: Sat, 25 Apr 2020 17:05:08 -0700 Subject: [PATCH 11/11] removed .travis.yml --- .travis.yml | 25 ------------------------- 1 file changed, 25 deletions(-) delete mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index ccbbca0..0000000 --- a/.travis.yml +++ /dev/null @@ -1,25 +0,0 @@ -language: rust -jobs: - include: - - stage: test - os: linux - before_script: - - rustup toolchain install stable - - rustup component add --toolchain stable clippy-preview || cargo install --git https://github.com/rust-lang/rust-clippy/ --force clippy - - rustup component add rustfmt - script: - - cargo build --verbose - - cargo test --verbose - - cargo fmt --all -- --check - - cargo +stable clippy --all-targets --all-features -- -D warnings -A clippy::unreadable_literal -A clippy::needless_range_loop -A clippy::float_cmp -A clippy::comparison-chain -A clippy::needless-doctest-main -A clippy::missing-safety-doc - - stage: test - os: osx - before_script: - - rustup toolchain install stable - - rustup component add --toolchain stable clippy-preview || cargo install --git https://github.com/rust-lang/rust-clippy/ --force clippy - - rustup component add rustfmt - script: - - cargo build --verbose - - cargo test --verbose - - cargo fmt --all -- --check - - cargo +stable clippy --all-targets --all-features -- -D warnings -A clippy::unreadable_literal -A clippy::needless_range_loop -A clippy::float_cmp -A clippy::comparison-chain -A clippy::needless-doctest-main -A clippy::missing-safety-doc \ No newline at end of file