ara2-bridge 0.1.7

Safe Rust bindings for the Celemony ARA2 SDK
Documentation

ara2-bridge

CI crates.io docs.rs License

Safe Rust bindings for the Celemony ARA2 SDK.

ARA2 (Audio Random Access) lets audio plugins access DAW audio regions directly — not just streaming audio at the insert point. This crate wraps the C API as Rust traits with vtable builders, so you can write ARA2 plugins in pure Rust.

Crates

Crate Purpose
ara2-bridge-sys Raw FFI bindings generated by bindgen from ARAInterface.h. 2,557 lines, 210 types, 55-field vtable structs.
ara2-bridge Safe Rust wrapper. DocumentController trait + vtable builder. Implements 25 of 55 vtable entries; null entries signal "not supported" per the ARA2 spec.

Installation

[dependencies]
ara2-bridge = "0.1.4"

This pulls in ara2-bridge-sys automatically. You do not need to depend on it directly unless you want raw FFI access.

Usage

1. Implement the DocumentController trait

use ara2_bridge::*;
use ara2_bridge_sys::*;

struct MyPlugin;

impl DocumentController for MyPlugin {
    fn destroy(&mut self) {
        // Clean up plugin resources
    }

    fn get_factory(&self) -> *const ARAFactory {
        // Return a pointer to your static ARAFactory struct
        std::ptr::null() // replace with your factory
    }

    fn begin_editing(&mut self) {
        // DAW started an editing session
    }

    fn end_editing(&mut self) {
        // DAW ended an editing session
    }

    fn notify_model_updates(&mut self) {
        // Send pending model updates to the host
    }

    fn update_document_properties(&mut self, properties: &ARADocumentProperties) {
        // Document name or other properties changed
    }

    fn create_audio_source(
        &mut self,
        host_ref: ARAAudioSourceHostRef,
        properties: &ARAAudioSourceProperties,
    ) -> ARAAudioSourceRef {
        // Create a new audio source. Return an opaque reference
        // that identifies it in future callbacks.
        std::ptr::null_mut()
    }

    fn update_audio_source_properties(
        &mut self,
        source: ARAAudioSourceRef,
        properties: &ARAAudioSourceProperties,
    ) {
        // Audio source sample rate, channel count, or name changed
    }

    fn update_audio_source_content(
        &mut self,
        source: ARAAudioSourceRef,
        range: Option<&ARAContentTimeRange>,
        flags: ARAContentUpdateFlags,
    ) {
        // Audio data for the source was modified by the host
    }

    fn enable_audio_source_samples_access(
        &mut self,
        source: ARAAudioSourceRef,
        enable: ARABool,
    ) {
        // Host is granting or revoking sample data access
    }

    fn deactivate_audio_source_for_undo_history(
        &mut self,
        source: ARAAudioSourceRef,
        deactivate: ARABool,
    ) {
        // Undo state management for audio source
    }

    fn destroy_audio_source(&mut self, source: ARAAudioSourceRef) {
        // Remove an audio source and free its resources
    }

    fn request_audio_source_content_analysis(
        &mut self,
        source: ARAAudioSourceRef,
        count: ARASize,
        content_types: *const ARAContentType,
    ) {
        // Host requests analysis of specific content types.
        // This is where you'd call into mixlens-core or your
        // analysis engine and report results back to the host
        // via ARAModelUpdateControllerInterface.
    }

    fn is_audio_source_content_available(
        &self,
        source: ARAAudioSourceRef,
        content_type: ARAContentType,
    ) -> ARABool {
        // Does analysis data exist for this content type?
        0
    }

    fn create_musical_context(
        &mut self,
        host_ref: ARAMusicalContextHostRef,
        properties: &ARAMusicalContextProperties,
    ) -> ARAMusicalContextRef {
        // Create a musical context (tempo, time signature, key)
        std::ptr::null_mut()
    }

    fn update_musical_context_properties(
        &mut self,
        ctx: ARAMusicalContextRef,
        properties: &ARAMusicalContextProperties,
    ) {
        // Musical context properties changed
    }

    fn update_musical_context_content(
        &mut self,
        ctx: ARAMusicalContextRef,
        range: Option<&ARAContentTimeRange>,
        flags: ARAContentUpdateFlags,
    ) {
        // Musical context content updated
    }

    fn destroy_musical_context(&mut self, ctx: ARAMusicalContextRef) {
        // Remove a musical context
    }

    fn create_region_sequence(
        &mut self,
        host_ref: ARARegionSequenceHostRef,
        properties: &ARARegionSequenceProperties,
    ) -> ARARegionSequenceRef {
        // Create a region sequence
        std::ptr::null_mut()
    }

    fn update_region_sequence_properties(
        &mut self,
        seq: ARARegionSequenceRef,
        properties: &ARARegionSequenceProperties,
    ) {
        // Region sequence properties changed
    }

    fn destroy_region_sequence(&mut self, seq: ARARegionSequenceRef) {
        // Remove a region sequence
    }

    fn create_playback_region(
        &mut self,
        host_ref: ARAPlaybackRegionHostRef,
        audio_modification_ref: ARAAudioModificationRef,
        properties: &ARAPlaybackRegionProperties,
    ) -> ARAPlaybackRegionRef {
        // Create a playback region linked to an audio modification
        std::ptr::null_mut()
    }

    fn update_playback_region_properties(
        &mut self,
        region: ARAPlaybackRegionRef,
        properties: &ARAPlaybackRegionProperties,
    ) {
        // Playback region properties changed
    }

    fn destroy_playback_region(&mut self, region: ARAPlaybackRegionRef) {
        // Remove a playback region
    }

    fn store_objects_to_archive(
        &mut self,
        archive_writer_host_ref: ARAArchiveWriterHostRef,
        filter: *const ARAStoreObjectsFilter,
    ) -> ARABool {
        // Serialize document state for project save
        0
    }

    fn restore_objects_from_archive(
        &mut self,
        archive_reader_host_ref: ARAArchiveReaderHostRef,
        filter: *const ARARestoreObjectsFilter,
    ) -> ARABool {
        // Deserialize document state from project load
        0
    }
}

2. Build the vtable and instance

use ara2_bridge::build_document_controller_instance;

// Create your controller
let controller = Box::new(MyPlugin);

// Build the C-compatible instance
let instance = build_document_controller_instance(controller);

// `instance` is a `*const ARADocumentControllerInstance` —
// pass it to the DAW through the ARAFactory callback.

3. Wire into the ARAFactory

The ARAFactory struct is defined in ara2-bridge-sys. Fill in the static fields:

use ara2_bridge_sys::*;
use std::ffi::CStr;

static FACTORY: ARAFactory = ARAFactory {
    structSize: std::mem::size_of::<ARAFactory>() as ARASize,
    lowestSupportedApiGeneration: kARAAPIGeneration_2_0_Final,
    highestSupportedApiGeneration: kARAAPIGeneration_2_0_Final,
    factoryID: b"com.example.myplugin\0".as_ptr() as ARAPersistentID,
    initializeARAWithConfiguration: Some(initialize_ara),
    uninitializeARA: Some(uninitialize_ara),
    plugInName: b"My Plugin\0".as_ptr() as ARAUtf8String,
    manufacturerName: b"Example\0".as_ptr() as ARAUtf8String,
    informationURL: b"https://example.com\0".as_ptr() as ARAUtf8String,
    version: b"1.0.0\0".as_ptr() as ARAUtf8String,
    createDocumentControllerWithDocument: Some(create_dc_factory_callback),
    documentArchiveID: b"com.example.myplugin.archive\0".as_ptr() as ARAPersistentID,
    compatibleDocumentArchiveIDsCount: 0,
    compatibleDocumentArchiveIDs: std::ptr::null(),
    analyzeableContentTypesCount: 0,
    analyzeableContentTypes: std::ptr::null(),
    supportedPlaybackTransformationFlags: 0,
    supportsStoringAudioFileChunks: 0,
};

unsafe extern "C" fn create_dc_factory_callback(
    host_instance: *const ARADocumentControllerHostInstance,
    properties: *const ARADocumentProperties,
) -> *const ARADocumentControllerInstance {
    let controller = Box::new(MyPlugin);
    build_document_controller_instance(controller)
}

unsafe extern "C" fn initialize_ara(_config: *const ARAInterfaceConfiguration) {}
unsafe extern "C" fn uninitialize_ara() {}

Architecture

ARA2 uses C structs of function pointers (vtables). The DAW calls into these structs to communicate with the plugin. Each vtable has a structSize field that tells the host which functions are present; null entries are treated as "not supported."

DAW calls ARAFactory::createDocumentControllerWithDocument()
  │
  ▼
Your callback returns an ARADocumentControllerInstance
  │
  ├── documentControllerRef → opaque pointer to your Rust state
  └── documentControllerInterface → pointer to vtable struct

DAW calls vtable functions:
  beginEditing()    → trait method
  createAudioSource() → trait method
  requestAudioSourceContentAnalysis() → your analysis engine
  storeObjectsToArchive() → serialization
  destroyDocumentController() → cleanup

Implemented Vtable Entries (25 of 55)

  • destroyDocumentController, getFactory
  • beginEditing, endEditing, notifyModelUpdates
  • updateDocumentProperties
  • createAudioSource, updateAudioSourceProperties, updateAudioSourceContent
  • enableAudioSourceSamplesAccess, deactivateAudioSourceForUndoHistory
  • destroyAudioSource
  • requestAudioSourceContentAnalysis, isAudioSourceContentAvailable
  • createMusicalContext, updateMusicalContextProperties, updateMusicalContextContent, destroyMusicalContext
  • createRegionSequence, updateRegionSequenceProperties, destroyRegionSequence
  • createPlaybackRegion, updatePlaybackRegionProperties, destroyPlaybackRegion
  • storeObjectsToArchive, restoreObjectsFromArchive

Not Yet Implemented (30 of 55)

Audio modifications, content readers, content grades, processing algorithms, licensing, and audio file chunks. These return null pointers; the DAW treats them as unsupported features. They will be added in future versions based on community demand.

Companion API Integration

ARA2 plugins are distributed as VST3 extensions. The ARAVST3.h, ARACLAP.h, and ARAAudioUnit.h headers in ara2-bridge-sys provide the companion API types. You'll need:

  • VST3: Register the ARA2 factory in your VST3 plugin's IPlugView / IComponent implementation
  • CLAP: Register via clap_plugin_ara extension
  • AU: Register via AudioUnit properties

These integrations are handled by your plugin framework (nih-plug, vst3-rs, or manual FFI), not by this crate.

Building

cargo build

Requires clang (for bindgen). The ara2-bridge-sys crate generates bindings from ARAInterface.h at build time.

License

MIT OR Apache-2.0

The ARA2 SDK headers (ARAInterface.h, ARAVST3.h, etc.) are Copyright Celemony Software GmbH and distributed under the Celemony ARA2 License. Plugin developers must agree to Celemony's ARA2 terms separately.