Sunday 23 January 2011

Introducing RMidi

In short, RMidi provides a way to work with MIDI from within R. The ambition is to open up the rich R language to applications in music informatics. There already exists R packages for working with audio, and I believe that the polygamous wedding of audio, MIDI and the strong data processing capabilities of R could become a powerful tool.

The basic data structure in RMidi is the RMidi matrix. This is a five column matrix with the following column headers: tick, command, channel, byte1 and byte2. The tick column sets the tick time at which the event will occur; the command column specifies the type of event (e.g. noteon, noteoff, etc.); channel is self-explanatory; byte1 and byte2 can differ depending on the command column, e.g. in noteon and noteoff messages, byte1 is the pitch and byte2 is the velocity.

If you are familiar with the MIDI standard you will notice that the RMidi matrix format is very similar to the way the actual MIDI protocol is laid out. I found this to be one of those rare occasions where minimising the level of abstraction actually simplifies the interface. Staying true to MIDI means that I am able to represent noteon, noteoff, control, pitch bend, etc., using the same data structure.

As an example, the sequence of notes C4, E4, G4 could look like this:

tick command channel byte1 byte2
[1,] 0 1 0 60 127
[2,] 92 1 0 64 127
[3,] 184 1 0 67 127
[4,] 92 2 0 60 0
[5,] 184 2 0 64 0
[6,] 276 2 0 67 0

In RMidi, all commands are represented by integers. The noteon command is represented by 1 and noteoff is represented by 2. In the matrix above, noteon for C4 is triggered at time 0, and noteoff for C4 at time 92. As you notice, the ordering of rows in the matrix is irrelevant.

In this first iteration of the software, RMidi supports only noteon and noteoff messages. This will of course change in the near future.

To create RMidi matrices you can either do it by hand, or you can use convenience functions. I recommend the latter approach. Using the midi.note function, creating the matrix above is as simple as

> midi.note(0:2 * 92, 92, c(midi.notes.c,
+ midi.notes.e, midi.notes.g) + 60)

The first parameter sets the start times of the notes, the second parameter the durations, and the third parameter is the actual pitches. See ?midi.note for full documentation.

Outputting this matrix as MIDI information is done using midi.play:

> m <- midi.note(0:2 * 92, 92, c(midi.notes.c,
+ midi.notes.e, midi.notes.g) + 60)
> midi.play(m)

Now is probably a good time to mention that RMidi uses the ALSA MIDI sequencer API, and is therefore (currently) Linux-only.

In order to route the output of RMidi to an actual audio module, I use qjackctl. Click the Connect button which opens the Connections window. In the ALSA tab you should see "rmidi" on your left hand side.

To set the tempo and pulses (ticks) per quarter note, use the midi.set.tempo and midi.set.ppq functions.

RMidi is able to read .mid files using the midi.read.file function. The set of supported messages is limited to whatever commands RMidi supports. In effect this means that while RMidi is able to parse an entire MIDI file, the resulting matrix may contain only a subset of the information in the file.

As a last touch to this initial version of RMidi, I've added the midi.scale function. midi.scale takes an arbitrary vector of numbers and "forces" the vector onto a musical scale. For example, if we have the following vector:

> v <- c(0.1, 1.2, 2.5, 4.3, 5.7, 8.9,
+ 10.6, 11.1, 15.5, 16.3)

We can apply a major scale to v like this:

> midi.scale(v, midi.scales.major)
[1] 0 0 2 4 5 7 9 11 14 16

Note that midi.scale always rounds down.

I've bundled around 300 scales with RMidi, all prepended with midi.scales. and all starting from C. More info at ?midi.scale.

RMidi is hosted on Google Code here on Github. If you spot any issues or have any feature requests, please file a bug report or just send me a message.

No comments:

Post a Comment