COMP 310
Fall 2016
|
HW06: ABC Music
Player
|
 |
ABC Music Files
An abc music file is a text based transcription of a musical score. The
abc music notation is relatively simple, enabling anyone to transcribe music
into a text file which can be parsed and played by a variety of open source
software. There are many pieces of music that have been transcribed into
abc notation. You can find out more about abc notation and find many
simple tunes here. For this
assignment, we will deal with a subset of abc music notation.
ABC music files consist of headers and notes. You will only need to
handle files that have headers first and then music. You do not need to handle
headers that are interspersed within notes. Furthermore, you will not need
to handle music that has repeats in it. The provided parser ignores repeat
symbols, so you only need to play the notes once, even if the score says a
particular phrase should be repeated.
Resources:
Music Basics
For this assignment, you will need to learn a little bit about music if you
do not already know it. This assignment is about programming using the
visitor design pattern, not about music. But you must be at least
passingly familiar with terms like "tempo", "key signature", "sharp", "quarter
note", and the like. Below are a few tutorials. You can and should
also discuss anything that is confusing with the staff. Please don't
struggle over musical jargon or concepts - ask for help! Also, Google is
your friend, use it!
You do not need to read the above sites from start to finish in order to do
this assignment. Skim parts of them
quickly if you feel like you don't know anything about music and refer back to
them as you do the assignment if you find them helpful.
The Assignment
In this assignment, you will implement visitors to print and play parsed abc
music files. We will provide you with a parser for that subset that reads
abc music files and gives them to you in a data structure that you can work
with.
Parsed Music
The parser returns the music in the following data structure
(click here for docs):
Click for full-size image

As the UML diagram shows, all of the elements of the abc file will be parsed
into IPhrase elements. The parsed file will
therefore yield a single IPhrase object, which you
can then perform operations on. Each "phrase" consists of either a header
element, a sequence of phrases, a collection of notes, or a note:
- Header: the headers of the abc file are
parsed into header objects. There are many headers that provide information
(such as the composer's name) that you can ignore when playing back music.
The three main headers of interest in playing back music are:
- L: this header specifies the default length of a note in the file
- K: this header specifies the key signature for the music
- Q: this header specifies the tempo for the piece
- NESeqList, MTSeqList:
most everything within a phrase is stored inside a sequence list. This is
implemented as a standard object-oriented list structure that holds
IPhrase elements. These lists can be
therefore nested to arbitrary depths (i.e., you can have a list of
lists of lists).
- NoteCollection: grouped notes (representing
chords or triplets) are stored in NoteCollection
objects. The notes in a chord must all be played at the same time. The
duration of each note in a triplet must be scaled by 2/3.
- Note: a Note is
the fundamental unit of music. Within a phrase, groups of notes are
contained within collections and sequences to form a piece of music.
Playing Music in Java
We will be using
MIDI to play music. MIDI is a somewhat dated standard
that allows you to play notes using computer generated instrument sounds. Most
music these days is sampled, but that is slightly harder to deal with for our
purposes when generating simple music from a score.
The Java MIDI library uses ticks to indicate time. You specify the
number of ticks in a quarter note and all note durations are then specified in
ticks. A typical number of ticks per quarter note is 16, enabling you to
play notes are short as 64th notes (which would then be 1 tick long). Internally, all notes begin
and end on a timer tick. The Java MIDI library (and/or your sound card)
provides a set of instruments that you can use to play your music. You
select a particular instrument and notes are played in such a way as to
(loosely) mimic that instrument.
To simplify the process, we have provided a
SequencePlayer class. The following are the important methods of
the class:
- Constructor: the constructor takes the number of ticks you want per
quarter note and the instrument you want to use. You can find out the
available instruments by calling the getInstruments
method which returns a String. Instruments
are specified as a number between 0 and 127.
getInstruments shows the mapping from numbers to instruments, one per
line. There is also an init method (which
is called by the constructor) that takes the same arguments. You can
reinitialize the sequence player as often as you like by calling the
init method.
- addNote: the addNote
method takes a Note and a starting tick.
The note is scheduled to be played at that tick. The method returns
the tick at which the note will stop playing (which you can then use to
schedule the next subsequent note). What does this say about
the input parameter and the return value of a visitor to process an
IPhrase?
- play: when you have finished scheduling
notes to be played using the addNote method, you
can call the play method to actually play the
music. The music is played asynchronously, so this method will return
immediately. When the music is done playing, it will print "Finished
playing" to the console and close all MIDI resources.
- close: you can stop the music at any time by
calling the close method.
- There are also getter and setter methods to read and update the tempo
and the number of ticks in a "default note". abc files use the notion of a
default note length. The Note class specifies
the duration of a note in terms of this default (a length of 1.0 means to use
the default). The tempo and ticks per default note must be udpated
from the headers of the abc file. In case the music does not specify
these (some abc files do not specify a tempo, for instance), you should
initialize the default note length and the tempo to something you find
reasonable before processing any music. If you then process an L
(default note) or Q (tempo) header, you would then update these values
appropriately.
The sequence player does not currently allow you to change the tempo in the
middle of the piece. The player uses the last tempo that was set before the
play method is invoked to play back the entire
piece.
Processing Music
For this assignment, you will be writing two visitors to process the parsed
music. You will be using Dr. Wong's extended visitor design pattern
implementation, whose code will be supplied.
Read the extended visitor documentation
here.
UML Diagram of the Extended Visitor Design Pattern
Implementation
Click for full-size view

The two visitors you must write will do the following:
- Convert the entire piece of music into a String
so that you can print it.
- Enqueue the entire peice to be played using the provided
SequencePlayer class and its
addNote method.
You should perform these tasks in order. The first visitor is a
relatively straightforward traversal of the sequence lists and will allow you to
see and get used to the structure of the parsed music. The second visitor
is more complex as it requires you to install many extended visitor commands and
properly deal with the musical structure.
We have provided several classes to make your task easier:
- ABCParser: this class is will parse an abc
file into an IPhrase data structure.
- SequencePlayer: this class is a wrapper to
access MIDI sound in Java. It enables you to add a sequence of notes
to a MIDI track and then play it when you are through.
- Code notes:
- The play() method of
SequencePlayer has been modified to take
an input parameter of type
ISequencePlayerStatus (a new provided interface). This
enhancement will enable you to know when a track has finished
playing nomrally as the
ISequencePlayerStatus.finished() method will be called at
that time.
- A null object instance,
ISequencePlayerStatus.NULL, has been provided that simply
prints a message to the console.
- An enhanced player, SequencePlayer2,
has been provided that can be used instead of the old
SequencePlayer if desired. In this
player, the play() or
stop() methods have been replaced with a
factory method, makePlayable(), that
creates SequencePlayer2.IPlayable
objects that are self-contained musics players that can be
separately started and stopped.
- This new player enables you to load and parse songs but not
play them right away and thus create multi-part harmonies from
multiple songs, e.g. rondos. The individual
songs (IPlayable objects) can be
started and stopped individually at will.
- This new player can thus be used to solve the "bug" in the
demo application where multiple songs could be played at once
but only the last song could be stopped.
- ABCUtil: this class has several static
methods that will enable you to parse things a little easier.
- parseFraction can be used to convert the
default note length from a String to a
double.
- parseTempo can be used to convert the
tempo from a String to a
double. With the help of the
defaultNotesPerQuarter note value, it will
convert the default notes per minute specified by the L header into the
quarter notes per minute (beats per minute) needed by
SequencePlayer.setTempo().
- getFileContents can be used to return
the contents of a file as a String.
- KeySignature: this class keeps track of
which notes are sharp/flat for the particular key signature you are using.
The adjust method returns a note whose pitch has
been properly adjusted for the given key signature.
Your final program
should always enqueue notes only after they have been adjusted for the key
signature of the piece (simply via a call to KeySignature.adjust on a properly initialized
KeySignature object). While you are initially
developing and debugging your program, if you only play music that is in the key
of C, you can ignore the key signature (as no notes need to be adjusted in that
key).
Be sure to call the SequencePlayer's
init() method beforeprocessing a song or you may hear the previous song
as well. The init method will clear out
any old music in the sequencer.
GUI
You may make your GUI operate however you think is appropriate.
However, you must be able to load, parse, play, and stop abc music files.
You must also display the currently loaded file and use the toString method to
display the music after it is parsed.
The demo GUI makes use of a JSplitPane component.
Each pane then has a JScrollPane which contains a
JTextArea. However, you may organize things
differently if you prefer.
If you want the text to wrap inside of the text area, look at the
setLineWrap and
setWrapStyleWord methods.
Assignment Notes
You should implement a very simple GUI that enables you to parse files, print
them (using your toString visitor), and play them (using your player visitor).
You should also add whatever debugging support you desire to your GUI.
In this program, the model is relatively simple, and contains only a few
things (including a parser, a phrase, and a player). Similarly, the view is
also relatively simple. But, you should still properly use the MVC
architecture.
We recommend approaching the assignment as follows:
- Implement the basic GUI and your MVC architecture. As you work on
the following steps, it will be easier if you can easily display your
results in the GUI.
- Implement the toString visitor first.
Look at the code for the toString() methods of
MTSeqList and NESeqList
and you will see that those two methods comprise the two cases of the main
(outer) algorithm of a forward accumulation algorithm to generate a string
representation of the list. What is missing is the recursive forward
accumulating helper (inner) algorithm. A handy,
setToStringAlgo method is provided in
NESeqList that enables you to write that helper
algorithm and install it into NESeqList.
(Why doesn't MTSeqList need this?)
- Basic extended visitor construction technique:
- Instantiate the visitor itself using an anonymous inner class,
specifying a default case command to the superclass constructor, if
needed. (see the
Anonymous Inner Class Techniques page to see how to do this).
- Instantiate the visitor's commands using anonymous inner
classes. Do this either externally to the visitor or
inside the visitor's initialization block (see the
Anonymous Inner Class Techniques page to see how to do this).
In either case, the commands will close over the visitor
itself. If you are using a local variable for the
visitor, the Java compiler may complain that the variable may not be
initialized if you try to access it. To solve this
problem, use the technique detailed in in the
Accessing Enclosing Anonymous Inner Classes section of the Anonymous
Inner Class Techniques page..
- Note: you will need to downcast
the host to the sub-type that the command is designed for.
- Add the commands to the visitor with their associated ID values.
How can you get multiple hosts to have the same processing without
duplicating code?
- Only the helper algorithm is required for
this assignment. However, one should note that the
entire toString process is technically an external algorithm to the
ISeqList and thus could be written as a
visitor. It is highly suggested that you attempt to
take the code from the toString() methods of
the empty and non-empty lists and adapt them into a fully external
ToStringAlgo (that utilizes your helper
algo) that when executed, will return the string representation of the
list.
- The toStringAglo anonymous inner class (APhraseVisitor
subclass) only requires two commands: one for an NESeqList and one for an
MTSeqList.
All other classes have already been supplied with a properly working
toString method which these commands will
use. This will refresh your memory on how visitors work and will
introduce you to the extended visitor code.
- Create an IPhrase object that consists of a
single note. Create a visitor with a command for
Note hosts and make sure you can play a single note. You will
need to initialize a SequencePlayer object with
some defaults for this to work. (This was the exercise from
Lab07.) Think about what parameters need
to be passed to the Note's visitor in order to use the
SequencePlayer and the return value of that
visitor.
- Once you can play a Note, add support to
play a single Chord or
Triplet.
- Create an IPhrase object that is a sequence
of notes (ISeqList). Add commands to your visitor to handle sequences and make sure you
can play them properly.
- Finally, add support in your visitor for Header
objects. If you are adventurous, try to play a parsed abc file. Otherwise,
construct your own IPhrase object that has a
sequence that contains a sequence of headers and a sequence of notes. Try to
play that musical phrase first.
- There are a lot of classes in the IPhrase
hierarchy. Of these classes, how many of them are
distinct extended visitor hosts? Is it necessary to
install commands to handle all of those classes? Can a
visitor even tell if certain classes are serving as its host? Why?
- Anonymous inner classes are crucial for this assignment. To
learn about how to do complex initializations of an anonymous inner class,
see the
Anonymous Inner Class Techniques page in the Java Resources site.
Especially pay attention to the discussion on "initialization blocks"
and on "Accessing Enclosing Anonymous Inner Classes".
- In order to properly play abc files, you will need to install commands
to deal with headers you do not care about. Consider the following
code for that purpose:
String headerString = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
for (int i = 0; i < headerString.length(); i++) {
myVisitor.addCmd("" + headerString.charAt(i), defaultHeaderCmd);
}
You should install commands for the headers you care about
after you do this, so that those commands will replace the default command being
used for a header with an arbitrary letter identifier.
- Tuplets are very difficult to describe concisely, requiring fairly advanced
musical knowledge. In such, Tuplets should be treated as an
unknown host type. If you think that you can write a reasonable
tuplet handler, document your work carefully, let the staff know that you are
attempting this and be sure that you include test songs for it. You might
be rewarded with some extra credit points!
Weird Potential Java MIDI bug:
If your program sounds as if it plays the first note
of a musical piece twice, try starting adding the notes to the
SequencePlayer starting at tick #1 rather than at
tick #0.
Another Weird Potential Java MIDI bug:
Windows users may encounter an error that says that
there is a "WindowsPreferences" error that cannot
make a "prefs root node". Please
see the discussion below.
Provided Code
The provided code is available via subversion. You
should follow the directions to acquire the provided code
and keep it up to date. All of the provided code will be contained
within a package called provided. Do not
modify, remove, or add anything in the provided
package.
Library documentation
Sample ABC music files: You can
access several sample abc music files at
https://svn.rice.edu/r/comp310/course/HW06/songs. You can access these
files directly via that URL or add them to your project in the same way you
added the provided package (the
svn:externals property can have multiple lines, one per external
repository).
But
don't limit yourself to your instructors' boring musical tastes, go find your
own files or make your own music! If you have problems playing
a particular file, let the staff know right away to make sure that there isn't a
problem in the libraries or that the file is using an unsupported feature.
Assignment requirements:
- All code must be properly commented. This, as always, includes UML
diagrams of your system. (10 pts)
- The GUI should support all of the required operations, be intuitive to
use, and all buttons should contain tool tips. (10 pts)
- MVC architecture (5 pts)
- You must write an extended visitor to implement
toString for music sequence lists. (15 pts)
- You must write an extended visitor to traverse an
IPhrase object and add the appropriate notes to the sequence player
so that the music may be played. Required features (60 pts total):
- Key signatures ("K" header) (5 pts)
- Tempo ("Q" header) (5 pts)
- Default note ("L" header) (5 pts)
- Ignored headers (5 pts)
- Individual notes (10 pts)
- Chords (5 pts)
- Triplets (5 pts)
- Non-empty sequence list (10 pts)
- Empty sequence list (end of song) (5 pts)
- Unknown host (5 pts)
You are NOT required to handle mutiple instrument choices as
the demo does, though it would be more fun if you did. ;-)
WindowsPreferences
Error (Windows only)
Some people's Windows machines have been displaying the
following error when they play their phrase sequence:
java.util.prefs.WindowsPreferences
<init>
WARNING: Could not open/create prefs root node
Software\JavaSoft\Prefs at root 0x80000002. Windows RegCreateKeyEx(...) returned
error code 5.
It is not clear whree exactly this comes from but here are some links to
the issues on the web:
© 2016 by Stephen Wong