In this lab, you’ll construct an object model of musical pieces with the ability to load parts of those pieces from files and to play all the parts together. The system, called the “Calgorythmic” music box, will load the parts using standard Java file I/O and it will play them using the Java Sound API. A key theme for the lab exercise will be the importance of assigning the appropriate responsibilities to each class in the model.
Music is an ancient art form and musical notation has developed into a rich and powerful system. Consider the following excerpt
from the traditional song “Frère Jacques”
:
Here we see the time-honored, graphical way of specifying the pitches, relative durations and sequence of notes in a piece of music. Pitches are denoted by the location on the five-lined staff; durations are denoted by the format of the note’s notation; sequence is read left-to-right.
As with other media, digitizing music requires looking at the content in a more detailed way
than one might commonly do. The Musical Instrument Digital Interface (MIDI) is a common music
protocol that models a piece of music as a set of pitches with pitch starting and stopping events.
It models playing a note on a piano as a three-step sequence of operations: pressing the piano key,
waiting an appropriate amount of time and then releasing that key. This model is shown in the
following figure:
Here we see the first eight notes of Frère Jacques. On the top of the diagram we see the pitches C4, D4, and E4 (i.e., the forth C, D and E notes from the bottom of the piano), each played as quarter notes (i.e., roughly speaking one beat) in the given sequence. On the bottom we see the equivalent MIDI notation, which views the part as a sequence of Midi “events”. To model the first note, the Midi sequence specifies a C4 pitch-start event followed by a quarter-note-long wait event. For the second note, Midi specifies a C4 pitch-stop event, a D4 pitch-start event, and then another quarter-note-long wait event.
The classes you will use in this lab is shown in the following figure.
These two packages serve the following purposes:
calgorythmic
models a Piece
of music as a set
of Part
objects to be played simultaneously, and an individual Part
as a
set of Chord
objects to be played sequentially. You will copy these classes into your
lab package and modify them for this lab exercise.midi
supports music applications (like Calgorythmic) by
providing basic MIDI services. These features are bundled in the midi.jar
JAR file
that you will add to your project’s build path. Note that the Midi library contains
pre-defined constants useful for specifying music (e.g., middle C is Midi.C4
, a
quarter note is Midi.QUARTER
, etc.). You will use these classes indirectly but will
not modify them.Take time now to review the complete API specification for these packages. Given this infrastructure, you can create a simple console-based program that plays pieces of music specified by your code.
Create a new package for this lab (edu.institution.yourLoginID.lab11
)
and load the initial code as follows:
midi.jar
from the standard course library to your main project’s
build path;calgorythmic
and data
) into your lab11
package;With done, make sure that you can play the middle C piece specified in the exercise0()
method by running the CalgorythmicConsole
application.
Now, extend CalgorythmicConsole
by adding an example1()
method that
plays the first 4 notes of Frère Jacques (i.e., C4
-D4
-E4
-C4
,
all Midi.QUARTER
notes). Base your new method on the exercise0()
method.
Given the Calgorythmic API, you can program simple pieces of music using basic control
structures. For example, to create and play a chromatic scale from Midi.C3
to Midi.C6
,
you can use the following algorithm:
Declare/initialize a new Piece (chromaticScalePiece
) with tempoMidi.PRESTO
.
Declare/initialize a new Part (chromaticScalePart
) with instrumentMidi.PIANO
.
Loop for i fromMidi.C3
up toMidi.C6
Add a new Chord object tochromaticScalePart
with pitchi
and durationMidi.QUARTER
.
Loop for i fromMidi.C6
back down toMidi.C3
Add a new Chord object tochromaticScalePart
with pitchi
and durationMidi.QUARTER
.
AddchromaticScalePart
tochromaticScalePiece
.
PlaychromaticScalePiece
.
Add methods to CalgorythmicConsole
that implement the following:
Chord
) song where each note has a random pitch and a random
duration (see the lecture notes for details on how to use the Java Random
class);Hard-coding pieces, as we have done so far, does not tend to scale well as the parts get
longer and more complicated. Instead, we store the sequence of chords for a part in a carefully
formatted text file and add a method to load that part from a file. This process is called parsing.
As discussed in class, parsing data from external sources like files or the keyboard can be a complicated business. However, in this lab, we’ll stick with a simply formatted text file that has one line for each chord (e.g., see the melody part for Frère Jacques: FrereJacques.txt), and we’ll assume that the file is properly formatted.
The knowledge of how to parse a Part
object has two key aspects:
Chord
- Each line in the part file represents a chord record (e.g.,
“QUARTER C4” represents a quarter note on C4). Knowledge of how to parse the fields on this line
resides in the Chord
class. We’ll implement it as a new Chord constructor method,
Chord(String line)
:
Receive theline
representing a chord from the calling program.
Setscanner
equal to a newScanner
for that line.
SetmyDuration
to the parsed duration represented by that token (usingMidi.parseDuration(scanner.next())
).
SetmyPitch
to the parsed pitch represented by that token (usingMidi.parsePitch(scanner.next())
).
Close thescanner
.
SetmyVelocity
to the default velocity.
Part
- Each part file represents a part. Knowledge of how to
open and parse a file resides in the Part
class. We’ll implement it as a new Part
constructor method Part(String filename, int instrument)
:
Receive thefilename
containing the part and theinstrument
from the calling program.
SetfileIn
equal to a newScanner
for a stream to the file with the given filename (new Scanner(new File(filename))
)
SetmyChords
equal to an empty List ofChord
objects.
Loop whilefileIn
has another line yet to read
Read the next line of the file and add a newChord
object based on the line tomyChords
.
(Note: Use theChord(String)
constructor you just built to do the parsing (i.e.,myChords.add(new Chord(fileIn.nextLine()))
).
ClosefileIn
.
SetmyInstrument
equal toinstrument
.
Given these new constructors for Chord
and Part
, you can load and
play the melody of Frère Jacques from FrereJacques.txt
using the following algorithm:
Declare/initialize a new Piece using the tempoMidi.ALLEGRETTO
.
Declare/initialize a new Part based on the chords specified in the following file:"src/edu/institution/yourLoginID/lab11/data/FrereJacques.txt"
(Note: Use thePart(String, int)
constructor you just created and pass it the given filename andMidi.PIANO
as the instrument.
Add the new Part to the Piece.
Play the new Piece.
Build the parsing constructors for Chord
and Part
using the
algorithms given above. Then create an exercise3()
method, based on the algorithm just
given, that loads and plays Frère Jacques from FrereJacques.txt
(found in your data
directory).
It is possible to unit test file input and output in the same way that you test all other aspects of an application’s model. For this lab, you can create sample text files, say one file with several lines specifying valid durations and pitches and then several other files each with different violations (e.g., a bad duration, a bad pitch), and then use these files in unit tests of your file loading and parsing methods.
Create three testing data files:
BLOB
);H4
).Create a JUnit test class that verifies that your new constructors successfully load the good file, and throw errors for the bad files. You only need to test the file opening and parsing; don’t bother testing any of the other features of the music model.
Pieces of music are generally made up of multiple parts, say a melody and harmonies, or multiple copies of a part played as a round. The Calgorythmic classes you now have can load the melody of Frère Jacques and craft it into a 3-part round using the following algorithm:
Declare/initialize a new Piece using the tempoMidi.ALLEGRETTO
.
Declare/initialize a new Part (melody
) constructed using the Frère Jacques file used above, setting the instrument toMidi.PIANO
.
Declare/initialize three new Parts:round1
,round2
,round3
, all using the instrumentMidi.PIANO
.
// Create a part for round 1
Add the chords of the melody toround1
(usinground1.addChords(melody.getChords())
).
Add two measures of rest to the end ofround1
(usinground1.addRests(2 * Midi.WHOLE)
).
// Create a part for round 2
Add one measure of rest to the beginning ofround2
.
Add the chords of the melody toround2
.
Add one measure of rest to the end ofround2
.
// Create a part for round 3
Add two measures of rest to the beginning ofround3
.
Add the chords of the melody to the end ofround3
.
Add all three rounds to the Piece.
Play the Piece.
Build an exercise4()
method that loads Frère Jacques from FrereJacques.txt and plays it as a three-part round.
Submit all the code and supporting files for the exercises in this lab.