reaper_low/
control_surface.rs

1#![allow(non_upper_case_globals)]
2#![allow(non_camel_case_types)]
3#![allow(non_snake_case)]
4use super::{firewall, raw::MediaTrack};
5use crate::raw;
6
7use std::fmt::Debug;
8use std::os::raw::c_void;
9use std::panic::RefUnwindSafe;
10use std::ptr::{null, null_mut, NonNull};
11
12/// This is the Rust analog to the C++ virtual base class `IReaperControlSurface`.
13///
14/// An implementation of this trait can be passed to [`add_cpp_control_surface()`]. After
15/// registering the returned C++ counterpart, REAPER will start invoking the callback methods.
16///
17/// # Design
18///
19/// ## Why do most methods here don't take `&mut self` as parameter?
20///
21/// **Short answer:** Because we follow the spirit of Rust here, which is to fail fast and thereby
22/// prevent undefined behavior.
23///
24/// **Long answer:** Taking `self` as `&mut` in control surface methods would give us a dangerous
25/// illusion of safety (safety as defined by Rust). It would tell Rust developers "It's safe here to
26/// mutate the state of my control surface struct". But in reality it's not safe. Not because of
27/// multi-threading (ControlSurfaces methods are invoked by REAPER's main thread only) but because
28/// of reentrancy. That can happen quite easily, just think of this scenario: A track is changed,
29/// REAPER notifies us about it by calling a ControlSurface method, thereby causing another change
30/// in REAPER which in turn synchronously notifies our ControlSurface again while our first method
31/// is still running ... and there you go: 2 mutable borrows of `self`. In a Rust-only world, Rust's
32/// compiler wouldn't allow us to do that. But Rust won't save us here because the call comes from
33/// "outside". By not having a `&mut self` reference, developers are forced to explicitly think
34/// about this scenario. One can use a `RefCell` along with `borrow_mut()` to still mutate some
35/// control surface state and failing fast whenever reentrancy happens - at runtime, by getting a
36/// panic. This is not as good as failing fast at compile time but still much better than to run
37/// into undefined behavior, which could cause hard-to-find bugs and crash REAPER - that's the last
38/// thing we want! Panicking is not so bad. We can catch it before it reaches REAPER and therefore
39/// let REAPER continue running. Ideally it's observed by the developer when he tests his plugin.
40/// Then he can think about how to solve that issue. They might find out that it's okay and
41/// therefore use some unsafe code to prevent the panic. They might find out that they want to check
42/// for reentrancy by using `try_borrow_mut()`. Or they might find out that they want to
43/// avoid this situation by just deferring the event handling to the next main loop cycle.
44///
45/// [`add_cpp_control_surface()`]: fn.add_cpp_control_surface.html
46pub trait IReaperControlSurface: RefUnwindSafe + Debug {
47    fn GetTypeString(&self) -> *const ::std::os::raw::c_char {
48        null()
49    }
50
51    fn GetDescString(&self) -> *const ::std::os::raw::c_char {
52        null()
53    }
54
55    fn GetConfigString(&self) -> *const ::std::os::raw::c_char {
56        null()
57    }
58
59    fn CloseNoReset(&self) {}
60
61    fn Run(&mut self) {}
62
63    fn SetTrackListChange(&self) {}
64
65    fn SetSurfaceVolume(&self, _trackid: *mut MediaTrack, _volume: f64) {}
66
67    fn SetSurfacePan(&self, _trackid: *mut MediaTrack, _pan: f64) {}
68
69    fn SetSurfaceMute(&self, _trackid: *mut MediaTrack, _mute: bool) {}
70
71    fn SetSurfaceSelected(&self, _trackid: *mut MediaTrack, _selected: bool) {}
72
73    fn SetSurfaceSolo(&self, _trackid: *mut MediaTrack, _solo: bool) {}
74
75    fn SetSurfaceRecArm(&self, _trackid: *mut MediaTrack, _recarm: bool) {}
76
77    fn SetPlayState(&self, _play: bool, _pause: bool, _rec: bool) {}
78
79    fn SetRepeatState(&self, _rep: bool) {}
80
81    fn SetTrackTitle(&self, _trackid: *mut MediaTrack, _title: *const ::std::os::raw::c_char) {}
82
83    fn GetTouchState(&self, _trackid: *mut MediaTrack, _isPan: ::std::os::raw::c_int) -> bool {
84        false
85    }
86
87    fn SetAutoMode(&self, _mode: ::std::os::raw::c_int) {}
88
89    fn ResetCachedVolPanStates(&self) {}
90
91    fn OnTrackSelection(&self, _trackid: *mut MediaTrack) {}
92
93    fn IsKeyDown(&self, _key: ::std::os::raw::c_int) -> bool {
94        false
95    }
96
97    fn Extended(
98        &self,
99        _call: ::std::os::raw::c_int,
100        _parm1: *mut ::std::os::raw::c_void,
101        _parm2: *mut ::std::os::raw::c_void,
102        _parm3: *mut ::std::os::raw::c_void,
103    ) -> ::std::os::raw::c_int {
104        0
105    }
106}
107
108/// Creates an `IReaperControlSurface` object on C++ side and returns a pointer to it.
109///
110/// This function is provided because [`plugin_register()`] isn't going to work if you just pass it
111/// a Rust struct as in `reaper.plugin_register("csurf_inst", my_rust_struct)`. Rust structs can't
112/// implement C++ virtual base classes.
113///
114/// **This function doesn't yet register the control surface!** The usual REAPER C++ way to register
115/// a control surface still applies. You need to pass the resulting pointer to
116/// [`plugin_register()`].
117///
118/// # Example
119///
120/// ```no_run
121/// # let reaper = reaper_low::Reaper::default();
122/// use reaper_low::{add_cpp_control_surface, remove_cpp_control_surface, IReaperControlSurface};
123/// use std::ffi::CString;
124/// use std::ptr::NonNull;
125/// use c_str_macro::c_str;
126///
127/// unsafe {
128///     // Register
129///     #[derive(Debug)]
130///     struct MyControlSurface;
131///     impl IReaperControlSurface for MyControlSurface {
132///         fn SetTrackListChange(&self) {
133///             println!("Tracks changed");
134///         }
135///     }
136///     let rust_cs: Box<dyn IReaperControlSurface> = Box::new(MyControlSurface);
137///     let thin_ptr_to_rust_cs: NonNull<_> = (&rust_cs).into();
138///     let cpp_cs = add_cpp_control_surface(thin_ptr_to_rust_cs);
139///     reaper.plugin_register(c_str!("csurf_inst").as_ptr(), cpp_cs.as_ptr() as _);
140///     // Unregister
141///     reaper.plugin_register(c_str!("-csurf_inst").as_ptr(), cpp_cs.as_ptr() as _);
142///     remove_cpp_control_surface(cpp_cs);
143/// }
144/// ```
145///
146/// # Cleaning up
147///
148/// If you register a control surface, you also must take care of unregistering it at
149/// the end. This is especially important for VST plug-ins because they live shorter than a REAPER
150/// session! **If you don't unregister the control surface before the VST plug-in is destroyed,
151/// REAPER will crash** because it will attempt to invoke functions which are not loaded anymore.
152///
153/// In order to avoid memory leaks, you also must take care of removing the C++ counterpart
154/// surface by calling [`remove_cpp_control_surface()`].
155///
156/// # Safety
157///
158/// This function is highly unsafe for all the reasons mentioned above. Better use the medium-level
159/// API instead, which makes registering a breeze.
160///
161/// [`plugin_register()`]: struct.Reaper.html#method.plugin_register
162/// [`remove_cpp_control_surface()`]: fn.remove_cpp_control_surface.html
163pub unsafe fn add_cpp_control_surface(
164    callback_target: NonNull<Box<dyn IReaperControlSurface>>,
165) -> NonNull<raw::IReaperControlSurface> {
166    let instance = crate::bindings::root::reaper_control_surface::add_control_surface(
167        callback_target.as_ptr() as *mut c_void,
168    );
169    NonNull::new_unchecked(instance)
170}
171
172/// Destroys a C++ `IReaperControlSurface` object.
173///
174/// Intended to be used on pointers returned from [`add_cpp_control_surface()`].
175///
176/// [`add_cpp_control_surface()`]: fn.add_cpp_control_surface.html
177pub unsafe fn remove_cpp_control_surface(surface: NonNull<raw::IReaperControlSurface>) {
178    crate::bindings::root::reaper_control_surface::remove_control_surface(surface.as_ptr());
179}
180
181#[no_mangle]
182extern "C" fn GetTypeString(
183    callback_target: *mut Box<dyn IReaperControlSurface>,
184) -> *const ::std::os::raw::c_char {
185    firewall(|| unsafe { &*callback_target }.GetTypeString()).unwrap_or(null_mut())
186}
187
188#[no_mangle]
189extern "C" fn GetDescString(
190    callback_target: *mut Box<dyn IReaperControlSurface>,
191) -> *const ::std::os::raw::c_char {
192    firewall(|| unsafe { &*callback_target }.GetDescString()).unwrap_or(null_mut())
193}
194
195#[no_mangle]
196extern "C" fn GetConfigString(
197    callback_target: *mut Box<dyn IReaperControlSurface>,
198) -> *const ::std::os::raw::c_char {
199    firewall(|| unsafe { &*callback_target }.GetConfigString()).unwrap_or(null_mut())
200}
201
202#[no_mangle]
203extern "C" fn CloseNoReset(callback_target: *mut Box<dyn IReaperControlSurface>) {
204    firewall(|| unsafe { &*callback_target }.CloseNoReset());
205}
206
207#[no_mangle]
208extern "C" fn Run(callback_target: *mut Box<dyn IReaperControlSurface>) {
209    // "Decoding" the thin pointer is not necessary right now because we have a static variable.
210    // However, we leave it. Might come in handy one day to support multiple control surfaces
211    // (see https://users.rust-lang.org/t/sending-a-boxed-trait-over-ffi/21708/6)
212    firewall(|| unsafe { &mut *callback_target }.Run());
213}
214
215#[no_mangle]
216extern "C" fn SetTrackListChange(callback_target: *mut Box<dyn IReaperControlSurface>) {
217    firewall(|| unsafe { &*callback_target }.SetTrackListChange());
218}
219
220#[no_mangle]
221extern "C" fn SetSurfaceVolume(
222    callback_target: *mut Box<dyn IReaperControlSurface>,
223    trackid: *mut MediaTrack,
224    volume: f64,
225) {
226    firewall(|| unsafe { &*callback_target }.SetSurfaceVolume(trackid, volume));
227}
228
229#[no_mangle]
230extern "C" fn SetSurfacePan(
231    callback_target: *mut Box<dyn IReaperControlSurface>,
232    trackid: *mut MediaTrack,
233    pan: f64,
234) {
235    firewall(|| unsafe { &*callback_target }.SetSurfacePan(trackid, pan));
236}
237
238#[no_mangle]
239extern "C" fn SetSurfaceMute(
240    callback_target: *mut Box<dyn IReaperControlSurface>,
241    trackid: *mut MediaTrack,
242    mute: bool,
243) {
244    firewall(|| unsafe { &*callback_target }.SetSurfaceMute(trackid, mute));
245}
246
247#[no_mangle]
248extern "C" fn SetSurfaceSelected(
249    callback_target: *mut Box<dyn IReaperControlSurface>,
250    trackid: *mut MediaTrack,
251    selected: bool,
252) {
253    firewall(|| unsafe { &*callback_target }.SetSurfaceSelected(trackid, selected));
254}
255
256#[no_mangle]
257extern "C" fn SetSurfaceSolo(
258    callback_target: *mut Box<dyn IReaperControlSurface>,
259    trackid: *mut MediaTrack,
260    solo: bool,
261) {
262    firewall(|| unsafe { &*callback_target }.SetSurfaceSolo(trackid, solo));
263}
264
265#[no_mangle]
266extern "C" fn SetSurfaceRecArm(
267    callback_target: *mut Box<dyn IReaperControlSurface>,
268    trackid: *mut MediaTrack,
269    recarm: bool,
270) {
271    firewall(|| unsafe { &*callback_target }.SetSurfaceRecArm(trackid, recarm));
272}
273
274#[no_mangle]
275extern "C" fn SetPlayState(
276    callback_target: *mut Box<dyn IReaperControlSurface>,
277    play: bool,
278    pause: bool,
279    rec: bool,
280) {
281    firewall(|| unsafe { &*callback_target }.SetPlayState(play, pause, rec));
282}
283
284#[no_mangle]
285extern "C" fn SetRepeatState(callback_target: *mut Box<dyn IReaperControlSurface>, rep: bool) {
286    firewall(|| unsafe { &*callback_target }.SetRepeatState(rep));
287}
288
289#[no_mangle]
290extern "C" fn SetTrackTitle(
291    callback_target: *mut Box<dyn IReaperControlSurface>,
292    trackid: *mut MediaTrack,
293    title: *const ::std::os::raw::c_char,
294) {
295    firewall(|| unsafe { &*callback_target }.SetTrackTitle(trackid, title));
296}
297
298#[no_mangle]
299extern "C" fn GetTouchState(
300    callback_target: *mut Box<dyn IReaperControlSurface>,
301    trackid: *mut MediaTrack,
302    isPan: ::std::os::raw::c_int,
303) -> bool {
304    firewall(|| unsafe { &*callback_target }.GetTouchState(trackid, isPan)).unwrap_or(false)
305}
306
307#[no_mangle]
308extern "C" fn SetAutoMode(
309    callback_target: *mut Box<dyn IReaperControlSurface>,
310    mode: ::std::os::raw::c_int,
311) {
312    firewall(|| unsafe { &*callback_target }.SetAutoMode(mode));
313}
314
315#[no_mangle]
316extern "C" fn ResetCachedVolPanStates(callback_target: *mut Box<dyn IReaperControlSurface>) {
317    firewall(|| unsafe { &*callback_target }.ResetCachedVolPanStates());
318}
319
320#[no_mangle]
321extern "C" fn OnTrackSelection(
322    callback_target: *mut Box<dyn IReaperControlSurface>,
323    trackid: *mut MediaTrack,
324) {
325    firewall(|| unsafe { &*callback_target }.OnTrackSelection(trackid));
326}
327
328#[no_mangle]
329extern "C" fn IsKeyDown(
330    callback_target: *mut Box<dyn IReaperControlSurface>,
331    key: ::std::os::raw::c_int,
332) -> bool {
333    firewall(|| unsafe { &*callback_target }.IsKeyDown(key)).unwrap_or(false)
334}
335
336#[no_mangle]
337extern "C" fn Extended(
338    callback_target: *mut Box<dyn IReaperControlSurface>,
339    call: ::std::os::raw::c_int,
340    parm1: *mut ::std::os::raw::c_void,
341    parm2: *mut ::std::os::raw::c_void,
342    parm3: *mut ::std::os::raw::c_void,
343) -> ::std::os::raw::c_int {
344    firewall(|| unsafe { &*callback_target }.Extended(call, parm1, parm2, parm3)).unwrap_or(0)
345}