Skip to main content

nice_plug/wrapper/
vst3.rs

1#[macro_use]
2mod util;
3
4mod context;
5mod factory;
6mod inner;
7mod note_expressions;
8mod param_units;
9pub mod subcategories;
10mod view;
11mod wrapper;
12
13/// Re-export for the wrapper.
14pub use factory::PluginInfo;
15use nice_plug_core::plugin::Plugin;
16pub use vst3;
17pub use wrapper::Wrapper;
18
19use crate::wrapper::vst3::subcategories::Vst3SubCategory;
20
21/// Provides auxiliary metadata needed for a VST3 plugin.
22pub trait Vst3Plugin: Plugin {
23    /// The unique class ID that identifies this particular plugin. You can use the
24    /// `*b"fooofooofooofooo"` syntax for this.
25    ///
26    /// This will be shuffled into a different byte order on Windows for project-compatibility.
27    const VST3_CLASS_ID: [u8; 16];
28    /// One or more subcategories. The host may use these to categorize the plugin. Internally this
29    /// slice will be converted to a string where each character is separated by a pipe character
30    /// (`|`). This string has a limit of 127 characters, and anything longer than that will be
31    /// truncated.
32    const VST3_SUBCATEGORIES: &'static [Vst3SubCategory];
33
34    /// [`VST3_CLASS_ID`][Self::VST3_CLASS_ID`] in the correct order for the current platform so
35    /// projects and presets can be shared between platforms. This should not be overridden.
36    const PLATFORM_VST3_CLASS_ID: [u8; 16] = swap_vst3_uid_byte_order(Self::VST3_CLASS_ID);
37}
38
39#[cfg(not(target_os = "windows"))]
40const fn swap_vst3_uid_byte_order(uid: [u8; 16]) -> [u8; 16] {
41    uid
42}
43
44#[cfg(target_os = "windows")]
45const fn swap_vst3_uid_byte_order(mut uid: [u8; 16]) -> [u8; 16] {
46    // No mutable references in const functions, so we can't use `uid.swap()`
47    let original_uid = uid;
48
49    uid[0] = original_uid[3];
50    uid[1] = original_uid[2];
51    uid[2] = original_uid[1];
52    uid[3] = original_uid[0];
53
54    uid[4] = original_uid[5];
55    uid[5] = original_uid[4];
56    uid[6] = original_uid[7];
57    uid[7] = original_uid[6];
58
59    uid
60}
61
62/// Export one or more VST3 plugins from this library using the provided plugin types. The first
63/// plugin's vendor information is used for the factory's information.
64#[macro_export]
65macro_rules! nice_export_vst3 {
66    ($($plugin_ty:ty),+) => {
67        // Earlier versions used a simple generic struct for this, but because we don't have
68        // variadic generics (yet) we can't generate the struct for multiple plugin types without
69        // macros. So instead we'll generate the implementation ad-hoc inside of this macro.
70        #[doc(hidden)]
71        mod vst3 {
72            use ::std::collections::HashSet;
73            use ::std::ffi::c_void;
74
75            // `vst3` is imported from the VST3 wrapper module
76            use $crate::wrapper::vst3::{PluginInfo, Wrapper};
77            use $crate::wrapper::vst3::vst3::Steinberg::{kInvalidArgument, kResultOk, tresult, int32, FIDString, TUID};
78            use $crate::wrapper::vst3::vst3::Steinberg::{
79                PFactoryInfo_::FactoryFlags_, IPluginFactory, IPluginFactory2, IPluginFactory3, FUnknown,
80                PClassInfo, PClassInfo2, PClassInfoW, PFactoryInfo, IPluginFactoryTrait, IPluginFactory2Trait, IPluginFactory3Trait,
81            };
82            use $crate::wrapper::vst3::vst3::{Class, ComWrapper};
83
84            // Because the `$plugin_ty`s are likely defined in the enclosing scope. This works even
85            // if the types are not public because this is a child module.
86            use super::*;
87
88            // Sneaky way to get the number of expanded elements
89            const PLUGIN_COUNT: usize = [$(stringify!($plugin_ty)),+].len();
90
91            #[doc(hidden)]
92            pub struct Factory {
93                // This is a type erased version of the information stored on the plugin types
94                plugin_infos: [PluginInfo; PLUGIN_COUNT],
95            }
96
97            impl Class for Factory {
98                type Interfaces = (IPluginFactory, IPluginFactory2, IPluginFactory3);
99            }
100
101            impl Factory {
102                pub fn new() -> Self {
103                    let plugin_infos = [$(PluginInfo::for_plugin::<$plugin_ty>()),+];
104
105                    if cfg!(debug_assertions) {
106                        let unique_cids: HashSet<[u8; 16]> = plugin_infos.iter().map(|d| *d.cid).collect();
107                        $crate::nice_debug_assert_eq!(
108                            unique_cids.len(),
109                            plugin_infos.len(),
110                            "Duplicate VST3 class IDs found in `nice_export_vst3!()` call"
111                        );
112                    }
113
114                    Factory { plugin_infos }
115                }
116            }
117
118            impl IPluginFactoryTrait for Factory {
119                unsafe fn getFactoryInfo(&self, info: *mut PFactoryInfo) -> tresult {
120                    if info.is_null() {
121                        return kInvalidArgument;
122                    }
123
124                    // We'll use the first plugin's info for this
125                    unsafe { *info = self.plugin_infos[0].create_factory_info(); }
126
127                    kResultOk
128                }
129
130                unsafe fn countClasses(&self) -> int32 {
131                    self.plugin_infos.len() as i32
132                }
133
134                unsafe fn getClassInfo(&self, index: int32, info: *mut PClassInfo) -> tresult {
135                    if index < 0 || index >= self.plugin_infos.len() as i32 {
136                        return kInvalidArgument;
137                    }
138
139                    unsafe { *info = self.plugin_infos[index as usize].create_class_info(); }
140
141                    kResultOk
142                }
143
144                unsafe fn createInstance(
145                    &self,
146                    cid: FIDString,
147                    iid: FIDString,
148                    obj: *mut *mut c_void,
149                ) -> tresult {
150                    // Can't use `check_null_ptr!()` here without polluting nice-plug's general
151                    // exports
152                    if cid.is_null() || obj.is_null() {
153                        return kInvalidArgument;
154                    }
155
156                    unsafe {
157                        let cid = &*(cid as *const [u8; 16]);
158
159                        // This is a poor man's way of treating `$plugin_ty` like an indexable array.
160                        // Assuming `self.plugin_infos` is in the same order, we can simply check all of
161                        // the registered plugin CIDs for matches using an unrolled loop.
162                        let mut plugin_idx = 0;
163                        $({
164                            let plugin_info = &self.plugin_infos[plugin_idx];
165                            if cid == plugin_info.cid {
166                                let wrapper = ComWrapper::new(Wrapper::<$plugin_ty>::new());
167                                let unknown = wrapper.as_com_ref::<FUnknown>().unwrap();
168                                let ptr = unknown.as_ptr();
169                                return ((*(*ptr).vtbl).queryInterface)(ptr, iid as *const TUID, obj);
170                            }
171
172                            plugin_idx += 1;
173                        })+
174                    }
175
176                    kInvalidArgument
177                }
178            }
179
180            impl IPluginFactory2Trait for Factory {
181                unsafe fn getClassInfo2(&self, index: int32, info: *mut PClassInfo2) -> tresult {
182                    if index < 0 || index >= self.plugin_infos.len() as i32 {
183                        return kInvalidArgument;
184                    }
185
186                    unsafe { *info = self.plugin_infos[index as usize].create_class_info_2(); }
187
188                    kResultOk
189                }
190            }
191
192            impl IPluginFactory3Trait for Factory {
193                unsafe fn getClassInfoUnicode(
194                    &self,
195                    index: int32,
196                    info: *mut PClassInfoW,
197                ) -> tresult {
198                    if index < 0 || index >= self.plugin_infos.len() as i32 {
199                        return kInvalidArgument;
200                    }
201
202                    unsafe { *info = self.plugin_infos[index as usize].create_class_info_unicode(); }
203
204                    kResultOk
205                }
206
207                unsafe fn setHostContext(&self, _context: *mut FUnknown) -> tresult {
208                    // We don't need to do anything with this
209                    kResultOk
210                }
211            }
212        }
213
214        /// The VST3 plugin factory entry point.
215        #[unsafe(no_mangle)]
216        pub extern "system" fn GetPluginFactory() -> *mut ::std::ffi::c_void {
217            use $crate::wrapper::vst3::vst3::{ComWrapper, Steinberg::IPluginFactory};
218
219            ComWrapper::new(self::vst3::Factory::new())
220                .to_com_ptr::<IPluginFactory>()
221                .unwrap()
222                .into_raw() as *mut ::std::ffi::c_void
223        }
224
225        // These two entry points are used on Linux, and they would theoretically also be used on
226        // the BSDs:
227        // https://github.com/steinbergmedia/vst3_public_sdk/blob/c3948deb407bdbff89de8fb6ab8500ea4df9d6d9/source/main/linuxmain.cpp#L47-L52
228        #[allow(missing_docs)]
229        #[unsafe(no_mangle)]
230        #[cfg(all(target_family = "unix", not(target_os = "macos")))]
231        pub extern "C" fn ModuleEntry(_lib_handle: *mut ::std::ffi::c_void) -> bool {
232            $crate::wrapper::setup_logger();
233            true
234        }
235
236        #[allow(missing_docs)]
237        #[unsafe(no_mangle)]
238        #[cfg(all(target_family = "unix", not(target_os = "macos")))]
239        pub extern "C" fn ModuleExit() -> bool {
240            true
241        }
242
243        // These two entry points are used on macOS:
244        // https://github.com/steinbergmedia/vst3_public_sdk/blob/bc459feee68803346737901471441fd4829ec3f9/source/main/macmain.cpp#L60-L61
245        #[allow(missing_docs)]
246        #[unsafe(no_mangle)]
247        #[cfg(target_os = "macos")]
248        pub extern "C" fn bundleEntry(_lib_handle: *mut ::std::ffi::c_void) -> bool {
249            $crate::wrapper::setup_logger();
250            true
251        }
252
253        #[allow(missing_docs)]
254        #[unsafe(no_mangle)]
255        #[cfg(target_os = "macos")]
256        pub extern "C" fn bundleExit() -> bool {
257            true
258        }
259
260        // And these two entry points are used on Windows:
261        // https://github.com/steinbergmedia/vst3_public_sdk/blob/bc459feee68803346737901471441fd4829ec3f9/source/main/dllmain.cpp#L59-L60
262        #[allow(missing_docs)]
263        #[unsafe(no_mangle)]
264        #[cfg(target_os = "windows")]
265        pub extern "system" fn InitDll() -> bool {
266            $crate::wrapper::setup_logger();
267            true
268        }
269
270        #[allow(missing_docs)]
271        #[unsafe(no_mangle)]
272        #[cfg(target_os = "windows")]
273        pub extern "system" fn ExitDll() -> bool {
274            true
275        }
276    };
277}