reaper-medium 0.1.0

Bindings for the REAPER C++ API - medium-level API
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
use c_str_macro::c_str;

use std::ptr::NonNull;

use reaper_low::{
    add_cpp_control_surface, raw, remove_cpp_control_surface, IReaperControlSurface,
    ReaperPluginContext,
};

use crate::infostruct_keeper::InfostructKeeper;

use crate::{
    concat_c_strs, delegating_hook_command, delegating_hook_post_command, delegating_toggle_action,
    CommandId, DelegatingControlSurface, MainThreadScope, MediumAudioHookRegister,
    MediumGaccelRegister, MediumHookCommand, MediumHookPostCommand, MediumOnAudioBuffer,
    MediumReaperControlSurface, MediumToggleAction, RealTimeAudioThreadScope, ReaperFunctionError,
    ReaperFunctionResult, ReaperFunctions, ReaperStringArg, RegistrationObject,
};
use reaper_low;
use reaper_low::raw::audio_hook_register_t;
use std::collections::{HashMap, HashSet};

/// This is the main hub for accessing medium-level API functions.
///
/// In order to use this struct, you first must obtain an instance of it by invoking [`new()`]
/// or [`load()`].
/// This struct itself is limited to REAPER functions for registering/unregistering certain things.
/// You can access all the other functions by calling [`functions()`].
///
/// Please note that this struct will take care of unregistering everything (also audio hooks)
/// automatically when it gets dropped (good RAII manners).
///
/// # Design
///
/// ## Why is there a separation into `Reaper` and `ReaperFunctions`?
///
/// Functions for registering/unregistering things have been separated from the rest because they
/// require more than just access to REAPER function pointers. They also need data structures to
/// keep track of the registered things and to offer them a warm and cosy place in memory. As a
/// result, this struct gains special importance, needs to be mutable and can't just be cloned as
/// desired. But there's no reason why this restriction should also apply to all other REAPER
/// functions. After all, being able to clone and pass things around freely can simplify things a
/// lot.
///
/// ### Example
///
/// Here's an example how things can get difficult without the ability to clone: In order to be able
/// to use REAPER functions also from e.g. the audio hook register, we would need to wrap it in an
/// `Arc` (not an `Rc`, because we access it from multiple threads). That's not enough though for
/// most real-world cases. We probably want to register/unregister things (in the main thread) not
/// only in the beginning but also at a later time. That means we need mutable access. So we end up
/// with `Arc<Mutex<Reaper>>`. However, why going through all that trouble and put up with possible
/// performance issues if we can avoid it?
///
/// [`new()`]: #method.new
/// [`load()`]: #method.load
/// [`functions()`]: #method.functions
#[derive(Debug, Default)]
pub struct Reaper {
    functions: ReaperFunctions<MainThreadScope>,
    gaccel_registers: InfostructKeeper<MediumGaccelRegister, raw::gaccel_register_t>,
    audio_hook_registers: InfostructKeeper<MediumAudioHookRegister, raw::audio_hook_register_t>,
    csurf_insts: HashMap<NonNull<raw::IReaperControlSurface>, Box<Box<dyn IReaperControlSurface>>>,
    plugin_registrations: HashSet<RegistrationObject<'static>>,
    audio_hook_registrations: HashSet<NonNull<raw::audio_hook_register_t>>,
}

impl Reaper {
    /// Creates a new instance by getting hold of a [low-level `Reaper`] instance.
    ///
    /// [low-level `Reaper`]: /reaper_low/struct.Reaper.html
    pub fn new(low: reaper_low::Reaper) -> Reaper {
        Reaper {
            functions: ReaperFunctions::new(low),
            gaccel_registers: Default::default(),
            audio_hook_registers: Default::default(),
            csurf_insts: Default::default(),
            plugin_registrations: Default::default(),
            audio_hook_registrations: Default::default(),
        }
    }

    /// Loads all available REAPER functions from the given plug-in context.
    ///
    /// Returns a medium-level `Reaper` instance which allows you to call these functions.
    pub fn load(context: &ReaperPluginContext) -> Reaper {
        let low = reaper_low::Reaper::load(context);
        Reaper::new(low)
    }

    /// Gives access to all REAPER functions which can be safely executed in the main thread.
    pub fn functions(&self) -> &ReaperFunctions<MainThreadScope> {
        &self.functions
    }

    /// Creates a new container of REAPER functions with only those unlocked that can be safely
    /// executed in the real-time audio thread.
    pub fn create_real_time_functions(&self) -> ReaperFunctions<RealTimeAudioThreadScope> {
        ReaperFunctions::new(self.functions.low().clone())
    }

    /// This is the primary function for plug-ins to register things.
    ///
    /// *Things* can be keyboard shortcuts, project importers etc. Typically you register things
    /// when the plug-in is loaded.
    ///
    /// It is not recommended to use this function directly because it's unsafe. Consider using
    /// the safe convenience functions instead. They all start with `plugin_register_add_`.
    ///
    /// The meaning of the return value depends very much on the actual thing being registered. In
    /// most cases it just returns 1. In any case it's not 0, *reaper-rs* translates this into an
    /// error.
    ///
    /// Also see [`plugin_register_remove()`].
    ///
    /// # Errors
    ///
    /// Returns an error if the registration failed.
    ///
    /// # Safety
    ///
    /// REAPER can crash if you pass an invalid pointer or if it dangles during the time it
    /// is registered. So you must ensure that the registered thing lives long enough and
    /// has a stable address in memory. Additionally, mutation of the thing while it is registered
    /// can lead to subtle bugs.
    ///
    /// [`plugin_register_remove()`]: #method.plugin_register_remove
    pub unsafe fn plugin_register_add(
        &mut self,
        object: RegistrationObject,
    ) -> ReaperFunctionResult<i32> {
        self.plugin_registrations
            .insert(object.clone().into_owned());
        let infostruct = object.ptr_to_raw();
        let result = self
            .functions
            .low()
            .plugin_register(object.key_into_raw().as_ptr(), infostruct);
        if result == 0 {
            return Err(ReaperFunctionError::new("couldn't register thing"));
        }
        Ok(result)
    }

    /// Unregisters things that you have registered with [`plugin_register_add()`].
    ///
    /// Please note that unregistering things manually just for cleaning up is unnecessary in most
    /// situations because *reaper-rs* takes care of automatically unregistering everything when
    /// this struct is dropped (RAII). This happens even when using the unsafe function variants.
    ///
    /// # Safety
    ///
    /// REAPER can crash if you pass an invalid pointer.
    ///
    /// [`plugin_register_add()`]: #method.plugin_register_add
    pub unsafe fn plugin_register_remove(&mut self, object: RegistrationObject) -> i32 {
        let infostruct = object.ptr_to_raw();
        let name_with_minus = concat_c_strs(c_str!("-"), object.clone().key_into_raw().as_ref());
        let result = self
            .functions
            .low()
            .plugin_register(name_with_minus.as_ptr(), infostruct);
        self.plugin_registrations.remove(&object.into_owned());
        result
    }

    /// Registers a hook command.
    ///
    /// REAPER calls hook commands whenever an action is requested to be run.
    ///
    /// This method doesn't take a closure because REAPER expects a plain function pointer here.
    /// Unlike [`audio_reg_hardware_hook_add`](#method.audio_reg_hardware_hook_add), REAPER
    /// doesn't offer the possibiity to pass a context to the function. So we can't access any
    /// context data in the hook command. You will probably have to use a kind of static
    /// variable which contains command IDs in order to make proper use of this method. The
    /// high-level API makes that much easier (it just takes an arbitrary closure). For the
    /// medium-level API this is out of scope.
    ///
    /// # Errors
    ///
    /// Returns an error if the registration failed.
    ///
    /// # Example
    ///
    /// ```no_run
    /// # let mut reaper = reaper_medium::Reaper::default();
    /// use reaper_medium::{MediumHookCommand, CommandId};
    ///
    /// // Usually you would use a dynamic command ID that you have obtained via
    /// // `plugin_register_add_command_id()`. Unfortunately that command ID must be exposed as
    /// // a static variable. The high-level API provides a solution for that.
    /// const MY_COMMAND_ID: CommandId = unsafe { CommandId::new_unchecked(42000) };
    ///
    /// struct MyHookCommand;
    ///
    /// impl MediumHookCommand for MyHookCommand {
    ///     fn call(command_id: CommandId, _flag: i32) -> bool {
    ///         if command_id != MY_COMMAND_ID {
    ///             return false;
    ///         }           
    ///         println!("Executing my command!");
    ///         true
    ///     }
    /// }
    /// reaper.plugin_register_add_hook_command::<MyHookCommand>();
    /// # Ok::<_, Box<dyn std::error::Error>>(())
    /// ```
    ///
    /// # Design
    ///
    /// You will note that this method has a somewhat strange signature: It expects a type parameter
    /// only, not a function pointer. That allows us to lift the API to medium-level style.
    /// The alternative would have been to expect a function pointer, but then consumers would have
    /// to deal with raw types.
    pub fn plugin_register_add_hook_command<T: MediumHookCommand>(
        &mut self,
    ) -> ReaperFunctionResult<()> {
        unsafe {
            self.plugin_register_add(RegistrationObject::HookCommand(
                delegating_hook_command::<T>,
            ))?;
        }
        Ok(())
    }

    /// Unregisters a hook command.
    pub fn plugin_register_remove_hook_command<T: MediumHookCommand>(&mut self) {
        unsafe {
            self.plugin_register_remove(RegistrationObject::HookCommand(
                delegating_hook_command::<T>,
            ));
        }
    }

    /// Registers a toggle action.
    ///
    /// REAPER calls toggle actions whenever it wants to know the on/off state of an action.
    ///
    /// See [`plugin_register_add_hook_command()`](#method.plugin_register_add_hook_command) for an
    /// example.
    ///
    /// # Errors
    ///
    /// Returns an error if the registration failed.
    pub fn plugin_register_add_toggle_action<T: MediumToggleAction>(
        &mut self,
    ) -> ReaperFunctionResult<()> {
        unsafe {
            self.plugin_register_add(RegistrationObject::ToggleAction(
                delegating_toggle_action::<T>,
            ))?
        };
        Ok(())
    }

    /// Unregisters a toggle action.
    pub fn plugin_register_remove_toggle_action<T: MediumToggleAction>(&mut self) {
        unsafe {
            self.plugin_register_remove(RegistrationObject::ToggleAction(
                delegating_toggle_action::<T>,
            ));
        }
    }

    /// Registers a hook post command.
    ///
    /// REAPER calls hook post commands whenever an action of the main section has been performed.
    ///
    /// See [`plugin_register_add_hook_command()`](#method.plugin_register_add_hook_command) for an
    /// example.
    ///
    /// # Errors
    ///
    /// Returns an error if the registration failed.
    pub fn plugin_register_add_hook_post_command<T: MediumHookPostCommand>(
        &mut self,
    ) -> ReaperFunctionResult<()> {
        unsafe {
            self.plugin_register_add(RegistrationObject::HookPostCommand(
                delegating_hook_post_command::<T>,
            ))?
        };
        Ok(())
    }

    /// Unregisters a hook post command.
    pub fn plugin_register_remove_hook_post_command<T: MediumHookPostCommand>(&mut self) {
        unsafe {
            self.plugin_register_remove(RegistrationObject::HookPostCommand(
                delegating_hook_post_command::<T>,
            ));
        }
    }

    /// Registers a command ID for the given command name.
    ///
    /// The given command name must be a unique identifier with only A-Z and 0-9.
    ///
    /// Returns the assigned command ID, an ID which is guaranteed to be unique within the current
    /// REAPER session. If the command name is already in use, it just seems to return the ID
    /// which has been assigned before.
    ///
    /// # Errors
    ///
    /// Returns an error if the registration failed (e.g. because not supported or out of actions).
    pub fn plugin_register_add_command_id<'a>(
        &mut self,
        command_name: impl Into<ReaperStringArg<'a>>,
    ) -> ReaperFunctionResult<CommandId> {
        let raw_id = unsafe {
            self.plugin_register_add(RegistrationObject::CommandId(
                command_name.into().into_inner(),
            ))?
        };
        Ok(CommandId(raw_id as _))
    }

    /// Registers a an action into the main section.
    ///
    /// This consists of a command ID, a description and a default binding for it. It doesn't
    /// include the actual code to be executed when the action runs (use
    /// [`plugin_register_add_hook_command()`] for that).
    ///
    /// This function returns a handle which you can use to unregister the action at any time via
    /// [`plugin_register_remove_gaccel()`].
    ///
    /// # Errors
    ///
    /// Returns an error if the registration failed.
    ///
    /// # Design
    ///
    /// This function takes ownership of the passed struct in order to take complete care of it.
    /// Compared to the alternative of taking a reference or pointer, that releases the API
    /// consumer from the responsibilities to guarantee a long enough lifetime and to maintain a
    /// stable address in memory. Giving up ownership also means that the consumer doesn't have
    /// access to the struct anymore - which is a good thing, because REAPER should be the new
    /// rightful owner of this struct. Thanks to this we don't need to mark this function as
    /// unsafe!
    ///
    /// [`plugin_register_add_hook_command()`]: #method.plugin_register_add_hook_command
    /// [`plugin_register_remove_gaccel()`]: #method.plugin_register_remove_gaccel
    pub fn plugin_register_add_gaccel(
        &mut self,
        register: MediumGaccelRegister,
    ) -> ReaperFunctionResult<NonNull<raw::gaccel_register_t>> {
        let handle = self.gaccel_registers.keep(register);
        unsafe { self.plugin_register_add(RegistrationObject::Gaccel(handle))? };
        Ok(handle)
    }

    /// Unregisters an action.
    pub fn plugin_register_remove_gaccel(&mut self, handle: NonNull<raw::gaccel_register_t>) {
        unsafe { self.plugin_register_remove(RegistrationObject::Gaccel(handle)) };
    }

    /// Registers a hidden control surface.
    ///
    /// This is very useful for being notified by REAPER about all kinds of events in the main
    /// thread.
    ///
    /// This function returns a handle which you can use to unregister the control surface at any
    /// time via [`plugin_register_remove_csurf_inst()`].
    ///
    /// # Errors
    ///
    /// Returns an error if the registration failed.
    ///
    /// # Example
    ///
    /// ```no_run
    /// # let mut reaper = reaper_medium::Reaper::default();
    /// use reaper_medium::MediumReaperControlSurface;
    ///
    /// #[derive(Debug)]
    /// struct MyControlSurface;
    ///
    /// impl MediumReaperControlSurface for MyControlSurface {
    ///     fn set_track_list_change(&self) {
    ///         println!("Tracks changed");
    ///     }
    /// }
    /// reaper.plugin_register_add_csurf_inst(MyControlSurface);
    /// # Ok::<_, Box<dyn std::error::Error>>(())
    /// ```
    ///
    /// [`plugin_register_remove_csurf_inst()`]: #method.plugin_register_remove_csurf_inst
    pub fn plugin_register_add_csurf_inst(
        &mut self,
        control_surface: impl MediumReaperControlSurface + 'static,
    ) -> ReaperFunctionResult<NonNull<raw::IReaperControlSurface>> {
        let rust_control_surface =
            DelegatingControlSurface::new(control_surface, &self.functions.get_app_version());
        // We need to box it twice in order to obtain a thin pointer for passing to C as callback
        // target
        let rust_control_surface: Box<Box<dyn IReaperControlSurface>> =
            Box::new(Box::new(rust_control_surface));
        let cpp_control_surface =
            unsafe { add_cpp_control_surface(rust_control_surface.as_ref().into()) };
        self.csurf_insts
            .insert(cpp_control_surface, rust_control_surface);
        unsafe { self.plugin_register_add(RegistrationObject::CsurfInst(cpp_control_surface))? };
        Ok(cpp_control_surface)
    }

    /// Unregisters a hidden control surface.
    pub fn plugin_register_remove_csurf_inst(
        &mut self,
        handle: NonNull<raw::IReaperControlSurface>,
    ) {
        unsafe {
            self.plugin_register_remove(RegistrationObject::CsurfInst(handle));
        }
        self.csurf_insts.remove(&handle);
        unsafe {
            remove_cpp_control_surface(handle);
        }
    }

    /// Like [`audio_reg_hardware_hook_add`] but doesn't manage memory for you.
    ///
    /// Also see [`audio_reg_hardware_hook_remove_unchecked()`].
    ///
    /// # Errors
    ///
    /// Returns an error if the registration failed.
    ///
    /// # Safety
    ///
    /// REAPER can crash if you pass an invalid pointer or if it dangles during the time it
    /// is registered. So you must ensure that the audio hook register lives long enough and
    /// has a stable address in memory. Additionally, incorrectly accessing the audio hook register
    /// while it is registered can lead to horrible race conditions and other undefined
    /// behavior.
    ///
    /// [`audio_reg_hardware_hook_remove_unchecked()`]:
    /// #method.audio_reg_hardware_hook_remove_unchecked
    /// [`audio_reg_hardware_hook_add`]: #method.audio_reg_hardware_hook_add
    pub unsafe fn audio_reg_hardware_hook_add_unchecked(
        &mut self,
        register: NonNull<audio_hook_register_t>,
    ) -> ReaperFunctionResult<()> {
        self.audio_hook_registrations.insert(register);
        let result = self
            .functions
            .low()
            .Audio_RegHardwareHook(true, register.as_ptr());
        if result == 0 {
            return Err(ReaperFunctionError::new("couldn't register audio hook"));
        }
        Ok(())
    }

    /// Unregisters the audio hook register that you have registered with
    /// [`audio_reg_hardware_hook_add_unchecked()`].
    ///
    /// Please note that unregistering audio hook registers manually just for cleaning up is
    /// unnecessary in most situations because *reaper-rs* takes care of automatically
    /// unregistering everything when this struct is dropped (RAII). This happens even when using
    /// the unsafe function variants.
    ///
    /// # Safety
    ///
    /// REAPER can crash if you pass an invalid pointer.
    ///
    /// [`audio_reg_hardware_hook_add_unchecked()`]: #method.audio_reg_hardware_hook_add_unchecked
    pub unsafe fn audio_reg_hardware_hook_remove_unchecked(
        &mut self,
        register: NonNull<audio_hook_register_t>,
    ) {
        self.functions
            .low()
            .Audio_RegHardwareHook(false, register.as_ptr());
        self.audio_hook_registrations.remove(&register);
    }

    /// Registers an audio hook register.
    ///
    /// This allows you to get called back in the real-time audio thread before and after REAPER's
    /// processing. You should be careful with this because you are entering real-time world.
    ///
    /// This function returns a handle which you can use to unregister the audio hook register at
    /// any time via [`audio_reg_hardware_hook_remove()`] (from the main thread).
    ///
    /// # Errors
    ///
    /// Returns an error if the registration failed.
    ///
    /// # Example
    ///
    /// ```no_run
    /// # let mut reaper = reaper_medium::Reaper::default();
    /// use reaper_medium::{
    ///     MediumReaperControlSurface, MediumOnAudioBuffer, OnAudioBufferArgs,
    ///     ReaperFunctions, RealTimeAudioThreadScope, MidiInputDeviceId
    /// };
    ///
    /// struct MyOnAudioBuffer {
    ///     counter: u64,
    ///     functions: ReaperFunctions<RealTimeAudioThreadScope>,
    /// }
    ///
    /// impl MediumOnAudioBuffer for MyOnAudioBuffer {
    ///     fn call(&mut self, args: OnAudioBufferArgs) {
    ///         // Mutate some own state (safe because we are the owner)
    ///         if self.counter % 100 == 0 {
    ///             println!("Audio hook callback counter: {}\n", self.counter);
    ///         }
    ///         self.counter += 1;
    ///         // Read some MIDI events
    ///         self.functions.get_midi_input(MidiInputDeviceId::new(0), |input| {
    ///             for event in input.get_read_buf().enum_items(0) {
    ///                 println!("Received MIDI event {:?}", event);
    ///             }   
    ///         });
    ///     }
    /// }
    ///
    /// reaper.audio_reg_hardware_hook_add(MyOnAudioBuffer {
    ///     counter: 0,
    ///     functions: reaper.create_real_time_functions()
    /// });
    /// # Ok::<_, Box<dyn std::error::Error>>(())
    /// ```
    ///
    /// [`audio_reg_hardware_hook_remove()`]: #method.audio_reg_hardware_hook_remove
    pub fn audio_reg_hardware_hook_add<T: MediumOnAudioBuffer + 'static>(
        &mut self,
        callback: T,
    ) -> ReaperFunctionResult<NonNull<audio_hook_register_t>> {
        let handle = self
            .audio_hook_registers
            .keep(MediumAudioHookRegister::new(callback));
        unsafe { self.audio_reg_hardware_hook_add_unchecked(handle)? };
        Ok(handle)
    }

    /// Unregisters an audio hook register.
    pub fn audio_reg_hardware_hook_remove(&mut self, handle: NonNull<audio_hook_register_t>) {
        unsafe { self.audio_reg_hardware_hook_remove_unchecked(handle) };
        let _ = self.audio_hook_registers.release(handle);
    }
}

impl Drop for Reaper {
    fn drop(&mut self) {
        for handle in self.audio_hook_registrations.clone() {
            unsafe {
                self.audio_reg_hardware_hook_remove_unchecked(handle);
            }
        }
        for reg in self.plugin_registrations.clone() {
            unsafe {
                self.plugin_register_remove(reg);
            }
        }
    }
}