On Chapter 11, you learned all about sound
and how it is used in computer games, as well as how to find and
record your own sounds. However, you didn't learn anything about
how to actually implement sound in Java. Sure, you might have
sampled a bunch of neat sounds, but they aren't of much use until
you under-stand how to play them in a real applet.
ToChapter you learn all about sound and how it works in Java. You
find out all the not-so-gory details about how sound is represented
in Java, along with the classes and methods used to load and play
sounds. It turns out that the current release of Java has pretty
limited support for sound. Nevertheless, more than enough audio
support is there to liven up Java games. You finish up toChapter's
lesson by using the sound support in Java 1.0 to build a pretty
neat applet that plays multiple sound effects.
The following topics are covered in toChapter's lesson:
The current sound support in Java comes in the form of a class
and a few methods in the Applet
class. The AudioClip class,
which is part of the applet package, models a digital audio sound
clip in the AU file format. You learn about this class next. You
learn about the methods supporting sound in the Applet
class later in toChapter's lesson.
The AU file format, which you learned about in yesterChapter's lesson,
is currently the only sound format supported by Java. If you recall,
it is designed around 8,000 Hz mono 8-bit ULAW encoded audio clips.
This is a fairly low-quality sound format, and it severely limits
Java in providing professional audio capabilities. However, in
the current context of the Web, just being able to play AU audio
clips in Java is plenty for many applets.
As far as games go, the quality of the sound isn't always as crucial
as you might think. Many sound effects (animal noises, for example)
don't require very high-quality audio. You find this out firsthand
in this lesson when you implement an applet using various animal
sound effects.
The AudioClip class models
a sound clip in Java. It is an abstract class, so you can't directly
create instances of it. The only way to create AudioClip
objects is by calling one of the getAudioClip
methods of the Applet class.
You'll learn more about that in a moment. But first, take a look
at the methods in the AudioClip
class.
public abstract void play()
public abstract void loop()
public abstract void stop()
As you can see, these methods are very high-level and quite simplistic.
You can't ask for a much easier interface than just calling play
to play an audio clip and stop
to stop an audio clip. The only twist is the loop
method, which plays an audio clip repeatedly in a loop until you
explicitly call stop to stop
it. The loop method is useful
when you have an audio clip that needs to be repeated, such as
a music clip or a footstep sound.
None of the methods in AudioClip
require parameters; the AudioClip
object is entirely self-contained. For this reason, there isn't
a lot to learn about using the AudioClip
class. By simply understanding the three methods implemented by
the AudioClip class (play,
loop, stop),
you are practically already a Java sound expert!
Before your ego gets too inflated, remember that you still haven't
learned the details of how to create an AudioClip
object. As I mentioned a little earlier, you use one of the Applet
class's getAudioClip methods
to create and initialize an AudioClip
object. The two versions of getAudioClip
are as follows:
public AudioClip getAudioClip(URL url)
public AudioClip getAudioClip(URL url, String name)
Note
I mentioned earlier that you can't create an AudioClip object directly because AudioClip is an abstract class. Because the class is abstract, you might be wondering how an AudioClip object can be created at all. Technically, it is impossible to ever create an object based on an abstract class. However, in the case of AudioClip, you use the getAudioClip method to get a platform-specific AudioClip derived object. In other words, getAudioClip acts as a native method that returns a native class
derived from AudioClip. The method and class are native because sound support varies so widely on different platforms. The purpose of the AudioClip, therefore, is to standardize the
interface for the native audio clip classes, which results in a general, platform-independent programming solution.
The only difference between these two getAudioClip
methods is whether or not the URL parameter contains a complete
reference to the name of the audio clip. In the first version,
it is assumed that the URL contains the complete name; the second
version uses the name in a separate name
parameter. You will typically use the second version, because
you can easily retrieve the base URL of the applet or the HTML
document in which the applet is embedded. You do this by using
either the getCodeBase or
the getDocumentBase method
of Applet, like this:
The getCodeBase method returns
the base URL of the applet itself, whereas getDocumentBase
returns the base URL of the HTML document containing the applet.
It is usually smarter to use getCodeBase
to specify the base URL for loading resources used by a Java applet.
The reason for this is that it is often useful to organize Java
applets into a directory structure beneath the HTML documents
in which they appear. Furthermore, you usually reference images
and sounds either from the same directory where the applet is
located or from a subdirectory beneath it.
Tip
I like to organize images and sounds used by an applet in a directory called Res, beneath the directory containing the actual Java classes. This isolates the executable part of an applet from the resource content used
by the applet, resulting in a more organized file structure.
In the audio discussion thus far, I might have led you to believe
that you must use an AudioClip
object to play sounds in Java. This isn't entirely true! The truth
is that you are only required to create an AudioClip
object if you want to play looped sounds. For normal (nonlooped)
sounds, you have the option of using one of the play
methods in the Applet class
instead of using an AudioClip
object. The definitions for the play
methods implemented in Applet
are as follows:
public void play(URL url)
public void play(URL url, String name)
These play methods take exactly
the same parameters taken by the getAudioClip
methods. In fact, the play
methods in Applet actually
call getAudioClip to get
an AudioClip object and then
use the AudioClip object's
play method to play the sound.
In this way, the Appletplay
methods basically provide a higher level method of playing audio.
This is evident in Listing 12.1, which shows the Java 1.0 source
code for the Appletplay
methods.
Listing 12.1. The Java 1.0 Applet
class's play
methods.
public void play(URL url) {
AudioClip clip = getAudioClip(url);
if (clip != null) {
clip.play();
}
}
public void play(URL url, String name) {
AudioClip clip = getAudioClip(url, name);
if (clip != null) {
clip.play();
}
}
The Appletplay
methods both create a temporary AudioClip
object and then use it to play the sound by calling its play
method. This provides an even higher level of interface to playing
nonlooped sounds than the AudioClip
class provides.
Now that you are a Java sound expert (at least in theory), it's
time to put your newfound knowledge to work in a sample applet.
Because this guide is ultimately about writing games, it's important
for you to never compromise in making your applets as entertaining
as possible. For this reason, the applet you're going to develop
to demonstrate sound is a little more than a simple sound player.
As a matter of fact, it's quite wild! Figure 12.1 shows a screen
of the WildAnimals applet, which uses the Java AudioClip
class to generate some entertaining results. The source code,
executable, images, and sounds for WildAnimals are located on
the accompanying CD-ROM.
The screen shot of WildAnimals doesn't quite convey the real purpose
of the applet. So, at this point, I encourage you to run it for
yourself from the CD-ROM to get the real effect. Just in case
you're the impatient type and choose to skip the wild animal experience,
I'll fill you in on what's happening. WildAnimals randomly plays
a variety of wild animal sounds to go with the eyes that are staring
at you from the darkness.
Now that you know what it does, let's take a look at the implementation
of WildAnimals. The WildAnimals
class really only defines one member variable beyond the variables
required for animation that you've already seen in prior applets:
private AudioClip[] clip = new AudioClip[5];
This array of AudioClip objects
is used to store the wild animal sounds.
The init method in WildAnimals
initializes the audio clips by calling the getAudioClip
method:
public void init() {
// Load and track the images
tracker = new MediaTracker(this);
Eyes.initResources(this, tracker, 0);
After the audio clips are initialized in init
using getAudioClip, they
are ready to be played.
The eyes you see in the applet are implemented as sprites, which
you'll learn about later in toChapter's lesson. These sprites are
created in the run method,
which also creates and initializes the sprite list. Listing 12.2
contains the source code for the run method.
// Create and add the sprites
sv = new SpriteVector(new ColorBackground(this, Color.black));
for (int i = 0; i < 8; i++) {
sv.add(new Eyes(this, new Point(Math.abs(rand.nextInt()
%
size().width), Math.abs(rand.nextInt()
% size().width)),
i % 2, Math.abs(rand.nextInt()
% 200)));
}
// Update everything
long t = System.currentTimeMillis();
while (Thread.currentThread() == animate) {
// Update the animations
sv.update();
repaint();
// Play an animal sound
if ((rand.nextInt() % 15) == 0)
clip[Math.abs(rand.nextInt()
% 5)].play();
try {
t += delay;
Thread.sleep(Math.max(0, t
- System.currentTimeMillis()));
}
catch (InterruptedException e) {
break;
}
}
}
Beyond creating the sprites for WildAnimals, the run
method also handles playing the random animal sounds. This is
carried out by using the nextInt
method of the Random object
to get a random number between -15
and 15. This random number
is checked to see whether it is equal to 0,
in which case a sound is played. This creates a 1-in-31 chance
of a sound being played each time through the update loop. There
is no magic surrounding the range of the random numbers; it was
determined by trying out different values. When a sound is to
be played, nextInt is used
again to randomly select which sound to play. That's all there
is to playing the random sounds.
That covers all the unique aspects of the WildAnimals
class. However, you still haven't seen how the eye sprites are
implemented. The Eye class
implements a blinking eye sprite that can be either small or large.
It uses a static two-dimensional array of Image
objects to store the frame animations for the blinking eye in
each size. Like all derived Sprite
classes you've seen, the images are initialized in the initResources
method:
public static void initResources(Applet
app, MediaTracker tracker, int id) {
for (int i = 0; i < 4; i++) {
image[0][i] = app.getImage(app.getCodeBase(),
"Res/SmEye" + i + ".gif");
tracker.addImage(image[0][i], id);
image[1][i] = app.getImage(app.getCodeBase(),
"Res/LgEye" + i + ".gif");
tracker.addImage(image[1][i], id);
}
}
Figure 12.2 shows what the animation images for the eye look like.
The Eye class contains two
member variables, blinkDelay
and blinkTrigger, for managing
the rate at which it blinks:
protected int blinkDelay,
blinkTrigger;
blinkDelay determines how
long the eye waits until it blinks again, and blinkTrigger
is the counter used to carry out the wait. They are both initialized
to the blink delay parameter passed into the constructor of Eye:
public Eyes(Component comp, Point pos,
int i, int bd) {
super(comp, image[i], 0, 1, 2, pos, new Point(0, 0),
0, Sprite.BA_WRAP);
blinkTrigger = blinkDelay = bd;
}
The only overridden method in Eye
is incFrame, which handles
incrementing the animation frame:
It is necessary to override incFrame
so that you can add the blinking functionality. This is done by
decrementing blinkTrigger
and seeing whether it has reached zero. If so, it's time to blink!
Notice that the blink is still dependent on the frame delay, which
is very important. This is important because you don't want an
added feature, such as blinking, to interrupt a basic function
of the sprite, such as the frame delay.
The incFrame method does
one other thing worth pointing out. If you think about it, a blink
must consist of going through the frame animations forward (to
close the eye) and then backward (to open the eye again). The
standard implementation of incFrame
in Sprite, which you saw
last week, always goes in a constant direction-that is, forward
or backward as determined by the sign of the frameInc
member variable. In Eye's
incFrame, you want the frame
animations to go forward and then backward without having to fool
with frameInc. The if-else
clause in incFrame solves
this problem beautifully.
That finishes up the WildAnimals sample applet. It proves that
sound in Java is not only fun, but it is also easy to implement!
ToChapter you learned all about how sound is used in Java. You started
off by learning how Java supports sound through the AudioClip
class and a few methods in the Applet
class. You then progressed to building a complete applet using
sound to create somewhat of a virtual wilderness at night. It
showed you how easy it is to add sound to Java applets. It also
was a good example of how the sprite classes can be used in new
and creative ways.
You now have all the background necessary to add sound to any
Java applet you write, including games. Speaking of games, it's
almost time for you to write another one. But that will have to
wait until tomorrow!
Do I have to do anything special to mix sounds in Java?
A
No. The Java sound support automatically handles mixing sounds that are being played at the same time. This might seem trivial, but it is actually a very nice feature.
Q
How do I play MIDI music in Java?
A
Right now, you can't. The current version of Java (1.0) doesn't provide any support for MIDI, but hopefully it will appear in a later release. Sun has promised more extensive multimedia features in the near
future. For now, you can record music as an audio clip and then loop it; more on this tomorrow.
Q
In the WildAnimals applet, how can I make the eyes blink faster?
A
Decrease the blink delay parameter passed into the constructor. More specifically, decrease the number used in the modulus operation after the call to nextInt.
Q
How can I add more animals to the WildAnimals applet?
A
The first step is to record or find more animal sounds and copy them to the Res directory. You then need to increase the size of the clip
array of AudioClip objects and load the new sounds in init using the getAudioClip method. Finally, in the call to play in the run method, increase the number used to index into the clip array (it is currently 5).
The Workshop section provides questions and exercises to help
you get a better feel for the material you learned toChapter. Try
to answer the questions and at least think about the exercises
before moving on to tomorrow's lesson. You'll find the answers
to the questions in appendix A, "Quiz Answers."
Draw or find an image containing some animals and use it as
the background. Hint: Use the ImageBackground
class instead of the ColorBackground
class when creating the SpriteVector.
Try out different values for the blink delay of the eyes.
Add more animal sounds.
Modify the Eye class
so that the eyes look like the animals are walking around.