CS 124, Spring 2017
Lab 8: Using Objects and Events
Although you have, in fact, been using objects such as Strings and Colors since the beginning of the course, this is the first lab in which you are actually supposed to understand what objects are and how to use them. The lab also introduces the closely related concept of events and event-driven programs.
You probably want to make a new Java project in Eclipse for this week's lab. You should copy the following four files into the src folder in your project: MidiKeyboard.java, SymBall.java, BallPen.java, and MovingBalls.java. They can be found in /classes/cs124/lab8-files. (Note that they are in a subdirectory of /classes/cs124!) Make sure that the files are in the default package.
This lab is due on March 24, the Friday after Spring break, at 3:00 PM. You will submit files named MovingBalls.java and MidiKeyboard.java. Submit them through the Submit124 web page, as usual.
An Exercise Using Objects
The first two files define two classes SymBall and BallPen, which are used in the program. The third file, MovingBalls.java, defines the main program. You should run the program to see what it does. You will work on MovingBalls.java. Do not make any changes to the other two files; your program will have to work with the original versions.
The basic idea is that class SymBall defines "balls" (which are really just circles) that can bounce around inside "pens" that are defined by class BallPen. Furthermore, a SymBall can actually reflect itself to make a symmetric set of two or four balls. The original version of MovingBalls.java applies a double horizontal/vertical reflection to all the balls, which produces a four-way symmetric pattern.
A BallPen is a rectangular area inside which balls move. The BallPen constructor specifies the (x,y) coordinate of the upper left corner of the rectangle and the width and height of the rectangle. The SymBall constructor specifies the pen in which the ball will bounce around. The BallPen class and the SymBall class each has its own small API consisting of instance methods that allow you to configure a pen and balls. In the MovingBalls program, the pen and balls are all created and configured in the method named createContents(). You can check out that method to see how its done.
Your job is to modify MovingBalls.java to make it do something original and hopefully attractive. Your version of the program should use at least three pens, each containing a number of balls. You should study BallPen.java and SymBall.java to learn about the configuration options that are available. You must use a variety of configuration options in your program. Try to demonstrate most or all of the available options! Use a background color on some of your pens. Consider trying rather large balls in one of the pens. Try different types of symmetry. You might consider using translucent colors. (Recall that the constructor new Color(r,g,b,a) can be used to create a translucent color.) Your first step should be to study the constructor in MovingBall.java to see how that program creates a ball pen and balls. The ball pen and the balls that it contains are created and configured in a method named createContents(). The ball pen is represented by a global state variable named pen because it also has to be used in other methods. Note that trivial modifications of the program are not acceptable!
This is an exercise in creating and using objects. You will create objects belonging to two different classes, and you will modify those objects by calling methods that set their properties. Recall that a constructor is a special kind of subroutine that can only be called with the new operator. Its purpose is to initialize the object that is being created. Generally, you have to provide parameters to the constructor that will be used to determine some initial properties of the object. Once an object has been created, you can call instance methods in the object. Instance methods are defined by non-static subroutine definitions.
An Exercise in Responding to Events
GUI programming is, in general, event-driven. That is, the program responds to events generated by the user (or by other external sources, such as the network). The most basic events are mouse events, which are generated when the user moves the mouse or uses a mouse button, and key events, which are generated when the user uses the keyboard. In this exercise, you will program responses to some mouse and key events.
You will work on the file MidiKeyboard.java, which should be in your Eclipse project. Midi is a standard for controlling musical instruments and music synthesizers. The software for a computer's sound card almost surely implements a synthesizer that can be controlled with Midi. Java comes with a rather complex Midi API, but as is often the case in Java, the API makes it possible to do some basic things easily.
If you run the original MidiKeyboard program, it will display a small piano-type keyboard in a window. The progam plays two notes as it starts up, but otherwise does not make any sounds. Your job is to let the user play notes by clicking the piano keyboard. You will also let the user control some aspect of the program by typing. (If you don't hear anything when you run the program, check your computer's volume control.)
A compiled sample solution to this exercise can be found in the file /classes/cs124/MidiKeyboard.jar. To run the sample program on the command line, cd into the directory /classes/cs124 and give the command
java -jar MidiKeyboard.jar
Clicking the displayed keyboard will now play notes. Typing the characters '1', '2', ..., '9', '0' will also play notes. (The characters '1' through '8' correspond to the first 8 white keys on the displayed keyboard, so they will play a C-major scale.) When using the number keys, you can play more than one note at once.
About the Midi API. A major core class for making music with Midi is Synthesizer. A Synthesizer object is an interface to an actual Midi system outside the program. (It's an abstraction for that system!) The default Synthesizer that is used in MidiKeyboard.java uses the software synthesizer in your computer's sound card. A Synthesizer can have multiple "channels," each controlling a different simulated musical "instrument." A channel is represented by an object of type MidiChannel. The MidiKeyboard program uses just one channel, which means that it can only use one instrument at a time. There is an instance variable named midiChannel that refers to the channel used in the program. Here are the instance methods in midiChannel that you will need:
midiChannel.noteOn(noteNumber, volume)— This is like hitting a key on a piano keyboard (and holding it down for at least some time). The note number specifies which key (60 is "middle C"). The second parameter controls the volume. Both parameters are integers in the range 0 to 127.
midiChannel.noteOff(noteNumber)— Turns off a note, like releasing the key on the piano. Midi can play several notes simultaneously, so you have to specify which note you want to turn off. The parameter matches the parameter to noteOn(). (Turning a note off does not necessarily cut off the sound immediately; depending on the instrument, the volume can take some time to diminish to zero.)
midiChannel.programChange(instrumentNumber)— Change to a different instrument. The parameter is an integer in the range 0 to 127. For the software synthesizer, there is a set of standard instruments (although it's hard to say whether they all really work on a given system). See "General Midi" on Wikipedia, but note that the instruments there are numbered 1 through 128, whereas you need a number from 0 to 127; so, subtract 1 from the number in that list.
The standard Java classes for Midi are in the package javax.sound.midi. You will see import directives for them at the top of MidiKeyboard.java.
Implement Mouse Events: Events in Java are handled by objects. An object is said to "listen" for events. When an event occurs, the appropriate method in the object is called by the system. In MidiKeyboar.java the object that listens for mouse events is defined by a class named MouseHandler that is nested in the main class. To implement mouse events for the program, you can work entirely inside the MouseHandler class. We will cover all this in much more detail later, but for now, all you have to know is that when the user presses a button on the mouse, the method named mousePressed will be called, and when the user releases a button on the mouse, the method named mouseReleased will be called.
The idea is that when the mouse is pressed on one of the keys of the displayed keyboard, you should turn on the note corresponding to that key. When the mouse button is released, you should turn the note off. To make sure that you know which note to turn off, you will need to add a global state variable to the MouseHandler class to record what note, if any, is currently playing. (I suggest that you give the variable an illegal note value such as -1 when no note is currently playing.)
The program already creates four arrays to help you process mouse events. (You do not have to create them.) An array named whiteKeyRect, of type array of Rectangle, holds the rectangles that represent the white keys, and blackKeyRect holds the rectangles that represent the black keys. A Rectangle is a simple object that represents a rectangle in the usual pixel coordinate. If r is an object of type Rectangle, then r.x and r.y give the coordinates of the upper left corner of the rectangle, while r.width and r.height give the width and height of the rectangle. You can see how these values are used for drawing the rectangles in paintComponent().
The other two arrays, whiteKeyNumber and blackKeyNumber, are arrays of int that tell you what Midi note number to use for each keyboard key. These are the note numbers that you have to use when calling midiChannel.noteOn() and midiChannel.noteOff. For example, whiteKeyNumber is the midi number for the first white key. If the user presses the mouse inside the rectangle whiteKeyRect, you should pass whiteKeyNumber to midiChannel.noteOn().
This means that in mousePressed, you have to determine which piano key, if any, was under the mouse. The mousePressed method has a parameter named evt of type MouseEvent. This parameter is provided by the system, and it contains information about the event. In particular, you can get the position of the mouse like this in mousePressed:
int x = evt.getX(); // x-coord of mouse location int y = evt.getY(); // y-coord of mouse location
Once you know the mouse position, you can simply go through the key rectangles one-by-one to find out which of the rectangles, if any, contains (x,y). You will have to think about what it means for (x,y) to be inside the rectangle with properties r.x, r.y, r.width, and r.height. Be sure to check the black rectangles first, since the black keys lie on top of the white keys!
In mouseReleased, you should already know which note number, if any, is playing. Just turn that note off.
Implement Key Events: Handling key events is similar to handling mouse events. (Remember that we are now talking about the keys on which you type, not the keyboard on the screen.) There is a nested class named KeyHandler in MidiKeyboard.java that defines instance methods named keyPressed and keyReleased, which will be called by the system when the user presses or releases a key on the computer's keyboard. In this case, you can find out which key was pressed or released using
int keyCode = evt.getKeyCode();
The keyCode is a numerical code for the key. It is not the same as the character that was typed. For the letter keys, the key code is numerically the same as the upper case version of the letter (even if the shift key is not down). This means that you can do things like test if (keycode == 'A') to see whether the A key was pressed. The key codes for the number keys o through 9 are 48 through 57.
Your program should do something with key events. You might use key events to select the Midi instrument that is being used. You might use them to play notes. They might control the volume. It's up to you, but you should be reasonably ambitious to get full credit.
I don't expect you to get to key events during the lab period. We will talk more about them in class on Friday.