The Sounds Panel


Testing the Loaders

The 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 Loaders

As 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.

A watcher can be assigned to multiple clips and to the currently playing MIDI sequence. MidisLoader can play one sequence at a time, so there's no need to specify the sequence's name when setWatcher( ) is called.


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.

Termination

When 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.

This call to exit( ) isn't necessary in J2SE 5.0.


The Listener Methods

In 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.

A quirk of LoadersTests's GUI is the lack of a button to stop a repeating clip. Instead, the deselection of its looping checkbox causes it to stop. This is perhaps counter-intuitive. Design decisions such as this one should be tested on users who are not involved in the application's design or implementation.


LoadersTests has no interface for allowing a clip to be paused and resumed, although this functionality is present in ClipsLoader.



Killer Game Programming in Java
Killer Game Programming in Java
ISBN: 0596007302
EAN: 2147483647
Year: 2006
Pages: 340

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net