MIDI-Perl



MIDI-Perl is a series of Perl modules for manipulating MIDI files. I use it for algorythmic composition, but it can also be used as a data dump or an editor. You can find Sean Burke's sooper-sekrit module page complete with example scripts right here

Sean has arranged MIDI-Perl into a series of layers, allowing the user to interact with the 'guts' to whatever degree he is comfortable with. I personally have had very little reason to go much deaper than the top-level MIDI::Simple module.




At its heart, the core of MIDI::Simple is

event FlagValue, [FlagValue], ... ;

the most common events being n (note) and r (rest). For example,

r d96, c0;
n 60, d96, V100, c0;

tells MIDI::Simple to rest for a quarter note on channel 1, then play middle C with a velocity of 100 for a quarter note on channel 1. Looking piece by piece, the r tells the interpreter to advance the clock, the d (duration) says how much (96 MIDI ticks), the c says where (channel 1 - computers, of course, run from 0-15, but MIDI gear runs from 1-16 on the assumption that musicians are stupid, so c0 equals MIDI channel 1 on your gear). The second line says 'play a note', MIDI Note Number 60 (middle c - note also that the syntax can be either 'n 60' or 'n n60' - the interpreter assumes an unflagged value in a note event is a note number), duration is again 96 MIDI tics, the note velocity (how hard the note is struck), V, is 100 (or fairly hard, on a scale of 0 - 127), and the channel is again 1.

There is another common event, noop, which sets values without advancing the clock. No, it is not used to make chords or concurrent events - I'll get to that next - it is used to set common values. In MIDI-Perl, any flag retains its value until it's set again. In other words, the above 2 lines could be written as:

r d96, c0;
n 60, V100;

and the duration and channel values will continue on from the first line.

Expanding on the idea and using noop:

noop d96, c0, V100;
r;
n 60;
n 63;
noop n65, d48;
for(0 .. 7) { n; }

So in this example, all the parameters for the rest have already been set by noop on the previous line, so a simple r; is all that is needed to make our quarter note rest. Similarly, since the duration, channel, and velocity have already been set, it is only neccessary to tell the n what note you wish to sound - in this case a middle C, followed by an E-flat (MN#s 60 and 63, respectively). The following line uses the n flag to set the active note to F (MN# 65) and the duration to an eighth note (48 tics), and the line after uses a for loop to repeatedly call n;, creating 8 eighth-notes, all Fs. (Another way to repeatedly call n is to put multiple notes within a single call, so lines 2 & 3 would combine as n 60, 63; and the loop could be written as n 65, 65, 65, 65, 65, 65, 65, 65;)

So all of this is very linear - one note follows the last in lockstep - how DOES one create chords or concurrent events? Well, this is where things start to get a little more complicated; but there is a way, and it is the synch(); function. It accepts a list of function references, executes the first, rewinds to the same point in time, executes the second, and so on through the list. It is not quite as easy to think about as the previous code samples, since you have to wrap the notes in a function first:

noop d96, V85;
n 60;
n 64;
n 67;
n 71;
synch(\&note1, \&note2, \&note3, \&note4);
sub note1 { n 60, d768, V85; }
sub note2 {
n 65, d384, V93;
n 64, d384, V93;
}
sub note3 { n 67, d768, V85; }
sub note4 { n 72, d768, V85; }

This code plays a C Major7 chord, arpeggiated in quarter notes, followed by a C suspended 4th chord, resolving to a C chord. The common chord tones ring out for 2 whole notes, while the F and E sound out for a whole note each at a slightly higher volume.

There is also nothing preventing you from... well, nesting is not quite the right word. There's nothing stopping a function you've called from synch(); from ALSO calling synch(); on ANOTHER set of functions...


So, I kinda just jumped right into the middle there. Bear in mind that the modules themselves are extremely well-documented - if anything I write here is unclear, you can always open up the module itself in a pod viewer or text editor and see what Sean wrote on the subject. But for the sake of context, here is the general framework I use to write a piece of music in MIDI-Perl:

#!/user/bin/perl -w
use MIDI::Simple; #invoke the module

new_score; #create anonymous namespace to store data until written
$tempo = set_bpm();
$tempo = 60000000/$tempo; #convert bpm to microseconds per quarter note
set_tempo $tempo; #pass result to score
sync(\&write1, \&write2); #create some kind of 2-part thing
write_score 'example1.mid';
print "Score Written!\n";

sub set_bpm { #code to return a random tempo in beats-per-minute# }
sub write1 { #code to set $rest;
code to set $note
code to set $duration
code to set $velocity
if($rest) { r 'd'.$duration; }
else { n $note, 'd'.$duration, 'v'.$velocity; }
}
sub write2 { #as above# }


Once your shell tells you "Score Written!", you will have a brand new MIDI file called "example1.mid" in the same folder as the Perl script.

On the next page, I will get into the more gruesome details