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(®ister);
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}