Allegro Music Representation Language

Roger B. Dannenberg
Carnegie Mellon University

November 2000

Allegro is a simple, declarative, text-based language for music representation. Allegro is very similar to Adagio, but it is extended somewhat to make the representation of beats and attributes simpler and more uniform.

Allegro is designed for use with Aura, and there is a mapping from Allegro to message streams in Aura. There is an internal, object representation of Allegro as well, which is implemented in both C++ and Serpent (the Aura scripting language).

Support for Aura was the major factor driving the redesign of Adagio to form Allegro. Readers are referred to  Generic Music Representation for Aura for a description.

Basic Concepts

Allegro represents notes, attribute/value pairs (typically updates to notes), and timing information in the form of tempo and beats. Allegro is a free-form ascii text language similar to Adagio (a notation introduced in the CMU MIDI Toolkit). In its simplest usage, every line of text represents a note. Every note is a set of attributes separated by white space.  For example, the following indicates a quarter note at middle C:

Q C4

As in Adagio, letters are reserved for duration (W for Whole, H for Half, Q for Quarter, I for eIghth,  S for sixteenth, and U for numerical dUration) and pitch (A through G, and P for numerical Pitch), leaving the rest of the alphabet for various other attributes.

Allegro allows each note to be tagged so that the note can be referenced by updates at a later time.

Standard Attributes

Notes have a set of attributes as follows. Attribute names here are Aura attributes, which use the last letter to convey type information. For example, "chani" should be read as attribute "chan" of type "i"nteger. An "r" suffix is for "r"eal values (double precision floats). When used with Aura, notes are represented by a sequence of messages, each conveying an attribute/value pair. Some of the messages are "stateful," that is, they change the way in which future messages are interpreted. For example, after the "chani" message, subsequent messages apply to the indicated channel.

Addressing Channels and Voices

As in MIDI, some updates apply to channels and some apply to note instances (i.e. note numbers or key numbers). A message stream has state consisting of the current channel and the current key. Initially, the channel is zero and the key is -1, which indicates that messages are not for any particular note/sound object.

A few attributes, such as beatr and tempor, key signature information, and time signature information apply to all channels automatically. Otherwise, the chani attribute can be set to -1 to indicate all channels (although this may not always be meaningful).

Allegro Syntax

Because Allegro has no nested expressions or complicated syntax, I will not present a detailed formal grammar. The previous section outlined the semantics of attributes. This section describes the syntax to denote an attribute. Additional syntax and semantics for tempo and beats is given in the next section.

Allegro is case-insensitive, that is, upper and lower case letters are treated as the same, except that full attribute names, when spelled out, are case-sensitive. By convention, attributes are lower case for uniformity and simplicity. In the following, an italic n indicates a decimal integer, e.g. "7", and an italic r indicates a decimal floating point number, e.g. "7.34".

Aura Attribute

Allegro Syntax

chani Vn
keyi Kn, Afn, An, Asn, ..., Gfn, Gn, Gsn
gater Lr, Lppp, ..., Lfff
pitchr Pr, Afn, An, Asn, ..., Gfn, Gn, Gsn
durr (or gater <= 0 after the duration) Ur, S, I, Q, H, W, etc.
(time is not an Aura attribute) Tr, TS, ..., TW, etc.
(next time is not an Aura attribute) Nr, NS, ..., NW, etc.
(syntax for additional attributes/value pairs) -attribute:value

Notes vs. Updates

A new note or sound object is created by the appearance of a field with any of the following initial letters: P, A, B, C, D, E, F, G (pitches), or U (duration). If neither an explicit pitch or duration appears, then an update is generated. In terms of Aura streams, an update means that no "gater" attribute will be generated, and no sound resource will be allocated. It is assumed (but not checked) that an update applies to an existing resource and merely modifies some of its attributes.

Since a pitch specification indicates a new note, use something like "-pitchr:60" to update pitch. Consider using "bendr" instead, or generate notes anyway and send them to a synthesizer that implements something like MIDI  "mono" mode.

Sticky Attributes

Most attribute values need not be specified if they do not change from their lexically previous value. However, a blank line does not mean "repeat the last note;" at least one attribute must be present to signify anything at all. Only if a note is specified using at least a pitch or duration will certain default values be used. For example, default pitch is ignored if only pitch bend is updated.

To get MIDI-like semantics, the keyi (K) attribute should not be specified at all. If you specify pitch using P or A through G, then the key will be implied by the pitch, and notes will therefore be identified as in MIDI. For example, a MIDI note-off message uses pitch to say which note to turn off. If keyi (K) is not specified, control changes will have no keyi and therefore apply to the channel as a whole. This also corresponds to MIDI. The one exception to this is that pressure (polyphonic aftertouch) MIDI messages need a key number. If you specify pitch along with the control change, a new note will be generated. Therefore, you should use something like "V5 KG4 -pressurer:50", which says "on channel 5, pressure 50 on key 67 (g above middle c)."

To get more general semantics, every note can have an identifier. This allows multiple notes with the same pitch or for pitches of notes to change without ambiguity. Identifiers are specified by the keyi (K) attribute, for example "V5 K279 G4" creates a note (g above middle c) and labels it with the identifier 279. With this style, all updates should normally have a keyi (K) attribute, for example "V5 K279 -pressurer:50" sends  a pressure changes to note 279. If "K" is omitted from a control change specification, then the control change applies to the channel, just as with the "MIDI style" in the previous paragraph. In other words, there is an implicit "K-1" attribute on all non-note attribute specifications. Thus, keyi is not a "sticky" attribute.

The initial default time is zero. The default time for the next event is computed as follows: Set the default time to the time of the current event. If a "N"ext attribute is given, the default time is incremented by the value of the "N"ext attribute. Otherwise, if the current event is a note, increment the default time by the duration of the note. This results in a new default, which may be overridden by a "T" attribute in the next event.

Pitch Specification

Pitches in Allegro are the same as in Adagio. The letters "A" through "G" are followed optionally by one or more "S" (sharps) or "F" (flats), and then by an octave designation (an integer). C4 is middle C. Octaves run from C to B, so B4 is higher in pitch than C4, as musicians would expect. Sharps and flats are added after the octave is computed, so for example, Cf5 is the same as B4, even though the octave numbers are different.

Octave numbers may be omitted, in which case the octave is chosen such that the pitch will be closest to the lexically previous pitch (in any voice). If the previous pitch is 6 half-steps (a tritone) away, then the current pitch will be higher than the previous pitch. When in doubt, specify the octave explicitly.

Pitch may also be specified as "P" followed by a number. P60 is middle C, P61 is a half step higher, equivalent to Cs4, and P60.5 is a quarter tone sharper than P60.

Although not recommended, "P" may also be followed by a "non-P" pitch specification starting with "A" through "G".

Duration Specification

Durations are the most complicated attributes in Allegro and Adagio. The duration letters S, I, Q, H, and W designate Sixteenth, eIghth, Quarter, Half, and Whole notes. These letters may be followed by any combination of "." (dots) and "T" (triplets). A dot multiplies the duration by 1.5. Two dots multiply the duration by 1.75, etc. A triplet multiplies the duration by 2/3. The end of the specification is an optional multiplier (an integer) followed by an optional divisor ("/" followed by an integer.)

Durations may be combined using "+", which has lower precedence than any other duration operators.

Some examples follow:

3 beats
3 beats
4/3 beats (half note triplet)
1/4 beats (a dotted sixteenth triplet)
8/9 beats
1/5 beats
12/23 beats
7/4 beats (doubly dotted quarter)
3/2 beats (quarter tied to eighth)
5 and 1/3 beats (eight triplet tied to 5 quarters)

Durations may also be specified by the letter U followed by a number of milliseconds. A decimal point is allowed, e.g. U23.25.

Loudness Specification

Loudness is specified by "L" followed by "ppp", "pp", "p", "mp", "mf", "f", "ff", "fff", or by a floating point number. The normal  loudness range is that of MIDI velocity, from 0 (silent) to 127 (maximum). The interpretation of dynamic markings and the numerical values is to be determined.

Time Specification

Times are specified just like durations, except that a leading "T" indicates this is a time. If the "T" is followed by a digit, then the time is specified numerically as milliseconds. For example, "TW5" means 5 whole notes or 20 beats, while "T20" or "T20.0" means 20 milliseconds.

Next Time Specification

The default time of the next note, sound or update can be specified just like durations, except that a leading "N" is given. If "N" is followed by a digit, then the value is specified numerically as milliseconds.  For example, "NQ" means the next event should take place a beat after this event. This specifies a default which may be overridden by an explicit time specification in the next event.

Tempo Maps

The goal of tempo specification in Allegro is to allow flexibility without getting overly complex. There is one and only one tempo map per score. This is a limitation, but it is certainly possible to have multiple score objects when multiple tempo maps are required. Tempo maps can be specified by entering beats or by entering tempo changes. Internally, a tempo map is a sequence of pairs indicating time and beat. Tempo is assumed to be constant between any two adjacent points in the tempo map. In a stream of Aura events, tempo map events are translated into two attributes: beatr and tempor. The beatr attribute gives the current beat, and the tempor attribute gives the tempo change. The use of tempor allows the receiver to respond to tempo changes without waiting for the next beat.

When the tempo map is manipulated, either the time or the beat position must change. In Allegro, changes to the tempo map also change event times so that their beat positions remain unchanged.

On the other hand, when beats are specified, the tempo map is manipulated but event times are not changed. This effectively changes the beat positions of events. This is useful if performance data is captured and then beat information is added after the fact.

The entries in the tempo map are in non-decreasing time order and non-decreasing in beat order. Any attempt to specify an illegal tempo map in Allegro generates an error. This would most likely occur in Allegro as the result of inserting a beat at a position that conflicts with other beats.

Avoiding Numerical Problems

Instantaneous changes in beat position (infinite tempo) are not allowed, nor are zero tempos allowed. However, attempts to create an infinite or zero tempo are automatically approximated by shifting beats or times in the time map by one microsecond or one microbeat. For example, if the tempo at some beat position b is set to zero, the next entry in the time map is changed to have a beat position b + 0.000001. There is no way in Allegro to specify an infinite tempo: if you specify a second beat position at at given time, the entry in the time map is simply edited to contain the new beat position. One exception to this is at the beginning of the score. Beat 0 occurs at time 0, so if you insert, say, beat 5 at time 0, you imply an infinite tempo. The score object will automatically change this to beat 5 at time 0.000001 seconds.

Specifying Beat Times

Beat encoding might be used to synchronize external MIDI sequences or to generate a MIDI file for use in a sequencer or music notation package where beat information is necessary. The following example specifies beat 25 at time 10.542 seconds:

-beatr:25.0 T10542

Unlike normal attribute/value pairs, which are stored as events in the score, the beatr attribute generates an entry in the tempo map. It is an error to try to insert a beat such that the implied tempo would be negative. (Beats in the tempo map must be non-decreasing.)

When a beat is inserted into the time map, events in the score retain their times. This can cause very strange behavior if the time is specified in beats, because you are saying something like "insert beat 10 at beat 15." This has a well-defined interpretation: beat 15 is mapped to time t in seconds. Then, the entry (t, 10) is inserted into the tempo map. Now, what used to be at beat 15 (or time t) is now at beat 10. Normally, one would only specify beats at absolute times as shown in the example above.

Specifying Tempo

For text based notation and composition, we usually want to specify notes in terms of beats and specify the tempo of different sections of the score. The following example specifies tempo changes:

TQ50 -tempor:80.0
TQ100 -tempor:100.0

These insert tempo changes to 80 and 100 beats per minute at beats 50 and 100, respectively. Notice the use of "Q" to specify units of beats instead of milliseconds. Tempo changes can be specified at any time. Event times throughout the score are adjusted according to the new tempo.

Times and durations expressed directly in milliseconds require special care in combination with tempo specifications. When tempo is changed, every score event is remapped according to the new tempo. If a duration is specified as 100ms and then later in the score, tempo in that region is doubled, the actual duration will be 50ms. If the duration had been specified as a sixteenth note, it would still be a sixteenth note after the tempo change. Thus, all times and durations are treated as if they are beats when tempo is altered.

To freely mix time and beat specifications, it is recommended to specify a complete time map before any score events.

Time Representation

Internally, score events are associated with timestamps in units of seconds. An auxiliary structure contains a sequence of timestamped beat positions. For example, the structure might contain ((0, 0), (10, 10), (20, 30)), which indicates that at time zero, we are at beat zero. At time 10, we are at beat 10, so the tempo for the first 10 seconds is 60 beats per minute. At time 20, we are at beat 30, so the tempo from 10 to 20 is 120 beats per minute (20 beats in 10 seconds). The structure may also contain a final tempo, indicating the mapping from time to beats after the last beat position entry. If no final tempo is indicated, tempo and beats are extrapolated from the last two points in the map. Initially, the map has just one point, (0, 0), and tempo100.

There is a danger that the extrapolated tempo may not correctly predict the next beat specification. This is not an error, and it just means that the tempo over some time interval must be recomputed based on the new beat information. The problem, however, is that any events that occur within that interval should probably be adjusted in time according to the new tempo. To avoid this problem, it is best to specify timing using tempo specifications rather than beat locations. In addition, the data structure does adjust times according to the beat information. Finally, when event data is generated, both beat positions and tempo changes are sent, avoiding the need to extrapolate tempo from previous beat positions.

Time Manipulation

An important aspect of this design is to support editing and manipulation of data. Operations include:

Internal Representation

In Serpent and C++, the representation is a class called Seq, containing the fields Notes and Map. Notes is a sequence of Events, and Map is a sequence of time/beat pairs.

An Event has fields time and channel. Subclasses of Event include Note and Update. A Note has fields dur, key, pitch, loud, and attributes (attributes is a dictionary of attribute:value pairs). An Update has fields key, attribute, and value.

Midi Representation

There is a mapping between MIDI data and Allegro. Aside from notes, most MIDI messages map to attribute/value pairs. It is important to know what attributes to use. Attributes that correspond to MIDI messages are listed below:

MIDI Message Type Allegro and Aura attribute

pressure (polyphonic aftertouch)

pressurer (keyi set to key number)
control change control0r, control1r, control2r, ... control127r (note: values are normalized to the range [0..1])
program change programi
channel pressure (aftertouch) pressurer (keyi is -1)
pitchbend bendr (normalized to the range [-1..1]
key signature TBD
time signature TBD