Core Java Technologies Tech Tips Tips, Techniques, and Sample Code Welcome to the Core Java Technologies Tech Tips, August 5, 2003. Here you'll get tips on using core Java technologies and APIs, such as those in Java 2 Platform, Standard Edition (J2SE). This issue covers: * Producing MIDI Sound * Saving and Reconstituting Swing Components These tips were developed using Java 2 SDK, Standard Edition, v 1.4. This issue of the Core Java Technologies Tech Tips is written by Daniel H. Steinberg, Director of Java Offerings for Dim Sum Thinking, Inc, and editor-in-chief for java.net (http://java.net). You can view this issue of the Tech Tips on the Web at http://java.sun.com/jdc/JDCTechTips/2003/tt0805.html. See the Subscribe/Unsubscribe note at the end of this newsletter to subscribe to Tech Tips that focus on technologies and products in other Java platforms. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - PRODUCING MIDI SOUND Adding audio cues to your Java desktop application can give it more of a polished feel and can dramatically improve usability. The Musical Instrument Digital Interface (MIDI) is a communication protocol for passing musical events between devices. A MIDI file contains audio commands rather than actual audio. Audio is a digital representation of sound, while MIDI represents the commands to a sound engine to recreate the sound. In this tech tip, you'll learn three ways to generate MIDI sound to augment your J2SE applications. Each of these techniques uses the javax.sound.midi package which has been part of the Java platform since J2SE 1.3. In the first technique, you generate a sound by making a direct call to a MidiChannel object. This object represent a single MIDI channel, that is, a single channel for MIDI transmission. To start a note playing through the channel, you use the noteOn() method of the MidiChannel object. To stop a note playing, you use the noteOff() method. The noteOn() method requires two ints. The first indicates the note being played. In the following program, SingleNoteChannel, the int 60 passed to the noteOn() method is the standard MIDI note number for middle C. An integer up or down corresponds to a half step. Twelve half steps comprise an octave. The second parameter for the noteOn() method indicates the speed with which the key is struck. Although this parameter is often referred to as the velocity, you can think of it as a volume control. In the SingleNoteChannel program, the second parameter passed to the noteOn() method is 70. After you run SingleNoteChannel, experiment with other speeds by changing 70 to other numbers. There are two signatures of the noteOff() method. One takes the same two parameters taken by noteOn(), and the other requires the number corresponding to the note being played. Before you can play a note, there is a certain amount of setup to be done. This is demonstrated in the SingleNoteChannel constructor. In order to generate sound, you need a Synthesizer object. This object represents a collection of MidiChannels, usually one for each of the 16 channels prescribed by the MIDI 1.0 specification. The SingleNoteChannel constructor gets a handle to a Synthesizer using a factory method in the MidiSystem class. The constructor then calls the Synthesizer's open() method, and gets an array of the available MidiChannels by calling the getChannels() method. It then selects the first MidiChannel at index 0. import javax.sound.midi.MidiChannel; import javax.sound.midi.Synthesizer; import javax.sound.midi.MidiSystem; import javax.sound.midi.MidiUnavailableException; public class SingleNoteChannel { private MidiChannel channel; public SingleNoteChannel() { try { Synthesizer synth = MidiSystem.getSynthesizer(); synth.open(); channel = synth.getChannels()[0]; } catch (MidiUnavailableException e) { e.printStackTrace(); } } public void playNote(int note) { channel.noteOn(note, 70); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } channel.noteOff(note, 70); } public static void main(String[] args) { new SingleNoteChannel().playNote(60); } } The playNote() method in the SingleNoteChannel program starts playing a note with a call to the MidiChannel noteOn() method. The note continues to play while the thread sleeps for one second. Then a call to the MidiChannel noteOff() method ends the playing of the note. You could use this first technique to associate certain notes with the pressing and releasing of buttons, keys, or other on-screen events. Now let's look at a second technique to generate MIDI sound. In this approach, you use a Receiver object associated with a Synthesizer. Instead of getting a specific MidiChannel for a Synthesizer, you get a handle to a Receiver. You create MIDI messages of type ShortMessage, customize them, and then play them by calling the send() method of the Receiver object. A ShortMessage object contains a MIDI message that has at most two data bytes. You use the setMessage() method to set the parameters of the MIDI message or to set message parameters for a channel message (this depends on the signature of the method) In the program below, SingleNoteSynthesizer, the signature of the setMessage() method sets message parameters for a channel message. This signature takes four ints. The first indicates the command being sent. The choices are NOTE_ON and NOTE_OFF. The second int identifies the target channel. To be consistent with the previous example, this parameter specifies index 0. For the NOTE_ON and NOTE_OFF commands the last two ints are the note identifier and velocity. In SingleNoteSynthesizer they are the same ints that were passed into the noteOn() and noteOff() methods in the SingleNoteChannel example. import javax.sound.midi.ShortMessage; import javax.sound.midi.InvalidMidiDataException; import javax.sound.midi.Receiver; import javax.sound.midi.Synthesizer; import javax.sound.midi.MidiSystem; import javax.sound.midi.MidiUnavailableException; public class SingleNoteSynthesizer { private ShortMessage message = new ShortMessage(); private Receiver receiver; private SingleNoteSynthesizer() { try { Synthesizer synth = MidiSystem.getSynthesizer(); synth.open(); receiver = synth.getReceiver(); } catch (MidiUnavailableException e) { e.printStackTrace(); } } public void playNote(int note) { setShortMessage(note, ShortMessage.NOTE_ON); receiver.send(message, -1); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } setShortMessage(note, ShortMessage.NOTE_OFF); receiver.send(message, -1); } private void setShortMessage( int note, int onOrOff) { try { message.setMessage(onOrOff, 0, note, 70); } catch (InvalidMidiDataException e) { e.printStackTrace(); } } public static void main(String[] args) { new SingleNoteSynthesizer().playNote(60); } } So far you've seen examples that play a single note. One thing you might not have realized is that the examples played the note on a default instrument. But what about playing a note on a different instrument? In this third technique for generating MIDI sound you'll see how to do this. Let's start by examining what's available. Here's a method that generates a list of available instruments: public void listAvailableInstruments(){ Instrument[] instrument = synth.getAvailableInstruments(); for (int i=0; i 200 100 Hello, world java.awt.event.ActionListener helloWorld Goodbye, cruel world 255 0 0 255 java.awt.event.ActionListener exit frame0 My Frame true It takes surprisingly little to reconstitute the configured JFrame from the file Frame.xml. The following program, FrameRecreator, instantiates an XMLDecoder that reads from the file Frame.xml. When you run FrameRecreator, you should see the JFrame that was created and configured just as it was in FrameCreator. import java.beans.XMLDecoder; import java.io.BufferedInputStream; import java.io.FileInputStream; public class FrameRecreator { public static void main(String[] args) throws Exception { XMLDecoder decoder = new XMLDecoder( new BufferedInputStream( new FileInputStream("Frame.xml"))); decoder.readObject(); decoder.close(); } } You've seen how to use the classes XMLEncoder and XMLDecoder to save and restore JavaBeans. However, XMLEncoder and XMLDecoder are much more general than this example implies. XMLEncoder is preconfigured to save all primitive data types, dates, strings, arrays, lists, hashmaps, and many other classes in the J2SE SDK that are not JavaBeans. You can also configure the XMEncoder to save your own Java classes, even if they aren't JavaBeans. For details on how to do this, and for information about other features of these technologies, see the following articles in the Swing Connection: o Using XMLEncoder (http://java.sun.com/products/jfc/tsc/articles/persistence4) o Long Term Persistence of JavaBeans Components: XML Schema (http://java.sun.com/products/jfc/tsc/articles/persistence3) You can add XMLEncoder to your set of persistence techniques, such as the Preferences API discussed in the tip titled "Using the Preferences API" in the July 15 issue of the Core Java Technologies Tech Tips (http://java.sun.com/jdc/JDCTechTips/2003/tt0715.html). . . . . . . . . . . . . . . . . . . . . . . . IMPORTANT: Please read our Terms of Use, Privacy, and Licensing policies: http://www.sun.com/share/text/termsofuse.html http://www.sun.com/privacy/ http://developer.java.sun.com/berkeley_license.html * FEEDBACK Comments? Please enter your feedback on the Tech Tips at: http://developers.sun.com/contact/feedback.jsp?category=sdn * SUBSCRIBE/UNSUBSCRIBE Subscribe to other Java developer Tech Tips: - Enterprise Java Technologies Tech Tips. Get tips on using enterprise Java technologies and APIs, such as those in the Java 2 Platform, Enterprise Edition (J2EE). - Wireless Developer Tech Tips. Get tips on using wireless Java technologies and APIs, such as those in the Java 2 Platform, Micro Edition (J2ME). To subscribe to these and other JDC publications: - Go to the JDC Newsletters and Publications page, (https://softwarereg.sun.com/registration/developer/en_US/subscriptions), choose the newsletters you want to subscribe to and click "Update". - To unsubscribe, go to the subscriptions page, (https://softwarereg.sun.com/registration/developer/en_US/subscriptions), uncheck the appropriate checkbox, and click "Update". - To use our one-click unsubscribe facility, see the link at the end of this email: - ARCHIVES You'll find the Core Java Technologies Tech Tips archives at: http://java.sun.com/jdc/TechTips/index.html - COPYRIGHT Copyright 2003 Sun Microsystems, Inc. All rights reserved. 4150 Network Circle, Santa Clara, California 95054 USA. This document is protected by copyright. For more information, see: http://java.sun.com/jdc/copyright.html Core Java Technologies Tech Tips August 5, 2003 Trademark Information: http://www.sun.com/suntrademarks/ Java, J2SE, J2EE, J2ME, and all Java-based marks are trademarks or registered trademarks of Sun Microsystems, Inc. in the United States and other countries.