beamer-vst3 0.2.3

VST3 implementation layer for the Beamer audio plugin framework
Documentation
//! VST3 Plugin Factory implementation.
//!
//! Generic factory with inline component creation.

use std::ffi::c_void;
use std::marker::PhantomData;

use beamer_core::Config;
use vst3::com_scrape_types::MakeHeader;
use vst3::{Class, ComWrapper, Steinberg::*};

use crate::util::{copy_cstring, copy_wstring};

/// VST3 Plugin Factory.
///
/// Generic over the component type C. Creates combined component instances
/// (IComponent + IEditController in one object).
pub struct Factory<C> {
    config: &'static Config,
    component_uid: TUID,
    controller_uid: Option<TUID>,
    _marker: PhantomData<C>,
}

impl<C> Factory<C> {
    /// Create a new factory with the given configuration.
    ///
    /// Computes the VST3 TUIDs from the unified Config.
    pub fn new(config: &'static Config) -> Self {
        let parts = config.vst3_uid_parts();
        let component_uid = vst3::uid(parts[0], parts[1], parts[2], parts[3]);
        let controller_uid = config.vst3_controller_uid_parts().map(|p| {
            vst3::uid(p[0], p[1], p[2], p[3])
        });
        Self {
            config,
            component_uid,
            controller_uid,
            _marker: PhantomData,
        }
    }

    /// Returns true if a dedicated controller class is registered.
    fn has_controller(&self) -> bool {
        self.controller_uid.is_some()
    }
}

/// Trait implemented by component types that can be constructed from plugin configs.
pub trait ComponentFactory: Class {
    fn create(config: &'static Config) -> Self;
}

impl<C> Class for Factory<C>
where
    C: ComponentFactory + 'static,
    C::Interfaces: MakeHeader<C, ComWrapper<C>>,
{
    type Interfaces = (IPluginFactory3,);
}

impl<C> IPluginFactoryTrait for Factory<C>
where
    C: ComponentFactory + 'static,
    C::Interfaces: MakeHeader<C, ComWrapper<C>>,
{
    unsafe fn getFactoryInfo(&self, info: *mut PFactoryInfo) -> tresult {
        if info.is_null() {
            return kInvalidArgument;
        }

        // SAFETY: Validated info is non-null above. Host guarantees validity for call duration.
        let info = unsafe { &mut *info };
        copy_cstring(self.config.vendor, &mut info.vendor);
        copy_cstring(self.config.url, &mut info.url);
        copy_cstring(self.config.email, &mut info.email);
        info.flags = PFactoryInfo_::FactoryFlags_::kUnicode as int32;

        kResultOk
    }

    unsafe fn countClasses(&self) -> i32 {
        if self.has_controller() {
            2
        } else {
            1
        }
    }

    unsafe fn getClassInfo(&self, index: i32, info: *mut PClassInfo) -> tresult {
        if info.is_null() {
            return kInvalidArgument;
        }

        match index {
            0 => {
                // SAFETY: Validated info is non-null above. Host guarantees validity.
                let info = unsafe { &mut *info };
                info.cid = self.component_uid;
                info.cardinality = PClassInfo_::ClassCardinality_::kManyInstances as int32;
                copy_cstring("Audio Module Class", &mut info.category);
                copy_cstring(self.config.name, &mut info.name);
                kResultOk
            }
            1 if self.has_controller() => {
                // SAFETY: Validated info is non-null above. Host guarantees validity.
                let info = unsafe { &mut *info };
                info.cid = self.controller_uid.unwrap();
                info.cardinality = PClassInfo_::ClassCardinality_::kManyInstances as int32;
                copy_cstring("Component Controller Class", &mut info.category);
                copy_cstring(self.config.name, &mut info.name);
                kResultOk
            }
            _ => kInvalidArgument,
        }
    }

    unsafe fn createInstance(
        &self,
        cid: FIDString,
        iid: FIDString,
        obj: *mut *mut c_void,
    ) -> tresult {
        if cid.is_null() || iid.is_null() || obj.is_null() {
            return kInvalidArgument;
        }

        // SAFETY: Validated cid is non-null above. Host guarantees it points to valid TUID.
        let requested_cid = unsafe { &*(cid as *const TUID) };

        // Check if request matches component or controller UID
        if *requested_cid != self.component_uid {
            if let Some(controller_uid) = self.controller_uid {
                if *requested_cid != controller_uid {
                    return kInvalidArgument;
                }
            } else {
                return kInvalidArgument;
            }
        }

        // Create component and query requested interface
        let component = ComWrapper::new(C::create(self.config));
        let unknown = component.as_com_ref::<FUnknown>().unwrap();
        let ptr = unknown.as_ptr();
        // SAFETY: ptr is valid COM pointer from ComWrapper. vtbl and queryInterface
        // are guaranteed valid by COM contract.
        unsafe { ((*(*ptr).vtbl).queryInterface)(ptr, iid as *const TUID, obj) }
    }
}

impl<C> IPluginFactory2Trait for Factory<C>
where
    C: ComponentFactory + 'static,
    C::Interfaces: MakeHeader<C, ComWrapper<C>>,
{
    unsafe fn getClassInfo2(&self, index: i32, info: *mut PClassInfo2) -> tresult {
        if info.is_null() {
            return kInvalidArgument;
        }

        match index {
            0 => {
                // SAFETY: Validated info is non-null above. Host guarantees validity.
                let info = unsafe { &mut *info };
                info.cid = self.component_uid;
                info.cardinality = PClassInfo_::ClassCardinality_::kManyInstances as int32;
                copy_cstring("Audio Module Class", &mut info.category);
                copy_cstring(self.config.name, &mut info.name);
                info.classFlags = 0;
                // Derive subcategories from shared Config
                let subcategories = self.config.vst3_subcategories();
                copy_cstring(&subcategories, &mut info.subCategories);
                copy_cstring(self.config.vendor, &mut info.vendor);
                copy_cstring(self.config.version, &mut info.version);
                copy_cstring("VST 3.8.0", &mut info.sdkVersion);
                kResultOk
            }
            1 if self.has_controller() => {
                // SAFETY: Validated info is non-null above. Host guarantees validity.
                let info = unsafe { &mut *info };
                info.cid = self.controller_uid.unwrap();
                info.cardinality = PClassInfo_::ClassCardinality_::kManyInstances as int32;
                copy_cstring("Component Controller Class", &mut info.category);
                copy_cstring(self.config.name, &mut info.name);
                info.classFlags = 1; // kComponentControllerClass
                copy_cstring("", &mut info.subCategories);
                copy_cstring(self.config.vendor, &mut info.vendor);
                copy_cstring(self.config.version, &mut info.version);
                copy_cstring("VST 3.8.0", &mut info.sdkVersion);
                kResultOk
            }
            _ => kInvalidArgument,
        }
    }
}

impl<C> IPluginFactory3Trait for Factory<C>
where
    C: ComponentFactory + 'static,
    C::Interfaces: MakeHeader<C, ComWrapper<C>>,
{
    unsafe fn getClassInfoUnicode(&self, index: i32, info: *mut PClassInfoW) -> tresult {
        if info.is_null() {
            return kInvalidArgument;
        }

        match index {
            0 => {
                // SAFETY: Validated info is non-null above. Host guarantees validity.
                let info = unsafe { &mut *info };
                info.cid = self.component_uid;
                info.cardinality = PClassInfo_::ClassCardinality_::kManyInstances as int32;
                copy_cstring("Audio Module Class", &mut info.category);
                copy_wstring(self.config.name, &mut info.name);
                info.classFlags = 0;
                // Derive subcategories from shared Config
                let subcategories = self.config.vst3_subcategories();
                copy_cstring(&subcategories, &mut info.subCategories);
                copy_wstring(self.config.vendor, &mut info.vendor);
                copy_wstring(self.config.version, &mut info.version);
                copy_wstring("VST 3.8.0", &mut info.sdkVersion);
                kResultOk
            }
            1 if self.has_controller() => {
                // SAFETY: Validated info is non-null above. Host guarantees validity.
                let info = unsafe { &mut *info };
                info.cid = self.controller_uid.unwrap();
                info.cardinality = PClassInfo_::ClassCardinality_::kManyInstances as int32;
                copy_cstring("Component Controller Class", &mut info.category);
                copy_wstring(self.config.name, &mut info.name);
                info.classFlags = 1; // kComponentControllerClass
                copy_cstring("", &mut info.subCategories);
                copy_wstring(self.config.vendor, &mut info.vendor);
                copy_wstring(self.config.version, &mut info.version);
                copy_wstring("VST 3.8.0", &mut info.sdkVersion);
                kResultOk
            }
            _ => kInvalidArgument,
        }
    }

    unsafe fn setHostContext(&self, _context: *mut FUnknown) -> tresult {
        kResultOk
    }
}