RMR: RtMIDI, reduced¶
Hello and welcome to RMR documentation.
RMR is a rewrite of RtMIDI project’s ALSA 1 part from C++ to C with GLib (it uses both GArray and GAsyncQueue for input). It is made as a personal experiment for use on embedded devices.
It is using the Sphinx documentation generator with Hawkmoth extension.
Contents¶
Starting¶
There is a wrapper function for creating each port type, start_port()
.
It accepts a mode argument and calls 4 lower-level wrapper functions.
Types of MIDI ports¶
There are virtual and non-virtual MIDI ports.
Think about virtual MIDI input and ouptut in terms of endpoints you connect to. “Normal” or “non-virtual” MIDI ports connect to those.
Client—server model, where a client 1 connects to a server is a nice analogy, as well. In this analogy, a server is a “virtual port” and a client is “just a port”.
Calling a wrapper¶
There is a single wrapper that is made of two parts: a port configurator (setup_port_config()
)
and a port starter (start_port()
).
The table below shows some init examples and there’s more info in the examples section.
MIDI port type |
Use |
Init function |
---|---|---|
Input |
Connect to |
setup_port_config(&port_config, MP_IN);
start_port(&amidi, port_config);
|
Output |
Connect to |
setup_port_config(&port_config, MP_OUT);
start_port(&amidi, port_config);
|
Virtual input |
Create an endpoint |
setup_port_config(&port_config, MP_VIRTUAL_IN);
start_port(&amidi, port_config);
|
Virtual output |
Create an endpoint |
setup_port_config(&port_config, MP_VIRTUAL_OUT);
start_port(&amidi, port_config);
|
Installation¶
Just clone the repo and include the library as the examples show.
Requirements¶
Alsa — should be already installed in your Linux distro
GNU Make — generally available in your Linux distro
glib-2.0 — libglib2.0-dev in Ubuntu
Footnotes
- 1
Alsa has its own client term , that has a different meaning: an Alsa seq client that has one or more ports as endpoints to communicate. Each port can be in a mode to read or write data and have other options.
Examples¶
This page shows several examples of using RMR.
Picking a mode¶
If you are writing a code that other programs will find and connect to, create a virtual port.
Otherwise, connect to software or devices using normal input or output ports.
Building¶
Open each directory, type “make” and it should be enough to get an executable file in that directory.
“Virtual input” is compatible with output, run “virtual input” first.
“Virtual output” is compatible with input, run “virtual output” first.
Virtual input¶
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 | #include <stdio.h>
#include <stdbool.h>
// Keeps process running until Ctrl-C is pressed.
// Contains a SIGINT handler and keep_process_running variable.
#include "util/exit_handling.h"
#include "util/output_handling.h"
#include "util/midi_parsing.h"
// Main RMR header file
#include "midi/midi_handling.h"
Alsa_MIDI_data * data;
MIDI_in_data * input_data;
MIDI_message * msg;
error_message * err_msg;
RMR_Port_config * port_config;
int main() {
// Allocate a MIDI_in_data instance, assign a
// MIDI message queue and an error queue
prepare_input_data_with_queues(&input_data);
// Create a port configuration with default values
setup_port_config(&port_config, MP_VIRTUAL_IN);
// Start a port with a provided configruation
start_port(&data, port_config);
// Assign amidi_data to input_data instance
assign_midi_data(input_data, data);
// Open a new port with a pre-set name
open_virtual_port(data, "rmr", input_data);
// Don't exit until Ctrl-C is pressed;
// Look up "output_handling.h"
keep_process_running = 1;
// Add a SIGINT handler to set keep_process_running to 0
// so the program can exit
signal(SIGINT, sigint_handler);
// Run until SIGINT is received
while (keep_process_running) {
while (g_async_queue_length(input_data->midi_async_queue)) {
// Read a message from a message queue
msg = g_async_queue_try_pop(input_data->midi_async_queue);
if (msg != NULL) {
print_midi_msg_buf(msg->buf, msg->count);
free_midi_message(msg);
}
}
while (g_async_queue_length(input_data->error_async_queue)) {
// Read an error message from an error queue,
// simply deallocate it for now
err_msg = g_async_queue_try_pop(input_data->midi_async_queue);
if (err_msg != NULL) free_error_message(err_msg);
}
}
// Close a MIDI input port,
// shutdown the input thread,
// do cleanup
destroy_midi_input(data, input_data);
// Destroy a port configuration
destroy_port_config(port_config);
// Exit without an error
return 0;
}
|
Virtual output¶
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 | #include <stdio.h>
#include <stdbool.h>
// Needed for usleep
#include <unistd.h>
// Needed for nanosleep
#include <time.h>
// Main RMR header file
#include "midi/midi_handling.h"
// Keeps process running until Ctrl-C is pressed.
// Contains a SIGINT handler and keep_process_running variable.
#include "util/exit_handling.h"
#include "util/timing.h"
#include "test_data.h"
Alsa_MIDI_data * amidi_data;
RMR_Port_config * port_config;
// Send "note on" or "note off" signal
bool msg_mode = false;
// Last recorded time in milliseconds
double timer_msec_last;
int main() {
// Record initial time to a timer
timer_msec_last = millis();
// Create a port configuration with default values
setup_port_config(&port_config, MP_VIRTUAL_OUT);
// Start a port with a provided configruation
start_port(&amidi_data, port_config);
// Send out a series of MIDI messages.
send_midi_message(amidi_data, MIDI_PROGRAM_CHANGE_MSG, 2);
send_midi_message(amidi_data, MIDI_CONTROL_CHANGE_MSG, 3);
// Add a SIGINT handler to set keep_process_running to 0
// so the program can exit
signal(SIGINT, sigint_handler);
// Don't exit until Ctrl-C is pressed;
// Look up "output_handling.h"
keep_process_running = 1;
// Run until SIGINT is received
while (keep_process_running) {
if (millis() - timer_msec_last > 100.) {
timer_msec_last = millis();
// Send a Note On message
if (msg_mode) send_midi_message(amidi_data, MIDI_NOTE_ON_MSG, 3);
// Send a Note Off message
else send_midi_message(amidi_data, MIDI_NOTE_OFF_MSG, 3);
printf("mode: %d\n", msg_mode);
msg_mode = !msg_mode;
fflush(stdout);
usleep(10);
}
}
// Destroy a MIDI output port:
// close a port connection and perform a cleanup.
if (destroy_midi_output(amidi_data, NULL) != 0) slog("destructor", "destructor error");
// Destroy a port configuration
destroy_port_config(port_config);
// Exit without an error
return 0;
}
|
Note: inconsistent intervals¶
I had inconsistent note intervals while making this example.
I’ve used millis() and usleep(N) calls. It’s possible to feed usleep values that make these intervals inconsistent. Current intervals are 100, but I’d have to play with this idea more.
// Returns the milliseconds since Epoch
double millis() {
struct timeval cur_time;
gettimeofday(&cur_time, NULL);
return (cur_time.tv_sec * 1000.0) + cur_time.tv_usec / 1000.0;
}
Input¶
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 | #include <stdbool.h>
// Keeps process running until Ctrl-C is pressed.
// Contains a SIGINT handler and keep_process_running variable.
#include "util/exit_handling.h"
#include "util/output_handling.h"
// Main RMR header file
#include "midi/midi_handling.h"
Alsa_MIDI_data * data;
MIDI_in_data * input_data;
MIDI_port * current_midi_port;
RMR_Port_config * port_config;
MIDI_message * msg;
error_message * err_msg;
int main() {
// Allocate a MIDI_in_data instance, assign a
// MIDI message queue and an error queue
prepare_input_data_with_queues(&input_data);
// Create a port configuration with default values
setup_port_config(&port_config, MP_IN);
// Start a port with a provided configruation
start_port(&data, port_config);
// Allocate memory for a MIDI_port instance
init_midi_port(¤t_midi_port);
// Assign amidi_data to input_data instance
assign_midi_data(input_data, data);
// Count the MIDI ports,
// open if a port containing "Synth" is available
if (find_midi_port(data, current_midi_port, MP_VIRTUAL_OUT, "rmr") > 0) {
print_midi_port(current_midi_port);
open_port(MP_IN, current_midi_port->id, current_midi_port->port_info_name, data, input_data);
// Don't exit until Ctrl-C is pressed;
// Look up "output_handling.h"
keep_process_running = 1;
}
// Add a SIGINT handler to set keep_process_running to 0
// so the program can exit
signal(SIGINT, sigint_handler);
// Run until SIGINT is received
while (keep_process_running) {
while (g_async_queue_length(input_data->midi_async_queue)) {
// Read a message from a message queue
msg = g_async_queue_try_pop(input_data->midi_async_queue);
if (msg != NULL) {
// Print and deallocate a midi message instance
print_midi_msg_buf(msg->buf, msg->count);
free_midi_message(msg);
}
}
while (g_async_queue_length(input_data->error_async_queue)) {
// Read an error message from an error queue,
// simply deallocate it for now
err_msg = g_async_queue_try_pop(input_data->midi_async_queue);
if (err_msg != NULL) free_error_message(err_msg);
}
}
// Close a MIDI input port,
// shutdown the input thread,
// do cleanup
destroy_midi_input(data, input_data);
// Destroy a port configuration
destroy_port_config(port_config);
// Exit without an error
return 0;
}
|
Output¶
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 | #include <stdio.h>
#include <stdbool.h>
// Needed for usleep
#include <unistd.h>
// Needed for nanosleep
#include <time.h>
// Main RMR header file
#include "midi/midi_handling.h"
// Keeps process running until Ctrl-C is pressed.
// Contains a SIGINT handler and keep_process_running variable.
#include "util/exit_handling.h"
#include "util/timing.h"
#include "test_data.h"
Alsa_MIDI_data * data;
MIDI_port * current_midi_port;
RMR_Port_config * port_config;
// Send "note on" or "note off" signal
bool msg_mode = false;
// Last recorded time in milliseconds
double timer_msec_last;
int main() {
// Record initial time to a timer
timer_msec_last = millis();
// Create a port configuration with default values
setup_port_config(&port_config, MP_VIRTUAL_OUT);
// Start a port with a provided configruation
start_port(&data, port_config);
// Allocate memory for a MIDI_port instance
init_midi_port(¤t_midi_port);
// Count the MIDI ports,
// open if a port containing a certain word is available
if (find_midi_port(data, current_midi_port, MP_VIRTUAL_IN, "rmr") > 0) {
print_midi_port(current_midi_port);
open_port(MP_OUT, current_midi_port->id, current_midi_port->port_info_name, data, NULL);
// Don't exit until Ctrl-C is pressed;
// Look up "output_handling.h"
keep_process_running = 1;
}
// Send out a series of MIDI messages.
send_midi_message(data, MIDI_PROGRAM_CHANGE_MSG, 2);
send_midi_message(data, MIDI_CONTROL_CHANGE_MSG, 3);
// Add a SIGINT handler to set keep_process_running to 0
// so the program can exit
signal(SIGINT, sigint_handler);
// Run until SIGINT is received
while (keep_process_running) {
if (millis() - timer_msec_last > 100.) {
timer_msec_last = millis();
// Send a Note On message
if (msg_mode) send_midi_message(data, MIDI_NOTE_ON_MSG, 3);
// Send a Note Off message
else send_midi_message(data, MIDI_NOTE_OFF_MSG, 3);
printf("mode: %d\n", msg_mode);
msg_mode = !msg_mode;
fflush(stdout);
usleep(10);
}
}
// Destroy a MIDI output port:
// close a port connection and perform a cleanup.
if (destroy_midi_output(data, NULL) != 0) slog("destructor", "destructor error");
// Destroy a port configuration
destroy_port_config(port_config);
// Exit without an error
return 0;
}
|
Using get_full_port_name()
¶
This example finds and identifies an input or output port.
If you run a “virtual output” example and expected_port_type variable is set as mp_type_t.MP_VIRTUAL_OUT
, it will display “rmr virtual output:rmr virtual output port 128:0”.
If you run a “virtual input” example and expected_port_type variable is set as mp_type_t.MP_VIRTUAL_IN
, it will display “rmr virtual input:rmr 128:0”.
While this output format might look unusual, it provides info about two Alsa containers: “client info” and “port info”, in that order.
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 | #include <stdbool.h>
#include "util/output_handling.h"
// Main RMR header file
#include "midi/midi_handling.h"
Alsa_MIDI_data * data;
MIDI_in_data * input_data;
MIDI_port * current_midi_port;
RMR_Port_config * port_config;
char * port_name;
#define PORT_NAME_MAX_LENGTH 512
// MP_VIRTUAL_OUT for virtual output, MP_VIRTUAL_IN for virtual input.
// Non-virtual ports are accepted the same way.
mp_type_t expected_port_type = MP_VIRTUAL_OUT;
int main() {
port_name = (char*)calloc(PORT_NAME_MAX_LENGTH, sizeof(char));
// Create a port configuration with default values
setup_port_config(&port_config, MP_IN);
// Start a port with a provided configruation
start_port(&data, port_config);
// Allocate memory for a MIDI_port instance
init_midi_port(¤t_midi_port);
// Count the MIDI ports,
// open if a port containing "Synth" is available
if (find_midi_port(data, current_midi_port, expected_port_type, "rmr") > 0) {
get_full_port_name(port_name, current_midi_port->id, expected_port_type, data);
printf("%s\n", port_name);
}
// Free the memory used by port_name
free(port_name);
// Close a MIDI input port,
// shutdown the input thread,
// do cleanup
destroy_midi_input(data, input_data);
// Destroy a port configuration
destroy_port_config(port_config);
// Exit without an error
return 0;
}
|
Terminology¶
PPQ / PPQN / TPQN value¶
Ticks are regular units of time a computer or hardware MIDI device uses for measuring time steps.
Pulses per quarter note, ticks per quarter note or PPQ defines the base resolution of the ticks, and it indicates the number of divisions a quarter note has been split into (according to Sweetwater’s “What is PPQN” page). Thus, PPQ value of zero is invalid.
It is the smallest unit of time used for sequencing note and automation events. PPQ is one of the two values Alsa uses to specify the tempo (another one is MIDI tempo). PPQ cannot be changed while the Alsa queue is running. It must be set before the queue is started. (For this library, it means that PPQ value stays while a port is open.)
According to Wikipedia, “modern computer-based MIDI sequencers designed to capture more nuance may use 960 PPQN and beyond”.
Typical PPQ values: 24, 48, 72, 96, 120, 144, 168, 192, 216, 240, 288, 360, 384, 436, 480, 768, 960, 972, 1024.
MIDI Tempo and BPM¶
We often hear about “beats per minute” or “BPM” in computer music.
BPM itself means “the amount of quarter notes in every minute”. Sometimes, BPM values are not strict enough and we might need more control.
For that reason, Alsa and RMR do not rely on “BPM” itself. Instead, Alsa is using so-called “MIDI tempo” and RMR provides an interface to configure it. (MIDI tempo is one of the two values Alsa uses to specify the tempo, another one is PPQ.)
MIDI tempo is not “the amount of quarter notes in every minute”, but “time in microseconds per quarter note”, so it defines the beat tempo in microseconds. Increasing MIDI tempo will increase the length of a tick, so it will make playback slower.
Name |
Description |
Example value |
---|---|---|
\(T\) |
|
|
\(BPM\) |
|
120 bpm |
\(min_{ms}\) |
A minute in microseconds |
60 s * 1000000 ms |
We calculate a MIDI tempo by dividing a minute in microseconds by an amount of BPM:
\(T = \frac{min_{ms}}{BPM}\)
Let us assume our BPM value is 120 and substitute a minute in microseconds to calculate an exact value.
\(T = \frac{min_{ms}}{120} = \frac{60 \cdot 1000000}{120} = 500000\)
ALSA project - the C library reference - Sequencer interface - setting queue tempo is the most interesting
MIDI Technical Fanatic’s Brainwashing Center — MIDI file format: tempo and timebase
Tempo in MIDI files¶
MIDI files use both MIDI tempo and PPQ value to specify the tempo.
Architecture¶
Approach to data¶
RMR is made to work well with in realtime, so:
Queue use¶
Both input and virtual input modes are using thread that communicates with the main code using GLib’s Asynchronous Queue mechanism for both messages and errors.
Messages are contained in a structure, MIDI_message
.
MIDI_message.buf
contains message bytes and MIDI_message.count
contains length of this byte array.
Each MIDI_message
also contains a MIDI_message.timestamp
member:
a double value, based on snd_seq_real_time_t contents and previous time values.
Double pointers¶
Often enough, you will be seeing pointers to pointers in code. It is done for allocating memory in a function or something close to that, usually.
API documentation¶
Core¶
A main include file
-
NANOSECONDS_IN_SECOND
¶ An amount of nanoseconds in a second
-
QUEUE_TEMPO
¶ A constant that defines the beat tempo in microseconds
-
QUEUE_STATUS_PPQ
¶ A constant that defines the base resolution of the ticks (pulses per quarter note)
-
void
init_midi_port
(MIDI_port **current_midi_port)¶ Allocates memory for a
MIDI_port
instance- Parameters
current_midi_port – a double pointer used to allocate memory for a
MIDI_port
instance
- Since
v0.1
-
void
print_midi_port
(MIDI_port *current_midi_port)¶ Prints a name of
snd_seq_client_info_t
andsnd_seq_port_info_t
containers- Parameters
current_midi_port – a
MIDI_port
instance to display
- Since
v0.1
-
void
free_midi_message
(MIDI_message *msg)¶ Deallocates memory for
MIDI_message
instance- Parameters
msg – a
MIDI_message
instance
- Since
v0.1
-
void
init_amidi_data_instance
(Alsa_MIDI_data **amidi_data)¶ Allocates memory for an instance of
Alsa_MIDI_data
or throws an error- Parameters
amidi_data – a double pointer used to allocate memory for a
Alsa_MIDI_data
instance
- Since
v0.1
-
void
assign_midi_queue
(MIDI_in_data *input_data)¶ Creates an assigns a MIDI message queue for a
MIDI_in_data
instance- Parameters
input_data – a pointer containing a
MIDI_in_data
instance for creating aGAsyncQueue
instance.
- Since
v0.1
-
void
assign_midi_data
(MIDI_in_data *input_data, Alsa_MIDI_data *amidi_data)¶ Assign
Alsa_MIDI_data
instance toMIDI_in_data
instance- Parameters
input_data –
MIDI_in_data
instanceamidi_data –
Alsa_MIDI_data
instance
- Since
v0.1
-
void
set_MIDI_in_callback
(MIDI_in_data *input_data, MIDI_callback callback, void *user_data)¶ Sets a callback for MIDI input events.
- Parameters
input_data –
MIDI_in_data
instancecallback –
MIDI_callback
instanceuser_data – an optional pointer to additional data that is passed to the callback function whenever it is called.
- Since
v0.1
-
void
enqueue_error
(MIDI_in_data *input_data, char *etype, char *msg)¶ Add an
error_message
instance toMIDI_in_data
instance- Parameters
input_data – a
MIDI_in_data
instance containing a queue to send an error toetype – error type string, for example, “V0001”, “S0001”
msg – error message
- Since
v0.1
-
void
assign_error_queue
(MIDI_in_data *input_data)¶ Create an error queue, add to
MIDI_in_data
instance- Parameters
input_data – a
MIDI_in_data
instance to add an error queue to
- Since
v0.1
-
int
init_amidi_data
(Alsa_MIDI_data *amidi_data, mp_type_t port_type)¶ Fill
Alsa_MIDI_data
struct instance- Parameters
amidi_data –
Alsa_MIDI_data
instance to initializeport_type – a port type to use, supports all values for
mp_type_t
- Returns
0 on success, -1 when
port_type
is notmp_type_t.MP_IN
,mp_type_t.MP_VIRTUAL_IN
,mp_type_t.MP_OUT
,mp_type_t.MP_VIRTUAL_OUT
.- Since
v0.1
-
int
init_seq
(Alsa_MIDI_data *amidi_data, const char *client_name, mp_type_t port_type)¶ Creates a seq instance, assigns it to
Alsa_MIDI_data
- Parameters
amidi_data –
Alsa_MIDI_data
instanceclient_name – client name string
port_type – a port type for a sequencer, supports all values for
mp_type_t
- Returns
0 on success, -1 on error.
- Since
v0.1
-
int
start_input_seq
(Alsa_MIDI_data *amidi_data, const char *queue_name, RMR_Port_config *port_config)¶ Creates a named input queue, sets its tempo and other parameters.
- Parameters
amidi_data –
Alsa_MIDI_data
instancequeue_name – a name for a new named queue
port_config – an instance of port configuration:
RMR_Port_config
- Returns
0 or -1 when an error happens
- Since
v0.1
-
int
prepare_output
(int is_virtual, Alsa_MIDI_data *amidi_data, const char *port_name)¶ Creates a MIDI event parser, a virtual port for virtual mode and allocates a buffer for normal mode.
TODO pick a new name, considering calls to snd_midi_event_new, snd_midi_event_init, snd_seq_create_simple_port (virtual mode) and malloc for amidi_data->buffer (normal mode).
- Parameters
is_virtual – should the function create normal output port or a virtual one?
amidi_data –
Alsa_MIDI_data
instanceport_name – a name to set for a new virtual output port
- Returns
0 on success, -1 on an error
- Since
v0.1
-
unsigned int
port_info
(int *seq, int *pinfo, unsigned int type, int port_number)¶ This function is used to count or get the pinfo structure for a given port number.
- Parameters
snd_seq_t – Alsa’s
snd_seq_t
instancepinfo – Alsa’s
snd_seq_port_info_t
instancetype – Alsa MIDI port capabilities
SND_SEQ_PORT_CAP_READ
|SND_SEQ_PORT_CAP_SUBS_READ
orSND_SEQ_PORT_CAP_WRITE
|SND_SEQ_PORT_CAP_SUBS_WRITE
port_number – use -1 for counting ports and a port number to get port info (in that case a function returns 1).
TODO do we query a client number, not a port number? We are using snd_seq_query_next_client.
- Returns
port count (or the amount of ports) when a negative port_number value is provided, 1 when a port ID is provided and the port is found, 0 when a port ID is not found
-
unsigned int
get_midi_port_count
(Alsa_MIDI_data *amidi_data, unsigned int type)¶ Counts midi ports for input and output types
- Parameters
amidi_data –
Alsa_MIDI_data
instancetype – Alsa MIDI port capabilities, like “
SND_SEQ_PORT_CAP_READ
|SND_SEQ_PORT_CAP_SUBS_READ
” or “SND_SEQ_PORT_CAP_WRITE
|SND_SEQ_PORT_CAP_SUBS_WRITE
”
- Returns
MIDI port count
- Since
v0.1
-
void
get_port_descriptor_by_id
(MIDI_port *port, Alsa_MIDI_data *amidi_data, unsigned int port_number, unsigned int type)¶ Updates a port pointer to
MIDI_port
instance from a selected port_number.- Parameters
port – a pointer to update
amidi_data –
Alsa_MIDI_data
instanceport_number – a port number
type – Alsa MIDI port capabilities
SND_SEQ_PORT_CAP_READ
|SND_SEQ_PORT_CAP_SUBS_READ
orSND_SEQ_PORT_CAP_WRITE
|SND_SEQ_PORT_CAP_SUBS_WRITE
- Since
v0.1
-
void
deallocate_input_thread
(struct MIDI_in_data *input_data)¶ Free an Alsa MIDI event parser, reset its value in
MIDI_in_data
instance, set currentMIDI_in_data
thread to dummy_thread_id- Parameters
input_data – a
MIDI_in_data
instance
- Since
v0.1
-
void *
alsa_MIDI_handler
(void *ptr)¶ A start routine for
alsa_MIDI_handler
.- Parameters
ptr – a void-pointer to
MIDI_in_data
.
- Since
v0.1
-
int
open_virtual_port
(Alsa_MIDI_data *amidi_data, const char *port_name, MIDI_in_data *input_data)¶ Opens a MIDI port with a given name, creates a thread and queues for it.
- Parameters
amidi_data –
Alsa_MIDI_data
instanceport_name – a char array pointer pointing to a port name string
input_data – a
MIDI_in_data
instance
- Returns
0 on success
- Since
v0.1
-
int
send_midi_message
(Alsa_MIDI_data *amidi_data, const unsigned char *message, int size)¶ Sends a MIDI message using a provided
Alsa_MIDI_data
instance- Parameters
amidi_data –
Alsa_MIDI_data
instancemessage – a MIDI message to be sent
size – a size of MIDI message in bytes
- Returns
0 on success, -1 on an error
- Since
v0.1
-
int
find_midi_port
(Alsa_MIDI_data *amidi_data, MIDI_port *port, mp_type_t port_type, const char *substr)¶ Finds a MIDI port (port) by a given substring. Matches a substring in
MIDI_port.client_info_name
attribute.- Parameters
amidi_data –
Alsa_MIDI_data
instanceport –
MIDI_port
instanceport_type – a port type for a sequencer, supports all values for
mp_type_t
, distinguishes only between “input” and “output”, so a virtual input will still be “an input”substr – a port substring
- Returns
1 when a port was found, -1 on an error, -2 when an invalid port type was provided
- Since
v0.1
-
int
open_port
(mp_type_t port_type, unsigned int port_number, const char *port_name, Alsa_MIDI_data *amidi_data, MIDI_in_data *input_data)¶ Opens a MIDI port by its number. Converted from two RtMIDI methods, initially accepted boolean pointing if it’s input.
TODO read all calls in a function and decide if it really makes sense for port_type not to be inverted.
- Parameters
port_type – a port type for a opening, supports all values for
mp_type_t
, currently expects MP_IN or MP_VIRTUAL_IN in input contextport_number – number of a port to look for
port_name – a name of a port to set using
snd_seq_port_info_set_name()
amidi_data –
Alsa_MIDI_data
instanceinput_data – a
MIDI_in_data
instance
- Returns
0 on success, -1 on an error
- Since
v0.1
-
void
close_port
(Alsa_MIDI_data *amidi_data, MIDI_in_data *input_data, int mode)¶ Closes input or output port, works for both virtual and not ports
- Parameters
amidi_data –
Alsa_MIDI_data
instanceinput_data –
MIDI_in_data
instancemode – accepts
SND_SEQ_OPEN_INPUT
orSND_SEQ_OPEN_OUTPUT
- Since
v0.1
-
int
destroy_midi_output
(Alsa_MIDI_data *amidi_data, MIDI_in_data *input_data)¶ Destroys a MIDI output port: closes a port connection and performs a cleanup.
- Parameters
amidi_data –
Alsa_MIDI_data
instanceinput_data –
MIDI_in_data
instance
- Returns
0 on success
- Since
v0.1
-
int
destroy_midi_input
(Alsa_MIDI_data *amidi_data, MIDI_in_data *input_data)¶ Destroys a MIDI input port: closes a port connection, shuts the input thread down, performs cleanup / deallocations.
- Parameters
amidi_data –
Alsa_MIDI_data
instanceinput_data –
MIDI_in_data
instance
- Returns
0 on success
- Since
v0.1
-
void
set_port_name
(Alsa_MIDI_data *amidi_data, const char *port_name)¶ Set the name of a port_info container in Alsa SEQ interface port information container, update a port info value for an
amidi_data.vport
value.- Parameters
amidi_data – Alsa_MIDI_data instance
port_name – a new name for Alsa seq port
- Since
v0.1
-
void
set_client_name
(Alsa_MIDI_data *amidi_data, const char *client_name)¶ Set name for a
amidi_data.seq
snd_seq_t
instance.- Parameters
amidi_data –
Alsa_MIDI_data
instanceclient_name – a new name for Alsa seq client
- Since
v0.1
-
int
prepare_input_data_with_queues
(MIDI_in_data **input_data)¶ Allocates memory for
MIDI_in_data
instance. Assigns two queues: one for MIDI messages and one for errors.- Parameters
input_data – a double pointer used to allocate memory for a
MIDI_in_data
instance
- Returns
0 on success
- Since
v0.1
-
int
start_virtual_output_port
(Alsa_MIDI_data **amidi_data, RMR_Port_config *port_config)¶ A wrapper function for a initializing a virtual output MIDI port.
- Parameters
amidi_data – a double pointer to
Alsa_MIDI_data
instanceport_config – an instance of port configuration:
RMR_Port_config
- Returns
0 on success
- Since
v0.1
-
int
start_output_port
(Alsa_MIDI_data **amidi_data, RMR_Port_config *port_config)¶ A wrapper function for starting a non-virtual output port.
- Parameters
amidi_data – a double pointer to
Alsa_MIDI_data
instanceport_config – an instance of port configuration:
RMR_Port_config
- Returns
0 on success
- Since
v0.1
-
int
start_virtual_input_port
(Alsa_MIDI_data **amidi_data, RMR_Port_config *port_config)¶ A wrapper function for opening a virtual input port
- Parameters
amidi_data – a double pointer to
Alsa_MIDI_data
instanceport_config – an instance of port configuration:
RMR_Port_config
- Returns
0 on success
- Since
v0.1
-
int
start_input_port
(Alsa_MIDI_data **amidi_data, RMR_Port_config *port_config)¶ A wrapper function for starting a non-virtual input port.
- Parameters
amidi_data – a double pointer to
Alsa_MIDI_data
instanceport_config – an instance of port configuration:
RMR_Port_config
- Returns
0 on success
- Since
v0.1
-
int
reset_port_config
(RMR_Port_config *port_config, mp_type_t port_type)¶ Fills
RMR_Port_config
attributes.A port type is set first, then a queue tempo and ppq you can change. “client_name”, “port_name” and “queue_name” are set as “N/A” and then changed to default values needed for a certain port type.
- Parameters
port_config – an instance of port configuration:
RMR_Port_config
port_type – a port type for a sequencer, supports all values for
mp_type_t
- Returns
0 on success
- Since
v0.1.3
-
int
setup_port_config
(RMR_Port_config **port_config, mp_type_t port_type)¶ Allocates memory for an instance of
RMR_Port_config
, sets default values for it- Parameters
port_config – an instance of port configuration:
RMR_Port_config
port_type – a port type for a sequencer, supports all values for
mp_type_t
- Returns
0 on success
- Since
v0.1.3
-
int
destroy_port_config
(RMR_Port_config *port_config)¶ Deallocates an instance of
RMR_Port_config
- Parameters
port_config – an instance of port configuration (
RMR_Port_config
)
- Returns
0 on success
- Since
v0.1.3
-
int
start_port
(Alsa_MIDI_data **amidi_data, RMR_Port_config *port_config)¶ A wrapper for in, out, virtual in and virtual out MIDI port initialization. TODO add Port_config input.
- Parameters
amidi_data – a double pointer to
Alsa_MIDI_data
instanceport_config – an instance of port configuration:
RMR_Port_config
- Returns
0 on success
- Since
v0.1
-
int
get_full_port_name
(char *port_name, unsigned int port_number, mp_type_t port_type, Alsa_MIDI_data *amidi_data)¶ Finds a complete port name, including both client info and port info. A rewrite of both RtMIDI’s getPortName functions.
While this output format might look unusual, it provides info about two Alsa containers: “client info” and “port info”, in that order. It shows names and IDs.
- Parameters
port_name – a const char pointer pointing to a string to be filled
port_number – a number of a port to look for
port_type – supports all values for
mp_type_t
, used to selectSND_SEQ_PORT_CAP_READ
|SND_SEQ_PORT_CAP_SUBS_READ
orSND_SEQ_PORT_CAP_WRITE
|SND_SEQ_PORT_CAP_SUBS_WRITE
port capabilitiesamidi_data – a double pointer to
Alsa_MIDI_data
instance
- Returns
0 on success, -1 when port_name is a null pointer, -2 when incorrect value was passed for port_type argument, -3 when port wasn’t found.
- Since
v0.1
Typedefs¶
MIDI-related type definitions
-
type
MIDI_callback
¶ A function definition for processing MIDI callbacks
Generic helpers¶
Helper functions
-
unsigned char
get_last_bytearray_byte
(int *bytearray)¶ Retrieve a last byte in a bytearray
- Parameters
bytearray – a GLib
GArray
instance to extract the last byte from
- Returns
a last character of GArray
- Since
v0.1
Error handling¶
-
ERROR_MSG_ETYPE_SIZE
¶ Error handling
-
struct
error_message
¶ A struct to hold an error message.
- Since
v0.1
-
char
error_type
[5]¶ Integer value to differentiate between possible error types
-
char
message
[255]¶ Current error message string
-
void
free_error_message
(error_message *msg)¶ Free the memory used by an error message
- Parameters
msg – a message to deallocate
- Since
v0.1
-
void
serr
(const char *err_id, const char *section, const char *message)¶ A function to display error messages. TODO: integrate error classes support, currently the function doesn’t differentiate between system and value errors.
- Parameters
err_id – an ID of an error to classify it by
section – where error happened
message – error message to send
- Since
v0.1
Errors¶
All errors, thrown by a library.
Error classes¶
System
Value
Errors¶
Error name |
Error description |
Details |
---|---|---|
V0001 |
Event parsing error or not a MIDI event |
Thrown if It is generated by |
S0001 |
Error initializing MIDI event parser |
Thrown if there was an error in
It is generated by |
RtMIDI feature translation¶
Queue messages as bytes¶
RtMIDI translates all ALSA messages to bytes. 1
It is left the same way, although data types might be changed to ensure bytes are always 8-bit (something like uint8_t
).
Rejoining message chunks¶
The ALSA sequencer has a maximum buffer size for MIDI sysex events of 256 bytes.
If a device sends sysex messages larger than this, they are segmented into 256 byte chunks.
RtMIDI rejoins 256-byte SysEx message chunks to a single bytearray.
Adding timestamps to MIDI messages¶
The initial source contains two ways to do that.
First one uses the system time.
(void)gettimeofday(&tv, (struct timezone *)NULL);
time = (tv.tv_sec * 1000000) + tv.tv_usec;
Second one uses the ALSA sequencer event time data and was implemented by Pedro Lopez-Cabanillas. 2
The second one was commented quite a bit and it can be found in a current source code.
init_seq client name setting¶
Uses an internal function and different ordering, but it doesn’t seem to influence anything and this doc section is about to be removed.
Before:
snd_seq_set_client_name(seq, client_name);
amidi_data->seq = seq;
Now:
amidi_data->seq = seq;
set_client_name(amidi_data, client_name);
Footnotes
- 1
I’m not sure if it helps to rejoin SysEx messages or it is done as a way to unify output for Alsa, Jack, etc.
- 2
LibC manual on getting elapsed time.
Changes done while rewriting¶
Some classes were changed to structs
There are changes from camelcase to underscore, too
RtMIDI’s MidiMessage¶
RtMIDI |
RMR |
---|---|
|
|
|
|
|
|
AlsaMidiData¶
RtMIDI |
RMR |
---|---|
|
|
|
|
|
renamed to |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
RtMidiInData¶
RtMIDI |
RMR |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
RtMidiCallback function¶
RtMIDI |
RMR |
---|---|
|
|
MIDI input opening¶
RtMIDI |
RMR |
---|---|
|
|
Plans¶
There are things I want to change, but issues are already full with random stuff I’ll need to track. This document exists to handle things gradually, not create issues when I solve other issues. (It seems I will always create more than I solve.)
P.S.: this doesn’t seem like a good way to continue, because now there’s a “clutter 1” and “clutter 2”, but I’ll let time to show how it goes.
Thread use¶
There’s dummy_thread_id and thread properties. dummy_thread_id receives a value returned by pthread_self(). Then the dummy_thread_id gets assigned to thread variable. It is done to check when previous input thread is finished.
Terminology¶
Terms I want to change¶
Check if “port_config->virtual_seq_name” is a term that makes sense, rename if it doesn’t.
Callback and queues¶
Error queue in callback examples¶
When I finished a callback example, closing issue #14, I noticed there’s still a queue for errors that happened in input thread. I am thinking about a different method of error propagation. There are also disconnections, which aren’t exactly errors. I think I can replace error queue with callback calls easily. In which thread the callback runs, though? I also feel like I’ll have to redesign “serr” function to work well with a new “error” or “status callback” mechanism.
Implementation details¶
Port capabilities while retrieving a port name¶
RtMIDI has two Alsa getPortName methods, I have one get_full_port_name function.
Port capabilities in RtMIDI might’ve been inverted, and I think that I have inverted them back, properly. I am probably wrong, but I debugged it and it works for some reason.
if (in) port_mode = SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE;
else port_mode = SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ;
“SND_SEQ_PORT_CAP_WRITE” is defined as “writable to this port”, too.
UPD: I have changed “in” variable type from bool to a “mp_type_t”. I renamed it to “port_type”, too. And it did not work with input until I changed arguments back. Now it all makes even less sense. It finds both “virtual input” and “virtual output” values.
It’s strange to think about, but maybe I understood boolean function arguments incorrectly?
UPD 2: I was wrong about the arguments, I changed them to less nonsensical ones, it all works better now. I think.
Architectural changes¶
Default amount of channels?¶
snd_seq_port_info_set_midi_channels accepts 16 channels. It sounds like a magic constant I’ll have to fix, but I am not sure about that. If that is correct, a define seems enough, but moving amount of channels into a port configurator sounds even better.
MIDI2 handling¶
MIDI2 support doesn’t seem to be globally implemented. I receive mixed news: some claim to have finished MIDI2 compatible synths, some claim MIDI2 is not supported by Windows, etc. For now I’ll collect information and will try to prepare.
Memory management¶
Check all “malloc”, “calloc”, “alloca” calls are cleaned up after.
Self-reviews¶
Read the header code through at least 4 times. I’m sure I missed something, be it apidoc or other things.
Current times: 1.