Skip to main content

wavecraft_protocol/
dev_audio_ffi.rs

1//! C-ABI FFI contract for dev-mode audio processing.
2//!
3//! This module defines the shared interface between the `wavecraft_plugin!` macro
4//! (which generates FFI exports in the user's cdylib) and the CLI consumer
5//! (`wavecraft start`) that loads and calls them.
6//!
7//! # Design Principles
8//!
9//! - **`#[repr(C)]`** struct with `extern "C"` function pointers for ABI stability
10//! - **`*mut c_void`** instance pointers for type erasure across the dylib boundary
11//! - **Version field** for forward-compatible ABI evolution
12//! - All memory alloc/dealloc stays inside the dylib (no cross-allocator issues)
13
14use std::ffi::c_void;
15
16/// C-ABI stable vtable for dev-mode audio processing.
17///
18/// This struct is returned by the `wavecraft_dev_create_processor` FFI symbol
19/// exported from user plugins. It provides function pointers for creating,
20/// using, and destroying a processor instance across the dylib boundary.
21///
22/// # ABI Stability
23///
24/// This struct uses `#[repr(C)]` and only `extern "C"` function pointers,
25/// making it safe across separately compiled Rust binaries. All data passes
26/// through primitive types (`f32`, `u32`, `*mut c_void`, `*mut *mut f32`).
27///
28/// # Versioning
29///
30/// A `version` field allows the CLI to detect incompatible vtable changes
31/// and provide clear upgrade guidance instead of undefined behavior.
32///
33/// # Memory Ownership
34///
35/// ```text
36/// create()  → Box::into_raw(Box::new(Processor))     [dylib allocates]
37/// process() → &mut *(ptr as *mut Processor)           [dylib borrows]
38/// drop()    → Box::from_raw(ptr as *mut Processor)    [dylib deallocates]
39/// ```
40///
41/// The CLI never allocates or frees the processor memory; it only passes the
42/// opaque pointer back into vtable functions.
43#[repr(C)]
44#[derive(Debug, Clone, Copy)]
45pub struct DevProcessorVTable {
46    /// VTable version. Must equal [`DEV_PROCESSOR_VTABLE_VERSION`].
47    pub version: u32,
48
49    /// Create a new processor instance.
50    ///
51    /// Returns an opaque pointer to a heap-allocated processor.
52    /// The caller must eventually pass this pointer to `drop` to free it.
53    pub create: extern "C" fn() -> *mut c_void,
54
55    /// Process audio in deinterleaved (per-channel) format.
56    ///
57    /// # Arguments
58    /// - `instance`: Opaque processor pointer from `create`
59    /// - `channels`: Pointer to an array of `num_channels` mutable f32 pointers
60    /// - `num_channels`: Number of audio channels (typically 2)
61    /// - `num_samples`: Number of samples per channel
62    ///
63    /// # Safety
64    /// - `instance` must be a valid pointer from `create`
65    /// - `channels[0..num_channels]` must each point to `num_samples` valid f32s
66    /// - Must be called from a single thread (not thread-safe)
67    pub process: extern "C" fn(
68        instance: *mut c_void,
69        channels: *mut *mut f32,
70        num_channels: u32,
71        num_samples: u32,
72    ),
73
74    /// Update the processor's sample rate.
75    pub set_sample_rate: extern "C" fn(instance: *mut c_void, sample_rate: f32),
76
77    /// Reset processor state (clear delay lines, filters, etc.).
78    pub reset: extern "C" fn(instance: *mut c_void),
79
80    /// Destroy the processor instance and free its memory.
81    ///
82    /// # Safety
83    /// - `instance` must be a valid pointer from `create`
84    /// - Must not be called more than once for the same pointer
85    /// - No other vtable function may be called after `drop`
86    pub drop: extern "C" fn(instance: *mut c_void),
87}
88
89/// Current vtable version. Increment on breaking changes.
90pub const DEV_PROCESSOR_VTABLE_VERSION: u32 = 1;
91
92/// FFI symbol name exported by `wavecraft_plugin!` macro.
93pub const DEV_PROCESSOR_SYMBOL: &[u8] = b"wavecraft_dev_create_processor\0";