The Midi Sequences LoaderMidisLoader stores sequences as a collection of MidiInfo objects in a HashMap, keyed by their names. The name and filename for a sequence are obtained from an information file loaded when MidisLoader is created. The file is assumed to be in the subdirectory Sounds/. MidisLoader allows a specified sequence to be played, stopped, resumed, and looped. A SoundsWatcher can be attached to the sequencer and not to a sequence. MidisLoader deliberately offers almost the same interface as ClipsLoader (see Figure 8-2), though it has some internal differences. MidisLoader was designed to have one Sequencer object for playing all the sequences, which avoids the overhead of supporting multiple sequencers. Consequently, one sequence will play at a time. This contrasts with ClipsLoader, where multiple clips can be playing concurrently since multiple Clip objects are created by ClipsLoader. A reference to the sequencer is passed to each MidiInfo object, thereby giving them the responsibility for playing, stopping, resuming and looping their sequences. The MidisLoader initializes the sequencer using initSequencer( ) and loads the information file: // globals private Sequencer sequencer; private HashMap midisMap; private MidiInfo currentMidi = null; // reference to currently playing MidiInfo object public MidisLoader( ) { midisMap = new HashMap( ); initSequencer( ); } public MidisLoader(String soundsFnm) { midisMap = new HashMap( ); initSequencer( ); loadSoundsFile(soundsFnm); }
initSequencer( ) is similar to the version in PlayMidi.java in Chapter 7. loadSoundsFile( ) is similar to the same named method in ClipsLoader since it parses the information file, assuming each line contains a name and filename. For example, midisInfo.txt used by LoadersTests is // midis baa bsheep.mid farmer farmerinthedell.mid mary maryhadalittlelamb.mid mcdonald mcdonald.mid
After a line's name and filename have been extracted, load( ) is called: public void load(String name, String fnm) // create a MidiInfo object, and store it under name { if (midisMap.containsKey(name)) System.out.println( "Error: " + name + "already stored"); else if (sequencer == null) System.out.println( "No sequencer for: " + name); else { midisMap.put(name, new MidiInfo(name, fnm, sequencer) ); System.out.println("-- " + name + "/" + fnm); } } This creates a MidiInfo object for the sequence and stores it in the midisMap HashMap. The last MidiInfo constructor argument is the sequencer. Playing SequencesPlaying a sequence is a matter of looking up the specified name in midisMap and calling its play( ) method. A slight complication is that one sequence will play at a time, a restriction included in the loader design to reduce processing overheads. play( ) only plays the requested tune if no sequence is playing; a reference to that sequence is stored in the currentMidi global: public void play(String name, boolean toLoop) // play (perhaps loop) the sequence { MidiInfo mi = (MidiInfo) midisMap.get(name); if (mi == null) System.out.println( "Error: " + name + "not stored"); else { if (currentMidi != null) System.out.println("Sorry, " + currentMidi.getName( ) + " already playing"); else { currentMidi = mi; // store a reference to playing midi mi.play(toLoop); // pass play request to MidiInfo object } } } Playing is prohibited if currentMidi is not null, which means that a sequence is playing. Pausing and Resuming SequencesPausing and resuming is handled by passing the tasks to the playing MidiInfo object: public void pause( ) { if (currentMidi != null) currentMidi.pause( ); else System.out.println( "No music to pause"); } public void resume( ) { if (currentMidi != null) currentMidi.resume( ); else System.out.println("No music to resume"); } Stopping SequencesStopping a sequence uses the same delegation strategy as pausing and resuming. The stop( ) method in MidisInfo will trigger an end-of-track metaevent in the sequencer, which is handled by MidisLoader's meta( ) method: public void stop( ) { if (currentMidi != null) currentMidi.stop( ); // this will cause an end-of-track event System.out.println("No music playing"); } public void meta(MetaMessage meta) { if (meta.getType( ) == END_OF_TRACK) { String name = currentMidi.getName( ); boolean hasLooped = currentMidi.tryLooping( ); // music still looping? if (!hasLooped) // no it's finished currentMidi = null; if (watcher != null) { // tell the watcher if (hasLooped) // the music is playing again watcher.atSequenceEnd(name, SoundsWatcher.REPLAYED); else // the music has finished watcher.atSequenceEnd(name, SoundsWatcher.STOPPED); } } } // end of meta( ) The code in meta( ) only deals with an end-of-track metaevent. These end-of-track events are triggered by a MidiInfo object when its sequence reaches its end or is stopped. However, a sequence at its end may be looping, which is checked by calling tryLooping( ) in MidiInfo. If there is a watcher, that watcher is notified of the status of the sequence. Closing SequencesAs LoadersTests terminates, it calls close( ) in MidisLoader to release the sequencer: public void close( ) { stop( ); // stop the playing sequence if (sequencer != null) { if (sequencer.isRunning( )) sequencer.stop( ); sequencer.removeMetaEventListener(this); sequencer.close( ); sequencer = null; } } |