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 閱讀(1020)
評論(0) 編輯 收藏