Showing posts with label threads. Show all posts
Showing posts with label threads. Show all posts

Thursday, 17 December 2009

Simple Java WAV Player

In this article we'll discuss a simple way how to play wav files in Java. We'll start with the complete example and explain each bit in the process. This example only involves one class, the one listed below.

package com.albertattard.pmm;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.SourceDataLine;
import javax.sound.sampled.UnsupportedAudioFileException;

public class TheMusicPlayer {

  public static void play(final InputStream inputStream) {
    new Thread() {
      @Override
      public void run() {
        AudioInputStream audioInputStream = null;
        try {
          audioInputStream = AudioSystem
              .getAudioInputStream(inputStream);
        } catch (UnsupportedAudioFileException e) {
          e.printStackTrace();
          return;
        } catch (IOException e) {
          e.printStackTrace();
          return;
        }

        SourceDataLine sourceDataLine = null;
        try {
          AudioFormat audioFormat 
              = audioInputStream.getFormat();
          DataLine.Info info = new DataLine.Info(
              SourceDataLine.class, audioFormat);
          sourceDataLine = 
              (SourceDataLine) AudioSystem.getLine(info);
          sourceDataLine.open(audioFormat);
        } catch (LineUnavailableException e) {
          e.printStackTrace();
          return;
        }

        sourceDataLine.start();
        byte[] data = new byte[524288];// 128Kb
        try {
          int bytesRead = 0;
          while (bytesRead != -1) {
            bytesRead = 
                audioInputStream.read(data, 0, data.length);
            if (bytesRead >= 0)
              sourceDataLine.write(data, 0, bytesRead);
          }
        } catch (IOException e) {
          e.printStackTrace();
          return;
        } finally {
          sourceDataLine.drain();
          sourceDataLine.close();
        }
      }
    }.start();
  }

  public static void play(final String wavFile)
      throws FileNotFoundException {
    play(new FileInputStream(wavFile));
  }
}

Those of you who are in a hurry, just copy and paste the above code and get on with your life. The others who can spare sometime, just read the following part.

Explained

The class has two static overloaded methods which play the wav file provides as a parameter. The second method simple invokes the first method passing an instance of input stream (and instance of FileInputStream, which inherits from InputStream)

All the logic is found in the first play() method, and that's what we'll be explaining next.

Let say we have a program and would like to play a sound (saved as a wav file) when an event/scenario occurs. The code to do so is very straight forward as illustrated below (a copy and paste from the example above).

        AudioInputStream audioInputStream = null;
        try {
          audioInputStream = AudioSystem
              .getAudioInputStream(The Input Stream);
        } catch (UnsupportedAudioFileException e) {
          e.printStackTrace();
          return;
        } catch (IOException e) {
          e.printStackTrace();
          return;
        }

        SourceDataLine sourceDataLine = null;
        try {
          AudioFormat audioFormat 
              = audioInputStream.getFormat();
          DataLine.Info info = new DataLine.Info(
              SourceDataLine.class, audioFormat);
          sourceDataLine = 
              (SourceDataLine) AudioSystem.getLine(info);
          sourceDataLine.open(audioFormat);
        } catch (LineUnavailableException e) {
          e.printStackTrace();
          return;
        }

        sourceDataLine.start();
        byte[] data = new byte[524288];// 128Kb
        try {
          int bytesRead = 0;
          while (bytesRead != -1) {
            bytesRead = 
                audioInputStream.read(data, 0, data.length);
            if (bytesRead >= 0)
              sourceDataLine.write(data, 0, bytesRead);
          }
        } catch (IOException e) {
          e.printStackTrace();
          return;
        } finally {
          sourceDataLine.drain();
          sourceDataLine.close();
        }

The above code fragment is made from three parts:

  • Initialisation of the audio input stream
  • Initialisation of the audio output
  • Playing the audio data

We're reading bytes from the input stream (the famous wav) and instead of writing/printing these in the console (as we traditionally do with text files), we're writing to the speaker/sound card of the computer.

We start by initialising the It's good to point out that the AudioInputStream, which inherits from the InputStream. This instance behaves the same like any other stream and can be treated/handled in the same manner as other streams.

AudioInputStream audioInputStream = null;
try {
  audioInputStream = AudioSystem
    .getAudioInputStream(The Input Stream);
} catch (UnsupportedAudioFileException e) {
  e.printStackTrace();
  return;
} catch (IOException e) {
  e.printStackTrace();
  return;
}

Then we initialise the audio output (this is NOT an OutputStream), that is, where the sound will be played. This has to match the audio input initialise in the previous step. Remember that there are many different sound files and not all these can be played with the following code.

SourceDataLine sourceDataLine = null;
try {
  AudioFormat audioFormat 
    = audioInputStream.getFormat();
  DataLine.Info info = new DataLine.Info(
    SourceDataLine.class, audioFormat);
  sourceDataLine = 
    (SourceDataLine) AudioSystem.getLine(info);
  sourceDataLine.open(audioFormat);
} catch (LineUnavailableException e) {
  e.printStackTrace();
  return;
}

The first two were quite straight forward. In the third step we're actually playing the sound by reading the bytes and play them in a similar fashion we do with other streams. We're reading a predefined amount of data (128k) into the array called data and saving the amount of bytes read in the other variable bytesRead.

bytesRead = audioInputStream.read(data, 0, data.length);

When the whole file is played, and there is no more data to be played, the read() method returns -1 (like it does with any other InputStream). This will have the while condition to false ending the playing process.

It is good to note that the third step will consume the focus of program until the wav file is played. That is, if you have a two minute wav file, than the above code will take two minutes to execute. All the other parts of the program (that are executed by this thread) will have to wait for the sound/music to finish. Since this is not what we want, the static method play() wraps this code into a thread which is started at the end of the method.

public static void play(final InputStream inputStream) {
  new Thread() {
    @Override
    public void run() {
      Code removed for brevity
    }
  }.start();
}

Since the code is started within a thread the control is immediately returned to the calling method which can proceed without having to wait for the file to play.

To play a wav file, all we need to do is invoke the pay method with the wav file as a parameter as illustrated below.

package com.albertattard.pmm;

public class TheMain {

  public static void main(String[] args) {
    // File downloaded from: 
    //   http://freewavesamples.com/bowed-bass-c2
    TheMusicPlayer.play(TheMain.class
        .getResourceAsStream("Bowed-Bass-C2.wav"));
  }
}

It's good to point out that the wav file Bowed-Bass-C2.wav (available at: http://freewavesamples.com/bowed-bass-c2) is in the same directory/package as the above class.

Thursday, 4 September 2008

Practical Example of Swing Worker

Please note that this page has moved to: http://www.javacreed.com/swing-worker-example/.

Java provides a neat way to carry out long lasting jobs without have to worry about threads and hanging the application. It's called SwingWorker. It's not the latest thing on Earth (released with Java 1.6) and you may have already read about it. What I never came across was a practical example of the swing worker.

Swing Worker

SwingWorker is an abstract class which hides the threading complexities from the developer. It's an excellent candidate for applications that are required to do tasks (such as retrieving information over the network/internet) which may take some time to finish. It's ideal to detach such tasks from the application and simply keep an eye on their progress. But before we hit the road and start working with the swing worker we have to see what "eye" are we going to put on our worker so to say.

The following example illustrates a simple empty worker that will return/evaluate to an integer when the given task is finished. It will inform the application (the "eye" thing) with what's happening using objects of type string, basically text messages.


import javax.swing.SwingWorker;
 
public class MyBlankWorker extends SwingWorker<Integer, String> {
 
  // Some code must go here
 
}

The string worker class provides two place holders (generics). The first one represents the type of object returned when the worker has finished working. The second one represents the type of information that the worker will use to inform (update) the application with its progress. The swing worker class also provides means to update the progress by means of an integer which has nothing to do with the two generics mentioned before.

Practical Example

Example: Let say we need to find the number of occurrences of a given word with in some documents. So we would be writing something like:

 
import java.io.File;
import javax.swing.SwingWorker;
 
public class SearchForWordWorker 
             extends SwingWorker<Integer, String> {
 
  private final String word;
  private final File[] documents;
 
  public SearchForWordWorker(String word, File[] documents){
    this.word = word;
    this.documents = documents;
  }
 
  @Override
  protected Integer doInBackground() throws Exception {
    int matches = 0;
    for(int i=0, size=documents.length; i<size; i++){
      // Update the status: the keep an eye on thing
      publish("Searching file: "+documents[i]);
 
      try {
        // Do the search stuff
        // Here you increment the variable matches
      } finally {
        // Close the current file
      }
 
      // update the progress
      setProgress( (i+1) * 100 / size);
    }
 
    return matches;
  }
}

The first thing that comes into mind is where is the text "Searching file: ..." is going? The swing worker class provides another method called process which accepts a list (of type string in our case) and used to process the published information (which can be an object of any kind). Overriding this method, allows us to take full control of this information.


  @Override
  protected void process(List<String> chunks){
    for(String message : chunks){
      System.out.println(message);
    }
  }

The above example is not much useful. We may need to update the status bar of an application or the text of the progress bar or a label sitting somewhere in the application. Since we may be monitoring this worker from various UI components, ideally we add a level of isolation between the worker and the UI components. In many examples, the worker was fed UI components as constructor parameters. I would go for interfaces instead to make the design pluggable when possible.

In other occasions a worker may be used to populate a table which information is coming from a slow source. In this case we may use the table model as one of the workers parameter and the array of objects representing the row. The following example makes use of the default table model as the design is simpler.

 
import java.util.List;
import javax.swing.SwingWorker;
import javax.swing.table.DefaultTableModel;
 
public class PopulateTableWorker 
             extends SwingWorker<DefaultTableModel, Object[]> {
 
  private final DefaultTableModel model;
 
  public PopulateTableWorker(DefaultTableModel model){
    this.model = model;
  }
 
  @Override
  protected DefaultTableModel doInBackground() throws Exception {
    // While there are more rows
    while(Math.random() < 0.5){
      // Get the row from the slow source
      Object[] row = {1, "Java"};
      Thread.sleep(2000);
 
      // Update the model with the new row
      publish(row);
    }
 
    return model;
  }
 
  @Override
  protected void process(List<Object[]> chunks){
    for(Object[] row : chunks){
      model.addRow(row);
    }
  }
}

Note that the table model interface does not support addition of new rows. Alternatively we can make use of the table model interface. The model must be able to add a new row when this is required as otherwise an exception may be thrown.


import java.util.List;
import javax.swing.SwingWorker;
import javax.swing.table.TableModel;
 
public class PopulateTableWorker 
             extends SwingWorker<TableModel, Object[]> {
 
  private final TableModel model;
  private int rowIndex = 0;
 
  public PopulateTableWorker(TableModel model){
    this.model = model;
  }
 
  @Override
  protected TableModel doInBackground() throws Exception {
    // While there are more rows
    while(Math.random() < 0.5){
      // Get the row from the slow source
      Object[] row = {1, "Java"};
      Thread.sleep(2000);
 
      // Update the model with the new row
      publish(row);
    }
 
    return model;
  }
 
  @Override
  protected void process(List<Object[]> chunks){
    for(Object[] row : chunks){
      for(int columnIndex=0, size=row.length; 
                                      columnIndex<size; 
                                      columnIndex++){
        model.setValueAt(row[columnIndex], rowIndex, columnIndex);
      }
    }
    rowIndex++;
  }
}

Back to our example, we can have an interface called Informable (or you can pick a name of your liking) with one method: messageChanged(String message), which will be invoked whenever some progress is made and published.

 
public interface Informable {
    void messageChanged(String message);
}

The worker also requires an instance of the interface as it will be used to publish the results through.

 
import java.io.File;
import java.util.List;
import javax.swing.SwingWorker;
 
public class SearchForWordWorker 
             extends SwingWorker<Integer, String> {
 
  private final String word;
  private final File[] documents;
  private final Informable informable;
 
  public SearchForWordWorker(String word,
                                File[] documents,
                                Informable informable){
    this.word = word;
    this.documents = documents;
    this.informable = informable;
  }
 
  @Override
  protected Integer doInBackground() throws Exception {
    int matches = 0;
    for(int i=0, size=documents.length; i<size; i++){
      // Update the status: the keep an eye on thing
      publish("Searching file: "+documents[i]);
 
      try {
        // Do the search stuff
        // Here you increment the variable matches
      } finally {
        // Close the current file
      }
 
      // update the progress
      setProgress( (i+1) * 100 / size);
    }
 
    return matches;
  }
 
  @Override
  protected void process(List<String> chunks){
    for(String message : chunks){
      informable.messageChanged(message);
    }
  }
}

The above worker can be easily plugged into the application as show in the following example. This application has three graphical components: a label acting as a title displaying the latest message published by the worker; a text area which displays all messages published by the worker; and a progress bar showing the progress made.

Demo: Swing Worker Application

The label and text area are governed by Informable interface. The progress bar is governed by the worker's progress property.

 
import java.awt.*;
import java.beans.*;
import java.io.*;
import javax.swing.*;
 
public class Application extends JFrame {
 
  // The UI Components
  private JLabel label;
  private JProgressBar progressBar;
  private JTextArea textArea;
 
  private void initComponents(){
    // The interface will update the text of the UI components
    Informable informable = new Informable(){
      @Override
      public void messageChanged(String message){
        label.setText(message);
        textArea.append(message + "\n");
      }
    };
 
    // The UI components
    label = new JLabel("");
    add(label, BorderLayout.NORTH);

    textArea = new JTextArea(5, 30);
    add(new JScrollPane(textArea), BorderLayout.CENTER);
 
    progressBar = new JProgressBar();
    progressBar.setStringPainted(true);
    add(progressBar, BorderLayout.SOUTH);
 
    //  The worker parameters
    String word = "hello";
    File[] documents = {new File("Application.java"),
                        new File("Informable.java"),
                        new File("SearchForWordWorker.java")};
 
    // The worker
    SearchForWordWorker worker =
           new SearchForWordWorker(word, documents, informable){
       // This method is invoked when the worker is finished
       // its task
      @Override
      protected void done(){
        try {
          // Get the number of matches. Note that the 
          // method get will throw any exception thrown 
          // during the execution of the worker.
          int matches = get();
          label.setText("Found: "+matches);
 
          textArea.append("Done\n");
          textArea.append("Matches Found: "+matches+"\n");

          progressBar.setVisible(false);
        }catch(Exception e){
          JOptionPane.showMessageDialog(Application.this, 
                        "Error", "Search", 
                        JOptionPane.ERROR_MESSAGE);
        }
      }
    };

    // A property listener used to update the progress bar
    PropertyChangeListener listener = 
                               new PropertyChangeListener(){
      public void propertyChange(PropertyChangeEvent event) {
        if ("progress".equals(event.getPropertyName())) {
          progressBar.setValue( (Integer)event.getNewValue() );
        }
      }
    };
    worker.addPropertyChangeListener(listener);
 
    // Start the worker. Note that control is 
    // returned immediately
    worker.execute();
  }
 
  // The main method
  public static void main(String[] args){
    SwingUtilities.invokeLater(new Runnable(){
      public void run(){
        Application app = new Application();
        app.initComponents();
        app.setDefaultCloseOperation(EXIT_ON_CLOSE);
        app.pack();
        app.setVisible(true);
      }
    });
  }
}

The above example builds the application and kicks off the worker. The worker updates the application through the Informable instance. While the worker is performing it task, the application can carry on doing other things (event listening and painting) without hanging.

Cancel the Worker

Can we cancel the task? Yes. The worker can be stopped or better cancelled. The worker provides a method called cancel which accepts a parameter of type boolean. This parameter determines whether or not the worker should be waked up should it be found sleeping.

 
import java.awt.*;
import java.awt.event.*;
import java.beans.*;
import java.io.*;
import javax.swing.*;
 
public class Application extends JFrame {
 
  private JLabel label;
  private JProgressBar progressBar;
  private JTextArea textArea;
  private JButton  button;
  private SearchForWordWorker worker;
 
  private void initComponents(){
    // The interface will update the text of the UI components
    Informable informable = new Informable(){
      @Override
      public void messageChanged(String message){
        label.setText(message);
        textArea.append(message + "\n");
      }
    };
 
    // The UI components
    label = new JLabel("");
    add(label, BorderLayout.NORTH);
 
    textArea = new JTextArea(5, 30);
    add(new JScrollPane(textArea), BorderLayout.CENTER);

    // The cancel button 
    button = new JButton("STOP");
    button.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent event) {
        // Cancel the worker and wake it up should it be sleeping
        worker.cancel(true);
      }
    });
    add(button, BorderLayout.EAST);
 
    progressBar = new JProgressBar();
    progressBar.setStringPainted(true);
        add(progressBar, BorderLayout.SOUTH);
 
    //  The worker parameters
    String word = "hello";
    File[] documents = {new File("Application.java"),
                        new File("Informable.java"),
                        new File("SearchForWordWorker.java")};
                        
    // The worker
    worker = new SearchForWordWorker(word, documents, informable){
      @Override
      protected void done(){
        try {
          int matches = get();
          label.setText("Found: "+matches);

          textArea.append("Done\n");
          textArea.append("Matches Found: "+matches+"\n");
 
          progressBar.setVisible(false);
        }catch(Exception e){
          JOptionPane.showMessageDialog(Application.this, 
                       "Error", "Search", 
                       JOptionPane.ERROR_MESSAGE);
        }
      }
    };

    // A property listener used to update the progress bar
    PropertyChangeListener listener = 
                               new PropertyChangeListener(){
      public void propertyChange(PropertyChangeEvent event) {
        if ("progress".equals(event.getPropertyName())) {
          progressBar.setValue( (Integer)event.getNewValue() );
        }
      }
    };
    worker.addPropertyChangeListener(listener);
    
    // Start the worker. Note that control is 
    // returned immediately
    worker.execute();
  }
 
  public static void main(String[] args){
    SwingUtilities.invokeLater(new Runnable(){
      public void run(){
        Application app = new Application();
        app.initComponents();
        app.setDefaultCloseOperation(EXIT_ON_CLOSE);
        app.pack();
        app.setVisible(true);
      }
    });
  }
}

This will cause the worker's get method to throw the exception CancellationException to indicate that the worker was forced cancellation.

Conclusion

One thing to take from this article is: when possible do not refer to UI components directly from the worker. The worker is better viewed as the subject of the observer pattern. Provide an interface which the worker will use to communicate with its owner (application).