Compiling a Program
We will start with a simple program that has already been written. The
purpose of this exercise is to learn how to run a program once it is
written. We will start with a working program to minimize the number
of possible problems.
The directory app/template has a simple program named template.c. Before
experimenting with this program, back up the template directory so the
original files can be recovered. Template is designed to serve as a template that you add to in order to build a program that does something.
Another example program is
To hear the results of a
program, you have to compile your program, then link it, and then
execute it. The C compiler and linker are run by typing make.
(With Amiga Lattice C, type lmk; for Microsoft C, type nmake /F
template.mak).
The resulting file, named template (or template.exe under DOS)
can then be
executed, meaning that the machine instructions in the file are loaded into
working memory, and the computer then carries them out.
On the Macintosh
with Lightspeed C, the procedure is different. You should consult your
Lightspeed C manual for instructions, run the project called template,
and proceed to Section ``Writing a Program''. Under DOS, you may prefer to
use a more integrated programming environment, for example the bc
command of Borland C, or pwb in Microsoft C. Consult your compiler
documentation for details.
If your program has errors, the line number where the error
was discovered will be printed along with a brief description of the error.
You must fix these errors by editing your program and then compiling again.
(
To run your program (except for Macintosh systems), type
On the Macintosh, there is a Run menu item in Think C, or you can build an application, save it, exit Think C, and double click on the application to run it. As with other systems, you must recompile and save the application after you edit the program.
A few more observations: notice that the ``score'' consists of calls to
On the first line of the file and in various other places, you will see
what is called a
comment. Any text between
Unlike Adagio, C is a case sensitive language. This means that
Your program will have a number of additional lines copied from
If you have problems, try typing
/* these declarations are necessary to keep most
compilers from complaining: */
void up();
void down();
void mainscore()
{
while (askbool("Type RETURN to play, or N RETURN to quit",
true)) {
up();
down();
up();
note(60, 100);
}
}
void up()
{
note(60, 20);
note(62, 20);
note(64, 20);
note(65, 20);
}
void down()
{
note(67, 20);
note(65, 20);
note(64, 20);
note(62, 20);
}
-- remainder of template.c omitted from this listing --
Notice the appearance of up and down near the beginning of the
file. These tell the compiler that up and down are procedures
that will be defined later. Notice the semicolons in these lines.
When this program is run, the computer will do what
The phrase could just as well have been calls to other procedures. The
next example uses
void mainscore()
{
repeat(i, 3)
up();
endrep
}
void up()
{
note(60, 20);
note(62, 20);
note(64, 20);
note(65, 20);
}
The
void mainscore()
{
newup(25);
newup(50);
}
void newup(int dur)
{
note(60, dur);
note(62, dur);
note(64, dur);
note(65, dur);
}
After the fourth note is played, the computer returns to
Parameters may be named with any string of letters, just like procedures.
It is a good idea to use mnemonic names, that is, names that remind you
of their purpose. It makes no difference to the computer, but when you
start writing large programs, you will find that it is important to
make programs readable and understandable.
Notice that parameters in the procedure definition are preceded by the word
int. This declares the type of the parameter to be an integer
value. For now, we will use nothing but integers, so all parameter
declarations should be of the form ``int parametername.''
Important: When you use parameters, the number of parameters in the
definition must match the number of parameters in the call. The order
of parameters in the call determines which parameter gets which value.
If you use a procedure (e.g. in mainscore) before defining it, you must
declare the procedure as illustrated in the top line of this example. The
declaration is similar to the definition, but the entire body of the definition
delineated by braces is replaced by a semicolon. The parameters in the
declaration must match the parameters in the full definition.
The
The third procedure call is another call to
Incidentally, the Adagio program also uses the same
app/test/prog.c
. This one actually plays some MIDI, so if you have trouble with app/template/template.c
, try getting prog.c
to run.
template.c
does not have any errors, so it should compile and link
without errors.)
template
The template program prompts you to type RETURN, but does nothing.
To run your program again, you need not
recompile unless you change your program with the editor.
Stopping a Program
Sometimes you need to stop programs in progress before they finish
on their own. You can type CTRL-C (hold down the CTRL key
and type C
or CTRL-BREAK to stop your program in most cases.
It is possible to write programs that cannot be stopped except by
turning off the computer, but these are unusual (Footnote 4)
Writing a Program
Let's say you want to modify template to play a couple
of notes.
In template.c, find the word "mainscore". You should
see a section of the file that looks like this:
void mainscore()
{
while (askbool("Type RETURN to play, or N RETURN to quit",
true)) {
/* PUT YOUR SCORE HERE */
}
}
Now, add some new lines as shown below:
void mainscore()
{
while (askbool("Type RETURN to play, or N RETURN to quit",
true)) {
note(60, 100);
note(62, 50);
note(64, 50);
note(65, 100);
}
}
This program will play 4 notes in sequence by ``calling''
note
4 times. You indicate what kind of note you want by
giving two parameters in parentheses after the word note
. The
parameters for note specify pitch and duration. The pitch parameter is 60
for middle C (C4), and goes up by one for each semitone. Thus, the pitches
in this program are C4, D4, E4, and F4. The second parameter is the
duration parameter, expressed in hundredths of a second. Thus, the first
note will be 1 second long, the next two will be 0.5 seconds, and the last
will be 1 second.
note
, and is
bracketed by braces: {
and }
. Also
notice that calls to note end with semicolons (;).
All of these details are essential, and C is not very forgiving of even
trivial mistakes.
/*
and */
is ignored by C.
You will find it useful to use comments to remind yourself what your program
does, when it was written, and so on.
mainscore
, MainScore
, MAINSCORE
, and MaInScOrE
are all distinct identifiers.
Because of this, I suggest that you never use upper case letters in C
programs, except in strings and comments.
template.c
.
Do not delete or change these for now. Use make or the equivalent
to recompile and relink template, and run it by typing
template. You should hear a 4-note sequence.
template -trace
to get a printout of the outgoing MIDI messages. If you see messages but do
not hear anything, then check your MIDI and audio connections.
Writing a Procedure
Procedures allow you to give a name to a musical phrase.
The phrase can then be called from several places. This saves your having
to retype all of the calls to note
every time you want to hear the
phrase. In the following example, there are two phrases, or procedures,
up
and down
, which are called from mainscore
:
-- initial part of template.c omitted from this listing --
Notice that up
and down
obey the same rules as mainscore
:
procedure names are preceded by void and followed by a pair of parentheses
and an open brace.
Then there is a list of calls to note
or other procedures
terminated by semicolons. While note
has two parameters, up
and down
each have zero parameters. You indicate the absence of
parameters when you call up
or down
by not putting anything
between the open and close parentheses. However, you must always
type the parentheses after the name of any procedure you call.
Finally, the sequence of calls is ended by a close brace.
mainscore
says
to do. The first call is to up
, so the computer finds the definition
of up
and does what up
says to do. In this case, up
plays
four notes (C, D, E, F). Now that up
is finished, mainscore
continues by calling down
, which in turn plays (G, F, E, D).
When down
returns, mainscore
continues with another call to
up
, and again up
will play C, D, E, F. Finally, up
returns and mainscore plays a C. At this point the mainscore
program is finished.
Repeats
Repetition
is important in both programming and
music. To repeat a phrase, a special ``repeat'' construct is provided.
Consider the following example. (In the remaining examples, only the relevant changes to template.c
are shown. You will need the entire file, with changes as shown, in order to compile and run the program:
void mainscore()
{
repeat(i, 5)
note(60, 30);
note(72, 30);
endrep
}
Look at the two calls to note
above. By themselves, these calls would
play the phrase C4 C5. The repeat construct consists of the form
repeat(counter, howmany) phrase endrep
where counter is just the
name you want the computer to give to a variable number that keeps track
of which repeat is being played (more about this later), howmany is
the number of repeats to take, and phrase is a sequence of
calls to note or any other procedure.
The example above will play the phrase C4 C5 C4 C5 C4 C5 C4 C5 C4 C5 because
we wrote 5 for the number of repeats.
repeat
to play C4 D4 E4 F4 three times, using the
up
procedure used earlier.
void up();
Conditions
Computer programs are made more flexible by the use of
conditionals, that is, the ability to test a condition
and act upon the result of the test. For example, suppose you want to
repeat a phrase, but you want the phrase to have a first
and second ending. In other words, the first time
through you want the end of the phrase to be played one way, and the second
time through, you want the ending to be played another way. How would you
do this using just repeats and procedures? The following program uses a new
construct (the if
construct) that makes this programming task easy:
void mainscore()
{
repeat(i, 2)
note(72, 30);
note(71, 15);
note(69, 15);
note(67, 15);
note(65, 15);
note(64, 15);
if (i == 1) {
note(65, 15);
note(67, 120);
} else {
note(62, 15);
note(60, 120);
}
endrep
}
This is a program with a repeat construct, but notice that the last
part of the repeated phrase is of the form:
if (condition) { phrase1 } else { phrase2 }
The computer interprets this construct as follows: whenever an if
is encountered, the computer evaluates the following condition. In this
case the condition is i == 1
, which is true when the repeat counter i
is equal to one (the first time through) and false when the repeat counter
is not equal to one (Footnote 5) .
If the condition is true (which happens the first time through in this
example), the phrase following the first open brace ({)
is performed up to ``} else {'',
and the second phrase (after else) is skipped.
If the condition is false, the first
phrase is skipped, and the phrase in braces after else
is performed. As usual, these phrases can be
calls to other procedures like up
and down
.
if
construct is used to select between two
alternatives where the selection is based on a
condition. Useful conditions are:
a == b true if a is equal to b
a > b true if a is greater than b
a < b true if a is less than b
a >= b true if a is greater than or equal to b
a <= b true if a is less than or equal to b
a != b true if a is not equal to b
Here, a and b stand for repeat counters (like i
) or numbers (like 0,
1, etc.). Like the repeat
construct, the if
construct can be
used anywhere in a phrase. The following example plays a phrase three
times. On the second time through, the middle of the phrase is extended.
The procedures p1
, p2
, and p3
are not shown.
mainscore()
{
repeat(i, 3)
p1();
if (i == 2) {
p2();
} else {
}
p3();
endrep
}
In this example, the phrase after else
is empty
(has no statments). That means that on the first and third times through,
the complete phrase will be equivalent to p1(); p3();
. On the second time
through, the phrase will be equivalent to p1(); p2(); p3();
.
The empty else
part can be omitted, resulting in:
mainscore()
{
repeat(i, 3)
p1();
if (i == 2) {
p2();
}
p3();
endrep
}
Parameters
It's time to confess: there is really no difference between
mainscore
, note
, up
, and down
. They are all procedures,
and every procedure has a list of parameters, which
can serve to modify its behavior. For example, the parameters to note
tell the note procedure what pitch and duration to use. You can easily
write your own procedures that have parameters. Study the
newup
procedure below, which plays C4 D4 E4 F4. The procedure has one
parameter, called dur
, which determines the duration of each note:
void newup(int dur);
When mainscore
is played, it first calls newup
with the parameter
25. The computer will then find the definition of newup
and notice that
the parameter is to be named dur
. Now, whenever the computer finds the
name dur
within newup
, it will substitute dur
's value (25).
Thus, the duration specified for all of the calls to note
will be
25, or one quarter second.
mainscore
, where
the next thing in the sequence is another call to newup
. But this time,
the parameter is 50. The computer goes through the same steps as before: it
finds the definition of newup
, associates the value of 50 with dur
,
and substitutes 50 for dur
throughout the definition of newup
.
The result is that the four notes (C4 D4 E4 F4) are now played with durations
of 50, or one half second.
Producing Chords
The C language is called a sequential language because programs are executed
in sequence, performing only one operation at a time. This is a big
limitation for music, and the next chapter will present some solutions to
the problems of using C. In the meantime, there is a fairly simple way to
get two notes sounding at once. The procedure pnote
is
just like note
except that pnote
does not wait for the specified
duration. Instead, it schedules the end of the note to happen in the
future but returns immediately without waiting. The following procedure
plays a minor chord with the specified tonic and duration:
minor(int tonic, int duration)
{
pnote(tonic, duration);
pnote(tonic + 3, duration);
note(tonic + 7, duration);
}
The first two notes of the chord are played using pnote
. Since
pnote
returns immediately, all three notes start very close
to the same time. The third note uses the note
routine, which will
delay for duration
before returning. Thus the minor
procedure
will also take duration
before returning.
Low-level Procedures
Up until now, we have concentrated on writing programs that control the
high level structure of your music.
By now, you are beginning to enjoy some of the power available to you
as a computer programmer. Now, it is time to learn about the lower levels
of control, which concern direct control over the synthesizer. Recall
that note
is just a procedure. Here it is:
void note(int pitch, int duration)
{
midi
note(1, pitch, 100);
rest(duration);
midi
note(1, pitch, 0);
}
The note
procedure first uses the procedure midi
note
to start
a note. The parameters are the MIDI channel number (1), the pitch, and
the key velocity (100).
rest
procedure stops the program for the length of time
specified by duration
. This sustains the note.
midi
note
. The
key velocity of zero (the third parameter) indicates to turn the note
off.
midi
note
procedure.
You can find the C programs that make up
Adagio in the directories app/adagio
and lib
.
Other Procedures and Functions
A complete list of music functions and procedures
are listed in the file midifns.c
and in Appendix ``The MIDI Interface''.
A summary of some useful ones, as well as some
useful C procedures are given below. Italicized parameters stand for
values that you supply when you call the function.
(channel, pitch, velocity)gprintf
(TRANS
, "this is a string\n");\n
should be
included after the text you want written. This tells the computer to
``write'' a newline after writing the line of text. gprintf
is portable
among versions of CMU Midi Toolkit. It is similar to fprintf
which
is described fully in any C programming manual.
rest
(duration);
getkey
(waitflag);
midi
note
note;
sends a MIDI note-on command.
The parameters are the MIDI channel (from 1 to 16),
the key number (from 0 to 127), and the key velocity (from 0 to 127).
If the velocity is 0, then the note is turned off.
midi
program
program
Previous Section | Next Section | Table of Contents | Index | Title Page