With MIDI, you can make music without actually having to do waveform synthesis, the same way that PostScript lets you draw circles, without having to do what you'd have to do with bitmap formats -- having to do trigonometry to get (x,y) for ``every'' point on the circle, deciding how many points will suffice for approximating ``every point'', and so on, to say nothing of aliasing. For the purposes of this article, MIDI represents music as consisting of events -- where each event is basically ``turn a note on [or off]'' -- which happen at certain times, on a certain channel (what other sound synthesis terminologies call a ``voice'' or a ``track''), with a certain note number, at a certain volume.
When I first started reading up on the MIDI internals, it seemed an ideal format for composing music in Perl, and so I set out to make routines for encoding to and decoding from the binary format used in MIDI files. In early August 1998, I sent to CPAN my first release of the imaginatively named ``MIDI-Perl'', a mostly object-oriented interface to these encoding/decoding routines (Burke 1998).
However, as I stood back from the pile of code, I realized that while I had created a fine object model for representing MIDI files, the most basic data structure, the MIDI event, was no more suited to musical composition than editing raw PostScript is for making architectural blueprints.
And so I found myself in the not-entirely-anticipated position of designing and implementing a language that would provide an interface for musical composition, a language that would at some level use the MIDI encoding routines for output, but which would necessarily be several levels higher in abstraction from them.
Now, it is the work of language designers to make their languages so that they will -- to quote Larry Wall -- ``make easy things easy, and hard things possible.'' What's implicit in this idea is that there's two domains that a programming language bridges -- the way we think of a problem, and the way we need to code up the program that solves the problem. Making ``hard things possible'' is a matter of making the language open-ended enough that one can do just about anything with it, while making ``easy things easy'' is a matter of making sure that things easy to think of (whether ``I want to replace all the times I say 'Jimmy' with 'James''', or ``I want to sort these words by the last letter'') are easy to code, probably by being codable concisely, and in the same terms as we think of them in.
In the remainder of this article I'll first explain my ideas about types of simple languages, and then from that I'll show how these ideas led me to design the MIDI::Simple language the way I did. The style here is clearly diaristic, because my goal is not to document MIDI::Simple, but instead to illustrate haw specific problems in language design, over time, led me to make MIDI::Simple the way it is. Readers whose interests do not include both language theory and music, can skip sections as they wish. But I hope that my points here are not specific to just these specific problems.
while(<DATA>) { next if /^\s*\#/s; chomp; my($addr, $file, $re) = split(/\s+/, $_, 3); next unless length($addr) && length($file) && length($re); save_finger_if_matching($address, $output, $regexp); # which we'll pretend we got from elsewhere; say, via require() } print "Done\n"; __END__ # list of addresses, in the format: # address file regexp foo@bar.com fooey.txt office # hm, not any more: zazy@bar.com zazout1.txt phone pati@lebar.fr patipata.txt ici
This program basically implements a language, and then has, after its ``__END__'', instructions in that language. Now, the language is simple: lines that start with optional whitespace and then a hash-mark, are ignored (so you can use them for comments), and lines that can be split into three non-null fields are considered commands to save that field-1's finger output in file field-2, if it matches field-3. And this language doesn't have anything like what we'd expect in a real programming language -- it has no flow-control structures, no variables, no ability to define procedures. But it is a language in the sense that it has a syntax -- i.e., an order things have to be in to make sense -- and it has semantics -- i.e., elements in the language have a certain order they appear in, and when they do, they do something or mean something.
Now, even by that token, this language's semantics are quite restricted, so it's a ``language'' only in the same way that a simple markup language like HTML is a language, or the way that the notation for recording chess moves is a language, or the way that common music notation is a language. None of these are languages you could write a program to calculate 5 * 12 in, but each is good at what it's for.
And none of these languages, in fact, make hard things possible. If, for example, you wanted to represent in the finger-capturer language ``save the finger output of foo@bar.com to fooey.txt if it matches office and if today is Tuesday'', you'd be out of luck. You could change the code that implements the language to support an optional ``day of week'' field, and that would work, and that may be the end of your need to extend the language.
But if you needed to denote an arbitrary criterion, not just ``is today a particular day of the week'', but anything at all, whether ``does this address match /\.fr$/'', or, who knows, ``is there an even number of vowels before the @ in the address?'', then you've hit a wall in your approach to the language -- because the basic approach has been to implement a language in Perl, but basically independent of it. It's a Novel Language. In other words, the language is self-contained, and never gets to use the features of Perl (or whatever language you're implementing the parser/evaluator in). For example, Perl has variables, but that doesn't mean this language gets to have variables.
Now, there's two ways to go here in expanding the functionality of the little language you've got so far:
Now, this is not to say that implementing just any high-level language in Perl (or any other language) is a trivial prospect -- not by a long shot -- but implementing in Perl a language with simple syntax and simple semantics, like a LOGO dialect, or even a simple Lisp dialect (as in Abelson & Sussman 1996), is quite doable. And then there's the other approach:
require(``my_great_lib.pl'')
makes a new language. First off,
I didn't say it was a entirely new language. Second, it does what you want (i.e., you have the semantics), and
that's probably all you're interested in, right? Now, the advantage to
using an Extensional Language is that you are really still in Perl, so you
get to use all the features of Perl -- Perl's conceptions of variables,
flow control, and so on. The disadvantage is that you have to be within the
confines of Perl syntax. (However, Perl syntax is so free-form that this is
as great a problem as it sounds.)
There's a parallel here in the creation of jargons (and remember what Larry Wall keeps saying about Perl having a basis in natural languages): Chemists, when they decided they needed a language to talk very precisely about chemical structures, could have decided to make up a whole new rich Novel Language, with verb tenses and noun compounding and and all the goodies we expect in a natural language, but also with exact ways to describe chemical structures. But this would mean having to figure out the best way to implement predicate argument structure, and pronoun binding, and phonology, and all the unpleasant cruft of natural language -- the same way that making a Java virtual machine in Perl would mean having to actually write code implementing exception handling and whatnot.
So instead, chemists implemented chemical names as an Extensional Language
based on English (or whatever language they're speaking). Now, ``boron
trifluoride'' isn't exactly core English, but it's more English than it is
Thai or Klingon; and, I admit, when the chemical ``extension'' to English
starts spawning forms like ``dichloro-1,2-ethane'' and
``2-chloro-4-ethylamino-6-isopropylamino-1,3,5-triazine'', it begins to
leave any natural language behind. But when a chemist says ``boron
trifluoride is an inorganic gas'', you understand that something is an inorganic gas, even if you can't picture what a boron trifluoride
molecule looks like. Similarly, if you're reading Perl code and you see &funk_it
, or $this->thingify
, you probably can't guess what funk_it
or thingify
do, but at least you can tell they're subroutines, whereas this might not
be clear if you were reading an unfamiliar language.
(The astute reader may have already noted that you can actually mix the approaches of a Novel Language and an Extensional Language. I'll give an example of this later.)
# event, wait-time, channel, note number, volume ['note_on', 0, 1, 25, 96], ['note_off', 96, 1, 25, 0], ['note_on', 0, 1, 29, 96], ['note_off', 96, 1, 29, 0], ['note_on', 0, 1, 27, 96], ['note_off', 96, 1, 27, 0], ['note_on', 0, 1, 20, 96], ['note_off', 192, 1, 20, 0],
...and so on, for a total of thirty-three such lines.
I won't explain the exact details of this format, as it's not relevant to the rest of this article, but suffice it to say that requiring a composer to represent notes in the above format was clearly not making ``easy things easy'', because this is not how composers (or anyone) think of notes. A more human conception of notes is that they have these qualities:
* duration: expressed as a quarter note, half note, etc. * pitch: expressed as a note-letter and octave number, e.g., "A5" * volume: expressed either numerically or as one of the common abbreviations from sheet music, e.g., "mf" for "medium-loud" * and, for purposes of MIDI, a logical channel number between 0 and 15
And in an early attempt at a music language (implemented as a Novel Language, with an interpreter written in Perl, not shown), the above code turned into:
note c1 f qn Cs2 # Cs2 = C sharp, octave 2 # qn = quarter note note c1 f qn F2 note c1 f qn Ds2 note c1 f hn Gs1 # yup, hn = half note note c1 f qn Cs2 note c1 f qn Ds2 # ...and so on...
(Incidentally, this tune I'm trying to represent is the Westminster Chimes, better known as that tune most fancy clocks play when they chime the hour. See Sturdy 1998.)
Now, what makes these typical lines of note-making code especially different from typical lines of code written in a high-level programming language, is the high degree of redundancy between them. The typical event's parameters of duration, pitch, volume, and channel, are mostly the same from one event to the next. So, in the name of brevity, I decided that each note's properties should be inherited from the previous note, overridden with whatever new properties are defined in this call. And so, in the new language thus defined, the above code can be rephrased as:
note c1 f qn Cs2 note F2 note Ds2 note hn Gs1 note qn Cs2 note Ds2 # ...and so on...
I was on the way to developing a workable language. But then, as I came to want to expand the language, I arrived at the same question raised in the above example of the finger-output capturer -- should I continue implementing the language as a Novel Language, probably ending up with a moderately simple language as far as flow control, functions provided, and variables and data structures; or should I implement it as an Extensional Language?
I had two considerations:
1. I wanted whatever language I ended up with, to be sufficient to the task of algorithmic composition -- composition where the notes are at least partially determined by the output of algorithms that the composer develops, and which may be arbitrarily complex. Composers like Xenakis (1992) use algorithms that involve some fairly complex algebra. If I wanted my simple Novel Language to be able to do anything like that, I'd need to bear down and implement algebraic functionality for that language. Ditto for providing mathematical functions like sines and logarithms, and at least scalars and lists as data structures, and functions to manipulate them. That sounded like a lot of work.
2. There are both kinds of music languages around already -- very simple languages, notably the language ``abc'' (Walshaw 1998); and composition extensions to high-level languages, notably the Forth-extension HMSL (Burk 1998). (I don't mean to suggest that these are all that's out there; rather, I use them here as representative of the field as I understand it. See Roads (1996:783-818) for a good survey.) But, I asked myself, which kind of language would it be most useful to the world (or, for that matter, me) to have now? The abc language seemed rather well thought-thru, implemented, and supported by various ports and utilities. So implementing a simple Novel Language for music, in Perl, would essentially be a mere duplication of the effort that went into (and continues to go into) abc. As to Extensional Languages based on high-level languages, HMSL seemed to me the richest. I was even willing to learn Forth to deal with it! However, the implementation of Forth that it's based on apparently works well only under MacOS and AmigaOS. So that ruled it right out, as I don't have either OS at my disposal. As to other such compositional languages that I'd heard of and could find information on, some were based on languages I wouldn't want anyone to have to compose in (e.g., C++), or they focused on acoustics and digital signal generation (e.g., Common Lisp Music) more than I would ever want to (and, incidentally, more than is practical in MIDI).
So, I thought, if I implemented my music language as an Extensional Language based on Perl, users/composers could write their programs and be able to run them on any Perl interpreter, on all the varied platforms Perl has been ported to. People new to programming who wanted to learn my Extensional Language, would have at their disposal all the documentation and support that all Perl learners have. Algorithmic composers would have at their disposal all the data structures and mathematical functions Perl provides. And -- no small consideration -- it'd save me a ton of work that'd otherwise go into working out the Novel Language's even rudimentary data structures and functions.
So the Extensional Language approach won. I scrapped the Novel Language and reimplemented its semantics (plus a lot more) as the MIDI::Simple module that comes in the MIDI-Perl suite, version 0.7 or later. The remainder of this article given a guided (and not entirely exhaustive) tour of the features of MIDI::Simple, with notes as to the line of thought that led to some features come out the way they did.
MIDI::Simple basically deals with a data structure I call a ``score'', which is basically a list of events, each with a time-offset; and some state variables that are mostly for storing defaults for notes. This is a partial list of the state variables, which will do for the moment:
* $Time
-- the time (in a notional unit called ``ticks'',
which is usually 1/96th of a quarter note) at which the next score event
will take place. * $Duration
-- the duration, in ticks, of the
next note/rest, by default. * $Channel
-- the channel number
the next note will occur on, by default. * $Volume
-- the
volume, as a number 0 to 127, the next note will have by default. *
@Notes
-- the pitches (stored as MIDI note numbers) that will
be added, by default, to the score by the next call to the n
routine, discussed below. Now, I expect that only the most advanced user
will have to deal with the contents of a score directly, because I have
supplied these functions that give the user an interface for manipulating
it:
* the function new_score
which initializes a ``score object'' -- i.e., a score along with some
incidental state variables I'll discuss later.
* the functions n
and r
, to add notes and (virtual) rests to the score with various parameters
(duration, pitch, volume, time, channel). (And there's also a function noop
, whose interaction with n
and r
is explained later.)
* functions to add arbitrary MIDI events to the score -- e.g.,
patch_change
, to set the patch (i.e., a simulated instrument, like piano, or banjo) for
a given channel.
* a function write_score
to write the newly composed score as a MIDI file.
* a function read_score
to read a single-track MIDI file as a score, which the user can add to.
* a function synch
, to take the output of several user-provided functions, and place them in
the score at the same time -- useful for making a score's measure out of
the output of a few different ``instruments''.
Using the language specified as above, the Westminster Chimes could be notated like this:
use MIDI::Simple; new_score; patch_change 1, 8; # set Channel 1 to Patch 8 = Celesta n c1, f, qn, Cs2; n F2; n Ds2; n hn, Gs1; n qn, Cs2; n Ds2; n F2; n hn, Cs2; n qn, F2; n Cs2; n Ds2; n hn, Gs1; n qn, Gs1; n Ds2; n F2; n hn, Cs2; write_score 'chimes.mid';
Much more concise than the forty-odd lines that this same thing takes if expressed as low-level MIDI events and calls!
As to where the value ``current octave'' comes from: it's a number stored
in a state variable called $Octave, which can be set directly, or which can
be set by calling n
or r
with a parameter in the form ``o6'' (where ``6'' can be replaced by any
number ``0'' to ``10''), and incidentally will be set as a side-effect of
calling n
or r
with any absolute note specifications.
So in other words, these lines of code all end up setting
$Octave
to 2, whether directly, or as a side-effect.
$Octave = 2; $Octave = 3; --$Octave; n o2 Cs; n Cs2; # Or even... r n25;
I've established that ``Cs'' would mean, not very surprisingly, ``C sharp in the current octave''. To this I added a way to mean ``... an octave above the current one'' or ``... an octave below the current one'':
* Cs_u1 ("u" for "up") * Cs_d1 ("d" for "down")
...where ``1'' can be replaced by any positive integer, just so long as the resulting note is within the note range of MIDI devices, C0 to G10.
So the upshot of this, is that this line of code:
n c1, f, qn, Cs2; n F2; n Ds2; n hn, Gs1;
can be expressed as:
$Octave = 2; n c1, f, qn, Cs; n F; n Ds; n hn, Gs_d1;
or as:
n c1, f, qn, Cs2; n F; n Ds; n hn, Gs_d1;
In this latter case, only the first note is specified absolutely, and sets the current octave to 1, for all the following notes to use. This means that all you have to do to move all these notes up two octaves, is to change the ``Cs2'' to ``Cs4''.
noop
n c9, ff, n41, qn; r; n; r; n; r;
which adds to the score a quarter note, a quarter rest, a quarter note, and a quarter rest, all played on the acoustic bass drum.
However, the way this is coded, above, looked to me a perfect violation of
``uniformity'', a concept I first saw expressed in Weinberg's The Psychology of Computer Programming, in the chapter ``Some Principles for Programming Language Design''
(1971:219). To Weinberg's way of thinking, uniformity is a psychological
principle, where users/programmers expect that things that look similar
should do similar things, and conversely that things that look different
should do different things. But the first call to n
, above, and the second and third calls to n
, look very different, even though all they do the same thing. What I
wanted was a way to set up all the state variables, and then be able to
just say ``n;r;n;r;''. Now, I could just say:
$Channel = 9; $Volume = 112; $Duration = 96; @Notes = (41); n; r; n; r;
But that seemed inelegant. What I ended up doing was adding another
function, called noop
(for ``no-operation''), which parses options just the same as n
and r
, and thereby has all the side-effects, but doesn't actually affect the
score. For example, consider these three lines of code:
n qn, C3; # C3 = n36, by the way r qn, C3; noop qn, C3;
The first one has the main effect of adding a note to the score, and
incrementing $Time
by the duration of a quarter note. The second one adds nothing to the
score, but has the main effect of incrementing
$Time
(this is how I implement rests -- just time consumed). And the third one
alters neither the score, nor $Time
. But it does have all the same side-effects as the first two: it sets $Duration
to the duration of a quarter note, and it sets @Notes
to (36)
. With noop
, you get to write code like:
noop c9, ff, n41, qn; # The setup... n; r; n; r; # ...and the work.
This not to say that you have to do it this way; but allowing for the organization of code to reflect different ways of organizing thought is the Perl way: ``there's more than one way to do it''.
(Incidentally, I do not mean to imply that ``noop'' is to be used only for dealing with percussion; it's simply that ``noop'' is meant to solve the kinds of non-uniformity problems which became most apparent to me in percussion code.)
It may have occurred to the reader that this is sufficient for manipulating only one score at once (although I expect that that is the typical case), but that this doesn't let one manipulate several scores at once. For this purpose, I've provided an OOP interface, summarized in this chart:
Procedural OOP ---------------- ------------------------------ new_score $score = MIDI::Simple->new_score $Channel = 3 $score->Channel(3) $Octave = 4 $score->Octave(4) @Notes = (30,34) $score->Notes(30,34) push @Notes, 36 push @{ $score->Notes_r }, 36 or: $score->Notes( $score->Notes, 36 ) n qn, Cs3 $score->n(qn, Cs3) noop o7, ff $score->noop(o7, ff) write_score 'X.mid' $score->write_score('X.mid')
(In point of fact, $score->n
and n
actually call the same sub, &MIDI::Simple::n
, which does double-duty as a procedure and as a method. Readers interested
in how this is done should read the source for MIDI::Simple. It is not for
the faint of heart, and I by no means hold it up as a model of how this is
done -- to say nothing of whether this should ever even be done this way
again!)
synch
is a procedure which takes a list of code references (generally of the form \&foo
, where foo
is a sub the user has defined for adding notes to the score). For each one
of these,
synch
sets $Time
back to whatever it is when you called
synch
, calls the user's routine, and then notes the value of
$Time
. After calling all the routines, synch sets $Time
as far ahead as any of the user's routines ended up setting it. In other
words, it makes the effects of given routines synchronous -- i.e., occurring (well, at least starting) at the same time. In other
words,
synch
orchestrates (literally as well as figuratively) several routines, each of
which would generate a measure's (or really any size interval's) worth of
notes or rests for a given virtual instrument.
Each user routine, incidentally, should expect its first parameter to be the score object it should add to, and should add to that score object via the object-oriented interface to MIDI::Simple instead of the procedural interface.
Now, its simplest use for synch
would be something like:
use MIDI::Simple 0.7; Main: new_score; @subs = (\&tom_two, \&double_clap); foreach (1 .. 10) { synch(@subs) } write_score("rhythm1.midi"); exit;
Subs: sub tom_two { my $it = shift; # n41 on c9 = low floor tom $it->n(c9, ff, n41, qn); $it->r; # qn = 1/4 note, ff = very loud $it->n(f); $it->r; # f = loud }
sub double_clap { my $it = shift; # n39 on c9 = hand-clap $it->n(c9, ff, n39, sn); # sn = a 16th note $it->n; # This only takes up 2 16th-notes # of time, but that's fine. }
Now, this generates twenty exactly identical measures. Minimalism aside, most music should vary from measure to measure. (This is not to say that measurewise composition is the way to go about making music, but it does seem to work well for percussive examples like I'm using here.) The way I go about having my instrument subs vary their effect from instrument to instrument is to have the first sub be a call to a measure counter, and to have the other subs be sensitive to the output of this. For example, add this sub:
sub measure_counter { my $it = shift; $it->r(wn); # a whole rest ++$measure; }
and change tom_two
to the following, to make it do two different things, depending in whether
or not $measure
is greater than 4:
sub tom_two { my $it = shift; if($measure < 5) { # For the first four measures... $it->n(c9, f, n41, qn); } else { # For measures after that... $it->n(c9, ff, n41, qn); $it->r; $it->n(f); $it->r; } }
Then we just change this line in the program:
@subs = (\&measure_counter, \&tom_two, \&double_clap);
And voilà, simple percussion. From there it's not hard to get more ornate really quickly:
use MIDI::Simple 0.7; Main: new_score; @subs = ( \&measure_counter, \&boom, \&tboom, \&clap ); foreach (1 .. 24) { synch(@subs) } write_score("rhythm2.midi"); exit;
Subs: sub measure_counter { my $it = shift; $it->r(wn); # a whole rest ++$measure; } sub boom { my $it = shift; return if $measure % 4 < 2; $it->n(c9, ff, n41, qn); $it->r; $it->n(f); r; } sub tboom { my $it = shift; return if $measure % 4 < 2; # 42 = 'Closed Hi-Hat' ; 43 = 'High Floor Tom' # In quick succession... $it->n( c9, ff, n43, sn); $it->n( n42 ); $it->r(dqn); # dqn = dotted quarter note/rest $it->r( c9, ff, n43, sn); $it->n( n42 ); $it->r(dqn); } sub clap { my $it = shift; return if $measure < 4; $it->n(c9, ff, n39, sn); $it->n; $it->r(dqn); $it->r(hn); }
Now, I promised that I'd show you a little Novel Language based on an
Extensional language. While I was tossing together up the above code,
originally just to test synch
's functionality, I decided I wanted a more complex instrument. For some
reason, I had the rhythm track to The Talking Heads' ``Psycho Killer''
stuck in my mind, and decided to try to code it up. I tried it with
combinations of eighth notes and rests and whatnot, but couldn't put my
finger an it. So I tried making up a very simple Novel Language where
whitespace is ignored, ``!'' means to hit the ``side stick'' (note 37 on
that magic channel 9) for a sixteenth note, and anything else makes a
sixteenth rest:
sub psycho { my $it = shift; my $pattern = " !.!. !.!. !.!. !.!. " ; # just a start $pattern =~ tr<\cm\cj\t ><>d; # kill whitespace warn "<$pattern> doesn't add up to a whole measure\n" unless length($pattern) == 16; $it->noop(c9, mf, n37, sn); # setup: n37 on c9 = side stick foreach (split('', $pattern)) { if($_ eq '!') { $it->n } else { $it->r } } }
From here I just monkeyed around with the quoted string on the line after my $pattern
. I eventually arrived at:
" !.!.!. !!!!!! !.!. " ;
...which indeed seemed to be the rhythm I was thinking of! To hear it,
paste the psycho
sub into the program above, and add it to the
@subs
line, like so:
@subs = ( \&measure_counter, \&psycho, \&boom, \&tboom, \&clap );
$measure
(or whatever you call it) to decide what notes to produce is the most
straightforward way to produce higher-level structures in music in
measurewise composition. You've already seen, above, a sub that uses the
expression
$measure % 4 < 2
to control what notes it adds to the score. The ``%'' operator (``modula''
or ``mod'', a.k.a. ``remainder'' -- so that X % Y
means ``the remainder of dividing X by Y'') is one that a surprising number
of people are unfamiliar with, so let's spell out just what it does here,
for various values of $measure
.
$measure = 1 | 1 % 4 = 1 | 1 < 2 is True $measure = 2 | 2 % 4 = 2 | 2 < 2 is False $measure = 3 | 3 % 4 = 3 | 4 < 2 is False $measure = 4 | 4 % 4 = 0 | 0 < 2 is True $measure = 5 | 5 % 4 = 1 | 1 < 2 is True $measure = 6 | 6 % 4 = 2 | 2 < 2 is False $measure = 7 | 7 % 4 = 3 | 3 < 2 is False $measure = 8 | 8 % 4 = 0 | 0 < 2 is True
The ``phase'' (i.e., the starting point of the repeating pattern) of this
T-T-F-F pattern can be changed by using a positive or negative number N in
an expression ($measure
+ N) % 4. Consider that with for N = 1, you get a F-T-T-F pattern:
$measure = 1 | (1+1) % 4 = 2 | 2 < 2 is False $measure = 2 | (2+1) % 4 = 3 | 3 < 2 is True $measure = 3 | (3+1) % 4 = 0 | 0 < 2 is True $measure = 4 | (4+1) % 4 = 1 | 1 < 2 is False $measure = 5 | (5+1) % 4 = 2 | 2 < 2 is False $measure = 6 | (6+1) % 4 = 3 | 3 < 2 is True $measure = 7 | (7+1) % 4 = 0 | 0 < 2 is True $measure = 8 | (8+1) % 4 = 1 | 1 < 2 is False
For N = -1, you get T-T-F-F.
$measure = 1 | (1+-1) % 4 = 0 | 0 < 2 is False $measure = 2 | (2+-1) % 4 = 1 | 1 < 2 is True $measure = 3 | (3+-1) % 4 = 2 | 2 < 2 is True $measure = 4 | (4+-1) % 4 = 3 | 3 < 2 is False ...etc...
These structures for cyclically queuing or out, or alternating between two
kinds of measures, are most useful for controlling rhythm tracks -- but the
same expression, ($measure
+ N) % 4 (or for whatever integer in place of 4), can be used for repeating
an 4-measure sequence. Consider the Westminster Chimes' four measures:
my @phrases =( [ Cs, F, Ds, Gs_d1 ], [Cs, Ds, F, Cs], [ F, Cs, Ds, Gs_d1 ], [Gs_d1, Ds, F, Cs] );
These four measures can be repeated ad infinitum (starting, appropriately, with the first measure), with code such as:
sub first { my $it = shift; $it->noop(c1,mf,o3,qn); # setup my $phrase_number = ($measure + -1) % 4; # There it is! my @phrase = @{$phrases[$phrase_number]}; foreach my $note (@phrase) { $it->n($note) } }
If you change ($measure + -1) % 4
to ($measure + 0) % 4
, you get a measure that does everything one measure later than before.
Change 0 to 1 and it's another measure later. That way, when the piece
starts, with a $measure
of 1, you get the third element of
@phrases
. Since you probably want it to keep quiet until measure 3, where it can
start at the beginning of @phrases
, just add a line to the effect of:
return if $measure < 3;
at the start of the sub. Now, you can have several subs, just like
&first
, above, that play all the same notes, from @phrases
, but just in different measures. This structure, called a ``canon'', may
seem very abstract, but it's common in songs, where it's called a
``round''. If you remember singing:
Group 1: Row row row your boat / Gently down the stream / Merrily... Group 2: Row row row your boat / Gently... Group 3: Row row... [and so on]
Then you were singing exactly the same kind of musical structure you get
out of &first
, copied and adjusted for different Ns in
($measure + N) % 4
. Namely, compare the subs &first
,
&second
, &third
, and &fourth
, in the this program, which plays a round based on the Westminster Chimes:
[Presumably this'll be in a greybox]
use MIDI::Simple .68; my $measure = 0; # changed by &counter my @phrases =( [ Cs, F, Ds, Gs_d1 ], [Cs, Ds, F, Cs], [ F, Cs, Ds, Gs_d1 ], [Gs_d1, Ds, F, Cs] ); @bass_line = ( F, Cs, Ds, Gs_d1, Gs_d1, Ds, F, Cs); new_score; # Some MIDI meta-information: copyright_text_event "1998 Sean M. Burke"; text_event "Title: Westminster Round"; # Patch inits: # patch 16 = Drawbar Organ. 8 = Celesta. patch_change 0, 16; patch_change 1, 8; patch_change 2, 8; patch_change 3, 8; patch_change 4, 8; for(1 .. 8) { synch(\&count, \&bass, \&first, \&second, \&third, \&fourth ); } r hn; # pause. take a bow! write_score("round2c.mid"); dump_score; exit; sub count { my $it = shift; ++$measure; $it->r(wn); # whole rest } sub first { my $it = shift; $it->noop(c1,mf,o3,qn); my $phrase_number = ($measure + -1) % 4; my @phrase = @{$phrases[$phrase_number]}; foreach my $note (@phrase) { $it->n($note) } } sub second { my $it = shift; return if $measure < 2 or $measure > 5; $it->noop(c2,mf,o4,qn); my $phrase_number = ($measure + 0) % 4; my @phrase = @{$phrases[$phrase_number]}; foreach my $note (@phrase) { $it->n($note) } } sub third { my $it = shift; return if $measure < 3 or $measure > 6; $it->noop(c3,mf,o5,qn); my $phrase_number = ($measure + 1) % 4; my @phrase = @{$phrases[$phrase_number]}; foreach my $note (@phrase) { $it->n($note) } } sub fourth { my $it = shift; return if $measure < 4 or $measure > 7; $it->noop(c4,mf,o6,qn); my $phrase_number = ($measure + 2) % 4; my @phrase = @{$phrases[$phrase_number]}; foreach my $note (@phrase) { $it->n($note) } } sub bass { my $it = shift; my $basis_note = $bass_line[($measure - 1) % 4]; $it->noop(c0,fff,o3, wn); # fff = REAL LOUD. $it->n($basis_note); } __END__
* The language has plenty of functions (only some of which are discussed in
this article) for dealing with notes, or characteristics of notes. However,
functions for larger structures are either oddly implemented (as with set_tempo
and time_signature
) or missing (there are no functions implementing ``crescendo'' or
``sostenuto'', for example).
* Currently, n
/r
/noop
accepts abbreviations like ``qn'', ``hn'', ``den'', for quarter note, half
note, dotted eighth note, and so on. However, these abbreviations assume
that you have just these names for these notes. But these abbreviations may
prove counterintuitive if, for example, you call a quarter note not
``quarter note'', but a ``crotchet'' (as much of the English-speaking world
does), or, for that matter, a ``Viertelnote'', ``semiminima'', ``negyed
hangjegy'', or a ``neljännesnuotti''. Future versions of MIDI::Simple
should provide a simple, documented interface for adding or changing
abbreviations.
* Currently, MIDI-Perl encodes only to MIDI files. However, the MIDI event
format is also (in fact, is primarily
) used as the basis of a serial communications protocol connecting
computers, synthesizers, drum machines, and other devices. The ability to
interface with devices attached to a MIDI port will likely require OS- and
hardware-specific programming well beyond what I can produce or support.
But at least in the case of Unixes that provide /dev/midi
and /dev/sequencer
, it should be possible, with a little experimentation, to provide
straightforward support for real-time access, which can be incorporated
into MIDI::Simple. As an example of a useful application based on real-time
MIDI access, a program using MIDI::Simple could generate a stream of
``music'' as a rendering of an incoming numeric data stream, such as stock
market data, or the status of a computer network.
But finally, I don't want to suggest that MIDI::Simple is by any means the definitive approach to music languages. As the Perl motto goes, ``there's more than one way to do it'' -- and I hope that others will take this to heart and implement their own MIDI-based languages, according to their own ideas of musical structure and language design.
Burke, Sean M. 1998. MIDI-Perl (including MIDI::Simple). <http://www.cpan.org/authors/id/S/SB/SBURKE/ > or <http://www.cpan.org/~sburke/ >
Sturdy, John C. G. 1998. The Cambridge Chimes. <http://www.ely.anglican.org/parishes/camgsm/chimes.html >
Walshaw, Chris. 1998. The abc Music Notation Language Home Page. <http://www.gre.ac.uk/~c.walshaw/abc/ >
Weinberg, Gerald M. 1971. The Psychology of Computer Programming. New York: Van Nostrand Reinhold.
Xenakis, Iannis. 1992. Formalized Music: Thought and Mathematics in Composition. Stuyvesant, NY: Pendragon Press.