openmohaa/code/client/snd_openal_new.cpp
smallmodel f5d2fdbcad
Return an empty string if the channel is neither playing nor paused
It was only checking if the channel was playing. This was an issue because, in the main menu the music is paused, which means saving from main menu would not save the music filename
2024-11-05 22:16:15 +01:00

4908 lines
124 KiB
C++

/*
===========================================================================
Copyright (C) 2024 the OpenMoHAA team
This file is part of OpenMoHAA source code.
OpenMoHAA source code is free software; you can redistribute it
and/or modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.
OpenMoHAA source code is distributed in the hope that it will be
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OpenMoHAA source code; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
===========================================================================
*/
#include "snd_local.h"
#include "snd_openal_new.h"
#include "client.h"
#include "../server/server.h"
#include "snd_codec.h"
#if defined(_MSC_VER) || defined(__APPLE__)
# include <alext.h>
#else
# include <AL/alext.h>
#endif
typedef struct {
const char *funcname;
void **funcptr;
bool required;
} extensions_table_t;
#define MAX_MUSIC_SONGS 16
static int s_iNextLoopingWarning = 0;
static int s_iReverbType = 0;
static float s_fReverbLevel = 0;
static bool s_bReverbChanged = false;
static bool s_bFading = false;
static float s_fFadeVolume = 1.f;
static float s_fVelocityScale = 0.005f;
static constexpr float s_fVolumeGain = 1.f; // = 84.f;
cvar_t *s_milesdriver;
cvar_t *s_openaldevice;
cvar_t *s_reverb;
cvar_t *s_show_cpu;
cvar_t *s_show_num_active_sounds;
cvar_t *s_show_sounds;
cvar_t *s_speaker_type;
cvar_t *s_obstruction_cal_time;
cvar_t *s_lastSoundTime;
// Added in OPM
cvar_t *s_openaldriver;
cvar_t *s_alAvailableDevices;
static float reverb_table[] = {
0.5f, 0.25f, 0.417f, 0.653f, 0.208f, 0.5f, 0.403f, 0.5f, 0.5f,
0.361f, 0.5f, 0.153f, 0.361f, 0.44400001f, 0.25f, 0.111f, 0.111f, 0.19400001f,
1.0f, 0.097000003f, 0.208f, 0.65200001f, 1.0f, 0.875f, 0.139f, 0.486f,
};
static vec3_t vec_zero = {0, 0, 0};
int s_iNumMilesAudioProviders = 0;
bool s_bProvidersEmunerated = false;
static bool al_initialized = false;
static bool al_use_reverb = false;
static float al_current_volume = 0;
static unsigned int al_frequency = 22050;
static ALCcontext *al_context_id = NULL;
static ALCdevice *al_device = NULL;
static ALboolean (*_alutLoadMP3_LOKI)(unsigned int buffer, const byte *data, int length);
static void (*_alReverbScale_LOKI)();
static void (*_alReverbDelay_LOKI)();
static qboolean music_active = qfalse;
int music_current_mood = 0;
int music_fallback_mood = 0;
float old_music_volume = 1.f;
float music_volume = 1.f;
float new_music_volume = 1.f;
float music_volume_fade_time = 0;
long int music_volume_start_time = 0;
int music_volume_direction = 0;
int music_volume_changed = 0;
int music_loaded = 0;
int music_numsongs = 0;
int music_currentsong = 0;
static qboolean enumeration_ext = qfalse;
static qboolean enumeration_all_ext = qfalse;
song_t music_songs[MAX_MUSIC_SONGS];
openal_internal_t openal;
static float s_fFadeStartTime;
static float s_fFadeStopTime;
static char current_soundtrack[128];
static void S_OPENAL_PlayMP3();
static void S_OPENAL_StopMP3();
static void S_OPENAL_Pitch();
static int
S_OPENAL_SpatializeStereoSound(const vec3_t listener_origin, const vec3_t listener_left, const vec3_t origin);
static void S_OPENAL_reverb(int iChannel, int iReverbType, float fReverbLevel);
static bool S_OPENAL_LoadMP3_Codec(const char *_path, sfx_t *pSfx);
static ALuint S_OPENAL_Format(int width, int channels);
#define alDieIfError() __alDieIfError(__FILE__, __LINE__)
#if defined(_WIN64)
# define ALDRIVER_DEFAULT "OpenAL64.dll"
#elif defined(_WIN32)
# define ALDRIVER_DEFAULT "OpenAL32.dll"
#elif defined(__APPLE__)
# define ALDRIVER_DEFAULT "libopenal.1.dylib"
#elif defined(__OpenBSD__)
# define ALDRIVER_DEFAULT "libopenal.so"
#else
# define ALDRIVER_DEFAULT "libopenal.so.1"
#endif
/*
==============
__alDieIfError
==============
*/
static void __alDieIfError(const char *file, int line)
{
ALint alErr = qalGetError();
if (alErr) {
if (s_show_sounds->integer) {
Com_DPrintf("OpenAL error, %s, line %i: [%s].\n", file, line, qalGetString(alErr));
}
}
}
/*
==============
S_OPENAL_NukeSource
==============
*/
static void S_OPENAL_NukeSource(ALuint *srcptr)
{
ALuint source;
source = *srcptr;
if (!*srcptr) {
return;
}
if (!qalIsSource(*srcptr)) {
return;
}
alDieIfError();
qalSourceStop(source);
alDieIfError();
qalSourcei(source, AL_BUFFER, 0);
alDieIfError();
qalDeleteSources(1, srcptr);
alDieIfError();
*srcptr = 0;
}
/*
==============
S_OPENAL_NukeBuffer
==============
*/
static void S_OPENAL_NukeBuffer(ALuint *bufptr)
{
if (!*bufptr) {
return;
}
if (!qalIsBuffer(*bufptr)) {
return;
}
alDieIfError();
qalDeleteBuffers(1, bufptr);
alDieIfError();
*bufptr = 0;
}
/*
==============
S_OPENAL_NukeChannel
==============
*/
static void S_OPENAL_NukeChannel(openal_channel *channel)
{
if (!channel) {
return;
}
S_OPENAL_NukeSource(&channel->source);
S_OPENAL_NukeBuffer(&channel->buffer);
if (channel->bufferdata) {
delete[] channel->bufferdata;
}
}
/*
==============
S_OPENAL_NukeContext
==============
*/
static void S_OPENAL_NukeContext()
{
int i;
Com_Printf("OpenAL: Destroying channels...\n");
for (i = 0; i < MAX_OPENAL_CHANNELS; ++i) {
S_OPENAL_NukeChannel(openal.channel[i]);
}
Com_Printf("OpenAL: Channels destroyed successfully.\n");
for (i = 0; i < s_numSfx; i++) {
S_OPENAL_NukeBuffer(&s_knownSfx[i].buffer);
}
S_OPENAL_NukeBuffer(&openal.movieSFX.buffer);
if (al_context_id) {
Com_Printf("OpenAL: Destroying context...\n");
qalcSuspendContext(al_context_id);
qalcMakeContextCurrent(NULL);
qalcDestroyContext(al_context_id);
al_context_id = NULL;
Com_Printf("OpenAL: Context destroyed successfully.\n");
}
if (al_device) {
Com_Printf("OpenAL: Closing device...\n");
qalcCloseDevice(al_device);
al_device = NULL;
Com_Printf("OpenAL: Device closed successfully.\n");
}
}
/*
==============
S_OPENAL_InitContext
==============
*/
static bool S_OPENAL_InitContext()
{
const char *dev;
int attrlist[8];
Com_DPrintf("OpenAL: Context initialization\n");
dev = NULL;
if (s_openaldevice) {
dev = s_openaldevice->string;
}
if (dev && !*dev) {
dev = NULL;
}
// Device enumeration support
enumeration_all_ext = qalcIsExtensionPresent(NULL, "ALC_ENUMERATE_ALL_EXT");
enumeration_ext = qalcIsExtensionPresent(NULL, "ALC_ENUMERATION_EXT");
if (enumeration_ext || enumeration_all_ext) {
char devicenames[16384] = "";
const char *devicelist;
#ifdef _WIN32
const char *defaultdevice;
#endif
int curlen;
// get all available devices + the default device name.
if (enumeration_all_ext) {
devicelist = qalcGetString(NULL, ALC_ALL_DEVICES_SPECIFIER);
#ifdef _WIN32
defaultdevice = qalcGetString(NULL, ALC_DEFAULT_ALL_DEVICES_SPECIFIER);
#endif
} else {
// We don't have ALC_ENUMERATE_ALL_EXT but normal enumeration.
devicelist = qalcGetString(NULL, ALC_DEVICE_SPECIFIER);
#ifdef _WIN32
defaultdevice = qalcGetString(NULL, ALC_DEFAULT_DEVICE_SPECIFIER);
#endif
enumeration_ext = qtrue;
}
#ifdef _WIN32
Com_DPrintf("OpenAL: Default playback device: \"%s\"\n", defaultdevice);
#endif
#ifdef _WIN32
// check whether the default device is generic hardware. If it is, change to
// Generic Software as that one works more reliably with various sound systems.
// If it's not, use OpenAL's default selection as we don't want to ignore
// native hardware acceleration.
if (!dev && defaultdevice && !strcmp(defaultdevice, "Generic Hardware")) {
dev = "Generic Software";
}
#endif
// dump a list of available devices to a cvar for the user to see.
if (devicelist) {
while ((curlen = strlen(devicelist))) {
Q_strcat(devicenames, sizeof(devicenames), devicelist);
Q_strcat(devicenames, sizeof(devicenames), "\n");
devicelist += curlen + 1;
}
}
Com_Printf("OpenAL: List of available devices:\n%s\n", devicenames);
s_alAvailableDevices = Cvar_Get("s_alAvailableDevices", devicenames, CVAR_ROM | CVAR_NORESTART);
}
Com_Printf("OpenAL: Opening device \"%s\"...\n", dev ? dev : "{default}");
al_device = qalcOpenDevice(dev);
if (!al_device && dev) {
Com_Printf("Failed to open OpenAL device '%s', trying default.\n", dev);
al_device = qalcOpenDevice(NULL);
}
if (!al_device) {
Com_Printf("OpenAL: Could not open device\n");
S_OPENAL_NukeContext();
return false;
}
Com_Printf("OpenAL: Device opened successfully.\n");
switch (s_khz->integer) {
case 11:
al_frequency = 11025;
break;
default:
case 22:
al_frequency = 22050;
break;
case 44:
al_frequency = 44100;
break;
}
attrlist[0] = ALC_FREQUENCY;
attrlist[1] = al_frequency;
attrlist[2] = ALC_SYNC;
attrlist[3] = qfalse;
s_speaker_type->modified = false;
#ifdef ALC_SOFT_output_mode
attrlist[4] = ALC_OUTPUT_MODE_SOFT;
switch (s_speaker_type->integer) {
// Two speakers
default:
case 0:
attrlist[5] = ALC_STEREO_SOFT;
break;
// Headphones
case 1:
attrlist[5] = ALC_STEREO_HRTF_SOFT;
break;
// Surround
case 2:
attrlist[5] = ALC_SURROUND_5_1_SOFT;
break;
// Quad speakers
case 3:
attrlist[5] = ALC_QUAD_SOFT;
break;
}
#else
# pragma message("OpenAL: ALC_OUTPUT_MODE_SOFT unavailable")
Com_Printf(
"OpenAL: ALC_OUTPUT_MODE_SOFT is unavailable. The speaker type will be ignored, fallback to normal stereo.\n"
);
attrlist[4] = 0;
attrlist[5] = 0;
#endif
attrlist[6] = 0;
attrlist[7] = 0;
Com_Printf("OpenAL: Creating AL context...\n");
al_context_id = qalcCreateContext(al_device, attrlist);
if (!al_context_id) {
Com_Printf("OpenAL: Could not create context\n");
S_OPENAL_NukeContext();
return false;
}
Com_Printf("OpenAL: Context created successfully.\n");
qalcMakeContextCurrent(al_context_id);
alDieIfError();
Com_Printf("AL_VENDOR: %s\n", qalGetString(AL_VENDOR));
alDieIfError();
Com_Printf("AL_VERSION: %s\n", qalGetString(AL_VERSION));
alDieIfError();
Com_Printf("AL_RENDERER: %s\n", qalGetString(AL_RENDERER));
alDieIfError();
Com_Printf("AL_EXTENSIONS: %s\n", qalGetString(AL_EXTENSIONS));
alDieIfError();
qalDistanceModel(AL_INVERSE_DISTANCE_CLAMPED);
alDieIfError();
qalcProcessContext(al_context_id);
alDieIfError();
return true;
}
/*
==============
S_OPENAL_InitExtensions
==============
*/
static bool S_OPENAL_InitExtensions()
{
Com_Printf("AL extensions ignored\n");
return true;
extensions_table_t extensions_table[4] = {
"alutLoadMP3_LOKI",
(void **)&_alutLoadMP3_LOKI,
true,
"alReverbScale_LOKI",
(void **)&_alReverbScale_LOKI,
true,
"alReverbDelay_LOKI",
(void **)&_alReverbDelay_LOKI,
true
};
extensions_table_t *i;
for (i = extensions_table; i->funcname; ++i) {
*i->funcptr = NULL;
}
for (i = extensions_table; i->funcname; ++i) {
Com_Printf("AL extension: Looking up %ssymbol \"%s\"...", i->required ? "required " : "", i->funcname);
*i->funcptr = qalGetProcAddress(i->funcname);
if (!*i->funcptr) {
Com_Printf("...not found! %d [%s]\n", qalGetError(), qalGetString(qalGetError()));
if (i->required) {
S_OPENAL_NukeContext();
return false;
}
continue;
}
Com_Printf("...found.\n");
}
qalGetError();
return true;
}
/*
==============
S_OPENAL_InitChannel
==============
*/
static bool S_OPENAL_InitChannel(int idx, openal_channel *chan)
{
openal.channel[idx] = chan;
VectorClear(chan->vOrigin);
chan->fVolume = 1.0;
chan->fNewPitchMult = 0.0;
chan->fMinDist = 0.0;
chan->fMaxDist = 0.0;
chan->pSfx = 0;
chan->iEntNum = 0;
chan->iEntChannel = 0;
chan->iBaseRate = 0;
chan->iStartTime = 0;
chan->iTime = 0;
chan->iEndTime = 0;
chan->iFlags = 0;
chan->iPausedOffset = 0;
chan->source = 0;
chan->buffer = 0;
chan->bufferdata = 0;
chan->fading = FADE_NONE;
chan->fade_time = 0;
chan->fade_start_time = 0;
chan->song_number = 0;
qalGenSources(1, &chan->source);
alDieIfError();
qalSourcei(chan->source, AL_SOURCE_RELATIVE, true);
alDieIfError();
return true;
}
/*
==============
S_OPENAL_Init
==============
*/
qboolean S_OPENAL_Init()
{
int i;
if (al_initialized) {
Com_DPrintf("S_OPENAL_Init(): Called when sound is already initialized!\n");
return qtrue;
}
for (i = 0; i < MAX_OPENAL_CHANNELS; i++) {
openal.channel[i] = NULL;
}
for (i = 0; i < MAX_OPENAL_LOOP_SOUNDS; i++) {
openal.loop_sounds[i] = {};
}
s_milesdriver = Cvar_Get("s_milesdriver", "auto", CVAR_SOUND_LATCH | CVAR_ARCHIVE);
s_openaldevice = Cvar_Get("s_openaldevice", "", CVAR_SOUND_LATCH);
s_reverb = Cvar_Get("s_reverb", "0", CVAR_SOUND_LATCH | CVAR_ARCHIVE);
s_show_cpu = Cvar_Get("s_show_cpu", "0", 0);
s_show_num_active_sounds = Cvar_Get("s_show_num_active_sounds", "0", 0);
s_show_sounds = Cvar_Get("s_show_sounds", "0", 0);
s_speaker_type = Cvar_Get("s_speaker_type", "0", CVAR_ARCHIVE);
s_obstruction_cal_time = Cvar_Get("s_obstruction_cal_time", "500", CVAR_ARCHIVE);
//
// Added in OPM
// Initialize the AL driver DLL
s_openaldriver = Cvar_Get("s_openaldriver", ALDRIVER_DEFAULT, CVAR_LATCH | CVAR_PROTECTED);
if (!QAL_Init(s_openaldriver->string)) {
Com_Printf("Failed to load library: \"%s\".\n", s_openaldriver->string);
if (!Q_stricmp(s_openaldriver->string, ALDRIVER_DEFAULT) || !QAL_Init(ALDRIVER_DEFAULT)) {
return qfalse;
}
}
if (!Cvar_Get("s_initsound", "1", 0)->integer) {
Com_Printf("OpenAL: s_initsound set to zero...disabling audio.\n");
return true;
}
if (!S_OPENAL_InitContext()) {
Com_Printf("OpenAL: initialization failed. No audio will play.\n");
return false;
}
if (!S_OPENAL_InitExtensions()) {
Com_Printf("OpenAL: A required extension is missing. No audio will play.\n");
return false;
}
al_use_reverb = false;
if (s_reverb->integer) {
STUB_DESC("reenable reverb support later.");
if (al_use_reverb) {
S_OPENAL_SetReverb(s_iReverbType, s_fReverbLevel);
} else {
Com_Printf("OpenAL: No reverb support. Reverb is disabled.\n");
}
}
al_current_volume = Square(s_volume->value);
qalListenerf(AL_GAIN, al_current_volume);
alDieIfError();
for (i = 0; i < MAX_OPENAL_CHANNELS_3D; i++) {
if (!S_OPENAL_InitChannel(i, &openal.chan_3D[i])) {
return false;
}
}
for (i = 0; i < MAX_OPENAL_CHANNELS_2D; i++) {
if (!S_OPENAL_InitChannel(i + MAX_OPENAL_CHANNELS_3D, &openal.chan_2D[i])) {
return false;
}
}
for (i = 0; i < MAX_OPENAL_CHANNELS_2D_STREAM; i++) {
if (!S_OPENAL_InitChannel(i + MAX_OPENAL_CHANNELS_3D + MAX_OPENAL_CHANNELS_2D, &openal.chan_2D_stream[i])) {
return false;
}
}
for (i = 0; i < MAX_OPENAL_SONGS; i++) {
if (!S_OPENAL_InitChannel(
i + MAX_OPENAL_CHANNELS_3D + MAX_OPENAL_CHANNELS_2D + MAX_OPENAL_CHANNELS_2D_STREAM,
&openal.chan_song[i]
)) {
return false;
}
}
if (!S_OPENAL_InitChannel(OPENAL_CHANNEL_MP3_ID, &openal.chan_mp3)) {
return false;
}
if (!S_OPENAL_InitChannel(OPENAL_CHANNEL_TRIGGER_MUSIC_ID, &openal.chan_trig_music)) {
return false;
}
if (!S_OPENAL_InitChannel(OPENAL_CHANNEL_MOVIE_ID, &openal.chan_movie)) {
return false;
}
Cmd_AddCommand("playmp3", S_OPENAL_PlayMP3);
Cmd_AddCommand("stopmp3", S_OPENAL_StopMP3);
Cmd_AddCommand("loadsoundtrack", S_loadsoundtrack);
Cmd_AddCommand("playsong", S_PlaySong);
Cmd_AddCommand("pitch", S_OPENAL_Pitch);
Cmd_AddCommand("tmstart", S_TriggeredMusic_Start);
Cmd_AddCommand("tmstartloop", S_TriggeredMusic_StartLoop);
Cmd_AddCommand("tmstop", S_TriggeredMusic_Stop);
S_OPENAL_ClearLoopingSounds();
load_sfx_info();
s_bProvidersEmunerated = true;
al_initialized = true;
// Added in OPM
S_CodecInit();
return true;
}
/*
==============
S_OPENAL_Shutdown
==============
*/
void S_OPENAL_Shutdown()
{
if (!al_initialized) {
Com_DPrintf("S_OPENAL_Shutdown(): Called when sound is NOT initialized!\n");
return;
}
S_OPENAL_StopAllSounds(true);
Cmd_RemoveCommand("playmp3");
Cmd_RemoveCommand("stopmp3");
Cmd_RemoveCommand("loadsoundtrack");
Cmd_RemoveCommand("playsong");
Cmd_RemoveCommand("pitch");
Cmd_RemoveCommand("tmstart");
Cmd_RemoveCommand("tmstartloop");
Cmd_RemoveCommand("tmstop");
S_OPENAL_NukeContext();
s_bProvidersEmunerated = false;
al_initialized = false;
}
/*
==============
S_FadeSound
==============
*/
void S_FadeSound(float fTime)
{
Com_Printf("Called FadeSound with: %f\n", fTime);
if (fTime > 0) {
s_bFading = true;
s_fFadeStartTime = cls.realtime;
s_fFadeVolume = 1;
s_fFadeStopTime = cls.realtime + fTime;
} else {
s_fFadeVolume = 1;
s_bFading = false;
// Fixed in OPM
// Make sure to restore the music volume
// in case the map restarts immediately after the intermission
music_volume_changed = true;
}
}
/*
==============
S_GetBaseVolume
==============
*/
float S_GetBaseVolume()
{
return s_volume->value * s_fFadeVolume;
}
/*
==============
S_NeedFullRestart
==============
*/
qboolean S_NeedFullRestart()
{
return Cvar_Get("s_initsound", "1", 0)->integer != s_bLastInitSound;
}
/*
==============
S_PrintInfo
==============
*/
void S_PrintInfo()
{
const char *dev;
Com_Printf("----- Sound Info -----\n");
if (s_bSoundStarted) {
dev = NULL;
if (s_openaldevice) {
dev = s_openaldevice->string;
}
if (!dev || !*dev) {
dev = "{default}";
}
Com_Printf("device - %s\n", dev);
if (al_use_reverb) {
Com_Printf("reverb - ON\n");
} else {
Com_Printf("reverb - OFF\n");
}
Com_Printf("samplebits - 16\n");
Com_Printf("speed - %d\n", al_frequency);
if (s_loadas8bit->integer) {
Com_Printf("Can NOT force all sounds to 8 bit in OpenAL, I think.\n");
}
} else {
Com_Printf("sound system not started\n");
}
Com_Printf("----------------------\n");
}
/*
==============
S_DumpStatus
==============
*/
static void S_DumpStatus(const char *pszChanName, int iChanNum, openal_channel *channel)
{
sfx_t *sfx;
ALint status;
sfx = channel->pSfx;
qalGetSourceiv(channel->source, AL_SOURCE_STATE, &status);
alDieIfError();
if (status == AL_PLAYING || status == AL_PAUSED) {
const char *pszMode = status == AL_PLAYING ? "playing" : "paused";
if (sfx) {
if (sfx != (sfx_t *)-16 && sfx->name[0]) {
Com_Printf("%s channel %i - %s sfx %s\n", pszChanName, iChanNum, pszMode, sfx->name);
} else {
Com_Printf("%s channel %i - %s with nameless sfx\n", pszChanName, iChanNum, pszMode);
}
} else {
Com_Printf("%s channel %i - %s with NULL sfx\n", pszChanName, iChanNum, pszMode);
}
}
}
/*
==============
S_DumpInfo
==============
*/
void S_DumpInfo()
{
int i;
for (i = 0; i < MAX_OPENAL_CHANNELS_3D; i++) {
S_DumpStatus("3D", i, openal.channel[i]);
}
for (i = 0; i < MAX_OPENAL_CHANNELS_2D; i++) {
S_DumpStatus("2D", i, openal.channel[MAX_OPENAL_CHANNELS_3D + i]);
}
for (i = 0; i < MAX_OPENAL_CHANNELS_2D_STREAM; i++) {
S_DumpStatus("2D stream", i, openal.channel[MAX_OPENAL_CHANNELS_3D + MAX_OPENAL_CHANNELS_2D + i]);
}
for (i = 0; i < MAX_OPENAL_MISC_CHANNELS; i++) {
S_DumpStatus(
"Misc",
i,
openal.channel[MAX_OPENAL_CHANNELS_3D + MAX_OPENAL_CHANNELS_2D + MAX_OPENAL_CHANNELS_2D_STREAM + i]
);
}
}
/*
==============
S_OPENAL_Pitch
==============
*/
static void S_OPENAL_Pitch()
{
Com_Printf("S_OPENAL_Pitch() needs to be implemented!\n");
}
/*
==============
S_OPENAL_LoadMP3
==============
*/
static bool S_OPENAL_LoadMP3(const char *_path, openal_channel *chan)
{
char path[MAX_QPATH];
snd_info_t info;
ALuint format;
chan->stop();
qalSourcei(chan->source, AL_BUFFER, 0);
alDieIfError();
Q_strncpyz(path, _path, sizeof(path));
path[MAX_QPATH - 1] = 0;
FS_CorrectCase(path);
// Try to load
chan->bufferdata = (ALubyte *)S_CodecLoad(path, &info);
if (!chan->bufferdata) {
return false;
}
format = S_OPENAL_Format(info.width, info.channels);
// Create a buffer
qalGenBuffers(1, &chan->buffer);
alDieIfError();
// Fill the buffer
if (info.size == 0) {
// We have no data to buffer, so buffer silence
byte dummyData[2] = {0};
qalBufferData(chan->buffer, AL_FORMAT_MONO16, (void *)dummyData, 2, 22050);
} else {
qalBufferData(chan->buffer, format, chan->bufferdata, info.size, info.rate);
}
alDieIfError();
// Free the memory
Hunk_FreeTempMemory(chan->bufferdata);
chan->bufferdata = NULL;
qalSourcei(chan->source, AL_BUFFER, chan->buffer);
alDieIfError();
chan->set_no_3d();
return true;
#if 0
char path[MAX_QPATH];
FILE *in;
size_t len;
size_t rc;
bool failed;
chan->stop();
qalSourcei(chan->source, AL_BUFFER, 0);
alDieIfError();
S_OPENAL_NukeBuffer(&chan->buffer);
alDieIfError();
Q_strncpyz(path, _path, sizeof(path));
path[MAX_QPATH - 1] = 0;
FS_CorrectCase(path);
in = fopen(path, "rb");
if (!in) {
Com_DPrintf("Failed to open MP3 \"%s\" for playback\n", path);
return false;
}
fseek(in, 0, SEEK_END);
len = ftell(in);
fseek(in, 0, SEEK_SET);
chan->bufferdata = new ALubyte[len];
rc = fread(chan->bufferdata, 1, len, in);
fclose(in);
if (rc != len) {
delete[] chan->bufferdata;
chan->bufferdata = NULL;
Com_DPrintf("Failed to read MP3 \"%s\" from disk\n", path);
return false;
}
qalGenBuffers(1, &chan->buffer);
alDieIfError();
failed = !_alutLoadMP3_LOKI(chan->buffer, chan->bufferdata, rc);
alDieIfError();
delete[] chan->bufferdata;
chan->bufferdata = NULL;
if (failed) {
Com_DPrintf("Failed to decode MP3 file \"%s\"\n", path);
return false;
}
qalSourcei(chan->source, AL_BUFFER, chan->buffer);
alDieIfError();
chan->set_no_3d();
return true;
#endif
}
/*
==============
S_OPENAL_PlayMP3
==============
*/
static void S_OPENAL_PlayMP3()
{
const char *path;
if (Cmd_Argc() != 2) {
Com_Printf("playmp3 <mp3 file>\n");
return;
}
path = Cmd_Argv(1);
if (!S_OPENAL_LoadMP3(path, &openal.chan_mp3)) {
Com_Printf("Failed to play mp3 - %s\n", path);
return;
}
openal.chan_mp3.play();
Com_Printf("Playing mp3 - %s\n", path);
}
/*
==============
S_OPENAL_StopMP3
==============
*/
static void S_OPENAL_StopMP3()
{
S_OPENAL_NukeChannel(&openal.chan_mp3);
}
/*
==============
MUSIC_Pause
==============
*/
void MUSIC_Pause()
{
int i;
for (i = 0; i < MAX_OPENAL_SONGS; i++) {
openal.chan_song[i].pause();
}
}
/*
==============
MUSIC_Unpause
==============
*/
void MUSIC_Unpause()
{
int i;
for (i = 0; i < MAX_OPENAL_SONGS; i++) {
if (openal.chan_song[i].is_paused()) {
openal.chan_song[i].play();
}
}
}
/*
==============
S_PauseSound
==============
*/
void S_PauseSound()
{
int i;
if (!s_bSoundStarted) {
return;
}
s_bSoundPaused = true;
for (i = 0; i < MAX_OPENAL_POSITION_CHANNELS; i++) {
openal_channel *pChannel = openal.channel[i];
if (!pChannel) {
continue;
}
if (pChannel->iEntChannel == CHAN_MENU) {
continue;
}
if (!pChannel->is_playing()) {
continue;
}
pChannel->pause();
}
if (openal.chan_mp3.is_playing()) {
openal.chan_mp3.pause();
}
MUSIC_Pause();
S_TriggeredMusic_Pause();
}
/*
==============
S_UnpauseSound
==============
*/
void S_UnpauseSound()
{
int i;
if (!s_bSoundStarted) {
return;
}
for (i = 0; i < MAX_OPENAL_POSITION_CHANNELS; i++) {
openal_channel *pChannel = openal.channel[i];
if (!pChannel) {
continue;
}
if (pChannel->is_paused() || (pChannel->iFlags & CHANNEL_FLAG_PLAY_DEFERRED)) {
pChannel->iFlags &= ~CHANNEL_FLAG_PLAY_DEFERRED;
pChannel->play();
}
}
if (openal.chan_mp3.is_paused()) {
openal.chan_mp3.play();
}
MUSIC_Unpause();
S_TriggeredMusic_Unpause();
s_bSoundPaused = false;
}
/*
==============
S_OPENAL_ShouldPlay
==============
*/
static qboolean S_OPENAL_ShouldPlay(sfx_t *pSfx)
{
if (sfx_infos[pSfx->sfx_info_index].max_number_playing <= 0) {
return qtrue;
}
int iRemainingTimesToPlay;
int i;
iRemainingTimesToPlay = sfx_infos[pSfx->sfx_info_index].max_number_playing;
for (i = 0; i < MAX_OPENAL_POSITION_CHANNELS; i++) {
openal_channel *pChannel = openal.channel[i];
if (!pChannel) {
continue;
}
if (pChannel->pSfx == pSfx && pChannel->is_playing()) {
iRemainingTimesToPlay--;
if (!iRemainingTimesToPlay) {
return qfalse;
}
}
}
return qtrue;
}
/*
==============
S_OPENAL_ShouldStart
==============
*/
static qboolean S_OPENAL_ShouldStart(const vec3_t vOrigin, float fMinDist, float fMaxDist)
{
vec3_t vDir;
vec3_t vListenerOrigin;
vec3_t alvec;
if (!al_initialized) {
return false;
}
qalGetListenerfv(AL_POSITION, alvec);
VectorCopy(alvec, vListenerOrigin);
VectorSubtract(vOrigin, vListenerOrigin, vDir);
return Square(fMaxDist) > VectorLengthSquared(vDir);
}
/*
==============
S_OPENAL_PickChannelBase
==============
*/
static int S_OPENAL_PickChannelBase(int iEntNum, int iEntChannel, int iFirstChannel, int iLastChannel)
{
int i;
int iBestChannel;
openal_channel *pChannel;
iBestChannel = -1;
if (iEntNum != ENTITYNUM_NONE && iEntChannel) {
bool bStoppedChannel = false;
for (i = iFirstChannel; i <= iLastChannel; i++) {
pChannel = openal.channel[i];
if (!pChannel) {
continue;
}
if (pChannel->is_free()) {
iBestChannel = i;
continue;
}
if (pChannel->iEntNum == iEntNum && pChannel->iEntChannel == iEntChannel) {
pChannel->end_sample();
bStoppedChannel = 1;
iBestChannel = i;
break;
}
}
if (!bStoppedChannel) {
for (i = 0; i < iFirstChannel; ++i) {
pChannel = openal.channel[i];
if (!pChannel || pChannel->is_free()) {
continue;
}
if (pChannel->iEntNum == iEntNum && pChannel->iEntChannel == iEntChannel) {
bStoppedChannel = 1;
break;
}
}
if (!bStoppedChannel) {
for (i = iLastChannel + 1; i < MAX_OPENAL_POSITION_CHANNELS; i++) {
pChannel = openal.channel[i];
if (!pChannel || pChannel->is_free()) {
continue;
}
if (pChannel->iEntNum == iEntNum && pChannel->iEntChannel == iEntChannel) {
break;
}
}
}
}
}
if (iBestChannel < 0) {
int iBestTime = 0x7FFFFFFF;
for (i = iFirstChannel; i <= iLastChannel; i++) {
pChannel = openal.channel[i];
if (!pChannel) {
continue;
}
if (pChannel->is_free()) {
return i;
}
if (pChannel->iEntNum == s_iListenerNumber && iEntNum != pChannel->iEntNum) {
continue;
}
if (pChannel->iEntChannel < iEntChannel
|| (pChannel->iEntChannel == iEntChannel && pChannel->iStartTime < iBestTime)) {
iBestChannel = i;
iEntChannel = pChannel->iEntChannel;
iBestTime = pChannel->iStartTime;
}
}
}
return iBestChannel;
}
/*
==============
S_OPENAL_PickChannel3D
==============
*/
static int S_OPENAL_PickChannel3D(int iEntNum, int iEntChannel)
{
return S_OPENAL_PickChannelBase(iEntNum, iEntChannel, 0, MAX_OPENAL_CHANNELS_3D - 1);
}
/*
==============
S_OPENAL_PickChannel2D
==============
*/
static int S_OPENAL_PickChannel2D(int iEntNum, int iEntChannel)
{
return S_OPENAL_PickChannelBase(
iEntNum, iEntChannel, MAX_OPENAL_CHANNELS_3D, MAX_OPENAL_CHANNELS_3D + MAX_OPENAL_CHANNELS_2D - 1
);
}
/*
==============
S_OPENAL_PickChannel2DStreamed
==============
*/
static int S_OPENAL_PickChannel2DStreamed(int iEntNum, int iEntChannel)
{
return S_OPENAL_PickChannelBase(
iEntNum,
iEntChannel,
MAX_OPENAL_CHANNELS_3D + MAX_OPENAL_CHANNELS_2D,
MAX_OPENAL_CHANNELS_3D + MAX_OPENAL_CHANNELS_2D + MAX_OPENAL_CHANNELS_2D_STREAM - 1
);
}
/*
==============
callbackServer
==============
*/
void callbackServer(int entnum, int channel_number, const char *name)
{
if (!com_sv_running->integer) {
return;
}
SV_SoundCallback(entnum, channel_number, name);
}
/*
==============
S_OPENAL_Start2DSound
==============
*/
static void S_OPENAL_Start2DSound(
const vec3_t vOrigin,
int iEntNum,
int iEntChannel,
sfx_t *pSfx,
float fVolume,
float fMinDistance,
float fPitch,
float fMaxDistance
)
{
int iRealEntNum;
int iFreeChannel;
openal_channel *pChannel;
float fRealVolume;
bool bSupportWaitTillSoundDone;
iRealEntNum = iEntNum & ~S_FLAG_DO_CALLBACK;
if (pSfx->iFlags & SFX_FLAG_STREAMED) {
iFreeChannel = S_OPENAL_PickChannel2DStreamed(iRealEntNum, iEntChannel);
} else {
iFreeChannel = S_OPENAL_PickChannel2D(iRealEntNum, iEntChannel);
}
if (iFreeChannel < 0) {
Com_DPrintf(
"Couldn't play %s sound '%s' for entity %i on channel %s\n",
(pSfx->iFlags & SFX_FLAG_STREAMED) ? "2Dstreamed" : "2D",
pSfx->name,
iRealEntNum,
S_ChannelNumToName(iEntChannel)
);
return;
}
if (iEntNum & S_FLAG_DO_CALLBACK) {
callbackServer(iRealEntNum, iFreeChannel, pSfx->name);
}
pChannel = openal.channel[iFreeChannel];
pChannel->force_free();
if (fVolume < 0) {
fVolume = 1;
}
fRealVolume = S_GetBaseVolume() * fVolume;
pChannel->fVolume = fVolume;
if (pChannel->iEntChannel == CHAN_LOCAL) {
pChannel->iFlags |= CHANNEL_FLAG_LOCAL_LISTENER;
} else {
pChannel->iFlags &= ~CHANNEL_FLAG_LOCAL_LISTENER;
}
pChannel->iFlags &= ~CHANNEL_FLAG_PAUSED;
if (fMinDistance < 0.0) {
fMinDistance = 200.0;
}
pChannel->fMinDist = fMinDistance;
if (fMaxDistance < 0.0) {
fMaxDistance = pChannel->fMinDist * 64;
}
pChannel->fMaxDist = fMaxDistance;
pChannel->fNewPitchMult = fPitch;
pChannel->pSfx = pSfx;
pChannel->iEntNum = iRealEntNum;
pChannel->iEntChannel = iEntChannel;
if (iRealEntNum == ENTITYNUM_NONE) {
VectorClear(pChannel->vOrigin);
pChannel->iFlags |= CHANNEL_FLAG_NO_ENTITY;
pChannel->iEntNum = 0;
if (vOrigin) {
VectorCopy(vOrigin, pChannel->vOrigin);
}
} else {
pChannel->iFlags &= ~CHANNEL_FLAG_NO_ENTITY;
pChannel->iEntNum = iRealEntNum;
if (vOrigin) {
VectorCopy(vOrigin, pChannel->vOrigin);
bSupportWaitTillSoundDone = cl.serverTime - 1 < 0;
pChannel->iTime = cl.serverTime - 1;
if (bSupportWaitTillSoundDone) {
pChannel->iTime = 0;
}
if (s_entity[iEntNum].time < pChannel->iTime) {
// Fixed in OPM
// Not sure if it's the real solution, but script_origin entities are usually
// never sent to client so the sound will immediately get stopped
pChannel->iFlags |= CHANNEL_FLAG_MISSING_ENT;
}
} else {
VectorClear(pChannel->vOrigin);
pChannel->iTime = 0;
}
}
pChannel->iStartTime = cl.serverTime;
pChannel->iEndTime = (int)(cl.serverTime + pSfx->time_length + 250.f);
if (iFreeChannel > MAX_OPENAL_CHANNELS_3D + MAX_OPENAL_CHANNELS_2D) {
// streamed
if (!pChannel->set_sfx(pSfx)) {
Com_DPrintf("Set stream error - %s\n", pSfx->name);
return;
}
pChannel->iBaseRate = pChannel->sample_playback_rate();
pChannel->set_no_3d();
fRealVolume = fRealVolume * s_fVolumeGain;
pChannel->set_gain(fRealVolume);
pChannel->play();
} else {
pChannel->stop();
pChannel->set_no_3d();
pChannel->set_sfx(pSfx);
pChannel->set_gain(fRealVolume);
pChannel->play();
}
if (s_show_sounds->integer > 0) {
Com_DPrintf(
"2D - %d (#%i) - %s (vol %f, mindist %f, maxdist %f)\n",
cl.serverTime,
iFreeChannel,
pSfx->name,
fVolume,
fMinDistance,
fMaxDistance
);
}
}
/*
==============
S_OPENAL_StartSound
==============
*/
void S_OPENAL_StartSound(
const vec3_t vOrigin,
int iEntNum,
int iEntChannel,
sfxHandle_t sfxHandle,
float fVolume,
float fMinDist,
float fPitch,
float fMaxDist,
qboolean bStreamed
)
{
int iChannel;
openal_channel *pChannel;
sfx_info_t *pSfxInfo;
sfx_t *pSfx;
ALint state;
bool bOnlyUpdate;
bool bSupportWaitTillSoundDone;
bOnlyUpdate = false;
pSfx = &s_knownSfx[sfxHandle];
if (bStreamed) {
pSfx->iFlags |= SFX_FLAG_STREAMED;
}
if (!S_OPENAL_ShouldPlay(pSfx)) {
Com_DPrintf("^~^~^ Not playing sound '%s'\n", pSfx->name);
return;
}
if ((pSfx->iFlags & (SFX_FLAG_NO_OFFSET) || pSfx->iFlags & (SFX_FLAG_STREAMED | SFX_FLAG_MP3))
|| iEntChannel == CHAN_MENU || iEntChannel == CHAN_LOCAL) {
S_OPENAL_Start2DSound(vOrigin, iEntNum, iEntChannel, pSfx, fVolume, fMinDist, fPitch, fMaxDist);
return;
}
bSupportWaitTillSoundDone = (iEntNum & S_FLAG_DO_CALLBACK) != 0;
iEntNum &= ~S_FLAG_DO_CALLBACK;
pSfxInfo = &sfx_infos[pSfx->sfx_info_index];
if (pSfx->iFlags & SFX_FLAG_STREAMED) {
Com_DPrintf("3D sounds not supported - couldn't play '%s'\n", pSfx->name);
return;
}
iChannel = S_OPENAL_PickChannel3D(iEntNum, iEntChannel);
if (iChannel < 0) {
Com_DPrintf(
"Couldn't play %s sound '%s' for entity %i on channel %s\n",
(pSfx->iFlags & SFX_FLAG_STREAMED) ? "3Dstreamed" : "3D",
pSfx->name,
iEntNum,
S_ChannelNumToName(iEntChannel)
);
return;
}
if (bSupportWaitTillSoundDone) {
callbackServer(iEntNum, iChannel, pSfx->name);
}
pChannel = &openal.chan_3D[iChannel];
pChannel->fNewPitchMult = fPitch;
pChannel->iEntChannel = iEntChannel;
pChannel->iFlags &= ~(CHANNEL_FLAG_PAUSED | CHANNEL_FLAG_LOCAL_LISTENER);
state = pChannel->sample_status();
if (pChannel->iEntNum == iEntNum && (state == AL_PLAYING || state == AL_PAUSED) && pChannel->pSfx == pSfx) {
bOnlyUpdate = true;
} else {
pChannel->stop();
pChannel->iFlags &= ~(CHANNEL_FLAG_LOOPING | CHANNEL_FLAG_PAUSED | CHANNEL_FLAG_LOCAL_LISTENER);
if (!pChannel->set_sfx(pSfx)) {
Com_DPrintf("Set sample error - %s\n", pSfx->name);
return;
}
}
if (fMinDist < 0.0) {
fMinDist = 200.0;
fMaxDist = 12800.0;
}
pChannel->fMinDist = fMinDist;
pChannel->fMaxDist = fMaxDist;
if (fVolume < 0.0) {
fVolume = 1.0;
}
pChannel->fVolume = S_GetBaseVolume() * fVolume;
pChannel->set_gain(pChannel->fVolume);
// Fixed in OPM
// Setup the channel for 3D
pChannel->set_3d();
if (s_show_sounds->integer > 0) {
Com_DPrintf(
"%d (#%i) - %s (vol %f, mindist %f, maxdist %f)\n",
cl.serverTime,
iChannel,
pSfx->name,
pChannel->fVolume,
fMinDist,
fMaxDist
);
}
pChannel->set_velocity(0, 0, 0);
if (iEntNum == ENTITYNUM_NONE) {
if (vOrigin) {
VectorCopy(vOrigin, pChannel->vOrigin);
} else {
//VectorClear(vOrigin);
// Fixed in OPM
// Tiny mistake found in original where the vOrigin parameter is set to 0
VectorClear(pChannel->vOrigin);
}
pChannel->set_position(pChannel->vOrigin[0], pChannel->vOrigin[1], pChannel->vOrigin[2]);
pChannel->iFlags |= CHANNEL_FLAG_NO_ENTITY;
pChannel->iEntNum = 0;
} else {
pChannel->iFlags &= ~CHANNEL_FLAG_NO_ENTITY;
pChannel->iEntNum = iEntNum;
if (vOrigin) {
VectorCopy(vOrigin, pChannel->vOrigin);
bSupportWaitTillSoundDone = cl.serverTime - 1 < 0;
pChannel->iTime = cl.serverTime - 1;
if (bSupportWaitTillSoundDone) {
pChannel->iTime = 0;
}
if (s_entity[iEntNum].time < pChannel->iTime) {
// Fixed in OPM
// Not sure if it's the real solution, but script_origin entities are usually
// never sent to client so the sound will immediately get stopped
pChannel->iFlags |= CHANNEL_FLAG_MISSING_ENT;
}
} else {
VectorClear(pChannel->vOrigin);
pChannel->iTime = 0;
}
pChannel->set_position(pChannel->vOrigin[0], pChannel->vOrigin[1], pChannel->vOrigin[2]);
}
if (pSfxInfo->loop_start != -1) {
pChannel->set_sample_loop_block(pSfxInfo->loop_start, pSfxInfo->loop_end);
pChannel->set_sample_loop_count(0);
pChannel->iFlags |= CHANNEL_FLAG_LOOPING;
if (s_show_sounds->integer) {
Com_DPrintf("loopblock - %d to %d\n", pSfxInfo->loop_start, pSfxInfo->loop_end);
}
} else {
pChannel->set_sample_loop_count(1);
}
if (!bOnlyUpdate && S_OPENAL_ShouldStart(pChannel->vOrigin, pChannel->fMinDist, pChannel->fMaxDist)) {
// Fixed in OPM
// Make sure to set the pitch before playing the sound
pChannel->iBaseRate = pSfx->info.rate;
pChannel->set_sample_playback_rate(pChannel->iBaseRate * pChannel->fNewPitchMult);
pChannel->fNewPitchMult = 0;
pChannel->play();
pChannel->iEndTime = cl.serverTime + (int)pChannel->pSfx->time_length + 250;
pChannel->iStartTime = cl.serverTime;
}
}
/*
==============
S_OPENAL_AddLoopingSound
==============
*/
void S_OPENAL_AddLoopingSound(
const vec3_t vOrigin,
const vec3_t vVelocity,
sfxHandle_t sfxHandle,
float fVolume,
float fMinDist,
float fMaxDist,
float fPitch,
int iFlags
)
{
int i;
int iFreeLoopSound;
sfx_t *pSfx;
openal_loop_sound_t *pLoopSound;
iFreeLoopSound = -1;
pSfx = &s_knownSfx[sfxHandle];
if (!pSfx || !pSfx->length) {
return;
}
for (i = 0; i < (MAX_OPENAL_CHANNELS_3D + MAX_OPENAL_CHANNELS_2D); i++) {
pLoopSound = &openal.loop_sounds[i];
if (pLoopSound->pSfx == pSfx && !pLoopSound->bInUse) {
iFreeLoopSound = i;
break;
}
}
if (iFreeLoopSound < 0) {
for (i = 0; i < (MAX_OPENAL_CHANNELS_3D + MAX_OPENAL_CHANNELS_2D); i++) {
pLoopSound = &openal.loop_sounds[i];
if (!pLoopSound->pSfx && !pLoopSound->bInUse) {
iFreeLoopSound = i;
pLoopSound->bPlaying = false;
break;
}
}
}
if (iFreeLoopSound < 0) {
if (cls.realtime >= s_iNextLoopingWarning) {
Com_DPrintf("Too many looping sounds\n");
s_iNextLoopingWarning = cls.realtime + 1000;
}
return;
}
pLoopSound = &openal.loop_sounds[iFreeLoopSound];
VectorCopy(vOrigin, pLoopSound->vOrigin);
VectorCopy(vVelocity, pLoopSound->vVelocity);
pLoopSound->pSfx = pSfx;
pLoopSound->bInUse = true;
pLoopSound->iStartTime = cls.realtime;
pLoopSound->fBaseVolume = fVolume;
pLoopSound->fMinDist = fMinDist;
pLoopSound->fMaxDist = fMaxDist;
pLoopSound->fPitch = fPitch;
pLoopSound->iFlags = iFlags;
pLoopSound->bCombine = VectorCompare(vVelocity, vec_zero) == 0;
if (pLoopSound->bCombine) {
for (i = 0; i < (MAX_OPENAL_CHANNELS_3D + MAX_OPENAL_CHANNELS_2D); i++) {
if (openal.loop_sounds[i].pSfx == pSfx && openal.loop_sounds[i].bInUse) {
pLoopSound->iStartTime = openal.loop_sounds[i].iStartTime;
if (openal.loop_sounds[i].bPlaying) {
pLoopSound->bPlaying = true;
pLoopSound->iChannel = openal.loop_sounds[i].iChannel;
}
}
}
}
}
/*
==============
S_OPENAL_StopLoopingSound
==============
*/
void S_OPENAL_StopLoopingSound(openal_loop_sound_t *pLoopSound)
{
bool bMayStop;
int i;
openal_channel *pChannel;
if (!pLoopSound->bPlaying) {
return;
}
bMayStop = true;
if (pLoopSound->bCombine) {
for (i = 0; i < (MAX_OPENAL_CHANNELS_3D + MAX_OPENAL_CHANNELS_2D); i++) {
if (openal.loop_sounds[i].pSfx == pLoopSound->pSfx && openal.loop_sounds[i].bInUse) {
bMayStop = false;
break;
}
}
}
if (bMayStop) {
if (s_show_sounds->integer > 0) {
Com_DPrintf(
"%d (#%i) - stopped loop - %s\n",
cl.serverTime,
pLoopSound->iChannel,
openal.channel[pLoopSound->iChannel]->pSfx->name
);
}
openal.channel[pLoopSound->iChannel]->force_free();
}
pLoopSound->pSfx = NULL;
pLoopSound->bPlaying = false;
if (pLoopSound->bCombine) {
for (i = 0; i < (MAX_OPENAL_CHANNELS_3D + MAX_OPENAL_CHANNELS_2D); i++) {
if (openal.loop_sounds[i].pSfx == pLoopSound->pSfx) {
openal.loop_sounds[i].bPlaying = false;
openal.loop_sounds[i].pSfx = NULL;
}
}
}
}
/*
==============
S_OPENAL_ClearLoopingSounds
==============
*/
void S_OPENAL_ClearLoopingSounds()
{
int i;
for (i = 0; i < (MAX_OPENAL_CHANNELS_3D + MAX_OPENAL_CHANNELS_2D); i++) {
openal.loop_sounds[i].bInUse = false;
}
}
/*
==============
S_OPENAL_StopLoopingSounds
==============
*/
void S_OPENAL_StopLoopingSounds()
{
int i;
for (i = 0; i < (MAX_OPENAL_CHANNELS_3D + MAX_OPENAL_CHANNELS_2D); i++) {
openal.loop_sounds[i].bInUse = false;
S_OPENAL_StopLoopingSound(&openal.loop_sounds[i]);
}
}
/*
==============
S_OPENAL_StopSound
==============
*/
void S_OPENAL_StopSound(int iEntNum, int iEntChannel)
{
int i;
for (i = 0; i < MAX_OPENAL_POSITION_CHANNELS; i++) {
openal_channel *pChannel = openal.channel[i];
if (!pChannel->is_free() && pChannel->iEntNum == iEntNum && pChannel->iEntChannel == iEntChannel) {
pChannel->end_sample();
break;
}
}
}
/*
==============
S_OPENAL_StopAllSounds
==============
*/
void S_OPENAL_StopAllSounds(qboolean bStopMusic)
{
int i;
if (!s_bSoundStarted) {
return;
}
S_OPENAL_StopLoopingSounds();
for (i = 0; i < MAX_OPENAL_POSITION_CHANNELS; i++) {
openal_channel *pChannel = openal.channel[i];
if (pChannel) {
pChannel->force_free();
}
}
if (bStopMusic) {
MUSIC_FreeAllSongs();
S_TriggeredMusic_Stop();
}
}
/*
==============
S_OPENAL_Start2DLoopSound
==============
*/
static int S_OPENAL_Start2DLoopSound(
openal_loop_sound_t *pLoopSound, float fVolume, float fVolumeToPlay, float fMinDistance, const vec3_t vLoopOrigin
)
{
int iChannel;
int iSoundOffset;
openal_channel *pChannel;
if (pLoopSound->pSfx->iFlags & SFX_FLAG_STREAMED) {
iChannel = S_OPENAL_PickChannel2DStreamed(0, 0);
} else {
iChannel = S_OPENAL_PickChannel2D(0, 0);
}
if (iChannel < 0) {
Com_DPrintf("Could not find a free 2D sound channel\n");
return iChannel;
}
pChannel = openal.channel[iChannel];
pChannel->force_free();
pChannel->fVolume = fVolume;
if (pLoopSound->iFlags & LOOPSOUND_FLAG_NO_PAN) {
pChannel->iFlags |= CHANNEL_FLAG_LOCAL_LISTENER;
} else {
pChannel->iFlags &= ~CHANNEL_FLAG_LOCAL_LISTENER;
}
pChannel->fMinDist = fMinDistance;
if (!pChannel->set_sfx(pLoopSound->pSfx)) {
Com_DPrintf("Set sample error\n");
pChannel->iFlags &= ~CHANNEL_FLAG_PLAY_DEFERRED;
return -1;
}
pChannel->iEntNum = 0;
pChannel->iEntChannel = 0;
pChannel->pSfx = pLoopSound->pSfx;
pChannel->iFlags |= CHANNEL_FLAG_PAUSED | CHANNEL_FLAG_NO_ENTITY;
pChannel->iBaseRate = pChannel->sample_playback_rate();
VectorCopy(vLoopOrigin, pChannel->vOrigin);
iSoundOffset = (int)(pLoopSound->pSfx->info.width * pLoopSound->pSfx->info.rate
* (float)(cls.realtime - pLoopSound->iStartTime) / 1000.0);
pChannel->set_sample_offset(iSoundOffset % pLoopSound->pSfx->length);
pChannel->set_sample_loop_count(0);
pChannel->fVolume = fVolumeToPlay;
pChannel->set_gain(fVolumeToPlay);
pChannel->start_sample();
if (s_show_sounds->integer > 0) {
Com_DPrintf("%d (#%i) - %s (vol %f)\n", cl.serverTime, pLoopSound->iChannel, pLoopSound->pSfx->name, fVolume);
}
return iChannel;
}
/*
==============
S_OPENAL_Start3DLoopSound
==============
*/
static int S_OPENAL_Start3DLoopSound(
openal_loop_sound_t *pLoopSound,
float fVolumeToPlay,
float fMinDistance,
float fMaxDistance,
const vec3_t vLoopOrigin,
const vec3_t vListenerOrigin
)
{
int iChannel;
vec3_t vDir;
int iSoundOffset;
openal_channel *pChan3D;
if (pLoopSound->pSfx->iFlags & SFX_FLAG_STREAMED) {
return -1;
}
if (!pLoopSound->pSfx->data || !pLoopSound->pSfx->length) {
return -1;
}
iChannel = S_OPENAL_PickChannel3D(0, 0);
if (iChannel < 0) {
Com_DPrintf("Could not find a free channel\n");
return iChannel;
}
pChan3D = &openal.chan_3D[iChannel];
pChan3D->force_free();
pChan3D->iEntNum = 0;
pChan3D->iEntChannel = 0;
pChan3D->fMinDist = fMinDistance;
pChan3D->fMaxDist = fMaxDistance;
pChan3D->set_3d();
if (!pChan3D->set_sfx(pLoopSound->pSfx)) {
Com_DPrintf("Set sample error - %s\n", pLoopSound->pSfx->name);
return -1;
}
pChan3D->set_position(vLoopOrigin[0], vLoopOrigin[1], vLoopOrigin[2]);
VectorScale(pLoopSound->vVelocity, s_fVelocityScale, pLoopSound->vVelocity);
pChan3D->set_velocity(pLoopSound->vVelocity[0], pLoopSound->vVelocity[1], pLoopSound->vVelocity[2]);
pChan3D->pSfx = pLoopSound->pSfx;
pChan3D->iFlags |= CHANNEL_FLAG_PAUSED | CHANNEL_FLAG_NO_ENTITY;
pChan3D->iBaseRate = pChan3D->sample_playback_rate();
iSoundOffset = (int)((int)pLoopSound->pSfx->info.width * pLoopSound->pSfx->info.rate
* (float)(cls.realtime - pLoopSound->iStartTime) / 1000.0)
% pLoopSound->pSfx->length;
pChan3D->set_sample_offset(iSoundOffset);
pChan3D->set_sample_loop_count(0);
pChan3D->fVolume = fVolumeToPlay;
pChan3D->set_gain(fVolumeToPlay);
pChan3D->play();
S_OPENAL_reverb(iChannel, s_iReverbType, s_fReverbLevel);
return iChannel;
}
/*
==============
S_OPENAL_UpdateLoopSound
==============
*/
static bool S_OPENAL_UpdateLoopSound(
openal_loop_sound_t *pLoopSound,
float fVolumeToPlay,
float fMinDistance,
float fMaxDistance,
const vec3_t vListenerOrigin,
const vec3_t vTempAxis,
const vec3_t vLoopOrigin
)
{
openal_channel *pChannel;
float fVolume;
float fMaxVolume;
vec3_t vDir;
float fDistance;
pChannel = openal.channel[pLoopSound->iChannel];
if (!pChannel) {
return false;
}
if (pChannel->pSfx != pLoopSound->pSfx) {
pLoopSound->bPlaying = 0;
return false;
}
pChannel->iStartTime = cl.serverTime;
if (pLoopSound->pSfx->iFlags & (SFX_FLAG_NO_OFFSET) || pLoopSound->pSfx->iFlags & (SFX_FLAG_STREAMED | SFX_FLAG_MP3)
|| (pLoopSound->iFlags & LOOPSOUND_FLAG_NO_PAN)) {
vec3_t vOrigin;
int iPan;
pChannel->fVolume = fVolumeToPlay / s_fVolumeGain;
fVolume = S_GetBaseVolume() * pChannel->fVolume;
fMaxVolume = fVolume;
if (pLoopSound->iFlags & LOOPSOUND_FLAG_NO_PAN) {
// Center the sound
iPan = 64;
} else {
VectorCopy(vLoopOrigin, vOrigin);
iPan = S_OPENAL_SpatializeStereoSound(vListenerOrigin, vTempAxis, vOrigin);
VectorSubtract(vListenerOrigin, vOrigin, vDir);
// Clamp the volume by distance
fDistance = VectorLength(vDir);
if (pChannel->fMinDist >= fDistance) {
fVolume = fMaxVolume;
} else {
fVolume = pChannel->fMinDist / fDistance * fMaxVolume;
}
}
pChannel->set_gain(fVolume);
pChannel->set_sample_pan(iPan);
} else {
pChannel->set_position(vLoopOrigin[0], vLoopOrigin[1], vLoopOrigin[2]);
pChannel->fVolume = fVolumeToPlay;
pChannel->set_gain(fVolumeToPlay);
}
if (s_bReverbChanged) {
// Make sure to update the reverb
S_OPENAL_reverb(pLoopSound->iChannel, s_iReverbType, s_fReverbLevel);
}
return true;
}
/*
==============
S_OPENAL_AddLoopSounds
==============
*/
void S_OPENAL_AddLoopSounds(const vec3_t vTempAxis)
{
int i, j;
static int iLoopFrame = 0;
float fDistance;
int iChannel;
vec3_t vListenerOrigin;
vec3_t vLoopOrigin;
openal_loop_sound_t *pLoopSound;
float fTotalVolume;
float fVolumeToPlay;
float fMinDistance, fMaxDistance;
float fVolume;
float fPitch;
float fMaxVolume, fMaxFactor;
openal_channel *pChannel;
bool bAlreadyAdded[MAX_OPENAL_LOOP_SOUNDS] = {false};
vec3_t alvec;
qalGetListenerfv(AL_POSITION, alvec);
VectorCopy(alvec, vListenerOrigin);
for (i = 0; i < MAX_OPENAL_LOOP_SOUNDS; i++) {
vec3_t vDir;
if (bAlreadyAdded[i]) {
continue;
}
pLoopSound = &openal.loop_sounds[i];
if (!pLoopSound->pSfx) {
continue;
}
pChannel = openal.channel[pLoopSound->iChannel];
fMinDistance = pLoopSound->fMinDist;
if (fMinDistance < 0) {
fMinDistance = 200;
}
fMaxDistance = pLoopSound->fMaxDist;
if (fMaxDistance < 0) {
fMaxDistance = fMinDistance * 64;
}
fVolume = pLoopSound->fBaseVolume;
if (fVolume < 0) {
fVolume = 1;
}
fVolume = fVolume * s_fAmbientVolume;
fTotalVolume = 0.0;
fMaxVolume = 0.0;
if (pLoopSound->bPlaying) {
pChannel->fNewPitchMult = pLoopSound->fPitch;
}
if (pLoopSound->bCombine) {
for (j = 0; j < MAX_LOOP_SOUNDS; j++) {
openal_loop_sound_t *pLoopSound2 = &openal.loop_sounds[j];
if (pLoopSound2->pSfx == pLoopSound->pSfx) {
VectorSubtract(pLoopSound2->vOrigin, vListenerOrigin, vDir);
VectorCopy(vDir, pLoopSound2->vRelativeOrigin);
fDistance = VectorLength(pLoopSound2->vRelativeOrigin);
if (fDistance <= fMinDistance) {
fVolumeToPlay = fVolume;
} else if (fDistance >= fMaxDistance) {
fVolumeToPlay = 0;
} else {
fVolumeToPlay = fMinDistance * fMinDistance * fVolume / (fDistance * fDistance);
}
if (fMaxVolume < fVolumeToPlay) {
fMaxVolume = fVolumeToPlay;
}
fTotalVolume += fVolumeToPlay;
bAlreadyAdded[j] = true;
}
}
} else {
VectorSubtract(pLoopSound->vOrigin, vListenerOrigin, vDir);
VectorCopy(vDir, pLoopSound->vRelativeOrigin);
fDistance = VectorLength(pLoopSound->vRelativeOrigin);
if (fDistance <= fMinDistance) {
fTotalVolume = fVolume;
} else if (fDistance >= fMaxDistance) {
fTotalVolume = 0;
} else {
fTotalVolume = fMinDistance * fMinDistance * fVolume / (fDistance * fDistance);
}
pLoopSound->fVolume = fTotalVolume;
fMaxVolume = fTotalVolume;
}
fMaxFactor = sfx_infos[pLoopSound->pSfx->sfx_info_index].max_factor;
if (fMaxFactor >= 1 && fMaxVolume * fMaxFactor < fTotalVolume) {
fTotalVolume = fMaxVolume * fMaxFactor;
}
if (fTotalVolume <= 0 && !(pLoopSound->iFlags & LOOPSOUND_FLAG_NO_PAN)) {
if (pLoopSound->bPlaying) {
if (s_show_sounds->integer > 0) {
Com_DPrintf(
"%d (#%i) - stopped loop - %s\n",
cl.serverTime,
pLoopSound->iChannel,
openal.channel[pLoopSound->iChannel]->pSfx->name
);
}
pChannel->stop();
pLoopSound->bPlaying = false;
if (pLoopSound->bCombine) {
for (j = 0; j < MAX_LOOP_SOUNDS; j++) {
openal_loop_sound_t *pLoopSound2 = &openal.loop_sounds[j];
if (pLoopSound2->pSfx == pLoopSound->pSfx) {
pLoopSound2->bPlaying = false;
}
}
}
}
continue;
}
VectorClear(vLoopOrigin);
if (pLoopSound->bCombine) {
for (j = 0; j < MAX_LOOP_SOUNDS; j++) {
openal_loop_sound_t *pLoopSound2 = &openal.loop_sounds[j];
if (pLoopSound2->pSfx == pLoopSound->pSfx) {
VectorNormalize(pLoopSound2->vRelativeOrigin);
VectorScale(
pLoopSound2->vRelativeOrigin, pLoopSound2->fVolume / fTotalVolume, pLoopSound2->vRelativeOrigin
);
VectorAdd(pLoopSound2->vRelativeOrigin, vLoopOrigin, vLoopOrigin);
}
}
VectorNormalize(vLoopOrigin);
VectorMA(vListenerOrigin, fMinDistance * 0.5f, vLoopOrigin, vLoopOrigin);
} else {
VectorCopy(pLoopSound->vOrigin, vLoopOrigin);
}
if (pLoopSound->bPlaying) {
S_OPENAL_UpdateLoopSound(
pLoopSound,
S_GetBaseVolume() * s_fVolumeGain * fTotalVolume,
fMinDistance,
fMaxDistance,
vListenerOrigin,
vTempAxis,
vLoopOrigin
);
continue;
}
if (s_show_sounds->integer > 0) {
Com_DPrintf("%d (#%i) - started loop - %s\n", cl.serverTime, pLoopSound->iChannel, pLoopSound->pSfx->name);
}
if (pLoopSound->pSfx->iFlags & (SFX_FLAG_NO_OFFSET)
|| pLoopSound->pSfx->iFlags & (SFX_FLAG_STREAMED | SFX_FLAG_MP3)
|| (pLoopSound->iFlags & LOOPSOUND_FLAG_NO_PAN)) {
iChannel = S_OPENAL_Start2DLoopSound(
pLoopSound, fVolume, S_GetBaseVolume() * s_fVolumeGain * fTotalVolume, fMinDistance, vLoopOrigin
);
} else {
iChannel = S_OPENAL_Start3DLoopSound(
pLoopSound,
S_GetBaseVolume() * s_fVolumeGain * fTotalVolume,
fMinDistance,
fMaxDistance,
vLoopOrigin,
vListenerOrigin
);
}
if (iChannel < 0) {
continue;
}
pLoopSound->bPlaying = true;
pLoopSound->iChannel = iChannel;
if (pLoopSound->bCombine) {
for (j = 0; j < MAX_LOOP_SOUNDS; j++) {
openal_loop_sound_t *pLoopSound2 = &openal.loop_sounds[j];
if (pLoopSound2->pSfx == pLoopSound->pSfx) {
pLoopSound2->bPlaying = true;
pLoopSound2->iChannel = iChannel;
}
}
}
}
}
/*
==============
S_OPENAL_Respatialize
==============
*/
void S_OPENAL_Respatialize(int iEntNum, const vec3_t vHeadPos, const vec3_t vAxis[3])
{
int i;
vec3_t vOrigin;
vec3_t vVelocity;
vec3_t vEntOrigin;
vec3_t vEntVelocity;
vec3_t vDir;
vec3_t vUp;
vec3_t vListenerOrigin;
int iPan;
vec3_t vTempAxis;
float fMaxVolume;
float fVolume;
float fDist;
openal_channel *pChannel;
vec3_t alvec {0};
vec3_t alorientation[2];
if (cls.no_menus) {
return;
}
s_iListenerNumber = iEntNum;
//
// Velocity
//
VectorCopy(s_entity[iEntNum].velocity, alvec);
VectorScale(alvec, s_fVelocityScale, alvec);
qalListenerfv(AL_VELOCITY, alvec);
alDieIfError();
//
// Position
//
VectorCopy(vHeadPos, alvec);
VectorCopy(alvec, vListenerOrigin);
qalListenerfv(AL_POSITION, alvec);
alDieIfError();
//
// Orientation
//
alorientation[0][0] = vAxis[0][0];
alorientation[0][1] = vAxis[0][1];
alorientation[0][2] = vAxis[0][2];
alorientation[1][0] = vAxis[2][0];
alorientation[1][1] = vAxis[2][1];
alorientation[1][2] = vAxis[2][2];
qalListenerfv(AL_ORIENTATION, (const ALfloat *)alorientation);
alDieIfError();
VectorCopy(vAxis[1], vTempAxis);
fVolume = 1;
iPan = 64;
for (i = 0; i < MAX_OPENAL_POSITION_CHANNELS; i++) {
pChannel = openal.channel[i];
fMaxVolume = S_GetBaseVolume() * pChannel->fVolume;
if (!pChannel) {
continue;
}
if (!pChannel->is_playing()) {
continue;
}
if (pChannel->iFlags & CHANNEL_FLAG_PAUSED) {
continue;
}
if (pChannel->iFlags & CHANNEL_FLAG_NO_ENTITY) {
VectorCopy(pChannel->vOrigin, vOrigin);
if (pChannel->iFlags & CHANNEL_FLAG_LOCAL_LISTENER) {
VectorCopy(vListenerOrigin, vOrigin);
if (i >= MAX_OPENAL_CHANNELS_3D) {
fVolume = fMaxVolume;
iPan = 64;
} else {
pChannel->set_position(vOrigin[0], vOrigin[1], vOrigin[2]);
}
} else {
if (i >= MAX_OPENAL_CHANNELS_3D) {
iPan = S_OPENAL_SpatializeStereoSound(vListenerOrigin, vTempAxis, vOrigin);
VectorSubtract(vListenerOrigin, vOrigin, vDir);
fDist = VectorLength(vDir);
if (fDist <= pChannel->fMinDist + 0.001f) {
fVolume = fMaxVolume;
} else if (fDist >= pChannel->fMaxDist - 0.001f) {
fVolume = 0;
} else {
fVolume = (1.0 - (fDist - pChannel->fMinDist) / (pChannel->fMaxDist - pChannel->fMinDist))
* fMaxVolume;
}
} else {
pChannel->set_position(vOrigin[0], vOrigin[1], vOrigin[2]);
}
}
} else if (pChannel->iFlags & CHANNEL_FLAG_LOCAL_LISTENER) {
VectorCopy(vListenerOrigin, vOrigin);
if (i >= MAX_OPENAL_CHANNELS_3D) {
fVolume = fMaxVolume;
iPan = 64;
} else {
pChannel->set_position(vOrigin[0], vOrigin[1], vOrigin[2]);
}
} else {
if (s_entity[pChannel->iEntNum].time < pChannel->iTime) {
VectorCopy(pChannel->vOrigin, vOrigin);
if (!(pChannel->iFlags & CHANNEL_FLAG_LOOPING) && !(pChannel->iFlags & (CHANNEL_FLAG_MISSING_ENT))) {
pChannel->end_sample();
continue;
}
} else {
pChannel->iFlags &= ~CHANNEL_FLAG_MISSING_ENT;
VectorCopy(s_entity[pChannel->iEntNum].position, vEntOrigin);
VectorCopy(vEntOrigin, vOrigin);
VectorCopy(vOrigin, pChannel->vOrigin);
pChannel->iTime = s_entity[pChannel->iEntNum].time;
}
if (s_entity[pChannel->iEntNum].use_listener) {
VectorCopy(vListenerOrigin, vOrigin);
}
if (pChannel->iEntNum == s_iListenerNumber) {
if (vListenerOrigin[0] == vOrigin[0] && vListenerOrigin[2] == vOrigin[2]) {
float fDelta = vListenerOrigin[1] - vOrigin[1];
if (fDelta > 89.9f && fDelta < 90.09f) {
VectorCopy(vListenerOrigin, vOrigin);
}
}
}
if (i >= MAX_OPENAL_CHANNELS_3D) {
iPan = S_OPENAL_SpatializeStereoSound(vListenerOrigin, vTempAxis, vOrigin);
VectorSubtract(vListenerOrigin, vOrigin, vDir);
fDist = VectorLength(vDir);
if (fDist <= pChannel->fMinDist + 0.001f) {
fVolume = fMaxVolume;
} else if (fDist >= pChannel->fMaxDist - 0.001f) {
fVolume = 0;
} else {
fVolume =
(1.0 - (fDist - pChannel->fMinDist) / (pChannel->fMaxDist - pChannel->fMinDist)) * fMaxVolume;
}
} else {
pChannel->set_position(vOrigin[0], vOrigin[1], vOrigin[2]);
VectorCopy(s_entity[pChannel->iEntNum].velocity, vEntVelocity);
VectorCopy(vEntVelocity, vVelocity);
VectorScale(vVelocity, s_fVelocityScale, vVelocity);
pChannel->set_velocity(vVelocity[0], vVelocity[1], vVelocity[2]);
}
}
if (i >= MAX_OPENAL_CHANNELS_3D) {
pChannel->set_gain(fVolume);
pChannel->set_sample_pan(iPan);
}
if (s_bReverbChanged) {
S_OPENAL_reverb(i, s_iReverbType, s_fReverbLevel);
}
}
S_OPENAL_AddLoopSounds(vTempAxis);
s_bReverbChanged = false;
}
/*
==============
S_OPENAL_SpatializeStereoSound
==============
*/
static int S_OPENAL_SpatializeStereoSound(const vec3_t listener_origin, const vec3_t listener_left, const vec3_t origin)
{
float lscale, rscale;
vec3_t source_vec;
float pan;
VectorSubtract(origin, listener_origin, source_vec);
VectorNormalize(source_vec);
pan = s_separation->value + (1.f - s_separation->value) * -DotProduct(listener_left, source_vec);
if (pan < 0) {
pan = 0;
}
return ceilf(pan * 127);
}
/*
==============
S_OPENAL_reverb
==============
*/
static void S_OPENAL_reverb(int iChannel, int iReverbType, float fReverbLevel)
{
// No reverb.
}
/*
==============
S_OPENAL_SetReverb
==============
*/
void S_OPENAL_SetReverb(int iType, float fLevel)
{
s_fReverbLevel = fLevel;
s_iReverbType = iType;
if (al_use_reverb) {
s_bReverbChanged = true;
}
}
/*
==============
S_OPENAL_Update
==============
*/
void S_OPENAL_Update()
{
int i;
openal_channel *pChannel;
if (cl.snap.ps.stats[STAT_CINEMATIC]) {
S_SetGlobalAmbientVolumeLevel(0.5f);
} else {
S_SetGlobalAmbientVolumeLevel(1.f);
}
if (paused->integer && !s_bSoundPaused) {
S_PauseSound();
} else if (!paused->integer && s_bSoundPaused) {
S_UnpauseSound();
}
if (s_bFading) {
s_fFadeVolume = 1.f - (cls.realtime - s_fFadeStartTime) / (s_fFadeStopTime - s_fFadeStartTime);
if (s_fFadeVolume < 0) {
s_fFadeVolume = 0;
}
music_volume_changed = true;
}
if (s_volume->modified) {
if (s_volume->value > 1) {
Cvar_Set("s_volume", "1.0");
} else if (s_volume->value < 0) {
Cvar_Set("s_volume", "0.0");
}
music_volume_changed = true;
s_volume->modified = 0;
al_current_volume = Square(s_volume->value);
qalListenerf(AL_GAIN, al_current_volume);
alDieIfError();
}
for (i = 0; i < MAX_OPENAL_POSITION_CHANNELS; i++) {
pChannel = openal.channel[i];
if (!pChannel) {
continue;
}
if (!pChannel->is_playing()) {
continue;
}
if (pChannel->fNewPitchMult <= 0) {
continue;
}
pChannel->set_sample_playback_rate(pChannel->iBaseRate * pChannel->fNewPitchMult);
pChannel->fNewPitchMult = 0;
}
if (s_speaker_type->modified) {
Cbuf_AddText("snd_restart\n");
s_speaker_type->modified = false;
}
if (s_reverb->modified) {
s_reverb->modified = false;
Com_Printf("FIXME: Allow reverb toggle at runtime in OpenAL code.\n");
}
if (s_show_num_active_sounds->integer == 1) {
int num = 0;
for (i = 0; i < MAX_OPENAL_POSITION_CHANNELS; i++) {
pChannel = openal.channel[i];
if (pChannel && pChannel->is_playing()) {
++num;
}
}
Com_DPrintf("Number of active sounds = %d\n", num);
}
Music_Update();
for (i = 0; i < MAX_LOOP_SOUNDS; i++) {
if (!openal.loop_sounds[i].bInUse) {
S_OPENAL_StopLoopingSound(&openal.loop_sounds[i]);
}
}
for (i = 0; i < MAX_OPENAL_CHANNELS; i++) {
openal.channel[i]->update();
}
}
/*
==============
S_OPENAL_GetMusicFilename
==============
*/
const char *S_OPENAL_GetMusicFilename()
{
// Don't call into the channel if QAL initialisation did not succeed.
if (!al_initialized) {
return "";
}
if (!openal.chan_trig_music.is_playing() && !openal.chan_trig_music.is_paused()) {
return "";
}
return openal.tm_filename;
}
/*
==============
S_OPENAL_GetMusicLoopCount
==============
*/
int S_OPENAL_GetMusicLoopCount()
{
return openal.tm_loopcount;
}
/*
==============
S_OPENAL_GetMusicOffset
==============
*/
unsigned int S_OPENAL_GetMusicOffset()
{
return openal.chan_trig_music.sample_offset();
}
/*
==============
S_IsSoundPlaying
==============
*/
qboolean S_IsSoundPlaying(int channel_number, const char *sfxName)
{
openal_channel *pChannel = openal.channel[channel_number];
if (!pChannel) {
return false;
}
if (s_bSoundPaused) {
return false;
}
if (!pChannel->is_playing()) {
return false;
}
if (!pChannel->pSfx) {
return false;
}
if (strcmp(sfxName, pChannel->pSfx->name)) {
return false;
}
return true;
}
/*
==============
S_StoreBase
==============
*/
static void S_StoreBase(channelbasesavegame_t *pBase, openal_channel *pChannel)
{
if (!pChannel) {
return;
}
if (pChannel->iEntChannel == CHAN_MENU || pChannel->is_free()) {
pBase->bPlaying = false;
pBase->iOffset = 0;
pBase->iLoopCount = 0;
pBase->sfx.szName[0] = 0;
pBase->sfx.iFlags = 0;
pBase->fNewPitchMult = 1.f;
pBase->iBaseRate = pChannel->iBaseRate;
pBase->iStatus = 0;
} else {
pBase->bPlaying = true;
pBase->iOffset = pChannel->sample_offset();
pBase->iLoopCount = pChannel->sample_loop_count();
memcpy(pBase->sfx.szName, pChannel->pSfx->name, sizeof(pBase->sfx.szName));
pBase->sfx.iFlags = pChannel->pSfx->iFlags;
pBase->iBaseRate = pChannel->iBaseRate;
pBase->iStatus = 0;
pBase->fNewPitchMult = (float)pBase->iBaseRate / (float)pChannel->sample_playback_rate();
}
pBase->iStartTime = pChannel->iStartTime - cl.serverTime;
pBase->iEndTime = pChannel->iEndTime - cl.serverTime;
pBase->iEntChannel = pChannel->iEntChannel;
pBase->iEntNum = pChannel->iEntNum;
pBase->iFlags = pChannel->iFlags;
pBase->fMaxDist = pChannel->fMaxDist;
pBase->fMinDist = pChannel->fMinDist;
pBase->iNextCheckObstructionTime = 0;
VectorCopy(pChannel->vOrigin, pBase->vOrigin);
pBase->iTime = pChannel->iTime - cl.serverTime;
pBase->fVolume = pChannel->fVolume;
}
/*
==============
S_StartSoundFromBase
==============
*/
static void
S_StartSoundFromBase(channelbasesavegame_t *pBase, openal_channel *pChannel, sfx_t *pSfx, bool bStartUnpaused)
{
if (!pChannel->set_sfx(pSfx)) {
Com_DPrintf("Set sample error - %s\n", pSfx->name);
pChannel->iFlags &= ~CHANNEL_FLAG_PLAY_DEFERRED;
return;
}
pChannel->iBaseRate = pChannel->sample_playback_rate();
if (sfx_infos[pSfx->sfx_info_index].loop_start != -1) {
pChannel->set_sample_loop_block(
sfx_infos[pSfx->sfx_info_index].loop_start, sfx_infos[pSfx->sfx_info_index].loop_end
);
pChannel->set_sample_loop_count(1);
pChannel->iFlags |= CHANNEL_FLAG_LOOPING;
if (s_show_sounds->integer > 0) {
Com_DPrintf(
"loopblock - %d to %d\n",
sfx_infos[pSfx->sfx_info_index].loop_start,
sfx_infos[pSfx->sfx_info_index].loop_end
);
}
} else {
pChannel->set_sample_loop_count(1);
}
pChannel->set_gain(pChannel->fVolume);
pChannel->set_sample_offset(pBase->iOffset);
pChannel->set_sample_playback_rate(pChannel->iBaseRate * pBase->fNewPitchMult);
if (bStartUnpaused) {
pChannel->resume_sample();
} else {
pChannel->iFlags |= CHANNEL_FLAG_PLAY_DEFERRED;
}
}
/*
==============
S_LoadBase
==============
*/
static void S_LoadBase(channelbasesavegame_t *pBase, openal_channel *pChannel, bool bStartUnpaused)
{
sfxHandle_t handle;
if (!pChannel) {
return;
}
if (!pBase->bPlaying) {
return;
}
if (strstr(pBase->sfx.szName, "null.wav")) {
return;
}
handle = S_RegisterSound(pBase->sfx.szName, (pBase->sfx.iFlags & SFX_FLAG_STREAMED), false);
pChannel->iBaseRate = pBase->iBaseRate;
pChannel->iStartTime = pBase->iStartTime;
pChannel->iEndTime = pBase->iEndTime;
pChannel->iEntChannel = pBase->iEntChannel;
pChannel->iEntNum = pBase->iEntNum;
pChannel->iFlags = pBase->iFlags;
pChannel->fMaxDist = pBase->fMaxDist;
pChannel->fMinDist = pBase->fMinDist;
pChannel->fNewPitchMult = pBase->fNewPitchMult;
VectorCopy(pBase->vOrigin, pChannel->vOrigin);
pChannel->iTime = pBase->iTime;
pChannel->fVolume = pBase->fVolume;
pChannel->pSfx = &s_knownSfx[handle];
S_StartSoundFromBase(pBase, pChannel, &s_knownSfx[handle], bStartUnpaused);
}
/*
==============
S_SaveData
==============
*/
void S_SaveData(soundsystemsavegame_t *pSave)
{
int i;
bool bSoundWasUnpaused;
bSoundWasUnpaused = !s_bSoundPaused;
if (!s_bSoundPaused) {
S_PauseSound();
}
for (i = 0; i < MAX_OPENAL_POSITION_CHANNELS; i++) {
S_StoreBase(&pSave->Channels[i], openal.channel[i]);
}
if (bSoundWasUnpaused) {
S_UnpauseSound();
}
}
/*
==============
S_ReLoad
==============
*/
void S_ReLoad(soundsystemsavegame_t *pSave)
{
int i;
bool bSoundWasUnpaused;
bSoundWasUnpaused = !s_bSoundPaused;
if (!s_bSoundPaused) {
S_PauseSound();
}
for (i = 0; i < MAX_OPENAL_POSITION_CHANNELS; i++) {
S_LoadBase(&pSave->Channels[i], openal.channel[i], bSoundWasUnpaused);
}
if (bSoundWasUnpaused) {
S_UnpauseSound();
}
}
/*
==============
S_InitBase
==============
*/
static void S_InitBase(channelbasesavegame_t *pBase)
{
if (!pBase->bPlaying) {
return;
}
if (strstr(pBase->sfx.szName, "null.wav")) {
return;
}
SV_AddSvsTimeFixup(&pBase->iStartTime);
SV_AddSvsTimeFixup(&pBase->iEndTime);
SV_AddSvsTimeFixup(&pBase->iTime);
}
/*
==============
S_LoadData
==============
*/
void S_LoadData(soundsystemsavegame_t *pSave)
{
int i;
for (i = 0; i < MAX_OPENAL_POSITION_CHANNELS; i++) {
S_InitBase(&pSave->Channels[i]);
}
}
/*
==============
openal_channel::set_velocity
==============
*/
void openal_channel::set_velocity(float v0, float v1, float v2)
{
qalSource3f(source, AL_VELOCITY, v0, v1, v2);
alDieIfError();
}
/*
==============
openal_channel::set_position
==============
*/
void openal_channel::set_position(float v0, float v1, float v2)
{
qalSource3f(source, AL_POSITION, v0, v1, v2);
alDieIfError();
}
/*
==============
openal_channel::set_gain
==============
*/
void openal_channel::set_gain(float gain)
{
qalSourcef(source, AL_GAIN, gain);
alDieIfError();
}
/*
==============
openal_channel::set_no_3d
==============
*/
void openal_channel::set_no_3d()
{
qalSource3f(source, AL_POSITION, 0, 0, 0);
alDieIfError();
qalSource3f(source, AL_VELOCITY, 0, 0, 0);
alDieIfError();
qalSourcei(source, AL_SOURCE_RELATIVE, true);
alDieIfError();
qalSourcei(source, AL_LOOPING, false);
alDieIfError();
qalSourcei(source, AL_ROLLOFF_FACTOR, 0);
alDieIfError();
qalSourcef(source, AL_GAIN, S_GetBaseVolume());
alDieIfError();
}
/*
==============
openal_channel::set_3d
==============
*/
void openal_channel::set_3d()
{
qalSourcei(source, AL_SOURCE_RELATIVE, false);
alDieIfError();
qalSourcei(source, AL_LOOPING, false);
alDieIfError();
qalSourcef(source, AL_ROLLOFF_FACTOR, 1.0f);
alDieIfError();
qalSourcef(source, AL_GAIN, S_GetBaseVolume());
alDieIfError();
qalSourcei(source, AL_DISTANCE_MODEL, AL_LINEAR_DISTANCE_CLAMPED);
alDieIfError();
//
// Added in OPM
//
if (fMinDist > 0) {
qalSourcef(source, AL_REFERENCE_DISTANCE, fMinDist);
alDieIfError();
}
if (fMaxDist > 0) {
qalSourcef(source, AL_MAX_DISTANCE, fMaxDist);
alDieIfError();
}
}
/*
==============
openal_channel::play
==============
*/
void openal_channel::play()
{
qalSourcePlay(source);
alDieIfError();
}
/*
==============
openal_channel::pause
==============
*/
void openal_channel::pause()
{
qalSourcePause(source);
alDieIfError();
}
/*
==============
openal_channel::stop
==============
*/
void openal_channel::stop()
{
qalSourceStop(source);
alDieIfError();
}
/*
==============
openal_channel::is_free
==============
*/
bool openal_channel::is_free()
{
ALint state = sample_status();
return state == AL_INITIAL || state == AL_STOPPED;
}
/*
==============
openal_channel::is_paused
==============
*/
bool openal_channel::is_paused()
{
ALint state = sample_status();
return state == AL_PAUSED;
}
/*
==============
openal_channel::is_playing
==============
*/
bool openal_channel::is_playing()
{
ALint state = sample_status();
return state == AL_PLAYING;
}
/*
==============
openal_channel::force_free
==============
*/
void openal_channel::force_free()
{
stop();
iFlags = 0;
}
/*
==============
openal_channel::set_sfx
==============
*/
bool openal_channel::set_sfx(sfx_t *pSfx)
{
ALint freq = 0;
assert(pSfx->length);
this->pSfx = pSfx;
if (!pSfx->buffer || !qalIsBuffer(pSfx->buffer)) {
if (pSfx->iFlags & SFX_FLAG_MP3) {
qalGenBuffers(1, &pSfx->buffer);
alDieIfError();
if (!S_OPENAL_LoadMP3_Codec(pSfx->name, pSfx)) {
qalDeleteBuffers(1, &pSfx->buffer);
alDieIfError();
Com_DPrintf("OpenAL: Failed to load MP3.\n");
return false;
}
alDieIfError();
} else {
ALenum fmt = S_OPENAL_Format(pSfx->info.width, pSfx->info.channels);
if (!fmt) {
Com_Printf(
"OpenAL: Bad Wave file (%d channels, %d bits) [%s].\n",
pSfx->info.channels,
(int)(pSfx->info.width * 8),
pSfx->name
);
return false;
}
qalGenBuffers(1, &pSfx->buffer);
alDieIfError();
qalBufferData(pSfx->buffer, fmt, &pSfx->data[pSfx->info.dataofs], pSfx->info.datasize - pSfx->info.dataofs, pSfx->info.rate);
alDieIfError();
}
}
qalSourceStop(source);
alDieIfError();
qalSourcei(source, AL_BUFFER, pSfx->buffer);
alDieIfError();
// Get the base frequency
qalGetBufferi(pSfx->buffer, AL_FREQUENCY, &freq);
alDieIfError();
iBaseRate = freq;
return true;
}
/*
==============
openal_channel::start_sample
==============
*/
void openal_channel::start_sample()
{
play();
}
/*
==============
openal_channel::stop_sample
==============
*/
void openal_channel::stop_sample()
{
pause();
}
/*
==============
openal_channel::resume_sample
==============
*/
void openal_channel::resume_sample()
{
play();
}
/*
==============
openal_channel::end_sample
==============
*/
void openal_channel::end_sample()
{
stop();
}
/*
==============
openal_channel::set_sample_pan
==============
*/
void openal_channel::set_sample_pan(S32 pan)
{
const float panning = (pan - 64) / 127.f;
const ALfloat sourcePosition[3] = {panning, 0, sqrtf(1.f - Square(panning))};
qalSourcef(source, AL_ROLLOFF_FACTOR, 0);
qalSourcei(source, AL_SOURCE_RELATIVE, AL_TRUE);
qalSourcefv(source, AL_POSITION, sourcePosition);
}
/*
==============
openal_channel::set_sample_playback_rate
==============
*/
void openal_channel::set_sample_playback_rate(S32 rate)
{
// Fixed in OPM
// Set the pitch in OpenAL
qalSourcef(source, AL_PITCH, rate / (float)iBaseRate);
alDieIfError();
}
/*
==============
openal_channel::sample_playback_rate
==============
*/
S32 openal_channel::sample_playback_rate()
{
float pitch = 1;
ALint freq = 0;
// Fixed in OPM
// The sample rate varies according to the pitch
qalGetSourcef(source, AL_PITCH, &pitch);
alDieIfError();
return buffer_frequency() * pitch;
}
/*
==============
openal_channel::buffer_frequency
==============
*/
U32 openal_channel::buffer_frequency() const
{
ALint freq = 0;
if (buffer) {
qalGetBufferi(buffer, AL_FREQUENCY, &freq);
alDieIfError();
} else if (pSfx) {
qalGetBufferi(pSfx->buffer, AL_FREQUENCY, &freq);
alDieIfError();
} else {
freq = 22050;
}
return freq;
}
/*
==============
openal_channel::sample_volume
==============
*/
S32 openal_channel::sample_volume()
{
ALfloat gain = 0;
qalGetSourcef(source, AL_GAIN, &gain);
alDieIfError();
return (gain / S_GetBaseVolume()) * 100;
}
/*
==============
openal_channel::sample_offset
==============
*/
U32 openal_channel::sample_offset()
{
ALint offset = 0;
qalGetSourcei(source, AL_SAMPLE_OFFSET, &offset);
alDieIfError();
return offset;
}
/*
==============
openal_channel::sample_ms_offset
==============
*/
U32 openal_channel::sample_ms_offset()
{
if (!is_playing()) {
return 0;
}
return sample_offset() * 1000ull / sample_playback_rate();
}
/*
==============
openal_channel::sample_loop_count
==============
*/
U32 openal_channel::sample_loop_count()
{
ALuint queued;
ALuint processed;
S32 left;
qalGetSourceiv(source, AL_BUFFERS_QUEUED, (ALint *)&queued);
alDieIfError();
qalGetSourcei(source, AL_BUFFERS_PROCESSED, (ALint *)&processed);
alDieIfError();
left = queued + ~processed;
if (left < 0) {
left = 0;
}
return left;
}
/*
==============
openal_channel::set_sample_offset
==============
*/
void openal_channel::set_sample_offset(U32 offset)
{
qalSourcei(source, AL_SAMPLE_OFFSET, offset);
alDieIfError();
}
/*
==============
openal_channel::set_sample_ms_offset
==============
*/
void openal_channel::set_sample_ms_offset(U32 offset)
{
set_sample_offset(offset * sample_playback_rate() / 1000);
}
/*
==============
openal_channel::set_sample_loop_count
==============
*/
void openal_channel::set_sample_loop_count(S32 count)
{
ALuint processed = 0;
stop();
qalGetSourceiv(source, AL_BUFFERS_PROCESSED, (ALint *)&processed);
alDieIfError();
for (ALuint i = 0; i < processed; i++) {
ALuint bufName;
qalSourceUnqueueBuffers(source, 1, &bufName);
alDieIfError();
}
if (buffer) {
for (S32 i = 0; i < count; i++) {
qalSourceQueueBuffers(source, 1, &buffer);
alDieIfError();
}
}
if (count == 0) {
qalSourcei(source, AL_LOOPING, true);
alDieIfError();
} else {
qalSourcei(source, AL_LOOPING, false);
alDieIfError();
}
}
/*
==============
openal_channel::set_sample_loop_block
==============
*/
void openal_channel::set_sample_loop_block(S32 start_offset, S32 end_offset)
{
// FIXME: unimplemented
STUB_DESC("sample_loop_block");
}
/*
==============
openal_channel::sample_status
==============
*/
U32 openal_channel::sample_status()
{
ALint retval;
qalGetSourcei(source, AL_SOURCE_STATE, &retval);
alDieIfError();
return retval;
}
/*
==============
MUSIC_LoadSoundtrackFile
==============
*/
qboolean MUSIC_LoadSoundtrackFile(const char *filename)
{
song_t *psong = NULL;
char args[MAX_MUSIC_SONGS][MAX_RES_NAME];
int numargs;
char com_token[MAX_STRING_CHARS];
char alias[128];
char file[128];
char load_path[128];
char *buffer;
char path[MAX_RES_NAME];
int i;
byte *data;
if (strrchr(filename, '.')) {
Com_sprintf(path, sizeof(path), "%s", filename);
} else {
Com_sprintf(path, sizeof(path), "%s.mus", filename);
}
music_numsongs = 0;
FS_ReadFile(path, (void **)&data);
if (!data) {
Com_DPrintf("Couldn't load %s\n", path);
return false;
}
Com_DPrintf("SOUNDTRACK: Loading %s\n", path);
MUSIC_StopAllSongs();
buffer = (char *)data;
load_path[0] = 0;
while (1) {
Q_strncpyz(com_token, COM_GetToken(&buffer, true), sizeof(com_token));
if (!com_token[0]) {
break;
}
if (strlen(com_token) >= MAX_RES_NAME) {
Com_Printf("MUSIC_LoadSoundtrackFile: argument too long, truncating in %s\n", path);
com_token[MAX_RES_NAME - 1] = 0;
}
numargs = 1;
Q_strncpyz(args[0], com_token, sizeof(args[0]));
while (1) {
Q_strncpyz(com_token, COM_GetToken(&buffer, false), sizeof(com_token));
if (!com_token[0]) {
break;
}
if (strlen(com_token) >= MAX_RES_NAME) {
Com_Printf("MUSIC_LoadSoundtrackFile: argument too long, truncating in %s\n", path);
com_token[MAX_RES_NAME - 1] = 0;
}
Q_strncpyz(args[numargs], com_token, sizeof(args[numargs]));
numargs++;
}
if (!Q_stricmp(args[0], "path")) {
Q_strncpyz(load_path, args[1], sizeof(load_path));
if (load_path[strlen(load_path) - 1] != '/' && load_path[strlen(load_path) - 1] != '\\') {
Q_strcat(load_path, sizeof(load_path), "/");
}
} else if (args[0][0] == '!') {
for (i = 0; i < music_numsongs; i++) {
psong = &music_songs[i];
if (!Q_stricmp(psong->alias, &args[0][1])) {
break;
}
}
if (i == music_numsongs) {
Com_Printf("MUSIC_LoadSoundtrackFile: song %s not found, command skipped in %s.\n", &args[0][1], path);
continue;
}
if (!Q_stricmp(args[1], "volume")) {
psong->volume = atof(args[2]);
} else if (!Q_stricmp(args[1], "fadetime")) {
psong->fadetime = atof(args[2]);
} else if (!Q_stricmp(args[1], "loop")) {
psong->flags |= 1;
} else if (!Q_stricmp(args[1], "restart")) {
psong->flags |= 2;
} else if (!Q_stricmp(args[1], "interrupt")) {
psong->fadetime = 0;
psong->flags |= 4 | 2;
} else {
Com_Printf(
"MUSIC_LoadSoundtrackFile: unknown command %s for song %s in %s.\n", args[1], &args[0][1], path
);
}
} else {
if (numargs > 1) {
Q_strncpyz(alias, args[0], sizeof(alias));
Q_strncpyz(file, load_path, sizeof(file));
Q_strcat(file, sizeof(file), args[1]);
} else {
size_t len;
Q_strncpyz(file, load_path, sizeof(file));
Q_strcat(file, sizeof(file), args[1]);
len = strlen(args[0]);
if (len >= 4) {
Q_strncpyz(alias, args[0], len - 4);
} else {
alias[0] = 0;
}
}
if (music_numsongs >= MAX_MUSIC_SONGS) {
Com_Printf("MUSIC_LoadSoundtrackFile: Too many songs in %s, skipping %s.\n", path, alias);
continue;
}
psong = &music_songs[music_numsongs];
Q_strncpyz(psong->alias, alias, sizeof(psong->alias));
Q_strncpyz(psong->path, file, sizeof(psong->path));
music_songs[music_numsongs].fadetime = 1.0;
music_songs[music_numsongs].volume = 1.0;
music_songs[music_numsongs].flags = 0;
music_songs[music_numsongs].current_pos = 0;
music_songs[music_numsongs].mood_num = MusicMood_NameToNum(alias);
music_numsongs++;
}
}
if (!music_numsongs) {
Com_Printf("MUSIC_LoadSoundtrackFile: could not load %s, no songs defined.\n", path);
FS_FreeFile(data);
return false;
}
music_currentsong = -1;
FS_FreeFile(data);
if (music_current_mood == mood_none) {
MUSIC_UpdateMood(mood_normal, mood_normal);
}
return true;
}
/*
==============
MUSIC_SongValid
==============
*/
qboolean MUSIC_SongValid(const char *mood)
{
return MUSIC_FindSong(mood) != -1;
}
/*
==============
MUSIC_Loaded
==============
*/
qboolean MUSIC_Loaded()
{
return music_loaded;
}
/*
==============
Music_Update
==============
*/
void Music_Update()
{
int currentsong;
if (!s_bSoundStarted) {
return;
}
if (!music_active) {
return;
}
if (s_bSoundPaused) {
return;
}
MUSIC_CheckForStoppedSongs();
if (!MUSIC_Loaded() && music_active && strlen(current_soundtrack)) {
MUSIC_LoadSoundtrackFile(current_soundtrack);
music_loaded = true;
}
if (music_currentsong >= 0) {
currentsong = music_songs[music_currentsong].mood_num;
} else {
currentsong = -1;
}
if (music_current_mood == mood_none) {
if (MUSIC_Playing()) {
MUSIC_StopAllSongs();
}
} else if (music_current_mood != currentsong) {
const char *mood = MusicMood_NumToName(music_current_mood);
if (MUSIC_SongValid(mood) && MUSIC_Loaded() && strlen(current_soundtrack)) {
Com_DebugPrintf("Playing %s.\n", mood);
MUSIC_PlaySong(mood);
}
}
if (new_music_volume != music_volume) {
if (music_volume_fade_time > 0) {
if (music_volume_direction == 0) {
music_volume = (cls.realtime - music_volume_start_time) * (new_music_volume - old_music_volume)
/ (music_volume_fade_time * 1000.f)
+ old_music_volume;
if (music_volume > new_music_volume) {
music_volume = new_music_volume;
music_volume_changed = 1;
} else {
music_volume_changed = 1;
}
} else if (music_volume_direction == 1) {
music_volume = 1.0
- (cls.realtime - music_volume_start_time) * (old_music_volume - new_music_volume)
/ (music_volume_fade_time * 1000.f);
if (music_volume >= new_music_volume) {
music_volume_changed = true;
} else {
music_volume = new_music_volume;
music_volume_changed = true;
}
} else {
music_volume_changed = true;
}
} else {
music_volume = new_music_volume;
music_volume_changed = true;
}
}
MUSIC_UpdateMusicVolumes();
}
/*
==============
MUSIC_SongEnded
==============
*/
void MUSIC_SongEnded()
{
Com_DPrintf(
"MUSIC: Song ended, changing from [ %s ] to [ %s ]\n",
MusicMood_NumToName(music_current_mood),
MusicMood_NumToName(music_fallback_mood)
);
music_current_mood = music_fallback_mood;
}
/*
==============
MUSIC_NewSoundtrack
==============
*/
void MUSIC_NewSoundtrack(const char *name)
{
if (!s_bSoundStarted) {
return;
}
if (!Q_stricmp(name, current_soundtrack)) {
return;
}
// Fixed in OPM
// Make sure to not get past the end of the buffer
//strcpy(current_soundtrack, name);
Q_strncpyz(current_soundtrack, name, sizeof(current_soundtrack));
if (!Q_stricmp(current_soundtrack, "none") || !Q_stricmp(current_soundtrack, "")) {
music_active = qfalse;
if (MUSIC_Playing()) {
MUSIC_StopAllSongs();
}
} else {
music_active = qtrue;
MUSIC_LoadSoundtrackFile(name);
music_loaded = qtrue;
}
}
/*
==============
MUSIC_UpdateMood
==============
*/
void MUSIC_UpdateMood(int current, int fallback)
{
static int last_current_mood = -1;
static int last_fallback_mood = -1;
static int current_mood = -1;
static int fallback_mood = -1;
qboolean was_action;
if (current == current_mood && fallback == fallback_mood) {
return;
}
was_action = current == last_current_mood && fallback == last_fallback_mood && current_mood == mood_action;
last_current_mood = current_mood;
last_fallback_mood = fallback_mood;
current_mood = current;
music_current_mood = current;
fallback_mood = fallback;
music_fallback_mood = fallback;
music_active = qtrue;
Com_DPrintf(
"MUSIC: changing from [ %s | %s ] to [ %s | %s ]\n",
MusicMood_NumToName(last_current_mood),
MusicMood_NumToName(last_fallback_mood),
MusicMood_NumToName(current_mood),
MusicMood_NumToName(fallback)
);
if (was_action) {
int songnum = MUSIC_FindSong(MusicMood_NumToName(current_mood));
if (songnum != -1 && (music_songs[songnum].flags & 4)) {
Com_DPrintf(
"MUSIC: restoring music from action state, skipping [ %s ] for [ %s ]\n",
MusicMood_NumToName(current_mood),
MusicMood_NumToName(fallback_mood)
);
music_current_mood = music_fallback_mood;
}
}
}
/*
==============
MUSIC_UpdateVolume
==============
*/
void MUSIC_UpdateVolume(float volume, float fade_time)
{
if (new_music_volume == volume && music_volume_fade_time == fade_time) {
return;
}
old_music_volume = music_volume;
new_music_volume = volume;
music_volume_fade_time = fade_time;
music_volume_start_time = cls.realtime;
if (volume > music_volume) {
music_volume_direction = 0;
} else if (volume < music_volume) {
music_volume_direction = 1;
}
}
/*
==============
MUSIC_StopAllSongs
==============
*/
void MUSIC_StopAllSongs()
{
for (int i = 0; i < MAX_OPENAL_SONGS; i++) {
MUSIC_StopChannel(i);
}
music_currentsong = -1;
}
/*
==============
MUSIC_FreeAllSongs
==============
*/
void MUSIC_FreeAllSongs()
{
MUSIC_StopAllSongs();
MUSIC_UpdateMood(mood_none, mood_none);
current_soundtrack[0] = 0;
music_loaded = false;
// Added in OPM
// Clear the list of songs so the songs from previous maps
// won't get played
music_numsongs = 0;
}
/*
==============
MUSIC_Playing
==============
*/
qboolean MUSIC_Playing()
{
return MUSIC_CurrentSongChannel() != -1;
}
/*
==============
MUSIC_FindSong
==============
*/
int MUSIC_FindSong(const char *name)
{
int i;
for (i = 0; i < music_numsongs; i++) {
if (!Q_stricmp(music_songs[i].alias, name)) {
return i;
}
}
return -1;
}
/*
==============
S_loadsoundtrack
==============
*/
void S_loadsoundtrack()
{
if (Cmd_Argc() != 2) {
Com_Printf("loadsoundtrack <sound track file>\n");
return;
}
MUSIC_LoadSoundtrackFile(Cmd_Argv(1));
music_loaded = true;
Q_strncpyz(current_soundtrack, Cmd_Argv(1), sizeof(current_soundtrack));
}
/*
==============
S_CurrentSoundtrack
==============
*/
const char *S_CurrentSoundtrack()
{
return current_soundtrack;
}
/*
==============
S_PlaySong
==============
*/
void S_PlaySong()
{
if (Cmd_Argc() != 2) {
Com_Printf("playsong <song alias>\n");
return;
}
MUSIC_PlaySong(Cmd_Argv(1));
music_active = true;
}
/*
==============
MUSIC_CurrentSongChannel
==============
*/
int MUSIC_CurrentSongChannel()
{
int channel_number = -1;
int ch_idx = 0;
for (ch_idx = 0; ch_idx < MAX_OPENAL_SONGS; ch_idx++) {
if (openal.chan_song[ch_idx].is_playing() && openal.chan_song[ch_idx].song_number == music_currentsong) {
channel_number = ch_idx;
}
}
return channel_number;
}
/*
==============
MUSIC_StopChannel
==============
*/
void MUSIC_StopChannel(int channel_number)
{
openal_channel *channel = &openal.chan_song[channel_number];
channel->pause();
if (music_songs[channel->song_number].flags & 2) {
music_songs[channel->song_number].current_pos = 0;
} else {
music_songs[channel->song_number].current_pos = channel->sample_ms_offset();
}
channel->stop();
}
/*
==============
MUSIC_PlaySong
==============
*/
qboolean MUSIC_PlaySong(const char *alias)
{
int channel_number;
song_t *song;
int songnum;
int channel_to_play_on;
int fading_song;
openal_channel_two_d_stream *song_channel;
unsigned int loop_start;
int rate;
fading_song = 0;
songnum = MUSIC_FindSong(alias);
if (songnum == -1) {
return true;
}
song = &music_songs[songnum];
if (MUSIC_Playing() && songnum == music_currentsong) {
return true;
}
channel_number = MUSIC_CurrentSongChannel();
music_currentsong = songnum;
if (channel_number != -1) {
if (song->flags & 4) {
MUSIC_StopChannel(channel_number);
} else {
song_channel = &openal.chan_song[channel_number];
song_channel->fading = FADE_OUT;
song_channel->fade_time = (int)song->fadetime;
song_channel->fade_start_time = cls.realtime;
fading_song = true;
}
}
channel_to_play_on = (fading_song != 0) && (channel_number == 0);
song_channel = &openal.chan_song[channel_to_play_on];
if (song_channel->is_playing() || song_channel->is_paused()) {
MUSIC_StopChannel(channel_to_play_on);
}
if (!song_channel->queue_stream(song->path)) {
Com_DPrintf("Could not start music file '%s'!", song->path);
return false;
}
rate = song_channel->sample_playback_rate();
song_channel->song_number = songnum;
if (song->current_pos) {
song_channel->set_sample_ms_offset(song->current_pos);
} else {
song_channel->set_sample_offset(rate * 0.063f);
}
if (song->flags & 1) {
song_channel->set_sample_loop_count(0);
song_channel->set_sample_loop_block(rate * 0.063f, -1);
} else {
song_channel->set_sample_loop_count(1);
}
if (fading_song) {
song_channel->fading = FADE_IN;
song_channel->fade_time = (int)song->fadetime;
song_channel->set_gain(0.0);
song_channel->fade_start_time = cls.realtime;
} else {
song_channel->fading = FADE_NONE;
song_channel->set_gain(S_GetBaseVolume() * (song->volume * s_ambientVolume->value) * s_fVolumeGain);
}
song_channel->play();
return true;
}
/*
==============
MUSIC_UpdateMusicVolumes
==============
*/
void MUSIC_UpdateMusicVolumes()
{
int i;
unsigned int current_time;
float new_volume, max_volume;
if (s_ambientVolume->modified || music_volume_changed) {
s_ambientVolume->modified = false;
for (i = 0; i < MAX_OPENAL_SONGS; i++) {
if (!openal.chan_song[i].is_playing() && !openal.chan_song[i].is_paused()) {
continue;
}
if (openal.chan_song[i].fading != FADE_NONE) {
continue;
}
openal.chan_song[i].set_gain(
S_GetBaseVolume() * (music_songs[openal.chan_song[i].song_number].volume * s_ambientVolume->value)
* s_fVolumeGain * music_volume
);
}
}
for (i = 0; i < MAX_OPENAL_SONGS; i++) {
if (!openal.chan_song[i].is_playing() && !openal.chan_song[i].is_paused()) {
continue;
}
switch (openal.chan_song[i].fading) {
case fade_t::FADE_IN:
max_volume = music_songs[openal.chan_song[i].song_number].volume * s_ambientVolume->value;
new_volume = (unsigned int)(cls.realtime - openal.chan_song[i].fade_start_time)
/ (openal.chan_song[i].fade_time * 1000.f) * max_volume;
if (new_volume > max_volume) {
openal.chan_song[i].set_gain(S_GetBaseVolume() * (max_volume * s_fVolumeGain * music_volume));
openal.chan_song[i].fading = FADE_NONE;
} else {
openal.chan_song[i].set_gain(S_GetBaseVolume() * (new_volume * s_fVolumeGain * music_volume));
}
break;
case fade_t::FADE_OUT:
max_volume = music_songs[openal.chan_song[i].song_number].volume * s_ambientVolume->value;
new_volume = (unsigned int)(cls.realtime - openal.chan_song[i].fade_start_time)
/ (openal.chan_song[i].fade_time * 1000.f) * max_volume;
if (new_volume > 0) {
openal.chan_song[i].set_gain(S_GetBaseVolume() * (new_volume * s_fVolumeGain * music_volume));
} else {
MUSIC_StopChannel(i);
}
break;
default:
break;
}
}
if (s_musicVolume->modified || music_volume_changed) {
s_musicVolume->modified = false;
if (openal.chan_trig_music.is_playing() || openal.chan_trig_music.is_paused()) {
openal.chan_trig_music.set_gain(
S_GetBaseVolume() * (openal.chan_trig_music.fVolume * s_musicVolume->value) * s_fVolumeGain
);
}
}
music_volume_changed = false;
}
/*
==============
MUSIC_CheckForStoppedSongs
==============
*/
void MUSIC_CheckForStoppedSongs()
{
int i;
for (i = 0; i < MAX_OPENAL_SONGS; i++) {
if (!openal.chan_song[i].is_playing()) {
continue;
}
if (openal.chan_song[i].sample_loop_count()) {
continue;
}
MUSIC_FindSong(MusicMood_NumToName(music_fallback_mood));
if (!openal.chan_song[i].is_playing() && !openal.chan_song[i].is_paused()) {
MUSIC_SongEnded();
}
}
}
/*
==============
S_TriggeredMusic_SetupHandle
==============
*/
void S_TriggeredMusic_SetupHandle(const char *pszName, int iLoopCount, int iOffset, qboolean autostart)
{
char *pszFilename;
const char *pszRealName;
float fVolume = 1.0;
AliasListNode_t *pSoundAlias = NULL;
void *stream = NULL;
if (!s_bSoundStarted) {
return;
}
if (openal.chan_trig_music.is_playing() || openal.chan_trig_music.is_paused()) {
Com_DPrintf("Didn't start new triggered music because some was already playing\n");
return;
}
openal.chan_trig_music.stop();
// Fixed in OPM
// Use strncpy instead
//strcpy(openal.tm_filename, pszName);
Q_strncpyz(openal.tm_filename, pszName, sizeof(openal.tm_filename));
openal.tm_loopcount = iLoopCount;
openal.chan_trig_music.set_sample_loop_count(iLoopCount);
pszRealName = Alias_FindRandom(pszName, &pSoundAlias);
if (!pszRealName) {
pszRealName = pszName;
} else if (pSoundAlias) {
fVolume = random() * pSoundAlias->volumeMod + pSoundAlias->volume;
}
pszFilename = FS_BuildOSPath(Cvar_VariableString("fs_basepath"), FS_Gamedir(), pszRealName);
if (!openal.chan_trig_music.queue_stream(pszRealName)) {
S_OPENAL_InitChannel(OPENAL_CHANNEL_TRIGGER_MUSIC_ID, &openal.chan_trig_music);
Com_DPrintf("Could not start triggered music '%s'\n", pszName);
return;
}
openal.chan_trig_music.fVolume = fVolume;
openal.chan_trig_music.set_gain(S_GetBaseVolume() * (fVolume * s_musicVolume->value) * s_fVolumeGain);
openal.chan_trig_music.set_sample_loop_count(iLoopCount);
openal.chan_trig_music.set_sample_offset(iOffset);
// Fixed in OPM
// Play the sound then pause it immediately
// So it can be unpaused upon loading from save
openal.chan_trig_music.play();
if (!autostart) {
openal.chan_trig_music.pause();
}
}
/*
==============
S_TriggeredMusic_Start
==============
*/
void S_TriggeredMusic_Start()
{
if (Cmd_Argc() != 2) {
Com_Printf("tmstart <sound file>\n");
return;
}
S_TriggeredMusic_SetupHandle(Cmd_Argv(1), 1, 0, true);
}
/*
==============
S_TriggeredMusic_StartLoop
==============
*/
void S_TriggeredMusic_StartLoop()
{
if (Cmd_Argc() != 2) {
Com_Printf("tmstartloop <sound file>\n");
return;
}
S_TriggeredMusic_SetupHandle(Cmd_Argv(1), 0, 0, true);
}
/*
==============
S_TriggeredMusic_Stop
==============
*/
void S_TriggeredMusic_Stop()
{
if (!s_bSoundStarted) {
return;
}
openal.chan_trig_music.stop();
// Fixed in OPM
// Make sure to clear the triggered music in case snd_restart is called
openal.tm_filename[0] = 0;
openal.tm_loopcount = 0;
}
/*
==============
S_TriggeredMusic_Pause
==============
*/
void S_TriggeredMusic_Pause()
{
if (!s_bSoundStarted) {
return;
}
if (openal.chan_trig_music.is_playing()) {
openal.chan_trig_music.pause();
}
}
/*
==============
S_TriggeredMusic_Unpause
==============
*/
void S_TriggeredMusic_Unpause()
{
if (!s_bSoundStarted) {
return;
}
if (openal.chan_trig_music.is_paused()) {
openal.chan_trig_music.play();
}
openal.chan_trig_music.set_gain(
S_GetBaseVolume() * (openal.chan_trig_music.fVolume * s_musicVolume->value) * s_fVolumeGain
);
}
/*
==============
S_TriggeredMusic_PlayIntroMusic
==============
*/
void S_TriggeredMusic_PlayIntroMusic()
{
S_TriggeredMusic_SetupHandle("sound/music/mus_MainTheme.mp3", 0, 0, true);
}
/*
==============
S_StopMovieAudio
==============
*/
void S_StopMovieAudio()
{
openal.chan_movie.stop();
}
/*
==============
S_SetupMovieAudio
==============
*/
void S_SetupMovieAudio(const char *pszMovieName)
{
char filename[MAX_QPATH];
S_StopMovieAudio();
COM_StripExtension(pszMovieName, filename, sizeof(filename));
Q_strcat(filename, sizeof(filename), ".mp3");
if (!openal.chan_movie.queue_stream(filename)) {
COM_StripExtension(pszMovieName, filename, sizeof(filename));
Q_strcat(filename, sizeof(filename), ".wav");
if (!openal.chan_movie.queue_stream(filename)) {
Com_DPrintf("Can't find any matching audio file for movie: %s\n", pszMovieName);
return;
}
}
openal.chan_movie.set_gain(S_GetBaseVolume() * 1.5f);
openal.chan_movie.play();
Com_DPrintf("Movie Audio setup: %s\n", filename);
}
/*
==============
S_CurrentMoviePosition
==============
*/
int S_CurrentMoviePosition()
{
return openal.chan_movie.sample_ms_offset();
}
/*
=================
S_AL_Format
=================
*/
static ALuint S_OPENAL_Format(int width, int channels)
{
ALuint format = AL_FORMAT_MONO16;
// Work out format
if (width == 1) {
if (channels == 1) {
format = AL_FORMAT_MONO8;
} else if (channels == 2) {
format = AL_FORMAT_STEREO8;
}
} else if (width == 2) {
if (channels == 1) {
format = AL_FORMAT_MONO16;
} else if (channels == 2) {
format = AL_FORMAT_STEREO16;
}
}
return format;
}
/*
==============
S_OPENAL_LoadMP3_Codec
==============
*/
static bool S_OPENAL_LoadMP3_Codec(const char *_path, sfx_t *pSfx)
{
void *data;
snd_info_t info;
ALuint format;
// Try to load
data = S_CodecLoad(_path, &info);
if (!data) {
return false;
}
format = S_OPENAL_Format(info.width, info.channels);
// Create a buffer
qalGenBuffers(1, &pSfx->buffer);
alDieIfError();
// Fill the buffer
if (info.size == 0) {
// We have no data to buffer, so buffer silence
byte dummyData[2] = {0};
qalBufferData(pSfx->buffer, AL_FORMAT_MONO16, (void *)dummyData, 2, 22050);
} else {
qalBufferData(pSfx->buffer, format, data, info.size, info.rate);
}
alDieIfError();
// Free the memory
Hunk_FreeTempMemory(data);
return true;
}
/*
==============
openal_channel::update
==============
*/
void openal_channel::update() {}
/*
==============
openal_channel_two_d_stream::stop
==============
*/
void openal_channel_two_d_stream::stop()
{
openal_channel::stop();
clear_stream();
}
/*
==============
openal_channel_two_d_stream::set_sfx
==============
*/
bool openal_channel_two_d_stream::set_sfx(sfx_t *pSfx)
{
snd_stream_t *stream;
unsigned int bytesToRead, bytesRead;
ALint freq = 0;
char rawData[MAX_BUFFER_SAMPLES * 2 * 2];
stop();
sampleLoopCount = 1;
this->pSfx = pSfx;
Q_strncpyz(this->fileName, pSfx->name, sizeof(this->fileName));
streamHandle = S_CodecLoad(pSfx->name, NULL);
if (!streamHandle) {
Com_DPrintf("OpenAL: Failed to load MP3.\n");
return false;
}
stream = (snd_stream_t *)streamHandle;
pSfx->info.channels = stream->info.channels;
pSfx->info.dataofs = stream->info.dataofs;
pSfx->info.datasize = stream->info.size;
pSfx->info.rate = stream->info.rate;
pSfx->info.samples = stream->info.samples;
pSfx->info.width = stream->info.width;
pSfx->info.format = S_OPENAL_Format(stream->info.width, stream->info.channels);
if (!pSfx->info.format) {
Com_DPrintf(
"OpenAL: Bad Wave file (%d channels, %d bits) [%s].\n",
pSfx->info.channels,
(int)(pSfx->info.width * 8.f),
pSfx->name
);
S_CodecCloseStream(stream);
return false;
}
qalSourceStop(source);
alDieIfError();
qalGenBuffers(MAX_STREAM_BUFFERS, buffers);
alDieIfError();
// Read a smaller sample
bytesToRead = Q_min(MAX_BUFFER_SAMPLES * stream->info.width * stream->info.channels, sizeof(rawData));
bytesRead = S_CodecReadStream(stream, bytesToRead, rawData);
streamNextOffset = bytesRead;
if (!bytesRead) {
// Valid stream but no data?
S_CodecCloseStream(stream);
return true;
}
qalBufferData(buffers[currentBuf], pSfx->info.format, rawData, bytesRead, stream->info.rate);
alDieIfError();
qalSourceQueueBuffers(source, 1, &buffers[currentBuf]);
alDieIfError();
iBaseRate = stream->info.rate;
currentBuf = (currentBuf + 1) % MAX_STREAM_BUFFERS;
streaming = true;
return true;
}
/*
==============
openal_channel_two_d_stream::set_sample_loop_count
==============
*/
void openal_channel_two_d_stream::set_sample_loop_count(S32 count)
{
sampleLoopCount = count;
sampleLooped = 0;
}
/*
==============
openal_channel_two_d_stream::openal_channel_two_d_stream
==============
*/
openal_channel_two_d_stream::openal_channel_two_d_stream()
{
int i;
streamHandle = NULL;
for (i = 0; i < MAX_STREAM_BUFFERS; i++) {
buffers[i] = 0;
}
currentBuf = 0;
sampleLoopCount = 0;
sampleLooped = 0;
streamNextOffset = 0;
streaming = false;
}
/*
==============
openal_channel_two_d_stream::openal_channel_two_d_stream
==============
*/
openal_channel_two_d_stream::~openal_channel_two_d_stream()
{
clear_stream();
}
/*
==============
openal_channel_two_d_stream::update
==============
*/
void openal_channel_two_d_stream::update()
{
snd_stream_t *stream = (snd_stream_t *)streamHandle;
unsigned int bytesToRead, bytesRead;
ALuint format;
ALint numProcessedBuffers = 0, numQueuedBuffers = 0;
ALint state = sample_status();
// 2 channels with a width of 2
char rawData[MAX_BUFFER_SAMPLES * 2 * 2];
if (!streaming || is_paused()) {
return;
}
qalGetSourcei(source, AL_BUFFERS_PROCESSED, &numProcessedBuffers);
alDieIfError();
qalGetSourcei(source, AL_BUFFERS_QUEUED, &numQueuedBuffers);
alDieIfError();
// Remove processed buffers after half of the queued buffers has been processed
if (numProcessedBuffers >= Q_max(numQueuedBuffers / 2, 1)) {
while (numProcessedBuffers-- > 0) {
ALuint tmpBuffer;
qalSourceUnqueueBuffers(source, 1, &tmpBuffer);
}
}
if (numQueuedBuffers >= MAX_STREAM_BUFFERS) {
return;
}
if (sampleLoopCount && sampleLooped >= sampleLoopCount) {
if (!is_playing()) {
// Can clear the stream
clear_stream();
}
return;
}
bytesToRead = Q_min(MAX_BUFFER_SAMPLES * stream->info.width * stream->info.channels, sizeof(rawData));
format = S_OPENAL_Format(stream->info.width, stream->info.channels);
bytesRead = S_CodecReadStream(stream, bytesToRead, rawData);
streamNextOffset += bytesRead;
if (!bytesRead) {
S_CodecCloseStream(stream);
sampleLooped++;
if (sampleLoopCount && sampleLooped >= sampleLoopCount) {
// Finished the last loop
streamHandle = NULL;
return;
}
//
// Looped, start again from the beginning
//
streamHandle = S_CodecLoad(this->fileName, NULL);
if (!streamHandle) {
clear_stream();
return;
}
stream = (snd_stream_t *)streamHandle;
//
// Re-read the format, we never know...
//
format = S_OPENAL_Format(stream->info.width, stream->info.channels);
bytesRead = S_CodecReadStream(stream, bytesToRead, rawData);
streamNextOffset = bytesRead;
if (!bytesRead) {
S_CodecCloseStream(stream);
streamHandle = NULL;
return;
}
}
qalBufferData(buffers[currentBuf], format, rawData, bytesRead, stream->info.rate);
alDieIfError();
qalSourceQueueBuffers(source, 1, &buffers[currentBuf]);
alDieIfError();
if (!is_playing()) {
// The sample has stopped during stream
// Could be because the storage is slow enough for the buffer
// or because the storage was powering on after standby
play();
}
currentBuf = (currentBuf + 1) % MAX_STREAM_BUFFERS;
}
/*
==============
openal_channel_two_d_stream::sample_offset
==============
*/
U32 openal_channel_two_d_stream::sample_offset()
{
snd_stream_t *stream = (snd_stream_t *)streamHandle;
ALint playerByteOffset = 0;
ALint numProcessedBuffers = 0;
ALint numQueuedBuffers = 0;
unsigned int totalQueueLength = 0;
unsigned int bytesPerSample = 0;
unsigned int offset = 0;
ALint bits = 0, channels = 0;
if (!streaming) {
// no stream
return 0;
}
// Get the byte offset in the queue
qalGetSourcei(source, AL_BYTE_OFFSET, &playerByteOffset);
alDieIfError();
totalQueueLength = getQueueLength();
bytesPerSample = getBytesPerSample();
assert(playerByteOffset < totalQueueLength);
assert(streamNextOffset >= totalQueueLength || stream);
if (streamNextOffset >= totalQueueLength) {
offset = streamNextOffset - totalQueueLength + playerByteOffset;
} else if (stream) {
// it probably means the sound is looped and will restart from the beginning
if (streamNextOffset + playerByteOffset <= totalQueueLength) {
// near the end of the stream
if (stream->info.size > totalQueueLength) {
offset = stream->info.size + streamNextOffset - totalQueueLength + playerByteOffset;
} else {
// the buffer is really small
offset = (streamNextOffset - totalQueueLength + playerByteOffset) % stream->info.size;
}
} else {
// it is past end of stream
// so, start from the beginning of the stream
offset = streamNextOffset + playerByteOffset - totalQueueLength;
}
assert(offset < stream->info.size);
} else {
offset = totalQueueLength - streamNextOffset + playerByteOffset;
}
return offset / bytesPerSample;
}
/*
==============
openal_channel_two_d_stream::buffer_frequency
==============
*/
U32 openal_channel_two_d_stream::buffer_frequency() const
{
unsigned int bufferId;
ALint freq = 0;
bufferId = (currentBuf - 1) % MAX_STREAM_BUFFERS;
qalGetBufferi(buffers[bufferId], AL_FREQUENCY, &freq);
alDieIfError();
return freq;
}
/*
==============
openal_channel_two_d_stream::set_sample_offset
==============
*/
void openal_channel_two_d_stream::set_sample_offset(U32 offset)
{
snd_stream_t *stream;
unsigned int bytesToRead, bytesRead;
unsigned int byteOffset;
unsigned int bytesPerSample;
unsigned int streamPosition;
ALuint format;
ALint numQueuedBuffers;
bool bWasPlaying;
char rawData[MAX_BUFFER_SAMPLES * 2 * 2];
if (!streaming) {
return;
}
stream = (snd_stream_t *)streamHandle;
streamPosition = getCurrentStreamPosition();
bytesPerSample = getBytesPerSample();
byteOffset = offset * bytesPerSample;
if (byteOffset >= streamPosition && byteOffset < streamNextOffset) {
//
// Sample is in the currently streamed data
//
qalSourcei(source, AL_BYTE_OFFSET, byteOffset - streamPosition);
alDieIfError();
return;
}
bWasPlaying = is_playing();
if (stream) {
if (byteOffset < streamPosition) {
//
// New offset is before the current offset,
// reload the stream from the beginning
//
S_CodecCloseStream(stream);
streamHandle = NULL;
} else if (byteOffset >= stream->info.size) {
//
// This shouldn't happen unless the file is different
// from the one used while saving
//
byteOffset %= stream->info.size;
S_CodecCloseStream(stream);
streamHandle = NULL;
}
}
if (!streamHandle) {
streamHandle = S_CodecLoad(this->fileName, NULL);
if (!streamHandle) {
clear_stream();
return;
}
stream = (snd_stream_t *)streamHandle;
streamNextOffset = 0;
}
while (streamNextOffset <= byteOffset) {
streamPosition = streamNextOffset;
bytesToRead = Q_min(MAX_BUFFER_SAMPLES * stream->info.width * stream->info.channels, sizeof(rawData));
bytesRead = S_CodecReadStream(stream, bytesToRead, rawData);
if (!bytesRead) {
clear_stream();
return;
}
streamNextOffset += bytesRead;
}
//
// Stop the sound and remove all buffers from the queue
//
qalSourceStop(source);
alDieIfError();
//
// Remove all buffers from the queue
//
numQueuedBuffers = 0;
qalGetSourcei(source, AL_BUFFERS_QUEUED, &numQueuedBuffers);
alDieIfError();
while (numQueuedBuffers--) {
ALuint b;
qalSourceUnqueueBuffers(source, 1, &b);
alDieIfError();
}
format = S_OPENAL_Format(stream->info.width, stream->info.channels);
qalBufferData(buffers[currentBuf], format, rawData, bytesRead, stream->info.rate);
alDieIfError();
qalSourceQueueBuffers(source, 1, &buffers[currentBuf]);
alDieIfError();
currentBuf = (currentBuf + 1) % MAX_STREAM_BUFFERS;
//
// Now set the source offset
//
qalSourcei(source, AL_BYTE_OFFSET, byteOffset - streamPosition);
alDieIfError();
if (bWasPlaying) {
//
// The sound was stopped when re-streaming
//
play();
}
}
/*
==============
openal_channel_two_d_stream::queue_stream
==============
*/
bool openal_channel_two_d_stream::queue_stream(const char *fileName)
{
snd_stream_t *stream;
unsigned int bytesToRead, bytesRead;
ALint freq = 0;
ALuint format = 0;
ALuint old = 0;
char rawData[MAX_BUFFER_SAMPLES * 2 * 2];
// Store the filename so it can be looped later
Q_strncpyz(this->fileName, fileName, sizeof(this->fileName));
clear_stream();
sampleLoopCount = 1;
//
// Load the file
//
streamHandle = S_CodecLoad(this->fileName, NULL);
if (!streamHandle) {
return false;
}
stream = (snd_stream_t *)streamHandle;
iStartTime = cl.serverTime;
iEndTime = (int)(cl.serverTime + (stream->info.samples / stream->info.rate * 1000.f));
format = S_OPENAL_Format(stream->info.width, stream->info.channels);
if (!format) {
Com_Printf(
"OpenAL: Bad Wave file (%d channels, %d bits) [%s].\n",
pSfx->info.channels,
(int)(pSfx->info.width * 8.f),
pSfx->name
);
S_CodecCloseStream(stream);
return false;
}
qalGenBuffers(MAX_STREAM_BUFFERS, buffers);
alDieIfError();
// Read a smaller sample
bytesToRead = Q_min(MAX_BUFFER_SAMPLES * stream->info.width * stream->info.channels, sizeof(rawData));
bytesRead = S_CodecReadStream(stream, bytesToRead, rawData);
if (!bytesRead) {
// Valid stream but no data?
return true;
}
streamNextOffset = bytesRead;
qalBufferData(buffers[currentBuf], format, rawData, bytesRead, stream->info.rate);
alDieIfError();
qalSourceQueueBuffers(source, 1, &buffers[currentBuf]);
alDieIfError();
currentBuf = (currentBuf + 1) % MAX_STREAM_BUFFERS;
streaming = true;
return true;
}
/*
==============
openal_channel_two_d_stream::clear_stream
==============
*/
void openal_channel_two_d_stream::clear_stream()
{
if (!streaming) {
return;
}
qalSourceStop(source);
qalSourcei(source, AL_BUFFER, 0);
qalDeleteBuffers(MAX_STREAM_BUFFERS, buffers);
if (streamHandle) {
S_CodecCloseStream((snd_stream_t *)streamHandle);
}
fileName[0] = 0;
streamHandle = NULL;
currentBuf = 0;
streamNextOffset = 0;
streaming = false;
sampleLooped = 0;
}
/*
==============
openal_channel_two_d_stream::getQueueLength
Return the total length of all buffers being streamed.
==============
*/
unsigned int openal_channel_two_d_stream::getQueueLength() const
{
ALint numQueuedBuffers = 0;
unsigned int totalQueueLength = 0;
unsigned int bufferId;
unsigned int i;
qalGetSourcei(source, AL_BUFFERS_QUEUED, &numQueuedBuffers);
alDieIfError();
for (i = 0; i < numQueuedBuffers; i++) {
ALint bufferSize = 0;
bufferId = (currentBuf - i - 1) % MAX_STREAM_BUFFERS;
qalGetBufferi(buffers[bufferId], AL_SIZE, &bufferSize);
totalQueueLength += bufferSize;
}
return totalQueueLength;
}
/*
==============
openal_channel_two_d_stream::getCurrentStreamPosition
Return the current position in the sound being streamed.
==============
*/
unsigned int openal_channel_two_d_stream::getCurrentStreamPosition() const
{
unsigned int queueLength = getQueueLength();
if (streamNextOffset >= queueLength) {
return streamNextOffset - queueLength;
} else if (streamHandle) {
snd_stream_t* stream = (snd_stream_t*)streamHandle;
return (stream->info.size + streamNextOffset - queueLength) % stream->info.size;
} else {
return queueLength - streamNextOffset;
}
}
/*
==============
openal_channel_two_d_stream::getBytesPerSample
Return the number of bytes per sample.
It assumes that all pending buffers have the same channels and bits.
==============
*/
unsigned int openal_channel_two_d_stream::getBytesPerSample() const
{
unsigned int bufferId;
ALint bits = 0, channels = 0;
bufferId = (currentBuf - 1) % MAX_STREAM_BUFFERS;
qalGetBufferi(buffers[bufferId], AL_BITS, &bits);
alDieIfError();
qalGetBufferi(buffers[bufferId], AL_CHANNELS, &channels);
alDieIfError();
return bits * channels / 8;
}