Skip to content

AudioLab Plugin Architecture

Plugin registration, factory pattern, and VST3 wrapper infrastructure for AudioLab processors.

Overview

This module provides the infrastructure to: - Register plugins dynamically using factory pattern - Create processors by ID at runtime - Expose metadata without instantiation - Wrap AudioLab processors as VST3 plugins (skeleton for Phase 2)

Components

1. PluginDescriptor (plugin_descriptor.hpp)

Static metadata about a plugin, including: - Identification (ID, name, vendor, version, UID) - Capabilities (category, channels, MIDI, sidechain) - Parameters (count, IDs for preset compatibility) - Resources (factory function, icon, manual URL)

Features: - ✅ Validation (checks completeness) - ✅ JSON export (for plugin scanning) - ✅ Deterministic UID generation (hash from string ID) - ⏸️ JSON import (stub in Phase 1, full in Phase 2)

Usage:

PluginDescriptor desc{
    .id = "audiolab.my_plugin",
    .name = "My Plugin",
    .vendor = "AudioLab",
    .version = "1.0.0",
    .category = PluginCategory::Effect,
    .num_inputs = 2,
    .num_outputs = 2,
    .factory_function = &create_my_plugin
};

2. ProcessorFactory (processor_factory.hpp)

Registry for creating AudioProcessors dynamically.

Design Pattern: Meyers Singleton + Function Registry - Thread-safe initialization (C++11 magic statics) - O(1) lookup by plugin ID - Lock-free reads after static initialization

API:

// Register (called at static init time)
ProcessorFactory::register_processor(descriptor);

// Create instance
auto processor = ProcessorFactory::create("audiolab.my_plugin");

// Query
auto all = ProcessorFactory::all_descriptors();
const auto* desc = ProcessorFactory::find_descriptor("audiolab.my_plugin");
bool exists = ProcessorFactory::is_registered("audiolab.my_plugin");

Registration Macro:

// In plugin implementation file (.cpp)
PluginDescriptor my_plugin_desc = { /* ... */ };
AUDIOLAB_REGISTER_PLUGIN(my_plugin_desc);

3. VST3 Adapter (vst3/vst3_adapter.hpp)

⚠️ Phase 1: Skeleton Only

Documents the VST3 integration requirements without SDK dependency. All methods are stubbed with clear TODOs for Phase 2 implementation.

Design Pattern: Adapter - Wraps AudioLab AudioProcessor - Translates VST3 API ↔ AudioLab API - Maps parameters, buses, state

Phase 1 Status: - ✅ Interface documented - ✅ All VST3 methods identified - ✅ Clear TODOs for implementation - ❌ No VST3 SDK dependency yet - ❌ Methods return kNotImplemented

Phase 2 Requirements: - Link VST3 SDK (vcpkg: vst3sdk) - Inherit from Steinberg::Vst::IComponent - Inherit from Steinberg::Vst::IAudioProcessor - Implement all stubbed methods - Pass VST3 validator

4. VST3 Types (vst3/vst3_types.hpp)

Placeholder type definitions without VST3 SDK. These will be replaced by actual Steinberg types in Phase 2.

Defines: - Basic types (UID, ParamID, ParamValue, Result) - Bus types (Direction, Type, MediaType) - Processing types (ProcessSetup, ProcessData, AudioBusBuffers) - Interface documentation (IComponent, IAudioProcessor methods)

Examples

Complete Registration Example

See examples/register_plugin.cpp:

// 1. Define processor
class SimpleGain : public AudioProcessor {
    // ... implementation ...
};

// 2. Factory function
std::unique_ptr<AudioProcessor> create_simple_gain() {
    return std::make_unique<SimpleGain>();
}

// 3. Descriptor
PluginDescriptor simple_gain_desc{
    .id = "audiolab.simple_gain",
    .name = "Simple Gain",
    .factory_function = &create_simple_gain,
    // ... other fields ...
};

// 4. Register
AUDIOLAB_REGISTER_PLUGIN(simple_gain_desc);

// 5. In main(): create dynamically
int main() {
    auto processor = ProcessorFactory::create("audiolab.simple_gain");
    // Use processor...
}

Run example:

cmake --build build --target register_plugin_example
.\build\04_CORE\04_09_plugin_lifecycle\examples\Release\register_plugin_example.exe

Tests

Factory Tests (tests/test_factory.cpp)

  • ✅ Register and create
  • ✅ Query descriptors
  • ✅ Unknown plugin returns nullptr
  • ✅ Duplicate registration (last wins)
  • ✅ Invalid descriptor not registered

Descriptor Tests (tests/test_descriptor.cpp)

  • ✅ Validation (valid/invalid cases)
  • ✅ JSON serialization roundtrip
  • ✅ UID generation is deterministic
  • ✅ UID uniqueness for different IDs

Run tests:

ctest --test-dir build -R test_factory
ctest --test-dir build -R test_descriptor

Future Roadmap

Phase 2: VST3 Implementation (2-3 weeks)

Goals: 1. Integrate VST3 SDK (vcpkg) 2. Implement VST3Adapter fully 3. Parameter automation mapping 4. State serialization (presets) 5. MIDI handling 6. Pass VST3 validator

Tasks: - Replace placeholder types with Steinberg types - Implement IComponent interface - Implement IAudioProcessor interface - Convert ProcessData ↔ AudioBuffer - Handle parameter queues (IParamValueQueue) - Serialize/deserialize state (IBStream) - Build as VST3 module (.vst3 bundle)

Phase 3: Multi-Format Support (3-4 weeks per format)

Formats: 1. Audio Unit (AU) for macOS 2. AAX for Pro Tools 3. CLAP (future open standard)

Architecture: - Single AudioProcessor implementation - Multiple format wrappers (VST3, AU, AAX) - Format-agnostic descriptor - Cross-format preset conversion

Phase 4: Plugin Scanning

Features: - Multi-threaded plugin scanner - JSON cache for fast loading - Blacklist for crashed plugins - Sandboxed validation

Architecture Decisions

Why Factory Pattern?

  • Decouples creation from usage: Host doesn't need to know processor types
  • Runtime registration: Plugins self-register at static init
  • Type-safe: Returns unique_ptr<AudioProcessor>
  • Extensible: New plugins don't modify host code

Why Meyers Singleton?

  • Thread-safe: C++11 guarantees initialization
  • No order issues: Static initialization order is safe
  • Lazy initialization: Registry created on first access
  • Lifetime management: Lives until program end

Why Skeleton Before SDK?

  1. Validates architecture before SDK complexity
  2. Documents requirements clearly with TODOs
  3. Allows testing factory pattern independently
  4. Faster iteration without SDK build times
  5. Clear scope for Phase 2 work

Dependencies

Phase 1 (Current): - AudioProcessor (04_10_audio_processor) - Standard library only - No external dependencies

Phase 2 (Future): - VST3 SDK (vcpkg: vst3sdk) - Parameter system (04_08_parameter_system)

Build Configuration

Header-Only Library:

add_library(audiolab_plugin_architecture INTERFACE)
target_link_libraries(my_target PRIVATE audiolab_plugin_architecture)

Example Executable:

add_executable(register_plugin_example examples/register_plugin.cpp)
target_link_libraries(register_plugin_example PRIVATE audiolab_plugin_architecture)

Reference Implementations

Studied for architecture patterns (no code copied):

  1. JUCE Plugin Client
  2. modules/juce_audio_plugin_client/
  3. Single processor, multiple wrappers (VST3/AU/AAX)

  4. iPlug2 Wrapper

  5. IPlug/VST3/IPlugVST3.cpp
  6. Parameter mapping strategies

  7. VST3 SDK Examples

  8. public.sdk/samples/vst/
  9. Process method, bus handling

License

AudioLab Project - See root LICENSE file

Contact

For questions about plugin architecture: - See 04_CORE README - Phase 2 VST3 integration: TBD