A brief and casual introduction to the Rubato language is presented in this chapter. The Rubato language is a typographical music input language. This language can be used to represent conventional music notation within a computer and allow for the subsequent performance of the music represented through a music synthesizer connected to a computer system via a MIDI (Musical Instrument Digital Interface) connection. Musical pieces represented in the Rubato language are suitable for processing by the Rubato compiler rc(1) and bear many resemblances to modern, block-structured computer languages.
The Rubato High Level Language is a music input language that is closely modeled on conventional music notation. However, it is a typographical language rather than a pictorial language, and contains additional features such as variables, 'templates', functions, procedures, dynamic structured block scoping of declarations and concurrency which are analogous to equivalent features in many other 'modern' computer languages.
This tutorial provides a casual 'hands-on' introduction to the Rubato language, with emphasis on introducing the language structure and other features of the language rather than any conscious attempt at generating music of artistic merit. Not all features of the language are covered, some of the less useful features have been deliberately omitted. The next chapter provides a much more complete description of the language.
The tutorial begins with a simple example. This example will be built up as more and more features of the language are progressively introduced. The target audience will be people familiar with at least one other programming language such as Pascal or C, and possessing a minimal background in reading and writing music in conventional music notation.
First of all, access to a machine with the Rubato compiler rc(1) and the Rubato interpreter rx(1) while reading this tutorial is highly recommended. Ideally, the machine should also be capable of playing the music entered through a music synthesizer connected via MIDI. (MIDI stands for Musical Instrument Digital Interface. The appendix entitled Summary and Description of MIDI provides a summary of the technical aspects of MIDI.)
Currently, rc and rx will run on a DEC VAX 11/780 running Unix Version 8 and an IBM PC/XT running MS-DOS Version 3.30. Music performance is only possible on the IBM PC/XT via a player program called adagio(1)[3] and the Roland MPU-401 MIDI Processing Unit.
Musical pieces represented in the Rubato High Level Language are referred to as programs. A program is simply a text file created using a text editor on the host machine. The text file is stored under a filename with an extension of .r or .R. Once a Rubato program has been created, it can be compiled using the command
rc file.r
The compiler will compile the source file and generate an output file with the same file name as the program file but with the extension .r replaced with .m. The output file can be interpreted using the command
rx file.m
which will generate a player file, usually called rubato.gio. On a machine equipped with adagio, the music may be performed on a synthesizer via the player program using the command
adagio rubato.gio
Below is a simple example, somewhat akin to the 'hello world' examples typical of most computer language tutorials, which we shall call example1.r. This example, when performed on a synthesizer, will cause an ascending C major scale starting from middle C to be played on the synthesizer. The individual notes in the scale (seven of them) will be spaced a second apart.
conductor
{
tempo .1 = 60
c
d
e
f
g
a
b
}
The first line in the above example defines an entity known as the conductor. Every Rubato program must have at least one conductor definition. The curly brackets (also called 'braces') delimit the lines associated with the conductor and tells the compiler to generate code that will cause each element within the braces to be executed sequentially by the conductor.
The first line within the curly braces
tempo .1 = 60
specifies the speed that the following notes shall be played. This line will cause notes played to be separated by an interval of 1/60th of a minute, i.e. one note per second. Suppose the line had been
tempo .1 = 120
then notes will be played at the rate of 120 notes per minute, or two notes per second.
The rest of the lines specify which notes should sound in sequence.
In this context, c implies Middle C,
d implies D above Middle C and so on.
It is well worth pointing out at this stage that Rubato programs are free form in nature, i.e. white spaces between characters are not syntactically meaningful to the compiler and only serve to delimit characters. The format of the above example has been carefully designed to maximise human readability. Indeed, the above example could very well have been coded up thus, with resulting loss in readability:
conductor{tempo .1=60 c d e f g a b}
Also note that the Rubato compiler is case sensitive, i.e. conductor and CONDUCTOR mean entirely different things.
Suppose we wanted to play the C major scale from middle C to the C above middle C. example1.r can be easily extended by adding one more note:
conductor
{
tempo .1 = 60
c d e f g a b c'
}
Note that we have shortened the example considerably by joining all the notes to be played on one line.
Hence, the last note played, c'
implies the C above Middle C.
The ' operator (the quote),
when specified after
a note, will cause that note to be played one
octave
ABOVE its normal
pitch(8).
Similarly, the ' operator (the backquote) causes a note to be
played one octave BELOW its normal pitch. Given this, we can easily
construct
example3.r,
which plays most of the C notes on a piano in ascending order:
conductor
{
tempo .1 = 60
c``` c`` c` c c' c'' c'''
}
Note the effect of stuttering the quote and backquote operators is to
further reduce or augment the pitch of the note in decrements/increments of
one octave. In particular, c''
is equivalent to typing c without any trailing operators.
We can also insert
accidentals,
which modify the pitch of a note by one semitone rather than one octave.
The # operator (sharp) will increment (augment) the pitch of the note by one
semitone
and the $ operator (flat) will decrement (diminish) the pitch of the note
by one semitone(9).
There is a third operator, the % operator (natural), which restores the
note back to an unaugmented or undiminished state.
Be careful when specifying a note containing BOTH accidentals and octave operators to put the accidentals BEFORE the octave operators.
Here is an example playing the scale of A major:
conductor
{
tempo .1 = 60
a b c#' d' e' f#' g#' a''
}
Of course, there is a much simpler way of specifying the A major scale and that is to change the default key associated with the notes. This can be done with a $key statement.
$key a major
conductor
{
tempo .1 = 60
a b c' d' e' f' g' a''
}
Notice that changing the default key (from C major) to A major will cause the sharps associated with the A major key to be automatically bound to the relevant notes when played. Hence no sharps need to be specified for the actual notes.
One important point with respect to the natural operator must be mentioned
here.
Suppose the default key is other than C major and it is
desired to
represent the note e. However, there may be an accidental bound to the
note that is due to the default key. Specifying, in this instance e%
will ensure that e will be played without bound accidentals.
It would be quite boring if all notes in a piece of music had equal durations and were separated equally. Rubato allows each note to be bound with two attributes: delay and duration. Attributes are things that can be associated with each note. Rubato has quite a few attributes bound to each note, and most of these will be discussed in turn. The delay attribute is a time interval associated with a note. It specifies that the next note specified will not be played until the time interval represented by the current note's delay has elapsed. On the other hand, the duration attribute is a time interval that specifies how long a note should sound.
Delays are usually specified relative to a 'known' time interval which, for want of a better term, is called a unit. The default delay is usually some multiple of a unit (256). This is somewhat analogous to the specification of notes which are relative to the default key.
Two operators change the delay, the . operator (delaymul) and
the : operator (delaydiv).
conductor
{
tempo .1 = 60
c:2 d:4 e:4 c.2
}
In example6.r, the first note is played at half the default delay, the next two notes are played at a quarter of the default delay, and the last note is played at twice the default delay.
Hence, the delaymul operator causes a note to have a delay that is equal to the default delay multiplied by the argument that follows the operator. Similarly, the delaydiv operator specifies a delay equal to the default delay divided by the argument following the operator.
We can form arithmetical expressions with the arguments of the delay operators (indeed, an arithmetical expression can substitute a number whenever a number is required). For example,
c :2+:(4 * 3)-:8*3
causes a note to be played with the following duration:
default / 2 + default / (4 * 3) - default / 8 * 3
Note the use of parenthesis to enforce precedence. arithmetical operators recognized by Rubato include most of the common ones familiar to programmers well versed in traditional computer programming languages.
The following is a list of binary operators in Rubato specified in order of increasing precedence:
or
and
==
<>
<
>
<=
>=
+
-
*
/
mod
In addition, the following unary operators are recognized
The default duration of a note is slightly less than the delay of a note. This is because on most musical instruments, there is a perceptible interval between the playing of one note and the playing of the next. Two operators can change the duration relative to the current note delay.
The ^ operator (decdur) causes the duration of the note to decrease with
respect to the current delay. Stuttering the decdur operator will cause
the duration to become progressively less and less with respect to the
delay and the effect is to cause the playing of notes to become more and
more 'disjointed'. This is called
staccato.
The _ operator (incdur) causes the
duration of the note to increase the duration of the note closer to the
delay. A duration that is equal to or very close to the delay causes an
effect known as
legato
or 'smooth-playing' which is commonly associated with bowed stringed
musical instruments such as the violin.
The
tempo
statement, as seen in
example1.r,
consists of two arguments separated by an equals sign (=). The first
argument is the quantity, measured in delay units, and the second argument
is the number of times this quantity should count in the period of one
minute. Hence,
tempo :4 = 80
means the quantity represented by the current default delay divided by 4 should occur 80 times per minute.
Suppose the current default delay is 256 units. Then the above statement is equivalent to saying
tempo 64 = 80
as 256 / 4 = 64. Hence the rate of flow of units with respect to time is 64 * 80 = 5120 units per minute or around 85.3 units per second.
Yet another attribute associated with a note is the note's velocity. This can be visualized(?!) as the loudness or intensity of the note. The term 'velocity' comes from the action of a pianist hitting the keys on a piano. Obviously, the harder the pianist strikes the keys (the faster the keys move as a result of being struck) the louder and more intense the notes that result. Similarly, the higher the velocity of the note, the louder (or brighter) it will sound on a synthesizer suitably equipped with velocity circuits (Not all synthesizers will respond to the velocity data sent by a player while playing a Rubato program.)
The main velocity operator is ;. It can be used in many ways:
conductor
{
c;80
d;+20
e;-20
f; 20 + ;;
}
In the above example, the first note is played with an absolute velocity
value of 80. The second note is played at a velocity which is 20 greater
than the
current velocity.
Similarly, the third note is played at a velocity of 20 below the current
velocity. The last note is played at a velocity 20 greater than the
current velocity, hence it has the same velocity value as the second note.
The different syntax simply illustrates how arithmetical expressions can be
used when specifying attribute values. Note that stuttering the ;
operator returns a number equivalent to the default velocity value, this is
then added to 20. The final result is then used as the velocity value.
Some other attributes which may seem a bit obscure but can be quite useful are the patch and channel attributes. These are specified by the patch and channel operators respectively.
conductor
{
c patch 5 channel 2
}
In the above example, the note when played will be on Patch 5 Channel 2.
The patch attribute specifies the timbre of a note, or how a note will sound. As an example, the sound of a note on a piano is obviously very different from the sound of a note on a bassoon or cello. This can be attributed to (with apologies to the unintentional pun) the different timbres of the instruments. Patch numbers are usually assigned to distinctive timbres within a synthesizer. Each patch number corresponds to some setting of the synthesizer voice generation controls or voice generation algorithms within a synthesizer.
The channel number of a note can be used to identify which synthesizer a note should be played on, if more than one synthesizer is connected to a computer. Even though all the synthesizers may be connected together by a common line to the computer, synthesizers can usually be set up to respond to only selected MIDI channels.
Absolute pitch, delay and duration values of a note can also be specified
in a manner similar to the specification of velocity values. The pitch
number can be specified with the
pitch
or & operator. The delay value can be specified with the
delay
or \ operator. The duration value is specified with the
duration
or ! operator. Hence, the following notes
&100 \500 !42 &+5 \-20
specifies a note with a pitch number of 100 with a delay of 500 units and a duration of 42 units. The next note has a pitch number 5 greater than the default pitch number and a delay 20 less than the default delay. Absolute attribute specifications are useful when the 'note' to be played is actually directed to an instrument not actually designed as a musical instrument but has a MIDI interface. Examples of MIDI controlled equipment include stage-light controllers, effects generators and other paraphernalia associated with a concert hall. These instruments accept note commands but, instead of playing a note, will respond to the note command by turning on lights or starting a tape recorder.
So far, all the above descriptions of note attributes have made mysterious references to things called default attribute values. What are default attribute values and how are they changed?
Default attribute values are values taken by attributes of a note unless the attributes are changed using methods outlined previously. Initially, here are the default values for some of the attributes:
Default values can be changed using the default statement. For example,
conductor
{
default \512
default !20
default ;80
default patch 42
default channel 13
c d e f g a b
}
will change the default delay to 512 units, the default duration to 20 less than the current delay, the default velocity to 80, the default patch to 42 and the default channel to 13.
Pitch number defaults are changed using a different method:
$default a = a''
conductor
{
c d e f g a b
}
This will cause the default pitch of the note C to be the note C two octaves above Middle C rather than Middle C and so on.
There are actually two sets of pitch defaults:
$default a = a'
$default A = A`
conductor
[
{ c d e f g a b }
{ B A G F E D C }
]
The note 'a' is distinguished from the note 'A'. Initially, both uppercase notes and lowercase notes are set to the same default pitch number, and hence will produce the same pitch when specified. The previous example changes the lowercase pitch default to be one octave higher and the uppercase pitch default to be one octave lower.
How can notes be combined together?
So far, in all the previous examples, notes have been joined together sequentially, separated by the delay values of each note, into a melodic line or phrase.
The phrase constructor in Rubato is the pair of curly braces. Any note within a pair of curly braces will be played in sequential order in the manner outlined in the previous examples. It is important to note that phrases may contain other phrases within themselves. For example, the following two phrases are identical, i.e. they produce the same line of melody.
{ c d e f c d e f g c d e f c }
{ { { c d e f } { c d e f } g } { c d e f } c }
Phrases are entities rather like notes and may possess attributes. Any attributes belonging to a phrase will automatically be inherited as the default attributes of each note within a phrase. Hence,
{ c d e:2 }:4 f
all the notes within the phrase will have a default delay equivalent to the default delay outside the phrase divided by 4. Note that the last note within the phrase will have a delay equivalent to the current default delay divided by 8, as the result of dividing the delays are cumulative. The note outside the phrase, on the other hand, is unaffected by the phrase delay attribute. This is in keeping with the concept of each note (or phrase) being unaffected by entities before or after it.
Another entity much like phrases or notes is the chord. A chord is a collection of entities delimited by square brackets '[' and ']'. Each entity within a chord is played simultaneously. The delay of a chord is then equivalent to the shortest delay of any one of its entities.
The following is the specification of a chord that plays the C major triad
[ c e g ]:4
Note that chords, like phrases, may possess attributes which are passed to entities within themselves.
Chords may contain other chords within itself, or even phrases.
conductor
[
{ c d e f g a b c }:8
[ c e g ]
{ c c c c }:4
]
In example9.r, the two phrases and a chord within the chord tagged to the conductor all play simultaneously, forming three separate melodic lines.
Everything in Rubato can be assigned a name, which is a string of alphanumeric characters forming a distinct word. Once an entity is assigned to a name, it is considered to be bound to the name until some other assignment is made to the name. Invoking the entity can be accomplished by simplying typing the name which the entity is bound to.
For example, the following example defines a phrase called ditty which contains the chord definition cmajor which is played at the beginning and the end of the phrase. Also, a note called funny is defined outside the conductor. Note that that when an entity is bound to a name, it does not get 'played'. However, the entity will be played when it is invoked by the name it is bound to.
note funny = e#'':16;20
conductor
{
phrase ditty =
{
chord cmajor = [ c e g ]:2
cmajor
{
c d e f funny g a b funny.2
cmajor
}:8
cmajor:2
}
ditty
}
example10.r is also an excellent example of a concept called block scoping. The phrase ditty is defined within the phrase associated with the conductor. Similarly, the chord cmajor is defined within the phrase ditty.
The point of all these nested definitions is Rubato employs identifier scoping rules when binding entities to names. The chord
[ c e g ]:2
is locally bound to the string cmajor only within the phrase ditty. Outside ditty, such as elsewhere within the conductor, the binding of cmajor is undefined. However, within the anonymous phrase within ditty, the binding of cmajor is still active as the phrase is contained entirely within ditty, which 'holds' the binding of cmajor.
The note funny is defined or bound to outside the conductor. It is therefore technically known as a global binding and will be active for the rest of the Rubato program.
Block scoping is a powerful concept employed in many modern programming languages. Rubato uses block scoping rules in order to 'hide' bindings outside areas where they are useful.
In the context of 'block scoping', a block is simply an entity which may contain other entities, i.e. a phrase or a chord. A note is not considered a block because a note cannot contain other notes or phrases or chords. Bindings can only occur either 'externally', such as in the case of the note funny, or within a block. In a phrase or chord, all bindings must be specified before any specification of entities within the block. Note that in the phrase ditty, the binding of cmajor is done before the specification of phrases and chords within ditty.
Rubato allows yet another sort of binding, called variable binding. Variables are names associated with an integer. They can be considered as a 'location' that can hold an integer for later use. If a new integer value is bound to a variable that was previously bound (this is called variable assignment), the old binding is discarded.
Variables can also be declared with no initial binding. In this case, a storage location is allocated for the name of the variable, but the contents of the storage location is left unbound. If an assignment occurs in some later stage, the storage location will then be bound to the new value.
The following example will make this clear.
conductor
{
var highnote = key c'''
var longdelay
longdelay = 10
&highnote .longdelay
}
Two variables are declared in example11.r. highnote has an initial binding of the pitch number associated with the note c'''. longdelay has no initial binding but is later assigned to the value 10. A note is then played using the contents of both variables.
Variable declarations are also subject to the scoping rules mentioned above.
Variables are really useful in directing control flow within a phrase. As mentioned previously, entities within a phrase are executed sequentially. Sometimes, however, it may be desired to break the flow of execution within a phrase. Control flow statements are entities whose sole purpose is to modify the control flow within a phrase.(10)
The following are a brief summary of control flow statements available in Rubato. In the context of the syntax descriptions an expression is simply an arithmetical expression that evaluates to an integer value and a statement is either a control flow statement, a variable assignment, or an entity (e.g. note, phrase or chord). A statement can also be a procedure or template call. Procedures and templates will be introduced later.
Syntax:
if expression then statement1
if expression then statement1 else statement2
The expression is evaluated. If the result is true (nonzero value) statement1 is executed. If the result is false (zero value) control flow will pass on to the next entity or statement in sequence or, if the else keyword is present, statement2 is executed.
Syntax:
while expression do statement
The expression is evaluated. If the result is true, then statement is executed. The expression is then reevaluated. Statement will be rexecuted until the expression evaluates to false (0), at which point control will be passed onto the next statement after the while-do statement.
Syntax:
do statement while expression
The statement is executed. The expression is then evaluated. If the result is true, the statement is reexecuted. This continues until the expression evaluates to false. Control then passes to the next statement.
Syntax:
repeat expression do statement
The statement is repeated expression number of times.
The following simple example will hopefully make the flavour of the above control flow statements clarified. Try and figure out what it does. If in doubt, key it in and then play it!
conductor
{
var aa = 1
while aa < 32 do
{
aa = aa * 2
if aa == 32 then
repeat 4 do { c:aa d:aa }
else
c:aa
}
}
A procedure is analogous to a subroutine call in a computer programming language. A procedure binding is similar to a phrase or chord binding, except during the invocation of the procedure, entities and values may be passed to the procedure body which is the entity bound to the procedure name.
The invocation of the procedure is called a procedure call, and the values and entities passed to the procedure body are called procedure parameters.
Parameters may be values, variables, notes, chords phrases or even other procedure names. A procedure binding (also called 'declaration') binds its parameters to local names and act as if the entities passed to the procedure has been bound locally within the procedure. If a parameter binding is subsequently changed within a procedure, this change is invisible to the outer block that invoked the procedure.
Syntax:
procedure name(parameters) procedure_body
procedure name() procedure_body
The name of a procedure is the string by which the procedure is bound to. The parameters are simply variables, notes, chords or phrases declarations(11) separated by the ',' character. The procedure_body is a block (either a phrase or a chord) that will be executed by the procedure when invoked.
For example,
procedure playtwice(phrase p)
{
p
p
}
will play a phrase twice in succession. This procedure can be invoked by passing a name bound to a phrase or an actual phrase:
playtwice(phrasename)
playtwice({c d e f g a b})
A more general procedure could be written to play a phrase a certain number of times:
procedure playmany(phrase p, var n)
{
repeat n do p
}
This can be called by passing either a variable or an expression as the second parameter:
playmany(phrasename, i)
playmany({c d e f g a b}, 23+4*7)
We can also define the previous procedure using a while-do statement in the procedure body:
procedure playmany2(phrase p, var n)
{
var i = 0
while i < n do
{
p
i = i + 1
}
}
A procedure with no parameters is declared with nothing in between the parentheses, but the parentheses must still be included in the definition.
Templates are conceptually similar to procedures but much easier to use. A template is simply a procedure with an implied procedure body. An example will make this clear:
template mybar { :8, :4, :4;20, :8 }
If this is invoked as
mybar(c, d:2, e, f)
this would be equivalent to the phrase
{
c:8
d:2:4
e:4;20
f:8
}
This tutorial of Rubato concludes with an example featuring many of the
features that has been introduced in the previous pages. It actually plays
a 'real' (!) piece of music. The transcription of this piece into the
syntax accepted by Rubato is deliberately stylized in order to show off as
many features in Rubato as possible. Note that comments in the code are
delimited by the strings /* and */.
/*
* over.r
*
* OVER THE RAINBOW
* From the M-G-M Picture "THE WIZARD OF OZ"
* by E.Y. Harburg & Harold Arien
*
* Transcribed to Rubato by Chris Tham (christie@basser.cs.su.oz)
*/
$key f major
$default a = a'
conductor
{
procedure alternate(note lo, note hi)
{
{ lo:8 hi:8 lo:8 } hi:8^
repeat 2 do { lo:8^ hi:8^ }
}
phrase song =
{
/* this plays the melody of the song from start to end */
phrase common =
{
template motif(:4, :8, :8, :4, :4)
phrase theme =
{
/* Plays the main theme of the song
* This is repeated at least 6 times
* in the song */
f`:2 f:2
motif(e, c, d, e, f)
f`:2 d:2
c
d`:2 b`:2
motif(a`, f`, g`, a`, b`)
motif(g`, e`, f`, g`, a`)
}
/* common part, song has structure common common */
theme f`:2 r:2
theme f`:2+:4 r:8 c:8^
alternate(a, c) alternate(b, c)
d:2 d:4*5 r:8 c:8^
alternate(a, c) alternate(b%, d)
e:2_ e:2_ g:2 d:2
theme
}
common f`:2 r:2
common f`:4*3 r:8 c:8^
alternate(a, c)
{ b:8 c:8 b:8 } c:8^ { b:8 c:8 d:8 e:8 }
f:4*(4+2+1)
r:4
}
/* set up global (absolute) values */
tempo :4=80
song
}
Next: Chapter 5: A SPECIFICATION OF THE RUBATO LANGUAGE
Previous: Chapter 4: A TUTORIAL TO THE RUBATO LANGUAGE
Back to: Table of Contents