[译文]JOAL教程
原文地址:http://jogamp.org/joal-demos/www/devmaster/lesson3.html
原文作者:Athomas Goldberg
译文:三向板砖
转载请保留以上信息。
本节对应的连续代码页及学习笔记:http://blog.csdn.net/shuzhe66/article/details/40260861
第三课 多声源
本文是DevMaster.net(http://devmaster.net/)的OpenAL教程对应的JOAL版本。C语言版原文作者为JesseMaurais
大家好,距离上次发布教程也有一段时间了,但迟到总比没有要好,我猜大家都渴望着阅读新一期教程,于是我便着手去写了。
本期教程将教会大家如何同时播放多个音频。在很多激烈的游戏当中包含有各种各样的元素,在它们被触发时往往伴随着各类音效,这实现起来并不困难,处理多路音频与处理单路音频的方法极为相似。
import java.nio.ByteBuffer;
import java.util.Random;
import com.jogamp.openal.AL;
import com.jogamp.openal.ALFactory;
import com.jogamp.openal.util.ALut;
public class MultipleSources {
static AL al;
// 所需缓冲区的最大数量.
static final int NUM_BUFFERS = 3;
// 所需声源的最大数量
static final int NUM_SOURCES = 3;
// 下面三个变量标记了不同的声源
static final int BATTLE = 0;
static final int GUN1 = 1;
static final int GUN2 = 2;
// 容纳声音数据的缓冲区
static int[] buffers = new int[NUM_BUFFERS];
// 播放声音的点声源
static int[] sources = new int[NUM_SOURCES];
// 各个声源的位置
static float[][] sourcePos = new float[NUM_SOURCES][3];
// 各个声源的速度
static float[][] sourceVel = new float[NUM_SOURCES][3];
// 听众的位置
static float[] listenerPos = { 0.0f, 0.0f, 0.0f };
// 听众的速度
static float[] listenerVel = { 0.0f, 0.0f, 0.0f };
// 听众的朝向
static float[] listenerOri = { 0.0f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f };
我相信上面这段代码对于看过前两个章节的人来讲会非常熟悉,唯一的不同在于我们需要将三种不同的音效装入OpenAL系统中。
static int loadALData() {
//需要填装的值
int[] format = new int[1];
int[] size = new int[1];
ByteBuffer[] data = new ByteBuffer[1];
int[] freq = new int[1];
int[] loop = new int[1];
// 将wav文件读入缓冲区
al.alGenBuffers(NUM_BUFFERS, buffers, 0);
if (al.alGetError() != AL.AL_NO_ERROR) {
return AL.AL_FALSE;
}
ALut.alutLoadWAVFile(
"wavdata/Battle.wav",
format,
data,
size,
freq,
loop);
al.alBufferData(
buffers[BATTLE],
format[0],
data[0],
size[0],
freq[0]);
ALut.alutLoadWAVFile(
"wavdata/Gun1.wav",
format,
data,
size,
freq,
loop);
al.alBufferData(
buffers[GUN1],
format[0],
data[0],
size[0],
freq[0]);
ALut.alutLoadWAVFile(
"wavdata/Gun2.wav",
format,
data,
size,
freq,
loop);
al.alBufferData(
buffers[GUN2],
format[0],
data[0],
size[0],
freq[0]);
//将缓冲区与声源绑定
al.alGenSources(NUM_SOURCES, sources, 0);
al.alSourcei(sources[BATTLE], AL.AL_BUFFER, buffers[BATTLE]);
al.alSourcef(sources[BATTLE], AL.AL_PITCH, 1.0f);
al.alSourcef(sources[BATTLE], AL.AL_GAIN, 1.0f);
al.alSourcefv(sources[BATTLE], AL.AL_POSITION, sourcePos[BATTLE], 0);
al.alSourcefv(sources[BATTLE], AL.AL_VELOCITY, sourceVel[BATTLE], 0);
al.alSourcei(sources[BATTLE], AL.AL_LOOPING, AL.AL_TRUE);
al.alSourcei(sources[GUN1], AL.AL_BUFFER, buffers[GUN1]);
al.alSourcef(sources[GUN1], AL.AL_PITCH, 1.0f);
al.alSourcef(sources[GUN1], AL.AL_GAIN, 1.0f);
al.alSourcefv(sources[GUN1], AL.AL_POSITION, sourcePos[GUN1], 0);
al.alSourcefv(sources[GUN1], AL.AL_VELOCITY, sourceVel[GUN1], 0);
al.alSourcei(sources[GUN1], AL.AL_LOOPING, AL.AL_FALSE);
al.alSourcei(sources[GUN2], AL.AL_BUFFER, buffers[GUN2]);
al.alSourcef(sources[GUN2], AL.AL_PITCH, 1.0f);
al.alSourcef(sources[GUN2], AL.AL_GAIN, 1.0f);
al.alSourcefv(sources[GUN2], AL.AL_POSITION, sourcePos[GUN2], 0);
al.alSourcefv(sources[GUN2], AL.AL_VELOCITY, sourceVel[GUN2], 0);
al.alSourcei(sources[GUN2], AL.AL_LOOPING, AL.AL_FALSE);
// 检查错误并返回结果
if (al.alGetError() != AL.AL_NO_ERROR) {
return AL.AL_FALSE;
}
return AL.AL_TRUE;
}
上面的代码看起来与之前有些许不同,实际上并不是这样。大体上讲,我们只是将三个音频文件装载到缓冲区中,并将三个缓冲区与对应的声源绑定起来。唯一一点不同是Battle.wav(对应source[0])被设定为循环播放而其它两个音频则没有。
static void setListenerValues() {
al.alListenerfv(AL.AL_POSITION, listenerPos, 0);
al.alListenerfv(AL.AL_VELOCITY, listenerVel, 0);
al.alListenerfv(AL.AL_ORIENTATION, listenerOri, 0);
}
static void killAllData() {
al.alDeleteBuffers(NUM_BUFFERS, buffers, 0);
al.alDeleteSources(NUM_SOURCES, sources, 0);
ALut.alutExit();
}
上面的代码与之前相比没有什么不同。
public static void main(String[] args) {
al = ALFactory.getAL();
//初始化OpenAL,并将错误标记重置
ALut.alutInit();
al.alGetError();
//装载音频数据.
if(loadALData() == AL.AL_FALSE) {
System.exit(1);
}
setListenerValues();
//播放“战斗”的背景音乐
al.alSourcePlay(sources[BATTLE]);
long startTime = System.currentTimeMillis();
long elapsed = 0;
long totalElapsed = 0;
Random rand = new Random();
int[] state = new int[1];
while (totalElapsed < 10000) {
elapsed = System.currentTimeMillis() - startTime;
if (elapsed > 50) {
totalElapsed += elapsed;
startTime = System.currentTimeMillis();
//随机的选择除背景乐外的两个音效之一,判断它是否在播放中
int pick = Math.abs((rand.nextInt()) % 2) + 1;
al.alGetSourcei(sources[pick], AL.AL_SOURCE_STATE, state, 0);
if (state[0] != AL.AL_PLAYING) {
//在听众周围选择一个随机的位置播放声音
double theta = (rand.nextInt() % 360) * 3.14 / 180.0;
sourcePos[pick][0] = - ((float) Math.cos(theta));
sourcePos[pick][1] = - ((float) (rand.nextInt() % 2));
sourcePos[pick][2] = - ((float) Math.sin(theta));
al.alSourcefv(
sources[pick],
AL.AL_POSITION,
sourcePos[pick], 0);
al.alSourcePlay(sources[pick]);
}
}
}
killAllData();
}
}
这里便是本次教程中最有趣的部分,我们一次检测各个声源是否处于播放当中,对没有播放的声源下达播放命令,而且我们在播放前为其设定了一个三维空间中的随机新位置(这样仅仅是为了好玩~)。
最后我们运行它,伴随着混合音效,我们成功了!大部分读者可能已经意识到,为了让声源一起播放,我们仅仅下达了命令而未做什么特别的事情,OpenAL已经处理了混音部分,并让实际播放的声音与声源、听众的位置、速度相符,这难道不是OpenAL的魅力所在吗?
这是如此的简单,以至于我自己都怀疑为什么本篇教程会拖这么久。如果读者朋友们想要一些其它特别的教程(不一定是OpenAL,我的知识量是很庞大的)可以通过lightonthewater@hotmail.com联系我,不出意外的话,我打算在未来的教程部分讲解缓冲区共享与多普勒效应的有关内容,祝大家编码愉快!