terrence3321
Jute User
发贴: 40
积分: 50
|
于 2003-08-05 14:58
Just found this, for those interested in java MIDI can look here...
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<instrument.length; i++){ System.out.println(i + " " + instrument[i].getName()); } }
If you call the method, you get a list whose first items look like this:
0 Piano 1 Bright Piano 2 Electric Grand 3 Honky Tonk Piano 4 Electric Piano 1 5 Electric Piano 2 6 Harpsichord 7 Clavinet 8 Celesta 9 Glockenspiel 10 Music Box 11 Vibraphone 12 Marimba 13 Xylophone 14 Tubular Bell 15 Dulcimer 16 Hammond Organ 17 Perc Organ 18 Rock Organ 19 Church Organ 20 Reed Organ 21 Accordion 22 Harmonica 23 Tango Accordion 24 Nylon Str Guitar 25 Steel String Guitar 26 Jazz Electric Gtr 27 Clean Guitar 28 Muted Guitar 29 Overdrive Guitar 30 Distortion Guitar
To illustrate the approach, let's enhance SingleNoteSynthesizer to play more than one note at a time using different instruments. If you examine the enhanced program, SingleNoteSynthesizer2, you will notice that the program still sleeps a Thread to measure time. This is really not robust enough for creating and playing music. In addition, if done in the AWT Event Dispatch thread, this will prevent your GUI from processing events or repainting. These issues will be addressed later in the tip using the final technique for generating MIDI sound.
As demonstrated in SingleNoteSynthesizer2, you change the instrument being played by calling the programChange() method in MidiChannel. You pass the method an int that corresponds to the instrument you want from the list you generated above. In this example, the int is 19, corresponding to a church organ.
Notice too that the code has also been refactored so that startNote() creates a message with the NOTE_ON command, and stopNote() creates one with the NOTE_OFF command. The playNote() method calls startNote(), sleeps for the number of milliseconds passed in as the duration, and then calls stopNote().
import javax.sound.midi.ShortMessage; import javax.sound.midi.Synthesizer; import javax.sound.midi.Receiver; import javax.sound.midi.MidiSystem; import javax.sound.midi.MidiUnavailableException; import javax.sound.midi.InvalidMidiDataException;
public class SingleNoteSynthesizer2 { private ShortMessage message = new ShortMessage(); private Synthesizer synth; private Receiver receiver;
public SingleNoteSynthesizer2() { try { synth = MidiSystem.getSynthesizer(); synth.open(); receiver = synth.getReceiver(); } catch (MidiUnavailableException e) { e.printStackTrace(); } }
public void startNote(int note) { setShortMessage(ShortMessage.NOTE_ON, note); receiver.send(message, -1); }
public void stopNote(int note) { setShortMessage(ShortMessage.NOTE_OFF, note); receiver.send(message, -1); }
public void playNote(int note, int duration){ startNote(note); try { Thread.sleep(duration); } catch (InterruptedException e) { e.printStackTrace(); } stopNote(note); }
public void setInstrument(int instrument){ synth.getChannels()[0].programChange( instrument); }
private void setShortMessage( int onOrOff, int note) { try { message.setMessage(onOrOff, 0, note, 70); } catch (InvalidMidiDataException e) { e.printStackTrace(); } }
public void playMajorChord(int baseNote){ playNote(baseNote,1000); playNote(baseNote+4,1000); playNote(baseNote+7, 1000); startNote(baseNote); startNote(baseNote+4); playNote(baseNote+7,2000); stopNote(baseNote+4); stopNote(baseNote); }
public static void main(String[] args) { SingleNoteSynthesizer2 synth = new SingleNoteSynthesizer2(); synth.setInstrument(19); synth.playMajorChord(60); } }
A major chord is constructed from a base note, the note four half steps above it, and the note three half spaces above that. In SingleNoteSynthesizer2, the playMajorChord() method first plays these notes, one at a time, and then plays them all at once. The timing is done using Threads. This is not the recommended approach when timing is important. In that case you should use a Sequencer.
A Sequencer object includes methods that give you more control (as compared to the previous approach) over the timing of MIDI events. As before, use a factory method from MidiSystem. This time obtain and open a Sequencer. This is shown in the next example, SequencerSound. Comparing this example to the preceding examples, you should notice some notable changes to the code. First the startNote() and stopNote() methods take an int that represents the point in time at which the message being created takes place. The createTrack() method creates a Sequence with four ticks for each quarter note. You can think of ticks as subdivisions. In other words, a measure of music in 4/4 time has four quarter notes or sixteen ticks.
The setShortMessage() method calls setMessage(), as before. Then the program creates a MidiEvent object based on this ShortMessage and associated with a specific tick. Finally the program adds a MidiEvent to the Track. To play the track, the startSequencer() method assigns the newly created Sequence to the Sequencer. The playback begins with a call to the start() method, and the tempo is set to sixty beats per minute (BPM) using the setTempoInBPM() method.
import javax.sound.midi.Track; import javax.sound.midi.Sequencer; import javax.sound.midi.Sequence; import javax.sound.midi.MidiSystem; import javax.sound.midi.MidiUnavailableException; import javax.sound.midi.InvalidMidiDataException; import javax.sound.midi.ShortMessage; import javax.sound.midi.MidiEvent;
public class SequencerSound { private Track track; private Sequencer sequencer; private Sequence sequence;
public SequencerSound() { try { sequencer = MidiSystem.getSequencer(); sequencer.open(); } catch (MidiUnavailableException e) { e.printStackTrace(); } createTrack(); makeScale(20); startSequencer(); }
private void startSequencer() { try { sequencer.setSequence(sequence); } catch (InvalidMidiDataException e) { e.printStackTrace(); } sequencer.start(); sequencer.setTempoInBPM(60); }
private void createTrack() { try { sequence = new Sequence(Sequence.PPQ, 4); } catch (InvalidMidiDataException e) { e.printStackTrace(); } track = sequence.createTrack(); }
public void startNote(int note, int tick) { setShortMessage( ShortMessage.NOTE_ON, note, tick); }
public void stopNote(int note, int tick) { setShortMessage( ShortMessage.NOTE_OFF, note, tick); }
private void setShortMessage( int onOrOff, int note, int tick) { ShortMessage message = new ShortMessage(); try { message.setMessage(onOrOff, 0, note, 90); MidiEvent event = new MidiEvent( message, tick); track.add(event); } catch (InvalidMidiDataException e) { e.printStackTrace(); } }
public void makeScale(int baseNote) { for (int i = 0; i < 13; i++) { startNote(baseNote + i, i); stopNote(baseNote + i, i + 1); startNote(baseNote + i, 25 - i); stopNote(baseNote + i, 26 - i); } }
public static void main(String[] args) { new SequencerSound(); } } This tip showed you how to produce and play back MIDI files. As a developer, you are able to hear the music because a soundbank is automatically installed along with the J2SE SDK. A soundbank represents a set of instruments, and is needed to synthesize sound. (There's a lot more to the soundbank concept, and it will be covered in a later tip.) Non-developers might have to install a soundbank separately to hear what you have created. When installing a JRE for J2SE 1.4.1 or later, users have to opt-in to include the soundbank in the installation.
For more information on saving and playing back MIDI files, consult the documentation for javax.sound.midi (http://java.sun.com/j2se/1.4.2/docs/api/javax/sound/midi/package-summary.html). You'll find more information on the Java Sound API home page (http://java.sun.com/products/java-media/sound/).
|