17.4 Playing Sounds with javax.sound

To achieve more detailed control over the playback of sounds, including the ability to skip to any specified spot in the sound and control things such as volume and balance, we must use the Java Sound API, which is a much lower-level API than the AudioClip interface. JavaSound consists of the javax.sound.sampled package for sampled audio and the javax.sound.midi package for MIDI-based audio. Example 17-3 demonstrates the basic capabilities of these two packages. It loads a sound file (sampled audio or MIDI) completely into memory and then displays a GUI that allows you to play it. The GUI makes extensive use of the Swing JSlider component, which allows you both to select the playback position of the sound and to set things such as the volume, balance, and tempo of the sound. The program displays different controls for sampled audio files than it does for MIDI files. Both GUIs are shown in Figure 17-1. You'll notice that the code is substantially different for sampled audio and MIDI files as well.

Figure 17-1. SoundPlayer playing sampled audio and MIDI files
figs/jex3_1701.gif

A shortcoming of the SoundPlayer class is that it can only play sampled audio files that use PCM encoding. ALAW and ULAW encoded files are not supported, nor are more complex compressed encodings, such as MP3. The JavaSound API attempts to directly mirror the capabilities of sound hardware, and can therefore only play PCM sounds. It does provide a transcoding technique, however, to convert sounds to PCM encoding. We'll see this later in the chapter, in Example 17-4.

Example 17-3. SoundPlayer.java
package je3.sound; import java.io.*; import java.net.*; import java.awt.*; import java.awt.event.*; import javax.swing.*; import javax.swing.event.*; import javax.swing.border.*; import javax.sound.sampled.*; import javax.sound.midi.*; /**  * This class is a Swing component that can load and play a sound clip,  * displaying progress and controls.  The main( ) method is a test program.  * This component can play sampled audio or MIDI files, but handles them   * differently. For sampled audio, time is reported in microseconds, tracked in  * milliseconds and displayed in seconds and tenths of seconds. For midi  * files time is reported, tracked, and displayed in MIDI "ticks".  * This program does no transcoding, so it can only play sound files that use  * the PCM encoding.  */ public class SoundPlayer extends JComponent {     boolean midi;            // Are we playing a midi file or a sampled one?     Sequence sequence;       // The contents of a MIDI file     Sequencer sequencer;     // We play MIDI Sequences with a Sequencer     Clip clip;               // Contents of a sampled audio file     boolean playing = false; // whether the sound is currently playing     // Length and position of the sound are measured in milliseconds for      // sampled sounds and MIDI "ticks" for MIDI sounds     int audioLength;         // Length of the sound.       int audioPosition = 0;   // Current position within the sound     // The following fields are for the GUI     JButton play;             // The Play/Stop button     JSlider progress;         // Shows and sets current position in sound     JLabel time;              // Displays audioPosition as a number     Timer timer;              // Updates slider every 100 milliseconds     // The main method just creates a SoundPlayer in a Frame and displays it     public static void main(String[  ] args)          throws IOException,                UnsupportedAudioFileException,                LineUnavailableException,                MidiUnavailableException,                InvalidMidiDataException     {         SoundPlayer player;         File file = new File(args[0]);   // This is the file we'll be playing         // Determine whether it is midi or sampled audio         boolean ismidi;         try {             // We discard the return value of this method; we just need to know             // whether it returns successfully or throws an exception             MidiSystem.getMidiFileFormat(file);             ismidi = true;         }         catch(InvalidMidiDataException e) {             ismidi = false;         }         // Create a SoundPlayer object to play the sound.         player = new SoundPlayer(file, ismidi);         // Put it in a window and play it         JFrame f = new JFrame("SoundPlayer");         f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);         f.getContentPane( ).add(player, "Center");         f.pack( );         f.setVisible(true);     }     // Create a SoundPlayer component for the specified file.     public SoundPlayer(File f, boolean isMidi)         throws IOException,                UnsupportedAudioFileException,                LineUnavailableException,                MidiUnavailableException,                InvalidMidiDataException     {         if (isMidi) {     // The file is a MIDI file             midi = true;             // First, get a Sequencer to play sequences of MIDI events             // That is, to send events to a Synthesizer at the right time.             sequencer = MidiSystem.getSequencer( );  // Used to play sequences             sequencer.open( );                       // Turn it on.             // Get a Synthesizer for the Sequencer to send notes to             Synthesizer synth = MidiSystem.getSynthesizer( );             synth.open( );  // acquire whatever resources it needs                          // The Sequencer obtained above may be connected to a Synthesizer             // by default, or it may not.  Therefore, we explicitly connect it.             Transmitter transmitter = sequencer.getTransmitter( );             Receiver receiver = synth.getReceiver( );             transmitter.setReceiver(receiver);                          // Read the sequence from the file and tell the sequencer about it             sequence = MidiSystem.getSequence(f);             sequencer.setSequence(sequence);             audioLength = (int)sequence.getTickLength( ); // Get sequence length         }         else {            // The file is sampled audio             midi = false;             // Getting a Clip object for a file of sampled audio data is kind             // of cumbersome.  The following lines do what we need.             AudioInputStream ain = AudioSystem.getAudioInputStream(f);             try {                 DataLine.Info info =                     new DataLine.Info(Clip.class,ain.getFormat( ));                 clip = (Clip) AudioSystem.getLine(info);                 clip.open(ain);             }             finally { // We're done with the input stream.                 ain.close( );             }             // Get the clip length in microseconds and convert to milliseconds             audioLength = (int)(clip.getMicrosecondLength( )/1000);         }         // Now create the basic GUI         play = new JButton("Play");                // Play/stop button         progress = new JSlider(0, audioLength, 0); // Shows position in sound         time = new JLabel("0");                    // Shows position as a #         // When clicked, start or stop playing the sound         play.addActionListener(new ActionListener( ) {                 public void actionPerformed(ActionEvent e) {                     if (playing) stop( ); else play( );                 }             });         // Whenever the slider value changes, first update the time label.         // Next, if we're not already at the new position, skip to it.         progress.addChangeListener(new ChangeListener( ) {                 public void stateChanged(ChangeEvent e) {                     int value = progress.getValue( );                     // Update the time label                     if (midi) time.setText(value + "");                     else time.setText(value/1000 + "." +                                       (value%1000)/100);                     // If we're not already there, skip there.                     if (value != audioPosition) skip(value);                 }             });                  // This timer calls the tick( ) method 10 times a second to keep          // our slider in sync with the music.         timer = new javax.swing.Timer(100, new ActionListener( ) {                 public void actionPerformed(ActionEvent e) { tick( ); }             });                  // put those controls in a row         Box row = Box.createHorizontalBox( );         row.add(play);         row.add(progress);         row.add(time);                  // And add them to this component.         setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));         this.add(row);         // Now add additional controls based on the type of the sound         if (midi) addMidiControls( );         else addSampledControls( );     }     /** Start playing the sound at the current position */     public void play( ) {         if (midi) sequencer.start( );         else clip.start( );         timer.start( );         play.setText("Stop");         playing = true;     }     /** Stop playing the sound, but retain the current position */     public void stop( ) {         timer.stop( );         if (midi) sequencer.stop( );         else clip.stop( );         play.setText("Play");         playing = false;     }     /** Stop playing the sound and reset the position to 0 */     public void reset( ) {         stop( );         if (midi) sequencer.setTickPosition(0);         else clip.setMicrosecondPosition(0);         audioPosition = 0;          progress.setValue(0);     }     /** Skip to the specified position */     public void skip(int position) { // Called when user drags the slider         if (position < 0 || position > audioLength) return;         audioPosition = position;         if (midi) sequencer.setTickPosition(position);         else clip.setMicrosecondPosition(position * 1000);         progress.setValue(position); // in case skip( ) is called from outside     }     /** Return the length of the sound in ms or ticks */     public int getLength( ) { return audioLength; }     // An internal method that updates the progress bar.     // The Timer object calls it 10 times a second.     // If the sound has finished, it resets to the beginning     void tick( ) {         if (midi && sequencer.isRunning( )) {             audioPosition = (int)sequencer.getTickPosition( );             progress.setValue(audioPosition);         }         else if (!midi && clip.isActive( )) {             audioPosition = (int)(clip.getMicrosecondPosition( )/1000);             progress.setValue(audioPosition);         }         else reset( );       }     // For sampled sounds, add sliders to control volume and balance     void addSampledControls( ) {         try {             FloatControl gainControl =                 (FloatControl)clip.getControl(FloatControl.Type.MASTER_GAIN);             if (gainControl != null) this.add(createSlider(gainControl));         }         catch(IllegalArgumentException e) {             // If MASTER_GAIN volume control is unsupported, just skip it         }         try {             // FloatControl.Type.BALANCE is probably the correct control to             // use here, but it doesn't work for me, so I use PAN instead.             FloatControl panControl =                 (FloatControl)clip.getControl(FloatControl.Type.PAN);             if (panControl != null) this.add(createSlider(panControl));         }         catch(IllegalArgumentException e) {  }     }     // Return a JSlider component to manipulate the supplied FloatControl     // for sampled audio.     JSlider createSlider(final FloatControl c) {         if (c == null) return null;         final JSlider s = new JSlider(0, 1000);         final float min = c.getMinimum( );         final float max = c.getMaximum( );         final float width = max-min;         float fval = c.getValue( );         s.setValue((int) ((fval-min)/width * 1000));         java.util.Hashtable labels = new java.util.Hashtable(3);         labels.put(new Integer(0), new JLabel(c.getMinLabel( )));         labels.put(new Integer(500), new JLabel(c.getMidLabel( )));         labels.put(new Integer(1000), new JLabel(c.getMaxLabel( )));         s.setLabelTable(labels);         s.setPaintLabels(true);         s.setBorder(new TitledBorder(c.getType( ).toString( ) + " " +                                      c.getUnits( )));         s.addChangeListener(new ChangeListener( ) {                 public void stateChanged(ChangeEvent e) {                     int i = s.getValue( );                     float f = min + (i*width/1000.0f);                     c.setValue(f);                 }             });         return s;     }     // For Midi files, create a JSlider to control the tempo,     // and create JCheckBoxes to mute or solo each MIDI track.     void addMidiControls( ) {         // Add a slider to control the tempo         final JSlider tempo = new JSlider(50, 200);         tempo.setValue((int)(sequencer.getTempoFactor( )*100));         tempo.setBorder(new TitledBorder("Tempo Adjustment (%)"));         java.util.Hashtable labels = new java.util.Hashtable( );         labels.put(new Integer(50), new JLabel("50%"));         labels.put(new Integer(100), new JLabel("100%"));         labels.put(new Integer(200), new JLabel("200%"));         tempo.setLabelTable(labels);         tempo.setPaintLabels(true);         // The event listener actually changes the tempo         tempo.addChangeListener(new ChangeListener( ) {                 public void stateChanged(ChangeEvent e) {                     sequencer.setTempoFactor(tempo.getValue( )/100.0f);                 }             });         this.add(tempo);         // Create rows of solo and checkboxes for each track         Track[  ] tracks = sequence.getTracks( );         for(int i = 0; i < tracks.length; i++) {             final int tracknum = i;             // Two checkboxes per track             final JCheckBox solo = new JCheckBox("solo");             final JCheckBox mute = new JCheckBox("mute");             // The listeners solo or mute the track             solo.addActionListener(new ActionListener( ) {                     public void actionPerformed(ActionEvent e) {                         sequencer.setTrackSolo(tracknum,solo.isSelected( ));                     }                 });             mute.addActionListener(new ActionListener( ) {                     public void actionPerformed(ActionEvent e) {                         sequencer.setTrackMute(tracknum,mute.isSelected( ));                     }                 });             // Build up a row             Box box = Box.createHorizontalBox( );             box.add(new JLabel("Track " + tracknum));             box.add(Box.createHorizontalStrut(10));             box.add(solo);             box.add(Box.createHorizontalStrut(10));             box.add(mute);             box.add(Box.createHorizontalGlue( ));             // And add it to this component             this.add(box);         }     } }


Java Examples in a Nutshell
Java Examples in a Nutshell, 3rd Edition
ISBN: 0596006209
EAN: 2147483647
Year: 2003
Pages: 285

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