Expand description

This crate contains the medium-level API of reaper-rs.

To get started, have a look at the ReaperSession struct.

General usage hints

  • Whenever you find an identifier in this crate that ends with index, you can assume it’s a zero-based integer. That means the first index is 0, not 1!

Example

use reaper_medium::ProjectContext::CurrentProject;

let reaper = session.reaper();
reaper.show_console_msg("Hello world from reaper-rs medium-level API!");
let track = reaper.get_track(CurrentProject, 0).ok_or("no tracks")?;
unsafe { reaper.delete_track(track); }

Design goals

The ultimate goal of the medium-level API is to provide all functions offered by the low-level API, but in an idiomatic and type-safe way. The result is still a plain list of functions, where each function is basically named like its original. Going all object-oriented, using reactive extensions, introducing a fluid API, finding function names that make more sense … all of that is intentionally out of scope. The medium-level API is intended to stay close to the original API. This has the benefit that ReaScript (e.g. Lua) and C++ code seen in forum threads, blogs and existing extensions can be helpful even for writing plug-ins in Rust.

Design principles

In order to achieve these goals, this API follows a bunch of design principles.

Follow Rust naming conventions

Most low-level functions and types don’t follow the Rust naming conventions. We adjust them accordingly while still staying as close as possible to the original names.

Use unsigned integers where appropriate

We don’t use signed integers when it’s totally clear that a number can never be negative. Example: insert_track_at_index()

Use enums where appropriate

We want more type safety and more readable code. Enums can contribute to that a lot. Here’s how we use them:

  1. If the original function uses an integer which represents a limited set of options that can be easily named, we introduce an enum. Example: get_track_automation_mode(), AutomationMode

  2. If the original function uses a string and there’s a clear set of predefined options, we introduce an enum. Example: get_media_track_info_value(), TrackAttributeKey

  3. If the original function uses a bool and the name of the function doesn’t give that bool meaning, introduce an enum. Example: set_current_bpm(), UndoBehavior

  4. If the original function can have different mutually exclusive results, introduce an enum. Example: get_last_touched_fx(), GetLastTouchedFxResult

  5. If the original function has several parameters of which only certain combinations are valid, introduce an enum for combining those. Example: kbd_on_main_action_ex(), ActionValueChange

  6. If the original function takes a parameter which describes how another parameter is interpreted, introduce an enum. Example: csurf_on_pan_change_ex(), ValueChange

  7. If the original function takes an optional value and one cannot conclude from the function name what a None would mean, introduce an enum. Example: count_tracks(), ProjectContext

The first design didn’t have many enums. Then, with every enum introduced in the medium-level API, the high-level API code was getting cleaner, more understandable and often even shorter. More importantly, some API usage bugs suddenly became obvious!

Adjust return types where appropriate

  1. Use bool instead of i32 as return value type for “yes or no” functions. Example: is_in_real_time_audio()
  2. Use return values instead of output parameters. Example: gen_guid()
  3. If a function has multiple results, introduce and return a struct for aggregating them. Example: get_focused_fx()
  4. If a function can return a value which represents that something is not present, return an Option. Example: named_command_lookup()

Use newtypes where appropriate

  1. If a value represents an ID, introduce a newtype. Example: CommandId
  2. If a number value is restricted in its value range, represents a mathematical unit or can be easily confused, consider introducing a meaningful newtype. Example: ReaperVolumeValue

We don’t use newtypes for numbers that represent indexes.

Use convenience functions where necessary

In general, the medium-level API shouldn’t have too much additional magic and convenience. However, there are some low-level functions which are true allrounders. With allrounders it’s often difficult to find accurate signatures and impossible to avoid unsafe. Adding multiple convenience functions can sometimes help with that, at least with making them a bit more safe to use. Examples: get_set_media_track_info(), plugin_register_add_command_id()

Make it easy to work with strings

  • String parameters are used as described in ReaperStringArg. Example: string_to_guid()
  • Strings in return positions are dealt with in different ways:
    • When returning an owned string, we return ReaperString. Consumers can easily convert them to regular Rust strings when needed. Example: guid_to_string()
    • When returning a string owned by REAPER and we know that string has a static lifetime, we return a 'static reference. Example: get_app_version()
    • When returning a string owned by REAPER and we can’t give it a proper lifetime annotation (in most cases we can’t), we grant the user only temporary access to that string by taking a closure with a &ReaperStr argument which is executed right away. Example: undo_can_undo_2()
  • Strings in enums are often Cow<ReaperStr> because we want them to be flexible enough to carry both owned and borrowed strings.

Use pointer wrappers where appropriate

When we deal with REAPER, we have to deal with pointers. REAPER often returns pointers and we can’t give them a sane lifetime annotation. Depending on the type of plug-in and the type of pointer, some are rather static from the perspective of the plug-in and others can come and go anytime. In any case, just turning them into 'static references would be plain wrong. At the same time, annotating them with a bounded lifetime 'a (correlated to another lifetime) is often impossible either, because mostly we don’t have another lifetime at the disposal which can serve as frame of reference.

In most cases the best we can do is passing pointers around. How exactly this is done, depends on the characteristics of the pointed-to struct and how it is going to be used.

Case 1: Internals not exposed | no vtable

Strategy
  • Use NonNull pointers directly
  • Make them more accessible by introducing an alias
Explanation

Such structs are relevant for the consumers as pointers only. Because they are completely opaque (internals not exposed, not even a vtable) and we never own them. We don’t create a newtype because the NonNull guarantee is all we need and we will never provide any methods on them (no vtable emulation, no convenience methods). Using a wrapper just for reasons of symmetry would not be good because it comes with a cost (more code to write, less substitution possibilities) but in this case without any benefit.

We use pointers instead of references because there’s no way we could name a lifetime. REAPER itself provides functions for checking if those pointers are still valid.

Examples

Case 2: Internals exposed | no vtable

Strategy
  • Don’t create an alias for a NonNull pointer! In situations where just the pointer is interesting and not the internals, write NonNull<...> everywhere. Reason: We need the alias name for a newtype that’s going to be used in situations where the internals matter.
  • If the consumer shall get access to the internals: Create a newtype that wraps a value of this struct (not a reference and not a pointer!). By using refcast, we can use this newtype not just for owned values but also for borrowed ones (by taking the newtype by reference, as one would expect). For borrowed structs, it won’t matter if they are owned by REAPER or by our Rust code. Previously, the guideline was to create an extra newtype for borrowed usage that would wrap the NonNull pointer, but this is unnecessary now.
  • The newtype should expose the internals in a way which is idiomatic for Rust (like the rest of the medium-level API does).
  • If the consumer needs to be able to create such a struct: Provide an idiomatic Rust factory function on the newtype.
  • In situations where the raw struct is not completely owned (contains pointers itself) and we need a completely owned version, add an owned version of that struct, prefixed with Owned. Ideally it should wrap the raw struct. That way, the owned struct can conveniently deref to the borrowed struct.
Explanation

Unlike raw::MediaTrack and friends, these structs are not opaque. Also, we might create and own such values.

Up-to-date examples (using ref-cast)
Legacy examples (NonNull pointer wrappers with separation into borrowed and owned versions)

Case 3: Internals not exposed | vtable

Strategy
  • Create an alias for a NonNull pointer!
  • If the consumer shall get access to the virtual functions: Wrap low-level struct in a public newtype starting with Borrowed and use RefCast. This newtype should expose the virtual functions in a way which is idiomatic for Rust. It’s intended for the communication from Rust to REAPER. This needs appropriate companion C code in the low-level API.
  • If the consumer shall be able to own the type, introduce another newtype starting with Owned that wraps a NonNull pointer.
  • If the consumer needs to be able to provide such a type (for communication from REAPER to Rust): Create a new trait which can be implemented by the consumer. This also needs appropriate companion C code in the low-level API.
Up-to-date examples (using ref-cast)
Legacy examples

Panic/error/safety strategy

  • We panic if a REAPER function is not available, e.g. because it’s an older REAPER version. Rationale: If all function signatures would be cluttered up with Results, it would be an absolute nightmare to use the API. It’s also not necessary: The consumer can always check if the function is there, and mostly it is (see reaper_low::Reaper).
  • We panic when passed parameters don’t satisfy documented preconditions which can be easily satisfied by consumers. Rationale: This represents incorrect API usage.
    • Luckily, the need for precondition checks is mitigated by using lots of newtypes and enums, which don’t allow parameters to be out of range in the first place. Example: track_fx_get_fx_name()
  • When a function takes pointers, we generally mark it as unsafe. Rationale: Pointers can dangle (e.g. a pointer to a track dangles as soon as that track is removed). Passing a dangling pointer to a REAPER function can and often will make REAPER crash. Example: delete_track()
    • That’s a bit unfortunate, but unavoidable given the medium-level APIs design goal to stay close to the original API. The unsafe is a hint to the consumer to be extra careful with those functions.

    • The consumer has ways to ensure that the passed pointer is valid:

      1. Using obtained pointers right away instead of caching them (preferred)

      2. Using validate_ptr_2() to check if the cached pointer is still valid.

      3. Using a hidden control surface to be informed whenever e.g. a MediaTrack is removed and invalidating the cached pointer accordingly.

  • There’s one exception to this: If the parameters passed to the function in question are enough to check whether the pointer is still valid, we do it, right in that function. If it’s invalid, we panic. We use validate_ptr_2() to check the pointer. Sadly, for all but project pointers it needs a project context to be able to validate a pointer. Otherwise we could apply this rule much more. Rationale: This allows us to remove the unsafe (if there was no other reason for it). That’s not ideal either but it’s far better than undefined behavior. Failing fast without crashing is one of the main design principles of reaper-rs. Because checking the pointer is an “extra” thing that the medium-level API does, we also offer an unsafe _unchecked variant of the same function, which doesn’t do the check. Example: count_tracks() and count_tracks_unchecked()
  • If a REAPER function can return a value which represents that execution was not successful, return a Result. Example: string_to_guid()

Verdict: Making the API completely safe to use can’t be done in the medium-level API. But it can be done in the high-level API because it’s not tied to the original REAPER flat function signatures. For example, there could be a Track struct which holds a ReaProject pointer, the track index and the track’s GUID. With that combination it’s possible to detect reliably whether a track is still existing. Needless to say, this is far too opinionated for the medium-level API.

Try to follow “zero-cost” principle

If someone uses C++ or Rust instead of just settling with ReaScript, chances are that better performance is at least one of the reasons. The medium-level API acknowledges that and tries to be very careful not to introduce possibly performance-harming indirections. In general it shouldn’t do extra stuff. Just the things which are absolutely necessary to reach the design goals mentioned above. This is essential for code that is intended to be executed in the real-time audio thread (no heap allocations etc.).

This is an important principle. It would be bad if it’s necessary to reach out to the low-level API whenever someone wants to do something performance-critical. The low-level API shouldn’t even be considered as a serious Rust API, it’s too raw and unsafe for Rust standards.

Modules

Contains predefined virtual keys.

Macros

This creates a static ReaperStr string literal embedded in the binary.

Structs

A value that either refers to a character code or to a virtual key.
A record which lets one get a place in the keyboard processing queue.
Pointer to an audio hook register.
A marker or region ID.
A list of MIDI events borrowed from REAPER.
Borrowed (reference-only) PCM sink.
Borrowed (reference-only) PCM source.
Pointer to a project state context.
Borrowed (reference-only) REAPER pitch shift instance.
Borrowed (reference-only) REAPER resample instance.
This represents a tempo measured in beats per minute.
A command ID.
Represents an owned PCM source that is backed by a Rust CustomPcmSource trait implementation.
This represents a volume measured in decibel.
This represents a duration expressed as positive amount of beats.
This represents a duration expressed as positive amount of seconds.
MIDI event list iterator.
Just a placeholder for upward compatibility reasons.
Just a placeholder for upward compatibility reasons.
Just a placeholder for upward compatibility reasons.
Additional data available in the context of extension plug-ins.
The given indexes count both markers and regions.
The given indexes count both markers and regions.
Each of these values can be negative! They are not normalized.
The arrange view start/end time for the given screen coordinates.
Represents a value which can neither be accessed nor created by the consumer.
This represents a frequency measured in hertz (how often something happens per second).
Borrowed action.
Pointer to a section (in which actions can be registered).
An owned MIDI event which can hold more than just the usual 3-byte short MIDI message.
A usage scope which unlocks all functions that are safe to execute from the main thread.
An owned or borrowed MIDI event for or from REAPER.
A MIDI frame offset.
Pointer to a MIDI input device.
A MIDI input device ID.
An MIDI message borrowed from a REAPER MIDI event.
Pointer to a MIDI output device.
A MIDI output device ID.
An OS-dependent color.
This represents a play rate measured as value between 0 and 1.
A kind of action descriptor.
Owned PCM sink.
Owned PCM source.
An owned preview register.
Owned REAPER pitch shift instance.
Owned REAPER resample instance.
PCM source peak transfer.
PCM source transfer.
A pitch shift mode, backed by a positive integer.
A pitch shift sub mode, backed by a positive integer.
This represents a play rate measured as factor of the normal play speed.
This represents the context in which this REAPER plug-in runs.
This represents a position expressed as an amount of beats.
This represents a position expressed as an amount of quarter notes.
This represents a position expressed as amount of seconds.
Location of a track or take FX including the parent track.
A usage scope which unlocks all functions that are safe to execute from the real-time audio thread.
This is the main access point for most REAPER functions.
An error which can occur when executing a REAPER function.
An error which can occur when trying to lock a REAPER mutex.
Mutex that works on native critical sections / mutexes exposed by the REAPER API.
This represents a particular value of an FX parameter in “REAPER-normalized” form.
This represents a value that could either be a pan or a width value.
This represents a pan measured in REAPER’s native pan unit.
This is the main hub for accessing medium-level API functions.
A borrowed string owned by REAPER.
An owned string created by REAPER.
A string parameter.
Represents a particular version of REAPER.
This represents a volume measured in REAPER’s native volume unit.
This represents a width measured in REAPER’s native width unit.
Handle which is returned from some session functions that register something.
A resample mode, backed by a positive integer.
A section ID.
Time signature.
An error which can occur when converting from a type with a greater value range to one with a smaller one.
Virtual key according to this list.
This represents a volume measured as fader position.
Additional data available in the context of VST plug-ins.

Enums

Defines the behavior of an accelerator.
Determines the position of the accelerator in the keyboard processing queue.
Represents a value change targeted to a REAPER action.
Determines the behavior when adding an FX.
Determines which info about the audio device shall be returned.
Describes whether to allow auto-seek or not.
Automation mode of a track.
Something which refers to a certain marker or region.
Defines how REAPER will buffer when playing previews.
A performance/caching hint which determines how REAPER internally gets or sets a chunk.
Defines an edit mode for changing send volume or pan.
Envelope chunk name which you can pass e.g. to TrackAttributeKey::Env().
Either a REAPER PCM source or a custom one.
Information about visibility of an FX chain.
Something which refers to a certain FX preset.
Determines if and how to show/hide a FX user interface.
Determines the gang behavior.
Global override of track automation modes.
Whether to display help text temporarily or permanently.
Defines which action will be preselected when prompting for an action.
Describes whether and how the recording input is monitored.
Activates certain behaviors when inserting a media file.
Decides where to insert a media file.
Determines how to deal with the master track.
Defines whether to align with measure starts when playing previews.
Defines whether to ignore measures or from which measure to count.
Message box result informing about the user’s choice.
Type of message box to be displayed.
Determines whether to import MIDI as in-project MIDI events or not.
Determines which control surfaces will be informed.
Track pan.
Track pan mode.
Identifies an FX parameter.
Defines whether some adjustment is done or not.
Determines the project in which a function should be executed.
Part of a project that could have been affected by an undoable operation.
Something which refers to a certain project.
Possible result when prompting for an action.
Validatable REAPER pointer.
Defines whether a track is armed for recording.
Recording input of a track.
A thing that you can register at REAPER.
Determines which MIDI editors to considers when targeting the last focused one.
Determines the section in which an action is located.
Decides when a MIDI message will be sent.
Denotes the target of a send.
Defines which track grouping behaviors to prevent when using the set_track_ui_* functions.
Track solo mode.
Determines where to route a MIDI message.
Take attribute key which you can pass to get_set_media_item_take_info().
Time mode.
Override of time modes.
Describes which kind of time range we are talking about in a REAPER project.
Type of a touched parameter.
Area in the REAPER window where a track might be displayed.
Track attribute key which you can pass to get_set_media_track_info().
Determines how track defaults should be used.
Represents the type of a track FX chain.
Describes the current location of a track FX (assuming the track is already known).
Type and location of a certain track.
Defines how to adjust track mute state.
Possible track polarity (phase) values.
Possible track polarity (phase) values.
Defines how to adjust track polarity (phase).
Defines how to adjust track arm mode.
Track send attribute key which you can pass to get_set_track_send_info().
Defines the kind of route.
Defines whether you are referring to a send or a receive.
Reference to a track send, hardware output send or track receive.
Defines which solo mode to set.
Determines whether to copy or move something (e.g. an FX).
Describes what to do with the received keystroke.
Additional stuff available in the plug-in context specific to a certain plug-in type.
Determines whether to refresh the UI.
Determines whether to create an undo point.
When creating an undo point, this defines what parts of the project might have been affected by the undoable operation.
Represents a change of a value (e.g. of a parameter).
Location of a track or take FX.
Location of a track FX.
Allows one to pass a window handle to the action function.

Traits

Represents a privilege to execute functions which are safe to execute from any thread.
Represents a privilege to execute functions which are only safe to execute from the real-time audio thread.
Consumers need to implement this trait in order to get notified about various REAPER events.
Consumers can implement this trait in order to provide own PCM source types.
Consumers need to implement this trait in order to define what should happen when a certain action is invoked.
Consumers need to implement this trait in order to define what should happen when a certain MIDI CC/mousewheel action is invoked.
Consumers need to implement this trait in order to get notified after a normal action of the main section has run.
Consumers need to implement this trait in order to get notified after a MIDI CC/mousewheel action has run.
Represents a privilege to execute functions which are only safe to execute from the main thread.
Consumers need to implement this trait in order to be called back in the real-time audio thread.
Consumers need to implement this trait in order to let REAPER know if a toggleable action is currently on or off.
Consumers need to implement this trait in order to be called back as part of the keyboard processing.

Functions

Type Definitions

Pointer to an audio accessor on track or take.
Pointer to a module/instance (module/instance handle).
Pointer to a window (window handle).
Pointer to an item on a track.
Pointer to a take in an item.
Pointer to a track in a project.
Pointer to a PCM sink.
Pointer to a PCM source.
Pointer to a project.
Pointer to a REAPER pitch shift instance.
Pointer to a REAPER resample instance.
Pointer to an envelope on a track.