Testing the LoadersThe constructor for LoadersTests creates the images canvas (a SoundsPanel object) and the rest of the GUI, and it initializes the loaders: // the clip and midi sound information files, located in Sounds/ private final static String SNDS_FILE = "clipsInfo.txt"; private final static String MIDIS_FILE = "midisInfo.txt"; // global variables private ClipsLoader clipsLoader; private MidisLoader midisLoader; public LoadersTests( ) { super( "Sounds Tests" ); Container c = getContentPane( ); c.setLayout( new BorderLayout( ) ); SoundsPanel sp = new SoundsPanel(this); // the images canvas c.add( sp, BorderLayout.CENTER); initGUI(c); // the rest of the controls // initialise the loaders clipsLoader = new ClipsLoader(SNDS_FILE); clipsLoader.setWatcher("dog", this); // watch the dog clip midisLoader = new MidisLoader(MIDIS_FILE); midisLoader.setWatcher(this); // watch the midi sequence addWindowListener(new WindowAdapter( ) { public void windowClosing(WindowEvent ev) { midisLoader.close( ); // shut down the sequencer System.exit(0); } }); pack( ); setResizable(false); // fixed size display centerFrame( ); // placed in the center of the screen setVisible(true); } Watching the LoadersAs part of the loaders setup, setWatcher( ) is called in the ClipsLoader and MidisLoader objects: clipsLoader.setWatcher("dog", this); // watch the dog clip midisLoader.setWatcher(this); // watch midi playing A call to setWatcher( ) tells the loader that this object (LoadersTest) should be notified whenever the specified sound reaches the end of an iteration when looping or when finished. This notification is achieved by having the loader call atSequenceEnd( ) in the object, which requires that LoadersTest implements the SoundsWatcher interface. LoadersTest has, therefore, become a watcher.
atSequenceEnd( ) is defined by LoadersTests this way: public void atSequenceEnd(String name, int status) // can be called by the ClipsLoader or MidisLoader { if (status == SoundsWatcher.STOPPED) System.out.println(name + " stopped"); else if (status == SoundsWatcher.REPLAYED) System.out.println(name + " replayed"); else System.out.println(name + " status code: " + status); } The two possible meanings of "sequence end" are represented by the SoundsWatcher constants STOPPED and REPLAYED. The name argument of atSequenceEnd( ) is a string assigned to the clip or sequence by the loader. TerminationWhen LoadersTests is terminated, windowClosing( ) calls close( ) in the MidisLoader to terminate its sequencer. This is preferable to relying on the audio system to release the resources. windowClosing( ) calls exit( ) to force the JVM to terminate even though some audio threads are still running.
The Listener MethodsIn initGUI( ), ActionListeners are attached to the buttons and ItemListeners to the check boxes. A simplified version of actionPerformed( ) is shown below, with the many calls to Component.setEnable( ) edited out. setEnable( ) manages the user's behavior by restricting the available buttons, which is a useful GUI trick. When nothing is playing, Play and Loop are enabled. When a sequence is executing, only Pause and Stop are available. When a piece of music is paused, only the Resume button is active (this is the renamed Pause button): public void actionPerformed(ActionEvent e) /* Triggered by a "Play", "Loop", "Pause/Resume", "Stop" button press. The relevant method in MidisLoader is called. A lot of effort is spent on disabling/enabling buttons, which I've edited out from the code here. */ { // which song is currently selected? String songName = shortSongNames[ namesJcb.getSelectedIndex( ) ]; if (e.getSource( ) == playJbut) // "Play" pressed midisLoader.play(songName, false); // play sequence, no looping else if (e.getSource( ) == loopJbut) // "Loop" pressed midisLoader.play(songName, true); // play with looping else if (e.getSource( ) == pauseJbut) { // "Pause/Resume" pressed if (isPauseButton) { midisLoader.pause( ); // pause the sequence pauseJbut.setText("Resume"); // Pause --> Resume } else { midisLoader.resume( ); // resume the sequence pauseJbut.setText("Pause"); // Resume --> Pause } isPauseButton = !isPauseButton; } else if (e.getSource( ) == stopJbut) // "Stop" pressed midisLoader.stop( ); // stop the sequence else System.out.println("Action unknown"); } // end of actionPerformed( ) The correspondence between button presses and calls to the MidisLoader is fairly clear. A once-only play, as well as repeated playing of a clip, are handled by play( ) with a Boolean argument to distinguish the two modes. itemStateChanged( ) handles the four checkboxes on the right side of the GUI, which specifies if clips should be looped when played. However, a clip only starts to play when the user clicks on its image in the SoundsPanel. The looping settings for all the clips are maintained in an array of Booleans called clipLoops[]. The relevant Boolean passes to ClipsLoader's play( ) method when the clip is played: // global clip image names (used to label the checkboxes) private final static String[] names = {"dog", "cat", "sheep", "chicken"}; // global clip loop flags, stored in names[] order private boolean[] clipLoops = {false, false, false, false}; public void itemStateChanged(ItemEvent e) // Triggered by selecting/deselecting a clip looping checkbox { // get the name of the selected checkbox String name = ((JCheckBox)e.getItem( )).getText( ); boolean isSelected = (e.getStateChange( ) == e.SELECTED) ? true : false; boolean switched = false; for (int i=0; i < names.length; i++) if (names[i].equals(name)) { clipLoops[i] = !clipLoops[i]; // update the clip loop flags switched = true; break; } if (!switched) System.out.println("Item unknown"); else { if (!isSelected) // user just switched off looping for name clipsLoader.stop(name); // so stop playing name's clip } } The checkbox's name is found in the names[] array, and the corresponding index is used to choose the Boolean in clipsLoops[] to be modified.
LoadersTests has no interface for allowing a clip to be paused and resumed, although this functionality is present in ClipsLoader. |