Skip to main content

ara2_bridge/
lib.rs

1//! Safe Rust bindings for the [Celemony ARA2 SDK](https://github.com/Celemony/ARA_API) **v2.3.0**.
2//!
3//! ARA2 (Audio Random Access) lets audio plugins access DAW audio regions
4//! directly — not just streaming audio at the insert point. This crate
5//! wraps the C API as Rust traits with vtable builders.
6//!
7//! ## Supported interfaces
8//!
9//! | Side | Trait | Status |
10//! |------|-------|--------|
11//! | Plugin | `DocumentController` (25 methods) | Full vtable |
12//! | Host | `PlaybackRegionHost` | Trait defined |
13//! | Host | `ModelUpdateController` | Trait defined |
14//! | Host | `ArchiveReaderHost` / `ArchiveWriterHost` | Trait defined |
15//!
16//! Host-side vtables are instantiated by the ARA2 SDK/library, not by this crate.
17//! This crate provides the Rust trait definitions that host implementations
18//! (like GAW) implement. The C↔Rust bridge lives in the host application.
19//! directly — not just streaming audio at the insert point. This crate
20//! wraps the C API as Rust traits with vtable builders.
21//!
22//! ## How ARA2 Works
23//!
24//! The DAW calls into your plugin through C structs of function pointers
25//! (vtables). Each vtable has a `structSize` field that tells the host
26//! which functions are present; null entries mean "not supported."
27//!
28//! Built against ARA2 SDK v2.3.0. This crate implements 25 of 55 vtable
29//! entries for the plugin side. Host-side vtables are constructed by the
30//! ARA2 SDK's C++ library via VST3/CLAP/AU factory queries.
31//!
32//! ## Quick Start
33//!
34//! ```rust,ignore
35//! use ara2_bridge::*;
36//! use ara2_bridge_sys::*;
37//!
38//! struct MyPlugin;
39//!
40//! impl DocumentController for MyPlugin {
41//!     // ... implement all 25 methods
42//! }
43//!
44//! // Build the C-compatible instance for the DAW
45//! let controller = Box::new(MyPlugin);
46//! let instance = build_document_controller_instance(controller);
47//! ```
48
49use ara2_bridge_sys::*;
50
51// Use auto-generated host vtable builders from bridge-sys
52pub use ara2_bridge_sys::build_araarchivingcontroller_vtable;
53pub use ara2_bridge_sys::build_araaudioaccesscontroller_vtable;
54pub use ara2_bridge_sys::build_aracontentaccesscontroller_vtable;
55pub use ara2_bridge_sys::build_aramodelupdatecontroller_vtable;
56pub use ara2_bridge_sys::build_araplaybackcontroller_vtable;
57
58// ────────────────────────────────────────────────────────────────────
59// DocumentController — the main plugin interface
60// ────────────────────────────────────────────────────────────────────
61
62/// Plugin-side document controller.
63///
64/// Created once per DAW document. Manages audio sources, musical contexts,
65/// region sequences, playback regions, persistence, and undo history.
66///
67/// The DAW calls these methods through the function pointer vtable
68/// built by [`build_document_controller_instance`].
69///
70/// ## Lifecycle
71///
72/// 1. DAW creates a document → calls your `ARAFactory` callback
73/// 2. You return an `ARADocumentControllerInstance`
74/// 3. DAW calls `begin_editing()` → you set up internal state
75/// 4. DAW calls `create_audio_source()`, `create_musical_context()`, etc.
76/// 5. DAW calls `request_audio_source_content_analysis()` → you analyze
77/// 6. DAW calls `end_editing()` → you commit changes
78/// 7. DAW calls `store_objects_to_archive()` → you serialize to project
79/// 8. DAW calls `destroy()` → you free resources
80pub trait DocumentController {
81    /// Destroy the controller and free all resources.
82    ///
83    /// Called by the DAW when the document is closed. After this call,
84    /// no further callbacks will be received.
85    fn destroy(&mut self);
86
87    /// Return a pointer to the static [`ARAFactory`] that created this controller.
88    ///
89    /// The returned pointer must remain valid for the lifetime of the controller.
90    /// This is how the DAW discovers the plugin's capabilities (name, version,
91    /// supported content types, archive IDs).
92    fn get_factory(&self) -> *const ARAFactory;
93
94    /// Begin an editing session.
95    ///
96    /// All model changes between `begin_editing` and [`end_editing`] are
97    /// treated as a single undoable operation. The plugin may defer
98    /// expensive updates until `end_editing` is called.
99    ///
100    /// [`end_editing`]: DocumentController::end_editing
101    fn begin_editing(&mut self);
102
103    /// End an editing session.
104    ///
105    /// The plugin should now perform any deferred updates and notify the
106    /// host of changes via `ARAModelUpdateControllerInterface`.
107    fn end_editing(&mut self);
108
109    /// Send all pending model update notifications to the host.
110    ///
111    /// Called periodically by the DAW when not editing. The plugin may
112    /// call back into the host using `ARAModelUpdateControllerInterface`
113    /// during this call.
114    fn notify_model_updates(&mut self);
115
116    /// Handle a change to the document's properties (name, etc.).
117    fn update_document_properties(&mut self, properties: &ARADocumentProperties);
118
119    /// Create a new audio source associated with this document.
120    ///
121    /// Audio sources represent the raw audio data that playback regions
122    /// reference. Sample data access is initially disabled — call
123    /// `enable_audio_source_samples_access` to grant access.
124    ///
125    /// Returns an opaque reference that identifies the source in future
126    /// callbacks.
127    fn create_audio_source(
128        &mut self,
129        host_ref: ARAAudioSourceHostRef,
130        properties: &ARAAudioSourceProperties,
131    ) -> ARAAudioSourceRef;
132
133    /// Update properties of an existing audio source.
134    ///
135    /// Called when sample rate, channel count, or name changes.
136    /// All properties are provided; the plugin determines which changed.
137    fn update_audio_source_properties(
138        &mut self,
139        source: ARAAudioSourceRef,
140        properties: &ARAAudioSourceProperties,
141    );
142
143    /// Called when audio sample data or content information for a source changes.
144    ///
145    /// `range` is `None` if the entire source is affected. The plugin should
146    /// invalidate any cached analysis for the affected range.
147    fn update_audio_source_content(
148        &mut self,
149        source: ARAAudioSourceRef,
150        range: Option<&ARAContentTimeRange>,
151        flags: ARAContentUpdateFlags,
152    );
153
154    /// Grant or revoke access to audio sample data for a source.
155    ///
156    /// When `enable` is non-zero, the plugin may read audio samples
157    /// through the host's audio access controller. When zero, sample
158    /// access is revoked and any cached samples should be freed.
159    fn enable_audio_source_samples_access(&mut self, source: ARAAudioSourceRef, enable: ARABool);
160
161    /// Manage undo history state for an audio source.
162    ///
163    /// `deactivate` is non-zero when the host is about to purge undo
164    /// history that references this source.
165    fn deactivate_audio_source_for_undo_history(
166        &mut self,
167        source: ARAAudioSourceRef,
168        deactivate: ARABool,
169    );
170
171    /// Destroy an audio source and free its resources.
172    fn destroy_audio_source(&mut self, source: ARAAudioSourceRef);
173
174    /// Host requests analysis of specific content types for an audio source.
175    ///
176    /// This is where you plug in your analysis engine. The content types
177    /// are a subset of the plugin's `analyzeableContentTypes` from the
178    /// [`ARAFactory`]. When analysis completes, notify the host via
179    /// `ARAModelUpdateControllerInterface`.
180    ///
181    /// `count` is the number of entries in `content_types`.
182    fn request_audio_source_content_analysis(
183        &mut self,
184        source: ARAAudioSourceRef,
185        count: ARASize,
186        content_types: *const ARAContentType,
187    ) -> ARABool;
188
189    /// Check whether analysis data is available for a given content type.
190    ///
191    /// Returns non-zero if the plugin has analysis results ready.
192    fn is_audio_source_content_available(
193        &self,
194        source: ARAAudioSourceRef,
195        content_type: ARAContentType,
196    ) -> ARABool;
197
198    /// Create a musical context (tempo, time signature, key).
199    fn create_musical_context(
200        &mut self,
201        host_ref: ARAMusicalContextHostRef,
202        properties: &ARAMusicalContextProperties,
203    ) -> ARAMusicalContextRef;
204
205    /// Update properties of an existing musical context.
206    fn update_musical_context_properties(
207        &mut self,
208        ctx: ARAMusicalContextRef,
209        properties: &ARAMusicalContextProperties,
210    );
211
212    /// Called when musical context content changes.
213    fn update_musical_context_content(
214        &mut self,
215        ctx: ARAMusicalContextRef,
216        range: Option<&ARAContentTimeRange>,
217        flags: ARAContentUpdateFlags,
218    );
219
220    /// Destroy a musical context.
221    fn destroy_musical_context(&mut self, ctx: ARAMusicalContextRef);
222
223    /// Create a region sequence (time-ordered regions on a track).
224    fn create_region_sequence(
225        &mut self,
226        host_ref: ARARegionSequenceHostRef,
227        properties: &ARARegionSequenceProperties,
228    ) -> ARARegionSequenceRef;
229
230    /// Update properties of an existing region sequence.
231    fn update_region_sequence_properties(
232        &mut self,
233        seq: ARARegionSequenceRef,
234        properties: &ARARegionSequenceProperties,
235    );
236
237    /// Destroy a region sequence.
238    fn destroy_region_sequence(&mut self, seq: ARARegionSequenceRef);
239
240    /// Create a playback region linked to an audio modification.
241    ///
242    /// Playback regions define where and how audio modifications appear
243    /// in the DAW timeline. Each playback region references an audio
244    /// modification, which in turn references an audio source.
245    fn create_playback_region(
246        &mut self,
247        host_ref: ARAPlaybackRegionHostRef,
248        audio_modification_ref: ARAAudioModificationRef,
249        properties: &ARAPlaybackRegionProperties,
250    ) -> ARAPlaybackRegionRef;
251
252    /// Update properties of an existing playback region.
253    fn update_playback_region_properties(
254        &mut self,
255        region: ARAPlaybackRegionRef,
256        properties: &ARAPlaybackRegionProperties,
257    );
258
259    /// Destroy a playback region.
260    fn destroy_playback_region(&mut self, region: ARAPlaybackRegionRef);
261
262    /// Serialize document state for DAW project save.
263    ///
264    /// Write analysis data through the provided archive writer.
265    /// `filter` is `None` if all objects should be stored, or a
266    /// filter specifying which objects to include.
267    ///
268    /// Returns non-zero on success.
269    fn store_objects_to_archive(
270        &mut self,
271        archive_writer_host_ref: ARAArchiveWriterHostRef,
272        filter: *const ARAStoreObjectsFilter,
273    ) -> ARABool;
274
275    /// Deserialize document state from DAW project load.
276    ///
277    /// Read analysis data through the provided archive reader.
278    /// `filter` is `None` if all objects should be restored, or a
279    /// filter specifying which objects to include.
280    ///
281    /// Returns non-zero on success.
282    fn restore_objects_from_archive(
283        &mut self,
284        archive_reader_host_ref: ARAArchiveReaderHostRef,
285        filter: *const ARARestoreObjectsFilter,
286    ) -> ARABool;
287}
288
289// ────────────────────────────────────────────────────────────────────
290// Vtable + Instance Builder
291// ────────────────────────────────────────────────────────────────────
292
293struct ControllerState {
294    controller: Box<dyn DocumentController>,
295}
296
297/// Build a C-compatible [`ARADocumentControllerInstance`] from a trait object.
298///
299/// Returns a heap-allocated instance that the DAW will destroy by calling
300/// the `destroyDocumentController` vtable entry. The returned pointer
301/// should be returned from your `ARAFactory::createDocumentControllerWithDocument`
302/// callback.
303///
304/// ## Safety
305///
306/// The returned pointer must eventually be freed by the DAW calling
307/// `destroyDocumentController`. If the DAW never calls it, the memory
308/// will leak.
309///
310/// ## Example
311///
312/// ```rust,ignore
313/// use ara2_bridge::*;
314/// use ara2_bridge_sys::*;
315///
316/// unsafe extern "C" fn factory_callback(
317///     _host: *const ARADocumentControllerHostInstance,
318///     _props: *const ARADocumentProperties,
319/// ) -> *const ARADocumentControllerInstance {
320///     let controller = Box::new(MyPlugin);
321///     build_document_controller_instance(controller)
322/// }
323/// ```
324pub fn build_document_controller_instance(
325    controller: Box<dyn DocumentController>,
326) -> *const ARADocumentControllerInstance {
327    let state = Box::into_raw(Box::new(ControllerState { controller }));
328    let vtable = Box::into_raw(Box::new(build_vtable()));
329    let instance = Box::new(ARADocumentControllerInstance {
330        structSize: std::mem::size_of::<ARADocumentControllerInstance>() as ARASize,
331        documentControllerRef: state as ARADocumentControllerRef,
332        documentControllerInterface: vtable,
333    });
334    Box::into_raw(instance)
335}
336
337fn build_vtable() -> ARADocumentControllerInterface {
338    let mut v: ARADocumentControllerInterface = unsafe { std::mem::zeroed() };
339    v.structSize = std::mem::size_of::<ARADocumentControllerInterface>() as ARASize;
340    v.destroyDocumentController = Some(destroy_dc);
341    v.getFactory = Some(get_factory_cb);
342    v.beginEditing = Some(begin_editing_cb);
343    v.endEditing = Some(end_editing_cb);
344    v.notifyModelUpdates = Some(notify_model_updates_cb);
345    v.updateDocumentProperties = Some(update_doc_props_cb);
346    v.createAudioSource = Some(create_audio_source_cb);
347    v.updateAudioSourceProperties = Some(update_audio_source_props_cb);
348    v.updateAudioSourceContent = Some(update_audio_source_content_cb);
349    v.enableAudioSourceSamplesAccess = Some(enable_audio_access_cb);
350    v.deactivateAudioSourceForUndoHistory = Some(deactivate_audio_undo_cb);
351    v.destroyAudioSource = Some(destroy_audio_source_cb);
352    v.requestAudioSourceContentAnalysis = Some(request_audio_analysis_cb);
353    v.isAudioSourceContentAvailable = Some(is_audio_content_avail_cb);
354    v.createMusicalContext = Some(create_musical_ctx_cb);
355    v.updateMusicalContextProperties = Some(update_musical_ctx_props_cb);
356    v.updateMusicalContextContent = Some(update_musical_ctx_content_cb);
357    v.destroyMusicalContext = Some(destroy_musical_ctx_cb);
358    v.createRegionSequence = Some(create_region_seq_cb);
359    v.updateRegionSequenceProperties = Some(update_region_seq_props_cb);
360    v.destroyRegionSequence = Some(destroy_region_seq_cb);
361    v.createPlaybackRegion = Some(create_playback_region_cb);
362    v.updatePlaybackRegionProperties = Some(update_playback_region_props_cb);
363    v.destroyPlaybackRegion = Some(destroy_playback_region_cb);
364    v.storeObjectsToArchive = Some(store_objects_cb);
365    v.restoreObjectsFromArchive = Some(restore_objects_cb);
366    v
367}
368
369unsafe fn state<'a>(r: ARADocumentControllerRef) -> &'a mut ControllerState {
370    &mut *(r as *mut ControllerState)
371}
372
373unsafe extern "C" fn destroy_dc(r: ARADocumentControllerRef) {
374    drop(Box::from_raw(r as *mut ControllerState));
375}
376
377unsafe extern "C" fn get_factory_cb(r: ARADocumentControllerRef) -> *const ARAFactory {
378    state(r).controller.get_factory()
379}
380
381unsafe extern "C" fn begin_editing_cb(r: ARADocumentControllerRef) {
382    state(r).controller.begin_editing();
383}
384
385unsafe extern "C" fn end_editing_cb(r: ARADocumentControllerRef) {
386    state(r).controller.end_editing();
387}
388
389unsafe extern "C" fn notify_model_updates_cb(r: ARADocumentControllerRef) {
390    state(r).controller.notify_model_updates();
391}
392
393unsafe extern "C" fn update_doc_props_cb(
394    r: ARADocumentControllerRef,
395    p: *const ARADocumentProperties,
396) {
397    state(r).controller.update_document_properties(&*p);
398}
399
400unsafe extern "C" fn create_audio_source_cb(
401    r: ARADocumentControllerRef,
402    h: ARAAudioSourceHostRef,
403    p: *const ARAAudioSourceProperties,
404) -> ARAAudioSourceRef {
405    state(r).controller.create_audio_source(h, &*p)
406}
407
408unsafe extern "C" fn update_audio_source_props_cb(
409    r: ARADocumentControllerRef,
410    s: ARAAudioSourceRef,
411    p: *const ARAAudioSourceProperties,
412) {
413    state(r).controller.update_audio_source_properties(s, &*p);
414}
415
416unsafe extern "C" fn update_audio_source_content_cb(
417    r: ARADocumentControllerRef,
418    s: ARAAudioSourceRef,
419    range: *const ARAContentTimeRange,
420    flags: ARAContentUpdateFlags,
421) {
422    let opt = if range.is_null() { None } else { Some(&*range) };
423    state(r)
424        .controller
425        .update_audio_source_content(s, opt, flags);
426}
427
428unsafe extern "C" fn enable_audio_access_cb(
429    r: ARADocumentControllerRef,
430    s: ARAAudioSourceRef,
431    enable: ARABool,
432) {
433    state(r)
434        .controller
435        .enable_audio_source_samples_access(s, enable);
436}
437
438unsafe extern "C" fn deactivate_audio_undo_cb(
439    r: ARADocumentControllerRef,
440    s: ARAAudioSourceRef,
441    deactivate: ARABool,
442) {
443    state(r)
444        .controller
445        .deactivate_audio_source_for_undo_history(s, deactivate);
446}
447
448unsafe extern "C" fn destroy_audio_source_cb(r: ARADocumentControllerRef, s: ARAAudioSourceRef) {
449    state(r).controller.destroy_audio_source(s);
450}
451
452unsafe extern "C" fn request_audio_analysis_cb(
453    r: ARADocumentControllerRef,
454    s: ARAAudioSourceRef,
455    count: ARASize,
456    types: *const ARAContentType,
457) {
458    state(r)
459        .controller
460        .request_audio_source_content_analysis(s, count, types);
461}
462
463unsafe extern "C" fn is_audio_content_avail_cb(
464    r: ARADocumentControllerRef,
465    s: ARAAudioSourceRef,
466    ct: ARAContentType,
467) -> ARABool {
468    state(r).controller.is_audio_source_content_available(s, ct)
469}
470
471unsafe extern "C" fn create_musical_ctx_cb(
472    r: ARADocumentControllerRef,
473    h: ARAMusicalContextHostRef,
474    p: *const ARAMusicalContextProperties,
475) -> ARAMusicalContextRef {
476    state(r).controller.create_musical_context(h, &*p)
477}
478
479unsafe extern "C" fn update_musical_ctx_props_cb(
480    r: ARADocumentControllerRef,
481    c: ARAMusicalContextRef,
482    p: *const ARAMusicalContextProperties,
483) {
484    state(r)
485        .controller
486        .update_musical_context_properties(c, &*p);
487}
488
489unsafe extern "C" fn update_musical_ctx_content_cb(
490    r: ARADocumentControllerRef,
491    c: ARAMusicalContextRef,
492    range: *const ARAContentTimeRange,
493    flags: ARAContentUpdateFlags,
494) {
495    let opt = if range.is_null() { None } else { Some(&*range) };
496    state(r)
497        .controller
498        .update_musical_context_content(c, opt, flags);
499}
500
501unsafe extern "C" fn destroy_musical_ctx_cb(r: ARADocumentControllerRef, c: ARAMusicalContextRef) {
502    state(r).controller.destroy_musical_context(c);
503}
504
505unsafe extern "C" fn create_region_seq_cb(
506    r: ARADocumentControllerRef,
507    h: ARARegionSequenceHostRef,
508    p: *const ARARegionSequenceProperties,
509) -> ARARegionSequenceRef {
510    state(r).controller.create_region_sequence(h, &*p)
511}
512
513unsafe extern "C" fn update_region_seq_props_cb(
514    r: ARADocumentControllerRef,
515    s: ARARegionSequenceRef,
516    p: *const ARARegionSequenceProperties,
517) {
518    state(r)
519        .controller
520        .update_region_sequence_properties(s, &*p);
521}
522
523unsafe extern "C" fn destroy_region_seq_cb(r: ARADocumentControllerRef, s: ARARegionSequenceRef) {
524    state(r).controller.destroy_region_sequence(s);
525}
526
527unsafe extern "C" fn create_playback_region_cb(
528    r: ARADocumentControllerRef,
529    m: ARAAudioModificationRef,
530    h: ARAPlaybackRegionHostRef,
531    p: *const ARAPlaybackRegionProperties,
532) -> ARAPlaybackRegionRef {
533    state(r).controller.create_playback_region(h, m, &*p)
534}
535
536unsafe extern "C" fn update_playback_region_props_cb(
537    r: ARADocumentControllerRef,
538    reg: ARAPlaybackRegionRef,
539    p: *const ARAPlaybackRegionProperties,
540) {
541    state(r)
542        .controller
543        .update_playback_region_properties(reg, &*p);
544}
545
546unsafe extern "C" fn destroy_playback_region_cb(
547    r: ARADocumentControllerRef,
548    reg: ARAPlaybackRegionRef,
549) {
550    state(r).controller.destroy_playback_region(reg);
551}
552
553unsafe extern "C" fn store_objects_cb(
554    r: ARADocumentControllerRef,
555    wr: ARAArchiveWriterHostRef,
556    f: *const ARAStoreObjectsFilter,
557) -> ARABool {
558    state(r).controller.store_objects_to_archive(wr, f)
559}
560
561unsafe extern "C" fn restore_objects_cb(
562    r: ARADocumentControllerRef,
563    rr: ARAArchiveReaderHostRef,
564    f: *const ARARestoreObjectsFilter,
565) -> ARABool {
566    state(r).controller.restore_objects_from_archive(rr, f)
567}
568
569// ────────────────────────────────────────────────────────────────────
570// Host-side traits — what the DAW implements for ARA2 plugins
571// ────────────────────────────────────────────────────────────────────
572
573/// Host-side playback region interface.
574///
575/// The plugin calls these methods to notify the DAW about content
576/// analysis results and rendering progress on this playback region.
577pub trait PlaybackRegionHost {
578    /// Plugin has completed content analysis for this region.
579    fn notify_content_analysis_completed(
580        &mut self,
581        region: ARAPlaybackRegionRef,
582        content: *mut std::ffi::c_void,
583    );
584
585    /// Plugin requests that the host start rendering this region.
586    fn request_playback(&mut self, region: ARAPlaybackRegionRef);
587}
588
589/// Host-side model update interface.
590///
591/// The plugin calls these methods to tell the DAW that model objects
592/// (audio sources, musical contexts, etc.) have changed.
593pub trait ModelUpdateController {
594    /// Notify the host that an audio source's content has changed.
595    fn notify_audio_source_content_changed(
596        &mut self,
597        source: ARAAudioSourceRef,
598        content: *const std::ffi::c_void,
599    );
600
601    /// Notify the host that new model objects are available.
602    fn notify_model_update(&mut self);
603
604    /// Notify the host that the plugin session has been restored from archive.
605    fn notify_restored_from_archive(&mut self);
606}
607
608/// Host-side archive reader interface.
609///
610/// The plugin calls these methods during `restore_objects_from_archive()`
611/// to read back serialized state from the DAW project file.
612pub trait ArchiveReaderHost {
613    /// Read bytes from the archive into `buffer`.
614    /// Returns the number of bytes actually read.
615    fn read(&mut self, buffer: &mut [u8]) -> usize;
616
617    /// Return the number of bytes available to read.
618    fn size(&self) -> usize;
619}
620
621/// Host-side archive writer interface.
622///
623/// The plugin calls these methods during `store_objects_to_archive()`
624/// to save plugin state into the DAW project file.
625pub trait ArchiveWriterHost {
626    /// Write bytes to the archive.
627    fn write(&mut self, data: &[u8]);
628}
629
630// ────────────────────────────────────────────────────────────────────
631// Tests
632// ────────────────────────────────────────────────────────────────────
633
634#[cfg(test)]
635mod tests {
636    use super::*;
637
638    /// Minimal plugin implementation for testing
639    struct TestPlugin {
640        destroyed: bool,
641        editing: bool,
642    }
643
644    impl DocumentController for TestPlugin {
645        fn destroy(&mut self) {
646            self.destroyed = true;
647        }
648        fn get_factory(&self) -> *const ARAFactory {
649            std::ptr::null()
650        }
651        fn begin_editing(&mut self) {
652            self.editing = true;
653        }
654        fn end_editing(&mut self) {
655            self.editing = false;
656        }
657        fn notify_model_updates(&mut self) {}
658        fn update_document_properties(&mut self, _: &ARADocumentProperties) {}
659        fn create_audio_source(
660            &mut self,
661            _: ARAAudioSourceHostRef,
662            _: &ARAAudioSourceProperties,
663        ) -> ARAAudioSourceRef {
664            std::ptr::null_mut()
665        }
666        fn update_audio_source_properties(
667            &mut self,
668            _: ARAAudioSourceRef,
669            _: &ARAAudioSourceProperties,
670        ) {
671        }
672        fn update_audio_source_content(
673            &mut self,
674            _: ARAAudioSourceRef,
675            _: Option<&ARAContentTimeRange>,
676            _: ARAContentUpdateFlags,
677        ) {
678        }
679        fn enable_audio_source_samples_access(&mut self, _: ARAAudioSourceRef, _: ARABool) {}
680        fn deactivate_audio_source_for_undo_history(&mut self, _: ARAAudioSourceRef, _: ARABool) {}
681        fn destroy_audio_source(&mut self, _: ARAAudioSourceRef) {}
682        fn request_audio_source_content_analysis(
683            &mut self,
684            _: ARAAudioSourceRef,
685            _: ARASize,
686            _: *const ARAContentType,
687        ) -> ARABool {
688            1
689        }
690        fn is_audio_source_content_available(
691            &self,
692            _: ARAAudioSourceRef,
693            _: ARAContentType,
694        ) -> ARABool {
695            1
696        }
697        fn create_musical_context(
698            &mut self,
699            _: ARAMusicalContextHostRef,
700            _: &ARAMusicalContextProperties,
701        ) -> ARAMusicalContextRef {
702            std::ptr::null_mut()
703        }
704        fn update_musical_context_properties(
705            &mut self,
706            _: ARAMusicalContextRef,
707            _: &ARAMusicalContextProperties,
708        ) {
709        }
710        fn update_musical_context_content(
711            &mut self,
712            _: ARAMusicalContextRef,
713            _: Option<&ARAContentTimeRange>,
714            _: ARAContentUpdateFlags,
715        ) {
716        }
717        fn destroy_musical_context(&mut self, _: ARAMusicalContextRef) {}
718        fn create_region_sequence(
719            &mut self,
720            _: ARARegionSequenceHostRef,
721            _: &ARARegionSequenceProperties,
722        ) -> ARARegionSequenceRef {
723            std::ptr::null_mut()
724        }
725        fn update_region_sequence_properties(
726            &mut self,
727            _: ARARegionSequenceRef,
728            _: &ARARegionSequenceProperties,
729        ) {
730        }
731        fn destroy_region_sequence(&mut self, _: ARARegionSequenceRef) {}
732        fn create_playback_region(
733            &mut self,
734            _: ARAPlaybackRegionHostRef,
735            _: ARAAudioModificationRef,
736            _: &ARAPlaybackRegionProperties,
737        ) -> ARAPlaybackRegionRef {
738            std::ptr::null_mut()
739        }
740        fn update_playback_region_properties(
741            &mut self,
742            _: ARAPlaybackRegionRef,
743            _: &ARAPlaybackRegionProperties,
744        ) {
745        }
746        fn destroy_playback_region(&mut self, _: ARAPlaybackRegionRef) {}
747        fn store_objects_to_archive(
748            &mut self,
749            _: ARAArchiveWriterHostRef,
750            _: *const ARAStoreObjectsFilter,
751        ) -> ARABool {
752            1
753        }
754        fn restore_objects_from_archive(
755            &mut self,
756            _: ARAArchiveReaderHostRef,
757            _: *const ARARestoreObjectsFilter,
758        ) -> ARABool {
759            1
760        }
761    }
762
763    #[test]
764    fn test_plugin_lifecycle() {
765        let mut plugin = TestPlugin {
766            destroyed: false,
767            editing: false,
768        };
769        assert!(!plugin.destroyed);
770        plugin.begin_editing();
771        assert!(plugin.editing);
772        plugin.end_editing();
773        assert!(!plugin.editing);
774        plugin.destroy();
775        assert!(plugin.destroyed);
776    }
777
778    #[test]
779    fn test_build_instance_creates_vtable() {
780        let plugin = Box::new(TestPlugin {
781            destroyed: false,
782            editing: false,
783        });
784        let instance = build_document_controller_instance(plugin);
785        // The instance has a valid vtable with the correct structSize
786        assert!(unsafe { (*(*instance).documentControllerInterface).structSize } > 0);
787    }
788}