Students who complete this lab will demonstrate that they can:
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 are 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
.lab12
) and load the initial code as follows:
midi.jar
from the standard course library to your main project’s
build path (off-campus students can download this library from here: midi.jar);
calgorythmic
and data
) into your lab12
package (off-campus students can download
these files from here: data.zip and calgorithmic.zip);
NOTES:
When done, your directory/package structure should look as follows:
edu institution loginID lab12 calgorythmic data
Now, do the following:
exercise0()
method by running the CalgorythmicConsole
application.
CalgorythmicConsole
by adding an exercise1()
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 from
Midi.C3
up toMidi.C6
- Add a new Chord object to
chromaticScalePart
with pitchi
and durationMidi.QUARTER
.- Loop for i from
Midi.C6
back down toMidi.C3
- Add a new Chord object to
chromaticScalePart
with pitchi
and durationMidi.QUARTER
.- Add
chromaticScalePart
tochromaticScalePiece
.- Play
chromaticScalePiece
.
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);
Call your new method(s) exercise2
(and exercise2b
for the extra
credit).
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
- found in the data directory downloaded earlier), 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)
:
line
representing a chord from the calling program.
scanner
equal to a new Scanner
for that line.
myDuration
to the parsed duration represented by that token (using Midi.parseDuration(scanner.next())
).
myPitch
to the parsed pitch represented by that token (using Midi.parsePitch(scanner.next())
).
scanner
.
myVelocity
to the default velocity. (Velocity here indicates how
"hard" you are playing the note e.g. hitting the key on a piano.)
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)
:
filename
containing the part and the instrument
from the calling program.
fileIn
equal to a new Scanner
for a stream to the file
with the given filename ( new Scanner(new File(filename))
)
myChords
equal to an empty List of Chord
objects.
Note that myChords
is already declared in the Part
class.
fileIn
has another line yet to read
Chord
object based on the
line to myChords
.
Chord(String)
constructor you just built to do the parsing (i.e., myChords.add(new
Chord(fileIn.nextLine()))
).
fileIn
.
myInstrument
equal to instrument
.
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 tempo
Midi.ALLEGRETTO
.- Declare/initialize a new Part based on the chords specified in the following file:
"src/edu/institution /yourLoginID /lab12/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).
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 tempo
Midi.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 four measures of rest to the end of
round1
(usinground1.addRests(4 * Midi.WHOLE)
).- // Create a part for round 2
Add two measures of rest to the beginning ofround2
.- Add the chords of the melody to
round2
.- Add two measures of rest to the end of
round2
.- // Create a part for round 3
Add four measures of rest to the beginning ofround3
.- Add the chords of the melody to the end of
round3
.- 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 the final version of your music player. We will grade this exercise according to the following criteria:
exercise 12.1
- Get the simple notes to play.
exercise 12.2
- Get the chromatic scale to play.
exercise 12.3
- Get Frère Jacques to play.