ara2-bridge 0.1.7

Safe Rust bindings for the Celemony ARA2 SDK
Documentation
# ara2-bridge
[![CI](https://github.com/entrepeneur4lyf/ara2-bridge/actions/workflows/ci.yml/badge.svg)](https://github.com/entrepeneur4lyf/ara2-bridge/actions/workflows/ci.yml)
[![crates.io](https://img.shields.io/crates/v/ara2-bridge.svg)](https://crates.io/crates/ara2-bridge)
[![docs.rs](https://docs.rs/ara2-bridge/badge.svg)](https://docs.rs/ara2-bridge)
[![License](https://img.shields.io/badge/license-MIT%2FApache--2.0-blue.svg)](LICENSE)


Safe Rust bindings for the [Celemony ARA2 SDK](https://github.com/Celemony/ARA_API).

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`]ara2-bridge-sys/ | Raw FFI bindings generated by `bindgen` from `ARAInterface.h`. 2,557 lines, 210 types, 55-field vtable structs. |
| [`ara2-bridge`]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

```toml
[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

```rust
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

```rust
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:

```rust
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."

```text
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

```bash
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](https://github.com/Celemony/ARA_API) separately.