Skip to main content

reaper_medium/
reaper.rs

1use c_str_macro::c_str;
2
3use std::ptr::NonNull;
4
5use reaper_low::{
6    add_cpp_control_surface, raw, remove_cpp_control_surface, IReaperControlSurface,
7    ReaperPluginContext,
8};
9
10use crate::infostruct_keeper::InfostructKeeper;
11
12use crate::{
13    concat_c_strs, delegating_hook_command, delegating_hook_post_command, delegating_toggle_action,
14    CommandId, DelegatingControlSurface, MainThreadScope, MediumAudioHookRegister,
15    MediumGaccelRegister, MediumHookCommand, MediumHookPostCommand, MediumOnAudioBuffer,
16    MediumReaperControlSurface, MediumToggleAction, RealTimeAudioThreadScope, ReaperFunctionError,
17    ReaperFunctionResult, ReaperFunctions, ReaperStringArg, RegistrationObject,
18};
19use reaper_low;
20use reaper_low::raw::audio_hook_register_t;
21use std::collections::{HashMap, HashSet};
22
23/// This is the main hub for accessing medium-level API functions.
24///
25/// In order to use this struct, you first must obtain an instance of it by invoking [`new()`]
26/// or [`load()`].
27/// This struct itself is limited to REAPER functions for registering/unregistering certain things.
28/// You can access all the other functions by calling [`functions()`].
29///
30/// Please note that this struct will take care of unregistering everything (also audio hooks)
31/// automatically when it gets dropped (good RAII manners).
32///
33/// # Design
34///
35/// ## Why is there a separation into `Reaper` and `ReaperFunctions`?
36///
37/// Functions for registering/unregistering things have been separated from the rest because they
38/// require more than just access to REAPER function pointers. They also need data structures to
39/// keep track of the registered things and to offer them a warm and cosy place in memory. As a
40/// result, this struct gains special importance, needs to be mutable and can't just be cloned as
41/// desired. But there's no reason why this restriction should also apply to all other REAPER
42/// functions. After all, being able to clone and pass things around freely can simplify things a
43/// lot.
44///
45/// ### Example
46///
47/// Here's an example how things can get difficult without the ability to clone: In order to be able
48/// to use REAPER functions also from e.g. the audio hook register, we would need to wrap it in an
49/// `Arc` (not an `Rc`, because we access it from multiple threads). That's not enough though for
50/// most real-world cases. We probably want to register/unregister things (in the main thread) not
51/// only in the beginning but also at a later time. That means we need mutable access. So we end up
52/// with `Arc<Mutex<Reaper>>`. However, why going through all that trouble and put up with possible
53/// performance issues if we can avoid it?
54///
55/// [`new()`]: #method.new
56/// [`load()`]: #method.load
57/// [`functions()`]: #method.functions
58#[derive(Debug, Default)]
59pub struct Reaper {
60    functions: ReaperFunctions<MainThreadScope>,
61    gaccel_registers: InfostructKeeper<MediumGaccelRegister, raw::gaccel_register_t>,
62    audio_hook_registers: InfostructKeeper<MediumAudioHookRegister, raw::audio_hook_register_t>,
63    csurf_insts: HashMap<NonNull<raw::IReaperControlSurface>, Box<Box<dyn IReaperControlSurface>>>,
64    plugin_registrations: HashSet<RegistrationObject<'static>>,
65    audio_hook_registrations: HashSet<NonNull<raw::audio_hook_register_t>>,
66}
67
68impl Reaper {
69    /// Creates a new instance by getting hold of a [low-level `Reaper`] instance.
70    ///
71    /// [low-level `Reaper`]: /reaper_low/struct.Reaper.html
72    pub fn new(low: reaper_low::Reaper) -> Reaper {
73        Reaper {
74            functions: ReaperFunctions::new(low),
75            gaccel_registers: Default::default(),
76            audio_hook_registers: Default::default(),
77            csurf_insts: Default::default(),
78            plugin_registrations: Default::default(),
79            audio_hook_registrations: Default::default(),
80        }
81    }
82
83    /// Loads all available REAPER functions from the given plug-in context.
84    ///
85    /// Returns a medium-level `Reaper` instance which allows you to call these functions.
86    pub fn load(context: &ReaperPluginContext) -> Reaper {
87        let low = reaper_low::Reaper::load(context);
88        Reaper::new(low)
89    }
90
91    /// Gives access to all REAPER functions which can be safely executed in the main thread.
92    pub fn functions(&self) -> &ReaperFunctions<MainThreadScope> {
93        &self.functions
94    }
95
96    /// Creates a new container of REAPER functions with only those unlocked that can be safely
97    /// executed in the real-time audio thread.
98    pub fn create_real_time_functions(&self) -> ReaperFunctions<RealTimeAudioThreadScope> {
99        ReaperFunctions::new(self.functions.low().clone())
100    }
101
102    /// This is the primary function for plug-ins to register things.
103    ///
104    /// *Things* can be keyboard shortcuts, project importers etc. Typically you register things
105    /// when the plug-in is loaded.
106    ///
107    /// It is not recommended to use this function directly because it's unsafe. Consider using
108    /// the safe convenience functions instead. They all start with `plugin_register_add_`.
109    ///
110    /// The meaning of the return value depends very much on the actual thing being registered. In
111    /// most cases it just returns 1. In any case it's not 0, *reaper-rs* translates this into an
112    /// error.
113    ///
114    /// Also see [`plugin_register_remove()`].
115    ///
116    /// # Errors
117    ///
118    /// Returns an error if the registration failed.
119    ///
120    /// # Safety
121    ///
122    /// REAPER can crash if you pass an invalid pointer or if it dangles during the time it
123    /// is registered. So you must ensure that the registered thing lives long enough and
124    /// has a stable address in memory. Additionally, mutation of the thing while it is registered
125    /// can lead to subtle bugs.
126    ///
127    /// [`plugin_register_remove()`]: #method.plugin_register_remove
128    pub unsafe fn plugin_register_add(
129        &mut self,
130        object: RegistrationObject,
131    ) -> ReaperFunctionResult<i32> {
132        self.plugin_registrations
133            .insert(object.clone().into_owned());
134        let infostruct = object.ptr_to_raw();
135        let result = self
136            .functions
137            .low()
138            .plugin_register(object.key_into_raw().as_ptr(), infostruct);
139        if result == 0 {
140            return Err(ReaperFunctionError::new("couldn't register thing"));
141        }
142        Ok(result)
143    }
144
145    /// Unregisters things that you have registered with [`plugin_register_add()`].
146    ///
147    /// Please note that unregistering things manually just for cleaning up is unnecessary in most
148    /// situations because *reaper-rs* takes care of automatically unregistering everything when
149    /// this struct is dropped (RAII). This happens even when using the unsafe function variants.
150    ///
151    /// # Safety
152    ///
153    /// REAPER can crash if you pass an invalid pointer.
154    ///
155    /// [`plugin_register_add()`]: #method.plugin_register_add
156    pub unsafe fn plugin_register_remove(&mut self, object: RegistrationObject) -> i32 {
157        let infostruct = object.ptr_to_raw();
158        let name_with_minus = concat_c_strs(c_str!("-"), object.clone().key_into_raw().as_ref());
159        let result = self
160            .functions
161            .low()
162            .plugin_register(name_with_minus.as_ptr(), infostruct);
163        self.plugin_registrations.remove(&object.into_owned());
164        result
165    }
166
167    /// Registers a hook command.
168    ///
169    /// REAPER calls hook commands whenever an action is requested to be run.
170    ///
171    /// This method doesn't take a closure because REAPER expects a plain function pointer here.
172    /// Unlike [`audio_reg_hardware_hook_add`](#method.audio_reg_hardware_hook_add), REAPER
173    /// doesn't offer the possibiity to pass a context to the function. So we can't access any
174    /// context data in the hook command. You will probably have to use a kind of static
175    /// variable which contains command IDs in order to make proper use of this method. The
176    /// high-level API makes that much easier (it just takes an arbitrary closure). For the
177    /// medium-level API this is out of scope.
178    ///
179    /// # Errors
180    ///
181    /// Returns an error if the registration failed.
182    ///
183    /// # Example
184    ///
185    /// ```no_run
186    /// # let mut reaper = reaper_medium::Reaper::default();
187    /// use reaper_medium::{MediumHookCommand, CommandId};
188    ///
189    /// // Usually you would use a dynamic command ID that you have obtained via
190    /// // `plugin_register_add_command_id()`. Unfortunately that command ID must be exposed as
191    /// // a static variable. The high-level API provides a solution for that.
192    /// const MY_COMMAND_ID: CommandId = unsafe { CommandId::new_unchecked(42000) };
193    ///
194    /// struct MyHookCommand;
195    ///
196    /// impl MediumHookCommand for MyHookCommand {
197    ///     fn call(command_id: CommandId, _flag: i32) -> bool {
198    ///         if command_id != MY_COMMAND_ID {
199    ///             return false;
200    ///         }           
201    ///         println!("Executing my command!");
202    ///         true
203    ///     }
204    /// }
205    /// reaper.plugin_register_add_hook_command::<MyHookCommand>();
206    /// # Ok::<_, Box<dyn std::error::Error>>(())
207    /// ```
208    ///
209    /// # Design
210    ///
211    /// You will note that this method has a somewhat strange signature: It expects a type parameter
212    /// only, not a function pointer. That allows us to lift the API to medium-level style.
213    /// The alternative would have been to expect a function pointer, but then consumers would have
214    /// to deal with raw types.
215    pub fn plugin_register_add_hook_command<T: MediumHookCommand>(
216        &mut self,
217    ) -> ReaperFunctionResult<()> {
218        unsafe {
219            self.plugin_register_add(RegistrationObject::HookCommand(
220                delegating_hook_command::<T>,
221            ))?;
222        }
223        Ok(())
224    }
225
226    /// Unregisters a hook command.
227    pub fn plugin_register_remove_hook_command<T: MediumHookCommand>(&mut self) {
228        unsafe {
229            self.plugin_register_remove(RegistrationObject::HookCommand(
230                delegating_hook_command::<T>,
231            ));
232        }
233    }
234
235    /// Registers a toggle action.
236    ///
237    /// REAPER calls toggle actions whenever it wants to know the on/off state of an action.
238    ///
239    /// See [`plugin_register_add_hook_command()`](#method.plugin_register_add_hook_command) for an
240    /// example.
241    ///
242    /// # Errors
243    ///
244    /// Returns an error if the registration failed.
245    pub fn plugin_register_add_toggle_action<T: MediumToggleAction>(
246        &mut self,
247    ) -> ReaperFunctionResult<()> {
248        unsafe {
249            self.plugin_register_add(RegistrationObject::ToggleAction(
250                delegating_toggle_action::<T>,
251            ))?
252        };
253        Ok(())
254    }
255
256    /// Unregisters a toggle action.
257    pub fn plugin_register_remove_toggle_action<T: MediumToggleAction>(&mut self) {
258        unsafe {
259            self.plugin_register_remove(RegistrationObject::ToggleAction(
260                delegating_toggle_action::<T>,
261            ));
262        }
263    }
264
265    /// Registers a hook post command.
266    ///
267    /// REAPER calls hook post commands whenever an action of the main section has been performed.
268    ///
269    /// See [`plugin_register_add_hook_command()`](#method.plugin_register_add_hook_command) for an
270    /// example.
271    ///
272    /// # Errors
273    ///
274    /// Returns an error if the registration failed.
275    pub fn plugin_register_add_hook_post_command<T: MediumHookPostCommand>(
276        &mut self,
277    ) -> ReaperFunctionResult<()> {
278        unsafe {
279            self.plugin_register_add(RegistrationObject::HookPostCommand(
280                delegating_hook_post_command::<T>,
281            ))?
282        };
283        Ok(())
284    }
285
286    /// Unregisters a hook post command.
287    pub fn plugin_register_remove_hook_post_command<T: MediumHookPostCommand>(&mut self) {
288        unsafe {
289            self.plugin_register_remove(RegistrationObject::HookPostCommand(
290                delegating_hook_post_command::<T>,
291            ));
292        }
293    }
294
295    /// Registers a command ID for the given command name.
296    ///
297    /// The given command name must be a unique identifier with only A-Z and 0-9.
298    ///
299    /// Returns the assigned command ID, an ID which is guaranteed to be unique within the current
300    /// REAPER session. If the command name is already in use, it just seems to return the ID
301    /// which has been assigned before.
302    ///
303    /// # Errors
304    ///
305    /// Returns an error if the registration failed (e.g. because not supported or out of actions).
306    pub fn plugin_register_add_command_id<'a>(
307        &mut self,
308        command_name: impl Into<ReaperStringArg<'a>>,
309    ) -> ReaperFunctionResult<CommandId> {
310        let raw_id = unsafe {
311            self.plugin_register_add(RegistrationObject::CommandId(
312                command_name.into().into_inner(),
313            ))?
314        };
315        Ok(CommandId(raw_id as _))
316    }
317
318    /// Registers a an action into the main section.
319    ///
320    /// This consists of a command ID, a description and a default binding for it. It doesn't
321    /// include the actual code to be executed when the action runs (use
322    /// [`plugin_register_add_hook_command()`] for that).
323    ///
324    /// This function returns a handle which you can use to unregister the action at any time via
325    /// [`plugin_register_remove_gaccel()`].
326    ///
327    /// # Errors
328    ///
329    /// Returns an error if the registration failed.
330    ///
331    /// # Design
332    ///
333    /// This function takes ownership of the passed struct in order to take complete care of it.
334    /// Compared to the alternative of taking a reference or pointer, that releases the API
335    /// consumer from the responsibilities to guarantee a long enough lifetime and to maintain a
336    /// stable address in memory. Giving up ownership also means that the consumer doesn't have
337    /// access to the struct anymore - which is a good thing, because REAPER should be the new
338    /// rightful owner of this struct. Thanks to this we don't need to mark this function as
339    /// unsafe!
340    ///
341    /// [`plugin_register_add_hook_command()`]: #method.plugin_register_add_hook_command
342    /// [`plugin_register_remove_gaccel()`]: #method.plugin_register_remove_gaccel
343    pub fn plugin_register_add_gaccel(
344        &mut self,
345        register: MediumGaccelRegister,
346    ) -> ReaperFunctionResult<NonNull<raw::gaccel_register_t>> {
347        let handle = self.gaccel_registers.keep(register);
348        unsafe { self.plugin_register_add(RegistrationObject::Gaccel(handle))? };
349        Ok(handle)
350    }
351
352    /// Unregisters an action.
353    pub fn plugin_register_remove_gaccel(&mut self, handle: NonNull<raw::gaccel_register_t>) {
354        unsafe { self.plugin_register_remove(RegistrationObject::Gaccel(handle)) };
355    }
356
357    /// Registers a hidden control surface.
358    ///
359    /// This is very useful for being notified by REAPER about all kinds of events in the main
360    /// thread.
361    ///
362    /// This function returns a handle which you can use to unregister the control surface at any
363    /// time via [`plugin_register_remove_csurf_inst()`].
364    ///
365    /// # Errors
366    ///
367    /// Returns an error if the registration failed.
368    ///
369    /// # Example
370    ///
371    /// ```no_run
372    /// # let mut reaper = reaper_medium::Reaper::default();
373    /// use reaper_medium::MediumReaperControlSurface;
374    ///
375    /// #[derive(Debug)]
376    /// struct MyControlSurface;
377    ///
378    /// impl MediumReaperControlSurface for MyControlSurface {
379    ///     fn set_track_list_change(&self) {
380    ///         println!("Tracks changed");
381    ///     }
382    /// }
383    /// reaper.plugin_register_add_csurf_inst(MyControlSurface);
384    /// # Ok::<_, Box<dyn std::error::Error>>(())
385    /// ```
386    ///
387    /// [`plugin_register_remove_csurf_inst()`]: #method.plugin_register_remove_csurf_inst
388    pub fn plugin_register_add_csurf_inst(
389        &mut self,
390        control_surface: impl MediumReaperControlSurface + 'static,
391    ) -> ReaperFunctionResult<NonNull<raw::IReaperControlSurface>> {
392        let rust_control_surface =
393            DelegatingControlSurface::new(control_surface, &self.functions.get_app_version());
394        // We need to box it twice in order to obtain a thin pointer for passing to C as callback
395        // target
396        let rust_control_surface: Box<Box<dyn IReaperControlSurface>> =
397            Box::new(Box::new(rust_control_surface));
398        let cpp_control_surface =
399            unsafe { add_cpp_control_surface(rust_control_surface.as_ref().into()) };
400        self.csurf_insts
401            .insert(cpp_control_surface, rust_control_surface);
402        unsafe { self.plugin_register_add(RegistrationObject::CsurfInst(cpp_control_surface))? };
403        Ok(cpp_control_surface)
404    }
405
406    /// Unregisters a hidden control surface.
407    pub fn plugin_register_remove_csurf_inst(
408        &mut self,
409        handle: NonNull<raw::IReaperControlSurface>,
410    ) {
411        unsafe {
412            self.plugin_register_remove(RegistrationObject::CsurfInst(handle));
413        }
414        self.csurf_insts.remove(&handle);
415        unsafe {
416            remove_cpp_control_surface(handle);
417        }
418    }
419
420    /// Like [`audio_reg_hardware_hook_add`] but doesn't manage memory for you.
421    ///
422    /// Also see [`audio_reg_hardware_hook_remove_unchecked()`].
423    ///
424    /// # Errors
425    ///
426    /// Returns an error if the registration failed.
427    ///
428    /// # Safety
429    ///
430    /// REAPER can crash if you pass an invalid pointer or if it dangles during the time it
431    /// is registered. So you must ensure that the audio hook register lives long enough and
432    /// has a stable address in memory. Additionally, incorrectly accessing the audio hook register
433    /// while it is registered can lead to horrible race conditions and other undefined
434    /// behavior.
435    ///
436    /// [`audio_reg_hardware_hook_remove_unchecked()`]:
437    /// #method.audio_reg_hardware_hook_remove_unchecked
438    /// [`audio_reg_hardware_hook_add`]: #method.audio_reg_hardware_hook_add
439    pub unsafe fn audio_reg_hardware_hook_add_unchecked(
440        &mut self,
441        register: NonNull<audio_hook_register_t>,
442    ) -> ReaperFunctionResult<()> {
443        self.audio_hook_registrations.insert(register);
444        let result = self
445            .functions
446            .low()
447            .Audio_RegHardwareHook(true, register.as_ptr());
448        if result == 0 {
449            return Err(ReaperFunctionError::new("couldn't register audio hook"));
450        }
451        Ok(())
452    }
453
454    /// Unregisters the audio hook register that you have registered with
455    /// [`audio_reg_hardware_hook_add_unchecked()`].
456    ///
457    /// Please note that unregistering audio hook registers manually just for cleaning up is
458    /// unnecessary in most situations because *reaper-rs* takes care of automatically
459    /// unregistering everything when this struct is dropped (RAII). This happens even when using
460    /// the unsafe function variants.
461    ///
462    /// # Safety
463    ///
464    /// REAPER can crash if you pass an invalid pointer.
465    ///
466    /// [`audio_reg_hardware_hook_add_unchecked()`]: #method.audio_reg_hardware_hook_add_unchecked
467    pub unsafe fn audio_reg_hardware_hook_remove_unchecked(
468        &mut self,
469        register: NonNull<audio_hook_register_t>,
470    ) {
471        self.functions
472            .low()
473            .Audio_RegHardwareHook(false, register.as_ptr());
474        self.audio_hook_registrations.remove(&register);
475    }
476
477    /// Registers an audio hook register.
478    ///
479    /// This allows you to get called back in the real-time audio thread before and after REAPER's
480    /// processing. You should be careful with this because you are entering real-time world.
481    ///
482    /// This function returns a handle which you can use to unregister the audio hook register at
483    /// any time via [`audio_reg_hardware_hook_remove()`] (from the main thread).
484    ///
485    /// # Errors
486    ///
487    /// Returns an error if the registration failed.
488    ///
489    /// # Example
490    ///
491    /// ```no_run
492    /// # let mut reaper = reaper_medium::Reaper::default();
493    /// use reaper_medium::{
494    ///     MediumReaperControlSurface, MediumOnAudioBuffer, OnAudioBufferArgs,
495    ///     ReaperFunctions, RealTimeAudioThreadScope, MidiInputDeviceId
496    /// };
497    ///
498    /// struct MyOnAudioBuffer {
499    ///     counter: u64,
500    ///     functions: ReaperFunctions<RealTimeAudioThreadScope>,
501    /// }
502    ///
503    /// impl MediumOnAudioBuffer for MyOnAudioBuffer {
504    ///     fn call(&mut self, args: OnAudioBufferArgs) {
505    ///         // Mutate some own state (safe because we are the owner)
506    ///         if self.counter % 100 == 0 {
507    ///             println!("Audio hook callback counter: {}\n", self.counter);
508    ///         }
509    ///         self.counter += 1;
510    ///         // Read some MIDI events
511    ///         self.functions.get_midi_input(MidiInputDeviceId::new(0), |input| {
512    ///             for event in input.get_read_buf().enum_items(0) {
513    ///                 println!("Received MIDI event {:?}", event);
514    ///             }   
515    ///         });
516    ///     }
517    /// }
518    ///
519    /// reaper.audio_reg_hardware_hook_add(MyOnAudioBuffer {
520    ///     counter: 0,
521    ///     functions: reaper.create_real_time_functions()
522    /// });
523    /// # Ok::<_, Box<dyn std::error::Error>>(())
524    /// ```
525    ///
526    /// [`audio_reg_hardware_hook_remove()`]: #method.audio_reg_hardware_hook_remove
527    pub fn audio_reg_hardware_hook_add<T: MediumOnAudioBuffer + 'static>(
528        &mut self,
529        callback: T,
530    ) -> ReaperFunctionResult<NonNull<audio_hook_register_t>> {
531        let handle = self
532            .audio_hook_registers
533            .keep(MediumAudioHookRegister::new(callback));
534        unsafe { self.audio_reg_hardware_hook_add_unchecked(handle)? };
535        Ok(handle)
536    }
537
538    /// Unregisters an audio hook register.
539    pub fn audio_reg_hardware_hook_remove(&mut self, handle: NonNull<audio_hook_register_t>) {
540        unsafe { self.audio_reg_hardware_hook_remove_unchecked(handle) };
541        let _ = self.audio_hook_registers.release(handle);
542    }
543}
544
545impl Drop for Reaper {
546    fn drop(&mut self) {
547        for handle in self.audio_hook_registrations.clone() {
548            unsafe {
549                self.audio_reg_hardware_hook_remove_unchecked(handle);
550            }
551        }
552        for reg in self.plugin_registrations.clone() {
553            unsafe {
554                self.plugin_register_remove(reg);
555            }
556        }
557    }
558}