# ara2-bridge
[](https://github.com/entrepeneur4lyf/ara2-bridge/actions/workflows/ci.yml)
[](https://crates.io/crates/ara2-bridge)
[](https://docs.rs/ara2-bridge)
[](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
| [`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.