#![doc = include_str!("../docs_boilerplate.md")]
#![doc = include_str!("../README.md")]
pub use conformal_ui::Size as UiSize;
use core::slice;
#[doc(hidden)]
pub use vst3 as _vst3;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct HostInfo {
pub name: String,
}
pub type ClassID = [u8; 16];
#[allow(clippy::module_name_repetitions)]
pub trait ComponentFactory: Clone {
type Component;
fn create(&self, host: &HostInfo) -> Self::Component;
}
impl<C, F: Fn(&HostInfo) -> C + Clone> ComponentFactory for F {
type Component = C;
fn create(&self, host_info: &HostInfo) -> C {
(self)(host_info)
}
}
#[derive(Debug, Clone, Copy)]
enum Resizability {
FixedSize,
Resizable {
ui_min_size: Option<UiSize>,
ui_max_size: Option<UiSize>,
},
}
#[derive(Debug, Clone, Copy)]
pub struct ClassInfo<'a> {
name: &'a str,
cid: ClassID,
edit_controller_cid: ClassID,
ui_initial_size: UiSize,
resizability: Resizability,
}
#[derive(Debug, Clone, Copy)]
pub struct ClassInfoBuilder<'a> {
info: ClassInfo<'a>,
}
#[derive(Debug, Clone, Copy, Default)]
pub struct ResizingOptions {
pub ui_min_size: Option<UiSize>,
pub ui_max_size: Option<UiSize>,
}
impl<'a> ClassInfoBuilder<'a> {
#[must_use]
pub const fn new(
name: &'a str,
cid: ClassID,
edit_controller_cid: ClassID,
ui_initial_size: UiSize,
) -> Self {
Self {
info: ClassInfo {
name,
cid,
edit_controller_cid,
ui_initial_size,
resizability: Resizability::FixedSize,
},
}
}
#[must_use]
pub const fn resizable(self, options: ResizingOptions) -> Self {
Self {
info: ClassInfo {
resizability: Resizability::Resizable {
ui_min_size: options.ui_min_size,
ui_max_size: options.ui_max_size,
},
..self.info
},
}
}
#[must_use]
pub const fn build(self) -> ClassInfo<'a> {
self.info
}
}
#[doc(hidden)]
pub struct ParameterModel {
pub parameter_infos: Box<dyn Fn(&HostInfo) -> Vec<conformal_component::parameters::Info>>,
}
#[doc(hidden)]
pub trait ClassCategory {
fn create_processor(&self, controller_cid: ClassID) -> vst3::ComPtr<IPluginBase>;
fn info(&self) -> &ClassInfo<'static>;
fn category_str(&self) -> &'static str;
fn create_parameter_model(&self) -> ParameterModel;
fn get_kind(&self) -> edit_controller::Kind;
}
pub struct SynthClass<CF> {
pub factory: CF,
pub info: ClassInfo<'static>,
}
fn create_parameter_model_internal<CF: ComponentFactory + 'static>(factory: CF) -> ParameterModel
where
CF::Component: Component,
{
ParameterModel {
parameter_infos: Box::new(move |host_info| {
let component = factory.create(host_info);
component.parameter_infos()
}),
}
}
impl<CF: ComponentFactory + 'static> ClassCategory for SynthClass<CF>
where
CF::Component: Component<Processor: Synth> + 'static,
{
fn create_processor(&self, controller_cid: ClassID) -> vst3::ComPtr<IPluginBase> {
vst3::ComWrapper::new(processor::create_synth(
self.factory.clone(),
controller_cid,
))
.to_com_ptr::<IPluginBase>()
.unwrap()
}
fn create_parameter_model(&self) -> ParameterModel {
create_parameter_model_internal(self.factory.clone())
}
fn category_str(&self) -> &'static str {
"Instrument|Synth"
}
fn info(&self) -> &ClassInfo<'static> {
&self.info
}
fn get_kind(&self) -> edit_controller::Kind {
edit_controller::Kind::Synth()
}
}
pub struct EffectClass<CF> {
pub factory: CF,
pub info: ClassInfo<'static>,
pub category: &'static str,
pub bypass_id: &'static str,
}
impl<CF: ComponentFactory<Component: Component<Processor: Effect> + 'static> + 'static>
ClassCategory for EffectClass<CF>
{
fn create_processor(&self, controller_cid: ClassID) -> vst3::ComPtr<IPluginBase> {
vst3::ComWrapper::new(processor::create_effect(
self.factory.clone(),
controller_cid,
))
.to_com_ptr::<IPluginBase>()
.unwrap()
}
fn category_str(&self) -> &'static str {
self.category
}
fn info(&self) -> &ClassInfo<'static> {
&self.info
}
fn create_parameter_model(&self) -> ParameterModel {
create_parameter_model_internal(self.factory.clone())
}
fn get_kind(&self) -> edit_controller::Kind {
edit_controller::Kind::Effect {
bypass_id: self.bypass_id,
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct Info<'a> {
pub vendor: &'a str,
pub url: &'a str,
pub email: &'a str,
pub version: &'a str,
}
use conformal_component::Component;
use conformal_component::effect::Effect;
use conformal_component::synth::Synth;
use vst3::Steinberg::{IPluginBase, IPluginFactory2, IPluginFactory2Trait};
use vst3::{Class, Steinberg::IPluginFactory};
mod edit_controller;
mod factory;
mod host_info;
mod io;
mod mpe;
mod parameters;
mod processor;
mod view;
#[cfg(test)]
mod dummy_host;
#[cfg(test)]
mod fake_ibstream;
#[doc(hidden)]
pub fn _wrap_factory(
classes: &'static [&'static dyn ClassCategory],
info: Info<'static>,
) -> impl Class<Interfaces = (IPluginFactory, IPluginFactory2)> + 'static + IPluginFactory2Trait {
factory::Factory::new(classes, info)
}
fn to_utf16(s: &str, buffer: &mut [u16]) {
for (i, c) in s.encode_utf16().chain([0]).enumerate() {
buffer[i] = c;
}
}
fn from_utf16_ptr(buffer: *const u16, max_size: usize) -> Option<String> {
let mut len = 0;
unsafe {
while *buffer.add(len) != 0 {
if len >= max_size {
return None;
}
len += 1;
}
}
let utf16_slice = unsafe { slice::from_raw_parts(buffer.cast(), len) };
String::from_utf16(utf16_slice).ok()
}
fn from_utf16_buffer(buffer: &[u16]) -> Option<String> {
let mut len = 0;
for c in buffer {
if *c == 0 {
break;
}
len += 1;
}
let utf16_slice = unsafe { slice::from_raw_parts(buffer.as_ptr().cast(), len) };
String::from_utf16(utf16_slice).ok()
}
#[macro_export]
macro_rules! wrap_factory {
($CLASSES:expr, $INFO:expr) => {
#[unsafe(no_mangle)]
#[allow(non_snake_case, clippy::missing_safety_doc, clippy::missing_panics_doc)]
pub unsafe extern "system" fn GetPluginFactory() -> *mut core::ffi::c_void {
let factory = $crate::_wrap_factory($CLASSES, $INFO);
$crate::_vst3::ComWrapper::new(factory)
.to_com_ptr::<$crate::_vst3::Steinberg::IPluginFactory>()
.unwrap()
.into_raw()
.cast()
}
#[cfg(target_os = "macos")]
#[unsafe(no_mangle)]
#[allow(non_snake_case)]
pub extern "system" fn bundleEntry(_: *mut core::ffi::c_void) -> bool {
true
}
#[cfg(target_os = "macos")]
#[unsafe(no_mangle)]
#[allow(non_snake_case)]
pub extern "system" fn bundleExit() -> bool {
true
}
};
}
#[cfg(target_os = "windows")]
type DefaultEnumType = std::ffi::c_int;
#[cfg(target_os = "windows")]
type FromU32ConversionError = std::num::TryFromIntError;
#[cfg(target_os = "windows")]
type ToU32ConversionError = std::num::TryFromIntError;
#[cfg(target_os = "windows")]
type FromI32ConversionError = std::convert::Infallible;
#[cfg(target_os = "windows")]
type ToI32ConversionError = std::convert::Infallible;
#[cfg(not(target_os = "windows"))]
type DefaultEnumType = std::ffi::c_uint;
#[cfg(not(target_os = "windows"))]
type FromU32ConversionError = std::convert::Infallible;
#[cfg(not(target_os = "windows"))]
type ToU32ConversionError = std::convert::Infallible;
#[cfg(not(target_os = "windows"))]
type FromI32ConversionError = std::num::TryFromIntError;
#[cfg(not(target_os = "windows"))]
type ToI32ConversionError = std::num::TryFromIntError;
#[cfg(target_os = "windows")]
fn enum_to_u32(value: DefaultEnumType) -> Result<u32, ToU32ConversionError> {
value.try_into()
}
#[cfg(target_os = "windows")]
fn u32_to_enum(value: u32) -> Result<DefaultEnumType, FromU32ConversionError> {
value.try_into()
}
#[cfg(target_os = "windows")]
#[allow(clippy::unnecessary_wraps)] fn i32_to_enum(value: i32) -> Result<DefaultEnumType, FromI32ConversionError> {
Ok(value)
}
#[cfg(target_os = "windows")]
#[allow(clippy::unnecessary_wraps)] fn enum_to_i32(value: DefaultEnumType) -> Result<i32, ToI32ConversionError> {
Ok(value)
}
#[cfg(not(target_os = "windows"))]
#[allow(clippy::unnecessary_wraps)] fn enum_to_u32(value: DefaultEnumType) -> Result<u32, ToU32ConversionError> {
Ok(value)
}
#[cfg(not(target_os = "windows"))]
#[allow(clippy::unnecessary_wraps)] fn u32_to_enum(value: u32) -> Result<DefaultEnumType, FromU32ConversionError> {
Ok(value)
}
#[cfg(not(target_os = "windows"))]
fn i32_to_enum(value: i32) -> Result<DefaultEnumType, FromI32ConversionError> {
value.try_into()
}
#[cfg(not(target_os = "windows"))]
fn enum_to_i32(value: DefaultEnumType) -> Result<i32, ToI32ConversionError> {
value.try_into()
}