Introduction

The ALSA MIDI API is like many avaiable APIs. It is difficult to get started with. It is easy to say that one wants to do X, but has no clue where to find out how to set things up. This brief introduction does not try to give all the information on how to use the ALSA MIDI API, but it does give a starting point.

Connecting to the Sequencer

To get started with MIDI, input is a good place. Before you are able to get any input, the ALSA sequencer needs to know about your program and have a running connection to it with port(s). This is done with snd_seq_open(), snd_seq_set_client_name(), and snd_seq_create_simple_port(). Typically you will want to store the sequencer handle and port handles for later use. (a good portion of the api uses these to determine how you are changing settings of various data structures.)

#include <alsa/asoundlib.h>

static snd_seq_t *seq_handle;
static int in_port;

void midi_open(void)
{
    snd_seq_open(&seq_handle, "default", SND_SEQ_OPEN_INPUT, 0);

    snd_seq_set_client_name(seq_handle, "Midi Listener");
    in_port = snd_seq_create_simple_port(seq_handle, "listen:in",
                      SND_SEQ_PORT_CAP_WRITE|SND_SEQ_PORT_CAP_SUBS_WRITE,
                      SND_SEQ_PORT_TYPE_APPLICATION));
}

Basic Input

Once the connection is made, getting input is trivial with the help of snd_seq_event_input(). With this function ALSA will block until a MIDI event has occured and then it will return it. For programs that need to not block ALSA offers a non-blocking mode for midi. You could also use threads or IO multiplexing with poll() or select(). The below example demonstrates simple usage:

Note
use -lasound to link to the alsa library
snd_seq_event_t *midi_read(void)
{
    snd_seq_event_t *ev = NULL;
    snd_seq_event_input(seq_handle, &ev);
    return ev;
}

int main()
{
    midi_open();
    while (1)
        midi_read();
    return -1;
}

When this is run, it should generate a MIDI port, "listen:in" that you can connect applications to. When you send it any midi event, it will read it and then proceed to do absolutely nothing with it. This is not a useful program yet.

Useful Basic Input

To do something useful, the midi events need to be processed somehow. Lets look at what these events contain to see what can be processed. The definition of the snd_seq_event is as follows (from /usr/include/alsa/seq_event.h).

typedef struct snd_seq_event {
    snd_seq_event_type_t type;    /**< event type */
    unsigned char flags;          /**< event flags */
    unsigned char tag;            /**< tag */

    unsigned char queue;          /**< schedule queue */
    snd_seq_timestamp_t time;     /**< schedule time */

    snd_seq_addr_t source;        /**< source address */
    snd_seq_addr_t dest;          /**< destination address */

    union {
        snd_seq_ev_note_t note;           /**< note information */
        snd_seq_ev_ctrl_t control;        /**< MIDI control information */
        snd_seq_ev_raw8_t raw8;           /**< raw8 data */
        snd_seq_ev_raw32_t raw32;         /**< raw32 data */
        snd_seq_ev_ext_t ext;             /**< external data */
        snd_seq_ev_queue_control_t queue; /**< queue control */
        snd_seq_timestamp_t time;         /**< timestamp */
        snd_seq_addr_t addr;              /**< address */
        snd_seq_connect_t connect;        /**< connect information */
        snd_seq_result_t result;          /**< operation result code */
    } data;                       /**< event data... */
} snd_seq_event_t;

For basic use, the type member and the data member will be the most useful. A large portion of the midi events will likely either be notes or controls. This means that type will either be SND_SEQ_EVENT_NOTEON, SND_SEQ_EVENT_NOTEOFF, or SND_SEQ_EVENT_CONTROLLER and the union data will either have note or control set. These events contatin information on the note, velocity, control value, control parameter, channel, etc… These definitions are both available in your header files or at the alsa Docs

Back to midi_process(): In order to make this do something useful lets print out what events we are receiving, some information on the events and their timestamps. This information should be inside the snd_seq_timestamp_t and the data union. As notes and control values were just mentioned, lets constrain ourselves to them. Once the event is handled, it can just go out of scope as the alsa library handles the storage managment itself.

void midi_process(snd_seq_event_t *ev)
{
    if ((ev->type == SND_SEQ_EVENT_NOTEON)||(ev->type == SND_SEQ_EVENT_NOTEOFF)) {
        const char *type = (ev->type == SND_SEQ_EVENT_NOTEON) ? "on " : "off";

        printf("[%d] Note %s: %2x vel(%2x)\n", ev->time.tick, type,
                                               ev->data.note.note,
                                               ev->data.note.velocity);
    }
    else if(ev->type == SND_SEQ_EVENT_CONTROLLER)
        printf("[%d] Control:  %2x val(%2x)\n", ev->time.tick,
                                                ev->data.control.param,
                                                ev->data.control.value);
    else
        printf("[%d] Unknown:  Unhandled Event Received\n", ev->time.tick);
}

int main()
{
    midi_open();
    while (1)
        midi_process(midi_read());
    return -1;
}

Sanity Checks

It is considered a bad habit if the error values are not checked. The ALSA library goes with the convention of returning negative values on an error. This means that it is recommended to wrap everything in if(stmt<0){ error_handle();}. That can lead to some messy code that is tedious to work on, so this is a good problem to let the preprocessor take over. Although this error checking is not reqired, it does have the ability to save hours of debugging if you do not let a library function fail silently.

#define CHK(stmt, msg) if((stmt) < 0) { puts("ERROR: "#msg); exit(1);}
void midi_open(void)
{
    CHK(snd_seq_open(&seq_handle, "default", SND_SEQ_OPEN_INPUT, 0),
        "Could not open sequencer");

    CHK(snd_seq_set_client_name(seq_handle, "Midi Listener"),
        "Could not set client name");
    CHK(in_port = snd_seq_create_simple_port(seq_handle, "listen:in",
                      SND_SEQ_PORT_CAP_WRITE|SND_SEQ_PORT_CAP_SUBS_WRITE,
                      SND_SEQ_PORT_TYPE_APPLICATION),
        "Could not open port");
}

The Completed Program

Now that this first program is built, you can look at other aspects of ALSA midi, such as output, sequencing, and more. A copy of the program can be found here.

See Also