Improve your programs with practical audio

Learn 3 ways to produce sound in Java applications and applets -- and how to grab sounds from JAR files

There are many reasons to include audio in a Java applet or application. Sometimes the audio is the reason for the application -- as in a media player, audio jukebox, or sound demonstration program. At other times, audio is not the main reason for a program, and programmers often overlook it or fail to include it in a program. Yet auditory cues can improve the comprehension and usability of any program. Audio can augment a program by providing:

  • Feedback for user input, such as button presses or item selection
  • Alarms or announcements for notifications and listener events
  • Auditory progress indicators for time-consuming actions

Java provides easy and useful application programming interfaces (APIs) that should make audio a part of all but the most basic Java programs. Many Java programmers have dabbled with sound, and even those who have not will find employing effective audio a relatively simple task.

This article intends to go beyond merely playing sound to cover its practical uses. Not only do we cover playing, stopping, looping, and rewinding sounds, but we also cover extracting sounds from resource files so that users can provide their own sound-scheme files for use with your programs.

The basics

The most basic of all sound for a Java program is the Applet class's play method. This method takes a uniform resource locator (URL) and a filename and simply plays the given sound file. The code below shows how to add a button to an applet that plays a sound completely:

public class AppletPlay extends java.applet.Applet {
   java.awt.Button play;
   // Applet role
   public void init() {
      // Create GUI.
      add( play = new Button( "play" ) );
   }
   // Component role (1.02 event model)
   public boolean handleEvent( Event event ) {
      if ( event.target == play ) {
         play( getCodeBase(), "dong.au" );
         return true;
      }
      return super.handleEvent( event );
   }
}

Listing 1: Playing a sound from an applet

A note about the limitations of the API: You can see from the code in Listing 1 that the Applet class provides only a play method and no corresponding stop method. With this method, once sound is started, there is no way to end it, and the developer therefore should ensure that sound files activated in this manner are limited in duration (5 seconds or less).

Also, as is true with all Java programs prior to Java 1.2, the Java Sound APIs can handle only sound files of the AU file format, and even this format is limited in Java to monophonic, 8-kilohertz sounds.

Even with this limited API, you can tackle one of the most important uses for sound in a program: auditory feedback for user-initiated actions. However, don't bombard your user with trumpets and horns every time they press a button! When it comes to including sound, Intuit's Quicken 98 and TurboTax are examples of good user interface design. These programs provide a simple click or scratch noise for most buttons and menus -- sounds that let a user know something has occurred without being obtrusive or overbearing.

A more sophisticated alternative employs the Applet class's getAudioClip method. The two versions of this method return an instance of an AudioClip object. (One version requires a URL; the other requires a URL and a string.) Java 1.2 (currently in beta) also provides a static method called newAudioClip that does not require an Applet instance.

The AudioClip interface supports the play, loop, and stop methods so that a user can stop a sound once started. The code below shows how to create an AudioClip object from a URL and how to use the play, loop, and stop methods of the AudioClip class.

import java.applet.AudioClip;
import java.awt.Button;
public class AudioClipPlay extends java.applet.Applet {
   AudioClip audioClip;
   Button play, loop, stop;
   // Applet role
   public void init() {
      // Get sound.
      audioClip =  getAudioClip( getCodeBase(), "dong.au" );
      // Create GUI.
      add( play = new Button( "play" ) );
      add( loop = new Button( "loop" ) );
      add( stop = new Button( "stop" ) );
   }
   // Component role (1.02 event model)
   public boolean handleEvent( Event event ) {
      if ( event.target == play ) {
         audioClip.play();
         return true;
      }
      if ( event.target == loop ) {
         audioClip.loop();
         return true;
      }
      if ( event.target == stop ) {
         audioClip.stop();
         return true;
      }
      return super.handleEvent( event );
   }
}

Listing 2: Playing a sound with an AudioClip object

This method is more useful for longer sounds and gives the user the ability to stop or loop the sound. However, notice that in the above example (Listing 2), the sound is always played from the beginning; the example code includes no pause or resume capability.

Intermediate sound: the sun.audio classes

If you want to pause, resume, and reset a sound, you must employ the AudioStream, AudioDataStream, and ContinuousAudioDataStream classes. However, because these classes are in the sun.audio package there are a two noteworthy limitations:

  • Not every Java virtual machine (JVM) is required to port the sun.audio package. However, it seems that most Java runtime engines (JREs) and most browsers (Netscape Navigator, Microsoft Internet Explorer, and Sun HotJava) include sun.audio.

  • Some browsers require security to be loosened to run a class in the sun.audio package. Usually the security can be set from a dialog box or command-line option.

Given the above restrictions, the sun.audio package gives you the most flexibility in starting, stopping, and resetting a sound.

The code below demonstrates how to implement a resettable sound stream. The first part of the init method shows you how to get a sound from an input file stream. This sound can just as easily come from a URL or any other type of input stream. The input stream is an input parameter to the AudioStream constructor.

The first step is skip over the sound header information and get to the actual sound data of the stream. You do this by calling the getData method, which instantiates an AudioData object from the stream. This sound data can create a one-shot AudioDataStream or a looping ContinuousAudioDataStream object. The objects are passed to the AudioPlayer methods to start and stop the stream.

import java.awt.Button;
import java.io.*;
import sun.audio.*;
public class AudioStreamPlay extends java.applet.Applet {
   Button play, loop, stop, reset;
   // AudioPlayer instantiated to force run of static initializers.
   AudioPlayer audioPlayer = AudioPlayer.player;
   AudioDataStream audioDataStream;
   ContinuousAudioDataStream continuousAudioDataStream;
   // Applet role
   public void init() {
      // Get sound from file stream.
      FileInputStream fis = new FileInputStream( new File( "spacemusic.au") );
      AudioStream as = new AudioStream( fis ); // header plus audio data
      AudioData ad = as.getData(); // audio data only, no header
      audioDataStream = new AudioDataStream( ad );
      continuousAudioDataStream = new ContinuousAudioDataStream( ad );
      // Create GUI.
      add( play = new Button( "play" ) );
      add( loop = new Button( "loop" ) );
      add( stop = new Button( "stop" ) );
      add( reset = new Button( "reset" ) );
   }
   // Component role (1.02 event model)
   public boolean handleEvent( Event event ) {
      if ( event.target == play ) {
         audioPlayer.start( audioDataStream );
         return true;
      }
      if ( event.target == loop ) {
         audioPlayer.start( continuousAudioDataStream );
         return true;
      }
      if ( event.target == stop ) {
         audioPlayer.stop( audioDataStream );
         audioPlayer.stop( continuousAudioDataStream );
         return true;
      }
      if ( event.target == reset ) {
         audioDataStream.reset();
         continuousAudioDataStream.reset();
         return true;
      }
      return super.handleEvent( event );
   }
}

Listing 3: Playing a sound with an AudioClip object

The new features demonstrated in Listing 3 are the resetting of the stream, shown in the reset clause of the handleEvent method, and the pause/resume capability.

Unlike the other examples, the streams are not automatically rewound. If you choose a longer sound file, you will notice that the sound is resumed from the point in the stream at which you pressed Stop.

Pluggable resources from JAR files

The above examples show hard-coded values for the sound data, which is not something you would want to deal with in real life. Many programs actually provide applet parameters or command-line arguments for sounds. Another approach is to package sound files in a JAR file and refer to the sounds using published names. This highly flexible approach lets developers provide several alternative sound-scheme files, and lets users provide their own sound scheme (provided they stick to the published naming conventions of the JAR file).

This next example takes 10 sounds from a JAR file, named 0.au, 1.au, and so on, and plays them in order. They are placed in a JAR file by running the command jar cvf sounds.jar *.au -- where the directory contains 10 sounds with the given names. More information on JAR packaging is given in the tools documentation of the Java Development Kit (JDK). I borrowed my 10 sounds from the Sun JDK 1.1 animator program in the audio directory, but you can just as easily bundle 10 sounds of your own choosing.

Although the sound-playing code is the same in this example as it is in the last example, the interesting code in this example involves the AppletClassLoader and AppletResourceLoader classes. These are in the sun.applet package, so like the sun.audio classes they suffer similar restrictions. Since the constructor to AppletClassLoader is protected, and therefore invisible to an applet, a common object-oriented hack is to create a class derived from AppletClassLoader and make it public. This is the function of the nested InstallableClassLoader class. Once the ClassLoader class is available, an AppletResourceLoader is created, which loads the JAR file (via the loadJar method) and loads the sounds (via the getLocalResource method). The getLocalResource method returns an object that we use to instantiate the audio stream and play the sound.

import java.net.*;
import java.io.*;
import sun.audio.*;
// For loading jar resources
import sun.applet.AppletClassLoader;
import sun.applet.AppletResourceLoader;
public class JarPlay extends java.applet.Applet {
   String [] soundNames = { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9" };
   // Applet role
   public void init() {
      String jarName = getParameter( "jarname" );
      if ( (null != jarName ) && ( jarName.length() > 0 )) {
         AppletClassLoader acl = new InstallableClassLoader( getCodeBase() );
         AppletResourceLoader arl = acl.getResourceLoader();
         if (arl != null) {
            System.out.println( "Loading " + jarName );
            try {
               // Load resources from jar file.
               // loadJar made public in 1.1.4. Not available in
1.1.1
               arl.loadJar( getCodeBase(), new FileInputStream( jarName ) );
               for ( int i = 0; i < soundNames.length; i++ ) {
                  System.out.println( "Loading sound \"" + soundNames[ i ] +
"\"." );
                  URL aurl = new URL( getCodeBase(), soundNames[ i ] + ".au" );
                  Object o = arl.getLocalResource( aurl ); // returns
BufferedInputStream
                  AudioStream as = new AudioStream( (InputStream) o );
                  AudioData ad = as.getData(); // audio only, no header
                  AudioDataStream audioDataStream = new AudioDataStream( ad );
                  AudioPlayer.player.start( audioDataStream );
               } /* endfor */
            } catch ( Exception e ) {
               e.printStackTrace();
            } /* endcatch */
         }
      } /* endif */
      System.out.println( "done with init" );
   }
   class InstallableClassLoader extends AppletClassLoader {
      // Make protected constructor public
      public InstallableClassLoader( URL base ) {
         super( base );
      }
   }
}

Listing 4: Playing named sounds from a JAR file

When you run this applet you should hear the 10 sounds of the telephone keypad. By replacing the sound JAR file, however, you can make this program play any 10 sounds you like. Instead of public names, you can also use the ordinal position in a JAR file for your users to change: Sounds 0 to 10 are menu sounds, sounds 11 to 20 are drag-and-drop sounds, and so on. Another variation is to create a ZipInputStream from the given JAR file, iterate through the entries using the getNextEntry method, perhaps filter the entries with a naming convention, and assign the sounds in a given file to user-interface actions. This method provides much flexibility.

Advanced sound and other alternatives

At the time of this writing, Sun announced other alternatives for producing sound in a Java program. At the high end, the Java Media Frameworks (JMF) provide a user-extensible framework for transporting and playing sound and video in a Java program. Although this flexible alternative has been implemented for many platforms, JMF is a Java extension and cannot be guaranteed to be available on all Java platforms. The package is quite large and has dependencies on a native implementation.

Sun also announced for Java 1.2 the Java Sound engine, which is implemented with the existing public sound APIs from JDK 1.02 and 1.1. The benefit of this new engine is its ability to play a wider variety of sound types, including WAV, MIDI, RMF, AIFF, and more AU sound formats. At JavaOne '98, Sun announced a public specification for interfacing with the sound engine. This public Java Sound API allows user inspection and manipulation of the sound data.

Conclusion

In this article I have demonstrated several alternative ways to play sound in a Java applet or application, and have shown how simple it is to play sounds in a Java program. I also have shown how to package and extract sounds from developer- or user-provided "sound-scheme" files. Sounds are easy to package in JAR files using published names, ordinal positions, or naming conventions. You can offer users a lot of options for customizing their environments to make them sound just right (from the individual user perspective). As is clear from the Sun announcements, Sun will offer additional and more flexible alternatives in the future. For now, however, these examples show several current options for programmers that want to include sound in their Java programs today.

Dan Becker works in the Network Computing Software Division of IBM Corp. in Austin, TX. He is currently working on Java 1.2 and other Java extensions for IBM Operating System/2. Before that Dan worked on porting previous Java virtual machines, the multimedia plug-ins for Netscape Navigator for OS/2, OpenDoc, and the Multimedia parts for OS/2 Warp Version 4.0. Dan can be reached at his public Webpage at http://www.io.com/~beckerdo.

Learn more about this topic

  • Sun's Java Media API page shows all media-related developments in Java related to playing sound and video http://java.sun.com/products/java-media
  • Sun's Java Media Frameworks Java Extension page shows an advanced extension to Java that allows playing many sound and video formats http://java.sun.com/products/java-media/jmf/index.html
  • Sun's Java Sound API for Java 1.2 page shows upcoming changes to Java 1.2 that allow playing, mixing, and editing many sound formats http://java.sun.com/products/java-media/sound/index.html
  • Sun's Java Tutorials page contains a tutorial on playing Java sounds, as well as other tutorials http://java.sun.com/docs/books/tutorial/TOC.html
Join the discussion
Be the first to comment on this article. Our Commenting Policies
See more