Development Game in Java (Java 游戏开发)这本书是有中文版的,但是听说翻译的不好,所以决定看英文版的,顺便翻译出来,共享给大家。前三章已经有人翻译并共享到网上了,我从第四章开始吧!好废话少说,Let's begin!
第四章 声效和音乐 (Chapter 4. Sound Effects and Music)
主题
· 声音基本原理
· Java Sound API
· 播放声音
· Creating a Real-Time Sound Filter Architecture实时声音过滤器体系结构
· Creating a Real-Time Echo Filter实时响应滤波器
· 效仿 3D 声效
· 创建一个声效管理器
· 播放音乐
· 总结
Unless you've never managed to figure out how to hook up speakers to your computer, you've noticed that games have sound effects—cool sound effects—and sometimes music, too.
译:除非你从来没有设法弄明白如何去打开你计算机的扬声器,否则你应该已经注意到游戏具有声效——非常酷的声音效果——有时也可以是音乐。
When you're playing a game (with the speakers on, of course), you might hear the sound effects but not really notice them. That's because you expect to hear them—sometimes sound effects are something you notice only when they're missing. They're sort of like electricity. To sum it up, sound effects are an important part of a game. People expect to hear them, and they shouldn't be left out.
译:当你玩游戏的时候(当然是在打开扬声器的情况下),你会听见一些声效,但是你从来都没有真正的去注意过这些声效。那是因为你认为本来就应该能听见他们——声效经常是那种当你听不到时才注意到的东西。就像电这类东西。总的来说,声效是游戏的重要组成部分。人们期望能够听到声效,声效不会被遗忘。
In this chapter, you'll learn the basics of playing sound and then move on to real-time sound-effect filters (such as echoes). You'll also create a sound manager to handle it all. In addition, you'll learn to play music (such as CD audio, MP3, Ogg Vorbis, and MIDI) and to make the music dynamically change to adapt to the state of the game.
译:在本章中,你将会先学习播放声音的基本原理,然后继续学习实时声效过滤器(例如回响器)。你还要创建一个声音管理器来处理声效。另外,你将要学习如何播放音乐(例如 CD 音频,MP3,Ogg Vorbis和MIDI(迷笛音乐)),并且动态地调整音乐以适应游戏的状态。
小节 4.1 声音基本原理 (Sound Basics)
Sound is created when something vibrates through a medium. In this case, this medium is air and the vibration comes from the computer's speakers. Your eardrums pick up the vibration and signal your brain, which interprets it as sound.
译:物体振动通过媒介传播,从而产生了声音。在我们的游戏中,计算机的扬声器产生震动,并通过空气传播。当你的耳膜接收到振动,并将信号传给大脑,大脑将其解释为声音。
This vibration through the air creates pressure fluctuations. Faster fluctuations create a higher sound wave frequency, leading you to hear a higher pitch. The amount of pressure in each fluctuation is known as its amplitude. Higher amplitude causes you to hear louder sound. In short, sound waves are just changing amplitudes over time, as shown in Figure 4.1.
译:空气的振动会产生的压力波(其实就是声波)。波动越快,创造的声波频率越高,你听到的音调就越高。每一个波动的压力值称为振幅。振幅越高,你听到声音就越响亮。总之,声波,只是随着时间的推移不断变化的振度,如图4.1所示。
图4.1。声波就是随时间变化的振幅
Figure 4.1. Sound waves are composed of changing amplitudes over time.
Digital sound, such as that in CD audio and many computer sound formats, contains sound as a series of discrete samples of the sound's amplitudes. The amount of samples stored per second is called the sample rate. CD audio, for example, has a sample rate of 44,100Hz. Of course, higher sample rates result in a more accurate audio representation, and lower sample rates mean poorer quality but a smaller file size. The samples themselves are typically 16 bits, giving 65,536 amplitude possibilities.
译:数字声音,例如, CD音频和许多计算机音频格式,包含的声音就是声音振幅的一系列离散样本。每秒钟存储样本的数量称为采样率。 例如 CD音频,采样率是 44,100Hz (赫兹)。当然,采样率越高,音频就越准确(高保真,非常接近原声),采样率越低音频质量就越差,但文件的尺寸就越小。声音通常采用16位 数据表示,最多可以表示 65,536 种振幅。
Many sound formats allow for multichannel sound. CD audio has two channels, one for a left speaker and one for a right speaker.
译:许多声音格式都是多声道的。 CD音频有两个声道,一个用于左扬声器,另一个用于右扬声器。
小节 4.2 The Java Sound API
To play sampled sound in your Java game, you use the Java Sound API, in the package, javax.sound.sampled.
译:要在你的Java 游戏中播放 声音样本,您可以使用 javax.sound.sampled 包中的Java API。
Java Sound can play sound formats with 8- or 16-bit samples with sample rates from 8000Hz to 48,000Hz. Also, it can play either mono or stereo sound. What sound format you use for your game depends on what you want. For these examples, we use 16-bit, mono, 44,100Hz sound.
译:Java Sound可以播放 8或16位声音样本(即采样率8000~48,000赫兹)。而且,它可以播放单声道或立体声音频。在你的游戏中使用什么声音格取决于你想要什么样的声音。对于本书例子,我们使用16位,单声道,44100 赫兹的声音。
If you are feeling brave, you could generate all these samples yourself in code, but typically you'll want to get sound samples from a sound file. Java Sound provides support for reading three sampled sound file formats: AIFF, AU, and WAV. All formats are very flexible, and it doesn't make much of a difference which one you use. We use the WAV format in our examples.
译:如果你勇于尝试,也可以通过代码产生所有声音样本,但通常你会从一个声音文件来获得声音样本。Java Sound支持读取3种声音采样文件格式:AIFF,AU,和WAV的支持。所有格式都非常灵活,在使用过程中你不会感觉到有什么不同。在我们的例子中我们使用的WAV格式。
Some sound programs that you can use to create, record, and edit sounds are Pro Tools FREE (www.digidesign.com/ptfree), Cool Edit (www.syntrillium.com/cooledit), GoldWave (www.goldwave.com), and Audacity (audacity.sourceforge.net). Be sure to check out Chapter 17, "Creating Game Art and Sounds," to get some ideas on creating sounds.
译:有一些可以创建,录制和编辑声音的应用,如Pro Tools FREE(www.digidesign.com / ptfree),Cool Edit(www.syntrillium.com / cooledit),GoldWave(www.goldwave.com)和Audacity(audacity.sourceforge.net)。一定要看一下第17章,“创建游戏艺术和声音” ,从中获得一些创建声音的想法。
打开一个声音文件 / Opening a Sound File
You can load a sound file with Java Sound using the AudioSystem class. The AudioSystem class contains several static functions, most of which you won't use. But it provides several getAudioInputStream() methods to open an audio file from the file system or other source, such as the Internet. These methods return an AudioInputStream object.
译:你可以使用Java Sound中的AudioSystem类加载声音文件。AudioSystem类包含了一些静态方法,其中大多数你用不到。但它提供了几个重载的getAudioInputStream() 方法用来从文件系统或从其他来源,例如互联网,打开音频文件。这些方法均返回一个AudioInputStream对象。
API描述:AudioInputStream(音频输入流)是具有指定音频格式和长度的输入流。长度用示例帧表示,不用字节表示。提供几种方法,用于从流读取一定数量的字节,或未指定数量的字节。音频输入流跟踪所读取的最后一个字节。可以跳过任意数量的字节以到达稍后的读取位置。音频输入流可支持标记。设置标记时,会记住当前位置,以便可以稍后返回到该位置。
With an AudioInputStream object, you can read the samples of a sound without having to mess with the sound file header or other extra information in the file. Also, you can query the format of the sound by calling the getFormat() method:
译:使用AudioInputStream对象,您可以直接读取一个声音的样本,而不必关心文件头或文件中的额外信息。而且,您可以调用getFormat() 方法获得声音的格式信息(AudioFormat对象):
File file = new File("sound.wav");
AudioInputStream stream = AudioSystem.getAudioInputStream(file);
AudioFormat format = stream.getFormat();
The AudioFormat class provides a way to get information about the format of the sound, such as the sample rate and number of channels. Also, it provides a way to get the frame size, which is the number of bytes required for every sample for every channel. For 16-bit stereo sound, the frame size is four, or 2 bytes for each sample (left and right). This can be useful if you want to find out how many bytes it takes to store a sound in memory. For example, a three-second-long sound with an audio format of 16-bit samples, stereo, 44,100Hz would be 44,100x3x4 bytes, or about 517KB. Using mono instead of stereo would cut the size in half.
译:AudioFormat类提供一种方式来获得声音的格式信息,如采样率和声道数。此外,它还提供了方法来获取帧大小,即每个声道每个样本的字节数。对于16位立体声,帧大小为4字节,或每个采样2字节(左右声道各一个采样)。如果你想了解存储在内存中的声音需要占用多少字节。这些方法将会非常有用。例如,一段音频格式为16位采样,立体声、44 100赫兹的时长3秒的声音的数据量是44100 X 3 X 4字节,约517KB。如果使用单声道来代替立体声(即双声道),数据量将会减半。
使用Line /Using a Line
Okay, now that you have a way to get the sound samples and the format they are in, what do you do with them? The answer is to feed them through a Line.
译:好吧,现在你有办法获得声音样本和格式了,你如何使用它们呢?答案是通过Line对象来处理它们。
A Line is an interface to send or receive audio from the sound system. You can use Lines to send sound samples to the sound system to play or to receive sound from, say, a microphone.
译: Line是一个用于接收或发送来自音响系统的音频的接口。您可以使用Line发送声音样本给音响系统播放,或从麦克风接收说话的声音。
The Line interface has several subinterfaces. The main Line subinterface used here is a SourceDataLine, which enables you to write audio data to the sound system.
译:Line有几个子接口。在这里使用的主要的子接口是 SourceDataLine ,这使您可以向音响系统输出音频数据。
Lines are created by using AudioSystem's getLine() method. You pass this method a Line.Info object, which specifies the type of Line you want to create. Line.Info has a DataLine.Info subclass, which you'll use to create your Lines because it contains information on the line's audio format.
译:使用AudioSystem的getLine() 方法可以创建Line。通过传递给该方法一个Line.Info对象,你可以创建指定类型的Line。 Line.Info有一个子类DataLine.Info,它包含了 Line的音频格式信息,您可以使用它创建Line对像。
Besides SourceDataLine, we'll touch on another Line called a Clip. A Clip does a lot of work for you, loading samples into memory from an AudioInputStream and feeding them to the audio system automatically. Here is how you would extend the AudioInputStream to play it using a Clip:
译:除了SourceDataLine 子接口外,我们还会用到Line的另外一个子接口Clip (剪辑)。Clip可以帮你完成大量的工作,从AudioInputStream 加载样本到内存并自动将样本提供给音频系统。下面我们来看如何扩展AudioInputStream 来播放一个Clip(剪辑):
// specify what kind of line we want to create 指定我们希望创建的Line的类型
DataLine.Info info = new DataLine.Info(Clip.class, format);
// create the line 创建line
Clip clip = (Clip)AudioSystem.getLine(info);
// load the samples from the stream 通过流载入音频采样
clip.open(stream);
// begin playback of the sound clip 开始播放音频剪辑
clip.start();
Clips are convenient and easy to use, and are similar to AudioClips introduced in Java SDK 1.0. But Clips have some drawbacks. Java Sound has a limit to the number of Lines you can have open at the same time, which is usually a maximum of 32 Lines. Because Clips are Lines, this means you can open only a limited number of sounds, even before you play any one of them.
译:剪辑方便易用,并且类似与Java SDK 1.0引入AudioClips。但是,剪辑有一些缺点。 Java Sound限制同一时间打开Line的数量,通常最多是32个。由于Clip(剪辑)是Line的子接口,这意味着你只可以同时打开有限数量的声音,即使在你还没有播放任何一个声音时。
Also, although several Clips can play simultaneously, each Clip can play only one sound at a time. For example, if you want two or three explosions to play simultaneously, you'll need a Clip for each one.
译:此外,虽然能够同时播放几个Clip(剪辑),每个Clip(剪辑)每次只能播放一个声音。例如,如果你希望同时播放两个或三个爆炸声,那么每个爆炸声都需要专门的一个 Clip。
Because of these drawbacks, in the next section you'll create a solution that will enable you to load any number of sounds and play several copies of each sound simultaneously.
译:由于这些缺点,在下一节中将创建一个解决方案,使我们能够加载任意数量的声音并同时播放每一个声音的多个拷贝。
小节 4.3 播放声音(Playing a Sound)
To get started, you'll create a SimpleSoundPlayer to play sound. This class loads samples from an AudioInputStream into a byte array. It also plays sound from any InputStream by copying data from it to a Line.
译:首先,创建一个用于播放音频的 SimpleSoundPlayer 类。这个类从一个 AudioInputStream 加载音频采样,并保存在一个字节数组中。 SimpleSoundPlayer类从任何一种 InputStream 读取数据,并复制到Line 中播放。
In the SimpleSoundPlayer example in Listing 4.1, the samples loaded are converted to an InputStream by using a ByteArrayInputStream. This enables you to read samples from memory instead of from disk. You could just write the byte array directly to the Line, but you'll need to read from InputStreams to add some more advanced functionality later.
译:例子 Listing 4.1 的SimpleSoundPlayer 类中,采样(字节数组形式)被 ByteArrayInputStream 转换成了一个 InputStream 。这样你就可以读取从内存读取采样而不是硬盘。你可以直接将字节数组中的采样数据写到Line,但是将来你需要从 InputStreams 读取采样数据,以便增加更为高级的功能。
Because you're using a ByteArrayInputStream wrapped around a byte array, you can create as many ByteArrayInputStreams for the same sound as you want, so you can play multiple copies of the same sound simultaneously.
译:因为你正在使用包装了一个字节数组的 ByteArrayInputStream ,你可以为同一个音频文件创建尽可能多的 ByteArrayInputStream,以便可以同时播放同一个声音的多个拷贝。
Listing 4.1 SimpleSoundPlayer.java
import java.io.*;
import javax.sound.sampled.*;
/**
The SimpleSoundPlayer encapsulates a sound that can be opened
from the file system and later played.
SimpleSoundPlayer 类可以从文件系统打开一个声音并播放。
*/
public class SimpleSoundPlayer {
public static void main(String[] args) {
// load a sound 载入声音
SimpleSoundPlayer sound =
new SimpleSoundPlayer("../sounds/voice.wav");
// create the stream to play 创建用于播放的输入流
InputStream stream =
new ByteArrayInputStream(sound.getSamples());
// play the sound 播放声音
sound.play(stream);
// exit 退出
System.exit(0);
}
private AudioFormat format;
private byte[] samples;
/**
Opens a sound from a file. 从文件系统打开一个声音
*/
public SimpleSoundPlayer(String filename) {
try {
// open the audio input stream 打开音频输入流
AudioInputStream stream =
AudioSystem.getAudioInputStream(
new File(filename));
format = stream.getFormat();
// get the audio samples 读取音频样本
samples = getSamples(stream);
}
catch (UnsupportedAudioFileException ex) {
ex.printStackTrace();
}
catch (IOException ex) {
ex.printStackTrace();
}
}
/**
Gets the samples of this sound as a byte array.
读取声音的样本字节数组
*/
public byte[] getSamples() {
return samples;
}
/**
Gets the samples from an AudioInputStream as an array
of bytes. 从 AudioInputStream 读取采样的数组
*/
private byte[] getSamples(AudioInputStream audioStream) {
// get the number of bytes to read 获取要读取的字节数量
int length = (int)(audioStream.getFrameLength() *
format.getFrameSize());
// read the entire stream 读取输入流
byte[] samples = new byte[length];
DataInputStream is = new DataInputStream(audioStream);
try {
is.readFully(samples);
}
catch (IOException ex) {
ex.printStackTrace();
}
// return the samples 返回样本
return samples;
}
/**
Plays a stream. This method blocks (doesn't return) until
the sound is finished playing. 播放流。 本方法阻塞(不返回)直到声音播放完毕。
*/
public void play(InputStream source) {
// use a short, 100ms (1/10th sec) buffer for real-time
// change to the sound stream 使用一个100毫秒的小缓冲实时改变音频流
int bufferSize = format.getFrameSize() *
Math.round(format.getSampleRate() / 10);
byte[] buffer = new byte[bufferSize];
// create a line to play to 创建 line ,然后播放
SourceDataLine line;
try {
DataLine.Info info =
new DataLine.Info(SourceDataLine.class, format);
line = (SourceDataLine)AudioSystem.getLine(info);
line.open(format, bufferSize);
}
catch (LineUnavailableException ex) {
ex.printStackTrace();
return;
}
// start the line 启动一个line
line.start();
// copy data to the line 拷贝输入流数据到line
try {
int numBytesRead = 0;
while (numBytesRead != -1) {
numBytesRead =
source.read(buffer, 0, buffer.length);
if (numBytesRead != -1) {
line.write(buffer, 0, numBytesRead);
}
}
}
catch (IOException ex) {
ex.printStackTrace();
}
// wait until all data is played 等待知道所有数据播放完毕
line.drain();
// close the line 关闭line
line.close();
}
}
In SimpleSoundPlayer, the getSamples(AudioInputStream) method reads from an AudioInputStream and stores the data in the samples byte array. The play() method reads data from an InputStream to a buffer and then writes the buffer to a SourceDataLine, which plays the sound. Also, the main() method in SimpleSoundPlayer tests the class by playing the voice.wav sound.
译:在 SimpleSoundPlayer 中,getSamples(AudioInputStream) 方法从AudioInputStream读取数据并保存到样本字节数组。play() 方法从一个 InputStream 读取数据到一个字节缓冲区然后将缓冲区中的数据写到一个 SourceDataLine 中,SourceDataLine 播放声音样本。而SimpleSoundPlayer 的main()方法通过播放 voice.wav 测试了本类。
Note that because of a bug in Java Sound, Java programs won't exit by themselves. Usually, the Java VM exits when there are only daemon threads running, but when you use Java Sound, a nondaemon thread always runs in the background. So, to exit your Java programs that use Java Sound, be sure to call System.exit(0).
译:注意,由于Java Sound 有一个缺陷,Java 程序不会自己退出。通常,Java 虚拟机在只有守护线程运行时退出。但使用 Java Sound 时,总是运行一个非守护后台线程。因此,要退出使用 Java Sound 的程序时一定要调用 System.exit(0)。
Well, you can play sounds yourself, but what if you want to play a sound repeatedly in a loop? This could be really useful for background ambient sounds or, say, for a buzzing fly.
译:好的,你可以播放声音了,但是如何在循环中重复播放声音呢?这个技巧对播放背景环境声音,如苍蝇的嗡嗡声。
To loop sound, you don't even need to make any changes to the SimpleSoundPlayer. Instead of using a ByteArrayInputStream, you'll create a LoopingByteInputStream in Listing 4.2, which works similarly to ByteArrayInputStream. The only difference is that LoopingByteInputStream indefinitely reads the byte array in a loop until its close() method is called.
译:要循环播放声音,你甚至不需要对SimpleSoundPlayer做任何的修改。你只需要创建一个清单4.2 中的 LoopingByteInputStream 来代替 ByteArrayInputStream 就可以了,它们两个用起来非常相似。唯一不同的是,LoopingByteInputStream在无限循环中不停地读字节数组的数据,直到它的 close() 方法被调用。
Listing 4.2 LoopingByteInputStream.java
package com.brackeen.javagamebook.util;
import java.io.ByteArrayInputStream;
import java.io.IOException;
/**
The LoopingByteInputStream is a ByteArrayInputStream that
loops indefinitely. The looping stops when the close() method
is called.
<p>Possible ideas to extend this class:<ul>
<li>Add an option to only loop a certain number of times.
</ul>
*/
public class LoopingByteInputStream extends ByteArrayInputStream {
private boolean closed;
/**
Creates a new LoopingByteInputStream with the specified
byte array. The array is not copied.
*/
public LoopingByteInputStream(byte[] buffer) {
super(buffer);
closed = false;
}
/**
Reads <code>length</code> bytes from the array. If the
end of the array is reached, the reading starts over from
the beginning of the array. Returns -1 if the array has
been closed.
*/
public int read(byte[] buffer, int offset, int length) {
if (closed) {
return -1;
}
int totalBytesRead = 0;
while (totalBytesRead < length) {
int numBytesRead = super.read(buffer,
offset + totalBytesRead,
length - totalBytesRead);
if (numBytesRead > 0) {
totalBytesRead += numBytesRead;
}
else {
reset();
}
}
return totalBytesRead;
}
/**
Closes the stream. Future calls to the read() methods
will return 1.
*/
public void close() throws IOException {
super.close();
closed = true;
}
}
There's nothing special about LoopingByteInputStream. It extends ByteArrayInputStream, and whenever the end of the stream is reached, it calls the reset() method to start reading from the beginning of the array again.
译:LoopingByteInputStream 没有什么特别的。它继承了ByteArrayInputStream 。当到达流的末尾时,就调用reset() 方法从数组的开头重新开始读取数据。
Now you can easily play and loop sound stored in a byte array. Also, because you have access to all the sound samples, you can manipulate the samples to create different effects, or filters
译:现在你可以很容易地播放和循环播放存储在一个字节数组中的声音。此外,因为您可以访问所有的声音样本,您可以操纵样本,以创建不同的效果,或过滤器。
学软件开发,到蜂鸟科技!
超强的师资力量 、完善的课程体系 、超低的培训价格 、真实的企业项目。
网址:www.ntcsoft.com
电话:0371-63839606
郑州软件开发兴趣小组群:38236716
posted on 2010-11-26 12:08
whistler 阅读(1007)
评论(0) 编辑 收藏