[译文]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联系我,不出意外的话,我打算在未来的教程部分讲解缓冲区共享与多普勒效应的有关内容,祝大家编码愉快!
原文地址:http://blog.csdn.net/shuzhe66/article/details/40260637