CS 124, Fall 2017
Lab 8: Objects and Events
You have worked with objects since the beginning of the course (for example, strings and colors are objects). But for this lab, you should be thinking about them with more depth and understanding. You will work with objects belonging to two classes, SimpleSynth and KeyboardPanel, to make a program that will let the user play notes by clicking on an illustration of a piano keyboard. The program will also require you to work with objects representing "events" that the user generates by using the computer's mouse and keyboard.
The Keyboard and SimpleSynth classes are defined in a package named "midi". That means that the .java files are in a folder named "midi". You will need to add a copy of the midi folder to your project. You will also need a copy of MidiKeyboard.java. You can find them in the folder /classes/cs124/lab8-files. Copy the contents of that folder into the src directory of your project. Be sure to copy the entire midi folder, not just the two files that it contains. In your project, you should see MidiKeyboard.java in the default package, and you should see SimpleSynth.java and KeyboardPanel.java in package midi.
Your work for this lab is due at the beginning of lab next Thursday, October 26. You will only work on MidiKeyboard.java, and that is the only file that you will submit. Do not make any changes to SimpleSynth.java or to KeyboardPanel.java; your program must work with the original versions. Use the submit web site to submit your program, as usual.
The Assignment
If you run the MidiKeyboard, you'll see a window containing a piano keyboard. The window also has a menu bar, with an "Instrument" menu from which the user can select various instrument names. However, neither the menu nor the keyboard do anything useful. (Selecting from the menu, or clicking the piano keyboard, or pressing a key on the computer keyboard will change the message that is displayed at the bottom of the window.) For a minimal version of the assignment (which does not mean full credit), you will implement three features:
- The user should be able to play notes by using the mouse to click keys on the piano keyboard. When the user presses a mouse button on a piano key, the note corresponding to that key should be turned on. When the user releases the mouse button, the note should be turned off.
- The user should also be able to play notes by pressing the '1', '2', '3', ..., '8' keys. These keys should play the same notes as the first 8 white keys on the piano keyboard (or, if you prefer, for the last 8 white keys on the keyboard). You can also play notes for other keys if you want.
- The "Instrument" menu should work. That is, after the user selects an instrument from the menu, that instrument should be used to play the notes. You can add more instruments to the menu if you want. When the user selects a new instrument, the program should still display a message about the selected instrument at the bottom of the window.
For more credit, you should implement at least one more feature (and maybe two for full credit, depending on exactly what you do). Several possible features are implemented in the sample program MidiKeyboardComplete.jar, which you can find in /classes/cs124. (You can run that program on the command line with the command java -jar MidiKeyboardComplete.jar.) For example:
- Give the user some way of changing the volume, either with another menu or by hitting certain keys on the computer keyboard. The sample program has a volume menu and also uses the up- and down-arrow keys for changing the volume.
- Suppose that the user drags the mouse on the piano keyboard, while holding down the mouse button. When the mouse moves from one key to another, you can change the note that is being played.
- When the user clicks the piano keyboard using the middle mouse button or right mouse button, the program can play a note different from what it plays for the left mouse button. The sample program subtracts two octaves from the note when the middle mouse button is used and adds two octaves when the right mouse button is used. Another possibility would be holding down the shift key to change the octave.
- Let the user change the instrument by pressing certain keys on the keyboard. The sample program selects a different instrument for each letter key 'A' through 'Z' (which is really overkill).
Be sure to document any features that you implement in your comment at the top of the program.
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. (It is also possible to play several different instruments at the same time, but we won't do 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 defines a simple interface to some of the basic Midi capabilities. To use it, you need to create an object of type SimpleSynth in the MidiKeyboard class; you need only one such object, and it should be a global variable in your program. If midi is that object, then you can call methods such as midi.noteOn(noteNumber,volume), midi.noteOff(noteNumber), and midi.setInstrument(instrumentNumber). There are a few 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 source code, and the Javadoc comments on the code, for more information. You can also see the list of instrument names in the source code.
Mouse Events and KeyboardPanel
GUI programs are driven by "events", many of which are caused by user actions. For now, you don't need to know exactly how events are handled by a program. You just need to know that the MidiKeyboard class has already set things up so that when the user presses a mouse button while the mouse is over the piano keyboard, then the system will call the method mousePressed() in the MidiKeyboard class. When the user releases the mouse button, the system will call mouseReleased(). And when the user drags the mouse on the piano keyboard, the system will call mouseDragged(). The mouseDragged() method will be called over and over as the user moves the mouse, until the mouse button is released.
All three methods have a parameter named evt of type MouseEvent. This parameter is an object that contains information about the event. For example, evt.getX() is a function that returns the x coordinate of the location of the mouse, and evt.getY() returns the y coordinate. The function evt.isMetaDown() returns a boolean value which is true if the right mouse button was used or if the Command key is being held down on a Mac. The function evt.isAltDown() returns a boolean value which is true if the middle mouse button was used or if the Alt/Option key is being held down.
In order to implement mouse interaction, you will need the global variable of type SimpleSynth that was mentioned above. You will also need to translate the location of the mouse into the Midi note number that should be played. For that, you need to use the global variable keyboard, of type KeyboardPanel, which already exists in the program; that object contains a simple method for translating mouse coordinates to note numbers. Finally, you should note that the location where the user releases the mouse does not have to be the same as the location where the user pressed the mouse, so you can't simply use the position of the mouse when the button is released to decide which note to turn off. This means that you have to keep track of which note is currently being played, if any, so that you can turn off the right note when the user releases the mouse. For that, you will need another global variable.
Menus and Action Events
When the use selects a command from a menu, an "action event" is generated. The MidiKeyboard program has arranged things so that when the user selects a command, the system will call the actionPerformed() method. That method has a parameter, evt, of type ActionEvent that contains information about the event. Specifically, evt.getActionCommand() contains the text of the menu command. You can use the value returned by this method in an if or switch statement to decide what to do in response to the event. For this program, the response to a command from the "Instrument" menu should be to change the Midi instrument that is being used.
Key Events
When the user presses or releases a key on the computer keyboard, a "key event" is generated. The MidiKeyboard program has arranged things so that when the user presses a key, the system will call the keyPressed() method. When the user releases a key, the system will call keyReleased(). These methods both have a parameter, evt, of type KeyEvent that contains information about the event. In particular, evt.getKeyCode() returns an integer code number that tells which key was pressed or released. The code numbers are not obvious, but the letter keys 'A' through 'Z' have codes that are equal to the upper case ASCII characters 'A' through 'Z'. The number keys '0', '1', ..., '9' have consecutive code numbers 48, 49, ..., 57. To discover the code numbers for other keys, note that the MidiKeyboard program currently displays a message with the code number for any key that you press.
To play the correct notes when the user presses a number key, or turn off the correct note when the user releases a number key, you will need to know the note numbers corresponding to the white keys on the piano keyboard. They are: 48, 50, 52, 53, 55, 57, 59, 60, 62, 64, 65, 67, 69, 71, 72.
There is one issue that you should be aware of: If the user holds down a key, the key might "auto-repeat". That is, you can get a sequence of keyPressed events before you get a keyReleased event. If you turn on a note for every keyPressed event, you might get a stuttery sound as the note is repeatedly turned on. To avoid that, it's a good idea to turn a note on only if it is not already playing. A SimpleSynth object has a method isNoteOn(noteNumber) that tells whether the specified note is currently playing.