├── rustfmt.toml ├── .gitignore ├── src ├── format │ ├── mod.rs │ ├── clap │ │ ├── host.rs │ │ ├── mod.rs │ │ ├── factory.rs │ │ ├── tests.rs │ │ └── gui.rs │ └── vst3 │ │ ├── host.rs │ │ ├── util.rs │ │ ├── mod.rs │ │ ├── factory.rs │ │ ├── view.rs │ │ ├── tests.rs │ │ ├── buffers.rs │ │ └── component.rs ├── sync │ ├── mod.rs │ ├── float.rs │ ├── params.rs │ ├── bitset.rs │ └── param_gestures.rs ├── lib.rs ├── host.rs ├── engine.rs ├── bus.rs ├── params.rs ├── util.rs ├── view.rs ├── events.rs ├── plugin.rs ├── buffers │ ├── convert.rs │ └── iter.rs ├── params │ └── range.rs └── buffers.rs ├── examples └── gain │ ├── format │ ├── clap │ │ ├── src │ │ │ └── lib.rs │ │ └── Cargo.toml │ └── vst3 │ │ ├── src │ │ └── lib.rs │ │ └── Cargo.toml │ ├── Cargo.toml │ └── src │ └── lib.rs ├── coupler-derive ├── Cargo.toml └── src │ ├── lib.rs │ ├── enum_.rs │ └── params.rs ├── cargo-coupler ├── Cargo.toml └── src │ └── main.rs ├── Cargo.toml ├── .github └── workflows │ └── ci.yml ├── LICENSE-MIT ├── README.md ├── assets └── coupler-banner.svg └── LICENSE-APACHE /rustfmt.toml: -------------------------------------------------------------------------------- 1 | chain_width = 80 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /src/format/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod clap; 2 | pub mod vst3; 3 | -------------------------------------------------------------------------------- /examples/gain/format/clap/src/lib.rs: -------------------------------------------------------------------------------- 1 | coupler::clap!(gain::Gain); 2 | -------------------------------------------------------------------------------- /examples/gain/format/vst3/src/lib.rs: -------------------------------------------------------------------------------- 1 | coupler::vst3!(gain::Gain); 2 | -------------------------------------------------------------------------------- /src/sync/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod bitset; 2 | pub mod float; 3 | pub mod param_gestures; 4 | pub mod params; 5 | -------------------------------------------------------------------------------- /src/format/clap/host.rs: -------------------------------------------------------------------------------- 1 | use crate::host::HostInner; 2 | 3 | pub struct ClapHost {} 4 | 5 | impl HostInner for ClapHost {} 6 | -------------------------------------------------------------------------------- /src/format/vst3/host.rs: -------------------------------------------------------------------------------- 1 | use crate::host::HostInner; 2 | 3 | pub struct Vst3Host {} 4 | 5 | impl Vst3Host { 6 | pub fn new() -> Vst3Host { 7 | Vst3Host {} 8 | } 9 | } 10 | 11 | impl HostInner for Vst3Host {} 12 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::missing_safety_doc)] 2 | 3 | pub mod buffers; 4 | pub mod bus; 5 | pub mod engine; 6 | pub mod events; 7 | pub mod format; 8 | pub mod host; 9 | pub mod params; 10 | pub mod plugin; 11 | pub mod view; 12 | 13 | mod sync; 14 | mod util; 15 | -------------------------------------------------------------------------------- /coupler-derive/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "coupler-derive" 3 | version = "0.1.0" 4 | authors = ["Micah Johnston "] 5 | edition = "2021" 6 | 7 | [lib] 8 | proc-macro = true 9 | 10 | [dependencies] 11 | syn = { version = "2.0", features = ["full"] } 12 | quote = "1.0" 13 | proc-macro2 = "1.0" 14 | -------------------------------------------------------------------------------- /src/host.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | pub trait HostInner {} 4 | 5 | #[derive(Clone)] 6 | pub struct Host { 7 | _inner: Arc, 8 | } 9 | 10 | impl Host { 11 | pub fn from_inner(inner: Arc) -> Host { 12 | Host { _inner: inner } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /cargo-coupler/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cargo-coupler" 3 | version = "0.1.0" 4 | authors = ["Micah Johnston "] 5 | edition = "2021" 6 | publish = false 7 | 8 | [dependencies] 9 | clap = { version = "3.1.17", features = ["derive", "cargo"] } 10 | cargo_metadata = "0.14.2" 11 | serde = "1.0" 12 | serde_json = "1.0" 13 | -------------------------------------------------------------------------------- /examples/gain/format/clap/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "gain-clap" 3 | version = "0.1.0" 4 | authors = ["Micah Johnston "] 5 | edition = "2021" 6 | publish = false 7 | 8 | [package.metadata.coupler] 9 | name = "gain" 10 | formats = ["clap"] 11 | 12 | [lib] 13 | crate-type = ["cdylib"] 14 | 15 | [dependencies] 16 | coupler = { workspace = true } 17 | gain = { path = "../.." } 18 | -------------------------------------------------------------------------------- /examples/gain/format/vst3/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "gain-vst3" 3 | version = "0.1.0" 4 | authors = ["Micah Johnston "] 5 | edition = "2021" 6 | publish = false 7 | 8 | [package.metadata.coupler] 9 | name = "gain" 10 | formats = ["vst3"] 11 | 12 | [lib] 13 | crate-type = ["cdylib"] 14 | 15 | [dependencies] 16 | coupler = { workspace = true } 17 | gain = { path = "../.." } 18 | -------------------------------------------------------------------------------- /src/engine.rs: -------------------------------------------------------------------------------- 1 | use crate::buffers::Buffers; 2 | use crate::bus::Layout; 3 | use crate::events::Events; 4 | 5 | #[derive(Clone)] 6 | pub struct Config { 7 | pub layout: Layout, 8 | pub sample_rate: f64, 9 | pub max_buffer_size: usize, 10 | } 11 | 12 | pub trait Engine: Send + Sized + 'static { 13 | fn reset(&mut self); 14 | fn flush(&mut self, events: Events); 15 | fn process(&mut self, buffers: Buffers, events: Events); 16 | } 17 | -------------------------------------------------------------------------------- /src/sync/float.rs: -------------------------------------------------------------------------------- 1 | use std::sync::atomic::{AtomicU64, Ordering}; 2 | 3 | pub struct AtomicF64(AtomicU64); 4 | 5 | impl AtomicF64 { 6 | pub fn new(value: f64) -> AtomicF64 { 7 | AtomicF64(AtomicU64::new(value.to_bits())) 8 | } 9 | 10 | pub fn load(&self, ordering: Ordering) -> f64 { 11 | f64::from_bits(self.0.load(ordering)) 12 | } 13 | 14 | pub fn store(&self, value: f64, ordering: Ordering) { 15 | self.0.store(value.to_bits(), ordering) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /examples/gain/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "gain" 3 | version = "0.1.0" 4 | authors = ["Micah Johnston "] 5 | edition = "2021" 6 | publish = false 7 | 8 | [dependencies] 9 | coupler = { workspace = true, features = ["derive"] } 10 | portlight = { git = "https://github.com/coupler-rs/portlight", rev = "fff171e9f3f70a9604102e1e1877ad67d0c2badd" } 11 | flicker = { git = "https://github.com/coupler-rs/flicker", rev = "80aca05cb6c7406f8c2a4ba66d85849dc344afaa" } 12 | serde = { version = "1.0", features = ["derive"] } 13 | serde_json = "1.0" 14 | -------------------------------------------------------------------------------- /src/bus.rs: -------------------------------------------------------------------------------- 1 | #[derive(Copy, Clone, Eq, PartialEq)] 2 | pub enum BusDir { 3 | In, 4 | Out, 5 | InOut, 6 | } 7 | 8 | pub struct BusInfo { 9 | pub name: String, 10 | pub dir: BusDir, 11 | } 12 | 13 | #[derive(Clone, Default, Eq, PartialEq, Hash)] 14 | pub struct Layout { 15 | pub formats: Vec, 16 | } 17 | 18 | #[derive(Clone, Eq, PartialEq, Hash)] 19 | pub enum Format { 20 | Mono, 21 | Stereo, 22 | } 23 | 24 | impl Format { 25 | pub fn channel_count(&self) -> usize { 26 | match self { 27 | Format::Mono => 1, 28 | Format::Stereo => 2, 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "coupler" 3 | version = "0.1.0" 4 | authors = ["Micah Johnston "] 5 | edition = "2021" 6 | license = "MIT OR Apache-2.0" 7 | publish = false 8 | 9 | [dependencies] 10 | coupler-derive = { path = "coupler-derive", optional = true } 11 | clap-sys = "0.3.0" 12 | vst3 = "0.3.0" 13 | uuid = { version = "1.18.1", features = ["v5"] } 14 | 15 | [features] 16 | derive = ["coupler-derive"] 17 | 18 | [workspace] 19 | members = [ 20 | "cargo-coupler", 21 | "coupler-derive", 22 | "examples/*", 23 | "examples/*/format/*", 24 | ] 25 | 26 | [workspace.dependencies] 27 | coupler = { path = "." } 28 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | master 7 | pull_request: 8 | branches: 9 | master 10 | 11 | jobs: 12 | ci: 13 | name: Build and run tests 14 | runs-on: ${{ matrix.os }} 15 | strategy: 16 | matrix: 17 | os: [ubuntu-latest, windows-latest, macOS-latest] 18 | steps: 19 | - name: Check out repository 20 | uses: actions/checkout@v3 21 | - name: Install Rust toolchain 22 | uses: dtolnay/rust-toolchain@stable 23 | - name: Build 24 | run: cargo build --workspace 25 | - name: Run tests 26 | run: cargo test --workspace 27 | -------------------------------------------------------------------------------- /src/format/vst3/util.rs: -------------------------------------------------------------------------------- 1 | use std::slice; 2 | 3 | use vst3::Steinberg::char16; 4 | 5 | pub fn copy_wstring(src: &str, dst: &mut [char16]) { 6 | let mut len = 0; 7 | for (src, dst) in src.encode_utf16().zip(dst.iter_mut()) { 8 | *dst = src as char16; 9 | len += 1; 10 | } 11 | 12 | if len < dst.len() { 13 | dst[len] = 0; 14 | } else if let Some(last) = dst.last_mut() { 15 | *last = 0; 16 | } 17 | } 18 | 19 | pub unsafe fn utf16_from_ptr<'a>(ptr: *const char16) -> &'a [u16] { 20 | let mut len = 0; 21 | while *ptr.add(len) != 0 { 22 | len += 1; 23 | } 24 | 25 | slice::from_raw_parts(ptr as *const u16, len) 26 | } 27 | -------------------------------------------------------------------------------- /coupler-derive/src/lib.rs: -------------------------------------------------------------------------------- 1 | use proc_macro::TokenStream; 2 | use syn::{parse_macro_input, DeriveInput}; 3 | 4 | mod enum_; 5 | mod params; 6 | 7 | use enum_::expand_enum; 8 | use params::expand_params; 9 | 10 | #[proc_macro_derive(Params, attributes(param))] 11 | pub fn derive_params(input: TokenStream) -> TokenStream { 12 | let input: DeriveInput = parse_macro_input!(input as DeriveInput); 13 | 14 | match expand_params(&input) { 15 | Ok(expanded) => expanded.into(), 16 | Err(err) => err.into_compile_error().into(), 17 | } 18 | } 19 | 20 | #[proc_macro_derive(Enum, attributes(name))] 21 | pub fn derive_enum(input: TokenStream) -> TokenStream { 22 | let input: DeriveInput = parse_macro_input!(input as DeriveInput); 23 | 24 | match expand_enum(&input) { 25 | Ok(expanded) => expanded.into(), 26 | Err(err) => err.into_compile_error().into(), 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/params.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::{self, Display, Formatter}; 2 | use std::str::FromStr; 3 | 4 | #[cfg(feature = "derive")] 5 | pub use coupler_derive::{Enum, Params}; 6 | 7 | mod range; 8 | 9 | pub use range::{Encode, Log, Range}; 10 | 11 | pub type ParamId = u32; 12 | pub type ParamValue = f64; 13 | 14 | pub struct ParamInfo { 15 | pub id: ParamId, 16 | pub name: String, 17 | pub default: ParamValue, 18 | pub steps: Option, 19 | } 20 | 21 | pub trait Params { 22 | fn params() -> Vec; 23 | fn set_param(&mut self, id: ParamId, value: ParamValue); 24 | fn get_param(&self, id: ParamId) -> ParamValue; 25 | fn parse_param(&self, id: ParamId, text: &str) -> Option; 26 | fn display_param( 27 | &self, 28 | id: ParamId, 29 | value: ParamValue, 30 | fmt: &mut Formatter, 31 | ) -> Result<(), fmt::Error>; 32 | } 33 | 34 | pub trait Enum: Encode + FromStr + Display {} 35 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any 2 | person obtaining a copy of this software and associated 3 | documentation files (the "Software"), to deal in the 4 | Software without restriction, including without 5 | limitation the rights to use, copy, modify, merge, 6 | publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following 9 | conditions: 10 | 11 | The above copyright notice and this permission notice 12 | shall be included in all copies or substantial portions 13 | of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /src/sync/params.rs: -------------------------------------------------------------------------------- 1 | use std::sync::atomic::Ordering; 2 | 3 | use super::bitset::{self, AtomicBitset}; 4 | use super::float::AtomicF64; 5 | use crate::params::ParamValue; 6 | 7 | pub struct ParamValues { 8 | values: Vec, 9 | dirty: AtomicBitset, 10 | } 11 | 12 | impl ParamValues { 13 | pub fn with_count(count: usize) -> ParamValues { 14 | ParamValues { 15 | values: (0..count).map(|_| AtomicF64::new(0.0)).collect(), 16 | dirty: AtomicBitset::with_len(count), 17 | } 18 | } 19 | 20 | pub fn set(&self, index: usize, value: ParamValue) { 21 | self.values[index].store(value, Ordering::Relaxed); 22 | self.dirty.set(index, true, Ordering::Release); 23 | } 24 | 25 | pub fn poll(&self) -> Poll<'_> { 26 | Poll { 27 | values: &self.values, 28 | iter: self.dirty.drain(Ordering::Acquire), 29 | } 30 | } 31 | } 32 | 33 | pub struct Poll<'a> { 34 | values: &'a [AtomicF64], 35 | iter: bitset::Drain<'a>, 36 | } 37 | 38 | impl<'a> Iterator for Poll<'a> { 39 | type Item = (usize, ParamValue); 40 | 41 | fn next(&mut self) -> Option { 42 | if let Some(index) = self.iter.next() { 43 | Some((index, self.values[index].load(Ordering::Relaxed))) 44 | } else { 45 | None 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ![Coupler](assets/coupler-banner.svg) 2 | 3 | Coupler is a framework for writing audio plugins in Rust. It currently supports the VST3 and CLAP APIs, with plans to support AUv2 and AAX in the near future. 4 | 5 | Like the Rust language itself, Coupler prioritizes: 6 | 7 | 1. **Performance:** Coupler follows a "pay for what you use" philosophy and strives to impose as little overhead as possible between hosts and plugins. 8 | 2. **Correctness:** A plugin API is a contract, with rules for threading and memory management that must be followed by both host and plugin. Coupler uses Rust's type system and ownership model to ensure that plugins implement their end of this contract correctly. 9 | 3. **Productivity:** Coupler provides a [Serde](https://crates.io/crates/serde)-like `#[derive(Params)]` macro for declaring plugin parameters and a `cargo coupler bundle` command for generating plugin bundles. 10 | 11 | Coupler is still early in development and should not be considered production-ready. Important functionality is still missing and there is little to no documentation. However, feel free to experiment with it. Questions and feedback are welcome on the [Zulip instance](https://coupler.zulipchat.com) or on the [Rust Audio Discord](https://discord.gg/yVCFhmQYPC). 12 | 13 | ## Building 14 | 15 | To build the `gain` example, run: 16 | 17 | ```console 18 | cargo run -p cargo-coupler -- coupler bundle -p gain-vst3 gain-clap --release 19 | ``` 20 | 21 | VST3 and CLAP plugin bundles will be placed in the `target/release/bundle` directory. 22 | 23 | ## License 24 | 25 | Coupler is distributed under the terms of both the [MIT license](LICENSE-MIT) and the [Apache license, version 2.0](LICENSE-APACHE). Contributions are accepted under the same terms. 26 | -------------------------------------------------------------------------------- /src/util.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::CString; 2 | use std::fmt::{self, Display, Formatter}; 3 | use std::os::raw::c_char; 4 | use std::slice; 5 | 6 | use crate::params::{ParamId, ParamValue}; 7 | use crate::plugin::Plugin; 8 | 9 | pub fn copy_cstring(src: &str, dst: &mut [c_char]) { 10 | let c_string = CString::new(src).unwrap_or_else(|_| CString::default()); 11 | let bytes = c_string.as_bytes_with_nul(); 12 | 13 | for (src, dst) in bytes.iter().zip(dst.iter_mut()) { 14 | *dst = *src as c_char; 15 | } 16 | 17 | if bytes.len() > dst.len() { 18 | if let Some(last) = dst.last_mut() { 19 | *last = 0; 20 | } 21 | } 22 | } 23 | 24 | // The pointer passed to `slice::from_raw_parts` must be non-null and aligned even for zero-length 25 | // slices. This won't be true in general for a pointer to a zero-length array received from an 26 | // external source. `slice_from_raw_parts_checked` is a convenience function that checks if `len` 27 | // is nonzero before calling `from_raw_parts`. 28 | pub unsafe fn slice_from_raw_parts_checked<'a, T>(ptr: *const T, len: usize) -> &'a [T] { 29 | if len > 0 { 30 | slice::from_raw_parts(ptr, len) 31 | } else { 32 | &[] 33 | } 34 | } 35 | 36 | pub struct DisplayParam<'a, P> { 37 | plugin: &'a P, 38 | id: ParamId, 39 | value: ParamValue, 40 | } 41 | 42 | impl<'a, P> DisplayParam<'a, P> { 43 | pub fn new(plugin: &'a P, id: ParamId, value: ParamValue) -> Self { 44 | DisplayParam { plugin, id, value } 45 | } 46 | } 47 | 48 | impl<'a, P> Display for DisplayParam<'a, P> 49 | where 50 | P: Plugin, 51 | { 52 | fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> { 53 | self.plugin.display_param(self.id, self.value, f) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/view.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::{c_ulong, c_void}; 2 | use std::marker::PhantomData; 3 | use std::rc::Rc; 4 | 5 | use crate::params::{ParamId, ParamValue}; 6 | 7 | pub trait ViewHostInner { 8 | fn begin_gesture(&self, id: ParamId); 9 | fn end_gesture(&self, id: ParamId); 10 | fn set_param(&self, id: ParamId, value: ParamValue); 11 | } 12 | 13 | #[derive(Clone)] 14 | pub struct ViewHost { 15 | inner: Rc, 16 | // Ensure !Send and !Sync 17 | _marker: PhantomData<*mut ()>, 18 | } 19 | 20 | impl ViewHost { 21 | pub fn from_inner(inner: Rc) -> ViewHost { 22 | ViewHost { 23 | inner, 24 | _marker: PhantomData, 25 | } 26 | } 27 | 28 | pub fn begin_gesture(&self, id: ParamId) { 29 | self.inner.begin_gesture(id); 30 | } 31 | 32 | pub fn end_gesture(&self, id: ParamId) { 33 | self.inner.end_gesture(id); 34 | } 35 | 36 | pub fn set_param(&self, id: ParamId, value: ParamValue) { 37 | self.inner.set_param(id, value); 38 | } 39 | } 40 | 41 | #[derive(Copy, Clone)] 42 | pub enum RawParent { 43 | Win32(*mut c_void), 44 | Cocoa(*mut c_void), 45 | X11(c_ulong), 46 | } 47 | 48 | pub struct ParentWindow { 49 | parent: RawParent, 50 | } 51 | 52 | impl ParentWindow { 53 | pub unsafe fn from_raw(parent: RawParent) -> ParentWindow { 54 | ParentWindow { parent } 55 | } 56 | 57 | pub fn as_raw(&self) -> RawParent { 58 | self.parent 59 | } 60 | } 61 | 62 | pub struct Size { 63 | pub width: f64, 64 | pub height: f64, 65 | } 66 | 67 | pub trait View: Sized + 'static { 68 | fn size(&self) -> Size; 69 | fn param_changed(&mut self, id: ParamId, value: ParamValue); 70 | } 71 | 72 | pub struct NoView; 73 | 74 | impl View for NoView { 75 | fn size(&self) -> Size { 76 | Size { 77 | width: 0.0, 78 | height: 0.0, 79 | } 80 | } 81 | 82 | fn param_changed(&mut self, _id: ParamId, _value: ParamValue) {} 83 | } 84 | -------------------------------------------------------------------------------- /src/events.rs: -------------------------------------------------------------------------------- 1 | use std::ops::{Index, RangeBounds}; 2 | use std::slice; 3 | 4 | use crate::params::{ParamId, ParamValue}; 5 | 6 | #[derive(Copy, Clone, Debug)] 7 | pub struct Event { 8 | pub time: i64, 9 | pub data: Data, 10 | } 11 | 12 | #[derive(Copy, Clone, Debug)] 13 | #[non_exhaustive] 14 | pub enum Data { 15 | ParamChange { id: ParamId, value: ParamValue }, 16 | } 17 | 18 | #[derive(Copy, Clone)] 19 | pub struct Events<'a> { 20 | events: &'a [Event], 21 | } 22 | 23 | impl<'a> Events<'a> { 24 | #[inline] 25 | pub fn new(events: &'a [Event]) -> Events<'a> { 26 | Events { events } 27 | } 28 | 29 | #[inline] 30 | pub fn len(&self) -> usize { 31 | self.events.len() 32 | } 33 | 34 | #[inline] 35 | pub fn is_empty(&self) -> bool { 36 | self.events.len() == 0 37 | } 38 | 39 | #[inline] 40 | pub fn get(&self, index: usize) -> Option<&'a Event> { 41 | self.events.get(index) 42 | } 43 | 44 | #[inline] 45 | pub fn slice(&self, range: R) -> Option> 46 | where 47 | R: RangeBounds, 48 | { 49 | let range = (range.start_bound().cloned(), range.end_bound().cloned()); 50 | let events = self.events.get(range)?; 51 | 52 | Some(Events { events }) 53 | } 54 | } 55 | 56 | impl<'a> Index for Events<'a> { 57 | type Output = Event; 58 | 59 | fn index(&self, index: usize) -> &Event { 60 | &self.events[index] 61 | } 62 | } 63 | 64 | impl<'a> IntoIterator for Events<'a> { 65 | type Item = &'a Event; 66 | type IntoIter = Iter<'a>; 67 | 68 | #[inline] 69 | fn into_iter(self) -> Self::IntoIter { 70 | Iter { 71 | iter: self.events.iter(), 72 | } 73 | } 74 | } 75 | 76 | pub struct Iter<'a> { 77 | iter: slice::Iter<'a, Event>, 78 | } 79 | 80 | impl<'a> Iterator for Iter<'a> { 81 | type Item = &'a Event; 82 | 83 | #[inline] 84 | fn next(&mut self) -> Option { 85 | self.iter.next() 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/plugin.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::{self, Formatter}; 2 | use std::io::{self, Read, Write}; 3 | 4 | use crate::bus::{BusInfo, Layout}; 5 | use crate::engine::{Config, Engine}; 6 | use crate::host::Host; 7 | use crate::params::{ParamId, ParamInfo, ParamValue}; 8 | use crate::view::{ParentWindow, View, ViewHost}; 9 | 10 | pub struct PluginInfo { 11 | pub name: String, 12 | pub version: String, 13 | pub vendor: String, 14 | pub url: String, 15 | pub email: String, 16 | pub buses: Vec, 17 | pub layouts: Vec, 18 | pub params: Vec, 19 | pub has_view: bool, 20 | } 21 | 22 | #[allow(clippy::derivable_impls)] 23 | impl Default for PluginInfo { 24 | fn default() -> PluginInfo { 25 | PluginInfo { 26 | name: String::new(), 27 | version: String::new(), 28 | vendor: String::new(), 29 | url: String::new(), 30 | email: String::new(), 31 | buses: Vec::new(), 32 | layouts: Vec::new(), 33 | params: Vec::new(), 34 | has_view: false, 35 | } 36 | } 37 | } 38 | 39 | pub trait Plugin: Send + Sized + 'static { 40 | type Engine: Engine; 41 | type View: View; 42 | 43 | fn info() -> PluginInfo; 44 | fn new(host: Host) -> Self; 45 | fn set_param(&mut self, id: ParamId, value: ParamValue); 46 | fn get_param(&self, id: ParamId) -> ParamValue; 47 | fn parse_param(&self, id: ParamId, text: &str) -> Option; 48 | fn display_param( 49 | &self, 50 | id: ParamId, 51 | value: ParamValue, 52 | fmt: &mut Formatter, 53 | ) -> Result<(), fmt::Error>; 54 | fn save(&self, output: impl Write) -> io::Result<()>; 55 | fn load(&mut self, input: impl Read) -> io::Result<()>; 56 | fn engine(&mut self, config: &Config) -> Self::Engine; 57 | fn view(&mut self, host: ViewHost, parent: &ParentWindow) -> Self::View; 58 | 59 | #[allow(unused_variables)] 60 | fn latency(&self, config: &Config) -> u64 { 61 | 0 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/format/clap/mod.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::{c_char, c_void}; 2 | 3 | use clap_sys::{entry::*, version::*}; 4 | 5 | mod factory; 6 | mod gui; 7 | mod host; 8 | mod instance; 9 | 10 | #[cfg(test)] 11 | mod tests; 12 | 13 | #[doc(hidden)] 14 | pub use factory::Factory; 15 | 16 | pub struct ClapInfo { 17 | pub id: String, 18 | } 19 | 20 | pub trait ClapPlugin { 21 | fn clap_info() -> ClapInfo; 22 | } 23 | 24 | #[doc(hidden)] 25 | #[repr(transparent)] 26 | pub struct EntryPoint { 27 | #[allow(unused)] 28 | entry: clap_plugin_entry, 29 | } 30 | 31 | impl EntryPoint { 32 | pub const fn new( 33 | init: unsafe extern "C" fn(_plugin_path: *const c_char) -> bool, 34 | deinit: unsafe extern "C" fn(), 35 | get_factory: unsafe extern "C" fn(factory_id: *const c_char) -> *const c_void, 36 | ) -> EntryPoint { 37 | EntryPoint { 38 | entry: clap_plugin_entry { 39 | clap_version: CLAP_VERSION, 40 | init: Some(init), 41 | deinit: Some(deinit), 42 | get_factory: Some(get_factory), 43 | }, 44 | } 45 | } 46 | } 47 | 48 | #[macro_export] 49 | macro_rules! clap { 50 | ($plugin:ty) => { 51 | #[allow(non_upper_case_globals)] 52 | #[no_mangle] 53 | static clap_entry: ::coupler::format::clap::EntryPoint = { 54 | static FACTORY: ::coupler::format::clap::Factory<$plugin> = 55 | ::coupler::format::clap::Factory::new(); 56 | 57 | unsafe extern "C" fn init(_plugin_path: *const ::std::ffi::c_char) -> bool { 58 | FACTORY.init() 59 | } 60 | 61 | unsafe extern "C" fn deinit() { 62 | FACTORY.deinit(); 63 | } 64 | 65 | unsafe extern "C" fn get_factory( 66 | factory_id: *const ::std::ffi::c_char, 67 | ) -> *const ::std::ffi::c_void { 68 | FACTORY.get(factory_id) 69 | } 70 | 71 | ::coupler::format::clap::EntryPoint::new(init, deinit, get_factory) 72 | }; 73 | }; 74 | } 75 | -------------------------------------------------------------------------------- /src/format/vst3/mod.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_snake_case)] 2 | 3 | use std::ffi::c_void; 4 | 5 | use vst3::{ComWrapper, Steinberg::IPluginFactory}; 6 | 7 | mod buffers; 8 | mod component; 9 | mod factory; 10 | mod host; 11 | mod util; 12 | mod view; 13 | 14 | #[cfg(test)] 15 | mod tests; 16 | 17 | use crate::plugin::Plugin; 18 | use factory::Factory; 19 | 20 | pub struct Uuid(pub u32, pub u32, pub u32, pub u32); 21 | 22 | impl Uuid { 23 | pub fn from_name(name: &str) -> Uuid { 24 | const NAMESPACE_COUPLER: uuid::Uuid = uuid::Uuid::from_bytes([ 25 | 0xad, 0xf0, 0x07, 0x9f, 0x40, 0x7f, 0x49, 0xcc, 0xb1, 0x0d, 0x2d, 0x37, 0x63, 0x36, 26 | 0x57, 0x58, 27 | ]); 28 | 29 | let uuid = uuid::Uuid::new_v5(&NAMESPACE_COUPLER, name.as_bytes()); 30 | let bytes = uuid.as_bytes(); 31 | Uuid( 32 | u32::from_be_bytes(bytes[0..4].try_into().unwrap()), 33 | u32::from_be_bytes(bytes[4..8].try_into().unwrap()), 34 | u32::from_be_bytes(bytes[8..12].try_into().unwrap()), 35 | u32::from_be_bytes(bytes[12..16].try_into().unwrap()), 36 | ) 37 | } 38 | } 39 | 40 | pub struct Vst3Info { 41 | pub class_id: Uuid, 42 | } 43 | 44 | pub trait Vst3Plugin { 45 | fn vst3_info() -> Vst3Info; 46 | } 47 | 48 | #[doc(hidden)] 49 | pub fn get_plugin_factory() -> *mut c_void { 50 | ComWrapper::new(Factory::

::new()) 51 | .to_com_ptr::() 52 | .unwrap() 53 | .into_raw() as *mut c_void 54 | } 55 | 56 | #[macro_export] 57 | macro_rules! vst3 { 58 | ($plugin:ty) => { 59 | #[cfg(target_os = "windows")] 60 | #[no_mangle] 61 | extern "system" fn InitDll() -> bool { 62 | true 63 | } 64 | 65 | #[cfg(target_os = "windows")] 66 | #[no_mangle] 67 | extern "system" fn ExitDll() -> bool { 68 | true 69 | } 70 | 71 | #[cfg(target_os = "macos")] 72 | #[no_mangle] 73 | extern "system" fn BundleEntry(_bundle_ref: *mut ::std::ffi::c_void) -> bool { 74 | true 75 | } 76 | 77 | #[cfg(target_os = "macos")] 78 | #[no_mangle] 79 | extern "system" fn BundleExit() -> bool { 80 | true 81 | } 82 | 83 | #[cfg(target_os = "linux")] 84 | #[no_mangle] 85 | extern "system" fn ModuleEntry(_library_handle: *mut ::std::ffi::c_void) -> bool { 86 | true 87 | } 88 | 89 | #[cfg(target_os = "linux")] 90 | #[no_mangle] 91 | extern "system" fn ModuleExit() -> bool { 92 | true 93 | } 94 | 95 | #[no_mangle] 96 | extern "system" fn GetPluginFactory() -> *mut ::std::ffi::c_void { 97 | ::coupler::format::vst3::get_plugin_factory::<$plugin>() 98 | } 99 | }; 100 | } 101 | -------------------------------------------------------------------------------- /src/buffers/convert.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error; 2 | use std::{array, fmt, slice}; 3 | 4 | use super::{AnyBuffer, Buffer, BufferMut, Buffers}; 5 | 6 | impl<'a, 'b> TryFrom> for Buffer<'a, 'b> { 7 | type Error = AnyBuffer<'a, 'b>; 8 | 9 | #[inline] 10 | fn try_from(value: AnyBuffer<'a, 'b>) -> Result, Self::Error> { 11 | match value { 12 | AnyBuffer::Const(buffer) => Ok(buffer), 13 | _ => Err(value), 14 | } 15 | } 16 | } 17 | 18 | impl<'a, 'b> TryFrom> for BufferMut<'a, 'b> { 19 | type Error = AnyBuffer<'a, 'b>; 20 | 21 | #[inline] 22 | fn try_from(value: AnyBuffer<'a, 'b>) -> Result, Self::Error> { 23 | match value { 24 | AnyBuffer::Mut(buffer) => Ok(buffer), 25 | _ => Err(value), 26 | } 27 | } 28 | } 29 | 30 | impl<'a, 'b, const N: usize> TryFrom> for [&'b [f32]; N] { 31 | type Error = Buffer<'a, 'b>; 32 | 33 | #[inline] 34 | fn try_from(value: Buffer<'a, 'b>) -> Result<[&'b [f32]; N], Self::Error> { 35 | if value.channel_count() == N { 36 | Ok(array::from_fn(|i| unsafe { 37 | slice::from_raw_parts(value.ptrs[i].offset(value.offset), value.len) 38 | })) 39 | } else { 40 | Err(value) 41 | } 42 | } 43 | } 44 | 45 | impl<'a, 'b, const N: usize> TryFrom> for [&'b mut [f32]; N] { 46 | type Error = BufferMut<'a, 'b>; 47 | 48 | #[inline] 49 | fn try_from(value: BufferMut<'a, 'b>) -> Result<[&'b mut [f32]; N], Self::Error> { 50 | if value.channel_count() == N { 51 | Ok(array::from_fn(|i| unsafe { 52 | slice::from_raw_parts_mut(value.ptrs[i].offset(value.offset), value.len) 53 | })) 54 | } else { 55 | Err(value) 56 | } 57 | } 58 | } 59 | 60 | #[derive(Debug)] 61 | pub struct TryFromBuffersError; 62 | 63 | impl fmt::Display for TryFromBuffersError { 64 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 65 | "buffer layout does not match".fmt(fmt) 66 | } 67 | } 68 | 69 | impl Error for TryFromBuffersError {} 70 | 71 | macro_rules! try_from_buffers { 72 | ($($buffer:ident),*) => { 73 | impl<'a, 'b, $($buffer),*> TryFrom> for ($($buffer,)*) 74 | where 75 | $($buffer: TryFrom>),* 76 | { 77 | type Error = TryFromBuffersError; 78 | 79 | #[inline] 80 | fn try_from(value: Buffers<'a, 'b>) -> Result { 81 | let mut iter = value.into_iter(); 82 | 83 | let result = ( 84 | $({ 85 | let next = iter.next().ok_or(TryFromBuffersError)?; 86 | $buffer::try_from(next).map_err(|_| TryFromBuffersError)? 87 | },)* 88 | ); 89 | 90 | if iter.next().is_none() { 91 | Ok(result) 92 | } else { 93 | Err(TryFromBuffersError) 94 | } 95 | } 96 | } 97 | } 98 | } 99 | 100 | try_from_buffers!(); 101 | try_from_buffers!(B0); 102 | try_from_buffers!(B0, B1); 103 | try_from_buffers!(B0, B1, B2); 104 | try_from_buffers!(B0, B1, B2, B3); 105 | try_from_buffers!(B0, B1, B2, B3, B4); 106 | try_from_buffers!(B0, B1, B2, B3, B4, B5); 107 | try_from_buffers!(B0, B1, B2, B3, B4, B5, B6); 108 | try_from_buffers!(B0, B1, B2, B3, B4, B5, B6, B7); 109 | try_from_buffers!(B0, B1, B2, B3, B4, B5, B6, B7, B8); 110 | try_from_buffers!(B0, B1, B2, B3, B4, B5, B6, B7, B8, B9); 111 | -------------------------------------------------------------------------------- /src/format/clap/factory.rs: -------------------------------------------------------------------------------- 1 | use std::cell::UnsafeCell; 2 | use std::ffi::{c_char, c_void, CStr, CString}; 3 | use std::marker::PhantomData; 4 | use std::ptr; 5 | use std::sync::Arc; 6 | 7 | use clap_sys::{host::*, plugin::*, plugin_factory::*, version::*}; 8 | 9 | use super::instance::Instance; 10 | use super::ClapPlugin; 11 | use crate::plugin::{Plugin, PluginInfo}; 12 | 13 | struct FactoryState { 14 | descriptor: clap_plugin_descriptor, 15 | info: Arc, 16 | } 17 | 18 | #[doc(hidden)] 19 | #[repr(C)] 20 | pub struct Factory

{ 21 | #[allow(unused)] 22 | factory: clap_plugin_factory, 23 | state: UnsafeCell>, 24 | _marker: PhantomData

, 25 | } 26 | 27 | unsafe impl

Sync for Factory

{} 28 | 29 | #[doc(hidden)] 30 | impl Factory

{ 31 | #[allow(clippy::new_without_default)] 32 | pub const fn new() -> Self { 33 | Factory { 34 | factory: clap_plugin_factory { 35 | get_plugin_count: Some(Self::get_plugin_count), 36 | get_plugin_descriptor: Some(Self::get_plugin_descriptor), 37 | create_plugin: Some(Self::create_plugin), 38 | }, 39 | state: UnsafeCell::new(None), 40 | _marker: PhantomData, 41 | } 42 | } 43 | 44 | pub unsafe fn init(&self) -> bool { 45 | let info = Arc::new(P::info()); 46 | let clap_info = P::clap_info(); 47 | 48 | let id = CString::new(&*clap_info.id).unwrap().into_raw(); 49 | let name = CString::new(&*info.name).unwrap().into_raw(); 50 | let vendor = CString::new(&*info.vendor).unwrap().into_raw(); 51 | let url = CString::new(&*info.url).unwrap().into_raw(); 52 | let version = CString::new(&*info.version).unwrap().into_raw(); 53 | 54 | const EMPTY: &CStr = unsafe { CStr::from_bytes_with_nul_unchecked(b"\0") }; 55 | const FEATURES: &[*const c_char] = &[ptr::null()]; 56 | 57 | *self.state.get() = Some(FactoryState { 58 | descriptor: clap_plugin_descriptor { 59 | clap_version: CLAP_VERSION, 60 | id, 61 | name, 62 | vendor, 63 | url, 64 | manual_url: EMPTY.as_ptr(), 65 | support_url: EMPTY.as_ptr(), 66 | version, 67 | description: EMPTY.as_ptr(), 68 | features: FEATURES.as_ptr(), 69 | }, 70 | info, 71 | }); 72 | 73 | true 74 | } 75 | 76 | pub unsafe fn deinit(&self) { 77 | if let Some(state) = (*self.state.get()).take() { 78 | drop(CString::from_raw(state.descriptor.id as *mut c_char)); 79 | drop(CString::from_raw(state.descriptor.name as *mut c_char)); 80 | drop(CString::from_raw(state.descriptor.vendor as *mut c_char)); 81 | drop(CString::from_raw(state.descriptor.url as *mut c_char)); 82 | drop(CString::from_raw(state.descriptor.version as *mut c_char)); 83 | } 84 | } 85 | 86 | pub unsafe fn get(&self, factory_id: *const c_char) -> *const c_void { 87 | if CStr::from_ptr(factory_id) == CLAP_PLUGIN_FACTORY_ID { 88 | return self as *const Self as *const c_void; 89 | } 90 | 91 | ptr::null() 92 | } 93 | 94 | unsafe extern "C" fn get_plugin_count(_factory: *const clap_plugin_factory) -> u32 { 95 | 1 96 | } 97 | 98 | unsafe extern "C" fn get_plugin_descriptor( 99 | factory: *const clap_plugin_factory, 100 | index: u32, 101 | ) -> *const clap_plugin_descriptor { 102 | let factory = &*(factory as *const Self); 103 | 104 | if index == 0 { 105 | if let Some(state) = &*factory.state.get() { 106 | return &state.descriptor; 107 | } 108 | } 109 | 110 | ptr::null() 111 | } 112 | 113 | unsafe extern "C" fn create_plugin( 114 | factory: *const clap_plugin_factory, 115 | host: *const clap_host, 116 | plugin_id: *const c_char, 117 | ) -> *const clap_plugin { 118 | let factory = &*(factory as *const Self); 119 | 120 | if let Some(state) = &*factory.state.get() { 121 | if CStr::from_ptr(plugin_id) == CStr::from_ptr(state.descriptor.id) { 122 | let instance = Box::new(Instance::

::new(&state.descriptor, &state.info, host)); 123 | return Box::into_raw(instance) as *const clap_plugin; 124 | } 125 | } 126 | 127 | ptr::null() 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/format/vst3/factory.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::{c_void, CStr}; 2 | use std::marker::PhantomData; 3 | use std::sync::Arc; 4 | 5 | use vst3::{uid, Class, ComWrapper, Steinberg::Vst::*, Steinberg::*}; 6 | 7 | use super::component::Component; 8 | use super::util::copy_wstring; 9 | use super::{Uuid, Vst3Info, Vst3Plugin}; 10 | use crate::plugin::{Plugin, PluginInfo}; 11 | use crate::util::copy_cstring; 12 | 13 | fn uuid_to_tuid(uuid: &Uuid) -> TUID { 14 | uid(uuid.0, uuid.1, uuid.2, uuid.3) 15 | } 16 | 17 | pub struct Factory

{ 18 | info: Arc, 19 | vst3_info: Vst3Info, 20 | _marker: PhantomData

, 21 | } 22 | 23 | impl Factory

{ 24 | pub fn new() -> Factory

{ 25 | Factory { 26 | info: Arc::new(P::info()), 27 | vst3_info: P::vst3_info(), 28 | _marker: PhantomData, 29 | } 30 | } 31 | } 32 | 33 | impl Class for Factory

{ 34 | type Interfaces = (IPluginFactory3,); 35 | } 36 | 37 | impl IPluginFactoryTrait for Factory

{ 38 | unsafe fn getFactoryInfo(&self, info: *mut PFactoryInfo) -> tresult { 39 | let info = &mut *info; 40 | 41 | copy_cstring(&self.info.vendor, &mut info.vendor); 42 | copy_cstring(&self.info.url, &mut info.url); 43 | copy_cstring(&self.info.email, &mut info.email); 44 | info.flags = PFactoryInfo_::FactoryFlags_::kUnicode as int32; 45 | 46 | kResultOk 47 | } 48 | 49 | unsafe fn countClasses(&self) -> int32 { 50 | 1 51 | } 52 | 53 | unsafe fn getClassInfo(&self, index: int32, info: *mut PClassInfo) -> tresult { 54 | if index == 0 { 55 | let info = &mut *info; 56 | 57 | info.cid = uuid_to_tuid(&self.vst3_info.class_id); 58 | info.cardinality = PClassInfo_::ClassCardinality_::kManyInstances as int32; 59 | copy_cstring("Audio Module Class", &mut info.category); 60 | copy_cstring(&self.info.name, &mut info.name); 61 | 62 | return kResultOk; 63 | } 64 | 65 | kInvalidArgument 66 | } 67 | 68 | unsafe fn createInstance( 69 | &self, 70 | cid: FIDString, 71 | iid: FIDString, 72 | obj: *mut *mut c_void, 73 | ) -> tresult { 74 | let cid = &*(cid as *const TUID); 75 | let class_id = uuid_to_tuid(&self.vst3_info.class_id); 76 | if cid == &class_id { 77 | let component = ComWrapper::new(Component::

::new(&self.info)); 78 | let unknown = component.as_com_ref::().unwrap(); 79 | let ptr = unknown.as_ptr(); 80 | return ((*(*ptr).vtbl).queryInterface)(ptr, iid as *const TUID, obj); 81 | } 82 | 83 | kInvalidArgument 84 | } 85 | } 86 | 87 | impl IPluginFactory2Trait for Factory

{ 88 | unsafe fn getClassInfo2(&self, index: int32, info: *mut PClassInfo2) -> tresult { 89 | if index == 0 { 90 | let info = &mut *info; 91 | 92 | info.cid = uuid_to_tuid(&self.vst3_info.class_id); 93 | info.cardinality = PClassInfo_::ClassCardinality_::kManyInstances as int32; 94 | copy_cstring("Audio Module Class", &mut info.category); 95 | copy_cstring(&self.info.name, &mut info.name); 96 | info.classFlags = 0; 97 | copy_cstring("Fx", &mut info.subCategories); 98 | copy_cstring(&self.info.vendor, &mut info.vendor); 99 | copy_cstring(&self.info.version, &mut info.version); 100 | let version_str = CStr::from_ptr(SDKVersionString).to_str().unwrap(); 101 | copy_cstring(version_str, &mut info.sdkVersion); 102 | 103 | return kResultOk; 104 | } 105 | 106 | kInvalidArgument 107 | } 108 | } 109 | 110 | impl IPluginFactory3Trait for Factory

{ 111 | unsafe fn getClassInfoUnicode(&self, index: int32, info: *mut PClassInfoW) -> tresult { 112 | if index == 0 { 113 | let info = &mut *info; 114 | 115 | info.cid = uuid_to_tuid(&self.vst3_info.class_id); 116 | info.cardinality = PClassInfo_::ClassCardinality_::kManyInstances as int32; 117 | copy_cstring("Audio Module Class", &mut info.category); 118 | copy_wstring(&self.info.name, &mut info.name); 119 | info.classFlags = 0; 120 | copy_cstring("Fx", &mut info.subCategories); 121 | copy_wstring(&self.info.vendor, &mut info.vendor); 122 | copy_wstring(&self.info.version, &mut info.version); 123 | let version_str = CStr::from_ptr(SDKVersionString).to_str().unwrap(); 124 | copy_wstring(version_str, &mut info.sdkVersion); 125 | 126 | return kResultOk; 127 | } 128 | 129 | kInvalidArgument 130 | } 131 | 132 | unsafe fn setHostContext(&self, _context: *mut FUnknown) -> tresult { 133 | kResultOk 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/format/clap/tests.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::{c_char, CStr}; 2 | use std::fmt::{self, Formatter}; 3 | use std::io::{self, Read, Write}; 4 | 5 | use crate::buffers::Buffers; 6 | use crate::events::Events; 7 | use crate::view::{ParentWindow, Size, View, ViewHost}; 8 | 9 | use clap_sys::plugin_factory::{clap_plugin_factory, CLAP_PLUGIN_FACTORY_ID}; 10 | use clap_sys::version::CLAP_VERSION; 11 | 12 | use crate::engine::{Config, Engine}; 13 | use crate::host::Host; 14 | use crate::params::{ParamId, ParamValue}; 15 | use crate::plugin::{Plugin, PluginInfo}; 16 | 17 | use super::{ClapInfo, ClapPlugin, Factory}; 18 | 19 | const NAME: &str = "test plugin"; 20 | const VERSION: &str = "1.2.3"; 21 | const VENDOR: &str = "test vendor"; 22 | const URL: &str = "https://example.com/"; 23 | const EMAIL: &str = "example@example.com"; 24 | const ID: &str = "com.example.plugin"; 25 | 26 | struct TestPlugin; 27 | 28 | impl Plugin for TestPlugin { 29 | type Engine = TestEngine; 30 | type View = TestView; 31 | 32 | fn info() -> PluginInfo { 33 | PluginInfo { 34 | name: NAME.to_string(), 35 | version: VERSION.to_string(), 36 | vendor: VENDOR.to_string(), 37 | url: URL.to_string(), 38 | email: EMAIL.to_string(), 39 | buses: Vec::new(), 40 | layouts: vec![], 41 | params: Vec::new(), 42 | has_view: false, 43 | } 44 | } 45 | fn new(_host: Host) -> Self { 46 | TestPlugin 47 | } 48 | fn set_param(&mut self, _id: ParamId, _value: ParamValue) {} 49 | fn get_param(&self, _id: ParamId) -> ParamValue { 50 | 0.0 51 | } 52 | fn parse_param(&self, _id: ParamId, _text: &str) -> Option { 53 | None 54 | } 55 | fn display_param( 56 | &self, 57 | _id: ParamId, 58 | _value: ParamValue, 59 | _fmt: &mut Formatter, 60 | ) -> Result<(), fmt::Error> { 61 | Ok(()) 62 | } 63 | fn save(&self, _output: impl Write) -> io::Result<()> { 64 | Ok(()) 65 | } 66 | fn load(&mut self, _input: impl Read) -> io::Result<()> { 67 | Ok(()) 68 | } 69 | fn engine(&mut self, _config: &Config) -> Self::Engine { 70 | TestEngine 71 | } 72 | fn view(&mut self, _host: ViewHost, _parent: &ParentWindow) -> Self::View { 73 | TestView 74 | } 75 | 76 | #[allow(unused_variables)] 77 | fn latency(&self, _config: &Config) -> u64 { 78 | 0 79 | } 80 | } 81 | 82 | impl ClapPlugin for TestPlugin { 83 | fn clap_info() -> ClapInfo { 84 | ClapInfo { id: ID.to_string() } 85 | } 86 | } 87 | 88 | struct TestEngine; 89 | 90 | impl Engine for TestEngine { 91 | fn reset(&mut self) {} 92 | fn flush(&mut self, _events: Events) {} 93 | fn process(&mut self, _buffers: Buffers, _events: Events) {} 94 | } 95 | 96 | struct TestView; 97 | 98 | impl View for TestView { 99 | fn size(&self) -> Size { 100 | Size { 101 | width: 0.0, 102 | height: 0.0, 103 | } 104 | } 105 | fn param_changed(&mut self, _id: ParamId, _value: ParamValue) {} 106 | } 107 | 108 | unsafe fn str_from_ptr<'a>(ptr: *const c_char) -> Result<&'a str, std::str::Utf8Error> { 109 | CStr::from_ptr(ptr).to_str() 110 | } 111 | 112 | #[test] 113 | fn factory() { 114 | let factory = Factory::::new(); 115 | 116 | let result = unsafe { factory.init() }; 117 | assert!(result); 118 | 119 | let plugin_factory = 120 | unsafe { factory.get(CLAP_PLUGIN_FACTORY_ID.as_ptr()) as *const clap_plugin_factory }; 121 | 122 | let plugin_count = unsafe { ((*plugin_factory).get_plugin_count).unwrap()(plugin_factory) }; 123 | assert_eq!(plugin_count, 1); 124 | 125 | let desc_ptr = unsafe { ((*plugin_factory).get_plugin_descriptor).unwrap()(plugin_factory, 1) }; 126 | assert!(desc_ptr.is_null()); 127 | 128 | let desc_ptr = unsafe { ((*plugin_factory).get_plugin_descriptor).unwrap()(plugin_factory, 0) }; 129 | assert!(!desc_ptr.is_null()); 130 | 131 | let desc = unsafe { &*desc_ptr }; 132 | assert_eq!(desc.clap_version.major, CLAP_VERSION.major); 133 | assert_eq!(desc.clap_version.minor, CLAP_VERSION.minor); 134 | assert_eq!(desc.clap_version.revision, CLAP_VERSION.revision); 135 | assert_eq!(unsafe { str_from_ptr(desc.id).unwrap() }, ID); 136 | assert_eq!(unsafe { str_from_ptr(desc.name).unwrap() }, NAME); 137 | assert_eq!(unsafe { str_from_ptr(desc.vendor).unwrap() }, VENDOR); 138 | assert_eq!(unsafe { str_from_ptr(desc.url).unwrap() }, URL); 139 | assert_eq!(unsafe { str_from_ptr(desc.manual_url).unwrap() }, ""); 140 | assert_eq!(unsafe { str_from_ptr(desc.support_url).unwrap() }, ""); 141 | assert_eq!(unsafe { str_from_ptr(desc.version).unwrap() }, VERSION); 142 | assert_eq!(unsafe { str_from_ptr(desc.description).unwrap() }, ""); 143 | assert!(unsafe { *desc.features }.is_null()); 144 | 145 | unsafe { factory.deinit() }; 146 | } 147 | -------------------------------------------------------------------------------- /coupler-derive/src/enum_.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream; 2 | use quote::quote; 3 | use syn::{Data, DataEnum, DeriveInput, Error, Fields, LitStr}; 4 | 5 | fn parse_names(body: &DataEnum) -> Result, Error> { 6 | let mut names = Vec::new(); 7 | 8 | for variant in &body.variants { 9 | let mut name = None; 10 | 11 | for attr in &variant.attrs { 12 | if attr.path().is_ident("name") { 13 | if name.is_some() { 14 | return Err(Error::new_spanned( 15 | attr.path(), 16 | "duplicate `name` attribute", 17 | )); 18 | } 19 | 20 | name = Some(attr.parse_args::()?); 21 | } 22 | } 23 | 24 | let name = if let Some(name) = name { 25 | name 26 | } else { 27 | LitStr::new(&variant.ident.to_string(), variant.ident.span()) 28 | }; 29 | 30 | names.push(name); 31 | } 32 | 33 | Ok(names) 34 | } 35 | 36 | pub fn expand_enum(input: &DeriveInput) -> Result { 37 | let body = match &input.data { 38 | Data::Enum(body) => body, 39 | _ => { 40 | return Err(Error::new_spanned( 41 | input, 42 | "#[derive(Enum)] can only be used on enums", 43 | )); 44 | } 45 | }; 46 | 47 | if body.variants.is_empty() { 48 | return Err(Error::new_spanned( 49 | input, 50 | "#[derive(Enum)] cannot be used on empty enums", 51 | )); 52 | } 53 | 54 | for variant in &body.variants { 55 | match &variant.fields { 56 | Fields::Unit => {} 57 | _ => { 58 | return Err(Error::new_spanned( 59 | variant, 60 | "#[derive(Enum)] can only be used on fieldless enums", 61 | )); 62 | } 63 | } 64 | } 65 | 66 | let names = parse_names(body)?; 67 | 68 | let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); 69 | let ident = &input.ident; 70 | 71 | let count = body.variants.len() as u32; 72 | 73 | let encode_cases = body.variants.iter().enumerate().map(|(index, variant)| { 74 | let index = index as u32; 75 | let variant = &variant.ident; 76 | quote! { 77 | #ident::#variant => (#index as ::std::primitive::f64 + 0.5) / #count as ::std::primitive::f64, 78 | } 79 | }); 80 | 81 | let decode_cases = body.variants.iter().enumerate().map(|(index, variant)| { 82 | let index = index as u32; 83 | let variant = &variant.ident; 84 | quote! { 85 | #index => #ident::#variant, 86 | } 87 | }); 88 | let last_variant = &body.variants.last().as_ref().unwrap().ident; 89 | 90 | let from_str_cases = body.variants.iter().zip(names.iter()).map(|(variant, name)| { 91 | let variant = &variant.ident; 92 | quote! { 93 | #name => ::std::result::Result::Ok(#ident::#variant), 94 | } 95 | }); 96 | 97 | let fmt_cases = body.variants.iter().zip(names.iter()).map(|(variant, name)| { 98 | let variant = &variant.ident; 99 | quote! { 100 | #ident::#variant => #name, 101 | } 102 | }); 103 | 104 | Ok(quote! { 105 | impl #impl_generics ::coupler::params::Encode for #ident #ty_generics #where_clause { 106 | fn steps() -> Option { 107 | Some(#count) 108 | } 109 | 110 | fn encode(&self) -> ParamValue { 111 | match self { 112 | #(#encode_cases)* 113 | } 114 | } 115 | 116 | fn decode(__value: ParamValue) -> Self { 117 | match (__value * #count as ::std::primitive::f64) as ::std::primitive::u32 { 118 | #(#decode_cases)* 119 | _ => #ident::#last_variant, 120 | } 121 | } 122 | } 123 | 124 | impl #impl_generics ::std::str::FromStr for #ident #ty_generics #where_clause { 125 | type Err = (); 126 | 127 | fn from_str(__str: &str) -> Result { 128 | match __str { 129 | #(#from_str_cases)* 130 | _ => ::std::result::Result::Err(()), 131 | } 132 | } 133 | } 134 | 135 | impl #impl_generics ::std::fmt::Display for #ident #ty_generics #where_clause { 136 | fn fmt(&self, __formatter: &mut ::std::fmt::Formatter<'_>) -> ::std::result::Result<(), ::std::fmt::Error> { 137 | ::std::fmt::Formatter::write_str(__formatter, match self { 138 | #(#fmt_cases)* 139 | }) 140 | } 141 | } 142 | 143 | impl #impl_generics ::coupler::params::Enum for #ident #ty_generics #where_clause {} 144 | }) 145 | } 146 | -------------------------------------------------------------------------------- /assets/coupler-banner.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /src/format/vst3/view.rs: -------------------------------------------------------------------------------- 1 | use std::cell::{RefCell, UnsafeCell}; 2 | use std::ffi::{c_void, CStr}; 3 | use std::sync::Arc; 4 | 5 | use vst3::Steinberg::Vst::{IComponentHandler, IComponentHandlerTrait}; 6 | use vst3::{Class, ComPtr, Steinberg::*}; 7 | 8 | use super::component::MainThreadState; 9 | use crate::params::{ParamId, ParamValue}; 10 | use crate::plugin::Plugin; 11 | use crate::view::{ParentWindow, RawParent, View, ViewHost, ViewHostInner}; 12 | 13 | pub struct Vst3ViewHost { 14 | pub handler: RefCell>>, 15 | } 16 | 17 | impl Vst3ViewHost { 18 | pub fn new() -> Vst3ViewHost { 19 | Vst3ViewHost { 20 | handler: RefCell::new(None), 21 | } 22 | } 23 | } 24 | 25 | impl ViewHostInner for Vst3ViewHost { 26 | fn begin_gesture(&self, id: ParamId) { 27 | let handler = self.handler.borrow(); 28 | if let Some(handler) = &*handler { 29 | unsafe { 30 | handler.beginEdit(id); 31 | } 32 | } 33 | } 34 | 35 | fn end_gesture(&self, id: ParamId) { 36 | let handler = self.handler.borrow(); 37 | if let Some(handler) = &*handler { 38 | unsafe { 39 | handler.endEdit(id); 40 | } 41 | } 42 | } 43 | 44 | fn set_param(&self, id: ParamId, value: ParamValue) { 45 | let handler = self.handler.borrow(); 46 | if let Some(handler) = &*handler { 47 | unsafe { 48 | handler.performEdit(id, value); 49 | } 50 | } 51 | } 52 | } 53 | 54 | pub struct PlugView { 55 | main_thread_state: Arc>>, 56 | } 57 | 58 | impl PlugView

{ 59 | pub fn new(main_thread_state: &Arc>>) -> PlugView

{ 60 | PlugView { 61 | main_thread_state: main_thread_state.clone(), 62 | } 63 | } 64 | } 65 | 66 | impl Class for PlugView

{ 67 | type Interfaces = (IPlugView,); 68 | } 69 | 70 | impl IPlugViewTrait for PlugView

{ 71 | unsafe fn isPlatformTypeSupported(&self, type_: FIDString) -> tresult { 72 | #[cfg(target_os = "windows")] 73 | if CStr::from_ptr(type_) == CStr::from_ptr(kPlatformTypeHWND) { 74 | return kResultTrue; 75 | } 76 | 77 | #[cfg(target_os = "macos")] 78 | if CStr::from_ptr(type_) == CStr::from_ptr(kPlatformTypeNSView) { 79 | return kResultTrue; 80 | } 81 | 82 | #[cfg(target_os = "linux")] 83 | if CStr::from_ptr(type_) == CStr::from_ptr(kPlatformTypeX11EmbedWindowID) { 84 | return kResultTrue; 85 | } 86 | 87 | kResultFalse 88 | } 89 | 90 | unsafe fn attached(&self, parent: *mut c_void, type_: FIDString) -> tresult { 91 | if self.isPlatformTypeSupported(type_) != kResultTrue { 92 | return kResultFalse; 93 | } 94 | 95 | #[cfg(target_os = "windows")] 96 | let raw_parent = RawParent::Win32(parent); 97 | 98 | #[cfg(target_os = "macos")] 99 | let raw_parent = RawParent::Cocoa(parent); 100 | 101 | #[cfg(target_os = "linux")] 102 | let raw_parent = RawParent::X11(parent as std::ffi::c_ulong); 103 | 104 | let main_thread_state = &mut *self.main_thread_state.get(); 105 | 106 | let host = ViewHost::from_inner(main_thread_state.view_host.clone()); 107 | let parent = ParentWindow::from_raw(raw_parent); 108 | let view = main_thread_state.plugin.view(host, &parent); 109 | main_thread_state.view = Some(view); 110 | 111 | kResultOk 112 | } 113 | 114 | unsafe fn removed(&self) -> tresult { 115 | let main_thread_state = &mut *self.main_thread_state.get(); 116 | 117 | main_thread_state.view = None; 118 | 119 | kResultOk 120 | } 121 | 122 | unsafe fn onWheel(&self, _distance: f32) -> tresult { 123 | kResultFalse 124 | } 125 | 126 | unsafe fn onKeyDown(&self, _key: char16, _keyCode: int16, _modifiers: int16) -> tresult { 127 | kResultFalse 128 | } 129 | 130 | unsafe fn onKeyUp(&self, _key: char16, _keyCode: int16, _modifiers: int16) -> tresult { 131 | kResultFalse 132 | } 133 | 134 | unsafe fn getSize(&self, size: *mut ViewRect) -> tresult { 135 | if size.is_null() { 136 | return kResultFalse; 137 | } 138 | 139 | let main_thread_state = &*self.main_thread_state.get(); 140 | 141 | if let Some(view) = &main_thread_state.view { 142 | let view_size = view.size(); 143 | 144 | let rect = &mut *size; 145 | rect.left = 0; 146 | rect.top = 0; 147 | rect.right = view_size.width.round() as int32; 148 | rect.bottom = view_size.height.round() as int32; 149 | 150 | return kResultOk; 151 | } 152 | 153 | kResultFalse 154 | } 155 | 156 | unsafe fn onSize(&self, _newSize: *mut ViewRect) -> tresult { 157 | kNotImplemented 158 | } 159 | 160 | unsafe fn onFocus(&self, _state: TBool) -> tresult { 161 | kResultFalse 162 | } 163 | 164 | unsafe fn setFrame(&self, _frame: *mut IPlugFrame) -> tresult { 165 | kNotImplemented 166 | } 167 | 168 | unsafe fn canResize(&self) -> tresult { 169 | kNotImplemented 170 | } 171 | 172 | unsafe fn checkSizeConstraint(&self, _rect: *mut ViewRect) -> tresult { 173 | kNotImplemented 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /src/params/range.rs: -------------------------------------------------------------------------------- 1 | use super::ParamValue; 2 | 3 | pub trait Range { 4 | fn steps(&self) -> Option; 5 | fn encode(&self, value: &T) -> ParamValue; 6 | fn decode(&self, value: ParamValue) -> T; 7 | } 8 | 9 | pub trait Encode { 10 | fn steps() -> Option; 11 | fn encode(&self) -> ParamValue; 12 | fn decode(value: ParamValue) -> Self; 13 | } 14 | 15 | #[derive(Copy, Clone)] 16 | pub struct Log(pub T); 17 | 18 | macro_rules! float_range { 19 | ($float:ty) => { 20 | impl Range<$float> for std::ops::Range<$float> { 21 | #[inline] 22 | fn steps(&self) -> Option { 23 | None 24 | } 25 | 26 | #[inline] 27 | fn encode(&self, value: &$float) -> ParamValue { 28 | ((value - self.start) / (self.end - self.start)) as f64 29 | } 30 | 31 | #[inline] 32 | fn decode(&self, value: ParamValue) -> $float { 33 | (1.0 - value as $float) * self.start + value as $float * self.end 34 | } 35 | } 36 | 37 | impl Range<$float> for std::ops::RangeInclusive<$float> { 38 | #[inline] 39 | fn steps(&self) -> Option { 40 | None 41 | } 42 | 43 | #[inline] 44 | fn encode(&self, value: &$float) -> ParamValue { 45 | ((value - self.start()) / (self.end() - self.start())) as f64 46 | } 47 | 48 | #[inline] 49 | fn decode(&self, value: ParamValue) -> $float { 50 | (1.0 - value as $float) * self.start() + value as $float * self.end() 51 | } 52 | } 53 | 54 | impl Range<$float> for Log> { 55 | #[inline] 56 | fn steps(&self) -> Option { 57 | None 58 | } 59 | 60 | #[inline] 61 | fn encode(&self, value: &$float) -> ParamValue { 62 | (value / self.0.start).log(self.0.end / self.0.start) as f64 63 | } 64 | 65 | #[inline] 66 | fn decode(&self, value: ParamValue) -> $float { 67 | self.0.start * (self.0.end / self.0.start).powf(value as $float) 68 | } 69 | } 70 | 71 | impl Range<$float> for Log> { 72 | #[inline] 73 | fn steps(&self) -> Option { 74 | None 75 | } 76 | 77 | #[inline] 78 | fn encode(&self, value: &$float) -> ParamValue { 79 | (value / self.0.start()).log(self.0.end() / self.0.start()) as f64 80 | } 81 | 82 | #[inline] 83 | fn decode(&self, value: ParamValue) -> $float { 84 | self.0.start() * (self.0.end() / self.0.start()).powf(value as $float) 85 | } 86 | } 87 | 88 | impl Encode for $float { 89 | fn steps() -> Option { 90 | (0.0..1.0).steps() 91 | } 92 | 93 | fn encode(&self) -> ParamValue { 94 | (0.0..1.0).encode(self) 95 | } 96 | 97 | fn decode(value: ParamValue) -> Self { 98 | (0.0..1.0).decode(value) 99 | } 100 | } 101 | }; 102 | } 103 | 104 | float_range!(f32); 105 | float_range!(f64); 106 | 107 | macro_rules! int_range { 108 | ($int:ty) => { 109 | impl Range<$int> for std::ops::Range<$int> { 110 | #[inline] 111 | fn steps(&self) -> Option { 112 | Some(self.end.abs_diff(self.start) as u32) 113 | } 114 | 115 | #[inline] 116 | fn encode(&self, value: &$int) -> ParamValue { 117 | let steps_recip = 1.0 / (self.end as f64 - self.start as f64); 118 | (*value as f64 - self.start as f64 + 0.5) * steps_recip 119 | } 120 | 121 | #[inline] 122 | fn decode(&self, value: ParamValue) -> $int { 123 | let steps = self.end as f64 - self.start as f64; 124 | (self.start as f64 + value * steps) as $int 125 | } 126 | } 127 | 128 | impl Range<$int> for std::ops::RangeInclusive<$int> { 129 | #[inline] 130 | fn steps(&self) -> Option { 131 | Some(self.end().abs_diff(*self.start()).saturating_add(1) as u32) 132 | } 133 | 134 | #[inline] 135 | fn encode(&self, value: &$int) -> ParamValue { 136 | let steps_recip = 1.0 / (*self.end() as f64 + 1.0 - *self.start() as f64); 137 | (*value as f64 - *self.start() as f64 + 0.5) * steps_recip 138 | } 139 | 140 | #[inline] 141 | fn decode(&self, value: ParamValue) -> $int { 142 | let steps = *self.end() as f64 + 1.0 - *self.start() as f64; 143 | (*self.start() as f64 + value * steps) as $int 144 | } 145 | } 146 | 147 | impl Encode for $int { 148 | fn steps() -> Option { 149 | (0..2).steps() 150 | } 151 | 152 | fn encode(&self) -> ParamValue { 153 | (0..2).encode(self) 154 | } 155 | 156 | fn decode(value: ParamValue) -> Self { 157 | (0..2).decode(value) 158 | } 159 | } 160 | }; 161 | } 162 | 163 | int_range!(u8); 164 | int_range!(u16); 165 | int_range!(u32); 166 | int_range!(u64); 167 | 168 | int_range!(i8); 169 | int_range!(i16); 170 | int_range!(i32); 171 | int_range!(i64); 172 | 173 | impl Encode for bool { 174 | fn steps() -> Option { 175 | Some(2) 176 | } 177 | 178 | fn encode(&self) -> ParamValue { 179 | match self { 180 | false => 0.25, 181 | true => 0.75, 182 | } 183 | } 184 | 185 | fn decode(value: ParamValue) -> Self { 186 | value >= 0.5 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /src/sync/bitset.rs: -------------------------------------------------------------------------------- 1 | use std::slice; 2 | use std::sync::atomic::{AtomicU64, Ordering}; 3 | 4 | const WORD_SIZE: usize = u64::BITS as usize; 5 | const WORD_SIZE_MASK: usize = WORD_SIZE - 1; 6 | const WORD_SIZE_SHIFT: usize = WORD_SIZE.trailing_zeros() as usize; 7 | 8 | pub struct Bitset { 9 | words: Vec, 10 | len: usize, 11 | } 12 | 13 | impl Bitset { 14 | #[inline] 15 | pub fn with_len(len: usize) -> Bitset { 16 | // Round up to nearest multiple of word size 17 | let bits_len = (len + WORD_SIZE - 1) >> WORD_SIZE_SHIFT; 18 | 19 | let words = vec![0; bits_len]; 20 | 21 | Bitset { words, len } 22 | } 23 | 24 | #[inline] 25 | pub fn set(&mut self, index: usize, value: bool) { 26 | assert!(index < self.len); 27 | 28 | let mask = 1 << (index & WORD_SIZE_MASK); 29 | let word = &mut self.words[index >> WORD_SIZE_SHIFT]; 30 | if value { 31 | *word |= mask; 32 | } else { 33 | *word &= !mask; 34 | } 35 | } 36 | 37 | #[inline] 38 | pub fn get(&self, index: usize) -> bool { 39 | assert!(index < self.len); 40 | 41 | let mask = 1 << (index & WORD_SIZE_MASK); 42 | let word = self.words[index >> WORD_SIZE_SHIFT]; 43 | word & mask != 0 44 | } 45 | } 46 | 47 | pub struct AtomicBitset { 48 | words: Vec, 49 | len: usize, 50 | } 51 | 52 | impl AtomicBitset { 53 | #[inline] 54 | pub fn with_len(len: usize) -> AtomicBitset { 55 | // Round up to nearest multiple of word size 56 | let bits_len = (len + WORD_SIZE - 1) >> WORD_SIZE_SHIFT; 57 | 58 | let mut words = Vec::new(); 59 | words.resize_with(bits_len, || AtomicU64::new(0)); 60 | 61 | AtomicBitset { words, len } 62 | } 63 | 64 | #[inline] 65 | pub fn set(&self, index: usize, value: bool, ordering: Ordering) { 66 | assert!(index < self.len); 67 | 68 | let mask = 1 << (index & WORD_SIZE_MASK); 69 | let word = &self.words[index >> WORD_SIZE_SHIFT]; 70 | if value { 71 | word.fetch_or(mask, ordering); 72 | } else { 73 | word.fetch_and(!mask, ordering); 74 | } 75 | } 76 | 77 | #[inline] 78 | pub fn swap(&self, index: usize, value: bool, ordering: Ordering) -> bool { 79 | assert!(index < self.len); 80 | 81 | let mask = 1 << (index & WORD_SIZE_MASK); 82 | let word = &self.words[index >> WORD_SIZE_SHIFT]; 83 | let word_prev = if value { 84 | word.fetch_or(mask, ordering) 85 | } else { 86 | word.fetch_and(!mask, ordering) 87 | }; 88 | 89 | word_prev & mask != 0 90 | } 91 | 92 | #[inline] 93 | pub fn get(&self, index: usize, ordering: Ordering) -> bool { 94 | assert!(index < self.len); 95 | 96 | let mask = 1 << (index & WORD_SIZE_MASK); 97 | let word = self.words[index >> WORD_SIZE_SHIFT].load(ordering); 98 | word & mask != 0 99 | } 100 | 101 | #[inline] 102 | pub fn drain(&self, ordering: Ordering) -> Drain<'_> { 103 | let mut iter = self.words.iter(); 104 | let current_word = iter.next().map(|word| word.swap(0, ordering)); 105 | 106 | Drain { 107 | iter, 108 | ordering, 109 | len: self.len, 110 | index: 0, 111 | current_word, 112 | } 113 | } 114 | } 115 | 116 | pub struct Drain<'a> { 117 | iter: slice::Iter<'a, AtomicU64>, 118 | ordering: Ordering, 119 | len: usize, 120 | index: usize, 121 | current_word: Option, 122 | } 123 | 124 | impl<'a> Iterator for Drain<'a> { 125 | type Item = usize; 126 | 127 | #[inline] 128 | fn next(&mut self) -> Option { 129 | while let Some(word) = self.current_word { 130 | let bit_index = word.trailing_zeros() as usize; 131 | if bit_index < WORD_SIZE && self.index + bit_index < self.len { 132 | // Zero out the bit we found 133 | let mask = 1 << bit_index; 134 | self.current_word = Some(word & !mask); 135 | 136 | return Some(self.index + bit_index); 137 | } 138 | 139 | self.current_word = self.iter.next().map(|word| word.swap(0, self.ordering)); 140 | self.index += WORD_SIZE; 141 | } 142 | 143 | None 144 | } 145 | } 146 | 147 | #[cfg(test)] 148 | mod tests { 149 | use super::*; 150 | 151 | #[test] 152 | fn set_get() { 153 | let mut bitset = Bitset::with_len(8); 154 | 155 | for index in 0..8 { 156 | assert!(!bitset.get(index)); 157 | 158 | bitset.set(index, true); 159 | assert!(bitset.get(index)); 160 | 161 | bitset.set(index, false); 162 | assert!(!bitset.get(index)); 163 | } 164 | } 165 | 166 | #[test] 167 | fn atomic_set_get() { 168 | let bitset = AtomicBitset::with_len(8); 169 | 170 | for index in 0..8 { 171 | assert!(!bitset.get(index, Ordering::Relaxed)); 172 | 173 | bitset.set(index, true, Ordering::Relaxed); 174 | assert!(bitset.get(index, Ordering::Relaxed)); 175 | 176 | bitset.set(index, false, Ordering::Relaxed); 177 | assert!(!bitset.get(index, Ordering::Relaxed)); 178 | } 179 | } 180 | 181 | #[test] 182 | fn atomic_swap() { 183 | let bitset = AtomicBitset::with_len(8); 184 | 185 | for index in 0..8 { 186 | let prev = bitset.swap(index, true, Ordering::Relaxed); 187 | assert!(!prev); 188 | 189 | let prev = bitset.swap(index, false, Ordering::Relaxed); 190 | assert!(prev); 191 | } 192 | } 193 | 194 | #[test] 195 | fn atomic_drain() { 196 | let bitset = AtomicBitset::with_len(8); 197 | 198 | bitset.set(0, true, Ordering::Relaxed); 199 | bitset.set(3, true, Ordering::Relaxed); 200 | bitset.set(7, true, Ordering::Relaxed); 201 | 202 | let mut iter = bitset.drain(Ordering::Relaxed); 203 | assert_eq!(iter.next().unwrap(), 0); 204 | assert_eq!(iter.next().unwrap(), 3); 205 | assert_eq!(iter.next().unwrap(), 7); 206 | } 207 | 208 | #[test] 209 | fn atomic_count() { 210 | let bitset = AtomicBitset::with_len(1000); 211 | 212 | for x in 0..128 { 213 | bitset.set(5 + 7 * x, true, Ordering::Relaxed); 214 | } 215 | 216 | let mut count = 0; 217 | for _ in bitset.drain(Ordering::Relaxed) { 218 | count += 1; 219 | } 220 | 221 | assert_eq!(count, 128); 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /src/format/clap/gui.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::ffi::{c_char, CStr}; 3 | use std::rc::Rc; 4 | use std::sync::Arc; 5 | 6 | use clap_sys::ext::{gui::*, params::*}; 7 | use clap_sys::{host::*, plugin::*}; 8 | 9 | use super::instance::Instance; 10 | use crate::params::{ParamId, ParamValue}; 11 | use crate::plugin::Plugin; 12 | use crate::sync::param_gestures::ParamGestures; 13 | use crate::view::{ParentWindow, RawParent, View, ViewHost, ViewHostInner}; 14 | 15 | struct ClapViewHost { 16 | host: *const clap_host, 17 | host_params: Option<*const clap_host_params>, 18 | param_map: Arc>, 19 | param_gestures: Arc, 20 | } 21 | 22 | impl ViewHostInner for ClapViewHost { 23 | fn begin_gesture(&self, id: ParamId) { 24 | self.param_gestures.begin_gesture(self.param_map[&id]); 25 | 26 | if let Some(host_params) = self.host_params { 27 | unsafe { (*host_params).request_flush.unwrap()(self.host) }; 28 | } 29 | } 30 | 31 | fn end_gesture(&self, id: ParamId) { 32 | self.param_gestures.end_gesture(self.param_map[&id]); 33 | 34 | if let Some(host_params) = self.host_params { 35 | unsafe { (*host_params).request_flush.unwrap()(self.host) }; 36 | } 37 | } 38 | 39 | fn set_param(&self, id: ParamId, value: ParamValue) { 40 | self.param_gestures.set_value(self.param_map[&id], value); 41 | 42 | if let Some(host_params) = self.host_params { 43 | unsafe { (*host_params).request_flush.unwrap()(self.host) }; 44 | } 45 | } 46 | } 47 | 48 | impl Instance

{ 49 | pub(super) const GUI: clap_plugin_gui = clap_plugin_gui { 50 | is_api_supported: Some(Self::gui_is_api_supported), 51 | get_preferred_api: Some(Self::gui_get_preferred_api), 52 | create: Some(Self::gui_create), 53 | destroy: Some(Self::gui_destroy), 54 | set_scale: Some(Self::gui_set_scale), 55 | get_size: Some(Self::gui_get_size), 56 | can_resize: Some(Self::gui_can_resize), 57 | get_resize_hints: Some(Self::gui_get_resize_hints), 58 | adjust_size: Some(Self::gui_adjust_size), 59 | set_size: Some(Self::gui_set_size), 60 | set_parent: Some(Self::gui_set_parent), 61 | set_transient: Some(Self::gui_set_transient), 62 | suggest_title: Some(Self::gui_suggest_title), 63 | show: Some(Self::gui_show), 64 | hide: Some(Self::gui_hide), 65 | }; 66 | 67 | #[cfg(target_os = "windows")] 68 | const API: &'static CStr = CLAP_WINDOW_API_WIN32; 69 | 70 | #[cfg(target_os = "macos")] 71 | const API: &'static CStr = CLAP_WINDOW_API_COCOA; 72 | 73 | #[cfg(target_os = "linux")] 74 | const API: &'static CStr = CLAP_WINDOW_API_X11; 75 | 76 | unsafe extern "C" fn gui_is_api_supported( 77 | _plugin: *const clap_plugin, 78 | api: *const c_char, 79 | is_floating: bool, 80 | ) -> bool { 81 | if is_floating { 82 | return false; 83 | } 84 | 85 | CStr::from_ptr(api) == Self::API 86 | } 87 | 88 | unsafe extern "C" fn gui_get_preferred_api( 89 | _plugin: *const clap_plugin, 90 | api: *mut *const c_char, 91 | is_floating: *mut bool, 92 | ) -> bool { 93 | *is_floating = false; 94 | 95 | *api = Self::API.as_ptr(); 96 | 97 | true 98 | } 99 | 100 | unsafe extern "C" fn gui_create( 101 | plugin: *const clap_plugin, 102 | api: *const c_char, 103 | is_floating: bool, 104 | ) -> bool { 105 | if !Self::gui_is_api_supported(plugin, api, is_floating) { 106 | return false; 107 | } 108 | 109 | true 110 | } 111 | 112 | unsafe extern "C" fn gui_destroy(plugin: *const clap_plugin) { 113 | let instance = &*(plugin as *const Self); 114 | let main_thread_state = &mut *instance.main_thread_state.get(); 115 | 116 | main_thread_state.view = None; 117 | } 118 | 119 | unsafe extern "C" fn gui_set_scale(_plugin: *const clap_plugin, _scale: f64) -> bool { 120 | false 121 | } 122 | 123 | unsafe extern "C" fn gui_get_size( 124 | plugin: *const clap_plugin, 125 | width: *mut u32, 126 | height: *mut u32, 127 | ) -> bool { 128 | let instance = &*(plugin as *const Self); 129 | let main_thread_state = &mut *instance.main_thread_state.get(); 130 | 131 | if let Some(view) = &main_thread_state.view { 132 | let size = view.size(); 133 | 134 | *width = size.width.round() as u32; 135 | *height = size.height.round() as u32; 136 | 137 | return true; 138 | } 139 | 140 | false 141 | } 142 | 143 | unsafe extern "C" fn gui_can_resize(_plugin: *const clap_plugin) -> bool { 144 | false 145 | } 146 | 147 | unsafe extern "C" fn gui_get_resize_hints( 148 | _plugin: *const clap_plugin, 149 | _hints: *mut clap_gui_resize_hints, 150 | ) -> bool { 151 | false 152 | } 153 | 154 | unsafe extern "C" fn gui_adjust_size( 155 | _plugin: *const clap_plugin, 156 | _width: *mut u32, 157 | _height: *mut u32, 158 | ) -> bool { 159 | false 160 | } 161 | 162 | unsafe extern "C" fn gui_set_size( 163 | _plugin: *const clap_plugin, 164 | _width: u32, 165 | _height: u32, 166 | ) -> bool { 167 | false 168 | } 169 | 170 | unsafe extern "C" fn gui_set_parent( 171 | plugin: *const clap_plugin, 172 | window: *const clap_window, 173 | ) -> bool { 174 | let window = &*window; 175 | 176 | if CStr::from_ptr(window.api) != Self::API { 177 | return false; 178 | } 179 | 180 | #[cfg(target_os = "windows")] 181 | let raw_parent = { RawParent::Win32(window.specific.win32) }; 182 | 183 | #[cfg(target_os = "macos")] 184 | let raw_parent = { RawParent::Cocoa(window.specific.cocoa) }; 185 | 186 | #[cfg(target_os = "linux")] 187 | let raw_parent = { RawParent::X11(window.specific.x11) }; 188 | 189 | let instance = &*(plugin as *const Self); 190 | let main_thread_state = &mut *instance.main_thread_state.get(); 191 | 192 | let host = ViewHost::from_inner(Rc::new(ClapViewHost { 193 | host: instance.host, 194 | host_params: main_thread_state.host_params, 195 | param_map: Arc::clone(&instance.param_map), 196 | param_gestures: Arc::clone(&instance.param_gestures), 197 | })); 198 | let parent = ParentWindow::from_raw(raw_parent); 199 | let view = main_thread_state.plugin.view(host, &parent); 200 | main_thread_state.view = Some(view); 201 | 202 | true 203 | } 204 | 205 | unsafe extern "C" fn gui_set_transient( 206 | _plugin: *const clap_plugin, 207 | _window: *const clap_window, 208 | ) -> bool { 209 | false 210 | } 211 | 212 | unsafe extern "C" fn gui_suggest_title(_plugin: *const clap_plugin, _title: *const c_char) {} 213 | 214 | unsafe extern "C" fn gui_show(_plugin: *const clap_plugin) -> bool { 215 | false 216 | } 217 | 218 | unsafe extern "C" fn gui_hide(_plugin: *const clap_plugin) -> bool { 219 | false 220 | } 221 | } 222 | -------------------------------------------------------------------------------- /src/sync/param_gestures.rs: -------------------------------------------------------------------------------- 1 | use std::sync::atomic::Ordering; 2 | 3 | use super::bitset::{self, AtomicBitset, Bitset}; 4 | use super::float::AtomicF64; 5 | use crate::params::ParamValue; 6 | 7 | pub struct ParamGestures { 8 | values: Vec, 9 | gesture_states: AtomicBitset, 10 | dirty: AtomicBitset, 11 | values_dirty: AtomicBitset, 12 | } 13 | 14 | impl ParamGestures { 15 | pub fn with_count(count: usize) -> ParamGestures { 16 | ParamGestures { 17 | values: (0..count).map(|_| AtomicF64::new(0.0)).collect(), 18 | gesture_states: AtomicBitset::with_len(count), 19 | dirty: AtomicBitset::with_len(count), 20 | values_dirty: AtomicBitset::with_len(count), 21 | } 22 | } 23 | 24 | pub fn begin_gesture(&self, index: usize) { 25 | self.gesture_states.set(index, true, Ordering::Relaxed); 26 | self.dirty.set(index, true, Ordering::Release); 27 | } 28 | 29 | pub fn end_gesture(&self, index: usize) { 30 | self.gesture_states.set(index, false, Ordering::Relaxed); 31 | self.dirty.set(index, true, Ordering::Release); 32 | } 33 | 34 | pub fn set_value(&self, index: usize, value: ParamValue) { 35 | self.values[index].store(value, Ordering::Relaxed); 36 | self.values_dirty.set(index, true, Ordering::Release); 37 | self.dirty.set(index, true, Ordering::Release); 38 | } 39 | 40 | pub fn poll<'a, 'b>(&'a self, states: &'b mut GestureStates) -> Poll<'a, 'b> { 41 | Poll { 42 | values: &self.values, 43 | gesture_states: &self.gesture_states, 44 | values_dirty: &self.values_dirty, 45 | current_gestures: &mut states.states, 46 | iter: self.dirty.drain(Ordering::Acquire), 47 | } 48 | } 49 | } 50 | 51 | pub struct GestureStates { 52 | states: Bitset, 53 | } 54 | 55 | impl GestureStates { 56 | pub fn with_count(count: usize) -> GestureStates { 57 | GestureStates { 58 | states: Bitset::with_len(count), 59 | } 60 | } 61 | } 62 | 63 | #[derive(Copy, Clone, Debug, PartialEq)] 64 | pub struct GestureUpdate { 65 | pub index: usize, 66 | pub begin_gesture: bool, 67 | pub set_value: Option, 68 | pub end_gesture: bool, 69 | } 70 | 71 | pub struct Poll<'a, 'b> { 72 | values: &'a [AtomicF64], 73 | gesture_states: &'a AtomicBitset, 74 | values_dirty: &'a AtomicBitset, 75 | current_gestures: &'b mut Bitset, 76 | iter: bitset::Drain<'a>, 77 | } 78 | 79 | impl<'a, 'b> Iterator for Poll<'a, 'b> { 80 | type Item = GestureUpdate; 81 | 82 | fn next(&mut self) -> Option { 83 | if let Some(index) = self.iter.next() { 84 | let mut update = GestureUpdate { 85 | index, 86 | begin_gesture: false, 87 | set_value: None, 88 | end_gesture: false, 89 | }; 90 | 91 | let mut current_state = self.current_gestures.get(index); 92 | 93 | if self.values_dirty.swap(index, false, Ordering::Acquire) { 94 | if !current_state { 95 | update.begin_gesture = true; 96 | current_state = true; 97 | } 98 | 99 | update.set_value = Some(self.values[index].load(Ordering::Relaxed)); 100 | } 101 | 102 | let next_state = self.gesture_states.get(index, Ordering::Relaxed); 103 | if !current_state && next_state { 104 | update.begin_gesture = true; 105 | } else if current_state && !next_state { 106 | update.end_gesture = true; 107 | } 108 | 109 | self.current_gestures.set(index, next_state); 110 | 111 | Some(update) 112 | } else { 113 | None 114 | } 115 | } 116 | } 117 | 118 | #[cfg(test)] 119 | mod tests { 120 | use super::*; 121 | 122 | #[test] 123 | fn gesture_updates() { 124 | let gestures = ParamGestures::with_count(1); 125 | let mut states = GestureStates::with_count(1); 126 | 127 | let updates = gestures.poll(&mut states).collect::>(); 128 | assert!(updates.is_empty()); 129 | 130 | gestures.begin_gesture(0); 131 | 132 | let updates = gestures.poll(&mut states).collect::>(); 133 | assert_eq!( 134 | updates, 135 | &[GestureUpdate { 136 | index: 0, 137 | begin_gesture: true, 138 | set_value: None, 139 | end_gesture: false, 140 | }] 141 | ); 142 | 143 | gestures.set_value(0, 0.0); 144 | 145 | let updates = gestures.poll(&mut states).collect::>(); 146 | assert_eq!( 147 | updates, 148 | &[GestureUpdate { 149 | index: 0, 150 | begin_gesture: false, 151 | set_value: Some(0.0), 152 | end_gesture: false, 153 | }] 154 | ); 155 | 156 | gestures.end_gesture(0); 157 | 158 | let updates = gestures.poll(&mut states).collect::>(); 159 | assert_eq!( 160 | updates, 161 | &[GestureUpdate { 162 | index: 0, 163 | begin_gesture: false, 164 | set_value: None, 165 | end_gesture: true, 166 | }] 167 | ); 168 | 169 | gestures.begin_gesture(0); 170 | gestures.end_gesture(0); 171 | 172 | let updates = gestures.poll(&mut states).collect::>(); 173 | assert_eq!( 174 | updates, 175 | &[GestureUpdate { 176 | index: 0, 177 | begin_gesture: false, 178 | set_value: None, 179 | end_gesture: false, 180 | }] 181 | ); 182 | 183 | gestures.begin_gesture(0); 184 | gestures.set_value(0, 1.0); 185 | 186 | let updates = gestures.poll(&mut states).collect::>(); 187 | assert_eq!( 188 | updates, 189 | &[GestureUpdate { 190 | index: 0, 191 | begin_gesture: true, 192 | set_value: Some(1.0), 193 | end_gesture: false, 194 | }] 195 | ); 196 | 197 | gestures.set_value(0, 2.0); 198 | gestures.end_gesture(0); 199 | 200 | let updates = gestures.poll(&mut states).collect::>(); 201 | assert_eq!( 202 | updates, 203 | &[GestureUpdate { 204 | index: 0, 205 | begin_gesture: false, 206 | set_value: Some(2.0), 207 | end_gesture: true, 208 | }] 209 | ); 210 | 211 | gestures.begin_gesture(0); 212 | gestures.set_value(0, 3.0); 213 | gestures.end_gesture(0); 214 | 215 | let updates = gestures.poll(&mut states).collect::>(); 216 | assert_eq!( 217 | updates, 218 | &[GestureUpdate { 219 | index: 0, 220 | begin_gesture: true, 221 | set_value: Some(3.0), 222 | end_gesture: true, 223 | }] 224 | ); 225 | 226 | gestures.begin_gesture(0); 227 | gestures.set_value(0, 4.0); 228 | gestures.set_value(0, 5.0); 229 | gestures.set_value(0, 6.0); 230 | gestures.end_gesture(0); 231 | 232 | let updates = gestures.poll(&mut states).collect::>(); 233 | assert_eq!( 234 | updates, 235 | &[GestureUpdate { 236 | index: 0, 237 | begin_gesture: true, 238 | set_value: Some(6.0), 239 | end_gesture: true, 240 | }] 241 | ); 242 | 243 | gestures.begin_gesture(0); 244 | gestures.set_value(0, 7.0); 245 | gestures.end_gesture(0); 246 | gestures.begin_gesture(0); 247 | gestures.set_value(0, 8.0); 248 | gestures.end_gesture(0); 249 | gestures.begin_gesture(0); 250 | gestures.set_value(0, 9.0); 251 | gestures.end_gesture(0); 252 | 253 | let updates = gestures.poll(&mut states).collect::>(); 254 | assert_eq!( 255 | updates, 256 | &[GestureUpdate { 257 | index: 0, 258 | begin_gesture: true, 259 | set_value: Some(9.0), 260 | end_gesture: true, 261 | }] 262 | ); 263 | 264 | let updates = gestures.poll(&mut states).collect::>(); 265 | assert!(updates.is_empty()); 266 | } 267 | } 268 | -------------------------------------------------------------------------------- /src/format/vst3/tests.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error; 2 | use std::ffi::CStr; 3 | use std::fmt::{self, Formatter}; 4 | use std::io::{self, Read, Write}; 5 | use std::{ptr, slice}; 6 | 7 | use crate::buffers::Buffers; 8 | use crate::engine::{Config, Engine}; 9 | use crate::events::Events; 10 | use crate::host::Host; 11 | use crate::params::{ParamId, ParamValue}; 12 | use crate::plugin::{Plugin, PluginInfo}; 13 | use crate::view::{ParentWindow, Size, View, ViewHost}; 14 | 15 | use vst3::Steinberg::Vst::{IComponent, SDKVersionString}; 16 | use vst3::Steinberg::{char16, char8, int32}; 17 | use vst3::Steinberg::{ 18 | kInvalidArgument, kResultOk, FIDString, IPluginFactory, IPluginFactory2, IPluginFactory2Trait, 19 | IPluginFactory3, IPluginFactory3Trait, IPluginFactoryTrait, PClassInfo, PClassInfo2, 20 | PClassInfoW, PClassInfo_, PFactoryInfo, PFactoryInfo_, 21 | }; 22 | use vst3::{uid, ComPtr, Interface}; 23 | 24 | use super::{get_plugin_factory, Uuid, Vst3Info, Vst3Plugin}; 25 | 26 | const NAME: &str = "test plugin"; 27 | const VERSION: &str = "1.2.3"; 28 | const VENDOR: &str = "test vendor"; 29 | const URL: &str = "https://example.com/"; 30 | const EMAIL: &str = "example@example.com"; 31 | const CLASS_ID: [u32; 4] = [0x11111111, 0x22222222, 0x33333333, 0x44444444]; 32 | 33 | struct TestPlugin; 34 | 35 | impl Plugin for TestPlugin { 36 | type Engine = TestEngine; 37 | type View = TestView; 38 | 39 | fn info() -> PluginInfo { 40 | PluginInfo { 41 | name: NAME.to_string(), 42 | version: VERSION.to_string(), 43 | vendor: VENDOR.to_string(), 44 | url: URL.to_string(), 45 | email: EMAIL.to_string(), 46 | buses: Vec::new(), 47 | layouts: vec![], 48 | params: Vec::new(), 49 | has_view: false, 50 | } 51 | } 52 | fn new(_host: Host) -> Self { 53 | TestPlugin 54 | } 55 | fn set_param(&mut self, _id: ParamId, _value: ParamValue) {} 56 | fn get_param(&self, _id: ParamId) -> ParamValue { 57 | 0.0 58 | } 59 | fn parse_param(&self, _id: ParamId, _text: &str) -> Option { 60 | None 61 | } 62 | fn display_param( 63 | &self, 64 | _id: ParamId, 65 | _value: ParamValue, 66 | _fmt: &mut Formatter, 67 | ) -> Result<(), fmt::Error> { 68 | Ok(()) 69 | } 70 | fn save(&self, _output: impl Write) -> io::Result<()> { 71 | Ok(()) 72 | } 73 | fn load(&mut self, _input: impl Read) -> io::Result<()> { 74 | Ok(()) 75 | } 76 | fn engine(&mut self, _config: &Config) -> Self::Engine { 77 | TestEngine 78 | } 79 | fn view(&mut self, _host: ViewHost, _parent: &ParentWindow) -> Self::View { 80 | TestView 81 | } 82 | 83 | #[allow(unused_variables)] 84 | fn latency(&self, _config: &Config) -> u64 { 85 | 0 86 | } 87 | } 88 | 89 | impl Vst3Plugin for TestPlugin { 90 | fn vst3_info() -> Vst3Info { 91 | Vst3Info { 92 | class_id: Uuid(CLASS_ID[0], CLASS_ID[1], CLASS_ID[2], CLASS_ID[3]), 93 | } 94 | } 95 | } 96 | 97 | struct TestEngine; 98 | 99 | impl Engine for TestEngine { 100 | fn reset(&mut self) {} 101 | fn flush(&mut self, _events: Events) {} 102 | fn process(&mut self, _buffers: Buffers, _events: Events) {} 103 | } 104 | 105 | struct TestView; 106 | 107 | impl View for TestView { 108 | fn size(&self) -> Size { 109 | Size { 110 | width: 0.0, 111 | height: 0.0, 112 | } 113 | } 114 | fn param_changed(&mut self, _id: ParamId, _value: ParamValue) {} 115 | } 116 | 117 | fn str_from_chars(chars: &[char8]) -> Result<&str, Box> { 118 | let bytes = unsafe { slice::from_raw_parts(chars.as_ptr() as *const u8, chars.len()) }; 119 | Ok(CStr::from_bytes_until_nul(bytes)?.to_str()?) 120 | } 121 | 122 | fn string_from_wchars(wchars: &[char16]) -> Result { 123 | let utf16 = unsafe { slice::from_raw_parts(wchars.as_ptr() as *const u16, wchars.len()) }; 124 | char::decode_utf16(utf16.iter().copied().take_while(|c| *c != 0)).collect() 125 | } 126 | 127 | #[test] 128 | fn factory() { 129 | let ptr = get_plugin_factory::() as *mut IPluginFactory; 130 | let factory = unsafe { ComPtr::from_raw(ptr) }.unwrap(); 131 | 132 | let mut factory_info = PFactoryInfo { 133 | vendor: [0; 64], 134 | url: [0; 256], 135 | email: [0; 128], 136 | flags: 0, 137 | }; 138 | let result = unsafe { factory.getFactoryInfo(&mut factory_info) }; 139 | assert_eq!(result, kResultOk); 140 | 141 | assert_eq!(str_from_chars(&factory_info.vendor).unwrap(), VENDOR); 142 | assert_eq!(str_from_chars(&factory_info.url).unwrap(), URL); 143 | assert_eq!(str_from_chars(&factory_info.email).unwrap(), EMAIL); 144 | assert_eq!( 145 | factory_info.flags, 146 | PFactoryInfo_::FactoryFlags_::kUnicode as int32 147 | ); 148 | 149 | let class_count = unsafe { factory.countClasses() }; 150 | assert_eq!(class_count, 1); 151 | 152 | let mut class_info = PClassInfo { 153 | cid: [0; 16], 154 | cardinality: 0, 155 | category: [0; 32], 156 | name: [0; 64], 157 | }; 158 | unsafe { factory.getClassInfo(0, &mut class_info) }; 159 | 160 | assert_eq!( 161 | class_info.cid, 162 | uid(CLASS_ID[0], CLASS_ID[1], CLASS_ID[2], CLASS_ID[3]) 163 | ); 164 | assert_eq!( 165 | class_info.cardinality, 166 | PClassInfo_::ClassCardinality_::kManyInstances as int32 167 | ); 168 | 169 | assert_eq!( 170 | str_from_chars(&class_info.category).unwrap(), 171 | "Audio Module Class" 172 | ); 173 | assert_eq!(str_from_chars(&class_info.name).unwrap(), NAME); 174 | 175 | let result = unsafe { factory.getClassInfo(1, &mut class_info) }; 176 | assert_eq!(result, kInvalidArgument); 177 | 178 | let factory_2 = factory.cast::().unwrap(); 179 | 180 | let mut class_info_2 = PClassInfo2 { 181 | cid: [0; 16], 182 | cardinality: 0, 183 | category: [0; 32], 184 | name: [0; 64], 185 | classFlags: 0, 186 | subCategories: [0; 128], 187 | vendor: [0; 64], 188 | version: [0; 64], 189 | sdkVersion: [0; 64], 190 | }; 191 | let result = unsafe { factory_2.getClassInfo2(0, &mut class_info_2) }; 192 | assert_eq!(result, kResultOk); 193 | 194 | let sdk_version = unsafe { CStr::from_ptr(SDKVersionString) }.to_str().unwrap(); 195 | 196 | assert_eq!( 197 | class_info_2.cid, 198 | uid(CLASS_ID[0], CLASS_ID[1], CLASS_ID[2], CLASS_ID[3]) 199 | ); 200 | assert_eq!( 201 | class_info_2.cardinality, 202 | PClassInfo_::ClassCardinality_::kManyInstances as int32 203 | ); 204 | assert_eq!( 205 | str_from_chars(&class_info_2.category).unwrap(), 206 | "Audio Module Class" 207 | ); 208 | assert_eq!(str_from_chars(&class_info_2.name).unwrap(), NAME); 209 | assert_eq!(class_info_2.classFlags, 0); 210 | assert_eq!(str_from_chars(&class_info_2.subCategories).unwrap(), "Fx"); 211 | assert_eq!(str_from_chars(&class_info_2.vendor).unwrap(), VENDOR); 212 | assert_eq!(str_from_chars(&class_info_2.version).unwrap(), VERSION); 213 | assert_eq!( 214 | str_from_chars(&class_info_2.sdkVersion).unwrap(), 215 | sdk_version 216 | ); 217 | 218 | let result = unsafe { factory_2.getClassInfo(1, &mut class_info) }; 219 | assert_eq!(result, kInvalidArgument); 220 | 221 | let factory_3 = factory.cast::().unwrap(); 222 | 223 | let mut class_info_w = PClassInfoW { 224 | cid: [0; 16], 225 | cardinality: 0, 226 | category: [0; 32], 227 | name: [0; 64], 228 | classFlags: 0, 229 | subCategories: [0; 128], 230 | vendor: [0; 64], 231 | version: [0; 64], 232 | sdkVersion: [0; 64], 233 | }; 234 | let result = unsafe { factory_3.getClassInfoUnicode(0, &mut class_info_w) }; 235 | assert_eq!(result, kResultOk); 236 | 237 | assert_eq!( 238 | class_info_w.cid, 239 | uid(CLASS_ID[0], CLASS_ID[1], CLASS_ID[2], CLASS_ID[3]) 240 | ); 241 | assert_eq!( 242 | class_info_w.cardinality, 243 | PClassInfo_::ClassCardinality_::kManyInstances as int32 244 | ); 245 | assert_eq!( 246 | str_from_chars(&class_info_w.category).unwrap(), 247 | "Audio Module Class" 248 | ); 249 | assert_eq!(string_from_wchars(&class_info_w.name).unwrap(), NAME); 250 | assert_eq!(class_info_w.classFlags, 0); 251 | assert_eq!(str_from_chars(&class_info_w.subCategories).unwrap(), "Fx"); 252 | assert_eq!(string_from_wchars(&class_info_w.vendor).unwrap(), VENDOR); 253 | assert_eq!(string_from_wchars(&class_info_w.version).unwrap(), VERSION); 254 | assert_eq!( 255 | string_from_wchars(&class_info_w.sdkVersion).unwrap(), 256 | sdk_version 257 | ); 258 | 259 | let result = unsafe { factory_3.getClassInfoUnicode(1, &mut class_info_w) }; 260 | assert_eq!(result, kInvalidArgument); 261 | 262 | let mut obj = ptr::null_mut(); 263 | let result = unsafe { 264 | factory.createInstance( 265 | uid(CLASS_ID[0], CLASS_ID[1], CLASS_ID[2], CLASS_ID[3]).as_ptr(), 266 | IComponent::IID.as_ptr() as FIDString, 267 | &mut obj, 268 | ) 269 | }; 270 | assert_eq!(result, kResultOk); 271 | 272 | unsafe { ComPtr::from_raw(obj as *mut IComponent) }.unwrap(); 273 | } 274 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | -------------------------------------------------------------------------------- /src/buffers/iter.rs: -------------------------------------------------------------------------------- 1 | use std::marker::PhantomData; 2 | 3 | use super::{Buffer, BufferData, BufferMut, Buffers, Sample, SampleMut, Samples}; 4 | use crate::events::Events; 5 | 6 | pub trait IntoSamples { 7 | type Sample; 8 | type SampleIter: Iterator; 9 | 10 | fn into_samples(self) -> Self::SampleIter; 11 | } 12 | 13 | impl<'a, 'b> IntoSamples for Buffers<'a, 'b> { 14 | type Sample = Samples<'a, 'b>; 15 | type SampleIter = SamplesIter<'a, 'b>; 16 | 17 | #[inline] 18 | fn into_samples(self) -> Self::SampleIter { 19 | SamplesIter::new(self) 20 | } 21 | } 22 | 23 | pub struct SamplesIter<'a, 'b> { 24 | buffers: &'a [BufferData], 25 | ptrs: &'a [*mut f32], 26 | offset: isize, 27 | end: isize, 28 | _marker: PhantomData<&'b mut f32>, 29 | } 30 | 31 | impl<'a, 'b> SamplesIter<'a, 'b> { 32 | fn new(buffers: Buffers<'a, 'b>) -> SamplesIter<'a, 'b> { 33 | SamplesIter { 34 | buffers: buffers.buffers, 35 | ptrs: buffers.ptrs, 36 | offset: buffers.offset, 37 | end: buffers.offset + buffers.len as isize, 38 | _marker: buffers._marker, 39 | } 40 | } 41 | } 42 | 43 | impl<'a, 'b> Iterator for SamplesIter<'a, 'b> { 44 | type Item = Samples<'a, 'b>; 45 | 46 | #[inline] 47 | fn next(&mut self) -> Option> { 48 | if self.offset < self.end { 49 | let offset = self.offset; 50 | self.offset += 1; 51 | 52 | unsafe { Some(Samples::from_raw_parts(self.buffers, self.ptrs, offset)) } 53 | } else { 54 | None 55 | } 56 | } 57 | } 58 | 59 | impl<'a, 'b> IntoSamples for Buffer<'a, 'b> { 60 | type Sample = Sample<'a, 'b>; 61 | type SampleIter = SampleIter<'a, 'b>; 62 | 63 | #[inline] 64 | fn into_samples(self) -> Self::SampleIter { 65 | SampleIter::new(self) 66 | } 67 | } 68 | 69 | pub struct SampleIter<'a, 'b> { 70 | ptrs: &'a [*mut f32], 71 | offset: isize, 72 | end: isize, 73 | _marker: PhantomData<&'b f32>, 74 | } 75 | 76 | impl<'a, 'b> SampleIter<'a, 'b> { 77 | fn new(buffer: Buffer<'a, 'b>) -> SampleIter<'a, 'b> { 78 | SampleIter { 79 | ptrs: buffer.ptrs, 80 | offset: buffer.offset, 81 | end: buffer.offset + buffer.len as isize, 82 | _marker: buffer._marker, 83 | } 84 | } 85 | } 86 | 87 | impl<'a, 'b> Iterator for SampleIter<'a, 'b> { 88 | type Item = Sample<'a, 'b>; 89 | 90 | #[inline] 91 | fn next(&mut self) -> Option> { 92 | if self.offset < self.end { 93 | let offset = self.offset; 94 | self.offset += 1; 95 | 96 | unsafe { Some(Sample::from_raw_parts(self.ptrs, offset)) } 97 | } else { 98 | None 99 | } 100 | } 101 | } 102 | 103 | impl<'a, 'b> IntoSamples for BufferMut<'a, 'b> { 104 | type Sample = SampleMut<'a, 'b>; 105 | type SampleIter = SampleIterMut<'a, 'b>; 106 | 107 | #[inline] 108 | fn into_samples(self) -> Self::SampleIter { 109 | SampleIterMut::new(self) 110 | } 111 | } 112 | 113 | pub struct SampleIterMut<'a, 'b> { 114 | ptrs: &'a [*mut f32], 115 | offset: isize, 116 | end: isize, 117 | _marker: PhantomData<&'b mut f32>, 118 | } 119 | 120 | impl<'a, 'b> SampleIterMut<'a, 'b> { 121 | fn new(buffer: BufferMut<'a, 'b>) -> SampleIterMut<'a, 'b> { 122 | SampleIterMut { 123 | ptrs: buffer.ptrs, 124 | offset: buffer.offset, 125 | end: buffer.offset + buffer.len as isize, 126 | _marker: buffer._marker, 127 | } 128 | } 129 | } 130 | 131 | impl<'a, 'b> Iterator for SampleIterMut<'a, 'b> { 132 | type Item = SampleMut<'a, 'b>; 133 | 134 | #[inline] 135 | fn next(&mut self) -> Option> { 136 | if self.offset < self.end { 137 | let offset = self.offset; 138 | self.offset += 1; 139 | 140 | unsafe { Some(SampleMut::from_raw_parts(self.ptrs, offset)) } 141 | } else { 142 | None 143 | } 144 | } 145 | } 146 | 147 | pub trait IntoBlocks { 148 | type Block; 149 | type BlockIter: BlockIterator; 150 | 151 | fn into_blocks(self) -> Self::BlockIter; 152 | } 153 | 154 | pub trait BlockIterator { 155 | type Block; 156 | 157 | fn len(&self) -> usize; 158 | 159 | #[inline] 160 | fn is_empty(&self) -> bool { 161 | self.len() == 0 162 | } 163 | 164 | fn next_block(&mut self, len: usize) -> Self::Block; 165 | 166 | #[inline] 167 | fn split_at_events(self, events: Events) -> SplitAtEvents 168 | where 169 | Self: Sized, 170 | { 171 | SplitAtEvents::new(self, events) 172 | } 173 | } 174 | 175 | impl<'a, 'b> IntoBlocks for Buffers<'a, 'b> { 176 | type Block = Buffers<'a, 'b>; 177 | type BlockIter = BlocksIter<'a, 'b>; 178 | 179 | #[inline] 180 | fn into_blocks(self) -> Self::BlockIter { 181 | BlocksIter::new(self) 182 | } 183 | } 184 | 185 | pub struct BlocksIter<'a, 'b> { 186 | buffers: &'a [BufferData], 187 | ptrs: &'a [*mut f32], 188 | offset: isize, 189 | end: isize, 190 | _marker: PhantomData<&'b mut f32>, 191 | } 192 | 193 | impl<'a, 'b> BlocksIter<'a, 'b> { 194 | fn new(buffers: Buffers<'a, 'b>) -> BlocksIter<'a, 'b> { 195 | BlocksIter { 196 | buffers: buffers.buffers, 197 | ptrs: buffers.ptrs, 198 | offset: buffers.offset, 199 | end: buffers.offset + buffers.len as isize, 200 | _marker: buffers._marker, 201 | } 202 | } 203 | } 204 | 205 | impl<'a, 'b> BlockIterator for BlocksIter<'a, 'b> { 206 | type Block = Buffers<'a, 'b>; 207 | 208 | #[inline] 209 | fn len(&self) -> usize { 210 | (self.end - self.offset) as usize 211 | } 212 | 213 | #[inline] 214 | fn next_block(&mut self, len: usize) -> Self::Block { 215 | let remainder = self.len(); 216 | let len = if len > remainder { remainder } else { len }; 217 | 218 | let offset = self.offset; 219 | self.offset += len as isize; 220 | 221 | unsafe { Buffers::from_raw_parts(self.buffers, self.ptrs, offset, len) } 222 | } 223 | } 224 | 225 | impl<'a, 'b> IntoBlocks for Buffer<'a, 'b> { 226 | type Block = Buffer<'a, 'b>; 227 | type BlockIter = BlockIter<'a, 'b>; 228 | 229 | #[inline] 230 | fn into_blocks(self) -> Self::BlockIter { 231 | BlockIter::new(self) 232 | } 233 | } 234 | 235 | pub struct BlockIter<'a, 'b> { 236 | ptrs: &'a [*mut f32], 237 | offset: isize, 238 | end: isize, 239 | _marker: PhantomData<&'b f32>, 240 | } 241 | 242 | impl<'a, 'b> BlockIter<'a, 'b> { 243 | fn new(buffer: Buffer<'a, 'b>) -> BlockIter<'a, 'b> { 244 | BlockIter { 245 | ptrs: buffer.ptrs, 246 | offset: buffer.offset, 247 | end: buffer.offset + buffer.len as isize, 248 | _marker: buffer._marker, 249 | } 250 | } 251 | } 252 | 253 | impl<'a, 'b> BlockIterator for BlockIter<'a, 'b> { 254 | type Block = Buffer<'a, 'b>; 255 | 256 | #[inline] 257 | fn len(&self) -> usize { 258 | (self.end - self.offset) as usize 259 | } 260 | 261 | #[inline] 262 | fn next_block(&mut self, len: usize) -> Self::Block { 263 | let remainder = self.len(); 264 | let len = if len > remainder { remainder } else { len }; 265 | 266 | let offset = self.offset; 267 | self.offset += len as isize; 268 | 269 | unsafe { Buffer::from_raw_parts(self.ptrs, offset, len) } 270 | } 271 | } 272 | 273 | impl<'a, 'b> IntoBlocks for BufferMut<'a, 'b> { 274 | type Block = BufferMut<'a, 'b>; 275 | type BlockIter = BlockIterMut<'a, 'b>; 276 | 277 | #[inline] 278 | fn into_blocks(self) -> Self::BlockIter { 279 | BlockIterMut::new(self) 280 | } 281 | } 282 | 283 | pub struct BlockIterMut<'a, 'b> { 284 | ptrs: &'a [*mut f32], 285 | offset: isize, 286 | end: isize, 287 | _marker: PhantomData<&'b mut f32>, 288 | } 289 | 290 | impl<'a, 'b> BlockIterMut<'a, 'b> { 291 | fn new(buffer: BufferMut<'a, 'b>) -> BlockIterMut<'a, 'b> { 292 | BlockIterMut { 293 | ptrs: buffer.ptrs, 294 | offset: buffer.offset, 295 | end: buffer.offset + buffer.len as isize, 296 | _marker: buffer._marker, 297 | } 298 | } 299 | } 300 | 301 | impl<'a, 'b> BlockIterator for BlockIterMut<'a, 'b> { 302 | type Block = BufferMut<'a, 'b>; 303 | 304 | #[inline] 305 | fn len(&self) -> usize { 306 | (self.end - self.offset) as usize 307 | } 308 | 309 | #[inline] 310 | fn next_block(&mut self, len: usize) -> Self::Block { 311 | let remainder = self.len(); 312 | let len = if len > remainder { remainder } else { len }; 313 | 314 | let offset = self.offset; 315 | self.offset += len as isize; 316 | 317 | unsafe { BufferMut::from_raw_parts(self.ptrs, offset, len) } 318 | } 319 | } 320 | 321 | pub struct SplitAtEvents<'e, B> { 322 | blocks: B, 323 | events: Events<'e>, 324 | time: i64, 325 | } 326 | 327 | impl<'e, B> SplitAtEvents<'e, B> { 328 | fn new(blocks: B, events: Events<'e>) -> SplitAtEvents<'e, B> { 329 | SplitAtEvents { 330 | blocks, 331 | events, 332 | time: 0, 333 | } 334 | } 335 | } 336 | 337 | impl<'e, B: BlockIterator> Iterator for SplitAtEvents<'e, B> { 338 | type Item = (B::Block, Events<'e>); 339 | 340 | #[inline] 341 | fn next(&mut self) -> Option<(B::Block, Events<'e>)> { 342 | let len = self.blocks.len(); 343 | 344 | if len == 0 { 345 | if self.events.is_empty() { 346 | return None; 347 | } 348 | 349 | // If we've reached the end of the buffer, yield all remaining events in one go: 350 | let buffers = self.blocks.next_block(0); 351 | 352 | let events = self.events; 353 | self.events = Events::new(&[]); 354 | 355 | return Some((buffers, events)); 356 | } 357 | 358 | // Find the first event with a timestamp greater than the current one: 359 | let mut event_count = 0; 360 | let mut split = len; 361 | for event in self.events { 362 | if event.time > self.time { 363 | let offset = (event.time - self.time) as usize; 364 | if offset < len { 365 | split = offset; 366 | } 367 | 368 | self.time = event.time; 369 | 370 | break; 371 | } 372 | 373 | event_count += 1; 374 | } 375 | 376 | let buffer = self.blocks.next_block(split); 377 | 378 | let events = self.events.slice(..event_count).unwrap(); 379 | self.events = self.events.slice(event_count..).unwrap(); 380 | 381 | Some((buffer, events)) 382 | } 383 | } 384 | -------------------------------------------------------------------------------- /src/format/vst3/buffers.rs: -------------------------------------------------------------------------------- 1 | use std::iter::zip; 2 | use std::ptr::NonNull; 3 | use std::slice; 4 | 5 | use vst3::Steinberg::Vst::ProcessData; 6 | 7 | use crate::buffers::{BufferData, BufferType, Buffers}; 8 | use crate::bus::{BusDir, BusInfo}; 9 | use crate::engine::Config; 10 | use crate::util::slice_from_raw_parts_checked; 11 | 12 | pub struct ScratchBuffers { 13 | inputs_active: Vec, 14 | outputs_active: Vec, 15 | data: Vec, 16 | ptrs: Vec<*mut f32>, 17 | buffers: Vec, 18 | silence: Vec, 19 | output_ptrs: Vec<*mut f32>, 20 | moves: Vec<(*const f32, *mut f32)>, 21 | } 22 | 23 | impl ScratchBuffers { 24 | pub fn new(input_count: usize, output_count: usize) -> ScratchBuffers { 25 | ScratchBuffers { 26 | inputs_active: vec![true; input_count], 27 | outputs_active: vec![true; output_count], 28 | data: Vec::new(), 29 | ptrs: Vec::new(), 30 | buffers: Vec::new(), 31 | silence: Vec::new(), 32 | output_ptrs: Vec::new(), 33 | moves: Vec::new(), 34 | } 35 | } 36 | 37 | pub fn set_input_active(&mut self, index: usize, active: bool) { 38 | self.inputs_active[index] = active; 39 | } 40 | 41 | pub fn set_output_active(&mut self, index: usize, active: bool) { 42 | self.outputs_active[index] = active; 43 | } 44 | 45 | pub fn resize(&mut self, buses: &[BusInfo], config: &Config) { 46 | self.data.clear(); 47 | let mut total_channels = 0; 48 | let mut output_channels = 0; 49 | let mut in_out_channels = 0; 50 | for (info, format) in zip(buses, &config.layout.formats) { 51 | let buffer_type = match info.dir { 52 | BusDir::In => BufferType::Const, 53 | BusDir::Out | BusDir::InOut => BufferType::Mut, 54 | }; 55 | let channel_count = format.channel_count(); 56 | 57 | self.data.push(BufferData { 58 | buffer_type, 59 | start: total_channels, 60 | end: total_channels + channel_count, 61 | }); 62 | 63 | total_channels += channel_count; 64 | if info.dir == BusDir::Out || info.dir == BusDir::InOut { 65 | output_channels += channel_count; 66 | } 67 | if info.dir == BusDir::InOut { 68 | in_out_channels += channel_count; 69 | } 70 | } 71 | 72 | self.ptrs.resize(total_channels, NonNull::dangling().as_ptr()); 73 | 74 | // Each input buffer can be aliased by an output buffer, each output buffer can belong to an 75 | // inactive bus, and each input provided to an in-out bus might need to be copied to 76 | // scratch space temporarily while copying inputs to outputs. 77 | let scratch_space = config.max_buffer_size * (total_channels + in_out_channels); 78 | self.buffers.resize(scratch_space, 0.0); 79 | 80 | // Silence buffer, to be used for inactive input buses 81 | self.silence.resize(config.max_buffer_size, 0.0); 82 | 83 | self.output_ptrs.clear(); 84 | self.output_ptrs.reserve(output_channels); 85 | 86 | self.moves.clear(); 87 | self.moves.reserve(in_out_channels); 88 | } 89 | 90 | /// Set up buffer pointers for the engine given a VST3 `ProcessData` struct. 91 | /// 92 | /// This method is responsible for detecting if any of the buffers for an input bus are aliased 93 | /// by a output buffer and, if so, copying those inputs to scratch buffers. It is also 94 | /// responsible for detecting if separate input and output buffers have been passed for an 95 | /// in-out bus and copying those inputs to the corresponding outputs. 96 | /// 97 | /// This method will return `Err` if the channel counts do not match the current layout or if 98 | /// the buffer's length exceeds the maximum buffer size. It will return `Ok(None)` if the 99 | /// buffer's length is 0, as hosts are not guaranteed to provide the correct number of inputs 100 | /// and outputs in that case, and we don't need to construct a `Buffers` object as we will be 101 | /// calling `Engine::flush` instead of `Engine::process`. 102 | pub unsafe fn get_buffers<'a, 'b>( 103 | &'a mut self, 104 | buses: &[BusInfo], 105 | input_bus_map: &[usize], 106 | output_bus_map: &[usize], 107 | config: &Config, 108 | data: &ProcessData, 109 | ) -> Result>, ()> { 110 | let len = data.numSamples as usize; 111 | if len > config.max_buffer_size { 112 | return Err(()); 113 | } 114 | 115 | let mut scratch = &mut self.buffers[..]; 116 | 117 | if len == 0 { 118 | return Ok(None); 119 | } 120 | 121 | let input_count = data.numInputs as usize; 122 | let output_count = data.numOutputs as usize; 123 | if input_count != input_bus_map.len() || output_count != output_bus_map.len() { 124 | return Err(()); 125 | } 126 | 127 | let inputs = slice_from_raw_parts_checked(data.inputs, input_count); 128 | let outputs = slice_from_raw_parts_checked(data.outputs, output_count); 129 | 130 | // Validate that the host has provided us with the correct number of channels for each bus. 131 | for (&bus_index, input) in zip(input_bus_map, inputs) { 132 | if input.numChannels as usize != config.layout.formats[bus_index].channel_count() { 133 | return Err(()); 134 | } 135 | } 136 | for (&bus_index, output) in zip(output_bus_map, outputs) { 137 | if output.numChannels as usize != config.layout.formats[bus_index].channel_count() { 138 | return Err(()); 139 | } 140 | } 141 | 142 | // Set up output pointers. 143 | self.output_ptrs.clear(); 144 | for (output_index, &bus_index) in output_bus_map.iter().enumerate() { 145 | let data = &self.data[bus_index]; 146 | if self.outputs_active[output_index] { 147 | let output = &outputs[output_index]; 148 | let channels = slice_from_raw_parts_checked( 149 | output.__field0.channelBuffers32, 150 | output.numChannels as usize, 151 | ); 152 | 153 | self.ptrs[data.start..data.end].copy_from_slice(channels); 154 | self.output_ptrs.extend_from_slice(channels); 155 | } else { 156 | // For inactive output buses, allocate a scratch buffer for each channel. 157 | for ptr in &mut self.ptrs[data.start..data.end] { 158 | let (first, rest) = scratch.split_at_mut(len); 159 | scratch = rest; 160 | 161 | *ptr = first.as_mut_ptr(); 162 | } 163 | } 164 | } 165 | 166 | // Sort the list of output pointers so that we can use binary search to check if input 167 | // pointers are aliased by output pointers. 168 | self.output_ptrs.sort_unstable(); 169 | 170 | // Set up input pointers. 171 | for (input_index, &bus_index) in input_bus_map.iter().enumerate() { 172 | let data = &self.data[bus_index]; 173 | let bus_info = &buses[bus_index]; 174 | if bus_info.dir == BusDir::In { 175 | if self.inputs_active[input_index] { 176 | let input = &inputs[input_index]; 177 | let channels = slice_from_raw_parts_checked( 178 | input.__field0.channelBuffers32, 179 | input.numChannels as usize, 180 | ); 181 | 182 | let ptrs = &mut self.ptrs[data.start..data.end]; 183 | for (&channel, ptr) in zip(channels, ptrs) { 184 | // If an input buffer is aliased by some output buffer, copy its contents to 185 | // a scratch buffer. 186 | if self.output_ptrs.binary_search(&channel).is_ok() { 187 | let (first, rest) = scratch.split_at_mut(len); 188 | scratch = rest; 189 | 190 | let input_slice = slice::from_raw_parts(channel, len); 191 | first.copy_from_slice(input_slice); 192 | *ptr = first.as_mut_ptr(); 193 | } else { 194 | *ptr = channel; 195 | } 196 | } 197 | } else { 198 | // For inactive input buses, provide pointers to the silence buffer. 199 | let silence = self.silence.as_ptr() as *mut f32; 200 | self.ptrs[data.start..data.end].fill(silence); 201 | } 202 | } 203 | } 204 | 205 | // If the host has passed us separate input and output buffers for an in-out bus, copy 206 | // inputs to outputs. 207 | self.moves.clear(); 208 | for (input_index, &bus_index) in input_bus_map.iter().enumerate() { 209 | let data = &self.data[bus_index]; 210 | let bus_info = &buses[bus_index]; 211 | if bus_info.dir == BusDir::InOut { 212 | if self.inputs_active[input_index] { 213 | let input = &inputs[input_index]; 214 | let channels = slice_from_raw_parts_checked( 215 | input.__field0.channelBuffers32, 216 | input.numChannels as usize, 217 | ); 218 | 219 | let ptrs = &self.ptrs[data.start..data.end]; 220 | for (&src, &dst) in zip(channels, ptrs) { 221 | // Only perform a copy if input and output pointers are not equal. 222 | if src != dst { 223 | // If an input buffer is aliased by an output buffer, we might overwrite 224 | // it when performing copies, so save its contents in a scratch 225 | // buffer. 226 | if self.output_ptrs.binary_search(&src).is_ok() { 227 | let (first, rest) = scratch.split_at_mut(len); 228 | scratch = rest; 229 | 230 | let input_slice = slice::from_raw_parts(src, len); 231 | first.copy_from_slice(input_slice); 232 | self.moves.push((first.as_ptr(), dst)); 233 | } else { 234 | self.moves.push((src, dst)); 235 | } 236 | } 237 | } 238 | } else { 239 | // For inactive input buses, copy from the silence buffer. 240 | for &dst in &self.ptrs[data.start..data.end] { 241 | self.moves.push((self.silence.as_ptr(), dst)); 242 | } 243 | } 244 | } 245 | } 246 | 247 | // Now that any aliased input buffers have been copied to scratch space, actually perform 248 | // the copies. 249 | for (src, dst) in self.moves.drain(..) { 250 | let src = slice::from_raw_parts(src, len); 251 | let dst = slice::from_raw_parts_mut(dst, len); 252 | dst.copy_from_slice(src); 253 | } 254 | 255 | self.output_ptrs.clear(); 256 | 257 | Ok(Some(Buffers::from_raw_parts( 258 | &self.data, &self.ptrs, 0, len, 259 | ))) 260 | } 261 | } 262 | -------------------------------------------------------------------------------- /coupler-derive/src/params.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream; 2 | use quote::{quote, ToTokens}; 3 | use syn::{Data, DeriveInput, Error, Expr, Field, Fields, LitInt, LitStr}; 4 | 5 | pub struct ParamAttr { 6 | pub id: LitInt, 7 | pub name: Option, 8 | pub range: Option, 9 | pub parse: Option, 10 | pub display: Option, 11 | pub format: Option, 12 | } 13 | 14 | pub fn parse_param(field: &Field) -> Result, Error> { 15 | let mut is_param = false; 16 | 17 | let mut id = None; 18 | let mut name = None; 19 | let mut range = None; 20 | let mut parse = None; 21 | let mut display = None; 22 | let mut format = None; 23 | 24 | for attr in &field.attrs { 25 | if !attr.path().is_ident("param") { 26 | continue; 27 | } 28 | 29 | is_param = true; 30 | 31 | attr.parse_nested_meta(|meta| { 32 | let ident = meta.path.get_ident().ok_or_else(|| { 33 | Error::new_spanned(&meta.path, "expected this path to be an identifier") 34 | })?; 35 | if ident == "id" { 36 | if id.is_some() { 37 | return Err(Error::new_spanned( 38 | &meta.path, 39 | "duplicate param attribute `id`", 40 | )); 41 | } 42 | 43 | id = Some(meta.value()?.parse::()?); 44 | } else if ident == "name" { 45 | if name.is_some() { 46 | return Err(Error::new_spanned( 47 | &meta.path, 48 | "duplicate param attribute `name`", 49 | )); 50 | } 51 | 52 | name = Some(meta.value()?.parse::()?); 53 | } else if ident == "range" { 54 | if range.is_some() { 55 | return Err(Error::new_spanned( 56 | &meta.path, 57 | "duplicate param attribute `range`", 58 | )); 59 | } 60 | 61 | range = Some(meta.value()?.parse::()?); 62 | } else if ident == "parse" { 63 | if parse.is_some() { 64 | return Err(Error::new_spanned( 65 | &meta.path, 66 | "duplicate param attribute `parse`", 67 | )); 68 | } 69 | 70 | parse = Some(meta.value()?.parse::()?); 71 | } else if ident == "display" { 72 | if display.is_some() { 73 | return Err(Error::new_spanned( 74 | &meta.path, 75 | "duplicate param attribute `display`", 76 | )); 77 | } 78 | 79 | if format.is_some() { 80 | return Err(Error::new_spanned( 81 | ident, 82 | "`format` attribute cannot be used with `display`", 83 | )); 84 | } 85 | 86 | display = Some(meta.value()?.parse::()?); 87 | } else if ident == "format" { 88 | if format.is_some() { 89 | return Err(Error::new_spanned( 90 | &meta.path, 91 | "duplicate param attribute `format`", 92 | )); 93 | } 94 | 95 | if display.is_some() { 96 | return Err(Error::new_spanned( 97 | ident, 98 | "`format` attribute cannot be used with `display`", 99 | )); 100 | } 101 | 102 | format = Some(meta.value()?.parse::()?); 103 | } else { 104 | return Err(Error::new_spanned( 105 | &meta.path, 106 | format!("unknown param attribute `{}`", ident), 107 | )); 108 | } 109 | 110 | Ok(()) 111 | })?; 112 | } 113 | 114 | if !is_param { 115 | return Ok(None); 116 | } 117 | 118 | let id = if let Some(id) = id { 119 | id 120 | } else { 121 | return Err(Error::new_spanned(field, "missing `id` attribute")); 122 | }; 123 | 124 | Ok(Some(ParamAttr { 125 | id, 126 | name, 127 | range, 128 | parse, 129 | display, 130 | format, 131 | })) 132 | } 133 | 134 | pub fn gen_encode(field: &Field, param: &ParamAttr, value: impl ToTokens) -> TokenStream { 135 | let ty = &field.ty; 136 | if let Some(range) = ¶m.range { 137 | quote! { ::coupler::params::Range::<#ty>::encode(&(#range), &#value) } 138 | } else { 139 | quote! { <#ty as ::coupler::params::Encode>::encode(&#value) } 140 | } 141 | } 142 | 143 | pub fn gen_decode(field: &Field, param: &ParamAttr, value: impl ToTokens) -> TokenStream { 144 | let ty = &field.ty; 145 | if let Some(range) = ¶m.range { 146 | quote! { ::coupler::params::Range::<#ty>::decode(&(#range), #value) } 147 | } else { 148 | quote! { <#ty as ::coupler::params::Encode>::decode(#value) } 149 | } 150 | } 151 | 152 | struct ParamField<'a> { 153 | field: &'a Field, 154 | param: ParamAttr, 155 | } 156 | 157 | fn parse_fields(input: &DeriveInput) -> Result, Error> { 158 | let body = match &input.data { 159 | Data::Struct(body) => body, 160 | _ => { 161 | return Err(Error::new_spanned( 162 | input, 163 | "#[derive(Params)] can only be used on structs", 164 | )); 165 | } 166 | }; 167 | 168 | let fields = match &body.fields { 169 | Fields::Named(fields) => fields, 170 | _ => { 171 | return Err(Error::new_spanned( 172 | input, 173 | "#[derive(Params)] can only be used on structs with named fields", 174 | )); 175 | } 176 | }; 177 | 178 | let mut param_fields = Vec::new(); 179 | 180 | for field in &fields.named { 181 | if let Some(param) = parse_param(field)? { 182 | param_fields.push(ParamField { field, param }); 183 | } 184 | } 185 | 186 | Ok(param_fields) 187 | } 188 | 189 | pub fn expand_params(input: &DeriveInput) -> Result { 190 | let fields = parse_fields(input)?; 191 | 192 | let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); 193 | let ident = &input.ident; 194 | 195 | let param_info = fields.iter().map(|field| { 196 | let ident = field.field.ident.as_ref().unwrap(); 197 | let ty = &field.field.ty; 198 | let id = &field.param.id; 199 | 200 | let name = if let Some(name) = &field.param.name { 201 | name.clone() 202 | } else { 203 | LitStr::new(&ident.to_string(), ident.span()) 204 | }; 205 | 206 | let default = gen_encode(field.field, &field.param, quote! { __default.#ident }); 207 | 208 | let steps = if let Some(range) = &field.param.range { 209 | let ty = &field.field.ty; 210 | quote! { ::coupler::params::Range::<#ty>::steps(&(#range)) } 211 | } else { 212 | quote! { <#ty as ::coupler::params::Encode>::steps() } 213 | }; 214 | 215 | quote! { 216 | ::coupler::params::ParamInfo { 217 | id: #id, 218 | name: ::std::string::ToString::to_string(#name), 219 | default: #default, 220 | steps: #steps, 221 | } 222 | } 223 | }); 224 | 225 | let set_cases = fields.iter().map(|field| { 226 | let ident = &field.field.ident; 227 | let id = &field.param.id; 228 | 229 | let decode = gen_decode(field.field, &field.param, quote! { __value }); 230 | 231 | quote! { 232 | #id => { 233 | self.#ident = #decode; 234 | } 235 | } 236 | }); 237 | 238 | let get_cases = fields.iter().map(|field| { 239 | let ident = &field.field.ident; 240 | let id = &field.param.id; 241 | 242 | let encode = gen_encode(field.field, &field.param, quote! { &self.#ident }); 243 | 244 | quote! { 245 | #id => { 246 | #encode 247 | } 248 | } 249 | }); 250 | 251 | let parse_cases = fields.iter().map(|field| { 252 | let ty = &field.field.ty; 253 | let id = &field.param.id; 254 | 255 | let encode = gen_encode(field.field, &field.param, quote! { __value }); 256 | let parse = if let Some(parse) = &field.param.parse { 257 | quote! { 258 | match (#parse)(__text) { 259 | ::std::option::Option::Some(__value) => ::std::option::Option::Some(#encode), 260 | _ => ::std::option::Option::None, 261 | } 262 | } 263 | } else { 264 | quote! { 265 | match <#ty as ::std::str::FromStr>::from_str(__text) { 266 | ::std::result::Result::Ok(__value) => ::std::option::Option::Some(#encode), 267 | _ => ::std::option::Option::None, 268 | } 269 | } 270 | }; 271 | 272 | quote! { 273 | #id => { 274 | #parse 275 | } 276 | } 277 | }); 278 | 279 | let display_cases = fields.iter().map(|field| { 280 | let id = &field.param.id; 281 | 282 | let decode = gen_decode(field.field, &field.param, quote! { __value }); 283 | let display = if let Some(display) = &field.param.display { 284 | quote! { (#display)(#decode, __fmt) } 285 | } else if let Some(format) = &field.param.format { 286 | quote! { write!(__fmt, #format, #decode) } 287 | } else { 288 | quote! { write!(__fmt, "{}", #decode) } 289 | }; 290 | 291 | quote! { 292 | #id => { 293 | #display 294 | } 295 | } 296 | }); 297 | 298 | Ok(quote! { 299 | impl #impl_generics ::coupler::params::Params for #ident #ty_generics #where_clause { 300 | fn params() -> ::std::vec::Vec<::coupler::params::ParamInfo> { 301 | let __default: #ident #ty_generics = ::std::default::Default::default(); 302 | 303 | ::std::vec![ 304 | #(#param_info,)* 305 | ] 306 | } 307 | 308 | fn set_param(&mut self, __id: ::coupler::params::ParamId, __value: ::coupler::params::ParamValue) { 309 | match __id { 310 | #(#set_cases)* 311 | _ => {} 312 | } 313 | } 314 | 315 | fn get_param(&self, __id: ::coupler::params::ParamId) -> ::coupler::params::ParamValue { 316 | match __id { 317 | #(#get_cases)* 318 | _ => 0.0, 319 | } 320 | } 321 | 322 | fn parse_param(&self, __id: ::coupler::params::ParamId, __text: &::std::primitive::str) -> ::std::option::Option<::coupler::params::ParamValue> { 323 | match __id { 324 | #(#parse_cases)* 325 | _ => ::std::option::Option::None 326 | } 327 | } 328 | 329 | fn display_param( 330 | &self, 331 | __id: ::coupler::params::ParamId, 332 | __value: ::coupler::params::ParamValue, 333 | __fmt: &mut ::std::fmt::Formatter, 334 | ) -> ::std::result::Result<(), ::std::fmt::Error> { 335 | match __id { 336 | #(#display_cases)* 337 | _ => Ok(()) 338 | } 339 | } 340 | } 341 | }) 342 | } 343 | -------------------------------------------------------------------------------- /examples/gain/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::cell::RefCell; 2 | use std::fmt::{self, Formatter}; 3 | use std::io::{self, Read, Write}; 4 | use std::rc::Rc; 5 | 6 | use serde::{Deserialize, Serialize}; 7 | 8 | use coupler::format::clap::*; 9 | use coupler::format::vst3::*; 10 | use coupler::params::{ParamId, ParamValue}; 11 | use coupler::view::{ParentWindow, RawParent, Size, View}; 12 | use coupler::{buffers::*, bus::*, engine::*, events::*, host::*, params::*, plugin::*, view::*}; 13 | 14 | use flicker::Renderer; 15 | 16 | use portlight::{ 17 | App, AppMode, AppOptions, Bitmap, Cursor, MouseButton, Point, RawWindow, Response, Window, 18 | WindowContext, WindowOptions, 19 | }; 20 | 21 | #[derive(Params, Serialize, Deserialize, Clone)] 22 | struct GainParams { 23 | #[param(id = 0, name = "Gain", range = 0.0..1.0, format = "{:.2}")] 24 | gain: f32, 25 | } 26 | 27 | impl Default for GainParams { 28 | fn default() -> GainParams { 29 | GainParams { gain: 1.0 } 30 | } 31 | } 32 | 33 | pub struct Gain { 34 | params: GainParams, 35 | } 36 | 37 | impl Plugin for Gain { 38 | type Engine = GainEngine; 39 | type View = GainView; 40 | 41 | fn info() -> PluginInfo { 42 | PluginInfo { 43 | name: "Gain".to_string(), 44 | version: "0.1.0".to_string(), 45 | vendor: "Vendor".to_string(), 46 | url: "https://example.com".to_string(), 47 | email: "example@example.com".to_string(), 48 | buses: vec![BusInfo { 49 | name: "Main".to_string(), 50 | dir: BusDir::InOut, 51 | }], 52 | layouts: vec![ 53 | Layout { 54 | formats: vec![Format::Stereo], 55 | }, 56 | Layout { 57 | formats: vec![Format::Mono], 58 | }, 59 | ], 60 | params: GainParams::params(), 61 | has_view: true, 62 | } 63 | } 64 | 65 | fn new(_host: Host) -> Self { 66 | Gain { 67 | params: GainParams::default(), 68 | } 69 | } 70 | 71 | fn set_param(&mut self, id: ParamId, value: ParamValue) { 72 | self.params.set_param(id, value); 73 | } 74 | 75 | fn get_param(&self, id: ParamId) -> ParamValue { 76 | self.params.get_param(id) 77 | } 78 | 79 | fn parse_param(&self, id: ParamId, text: &str) -> Option { 80 | self.params.parse_param(id, text) 81 | } 82 | 83 | fn display_param( 84 | &self, 85 | id: ParamId, 86 | value: ParamValue, 87 | fmt: &mut Formatter, 88 | ) -> Result<(), fmt::Error> { 89 | self.params.display_param(id, value, fmt) 90 | } 91 | 92 | fn save(&self, output: impl Write) -> io::Result<()> { 93 | serde_json::to_writer(output, &self.params)?; 94 | 95 | Ok(()) 96 | } 97 | 98 | fn load(&mut self, input: impl Read) -> io::Result<()> { 99 | self.params = serde_json::from_reader(input)?; 100 | 101 | Ok(()) 102 | } 103 | 104 | fn engine(&mut self, _config: &Config) -> Self::Engine { 105 | GainEngine { 106 | params: self.params.clone(), 107 | } 108 | } 109 | 110 | fn view(&mut self, host: ViewHost, parent: &ParentWindow) -> Self::View { 111 | GainView::open(host, parent, &self.params).unwrap() 112 | } 113 | } 114 | 115 | impl Vst3Plugin for Gain { 116 | fn vst3_info() -> Vst3Info { 117 | Vst3Info { 118 | class_id: Uuid::from_name("rs.coupler.gain"), 119 | } 120 | } 121 | } 122 | 123 | impl ClapPlugin for Gain { 124 | fn clap_info() -> ClapInfo { 125 | ClapInfo { 126 | id: "rs.coupler.gain".to_string(), 127 | } 128 | } 129 | } 130 | 131 | pub struct GainEngine { 132 | params: GainParams, 133 | } 134 | 135 | impl GainEngine { 136 | fn handle_event(&mut self, event: &Event) { 137 | if let Data::ParamChange { id, value } = event.data { 138 | self.params.set_param(id, value); 139 | } 140 | } 141 | } 142 | 143 | impl Engine for GainEngine { 144 | fn reset(&mut self) {} 145 | 146 | fn flush(&mut self, events: Events) { 147 | for event in events { 148 | self.handle_event(event); 149 | } 150 | } 151 | 152 | fn process(&mut self, buffers: Buffers, events: Events) { 153 | let mut buffers: (BufferMut,) = buffers.try_into().unwrap(); 154 | for (mut buffer, events) in buffers.0.split_at_events(events) { 155 | for event in events { 156 | self.handle_event(event); 157 | } 158 | 159 | for sample in buffer.samples() { 160 | for channel in sample { 161 | *channel *= self.params.gain; 162 | } 163 | } 164 | } 165 | } 166 | } 167 | 168 | struct Gesture { 169 | start_mouse_pos: Point, 170 | start_value: f32, 171 | } 172 | 173 | struct ViewState { 174 | host: ViewHost, 175 | params: Rc>, 176 | renderer: Renderer, 177 | framebuffer: Vec, 178 | mouse_pos: Point, 179 | gesture: Option, 180 | } 181 | 182 | impl ViewState { 183 | fn new(host: ViewHost, params: Rc>) -> ViewState { 184 | ViewState { 185 | host, 186 | params, 187 | renderer: Renderer::new(), 188 | framebuffer: Vec::new(), 189 | mouse_pos: Point { x: -1.0, y: -1.0 }, 190 | gesture: None, 191 | } 192 | } 193 | 194 | fn update_cursor(&self, window: &Window) { 195 | let pos = self.mouse_pos; 196 | if pos.x >= 96.0 && pos.x < 160.0 && pos.y >= 96.0 && pos.y < 160.0 { 197 | window.set_cursor(Cursor::SizeNs); 198 | } else { 199 | window.set_cursor(Cursor::Arrow); 200 | } 201 | } 202 | 203 | fn handle_event(&mut self, cx: &WindowContext, event: portlight::Event) -> Response { 204 | use flicker::{Affine, Color, Path, Point}; 205 | use portlight::Event; 206 | 207 | match event { 208 | Event::Frame => { 209 | let scale = cx.window().scale(); 210 | let size = cx.window().size(); 211 | let width = (size.width * scale) as usize; 212 | let height = (size.height * scale) as usize; 213 | self.framebuffer.resize(width * height, 0xFF000000); 214 | 215 | let mut target = self.renderer.attach(&mut self.framebuffer, width, height); 216 | 217 | target.clear(Color::rgba(21, 26, 31, 255)); 218 | 219 | let transform = Affine::scale(scale as f32); 220 | 221 | let value = self.params.borrow().gain; 222 | 223 | let center = Point::new(128.0, 128.0); 224 | let radius = 32.0; 225 | let angle1 = 0.75 * std::f32::consts::PI; 226 | let angle2 = angle1 + value * 1.5 * std::f32::consts::PI; 227 | let mut path = Path::new(); 228 | path.move_to(center + radius * Point::new(angle1.cos(), angle1.sin())); 229 | path.arc(radius, angle1, angle2); 230 | path.line_to(center + (radius - 4.0) * Point::new(angle2.cos(), angle2.sin())); 231 | path.arc(radius - 4.0, angle2, angle1); 232 | path.close(); 233 | target.fill_path(&path, transform, Color::rgba(240, 240, 245, 255)); 234 | 235 | let center = Point::new(128.0, 128.0); 236 | let radius = 32.0; 237 | let angle = 0.75 * std::f32::consts::PI; 238 | let span = 1.5 * std::f32::consts::PI; 239 | let mut path = Path::new(); 240 | path.move_to(center + radius * Point::new(angle.cos(), angle.sin())); 241 | path.arc(radius, angle, angle + span); 242 | path.line_to(center + (radius - 4.0) * Point::new(-angle.cos(), angle.sin())); 243 | path.arc(radius - 4.0, angle + span, angle); 244 | path.close(); 245 | target.stroke_path(&path, 1.0, transform, Color::rgba(240, 240, 245, 255)); 246 | 247 | cx.window().present(Bitmap::new(&self.framebuffer, width, height)); 248 | } 249 | Event::MouseMove(pos) => { 250 | self.mouse_pos = pos; 251 | if let Some(gesture) = &self.gesture { 252 | let delta = -0.005 * (pos.y - gesture.start_mouse_pos.y) as f32; 253 | let new_value = (gesture.start_value + delta).clamp(0.0, 1.0); 254 | self.host.set_param(0, new_value as f64); 255 | self.params.borrow_mut().gain = new_value; 256 | } else { 257 | self.update_cursor(cx.window()); 258 | } 259 | } 260 | Event::MouseDown(button) => { 261 | if button == MouseButton::Left { 262 | let pos = self.mouse_pos; 263 | if pos.x >= 96.0 && pos.x < 160.0 && pos.y >= 96.0 && pos.y < 160.0 { 264 | cx.window().set_cursor(Cursor::SizeNs); 265 | self.host.begin_gesture(0); 266 | let value = self.params.borrow().gain; 267 | self.host.set_param(0, value as f64); 268 | self.params.borrow_mut().gain = value; 269 | self.gesture = Some(Gesture { 270 | start_mouse_pos: pos, 271 | start_value: value, 272 | }); 273 | return Response::Capture; 274 | } 275 | } 276 | } 277 | Event::MouseUp(button) => { 278 | if button == MouseButton::Left { 279 | if self.gesture.is_some() { 280 | self.host.end_gesture(0); 281 | self.gesture = None; 282 | self.update_cursor(cx.window()); 283 | return Response::Capture; 284 | } 285 | } 286 | } 287 | _ => {} 288 | } 289 | 290 | Response::Ignore 291 | } 292 | } 293 | 294 | pub struct GainView { 295 | #[allow(unused)] 296 | app: App, 297 | window: Window, 298 | params: Rc>, 299 | } 300 | 301 | impl GainView { 302 | fn open( 303 | host: ViewHost, 304 | parent: &ParentWindow, 305 | params: &GainParams, 306 | ) -> portlight::Result { 307 | let app = AppOptions::new().mode(AppMode::Guest).build()?; 308 | 309 | let mut options = WindowOptions::new(); 310 | options.size(portlight::Size::new(256.0, 256.0)); 311 | 312 | let raw_parent = match parent.as_raw() { 313 | RawParent::Win32(window) => RawWindow::Win32(window), 314 | RawParent::Cocoa(view) => RawWindow::Cocoa(view), 315 | RawParent::X11(window) => RawWindow::X11(window), 316 | }; 317 | unsafe { options.raw_parent(raw_parent) }; 318 | 319 | let params = Rc::new(RefCell::new(params.clone())); 320 | let mut state = ViewState::new(host, Rc::clone(¶ms)); 321 | let window = options.open(app.handle(), move |cx, event| state.handle_event(cx, event))?; 322 | 323 | window.show(); 324 | 325 | Ok(GainView { 326 | app, 327 | window, 328 | params, 329 | }) 330 | } 331 | } 332 | 333 | impl View for GainView { 334 | fn size(&self) -> Size { 335 | let size = self.window.size(); 336 | 337 | Size { 338 | width: size.width, 339 | height: size.height, 340 | } 341 | } 342 | 343 | fn param_changed(&mut self, id: ParamId, value: ParamValue) { 344 | self.params.borrow_mut().set_param(id, value); 345 | } 346 | } 347 | -------------------------------------------------------------------------------- /src/buffers.rs: -------------------------------------------------------------------------------- 1 | use std::marker::PhantomData; 2 | use std::ops::{Index, IndexMut, Range}; 3 | use std::slice; 4 | 5 | pub mod convert; 6 | pub mod iter; 7 | 8 | use crate::events::Events; 9 | use iter::{BlockIterator, IntoBlocks, IntoSamples}; 10 | 11 | #[derive(Copy, Clone, Eq, PartialEq)] 12 | pub enum BufferType { 13 | Const, 14 | Mut, 15 | } 16 | 17 | pub enum AnyBuffer<'a, 'b> { 18 | Const(Buffer<'a, 'b>), 19 | Mut(BufferMut<'a, 'b>), 20 | } 21 | 22 | impl<'a, 'b> AnyBuffer<'a, 'b> { 23 | #[inline] 24 | pub unsafe fn from_raw_parts( 25 | buffer_type: BufferType, 26 | ptrs: &'a [*mut f32], 27 | offset: isize, 28 | len: usize, 29 | ) -> AnyBuffer<'a, 'b> { 30 | match buffer_type { 31 | BufferType::Const => AnyBuffer::Const(Buffer::from_raw_parts(ptrs, offset, len)), 32 | BufferType::Mut => AnyBuffer::Mut(BufferMut::from_raw_parts(ptrs, offset, len)), 33 | } 34 | } 35 | } 36 | 37 | pub struct BufferData { 38 | pub buffer_type: BufferType, 39 | pub start: usize, 40 | pub end: usize, 41 | } 42 | 43 | pub struct Buffers<'a, 'b> { 44 | buffers: &'a [BufferData], 45 | ptrs: &'a [*mut f32], 46 | offset: isize, 47 | len: usize, 48 | _marker: PhantomData<&'b mut f32>, 49 | } 50 | 51 | impl<'a, 'b> Buffers<'a, 'b> { 52 | #[inline] 53 | pub unsafe fn from_raw_parts( 54 | buffers: &'a [BufferData], 55 | ptrs: &'a [*mut f32], 56 | offset: isize, 57 | len: usize, 58 | ) -> Buffers<'a, 'b> { 59 | Buffers { 60 | buffers, 61 | ptrs, 62 | offset, 63 | len, 64 | _marker: PhantomData, 65 | } 66 | } 67 | 68 | #[inline] 69 | pub fn len(&self) -> usize { 70 | self.len 71 | } 72 | 73 | #[inline] 74 | pub fn is_empty(&self) -> bool { 75 | self.len == 0 76 | } 77 | 78 | #[inline] 79 | pub fn buffer_count(&self) -> usize { 80 | self.buffers.len() 81 | } 82 | 83 | #[inline] 84 | pub fn reborrow<'c>(&'c mut self) -> Buffers<'a, 'c> { 85 | Buffers { 86 | buffers: self.buffers, 87 | ptrs: self.ptrs, 88 | offset: self.offset, 89 | len: self.len, 90 | _marker: self._marker, 91 | } 92 | } 93 | 94 | #[inline] 95 | pub fn get<'c>(&'c mut self, index: usize) -> Option> { 96 | if let Some(buffer) = self.buffers.get(index) { 97 | unsafe { 98 | Some(AnyBuffer::from_raw_parts( 99 | buffer.buffer_type, 100 | &self.ptrs[buffer.start..buffer.end], 101 | self.offset, 102 | self.len, 103 | )) 104 | } 105 | } else { 106 | None 107 | } 108 | } 109 | 110 | #[inline] 111 | pub fn slice<'c>(&'c mut self, range: Range) -> Option> { 112 | if range.start > range.end || range.end > self.len { 113 | None 114 | } else { 115 | Some(Buffers { 116 | buffers: self.buffers, 117 | ptrs: self.ptrs, 118 | offset: self.offset + range.start as isize, 119 | len: range.end - range.start, 120 | _marker: self._marker, 121 | }) 122 | } 123 | } 124 | 125 | #[inline] 126 | pub fn samples<'c>(&'c mut self) -> iter::SamplesIter<'a, 'c> { 127 | self.reborrow().into_samples() 128 | } 129 | 130 | #[inline] 131 | pub fn split_at_events<'c, 'e>( 132 | &'c mut self, 133 | events: Events<'e>, 134 | ) -> iter::SplitAtEvents<'e, iter::BlocksIter<'a, 'c>> { 135 | self.reborrow().into_blocks().split_at_events(events) 136 | } 137 | } 138 | 139 | impl<'a, 'b> IntoIterator for Buffers<'a, 'b> { 140 | type Item = AnyBuffer<'a, 'b>; 141 | type IntoIter = BufferIter<'a, 'b>; 142 | 143 | #[inline] 144 | fn into_iter(self) -> Self::IntoIter { 145 | BufferIter { 146 | iter: self.buffers.iter(), 147 | ptrs: self.ptrs, 148 | offset: self.offset, 149 | len: self.len, 150 | _marker: PhantomData, 151 | } 152 | } 153 | } 154 | 155 | pub struct BufferIter<'a, 'b> { 156 | iter: slice::Iter<'a, BufferData>, 157 | ptrs: &'a [*mut f32], 158 | offset: isize, 159 | len: usize, 160 | _marker: PhantomData<&'b mut f32>, 161 | } 162 | 163 | impl<'a, 'b> Iterator for BufferIter<'a, 'b> { 164 | type Item = AnyBuffer<'a, 'b>; 165 | 166 | #[inline] 167 | fn next(&mut self) -> Option { 168 | if let Some(buffer) = self.iter.next() { 169 | unsafe { 170 | Some(AnyBuffer::from_raw_parts( 171 | buffer.buffer_type, 172 | &self.ptrs[buffer.start..buffer.end], 173 | self.offset, 174 | self.len, 175 | )) 176 | } 177 | } else { 178 | None 179 | } 180 | } 181 | } 182 | 183 | pub enum AnySample<'a, 'b> { 184 | Const(Sample<'a, 'b>), 185 | Mut(SampleMut<'a, 'b>), 186 | } 187 | 188 | impl<'a, 'b> AnySample<'a, 'b> { 189 | #[inline] 190 | pub unsafe fn from_raw_parts( 191 | buffer_type: BufferType, 192 | ptrs: &'a [*mut f32], 193 | offset: isize, 194 | ) -> AnySample<'a, 'b> { 195 | match buffer_type { 196 | BufferType::Const => AnySample::Const(Sample::from_raw_parts(ptrs, offset)), 197 | BufferType::Mut => AnySample::Mut(SampleMut::from_raw_parts(ptrs, offset)), 198 | } 199 | } 200 | } 201 | 202 | pub struct Samples<'a, 'b> { 203 | buffers: &'a [BufferData], 204 | ptrs: &'a [*mut f32], 205 | offset: isize, 206 | _marker: PhantomData<&'b mut f32>, 207 | } 208 | 209 | impl<'a, 'b> Samples<'a, 'b> { 210 | #[inline] 211 | pub unsafe fn from_raw_parts( 212 | buffers: &'a [BufferData], 213 | ptrs: &'a [*mut f32], 214 | offset: isize, 215 | ) -> Samples<'a, 'b> { 216 | Samples { 217 | buffers, 218 | ptrs, 219 | offset, 220 | _marker: PhantomData, 221 | } 222 | } 223 | 224 | #[inline] 225 | pub fn buffer_count(&self) -> usize { 226 | self.buffers.len() 227 | } 228 | 229 | #[inline] 230 | pub fn get<'c>(&'c mut self, index: usize) -> Option> { 231 | if let Some(buffer) = self.buffers.get(index) { 232 | unsafe { 233 | Some(AnySample::from_raw_parts( 234 | buffer.buffer_type, 235 | &self.ptrs[buffer.start..buffer.end], 236 | self.offset, 237 | )) 238 | } 239 | } else { 240 | None 241 | } 242 | } 243 | } 244 | 245 | impl<'a, 'b> IntoIterator for Samples<'a, 'b> { 246 | type Item = AnySample<'a, 'b>; 247 | type IntoIter = BufferSampleIter<'a, 'b>; 248 | 249 | #[inline] 250 | fn into_iter(self) -> Self::IntoIter { 251 | BufferSampleIter { 252 | iter: self.buffers.iter(), 253 | ptrs: self.ptrs, 254 | offset: self.offset, 255 | _marker: PhantomData, 256 | } 257 | } 258 | } 259 | 260 | pub struct BufferSampleIter<'a, 'b> { 261 | iter: slice::Iter<'a, BufferData>, 262 | ptrs: &'a [*mut f32], 263 | offset: isize, 264 | _marker: PhantomData<&'b mut f32>, 265 | } 266 | 267 | impl<'a, 'b> Iterator for BufferSampleIter<'a, 'b> { 268 | type Item = AnySample<'a, 'b>; 269 | 270 | #[inline] 271 | fn next(&mut self) -> Option { 272 | if let Some(buffer) = self.iter.next() { 273 | unsafe { 274 | Some(AnySample::from_raw_parts( 275 | buffer.buffer_type, 276 | &self.ptrs[buffer.start..buffer.end], 277 | self.offset, 278 | )) 279 | } 280 | } else { 281 | None 282 | } 283 | } 284 | } 285 | 286 | #[derive(Copy, Clone)] 287 | pub struct Buffer<'a, 'b> { 288 | ptrs: &'a [*mut f32], 289 | offset: isize, 290 | len: usize, 291 | _marker: PhantomData<&'b f32>, 292 | } 293 | 294 | impl<'a, 'b> Buffer<'a, 'b> { 295 | #[inline] 296 | pub unsafe fn from_raw_parts( 297 | ptrs: &'a [*mut f32], 298 | offset: isize, 299 | len: usize, 300 | ) -> Buffer<'a, 'b> { 301 | Buffer { 302 | ptrs, 303 | offset, 304 | len, 305 | _marker: PhantomData, 306 | } 307 | } 308 | 309 | #[inline] 310 | pub fn len(&self) -> usize { 311 | self.len 312 | } 313 | 314 | #[inline] 315 | pub fn is_empty(&self) -> bool { 316 | self.len == 0 317 | } 318 | 319 | #[inline] 320 | pub fn channel_count(&self) -> usize { 321 | self.ptrs.len() 322 | } 323 | 324 | #[inline] 325 | pub fn samples(&self) -> iter::SampleIter<'a, 'b> { 326 | self.into_samples() 327 | } 328 | 329 | #[inline] 330 | pub fn split_at_events<'e>( 331 | &self, 332 | events: Events<'e>, 333 | ) -> iter::SplitAtEvents<'e, iter::BlockIter<'a, 'b>> { 334 | self.into_blocks().split_at_events(events) 335 | } 336 | } 337 | 338 | impl<'a, 'b> Index for Buffer<'a, 'b> { 339 | type Output = [f32]; 340 | 341 | #[inline] 342 | fn index(&self, index: usize) -> &[f32] { 343 | unsafe { slice::from_raw_parts(self.ptrs[index].offset(self.offset), self.len) } 344 | } 345 | } 346 | 347 | impl<'a, 'b> IntoIterator for Buffer<'a, 'b> { 348 | type Item = &'b [f32]; 349 | type IntoIter = Channels<'a, 'b>; 350 | 351 | #[inline] 352 | fn into_iter(self) -> Self::IntoIter { 353 | Channels { 354 | iter: self.ptrs.iter(), 355 | offset: self.offset, 356 | len: self.len, 357 | _marker: PhantomData, 358 | } 359 | } 360 | } 361 | 362 | pub struct Channels<'a, 'b> { 363 | iter: slice::Iter<'a, *mut f32>, 364 | offset: isize, 365 | len: usize, 366 | _marker: PhantomData<&'b f32>, 367 | } 368 | 369 | impl<'a, 'b> Iterator for Channels<'a, 'b> { 370 | type Item = &'b [f32]; 371 | 372 | #[inline] 373 | fn next(&mut self) -> Option { 374 | if let Some(ptr) = self.iter.next() { 375 | unsafe { Some(slice::from_raw_parts(ptr.offset(self.offset), self.len)) } 376 | } else { 377 | None 378 | } 379 | } 380 | } 381 | 382 | pub struct Sample<'a, 'b> { 383 | ptrs: &'a [*mut f32], 384 | offset: isize, 385 | _marker: PhantomData<&'b f32>, 386 | } 387 | 388 | impl<'a, 'b> Sample<'a, 'b> { 389 | #[inline] 390 | pub unsafe fn from_raw_parts(ptrs: &'a [*mut f32], offset: isize) -> Sample<'a, 'b> { 391 | Sample { 392 | ptrs, 393 | offset, 394 | _marker: PhantomData, 395 | } 396 | } 397 | 398 | #[inline] 399 | pub fn channel_count(&self) -> usize { 400 | self.ptrs.len() 401 | } 402 | } 403 | 404 | impl<'a, 'b> Index for Sample<'a, 'b> { 405 | type Output = f32; 406 | 407 | #[inline] 408 | fn index(&self, index: usize) -> &f32 { 409 | unsafe { &*self.ptrs[index].offset(self.offset) } 410 | } 411 | } 412 | 413 | impl<'a, 'b> IntoIterator for Sample<'a, 'b> { 414 | type Item = &'b f32; 415 | type IntoIter = SampleChannels<'a, 'b>; 416 | 417 | #[inline] 418 | fn into_iter(self) -> Self::IntoIter { 419 | SampleChannels { 420 | iter: self.ptrs.iter(), 421 | offset: self.offset, 422 | _marker: PhantomData, 423 | } 424 | } 425 | } 426 | 427 | pub struct SampleChannels<'a, 'b> { 428 | iter: slice::Iter<'a, *mut f32>, 429 | offset: isize, 430 | _marker: PhantomData<&'b f32>, 431 | } 432 | 433 | impl<'a, 'b> Iterator for SampleChannels<'a, 'b> { 434 | type Item = &'b f32; 435 | 436 | #[inline] 437 | fn next(&mut self) -> Option { 438 | if let Some(ptr) = self.iter.next() { 439 | unsafe { Some(&*ptr.offset(self.offset)) } 440 | } else { 441 | None 442 | } 443 | } 444 | } 445 | 446 | pub struct BufferMut<'a, 'b> { 447 | ptrs: &'a [*mut f32], 448 | offset: isize, 449 | len: usize, 450 | _marker: PhantomData<&'b mut f32>, 451 | } 452 | 453 | impl<'a, 'b> BufferMut<'a, 'b> { 454 | #[inline] 455 | pub unsafe fn from_raw_parts( 456 | ptrs: &'a [*mut f32], 457 | offset: isize, 458 | len: usize, 459 | ) -> BufferMut<'a, 'b> { 460 | BufferMut { 461 | ptrs, 462 | offset, 463 | len, 464 | _marker: PhantomData, 465 | } 466 | } 467 | 468 | #[inline] 469 | pub fn len(&self) -> usize { 470 | self.len 471 | } 472 | 473 | #[inline] 474 | pub fn is_empty(&self) -> bool { 475 | self.len == 0 476 | } 477 | 478 | #[inline] 479 | pub fn channel_count(&self) -> usize { 480 | self.ptrs.len() 481 | } 482 | 483 | #[inline] 484 | pub fn reborrow<'c>(&'c mut self) -> BufferMut<'a, 'c> { 485 | BufferMut { 486 | ptrs: self.ptrs, 487 | offset: self.offset, 488 | len: self.len, 489 | _marker: self._marker, 490 | } 491 | } 492 | 493 | #[inline] 494 | pub fn samples<'c>(&'c mut self) -> iter::SampleIterMut<'a, 'c> { 495 | self.reborrow().into_samples() 496 | } 497 | 498 | #[inline] 499 | pub fn split_at_events<'c, 'e>( 500 | &'c mut self, 501 | events: Events<'e>, 502 | ) -> iter::SplitAtEvents<'e, iter::BlockIterMut<'a, 'c>> { 503 | self.reborrow().into_blocks().split_at_events(events) 504 | } 505 | } 506 | 507 | impl<'a, 'b> Index for BufferMut<'a, 'b> { 508 | type Output = [f32]; 509 | 510 | #[inline] 511 | fn index(&self, index: usize) -> &[f32] { 512 | unsafe { slice::from_raw_parts(self.ptrs[index].offset(self.offset), self.len) } 513 | } 514 | } 515 | 516 | impl<'a, 'b> IndexMut for BufferMut<'a, 'b> { 517 | #[inline] 518 | fn index_mut(&mut self, index: usize) -> &mut [f32] { 519 | unsafe { slice::from_raw_parts_mut(self.ptrs[index].offset(self.offset), self.len) } 520 | } 521 | } 522 | 523 | impl<'a, 'b> IntoIterator for BufferMut<'a, 'b> { 524 | type Item = &'b mut [f32]; 525 | type IntoIter = ChannelsMut<'a, 'b>; 526 | 527 | #[inline] 528 | fn into_iter(self) -> Self::IntoIter { 529 | ChannelsMut { 530 | iter: self.ptrs.iter(), 531 | offset: self.offset, 532 | len: self.len, 533 | _marker: PhantomData, 534 | } 535 | } 536 | } 537 | 538 | pub struct ChannelsMut<'a, 'b> { 539 | iter: slice::Iter<'a, *mut f32>, 540 | offset: isize, 541 | len: usize, 542 | _marker: PhantomData<&'b mut f32>, 543 | } 544 | 545 | impl<'a, 'b> Iterator for ChannelsMut<'a, 'b> { 546 | type Item = &'b mut [f32]; 547 | 548 | #[inline] 549 | fn next(&mut self) -> Option { 550 | if let Some(ptr) = self.iter.next() { 551 | unsafe { Some(slice::from_raw_parts_mut(ptr.offset(self.offset), self.len)) } 552 | } else { 553 | None 554 | } 555 | } 556 | } 557 | 558 | pub struct SampleMut<'a, 'b> { 559 | ptrs: &'a [*mut f32], 560 | offset: isize, 561 | _marker: PhantomData<&'b mut f32>, 562 | } 563 | 564 | impl<'a, 'b> SampleMut<'a, 'b> { 565 | #[inline] 566 | pub unsafe fn from_raw_parts(ptrs: &'a [*mut f32], offset: isize) -> SampleMut<'a, 'b> { 567 | SampleMut { 568 | ptrs, 569 | offset, 570 | _marker: PhantomData, 571 | } 572 | } 573 | 574 | #[inline] 575 | pub fn channel_count(&self) -> usize { 576 | self.ptrs.len() 577 | } 578 | } 579 | 580 | impl<'a, 'b> Index for SampleMut<'a, 'b> { 581 | type Output = f32; 582 | 583 | #[inline] 584 | fn index(&self, index: usize) -> &f32 { 585 | unsafe { &*self.ptrs[index].offset(self.offset) } 586 | } 587 | } 588 | 589 | impl<'a, 'b> IndexMut for SampleMut<'a, 'b> { 590 | #[inline] 591 | fn index_mut(&mut self, index: usize) -> &mut f32 { 592 | unsafe { &mut *self.ptrs[index].offset(self.offset) } 593 | } 594 | } 595 | 596 | impl<'a, 'b> IntoIterator for SampleMut<'a, 'b> { 597 | type Item = &'b mut f32; 598 | type IntoIter = SampleChannelsMut<'a, 'b>; 599 | 600 | #[inline] 601 | fn into_iter(self) -> Self::IntoIter { 602 | SampleChannelsMut { 603 | iter: self.ptrs.iter(), 604 | offset: self.offset, 605 | _marker: PhantomData, 606 | } 607 | } 608 | } 609 | 610 | pub struct SampleChannelsMut<'a, 'b> { 611 | iter: slice::Iter<'a, *mut f32>, 612 | offset: isize, 613 | _marker: PhantomData<&'b mut f32>, 614 | } 615 | 616 | impl<'a, 'b> Iterator for SampleChannelsMut<'a, 'b> { 617 | type Item = &'b mut f32; 618 | 619 | #[inline] 620 | fn next(&mut self) -> Option { 621 | if let Some(ptr) = self.iter.next() { 622 | unsafe { Some(&mut *ptr.offset(self.offset)) } 623 | } else { 624 | None 625 | } 626 | } 627 | } 628 | -------------------------------------------------------------------------------- /cargo-coupler/src/main.rs: -------------------------------------------------------------------------------- 1 | use cargo_metadata::{CargoOpt, Metadata, MetadataCommand}; 2 | use clap::{AppSettings, Args, Parser, Subcommand}; 3 | use serde::Deserialize; 4 | 5 | use std::collections::{HashMap, HashSet}; 6 | use std::env; 7 | use std::fmt::{self, Display}; 8 | use std::fs; 9 | use std::io::{self, Write}; 10 | use std::path::{Path, PathBuf}; 11 | use std::process::{self, Command}; 12 | use std::str::{self, FromStr}; 13 | 14 | #[derive(Parser)] 15 | #[clap(bin_name = "cargo")] 16 | enum Cargo { 17 | #[clap(subcommand)] 18 | Coupler(Coupler), 19 | } 20 | 21 | #[derive(Subcommand)] 22 | #[clap(version, about, long_about = None)] 23 | enum Coupler { 24 | Bundle(Bundle), 25 | } 26 | 27 | #[derive(Args, Debug)] 28 | #[clap(setting = AppSettings::DeriveDisplayOrder)] 29 | struct Bundle { 30 | #[clap(long, short, value_name = "PACKAGE")] 31 | package: Vec, 32 | 33 | #[clap(long)] 34 | workspace: bool, 35 | 36 | #[clap(long, value_name = "PACKAGE")] 37 | exclude: Vec, 38 | 39 | #[clap(long, short, value_name = "FORMAT")] 40 | format: Vec, 41 | 42 | #[clap(long, short)] 43 | release: bool, 44 | 45 | #[clap(long, value_name = "PROFILE-NAME")] 46 | profile: Option, 47 | 48 | #[clap(long, value_name = "FEATURES", multiple_values = true)] 49 | features: Vec, 50 | 51 | #[clap(long)] 52 | all_features: bool, 53 | 54 | #[clap(long)] 55 | no_default_features: bool, 56 | 57 | #[clap(long, value_name = "TRIPLE")] 58 | target: Option, 59 | 60 | #[clap(long, value_name = "DIRECTORY")] 61 | target_dir: Option, 62 | 63 | #[clap(long, parse(from_os_str))] 64 | manifest_path: Option, 65 | 66 | #[clap(long)] 67 | frozen: bool, 68 | 69 | #[clap(long)] 70 | locked: bool, 71 | 72 | #[clap(long)] 73 | offline: bool, 74 | } 75 | 76 | #[derive(Deserialize)] 77 | struct PackageMetadata { 78 | coupler: Option, 79 | } 80 | 81 | #[derive(Deserialize)] 82 | struct CouplerMetadata { 83 | name: Option, 84 | 85 | #[serde(default)] 86 | formats: Vec, 87 | } 88 | 89 | #[derive(Copy, Clone, Eq, PartialEq, Debug)] 90 | enum Format { 91 | Clap, 92 | Vst3, 93 | } 94 | 95 | impl Display for Format { 96 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 97 | let name = match *self { 98 | Format::Clap => "clap", 99 | Format::Vst3 => "vst3", 100 | }; 101 | 102 | f.write_str(name) 103 | } 104 | } 105 | 106 | impl FromStr for Format { 107 | type Err = (); 108 | 109 | fn from_str(s: &str) -> Result { 110 | match s { 111 | "clap" => Ok(Format::Clap), 112 | "vst3" => Ok(Format::Vst3), 113 | _ => Err(()), 114 | } 115 | } 116 | } 117 | 118 | #[derive(Copy, Clone, Eq, PartialEq, Debug)] 119 | enum Arch { 120 | Aarch64, 121 | I686, 122 | X86_64, 123 | Aarch64X86_64, // Universal binary containing both aarch64 and x86_64 124 | } 125 | 126 | #[allow(clippy::enum_variant_names)] 127 | #[derive(Copy, Clone, Eq, PartialEq, Debug)] 128 | enum Os { 129 | Linux, 130 | MacOs, 131 | Windows, 132 | } 133 | 134 | #[derive(Copy, Clone, Eq, PartialEq, Debug)] 135 | struct Target { 136 | arch: Arch, 137 | os: Os, 138 | } 139 | 140 | impl FromStr for Target { 141 | type Err = (); 142 | 143 | fn from_str(s: &str) -> Result { 144 | match s { 145 | "aarch64-apple-darwin" => Ok(Target { 146 | arch: Arch::Aarch64, 147 | os: Os::MacOs, 148 | }), 149 | "aarch64-x86_64-apple-darwin" => Ok(Target { 150 | arch: Arch::Aarch64X86_64, 151 | os: Os::MacOs, 152 | }), 153 | "i686-pc-windows-gnu" => Ok(Target { 154 | arch: Arch::I686, 155 | os: Os::Windows, 156 | }), 157 | "i686-pc-windows-msvc" => Ok(Target { 158 | arch: Arch::I686, 159 | os: Os::Windows, 160 | }), 161 | "i686-unknown-linux-gnu" => Ok(Target { 162 | arch: Arch::I686, 163 | os: Os::Linux, 164 | }), 165 | "x86_64-apple-darwin" => Ok(Target { 166 | arch: Arch::X86_64, 167 | os: Os::MacOs, 168 | }), 169 | "x86_64-pc-windows-gnu" => Ok(Target { 170 | arch: Arch::X86_64, 171 | os: Os::Windows, 172 | }), 173 | "x86_64-pc-windows-msvc" => Ok(Target { 174 | arch: Arch::X86_64, 175 | os: Os::Windows, 176 | }), 177 | "x86_64-unknown-linux-gnu" => Ok(Target { 178 | arch: Arch::X86_64, 179 | os: Os::Linux, 180 | }), 181 | _ => Err(()), 182 | } 183 | } 184 | } 185 | 186 | struct PackageInfo { 187 | package_name: String, 188 | lib_name: String, 189 | name: String, 190 | formats: Vec, 191 | } 192 | 193 | fn main() { 194 | let Cargo::Coupler(cmd) = Cargo::parse(); 195 | 196 | match cmd { 197 | Coupler::Bundle(cmd) => { 198 | bundle(&cmd); 199 | } 200 | } 201 | } 202 | 203 | fn out_dir_for_target(cmd: &Bundle, metadata: &Metadata, target: Option<&str>) -> PathBuf { 204 | let target_dir = if let Some(target_dir) = &cmd.target_dir { 205 | target_dir 206 | } else { 207 | metadata.target_directory.as_std_path() 208 | }; 209 | let mut out_dir = PathBuf::from(target_dir); 210 | 211 | if let Some(target) = target { 212 | out_dir.push(target); 213 | } 214 | 215 | let profile = if let Some(profile) = &cmd.profile { 216 | profile 217 | } else if cmd.release { 218 | "release" 219 | } else { 220 | "debug" 221 | }; 222 | out_dir.push(profile); 223 | 224 | out_dir 225 | } 226 | 227 | fn bundle(cmd: &Bundle) { 228 | // Query `rustc` for host target if no --target argument was given 229 | 230 | let target_str = if let Some(target) = &cmd.target { 231 | target.clone() 232 | } else { 233 | let rustc_path = 234 | env::var("RUSTC").map(PathBuf::from).unwrap_or_else(|_| PathBuf::from("rustc")); 235 | let mut rustc = Command::new(rustc_path); 236 | rustc.args(["--version", "--verbose"]); 237 | 238 | let result = rustc.output(); 239 | if let Err(error) = result { 240 | eprintln!( 241 | "error: failed to invoke `rustc` to query host information: {}", 242 | error 243 | ); 244 | process::exit(1); 245 | } 246 | 247 | let output = result.unwrap(); 248 | if !output.status.success() { 249 | eprintln!("error: failed to invoke `rustc` to query host information"); 250 | eprintln!(); 251 | io::stderr().write_all(&output.stderr).unwrap(); 252 | process::exit(1); 253 | } 254 | 255 | const HOST_FIELD: &str = "host: "; 256 | let output_str = str::from_utf8(&output.stdout).unwrap(); 257 | let host = output_str 258 | .lines() 259 | .find(|l| l.starts_with(HOST_FIELD)) 260 | .map(|l| &l[HOST_FIELD.len()..]); 261 | if host.is_none() { 262 | eprintln!("error: failed to invoke `rustc` to query host information"); 263 | process::exit(1); 264 | } 265 | host.unwrap().to_string() 266 | }; 267 | 268 | // Extract arch and OS from target triple 269 | 270 | let target = if let Ok(target) = Target::from_str(&target_str) { 271 | target 272 | } else { 273 | eprintln!("error: unsupported target `{}`", &target_str); 274 | process::exit(1); 275 | }; 276 | 277 | // Invoke `cargo metadata` 278 | 279 | let mut command = MetadataCommand::new(); 280 | 281 | if target.arch == Arch::Aarch64X86_64 { 282 | command.other_options(vec![ 283 | "--filter-platform".to_string(), 284 | "aarch64-apple-darwin".to_string(), 285 | "--filter-platform".to_string(), 286 | "x86_64-apple-darwin".to_string(), 287 | ]); 288 | } else { 289 | command.other_options(vec!["--filter-platform".to_string(), target_str.clone()]); 290 | } 291 | 292 | if let Some(manifest_path) = &cmd.manifest_path { 293 | command.manifest_path(manifest_path); 294 | } 295 | 296 | if cmd.no_default_features { 297 | command.features(CargoOpt::NoDefaultFeatures); 298 | } 299 | if cmd.all_features { 300 | command.features(CargoOpt::AllFeatures); 301 | } 302 | if !cmd.features.is_empty() { 303 | command.features(CargoOpt::SomeFeatures(cmd.features.clone())); 304 | } 305 | 306 | if cmd.frozen { 307 | command.other_options(vec!["--frozen".to_string()]); 308 | } 309 | if cmd.locked { 310 | command.other_options(vec!["--locked".to_string()]); 311 | } 312 | if cmd.offline { 313 | command.other_options(vec!["--offline".to_string()]); 314 | } 315 | 316 | let metadata = match command.exec() { 317 | Ok(metadata) => metadata, 318 | Err(error) => { 319 | match error { 320 | cargo_metadata::Error::CargoMetadata { stderr } => { 321 | eprint!("{}", stderr); 322 | } 323 | _ => { 324 | eprintln!("error: failed to invoke `cargo metadata`: {}", error); 325 | } 326 | } 327 | 328 | process::exit(1); 329 | } 330 | }; 331 | 332 | if !cmd.workspace && !cmd.exclude.is_empty() { 333 | eprintln!("error: --exclude can only be used together with --workspace"); 334 | process::exit(1); 335 | } 336 | 337 | let mut packages_by_id = HashMap::new(); 338 | for (index, package) in metadata.packages.iter().enumerate() { 339 | packages_by_id.insert(package.id.clone(), index); 340 | } 341 | 342 | let mut packages_by_name = HashMap::new(); 343 | for package_id in &metadata.workspace_members { 344 | let package_index = packages_by_id[package_id]; 345 | packages_by_name.insert(metadata.packages[package_index].name.clone(), package_index); 346 | } 347 | 348 | // Build a list of candidate packages for bundling 349 | 350 | let mut candidates = Vec::new(); 351 | if cmd.workspace { 352 | let mut exclude = HashSet::new(); 353 | for package_name in &cmd.exclude { 354 | exclude.insert(package_name); 355 | } 356 | 357 | for package_id in &metadata.workspace_members { 358 | let package_index = packages_by_id[package_id]; 359 | if !exclude.contains(&metadata.packages[package_index].name) { 360 | candidates.push(package_index); 361 | } 362 | } 363 | } else if !cmd.package.is_empty() { 364 | for package_name in &cmd.package { 365 | if let Some(&package_index) = packages_by_name.get(package_name) { 366 | candidates.push(package_index); 367 | } else { 368 | eprintln!( 369 | "error: package `{}` not found in workspace `{}`", 370 | package_name, &metadata.workspace_root 371 | ); 372 | process::exit(1); 373 | } 374 | } 375 | } else if let Some(root) = &metadata.resolve.as_ref().unwrap().root { 376 | // If neither --workspace nor --package is specified and there is a 377 | // root package, just try to build the root 378 | candidates.push(packages_by_id[root]); 379 | } else { 380 | // If there is no root package, search the entire workspace 381 | for package_id in &metadata.workspace_members { 382 | candidates.push(packages_by_id[package_id]); 383 | } 384 | } 385 | 386 | let mut formats_to_build = Vec::new(); 387 | for format_str in &cmd.format { 388 | if let Ok(format) = Format::from_str(format_str) { 389 | formats_to_build.push(format); 390 | } else { 391 | eprintln!("error: invalid format `{}`", format_str); 392 | process::exit(1); 393 | } 394 | } 395 | 396 | // Build the actual list of packages to bundle 397 | 398 | let mut packages_to_build = Vec::new(); 399 | for &candidate in &candidates { 400 | let package = &metadata.packages[candidate]; 401 | 402 | let package_metadata: Option = 403 | match serde_json::from_value(package.metadata.clone()) { 404 | Ok(package_metadata) => package_metadata, 405 | Err(err) => { 406 | eprintln!( 407 | "error: unable to parse [package.metadata.coupler] section: {}", 408 | err 409 | ); 410 | process::exit(1); 411 | } 412 | }; 413 | 414 | if let Some(coupler_metadata) = package_metadata.and_then(|m| m.coupler) { 415 | if coupler_metadata.formats.is_empty() { 416 | eprintln!( 417 | "warning: package `{}` does not specify any formats", 418 | &package.name 419 | ); 420 | continue; 421 | } 422 | 423 | let mut formats = Vec::new(); 424 | for format_str in &coupler_metadata.formats { 425 | let format = if let Ok(format) = Format::from_str(format_str) { 426 | format 427 | } else { 428 | eprintln!( 429 | "error: package `{}` specifies invalid format `{}`", 430 | &package.name, format_str 431 | ); 432 | process::exit(1); 433 | }; 434 | 435 | if formats_to_build.is_empty() || formats_to_build.contains(&format) { 436 | formats.push(format); 437 | } 438 | } 439 | 440 | let has_cdylib = 441 | package.targets.iter().any(|t| t.crate_types.iter().any(|c| c == "cdylib")); 442 | if !has_cdylib { 443 | eprintln!( 444 | "error: package `{}` does not have a lib target of type cdylib", 445 | package.name 446 | ); 447 | process::exit(1); 448 | } 449 | 450 | let crate_name = package.name.replace('-', "_"); 451 | let lib_name = match target.os { 452 | Os::Linux => format!("lib{crate_name}.so"), 453 | Os::MacOs => format!("lib{crate_name}.dylib"), 454 | Os::Windows => format!("{crate_name}.dll"), 455 | }; 456 | 457 | packages_to_build.push(PackageInfo { 458 | package_name: package.name.to_owned(), 459 | lib_name, 460 | name: coupler_metadata.name.as_ref().unwrap_or(&package.name).clone(), 461 | formats, 462 | }); 463 | } 464 | } 465 | 466 | if packages_to_build.is_empty() { 467 | eprintln!("error: no packages to bundle"); 468 | process::exit(1); 469 | } 470 | 471 | let out_dir = out_dir_for_target(cmd, &metadata, cmd.target.as_deref()); 472 | 473 | // Invoke `cargo build` 474 | 475 | if target.arch == Arch::Aarch64X86_64 { 476 | build_universal( 477 | cmd, 478 | &metadata, 479 | &["aarch64-apple-darwin", "x86_64-apple-darwin"], 480 | &packages_to_build, 481 | &out_dir, 482 | ); 483 | } else { 484 | build(cmd, cmd.target.as_deref(), &packages_to_build); 485 | } 486 | 487 | // Create bundles 488 | 489 | for package_info in &packages_to_build { 490 | for format in &package_info.formats { 491 | match format { 492 | Format::Clap => { 493 | bundle_clap(package_info, &out_dir, &target); 494 | } 495 | Format::Vst3 => { 496 | bundle_vst3(package_info, &out_dir, &target); 497 | } 498 | } 499 | } 500 | } 501 | } 502 | 503 | fn build_universal( 504 | cmd: &Bundle, 505 | metadata: &Metadata, 506 | targets: &[&str], 507 | packages: &[PackageInfo], 508 | out_dir: &Path, 509 | ) { 510 | for target in targets { 511 | build(cmd, Some(target), packages); 512 | } 513 | 514 | fs::create_dir_all(out_dir).unwrap(); 515 | 516 | for package_info in packages { 517 | let out_lib = out_dir.join(&package_info.lib_name); 518 | 519 | let mut lipo = Command::new("lipo"); 520 | lipo.arg("-create").arg("-output").arg(out_lib); 521 | 522 | for target in targets { 523 | let lib_name = &package_info.lib_name; 524 | let input_lib = out_dir_for_target(cmd, metadata, Some(target)).join(lib_name); 525 | lipo.arg(input_lib); 526 | } 527 | 528 | let result = lipo.spawn().and_then(|mut child| child.wait()); 529 | if let Err(error) = result { 530 | eprintln!("error: failed to invoke `lipo`: {error}"); 531 | process::exit(1); 532 | } 533 | } 534 | } 535 | 536 | fn build(cmd: &Bundle, target: Option<&str>, packages: &[PackageInfo]) { 537 | let cargo_path = 538 | env::var("CARGO").map(PathBuf::from).unwrap_or_else(|_| PathBuf::from("cargo")); 539 | let mut cargo = Command::new(cargo_path); 540 | cargo.arg("build"); 541 | 542 | for package_info in packages { 543 | cargo.args(["--package", &package_info.package_name]); 544 | } 545 | 546 | cargo.arg("--lib"); 547 | 548 | if cmd.release { 549 | cargo.arg("--release"); 550 | } 551 | if let Some(profile) = &cmd.profile { 552 | cargo.args(["--profile", profile]); 553 | } 554 | 555 | if !cmd.features.is_empty() { 556 | cargo.arg("--features"); 557 | for feature in &cmd.features { 558 | cargo.arg(feature); 559 | } 560 | } 561 | if cmd.all_features { 562 | cargo.arg("--all-features"); 563 | } 564 | if cmd.no_default_features { 565 | cargo.arg("--no-default-features"); 566 | } 567 | 568 | if let Some(target) = target { 569 | cargo.args(["--target", target]); 570 | } 571 | 572 | if let Some(target_dir) = &cmd.target_dir { 573 | cargo.arg("--target-dir"); 574 | cargo.arg(target_dir); 575 | } 576 | 577 | if let Some(manifest_path) = &cmd.manifest_path { 578 | cargo.arg("--manifest-path"); 579 | cargo.arg(manifest_path); 580 | } 581 | 582 | if cmd.frozen { 583 | cargo.arg("--frozen"); 584 | } 585 | if cmd.locked { 586 | cargo.arg("--locked"); 587 | } 588 | if cmd.offline { 589 | cargo.arg("--offline"); 590 | } 591 | 592 | let result = cargo.spawn().and_then(|mut child| child.wait()); 593 | if let Err(error) = result { 594 | eprintln!("error: failed to invoke `cargo build`: {}", error); 595 | process::exit(1); 596 | } 597 | 598 | if !result.unwrap().success() { 599 | process::exit(1); 600 | } 601 | } 602 | 603 | fn bundle_clap(package_info: &PackageInfo, out_dir: &Path, target: &Target) { 604 | let src = out_dir.join(&package_info.lib_name); 605 | 606 | let name = &package_info.name; 607 | let bundle_path = out_dir.join(format!("bundle/{name}.clap")); 608 | 609 | match target.os { 610 | Os::Linux | Os::Windows => { 611 | let dst = bundle_path; 612 | 613 | fs::create_dir_all(dst.parent().unwrap()).unwrap(); 614 | fs::copy(&src, &dst).unwrap(); 615 | } 616 | Os::MacOs => { 617 | if bundle_path.exists() { 618 | fs::remove_dir_all(&bundle_path).unwrap(); 619 | } 620 | 621 | let dst = bundle_path.join(format!("Contents/MacOS/{name}")); 622 | 623 | fs::create_dir_all(dst.parent().unwrap()).unwrap(); 624 | fs::copy(&src, &dst).unwrap(); 625 | 626 | macos_bundle_info(package_info, &bundle_path); 627 | } 628 | } 629 | } 630 | 631 | fn bundle_vst3(package_info: &PackageInfo, out_dir: &Path, target: &Target) { 632 | let src = out_dir.join(&package_info.lib_name); 633 | 634 | let name = &package_info.name; 635 | let bundle_path = out_dir.join(format!("bundle/{name}.vst3")); 636 | 637 | let dst = match target.os { 638 | Os::Linux => { 639 | let arch_str = match target.arch { 640 | Arch::Aarch64 => "aarch64", 641 | Arch::I686 => "i386", 642 | Arch::X86_64 => "x86_64", 643 | Arch::Aarch64X86_64 => unreachable!(), 644 | }; 645 | 646 | bundle_path.join(format!("Contents/{arch_str}-linux/{name}.so")) 647 | } 648 | Os::MacOs => bundle_path.join(format!("Contents/MacOS/{name}")), 649 | Os::Windows => { 650 | let arch_str = match target.arch { 651 | Arch::Aarch64 => "arm64", 652 | Arch::I686 => "x86", 653 | Arch::X86_64 => "x86_64", 654 | Arch::Aarch64X86_64 => unreachable!(), 655 | }; 656 | 657 | bundle_path.join(format!("Contents/{arch_str}-win/{name}.vst3")) 658 | } 659 | }; 660 | 661 | if bundle_path.exists() { 662 | fs::remove_dir_all(&bundle_path).unwrap(); 663 | } 664 | 665 | fs::create_dir_all(dst.parent().unwrap()).unwrap(); 666 | #[allow(clippy::needless_borrows_for_generic_args)] 667 | fs::copy(&src, &dst).unwrap(); 668 | 669 | if target.os == Os::MacOs { 670 | macos_bundle_info(package_info, &bundle_path); 671 | } 672 | } 673 | 674 | fn macos_bundle_info(package_info: &PackageInfo, bundle_path: &Path) { 675 | let name = &package_info.name; 676 | 677 | let plist = format!( 678 | r#" 679 | 680 | 681 | 682 | CFBundleDevelopmentRegion 683 | English 684 | CFBundleExecutable 685 | {name} 686 | CFBundleIdentifier 687 | 688 | CFBundleInfoDictionaryVersion 689 | 6.0 690 | CFBundlePackageType 691 | BNDL 692 | CFBundleSignature 693 | ???? 694 | CFBundleVersion 695 | 1.0.0 696 | CFBundleShortVersionString 697 | 1.0.0 698 | 699 | "# 700 | ); 701 | 702 | fs::write(bundle_path.join("Contents/Info.plist"), plist).unwrap(); 703 | fs::write(bundle_path.join("Contents/PkgInfo"), "BNDL????").unwrap(); 704 | } 705 | -------------------------------------------------------------------------------- /src/format/vst3/component.rs: -------------------------------------------------------------------------------- 1 | use std::cell::UnsafeCell; 2 | use std::collections::{HashMap, HashSet}; 3 | use std::ffi::{c_void, CStr}; 4 | use std::ptr; 5 | use std::rc::Rc; 6 | use std::sync::Arc; 7 | 8 | use vst3::{Class, ComRef, ComWrapper, Steinberg::Vst::*, Steinberg::*}; 9 | 10 | use super::buffers::ScratchBuffers; 11 | use super::host::Vst3Host; 12 | use super::util::{copy_wstring, utf16_from_ptr}; 13 | use super::view::{PlugView, Vst3ViewHost}; 14 | use crate::bus::{BusDir, Format, Layout}; 15 | use crate::engine::{Config, Engine}; 16 | use crate::events::{Data, Event, Events}; 17 | use crate::host::Host; 18 | use crate::params::ParamId; 19 | use crate::plugin::{Plugin, PluginInfo}; 20 | use crate::sync::params::ParamValues; 21 | use crate::util::{slice_from_raw_parts_checked, DisplayParam}; 22 | use crate::view::View; 23 | 24 | fn format_to_speaker_arrangement(format: &Format) -> SpeakerArrangement { 25 | match format { 26 | Format::Mono => SpeakerArr::kMono, 27 | Format::Stereo => SpeakerArr::kStereo, 28 | } 29 | } 30 | 31 | fn speaker_arrangement_to_format(speaker_arrangement: SpeakerArrangement) -> Option { 32 | match speaker_arrangement { 33 | SpeakerArr::kMono => Some(Format::Mono), 34 | SpeakerArr::kStereo => Some(Format::Stereo), 35 | _ => None, 36 | } 37 | } 38 | 39 | pub struct MainThreadState { 40 | pub config: Config, 41 | pub plugin: P, 42 | pub view_host: Rc, 43 | pub view: Option, 44 | } 45 | 46 | struct ProcessState { 47 | config: Config, 48 | scratch_buffers: ScratchBuffers, 49 | events: Vec, 50 | engine: Option, 51 | } 52 | 53 | pub struct Component { 54 | info: Arc, 55 | input_bus_map: Vec, 56 | output_bus_map: Vec, 57 | layout_set: HashSet, 58 | param_map: HashMap, 59 | plugin_params: ParamValues, 60 | engine_params: ParamValues, 61 | _host: Arc, 62 | main_thread_state: Arc>>, 63 | // When the audio processor is *not* active, references to ProcessState may only be formed from 64 | // the main thread. When the audio processor *is* active, references to ProcessState may only 65 | // be formed from the audio thread. 66 | process_state: UnsafeCell>, 67 | } 68 | 69 | impl Component

{ 70 | pub fn new(info: &Arc) -> Component

{ 71 | let mut input_bus_map = Vec::new(); 72 | let mut output_bus_map = Vec::new(); 73 | for (index, bus) in info.buses.iter().enumerate() { 74 | match bus.dir { 75 | BusDir::In => input_bus_map.push(index), 76 | BusDir::Out => output_bus_map.push(index), 77 | BusDir::InOut => { 78 | input_bus_map.push(index); 79 | output_bus_map.push(index); 80 | } 81 | } 82 | } 83 | 84 | let layout_set = info.layouts.iter().cloned().collect::>(); 85 | 86 | let mut param_map = HashMap::new(); 87 | for (index, param) in info.params.iter().enumerate() { 88 | param_map.insert(param.id, index); 89 | } 90 | 91 | let config = Config { 92 | layout: info.layouts.first().cloned().unwrap_or_default(), 93 | sample_rate: 0.0, 94 | max_buffer_size: 0, 95 | }; 96 | 97 | let scratch_buffers = ScratchBuffers::new(input_bus_map.len(), output_bus_map.len()); 98 | 99 | let host = Arc::new(Vst3Host::new()); 100 | 101 | Component { 102 | info: info.clone(), 103 | input_bus_map, 104 | output_bus_map, 105 | layout_set, 106 | param_map, 107 | plugin_params: ParamValues::with_count(info.params.len()), 108 | engine_params: ParamValues::with_count(info.params.len()), 109 | _host: host.clone(), 110 | main_thread_state: Arc::new(UnsafeCell::new(MainThreadState { 111 | config: config.clone(), 112 | plugin: P::new(Host::from_inner(host)), 113 | view_host: Rc::new(Vst3ViewHost::new()), 114 | view: None, 115 | })), 116 | process_state: UnsafeCell::new(ProcessState { 117 | config, 118 | scratch_buffers, 119 | events: Vec::with_capacity(4096), 120 | engine: None, 121 | }), 122 | } 123 | } 124 | 125 | fn sync_plugin(&self, plugin: &mut P) { 126 | for (index, value) in self.plugin_params.poll() { 127 | let id = self.info.params[index].id; 128 | plugin.set_param(id, value); 129 | } 130 | } 131 | } 132 | 133 | impl Class for Component

{ 134 | type Interfaces = ( 135 | IComponent, 136 | IAudioProcessor, 137 | IProcessContextRequirements, 138 | IEditController, 139 | ); 140 | } 141 | 142 | impl IPluginBaseTrait for Component

{ 143 | unsafe fn initialize(&self, _context: *mut FUnknown) -> tresult { 144 | kResultOk 145 | } 146 | 147 | unsafe fn terminate(&self) -> tresult { 148 | kResultOk 149 | } 150 | } 151 | 152 | impl IComponentTrait for Component

{ 153 | unsafe fn getControllerClassId(&self, _classId: *mut TUID) -> tresult { 154 | kNotImplemented 155 | } 156 | 157 | unsafe fn setIoMode(&self, _mode: IoMode) -> tresult { 158 | kResultOk 159 | } 160 | 161 | unsafe fn getBusCount(&self, type_: MediaType, dir: BusDirection) -> int32 { 162 | match type_ as MediaTypes { 163 | MediaTypes_::kAudio => match dir as BusDirections { 164 | BusDirections_::kInput => self.input_bus_map.len() as int32, 165 | BusDirections_::kOutput => self.output_bus_map.len() as int32, 166 | _ => 0, 167 | }, 168 | MediaTypes_::kEvent => 0, 169 | _ => 0, 170 | } 171 | } 172 | 173 | unsafe fn getBusInfo( 174 | &self, 175 | type_: MediaType, 176 | dir: BusDirection, 177 | index: int32, 178 | bus: *mut BusInfo, 179 | ) -> tresult { 180 | let main_thread_state = &*self.main_thread_state.get(); 181 | 182 | match type_ as MediaTypes { 183 | MediaTypes_::kAudio => { 184 | let bus_index = match dir as BusDirections { 185 | BusDirections_::kInput => self.input_bus_map.get(index as usize), 186 | BusDirections_::kOutput => self.output_bus_map.get(index as usize), 187 | _ => return kInvalidArgument, 188 | }; 189 | 190 | if let Some(&bus_index) = bus_index { 191 | let info = self.info.buses.get(bus_index); 192 | let format = main_thread_state.config.layout.formats.get(bus_index); 193 | 194 | if let (Some(info), Some(format)) = (info, format) { 195 | let bus = &mut *bus; 196 | 197 | bus.mediaType = type_; 198 | bus.direction = dir; 199 | bus.channelCount = format.channel_count() as int32; 200 | copy_wstring(&info.name, &mut bus.name); 201 | bus.busType = if index == 0 { 202 | BusTypes_::kMain as BusType 203 | } else { 204 | BusTypes_::kAux as BusType 205 | }; 206 | bus.flags = BusInfo_::BusFlags_::kDefaultActive as uint32; 207 | 208 | return kResultOk; 209 | } 210 | } 211 | } 212 | MediaTypes_::kEvent => {} 213 | _ => {} 214 | } 215 | 216 | kInvalidArgument 217 | } 218 | 219 | unsafe fn getRoutingInfo( 220 | &self, 221 | _inInfo: *mut RoutingInfo, 222 | _outInfo: *mut RoutingInfo, 223 | ) -> tresult { 224 | kNotImplemented 225 | } 226 | 227 | unsafe fn activateBus( 228 | &self, 229 | type_: MediaType, 230 | dir: BusDirection, 231 | index: int32, 232 | state: TBool, 233 | ) -> tresult { 234 | let process_state = &mut *self.process_state.get(); 235 | 236 | match type_ as MediaTypes { 237 | MediaTypes_::kAudio => match dir as BusDirections { 238 | BusDirections_::kInput => { 239 | if self.input_bus_map.get(index as usize).is_some() { 240 | process_state.scratch_buffers.set_input_active(index as usize, state != 0); 241 | return kResultOk; 242 | } 243 | } 244 | BusDirections_::kOutput => { 245 | if self.output_bus_map.get(index as usize).is_some() { 246 | process_state.scratch_buffers.set_output_active(index as usize, state != 0); 247 | return kResultOk; 248 | } 249 | } 250 | _ => {} 251 | }, 252 | MediaTypes_::kEvent => {} 253 | _ => {} 254 | } 255 | 256 | kInvalidArgument 257 | } 258 | 259 | unsafe fn setActive(&self, state: TBool) -> tresult { 260 | let main_thread_state = &mut *self.main_thread_state.get(); 261 | let process_state = &mut *self.process_state.get(); 262 | 263 | if state == 0 { 264 | // Apply any remaining engine -> plugin parameter changes. There won't be any more 265 | // until the plugin becomes active again. 266 | self.sync_plugin(&mut main_thread_state.plugin); 267 | 268 | process_state.engine = None; 269 | } else { 270 | process_state.config = main_thread_state.config.clone(); 271 | process_state.scratch_buffers.resize(&self.info.buses, &process_state.config); 272 | 273 | // Discard any pending plugin -> engine parameter changes, since they will already be 274 | // reflected in the initial state of the engine. 275 | for _ in self.engine_params.poll() {} 276 | 277 | process_state.engine = Some(main_thread_state.plugin.engine(&process_state.config)); 278 | } 279 | 280 | kResultOk 281 | } 282 | 283 | unsafe fn setState(&self, state: *mut IBStream) -> tresult { 284 | use std::io::{Error, ErrorKind, Read, Result}; 285 | 286 | struct StreamReader<'a>(ComRef<'a, IBStream>); 287 | 288 | impl<'a> Read for StreamReader<'a> { 289 | fn read(&mut self, buf: &mut [u8]) -> Result { 290 | let ptr = buf.as_mut_ptr() as *mut c_void; 291 | let len = buf.len() as int32; 292 | let mut bytes: int32 = 0; 293 | let result = unsafe { self.0.read(ptr, len, &mut bytes) }; 294 | 295 | if result == kResultOk { 296 | Ok(bytes as usize) 297 | } else { 298 | Err(Error::new(ErrorKind::Other, "failed to read from stream")) 299 | } 300 | } 301 | } 302 | 303 | if let Some(state) = ComRef::from_raw(state) { 304 | let main_thread_state = &mut *self.main_thread_state.get(); 305 | 306 | self.sync_plugin(&mut main_thread_state.plugin); 307 | 308 | if main_thread_state.plugin.load(&mut StreamReader(state)).is_ok() { 309 | for (index, param) in self.info.params.iter().enumerate() { 310 | let value = main_thread_state.plugin.get_param(param.id); 311 | self.engine_params.set(index, value); 312 | 313 | if let Some(view) = &mut main_thread_state.view { 314 | view.param_changed(param.id, value); 315 | } 316 | } 317 | 318 | return kResultOk; 319 | } 320 | } 321 | 322 | kResultFalse 323 | } 324 | 325 | unsafe fn getState(&self, state: *mut IBStream) -> tresult { 326 | use std::io::{Error, ErrorKind, Result, Write}; 327 | 328 | struct StreamWriter<'a>(ComRef<'a, IBStream>); 329 | 330 | impl<'a> Write for StreamWriter<'a> { 331 | fn write(&mut self, buf: &[u8]) -> Result { 332 | let ptr = buf.as_ptr() as *mut c_void; 333 | let len = buf.len() as int32; 334 | let mut bytes: int32 = 0; 335 | let result = unsafe { self.0.write(ptr, len, &mut bytes) }; 336 | 337 | if result == kResultOk { 338 | Ok(bytes as usize) 339 | } else { 340 | Err(Error::new(ErrorKind::Other, "failed to write to stream")) 341 | } 342 | } 343 | 344 | fn flush(&mut self) -> Result<()> { 345 | Ok(()) 346 | } 347 | } 348 | 349 | if let Some(state) = ComRef::from_raw(state) { 350 | let main_thread_state = &mut *self.main_thread_state.get(); 351 | 352 | self.sync_plugin(&mut main_thread_state.plugin); 353 | 354 | if main_thread_state.plugin.save(&mut StreamWriter(state)).is_ok() { 355 | return kResultOk; 356 | } 357 | } 358 | 359 | kResultFalse 360 | } 361 | } 362 | 363 | impl IAudioProcessorTrait for Component

{ 364 | unsafe fn setBusArrangements( 365 | &self, 366 | inputs: *mut SpeakerArrangement, 367 | numIns: int32, 368 | outputs: *mut SpeakerArrangement, 369 | numOuts: int32, 370 | ) -> tresult { 371 | let input_count = numIns as usize; 372 | let output_count = numOuts as usize; 373 | if input_count != self.input_bus_map.len() || output_count != self.output_bus_map.len() { 374 | return kInvalidArgument; 375 | } 376 | 377 | let mut candidate = Layout { 378 | formats: Vec::new(), 379 | }; 380 | 381 | let mut inputs = slice_from_raw_parts_checked(inputs, input_count).iter(); 382 | let mut outputs = slice_from_raw_parts_checked(outputs, output_count).iter(); 383 | for bus in &self.info.buses { 384 | let arrangement = match bus.dir { 385 | BusDir::In => *inputs.next().unwrap(), 386 | BusDir::Out => *outputs.next().unwrap(), 387 | BusDir::InOut => { 388 | let input_arrangement = *inputs.next().unwrap(); 389 | let output_arrangement = *outputs.next().unwrap(); 390 | if input_arrangement != output_arrangement { 391 | return kResultFalse; 392 | } 393 | output_arrangement 394 | } 395 | }; 396 | 397 | if let Some(format) = speaker_arrangement_to_format(arrangement) { 398 | candidate.formats.push(format); 399 | } else { 400 | return kResultFalse; 401 | } 402 | } 403 | 404 | if self.layout_set.contains(&candidate) { 405 | let main_thread_state = &mut *self.main_thread_state.get(); 406 | main_thread_state.config.layout = candidate; 407 | return kResultTrue; 408 | } 409 | 410 | kResultFalse 411 | } 412 | 413 | unsafe fn getBusArrangement( 414 | &self, 415 | dir: BusDirection, 416 | index: int32, 417 | arr: *mut SpeakerArrangement, 418 | ) -> tresult { 419 | let main_thread_state = &*self.main_thread_state.get(); 420 | 421 | let bus_index = match dir as BusDirections { 422 | BusDirections_::kInput => self.input_bus_map.get(index as usize), 423 | BusDirections_::kOutput => self.output_bus_map.get(index as usize), 424 | _ => return kInvalidArgument, 425 | }; 426 | 427 | if let Some(&bus_index) = bus_index { 428 | #[allow(clippy::unnecessary_cast)] // The type of BusDirection varies by platform 429 | if let Some(format) = main_thread_state.config.layout.formats.get(bus_index as usize) { 430 | *arr = format_to_speaker_arrangement(format); 431 | return kResultOk; 432 | } 433 | } 434 | 435 | kInvalidArgument 436 | } 437 | 438 | unsafe fn canProcessSampleSize(&self, symbolicSampleSize: int32) -> tresult { 439 | match symbolicSampleSize as SymbolicSampleSizes { 440 | SymbolicSampleSizes_::kSample32 => kResultTrue, 441 | SymbolicSampleSizes_::kSample64 => kResultFalse, 442 | _ => kInvalidArgument, 443 | } 444 | } 445 | 446 | unsafe fn getLatencySamples(&self) -> uint32 { 447 | let main_thread_state = &mut *self.main_thread_state.get(); 448 | 449 | self.sync_plugin(&mut main_thread_state.plugin); 450 | main_thread_state.plugin.latency(&main_thread_state.config) as uint32 451 | } 452 | 453 | unsafe fn setupProcessing(&self, setup: *mut ProcessSetup) -> tresult { 454 | let main_thread_state = &mut *self.main_thread_state.get(); 455 | 456 | let setup = &*setup; 457 | main_thread_state.config.sample_rate = setup.sampleRate; 458 | main_thread_state.config.max_buffer_size = setup.maxSamplesPerBlock as usize; 459 | 460 | kResultOk 461 | } 462 | 463 | unsafe fn setProcessing(&self, state: TBool) -> tresult { 464 | let process_state = &mut *self.process_state.get(); 465 | 466 | let Some(engine) = &mut process_state.engine else { 467 | return kNotInitialized; 468 | }; 469 | 470 | if state == 0 { 471 | // Flush plugin -> engine parameter changes 472 | process_state.events.clear(); 473 | for (index, value) in self.engine_params.poll() { 474 | process_state.events.push(Event { 475 | time: 0, 476 | data: Data::ParamChange { 477 | id: self.info.params[index].id, 478 | value, 479 | }, 480 | }); 481 | } 482 | 483 | if !process_state.events.is_empty() { 484 | engine.flush(Events::new(&process_state.events)); 485 | } 486 | 487 | engine.reset(); 488 | } 489 | 490 | kResultOk 491 | } 492 | 493 | unsafe fn process(&self, data: *mut ProcessData) -> tresult { 494 | let process_state = &mut *self.process_state.get(); 495 | 496 | let Some(engine) = &mut process_state.engine else { 497 | return kNotInitialized; 498 | }; 499 | 500 | let data = &*data; 501 | 502 | let Ok(buffers) = process_state.scratch_buffers.get_buffers( 503 | &self.info.buses, 504 | &self.input_bus_map, 505 | &self.output_bus_map, 506 | &process_state.config, 507 | data, 508 | ) else { 509 | return kInvalidArgument; 510 | }; 511 | 512 | process_state.events.clear(); 513 | 514 | for (index, value) in self.engine_params.poll() { 515 | process_state.events.push(Event { 516 | time: 0, 517 | data: Data::ParamChange { 518 | id: self.info.params[index].id, 519 | value, 520 | }, 521 | }); 522 | } 523 | 524 | if let Some(param_changes) = ComRef::from_raw(data.inputParameterChanges) { 525 | for index in 0..param_changes.getParameterCount() { 526 | let param_data = param_changes.getParameterData(index); 527 | let Some(param_data) = ComRef::from_raw(param_data) else { 528 | continue; 529 | }; 530 | 531 | let id = param_data.getParameterId(); 532 | let point_count = param_data.getPointCount(); 533 | 534 | let Some(¶m_index) = self.param_map.get(&id) else { 535 | continue; 536 | }; 537 | 538 | for index in 0..point_count { 539 | let mut offset = 0; 540 | let mut value = 0.0; 541 | let result = param_data.getPoint(index, &mut offset, &mut value); 542 | 543 | if result != kResultOk { 544 | continue; 545 | } 546 | 547 | process_state.events.push(Event { 548 | time: offset as i64, 549 | data: Data::ParamChange { id, value }, 550 | }); 551 | 552 | self.plugin_params.set(param_index, value); 553 | } 554 | } 555 | } 556 | 557 | let events = Events::new(&process_state.events); 558 | if let Some(buffers) = buffers { 559 | engine.process(buffers, events); 560 | } else { 561 | engine.flush(events); 562 | } 563 | 564 | kResultOk 565 | } 566 | 567 | unsafe fn getTailSamples(&self) -> uint32 { 568 | kInfiniteTail 569 | } 570 | } 571 | 572 | impl IProcessContextRequirementsTrait for Component

{ 573 | unsafe fn getProcessContextRequirements(&self) -> uint32 { 574 | 0 575 | } 576 | } 577 | 578 | impl IEditControllerTrait for Component

{ 579 | unsafe fn setComponentState(&self, _state: *mut IBStream) -> tresult { 580 | kResultOk 581 | } 582 | 583 | unsafe fn setState(&self, _state: *mut IBStream) -> tresult { 584 | kResultOk 585 | } 586 | 587 | unsafe fn getState(&self, _state: *mut IBStream) -> tresult { 588 | kResultOk 589 | } 590 | 591 | unsafe fn getParameterCount(&self) -> int32 { 592 | self.info.params.len() as int32 593 | } 594 | 595 | unsafe fn getParameterInfo(&self, paramIndex: int32, info: *mut ParameterInfo) -> tresult { 596 | if let Some(param) = self.info.params.get(paramIndex as usize) { 597 | let info = &mut *info; 598 | 599 | info.id = param.id as ParamID; 600 | copy_wstring(¶m.name, &mut info.title); 601 | copy_wstring(¶m.name, &mut info.shortTitle); 602 | copy_wstring("", &mut info.units); 603 | info.stepCount = if let Some(steps) = param.steps { 604 | (steps.max(2) - 1) as int32 605 | } else { 606 | 0 607 | }; 608 | info.defaultNormalizedValue = param.default; 609 | info.unitId = 0; 610 | info.flags = ParameterInfo_::ParameterFlags_::kCanAutomate as int32; 611 | 612 | return kResultOk; 613 | } 614 | 615 | kInvalidArgument 616 | } 617 | 618 | unsafe fn getParamStringByValue( 619 | &self, 620 | id: ParamID, 621 | valueNormalized: ParamValue, 622 | string: *mut String128, 623 | ) -> tresult { 624 | let main_thread_state = &*self.main_thread_state.get(); 625 | 626 | if self.param_map.contains_key(&id) { 627 | let display = format!( 628 | "{}", 629 | DisplayParam::new(&main_thread_state.plugin, id, valueNormalized) 630 | ); 631 | copy_wstring(&display, &mut *string); 632 | 633 | return kResultOk; 634 | } 635 | 636 | kInvalidArgument 637 | } 638 | 639 | unsafe fn getParamValueByString( 640 | &self, 641 | id: ParamID, 642 | string: *mut TChar, 643 | valueNormalized: *mut ParamValue, 644 | ) -> tresult { 645 | let main_thread_state = &*self.main_thread_state.get(); 646 | 647 | if self.param_map.contains_key(&id) { 648 | if let Ok(display) = String::from_utf16(utf16_from_ptr(string)) { 649 | if let Some(value) = main_thread_state.plugin.parse_param(id, &display) { 650 | *valueNormalized = value; 651 | return kResultOk; 652 | } 653 | } 654 | } 655 | 656 | kInvalidArgument 657 | } 658 | 659 | unsafe fn normalizedParamToPlain( 660 | &self, 661 | _id: ParamID, 662 | valueNormalized: ParamValue, 663 | ) -> ParamValue { 664 | valueNormalized 665 | } 666 | 667 | unsafe fn plainParamToNormalized(&self, _id: ParamID, plainValue: ParamValue) -> ParamValue { 668 | plainValue 669 | } 670 | 671 | unsafe fn getParamNormalized(&self, id: ParamID) -> ParamValue { 672 | let main_thread_state = &*self.main_thread_state.get(); 673 | 674 | if self.param_map.contains_key(&id) { 675 | return main_thread_state.plugin.get_param(id); 676 | } 677 | 678 | 0.0 679 | } 680 | 681 | unsafe fn setParamNormalized(&self, id: ParamID, value: ParamValue) -> tresult { 682 | let main_thread_state = &mut *self.main_thread_state.get(); 683 | 684 | if self.param_map.contains_key(&id) { 685 | main_thread_state.plugin.set_param(id, value); 686 | 687 | if let Some(view) = &mut main_thread_state.view { 688 | view.param_changed(id, value); 689 | } 690 | 691 | return kResultOk; 692 | } 693 | 694 | kInvalidArgument 695 | } 696 | 697 | unsafe fn setComponentHandler(&self, handler: *mut IComponentHandler) -> tresult { 698 | let main_thread_state = &mut *self.main_thread_state.get(); 699 | 700 | let mut current_handler = main_thread_state.view_host.handler.borrow_mut(); 701 | if let Some(handler) = ComRef::from_raw(handler) { 702 | *current_handler = Some(handler.to_com_ptr()); 703 | } else { 704 | *current_handler = None; 705 | } 706 | 707 | kResultOk 708 | } 709 | 710 | unsafe fn createView(&self, name: FIDString) -> *mut IPlugView { 711 | if !self.info.has_view { 712 | return ptr::null_mut(); 713 | } 714 | 715 | if CStr::from_ptr(name) != CStr::from_ptr(ViewType::kEditor) { 716 | return ptr::null_mut(); 717 | } 718 | 719 | let view = ComWrapper::new(PlugView::new(&self.main_thread_state)); 720 | view.to_com_ptr::().unwrap().into_raw() 721 | } 722 | } 723 | --------------------------------------------------------------------------------