cxx_juce/
lib.rs

1//! Rust bindings for [JUCE](https://juce.com/) using [cxx](https://github.com/dtolnay/cxx).
2
3pub mod juce_audio_basics;
4pub mod juce_audio_devices;
5
6use {
7    juce_audio_devices::{
8        ffi::{
9            audio_io_device::{
10                device_available_buffer_sizes, device_available_sample_rates, device_buffer_size,
11                device_close, device_name, device_open, device_sample_rate, device_type_name,
12            },
13            audio_io_device_callback::{about_to_start, process_block, stopped},
14            audio_io_device_type::{
15                create_device, destroy_device, get_device_names, name, scan_for_devices,
16            },
17        },
18        BoxedAudioIODevice, BoxedAudioIODeviceCallback, BoxedAudioIODeviceType,
19    },
20    std::{
21        rc::Rc,
22        sync::atomic::{AtomicBool, Ordering},
23    },
24};
25
26/// Returns the version of the JUCE library.
27pub fn juce_version() -> String {
28    juce::version()
29}
30
31/// A handle to the JUCE runtime. Required for certain JUCE classes.
32///
33/// Once all references to this object are dropped, the JUCE runtime will be shut down.
34#[must_use]
35#[derive(Clone)]
36pub struct JUCE {
37    _app: Rc<JuceApp>,
38}
39
40static IS_JUCE_RUNNING: AtomicBool = AtomicBool::new(false);
41
42struct JuceApp;
43
44impl JuceApp {
45    fn new() -> Self {
46        juce::initialise_juce();
47
48        #[cfg(target_os = "macos")]
49        juce::initialise_ns_application();
50
51        Self
52    }
53}
54
55impl Drop for JuceApp {
56    fn drop(&mut self) {
57        juce::shutdown_juce();
58
59        IS_JUCE_RUNNING.store(false, Ordering::SeqCst);
60    }
61}
62
63#[derive(Debug)]
64enum InitialiseError {
65    JuceAlreadyInitialised,
66}
67
68impl std::error::Error for InitialiseError {}
69
70impl std::fmt::Display for InitialiseError {
71    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
72        match self {
73            Self::JuceAlreadyInitialised => write!(f, "JUCE has already been initialised"),
74        }
75    }
76}
77
78impl JUCE {
79    /// Initialises the JUCE runtime.
80    ///
81    /// # Panics
82    ///
83    /// This function will panic if the JUCE runtime is already initialised.
84    pub fn initialise() -> Self {
85        Self::try_initialise().unwrap()
86    }
87
88    fn try_initialise() -> std::result::Result<Self, InitialiseError> {
89        let result =
90            IS_JUCE_RUNNING.compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst);
91
92        if result.is_err() {
93            return Err(InitialiseError::JuceAlreadyInitialised);
94        }
95
96        Ok(Self {
97            _app: Rc::new(JuceApp::new()),
98        })
99    }
100}
101
102pub type Exception = cxx::Exception;
103pub type Result<T> = std::result::Result<T, Exception>;
104
105#[cxx::bridge(namespace = "cxx_juce")]
106pub(crate) mod juce {
107    extern "Rust" {
108        type BoxedAudioIODeviceCallback;
109
110        #[namespace = "audio_io_device_callback"]
111        #[cxx_name = "aboutToStart"]
112        fn about_to_start(
113            callback: Pin<&mut BoxedAudioIODeviceCallback>,
114            device: Pin<&mut AudioIODevice>,
115        );
116
117        #[namespace = "audio_io_device_callback"]
118        #[cxx_name = "processBlock"]
119        fn process_block(
120            callback: Pin<&mut BoxedAudioIODeviceCallback>,
121            input: &AudioSampleBuffer,
122            output: Pin<&mut AudioSampleBuffer>,
123        );
124
125        #[namespace = "audio_io_device_callback"]
126        #[cxx_name = "stopped"]
127        fn stopped(callback: Pin<&mut BoxedAudioIODeviceCallback>);
128
129        type BoxedAudioIODeviceType;
130
131        #[namespace = "audio_io_device_type"]
132        #[cxx_name = "name"]
133        fn name(self_: &BoxedAudioIODeviceType) -> String;
134
135        #[namespace = "audio_io_device_type"]
136        #[cxx_name = "scanForDevices"]
137        fn scan_for_devices(self_: Pin<&mut BoxedAudioIODeviceType>);
138
139        #[namespace = "audio_io_device_type"]
140        #[cxx_name = "getDeviceNames"]
141        fn get_device_names(self_: &BoxedAudioIODeviceType, input: bool) -> Vec<String>;
142
143        #[namespace = "audio_io_device_type"]
144        #[cxx_name = "createDevice"]
145        fn create_device(
146            self_: Pin<&mut BoxedAudioIODeviceType>,
147            input_device_name: &str,
148            output_device_name: &str,
149        ) -> *mut BoxedAudioIODevice;
150
151        #[namespace = "audio_io_device_type"]
152        #[cxx_name = "destroyDevice"]
153        unsafe fn destroy_device(self_: *mut BoxedAudioIODevice);
154
155        type BoxedAudioIODevice;
156
157        #[namespace = "audio_io_device"]
158        #[cxx_name = "deviceName"]
159        pub fn device_name(self_: &BoxedAudioIODevice) -> String;
160
161        #[namespace = "audio_io_device"]
162        #[cxx_name = "typeName"]
163        pub fn device_type_name(self_: &BoxedAudioIODevice) -> String;
164
165        #[namespace = "audio_io_device"]
166        #[cxx_name = "sampleRate"]
167        pub fn device_sample_rate(self_: Pin<&mut BoxedAudioIODevice>) -> f64;
168
169        #[namespace = "audio_io_device"]
170        #[cxx_name = "bufferSize"]
171        pub fn device_buffer_size(self_: Pin<&mut BoxedAudioIODevice>) -> usize;
172
173        #[namespace = "audio_io_device"]
174        #[cxx_name = "availableSampleRates"]
175        pub fn device_available_sample_rates(self_: Pin<&mut BoxedAudioIODevice>) -> Vec<f64>;
176
177        #[namespace = "audio_io_device"]
178        #[cxx_name = "availableBufferSizes"]
179        pub fn device_available_buffer_sizes(self_: Pin<&mut BoxedAudioIODevice>) -> Vec<usize>;
180
181        #[namespace = "audio_io_device"]
182        #[cxx_name = "open"]
183        pub fn device_open(
184            self_: Pin<&mut BoxedAudioIODevice>,
185            sample_rate: f64,
186            buffer_size: usize,
187        ) -> String;
188
189        #[namespace = "audio_io_device"]
190        #[cxx_name = "close"]
191        pub fn device_close(self_: Pin<&mut BoxedAudioIODevice>);
192    }
193
194    unsafe extern "C++" {
195        include!("cxx-juce/bridge/cxx_juce.h");
196
197        #[rust_name = "version"]
198        pub fn juceVersion() -> String;
199
200        #[rust_name = "initialise_juce"]
201        pub fn initialiseJuce();
202
203        #[rust_name = "shutdown_juce"]
204        pub fn shutdownJuce();
205
206        #[cfg(target_os = "macos")]
207        #[namespace = "juce"]
208        #[rust_name = "initialise_ns_application"]
209        pub fn initialiseNSApplication();
210
211        #[namespace = "juce"]
212        pub type AudioIODeviceTypeArray;
213
214        pub fn size(self: &AudioIODeviceTypeArray) -> i32;
215
216        #[rust_name = "get_unchecked"]
217        pub fn getUnchecked(self: &AudioIODeviceTypeArray, index: i32) -> *mut AudioIODeviceType;
218
219        pub type AudioDeviceSetup;
220
221        #[rust_name = "create_audio_device_setup"]
222        pub fn createAudioDeviceSetup() -> UniquePtr<AudioDeviceSetup>;
223
224        #[rust_name = "output_device_name"]
225        pub fn outputDeviceName(self: &AudioDeviceSetup) -> &str;
226
227        #[rust_name = "input_device_name"]
228        pub fn inputDeviceName(self: &AudioDeviceSetup) -> &str;
229
230        #[rust_name = "sample_rate"]
231        pub fn sampleRate(self: &AudioDeviceSetup) -> f64;
232
233        #[rust_name = "buffer_size"]
234        pub fn bufferSize(self: &AudioDeviceSetup) -> i32;
235
236        #[rust_name = "set_output_device_name"]
237        pub fn setOutputDeviceName(self: Pin<&mut AudioDeviceSetup>, name: &str);
238
239        #[rust_name = "set_input_device_name"]
240        pub fn setInputDeviceName(self: Pin<&mut AudioDeviceSetup>, name: &str);
241
242        #[rust_name = "set_sample_rate"]
243        pub fn setSampleRate(self: Pin<&mut AudioDeviceSetup>, sample_rate: f64);
244
245        #[rust_name = "set_buffer_size"]
246        pub fn setBufferSize(self: Pin<&mut AudioDeviceSetup>, buffer_size: i32);
247
248        #[rust_name = "number_of_input_channels"]
249        pub fn numberOfInputChannels(self: &AudioDeviceSetup) -> i32;
250
251        #[rust_name = "set_number_of_input_channels"]
252        pub fn setNumberOfInputChannels(
253            self: Pin<&mut AudioDeviceSetup>,
254            number_of_input_channels: i32,
255        );
256
257        #[rust_name = "use_default_input_channels"]
258        pub fn useDefaultInputChannels(self: Pin<&mut AudioDeviceSetup>, use_default: bool);
259
260        #[rust_name = "using_default_input_channels"]
261        pub fn usingDefaultInputChannels(self: &AudioDeviceSetup) -> bool;
262
263        #[rust_name = "number_of_output_channels"]
264        pub fn numberOfOutputChannels(self: &AudioDeviceSetup) -> i32;
265
266        #[rust_name = "set_number_of_output_channels"]
267        pub fn setNumberOfOutputChannels(
268            self: Pin<&mut AudioDeviceSetup>,
269            number_of_output_channels: i32,
270        );
271
272        #[rust_name = "use_default_output_channels"]
273        pub fn useDefaultOutputChannels(self: Pin<&mut AudioDeviceSetup>, use_default: bool);
274
275        #[rust_name = "using_default_output_channels"]
276        pub fn usingDefaultOutputChannels(self: &AudioDeviceSetup) -> bool;
277
278        pub type AudioDeviceManager;
279
280        #[rust_name = "create_audio_device_manager"]
281        pub fn createAudioDeviceManager() -> UniquePtr<AudioDeviceManager>;
282
283        #[rust_name = "wrap_audio_callback"]
284        pub fn wrapAudioCallback(
285            callback: Box<BoxedAudioIODeviceCallback>,
286        ) -> UniquePtr<AudioCallbackWrapper>;
287
288        #[rust_name = "initialise_with_default_devices"]
289        pub fn initialiseWithDefaultDevices(
290            self: Pin<&mut AudioDeviceManager>,
291            num_input_channels: i32,
292            num_output_channels: i32,
293        ) -> Result<()>;
294
295        #[rust_name = "get_audio_device_setup"]
296        pub fn getAudioDeviceSetup(self: &AudioDeviceManager) -> UniquePtr<AudioDeviceSetup>;
297
298        #[rust_name = "set_audio_device_setup"]
299        pub fn setAudioDeviceSetup(self: Pin<&mut AudioDeviceManager>, setup: &AudioDeviceSetup);
300
301        #[rust_name = "get_current_audio_device"]
302        pub fn getCurrentAudioDevice(self: &AudioDeviceManager) -> *mut AudioIODevice;
303
304        #[rust_name = "get_available_device_types"]
305        pub fn getAvailableDeviceTypes(
306            self: Pin<&mut AudioDeviceManager>,
307        ) -> &AudioIODeviceTypeArray;
308
309        #[rust_name = "get_current_device_type_object"]
310        pub fn getCurrentDeviceTypeObject(self: &AudioDeviceManager) -> *mut AudioIODeviceType;
311
312        #[rust_name = "play_test_sound"]
313        pub fn playTestSound(self: Pin<&mut AudioDeviceManager>);
314
315        #[rust_name = "add_audio_callback"]
316        pub fn addAudioCallback(
317            self: Pin<&mut AudioDeviceManager>,
318            callback: &UniquePtr<AudioCallbackWrapper>,
319        );
320
321        #[rust_name = "remove_audio_callback"]
322        pub fn removeAudioCallback(
323            self: Pin<&mut AudioDeviceManager>,
324            callback: &UniquePtr<AudioCallbackWrapper>,
325        );
326
327        #[rust_name = "add_audio_device_type"]
328        pub fn addAudioDeviceType(
329            self: Pin<&mut AudioDeviceManager>,
330            device_type: Box<BoxedAudioIODeviceType>,
331        );
332
333        #[rust_name = "set_current_audio_device_type"]
334        pub fn setCurrentAudioDeviceType(self: Pin<&mut AudioDeviceManager>, device_type: &str);
335
336        #[namespace = "juce"]
337        pub type AudioIODevice;
338
339        #[namespace = "cxx_juce::audio_io_device"]
340        #[rust_name = "get_device_name"]
341        pub fn getDeviceName(self_: &AudioIODevice) -> &str;
342
343        #[namespace = "cxx_juce::audio_io_device"]
344        #[rust_name = "get_device_type_name"]
345        pub fn getDeviceTypeName(self_: &AudioIODevice) -> &str;
346
347        #[rust_name = "get_current_sample_rate"]
348        pub fn getCurrentSampleRate(self: Pin<&mut AudioIODevice>) -> f64;
349
350        #[rust_name = "get_current_buffer_size_samples"]
351        pub fn getCurrentBufferSizeSamples(self: Pin<&mut AudioIODevice>) -> i32;
352
353        #[namespace = "cxx_juce::audio_io_device"]
354        #[rust_name = "get_available_sample_rates"]
355        pub fn getAvailableSampleRates(self_: Pin<&mut AudioIODevice>) -> Vec<f64>;
356
357        #[namespace = "cxx_juce::audio_io_device"]
358        #[rust_name = "get_available_buffer_sizes"]
359        pub fn getAvailableBufferSizes(self_: Pin<&mut AudioIODevice>) -> Vec<usize>;
360
361        #[namespace = "cxx_juce::audio_io_device"]
362        #[rust_name = "open"]
363        pub fn open(
364            self_: Pin<&mut AudioIODevice>,
365            sample_rate: f64,
366            buffer_size: usize,
367        ) -> Result<()>;
368
369        #[rust_name = "close"]
370        pub fn close(self: Pin<&mut AudioIODevice>);
371
372        #[namespace = "cxx_juce::audio_io_device"]
373        #[rust_name = "count_active_input_channels"]
374        pub fn countActiveInputChannels(self_: &AudioIODevice) -> i32;
375
376        #[namespace = "cxx_juce::audio_io_device"]
377        #[rust_name = "count_active_output_channels"]
378        pub fn countActiveOutputChannels(self_: &AudioIODevice) -> i32;
379
380        #[namespace = "juce"]
381        pub type AudioIODeviceType;
382
383        #[namespace = "cxx_juce::audio_io_device_type"]
384        #[rust_name = "get_type_name"]
385        pub fn getTypeName(self_: &AudioIODeviceType) -> String;
386
387        #[rust_name = "scan_for_devices"]
388        pub fn scanForDevices(self: Pin<&mut AudioIODeviceType>);
389
390        #[namespace = "cxx_juce::audio_io_device_type"]
391        #[rust_name = "get_input_device_names"]
392        pub fn getInputDeviceNames(self_: &AudioIODeviceType) -> Vec<String>;
393
394        #[namespace = "cxx_juce::audio_io_device_type"]
395        #[rust_name = "get_output_device_names"]
396        pub fn getOutputDeviceNames(self_: &AudioIODeviceType) -> Vec<String>;
397
398        #[namespace = "cxx_juce::audio_io_device_type"]
399        #[rust_name = "new_device"]
400        pub fn createDevice(
401            self_: Pin<&mut AudioIODeviceType>,
402            input_device_name: &str,
403            output_device_name: &str,
404        ) -> UniquePtr<AudioIODevice>;
405
406        #[namespace = "juce"]
407        pub type AudioSampleBuffer;
408
409        #[rust_name = "get_num_channels"]
410        pub fn getNumChannels(self: &AudioSampleBuffer) -> i32;
411
412        #[rust_name = "get_num_samples"]
413        pub fn getNumSamples(self: &AudioSampleBuffer) -> i32;
414
415        #[rust_name = "get_read_pointer"]
416        pub fn getReadPointer(self: &AudioSampleBuffer, channel: i32) -> *const f32;
417
418        #[rust_name = "get_write_pointer"]
419        pub fn getWritePointer(self: Pin<&mut AudioSampleBuffer>, channel: i32) -> *mut f32;
420
421        #[rust_name = "clear"]
422        pub fn clear(self: Pin<&mut AudioSampleBuffer>);
423
424        pub type AudioCallbackWrapper;
425
426        #[namespace = "cxx_juce::system_audio_volume"]
427        #[rust_name = "set_muted"]
428        pub fn setMuted(muted: bool);
429
430        #[namespace = "cxx_juce::system_audio_volume"]
431        #[rust_name = "is_muted"]
432        pub fn isMuted() -> bool;
433
434        #[namespace = "cxx_juce::system_audio_volume"]
435        #[rust_name = "set_gain"]
436        pub fn setGain(gain: f32);
437
438        #[namespace = "cxx_juce::system_audio_volume"]
439        #[rust_name = "get_gain"]
440        pub fn getGain() -> f32;
441
442        #[namespace = "juce"]
443        pub type SingleThreadedIIRFilter;
444
445        #[namespace = "cxx_juce::iir_filter"]
446        #[rust_name = "create_iir_filter"]
447        pub fn createIIRFilter(coefficients: [f32; 5]) -> UniquePtr<SingleThreadedIIRFilter>;
448
449        #[namespace = "juce"]
450        #[rust_name = "process_samples"]
451        pub unsafe fn processSamples(
452            self: Pin<&mut SingleThreadedIIRFilter>,
453            samples: *mut f32,
454            num_samples: i32,
455        );
456
457        #[namespace = "cxx_juce::iir_filter"]
458        #[rust_name = "make_low_pass"]
459        pub fn makeLowPass(sample_rate: f64, frequency: f64, q: f64) -> [f32; 5];
460
461        #[namespace = "cxx_juce::iir_filter"]
462        #[rust_name = "make_high_pass"]
463        pub fn makeHighPass(sample_rate: f64, frequency: f64, q: f64) -> [f32; 5];
464
465        #[namespace = "cxx_juce::iir_filter"]
466        #[rust_name = "make_notch_filter"]
467        pub fn makeNotchFilter(sample_rate: f64, frequency: f64, q: f64) -> [f32; 5];
468    }
469}
470
471#[cfg(test)]
472mod test {
473    use super::*;
474
475    fn try_to_initialise_juce_on_new_thread() -> std::thread::Result<()> {
476        std::thread::spawn(move || {
477            let _juce = JUCE::initialise();
478        })
479        .join()
480    }
481
482    #[test]
483    #[should_panic]
484    fn initialising_juce_twice_on_the_same_thread_should_panic() {
485        let _juce = JUCE::initialise();
486        let _juce = JUCE::initialise();
487    }
488
489    #[test]
490    fn initialising_juce_again_on_the_same_thread_after_shutdown_is_ok() {
491        let juce = JUCE::initialise();
492        drop(juce);
493
494        let _juce = JUCE::initialise();
495    }
496
497    #[test]
498    fn juce_cant_be_initialised_simultaneously_on_two_different_threads() {
499        let _juce = JUCE::initialise();
500
501        assert!(try_to_initialise_juce_on_new_thread().is_err());
502    }
503
504    #[test]
505    fn juce_can_run_on_a_different_thread_after_finishing_on_another() {
506        let juce = JUCE::initialise();
507        drop(juce);
508
509        assert!(try_to_initialise_juce_on_new_thread().is_ok());
510    }
511
512    #[test]
513    fn juce_is_shutdown_once_all_references_have_been_dropped() {
514        let a = JUCE::initialise();
515        let b = a.clone();
516
517        drop(a);
518
519        assert!(try_to_initialise_juce_on_new_thread().is_err());
520
521        drop(b);
522
523        assert!(try_to_initialise_juce_on_new_thread().is_ok());
524    }
525}