Programming Assignment #1
Simple
Music Synthesizer Language
Due
by 12PM Eastern Time on Tuesday, February 17, 2009.
Late
Policy: No late
assignments will be accepted.
Introduction
In this
assignment, you will implement a very simple music
synthesizer language SYNTH. SYNTH has three basic
musical operators: PLAYNOTE,
REST,
CHANGE_INSTRUMENT.
PLAYNOTE
plays a
specified "musical note" for a given number of "beats" (a
beat is a musical term for the basic unit length of
time). The synthesizer is monophonic
which
means only one note can be played at any given
time.
REST specifies
the number of consecutive beats that the synthesizer should
be silent. The CHANGE_INSTRUMENT
operator
specifies the instrument that the synthesizer should set as
its current instrument. The synthesizer has 16
different instruments (many of them will sound very
similar). In addition to the musical operators,
SYNTH includes variables, simple integer arithmetic
operators, and a REPEAT
loop
construct. A SYNTH program specifies an algorithm for
constructing a sequence of notes and rests to be played on
your machine.
Your implementation of SYNTH will have two distinct
phases:
1. The compilation phase:
You will parse and compile a program written in SYNTH into
a lower-level language miniSYNTH which only has three
instructions (PLAYNOTE,
REST,
CHANGE_INSTRUMENT)
and no additional language constructs. Your
compiler will incorporate a
recursive descent parser written
in
Perl or Python (your choice).
2. The interpretation phase:
You will interpret the "intermediate" miniSYNTH program by
writing an interpreter in
Java.
The interpreter will produce the audible musical sequence
specified by the original SYNTH program code.
PHASE
I: SYNTH Recursive Descent Parser & Compiler (written
in Perl or Python)
The grammar for SYNTH is as follows:
<
synth_program>
--> < declaration_list> <
statement_list> eof
<
declaration_list> --> < declaration> <
declaration_list> | e
< declaration> --> DECL
identifier
< statement_list> --> < statement> <
statement_list> | e
< statement> --> < music_operation> |
< repeat_statement> | <
variable_operation>
< music_operation> --> PLAYNOTE
< argument>
< argument>
|REST
< argument>
|CHANGE_INSTRUMENT
< argument>
< repeat_statement> --> REPEAT
< argument>
< statement_list> END_REPEAT
< variable_operation> --> ASSIGN
identifier <
argument>
| ADD
identifier <
argument>
| SUB
identifier <
argument>
| MUL
identifier <
argument>
| DIV
identifier <
argument>
< argument> --> identifier |integer
The
boldface symbols represent the language operator,
e
represents the empty string, and eof represents
the end-of-file character. Note that this is a
free
format language,
so tokens can be separated by one or more whitespaces, tabs
and end of line characters. Your parser should
be case-sensitive.
The two literals identifier and integer are generated by
the following regular expressions:
<
identifier> --> < letter> { < letter> |
< digit> | _ } *
< integer> -->[+|-]
< digit> { < digit> } *
The semantics of the SYNTH language are relatively
straightforward. There are 128 possible notes on the
synthesizer with '0' corresponding to the lowest and '127'
to the highest (for the musically inclined, a value of 60
typically represents a middle C note on a piano and
consecutive integers represent half-steps). The
synthesizer has 16 instruments mapped to the integer values
of 0 to 15. The default instrument will be '0'.
The semantics of the music operators are:
1.
PLAYNOTE
arg1
arg2 :
Plays (and holds) the note corresponding to the value of
(arg1
mod 128)
for arg2
consecutive
beats. A beat corresponds to 50 milliseconds.
If arg2
is zero
or negative, then the PLAYNOTE instruction will be
ignored. The following example plays a note
corresponding to 2 for one second:
*
PLAYNOTE 130 20
2.
REST
arg1
:
maintains silence on the synthesizer for
arg1 consecutive
beats. The following example maintains 100
milliseconds of silence:
* REST 2
3.
CHANGE_INSTRUMENT
arg1 : changes the current instrument to the instrument
corresponding to the value (arg1 mod 16). For
example, the following example instruction changes the
current instrument to 3:
*
CHANGE_INSTRUMENT 19
SYNTH
has variables that hold integer values. All variables
must be declared in the declaration section (if a variable
is used without being declared, you should output a syntax
error). During the declaration of a variable it is
assigned a default value of 0. Each variable operator
receives two parameters, a variable name and an
argument. The result of the operation is stored in
the variable. Note that the DIV operator divides the
variable value by the specified argument.
Furthermore, the real portion of the resulting quotient is
truncated, so that the stored value is integer. For
instance,
DECL A
DECL B
ASSIGN B 4
ASSIGN A 10
DIV A B
has variable A equal to 2 and variable B equal to 4 after
execution.
The semantics of the REPEAT
...
END_REPEAT
block is
to execute the nested code block for the number of times
specified in the argument.
Your recursive descent parser should be able to correctly
parse any syntactically correct SYNTH program. If the
parser is supplied a syntactically incorrect program, it
should output a descriptive syntax error message. The
result of your parser/compiler when supplied a SYNTH
program should be a syntactically correct miniSYNTH
program. The grammar for miniSYNTH is:
<
mini_synth_program> --> <
music_statement_list> eof
<
music_statement_list> --> < music_operation>
< music_statement_list> | e
<
music_operation> --> PLAYNOTE
integer
integer
|REST
integer
|CHANGE_INSTRUMENT
integer
The
SYNTH program is basically compiled into a sequence of
musical operations.
Here is an
example SYNTH program;
this is
the miniSYNTH output from the parser/compiler. It
is up to you to design and use the algorithms/data
structures to successfully compile a SYNTH program into
miniSYNTH code.
The parser/compiler should be invoked by
perl
synth.pl program_name
OR
python
synth.py program_name
where program_name is the name of the file containing the
SYNTH source code. The resulting miniSYNTH code
should be written to standard output.
PHASE
II: miniSYNTH Interpreter (written in
Java)
The miniSYNTH interpreter generates sound based on the
sequence of music operator commands it reads from an input
miniSYNTH program. The job of the interpreter is to
create the audio from this sequence of music operator
commands. I have provided the following Java
package
algo_music.ToySynthesizer
to
produce the sounds. The ToySynthesizer class has
four public subroutines:
1.
ToySynthesizer();
//constructor
2.
void PlayNote(int
note, int num_beats);
3.
void Rest(int
num_beats);
4.
void
ChangeInstrument(int instrument_num);
The semantics of the ToySynthesizer API are exactly the
same as the music operations of SYNTH. Therefore, it
should be very straightforward to implement the interpreter
for miniSYNTH.
Here is an
example Java program that uses
algo_music.ToySynthesizer. (It will be a
good idea to compile and run the test program to ensure
that your machine supports the needed sound
hardware/software. Please contact me immediately
if you have trouble with producing sound with this test
program. Though it is possible to complete the
assignment without having sound capabilities, it will
not be as fun or rewarding.)
There should be two possible ways to invoke your
interpreter. The first is to read a miniSYNTH file:
java minisynth -f mini_program_file
The
second reads the miniSYNTH program directly from the output
stream of the SYNTH compiler:
perl
synth.pl synth_program_file | java minisynth
OR
python
synth.py synth_program_file | java minisynth
What
to Hand-in:
You should send me an email titled "COMP524 Program1" and attach a zip file called yourlastname.zip containing the following
files:
1.The Perl
source program that implements the parser/compiler.
The program file name should be synth.pl or synth.py
.
2. The Java
source program that implements the interpreter. The
program file name should be
miniSYNTH.java.
3. A
document (one or two pages, at most) describing your
solution for both the parser/compiler and
interpreter. The document should also contain any
special instructions on running your code.
4. A SYNTH
program that plays the sequence of the first 40 fibonacci
numbers. (Do not hardcode the sequence - use the
SYNTH language constructs).
5.
Another
SYNTH program of your choosing (either an original
composition or a previously published song). I'm not
grading the composition on the content; so this is your
chance to be really creative!
Software:
Your parser/compiler should be written for Perl 5.8.8
(obtainable from
www.perl.org) or Python 2.5.1
(obtainable from
www.python.org) .
The interpreter written in Java should run on Java
2 Standard Edition 5 (J2SE 1.5.0) . You can obtain this
from:
http://java.sun.com/javase/downloads/index_jdk5.jsp
I
cannot grade assignments that I cannot compile.
Therefore, please ensure that your code compiles using the
above software. In particular, note that major changes in the Python language accompanied its very recent version 3 release. Please use only version 2 for this assignment if you choose to implement your parser/compiler in Python. Further links to documentation for
Perl, Python, and Java languages are maintained on the respective
sites.
Grading:
Your assignment will be graded not only on the ability to
be correctly compiled and produce the correct output, but
also on the correct and efficient use of language
constructs provided by Perl/Python, Java, and SYNTH. For
instance, for matching the tokens you should use Perl/Python's
pattern matching and string manipulation functionality
instead of writing your own. I will grade both the
quality of the source code (design and documentation) and
the output. Collaboration is encouraged, but
you cannot share source code.
The Honor Code applies
to this program. Please, read
http://www.cs.unc.edu/Admin/Courses/HonorCode.html
for more
details.
--
GOOD LUCK AND HAVE FUN --