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