Introduction:
Q2A3D is an openAL (open Audio
Layer) sound implimentation for Quake2 based engines.
However it is also intended to be as portable as possible,
automating many comman audio tasks and providing seemless
integratiion into any game.
OpenAL is an LGPL audio platform
compatible with windows/linux/mac which provides access
advanced audio functions such as multi speaker systems,
EAX extensions and much more. It is hardware independant
and similar in implimetation to the OpenGL graphics
library. You can find out more on creatives developer
site @ developer.creative.com.
In contrast, Q2A3D is designed
to be similar in implimentation to Q2's ref_soft or
ref_gl graphics engines. providing a simple inteface
for the game engine to issue sounds which are then controled
by the q2a3d audio engine. Below you will find code
& snippets to impliment this audio engine into the
Q2, and Q2E (Q2Max based?) game engines.
Q2E (Q2Max?) coding:
Requirements:
Q2E
(and Q2Max?) engine specific source code.
The
latest Q2A3D & OpenAL
audio engines.
Implimentation:
First up, you need to add the
engine specific source files into the Q2 project, this
provides Q2A3D implimentations of the Q2 sound functions
(like S_registersound and S_startsound, along with pickchannel
for looping sounds). Extract the Q2E specific code into
a new 'a3d' directory in the main directory (e.g. /q2e
0.23/a3d/q2a3d.c, /q2e 0.23/a3d/q2a3d.h)
along with the q2a3d library file
and header ( /q2e 0.23/a3d/q2a3d.lib
and /q2e 0.23/a3d/a3d.h)
Next, add the Q2A3D audio engine
functions into the build environment, in MS VC++ 6 this
is done by clicking Project->Settings, selecting
the quake2 project, clicking the 'LINK' tab on the right
and adding './a3d/q2a3d.lib'to
the "Object/Library Modules " (in this case
after winmm.lib ). Then add the q2a3d.c and q2a3d.h
to the project using the fileview window.
Adding the code:
Open snd_local.h,
First, add the Id variable to
the sound effects structure:
typedef struct sfx_s
{
char name[MAX_QPATH];
char trueName[MAX_QPATH];
sfxCache_t *cache;
//A3D ADD
int Id;
} sfx_t;
This is used to control and manipulate
continuous (looping/autosounds) as the audio environment
changes.
Next add a copy of the
listener_tstructure and a global snd_listener.
} dma_t;
// =====================================================================
// snd_dma.c
//A3D ADD
typedef struct {
vec3_t origin;
vec3_t axis[3];
} listener_t;
extern listener_t snd_listener;
//A3D ADD END
extern playSound_t snd_pendingPlays;
Open sound.h,
and at the very top add the q2a3d engine function definitions:
//A3D ADD
#include "../a3d/a3d.h"
Open snd_dma.c,
At the top find the line
#include "snd_local.h"and
add the engine specific function header:
#include "snd_local.h"
//A3D ADD
#include "../a3d/q2a3d.h"
This declares all the functions
we will use, along with the s_a3d cvar to enable/disable
the q2a3d engine.
Next, lower down, comment out
the listener_t structure
definition, as described above this is to be moved to
snd_local.h to be used
in the engine specific q2a3d code.
#define SND_LOOPATTENUATE
0.003
//A3D CHANGE - moved to snd_local.h
/* typedef struct {
vec3_t origin;
vec3_t axis[3];
} listener_t; */
//A3D CHANGE END
now remove the static declaration
for snd_listener, since
its now a global variable it is no longer needed.
//A3D CHANGE
/*static*/ listener_t snd_listener;
Move down to the S_RegisterSound function, and add in the A3D replacement,
so it looks like this:
sfx_t *S_RegisterSound
(const char *name){
sfx_t *sfx;
//A3D ADD
if(a3dsound_started)
return S_Q2A3DRegisterSound (name);
//A3D END
if (!snd_initialized)
return NULL;
Repeat for S_PickChannel:
channel_t *S_PickChannel
(int entNum, int entChannel){
channel_t *ch;
int chIdx;
int firstToDie = -1;
int lifeLeft = 0x7fffffff;
//A3D ADD
if(a3dsound_started)
return S_Q2A3DPickChannel(entNum, entChannel);
//A3D END
if (entChannel <
0)
Now move down to S_Startsound.
Here we need to add a check whether the a3d sound engine
has been started, and add the replacement function call.
void S_StartSound
(const vec3_t origin, int entNum, int entChannel,
sfx_t *sfx, float volume, float attenuation, float
timeOfs){
sfxCache_t *sc;
playSound_t *ps, *sort;
int start;
//A3D CHANGE
if (!snd_initialized && !a3dsound_started)
return;
//A3D CHANGE END
if (!sfx)
return;
if (sfx->name[0]
== '*')
sfx = S_RegisterSexedSound(&cl.entities[entNum].current,
sfx->name);
//A3D ADD
if (a3dsound_started)
{
S_Q2A3DStartSound(origin,entNum,entChannel,sfx,volume,attenuation,timeOfs);
return;
}
//A3D END
// Make sure the sound is loaded
sc = S_LoadSound(sfx);
Now add the check for a3d to
S_StartLocalSound
void S_StartLocalSound
(sfx_t *sfx){
//A3D CHANGE
if (!snd_initialized && !a3dsound_started)
return;
//A3D CHANGE END
if (!sfx)
return;
Now move to S_StopAllSoundsand change it as follows
void S_StopAllSounds
(void){
int i;
//A3D CHANGE
if(a3dsound_started)
S_Q2A3DStopAllSounds();
if (!snd_initialized
&& !a3dsound_started )
return;
// Clear all the
playsounds
memset(snd_playSounds, 0, sizeof(snd_playSounds));
snd_freePlays.next = snd_freePlays.prev = &snd_freePlays;
snd_pendingPlays.next = snd_pendingPlays.prev = &snd_pendingPlays;
for (i = 0; i <
MAX_PLAYSOUNDS; i++){
snd_playSounds[i].prev = &snd_freePlays;
snd_playSounds[i].next = snd_freePlays.next;
snd_playSounds[i].prev->next = &snd_playSounds[i];
snd_playSounds[i].next->prev = &snd_playSounds[i];
}
// Clear all the
channels
memset(snd_channels, 0, sizeof(snd_channels));
// Stop the background track
S_StopBackgroundTrack();
snd_rawEnd = 0;
snd_soundTime =
0;
snd_paintedTime = 0;
if(a3dsound_started)
return;
S_ClearBuffer();
}//A3D CHANGE END
This makes sure all the sounds
are stopped, and S_Clearbuffer isn't run when the a3d
engine is active.
Next move to S_FreeSounds
and prevent it being run if the a3d engine is active.
int i;
//A3D ADD
if(a3dsound_started)
return;
// Free all sounds
Currently, there is no implimentation
for 'freeing' sounds as such with Q2A3D, it just has
a set cache of datafiles, which it fills and replaces
and frees as nessecery.
Next move to
S_update and add the Q2A3D replacement.
int samps;
//A3D ADD
if (a3dsound_started)
{
S_Q2A3DUpdate(origin,axis[0],axis[1],axis[2]);
return;
}
//A3D END
if (!snd_initialized)
return;
This Function is quite deceptive
with the scope it actually covers. Q2 initializes sounds
which continually loop (such as entity noises like the
blaster fizz) as 'autosounds' this function updates
the audio environement so these sounds are placed correctly
based on the listener position and orientation. Q2A3D
tags these sounds with 'id's' which automatically initiates
them as as looping sounds and allows them to be updated.
This id 'tagging' is handled jointly by S_startsound
and S_pickchannel.
Now, onto the initialization
code. Move down futher still to S_Init
and add the s_a3d cvar initialization:
s_testSound = Cvar_Get("s_testSound",
"0", CVAR_CHEAT);
//A3D ADD
s_a3d = Cvar_Get ("s_a3d", "0",
CVAR_ARCHIVE); //sound engine
Cmd_AddCommand("play",
S_Play_f);
Still in S_Init, add the call
to initialize q2a3d if required:
Cmd_AddCommand("snd_restart",
S_Restart_f);
//A3D CHANGE
if(s_a3d->integer)
{
S_Q2A3DInit();
}
else
{
if (!S_InitDMA()){
Com_Printf("------------------------------------\n");
return;
}
snd_initialized = true;
S_SoundInfo_f();
}
S_InitScaleTable();
// num_sfx = 0;
// soundtime = 0;
// paintedtime = 0;
//A3D CHANGE END
S_StopAllSounds();
Finally forsnd_dma.c
move down a few lines to S_Shutdown
and add the call to shutdown Q2A3D if required:
void S_Shutdown (void){
//A3D CHANGE
if(a3dsound_started)
A3D_Shutdown();
a3dsound_started = 0;
//A3D CHANGE END
Cmd_RemoveCommand("play");
Now, just a few minor changes
to snd_mem.c:
Firstly, add the engine specific
header file:
#include "snd_local.h"
//A3D ADD
#include "../a3d/q2a3d.h"
Last, but by no means least,
move to S_LoadSound and
prevent Q2 from loading the sound:
int length;
//A3D CHANGE
if (sfx->name[0] == '*' || a3dsound_started)
return NULL;
//A3D CHANGE END
// See if still in memory
if (sfx->cache)
You can download
direct replacement source files (snd_mem.c and snd_dma.c)
for Q2E v0.23 from
here. I believe this tutorial + source should work equally
well with Q2Max based engines although I have not tried
it myself. You will have to make the changes to snd_local.h
and sound.h yourself. |