CS 124, Spring 2021
Lab 8: Classes and Objects

For this lab, you will write two complete classes and a main program that will create and use instances of those classes. The program will use Midi to play short tunes. In addition to the classes that you write, the program will use the Scanner class and a class named SimpleSynth that provides a simplified interface to Java's Midi support.

You will need a copy of the file SimpleSynth.java, which must be added to a folder named midi in your Eclipse project. You will probably also want to add two sample main programs, TestMidi.java and TestTune.java, to the default package in your project. Note that TestTune.java will have errors until you have written the Tune class.

You will turn in three files for this lab: Note.java, Tune.java, and MidiPlayer.java. These files must work with the original version of SimpleSynth.java, which must be in package midi. Do not modify SimpleSynth.java or move it out of package midi.

The lab is due in two weeks, by midnight on Monday, April 12, for full credit. It will be accepted late, with a 10% penalty, until midnight on Monday, April 19. (The original late deadline was noon on Saturday, but it has been extended until midnight on Monday.)

About Midi and SimpleSynth

MIDI (Musical Instrument Device Interface) was designed as a way for computers to control musical instruments. Midi can also be used to control a "synthesizer" that is part of the sound card on a computer. A synthesizer can play notes through the computer's speakers that imitate the sounds of various instruments. Midi has a set of 128 standard "instruments", although not all of them sound realistic and some of them don't actually imitate instruments as such. The instruments are often numbered from 1 to 128 in the documentation, but in Midi programs, they are actually numbered from 0 to 127. A Midi instrument is specified by an integer in that range.

One can do some complex things with Midi, but it supports very simple uses with a few commands for turning particular notes on and off, for selecting the volume, and for selecting the instrument that is used. (It is possible to play several notes at the same time, and it is possible to play several different instruments at the same time, but we won't be doing that.) Like instruments, the possible notes and volume are identified by numbers in the range from 0 to 127. Note number 60 is "Middle C" (the middle key on a piano keyboard). There are 12 notes to an octave, so going up an octave means adding 12 to the note number.

The file SimpleSynth.java defines a simple interface to some of the most basic Midi capabilities. To use it, you need to create an object of type SimpleSynth; you only need one such object, which can be a global variable in your program. If synth is that object, then you can call methods such as synth.noteOn(noteNumber), synth.noteOff(noteNumber), synth.setVolume(volumeNumber), and synth.setInstrument(instrumentNumber). There are a few static named constants such as SimpleSynth.PIANO that give the instrument numbers for some common instruments. There is also a static method SimpleSynth.getNameForInstrument(N) that returns the official name for a given instrument number. Read the Javadoc comments on the code, for more information! The source code includes a complete list of instrument names.

To play a note, you have to turn a note on, wait a while, and then turn the note off. There is a standard method Thread.sleep(n) that implements a pause of n milliseconds. You can use it to implement "wait a while", but it can throw a funny exception that has to caught, even though it will never happen in this program. So the code for delaying by n milliseconds is

try {
    Thread.sleep(n);
}
catch (InterruptedException e) {
}

See the sample program TestMidi.java for an example of playing notes.

Reviewing Scanner

Although the Scanner class has been introduced briefly in class, we have been using TextIO for input in example programs. For this lab, you should use Scanner instead. To review, to use Scanner to read user input in your program, you must have

import java.util.Scanner;

at the top of the program. You need to create a Scanner object for reading input from the user. You should only make one object for use in the entire program. You can declare a variable in of type Scanner and say

in = new Scanner( System.in );

at the start of the main() routine. You can then use in.nextInt() to read integers from the user.

You will get full credit on this lab as long as you can handle legal inputs. If the user enters something that is not an int, the program will crash. If you want to avoid that crash and give an error message to the user instead, you can use in.hasNextInt() to test whether the next item in input is an int before reading it. Note that if you detect an error, it is important to discard the user's bad input before proceeding. You can do that by calling in.nextLine(), and discarding the input that is returned by that method.

The Main Program

To begin work, you should add the file SimpleSynth.java to a folder named midi in the src folder of your Eclipse project. You will probalby also want to add TestMidi.java to the default package in your project. You can run it to test that the midi system on your computer is working. It plays no other role in this lab, but it also shows how to play notes using a SimpleSynth object.

You will write a program named MidiPlayer. It will be a command-line program rather than a GUI program. It will have the usual main() routine, and any subroutines or global variables in the program will be static. You should create the MidiPlayer class. I think it makes sense to put it in the default package, not in the midi package. You should add import midi.*; to get access to SimpleSynth.

The program will need a SimpleSynth object that it will use for playing notes and a Scanner for reading the user's input. Assuming that you plan to use some subroutines in your program, these can be stored in global static variables.

It would be nice to start the main() routine by printing out some information and instructions for the user. The program will then run in a loop where it presents a text menu to the user, reads the user's response, and carries out the user's command. Here is the menu that you should use:

What do you want to do?

 0.  EXIT  
 1.  Select an instrument.
 2.  Select a volume.
 3.  Play some notes.
 4.  Enter some notes for a tune.
 5.  Make up a random tune.
 6.  Play the tune.
 
Enter the number for your choice:

The user should respond with a number from 0 to 6. The program should carry out the requested action before returning to the start of the loop and printing the menu again.

Note that you can write the main program and code the non-trivial actions as subroutine "stubs." A subroutine stub is an empty subroutine that you can call but that doesn't yet do anything useful. You would then go on to complete the program by filling in the subroutine stubs. This is typical top-down design, where you break up a problem into smaller tasks, and then work on the individual tasks.

You will need to write Note and Tune classes before you can implement commands number 4, 5, and 6, but it is possible to do commands 0 through 3 before those classes are written. Here is what the commands should do:

About Creating a Random Tune

One part of the assignment is to create random tunes. Now, you could make a random tune simply by adding a bunch of notes with randomly selected note numbers to a Tune, but that would probably sound bad. My own idea for making a random tune was that the change from one note to the next should ordinarily be very small, but that sometimes there should be larger jumps. That is, I select a random change in the note number from one note to the next. So, the notes do a kind of random walk along the keyboard. The results sound better to me when the probability of a large jump is much smaller than the probability of a small jump. (If you think about it, this is kind of like the problem of selecting a random number of runs scored in an inning in our baseball simulation.) I also add occasional random pauses. My random songs are 100 notes long. You might also play with the idea of random durations for the notes.

When selecting random values or doing a random walk, you should make sure that the note numbers lie in a reasonable range. Although there are 128 possible note numbers, many of the notes are actually just about inaudible to human hearing. My program makes sure that the note numbers remain in the range from 36 to 84.

The Note Class

You should create a new class named Note. To me, it makes sense for Note.java to be in the midi package. A Note object will represent some specific note, to be played for some specific duration. Since you are working with objects here, there will be nothing static in the class.

A note needs two instance variables to represent the Midi note number for the note and the number of milliseconds for which the note will play. You can use a note number of -1 to represent a period of silence. The Note class is required to have a constructor that specifies the note number and the time in milliseconds. This means that a Note object can be created, for example, as new Note(60,100). The constructor should throw an IllegalArgumentException if the values passed as parameters to the constructor are not legal values for the instance variables.

A note is also required to have a play() method. To play a note requires a Midi synthesizer, that is, an object of type SimpleSynth. The SimpleSynth object should be a parameter to the play() method. This means that if note is a Note and synth is a SimpleSynth, then you can call note.play(synth) to play the note.

You should follow good style when writing Note.java. For example, the instance variables should be private. (They could, in fact, be final.) And you might provide getter methods for the instance variables, even though they are not required by this project. It would be nice to have a Note class that is somewhat general and could be used in other projects. And don't forget comments, in Javadoc style!

The Tune Class

You should create a new class named Tune. To me, it makes sense for Tune.java to be in the midi package. A Tune object represents a list of notes. Each note is represented by an object of type Note. Playing a tune will mean playing each of the Notes in the list.

The list of notes can be represented by a partially full array of type Note[]. This would require two instance variables, one for the array and one for the number of notes that are actually stored in the array. (Alternatively, if you already know about ArrayList, you could store the notes in a variable of type ArrayList<Note>.)

The Tune class is required to have a constructor that has no parameters, so that a Tune can be created by calling new Tune(). The constructor can create the note array.

The Tune class is also required to have a method named add() that adds one Note to the list of notes. That is, if tune is a Tune and note is a Note, then calling tune.add(note) will add the note to the tune.

And the Tune class is required to have a method named play() with one parameter of type SimpleSynth that will use the SimpleSynth to play all of the notes in the tune.

Of course, you should follow good programming style when writing the Tune class!

The short main program TestTune.java tests the Tune and Note classes by creating a very short tune and playing it.