COMP 310
Spring 2018

HW06:  ABC Music Player

Home  Info  Canvas   Java Resources  Eclipse Resources  Piazza

Deprecated Web Page! This content has either been moved to Canvas or has been removed from the course. Please inform the staff right away about any links that led to this page.

The Demo

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
IPhrase UML Diagram

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:

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:

  1. 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.
  2. 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?
  3. 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.
  4. close: you can stop the music at any time by calling the close method.
  5. 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.

Note lengths, tempo and ticks/default-note vs. ticks/quarter-note

Understanding note lengths and tempo in the system can get a bit confusing.   The problem is that MIDI and ABC Music have two very different notions of tempo.  

ABC has a notion of the "default note" by which everything else is measured, including the tempo.   In ABC, you can define the "default note" to be any length note you choose, e.g. quarter note, eighth note, whole note, etc.   ABC considers the default note to be the "beat" length.   "L" header defines the default note for the song.    Note that the ABCUtils provides a utility method to parse the string-represented fraction that ABC gives as the default note length into a decimal value that is in units of whole notes, i.e. a "1/4" (quarter note) is converted into 0.25. 

ABC then defines a tempo in terms of "default notes/minute" or "beats/min" (bpm).   So the actual "speed" of a song depends on the size of the default note as well as the given tempo.   For instance, if the tempo is set to 140 bpm, a song with a default note set to a quarter note will play twice as fast as the same song but with the default note set to an eighth note.

MIDI, on the other hand, has a notion of "ticks" which are just like the ticks of a metronome.   A tick represents the length of time for the shortest possible single note.   For convenience, the supplied SequencePlayer is configured in a song-independent manner using the value of "ticks/quarter-note".   A value of 16 ticks/quarter-note means that the very shortest note that the SequencePlayer can play is a 1/64'th note  (=1/4 divided by 16).    That should handle anything but the craziest of songs. 

For MIDI, tempo is thus defined in terms of ticks/minute.     To more easily relate that value to an ABC song, the SequencePlayer can be configured to understand how long a default note is, in terms of ticks.   For the "L" header, we know the definition of a default note.   From the SequencePlayer we can get the configured ticks/quarter-note.   This is enough information to configure the  SequencePlayer's ticks/default-note setting.  

Conversely, since the SequencePlayer can tell us both the ticks/quarter-note and the ticks/default-note (once the "L" header has been processed -- the SequencePlayer  defaults the default note to be a quarter note otherwise), the value of default-notes/quarter-note can be calculated, which is a value that is conveniently independent of ticks.

The SequencePlayer's tempo is set using the song-independent value of quarter-notes/minute.   But since ABC is defining tempo in terms of default-notes/minute, a conversion needs to take place that requires the value of the default-notes/quarter-note.   Conveniently, ABCUtils provides a method that will parse an ABC tempo string which combined with the default-notes/quarter-note value, will return the decimal value of quarter-notes/minute.    Processing a "Q" headers needs to go through this process to set the SequencePlayer's tempo.

Advice:  If your songs are playing too fast or too slow, check both your "L" and "Q" header processing!

 

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
Extended visitor UML

The two visitors you must write will do the following:

  1. Convert the entire piece of music into a String so that you can print it.
  2. 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:

  1. ABCParser: this class is will parse an abc file into an IPhrase data structure.
  2. 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.
  3. ABCUtil: this class has several static methods that will enable you to parse things a little easier.
    1. parseFraction can be used to convert the default note length from a String to a double.
    2. 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 Q header into the quarter notes per minute needed by SequencePlayer.setTempo().
    3. getFileContents can be used to return the contents of a file as a String.
  4. 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:

  1. 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.
  2. 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?)
  3. 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.
  4. Once you can play a Note, add support to play a single Chord or Triplet.
  5. 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. 
  6. 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.
  7. 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?
  8. 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".
  9. 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:
  10. 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.

  11. 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:

  1. All code must be properly commented. This, as always, includes UML diagrams of your system. (10 pts)
  2. The GUI should support all of the required operations, be intuitive to use, and all buttons should contain tool tips. (10 pts)
  3. MVC architecture (5 pts)
  4. You must write an extended visitor to implement toString for music sequence lists.  (15 pts)
  5. 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):
    1. Key signatures ("K" header) (5 pts)
    2. Tempo ("Q" header) (5 pts)
    3. Default note ("L" header)  (5 pts)
    4. Ignored headers (5 pts)
    5. Individual notes  (10 pts)
    6. Chords (5 pts)
    7. Triplets (5 pts)
    8. Non-empty sequence list  (10 pts)
    9. Empty sequence list (end of song)  (5 pts)
    10. 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:

 


© 2018 by Stephen Wong