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 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.
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.
To run your program (except for Macintosh systems), type
templateThe 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.
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.
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)
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.
A few more observations: notice that the ``score'' consists of calls to
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.
On the first line of the file and in various other places, you will see
what is called a
comment. Any text between /*
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.
If you have problems, try typing
template -traceto get a printout of the outgoing MIDI messages. If you see messages but do not hear anything, then check your MIDI and audio connections.
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/* 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 --
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.
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 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.
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 endrepwhere 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.
The phrase could just as well have been calls to other procedures. The
next example uses repeat
to play C4 D4 E4 F4 three times, using the
up
procedure used earlier.
void up();void mainscore() { repeat(i, 3) up(); endrep }
void up() { note(60, 20); note(62, 20); note(64, 20); note(65, 20); }
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
.
The 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 bHere, 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 }
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);Whenvoid mainscore() { newup(25); newup(50); }
void newup(int dur) { note(60, dur); note(62, dur); note(64, dur); note(65, dur); }
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.
After the fourth note is played, the computer returns to 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.
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.
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.
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).
The rest
procedure stops the program for the length of time
specified by duration
. This sustains the note.
The third procedure call is another call to midi
note
. The
key velocity of zero (the third parameter) indicates to turn the note
off.
Incidentally, the Adagio program also uses the same midi
note
procedure.
You can find the C programs that make up
Adagio in the directories app/adagio
and lib
.
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.
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.