Skip to main content

bela_sys/
midi.rs

1//! The C surface over Bela's `Midi` class.
2//!
3//! Unlike the rest of this crate, these declarations are not generated:
4//! the C they describe is `shim/midi.h` in this crate, compiled by
5//! `build.rs` and linked into device binaries alongside
6//! `libbelaextra`. That header is the contract, this module mirrors it
7//! by hand, and the two have to be edited together.
8//!
9//! Bela ships a C surface of its own in `libraries/Midi/Midi_c.h`,
10//! which the shim replaces rather than extends; `docs/midi.md` in the
11//! repository records why, along with what the class does on the audio
12//! thread and what it does not report.
13//!
14//! Off the device target the shim is not compiled, so these symbols do
15//! not resolve. Declaring them anyway keeps the module compiling
16//! everywhere the rest of the crate does.
17//!
18//! Every function takes a `midi` from [`bela_midi_new`] that has not
19//! been deleted, and a non-null `port` or `buf` where it takes one.
20//! Nothing checks; [`bela_midi_delete`] is the one that also accepts
21//! null.
22
23use core::ffi::{c_char, c_int, c_uchar, c_uint};
24use core::marker::{PhantomData, PhantomPinned};
25
26/// An opened `Midi` object, as an opaque pointee.
27///
28/// Deliberately not a description of the C++ class: nothing on this
29/// side may depend on its layout, and the shim is what allocates it.
30///
31/// The marker is what the nomicon asks of an opaque type: a bare
32/// zero-length array would make this `Send`, `Sync` and `Unpin`, and
33/// the object behind it is none of those — it owns a thread that reads
34/// the port, and its address is in the C++ object's own members.
35#[repr(C)]
36#[derive(Debug)]
37pub struct BelaMidi {
38    _data: [u8; 0],
39    _marker: PhantomData<(*mut u8, PhantomPinned)>,
40}
41
42/// Bytes of the longest message [`bela_midi_get_message`] writes: a
43/// status byte and two data bytes.
44pub const BELA_MIDI_MESSAGE_MAX: usize = 3;
45
46/// No port has the name that was given.
47///
48/// The names are [`bela_midi_list_ports`]'s, which carry the
49/// subdevice. Far outside the `errno` range on purpose: `-1` would be
50/// this and `EPERM` at once.
51pub const BELA_MIDI_NO_SUCH_PORT: c_int = -1000;
52
53/// That direction of this object is already open.
54///
55/// Bela's `inputEnabled` and `outputEnabled` are set once and never
56/// cleared, so a second open cannot be judged by them; it is refused
57/// rather than allowed to leak an ALSA handle and start a second
58/// reader thread.
59pub const BELA_MIDI_ALREADY_OPEN: c_int = -1001;
60
61unsafe extern "C" {
62    /// Writes every MIDI port ALSA reports into `buf` as NUL-terminated
63    /// names, one after another, and returns the bytes the whole list
64    /// needs. A `len` shorter than that holds as many whole names as
65    /// fit. `buf` may be null when `len` is 0, which is how to ask for
66    /// the size before allocating.
67    ///
68    /// 0 means there are no ports, and also means the query threw;
69    /// the two are not told apart, because the C++ underneath answers
70    /// an ALSA failure with a partial list rather than an error.
71    ///
72    /// The names carry card, device *and* subdevice — `hw:0,0,0` where
73    /// `amidi -l` prints `hw:0,0` — and [`bela_midi_read_from`] and
74    /// [`bela_midi_write_to`] match against exactly these.
75    ///
76    /// Allocates and reads the ALSA control interface.
77    pub fn bela_midi_list_ports(buf: *mut c_char, len: c_uint) -> c_uint;
78
79    /// Creates a `Midi` object with its input parser enabled, opening
80    /// no port. Returns null if it could not be created.
81    pub fn bela_midi_new() -> *mut BelaMidi;
82
83    /// Destroys a `Midi` object, joining its input thread. Null is
84    /// accepted. Blocks for as long as that thread takes to notice,
85    /// which is up to its 50 ms poll timeout.
86    pub fn bela_midi_delete(midi: *mut BelaMidi);
87
88    /// Opens `port` for input and starts reading from it, once per
89    /// object. Returns 0 when input is enabled afterwards,
90    /// [`BELA_MIDI_NO_SUCH_PORT`], [`BELA_MIDI_ALREADY_OPEN`], or a
91    /// negative value from Bela — `-errno` when ALSA refused the
92    /// device.
93    ///
94    /// A failure ends the object: Bela can fail with the ALSA device
95    /// already open and the flag the guard reads still false, so a
96    /// retry opens a second device over the first. Delete it and make
97    /// another.
98    pub fn bela_midi_read_from(midi: *mut BelaMidi, port: *const c_char) -> c_int;
99
100    /// Opens `port` for output, once per object, with the same return
101    /// values as [`bela_midi_read_from`] and the same end after a
102    /// failure.
103    pub fn bela_midi_write_to(midi: *mut BelaMidi, port: *const c_char) -> c_int;
104
105    /// How many parsed messages are waiting. Reads two ring indices:
106    /// no allocation, no system call — and no synchronisation either,
107    /// the input thread writing them as plain `unsigned int`s. What
108    /// that can cost is a count one message stale.
109    pub fn bela_midi_available_messages(midi: *mut BelaMidi) -> c_int;
110
111    /// Writes the oldest waiting message into `buf`, which must have
112    /// room for [`BELA_MIDI_MESSAGE_MAX`] bytes, and returns how many
113    /// bytes it wrote — 0 when nothing was waiting, leaving `buf`
114    /// untouched. The status byte carries the channel in its low
115    /// nibble, as on the wire.
116    pub fn bela_midi_get_message(midi: *mut BelaMidi, buf: *mut c_uchar) -> c_uint;
117
118    /// Hands `length` bytes to Bela's output task. Returns 1, or 0 if
119    /// output was never enabled — and 1 says the bytes were handed
120    /// over, not that they were queued or sent. The `-1` in the C++ is
121    /// unreachable while `commsSend` reports success unconditionally,
122    /// which is what makes 1 and 0 the whole range.
123    ///
124    /// Not to be called from `render`: on a full pipe the path below
125    /// this prints to `stderr` from the calling thread.
126    pub fn bela_midi_write_output(
127        midi: *mut BelaMidi,
128        bytes: *const c_uchar,
129        length: c_uint,
130    ) -> c_int;
131}