The Borg Matrix, Economics Modelling, Financial Management, Strategic planning and PC gaming for windows and Linux

AQ2 LTKTBM Edition
Google
openAL-tutorial-for-Q2E
Not Logged in : Login | Register

Developer Resources - q2a3d implimentation turial for Q2E/Q2MAX

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.

 

Main +
Media +
Downloads +
News +
Forums
IRC
Plans
Developers +
Game Engine +
Ragdol Physics +
Secure Client Identification
openAL +
openAL tutorial for Q2E
openAL/eax developement discussion
Water Engine
Engine source request
Artificial Intelligence +

Word of the week
cortege
a funeral procession
 

Respect to:
modDB.com - every game, every mod, one site... go figure!

Action Games
AQ2World
AQMD

Supporting:

Against TCPA NØ2ID

Donate: