From ba0ed7504d290d58e912bcc49894653693a6135e Mon Sep 17 00:00:00 2001 From: OM Date: Wed, 24 May 2023 19:40:09 +0200 Subject: [PATCH] New sound system --- code/client/new/snd_main_new.cpp | 292 ++++ code/client/new/snd_public_new.h | 40 + code/client/snd_adpcm.c | 8 +- code/client/snd_altivec.c | 229 +++ code/client/snd_codec.c | 237 +++ code/client/snd_codec.h | 107 ++ code/client/snd_codec_ogg.c | 475 ++++++ code/client/snd_codec_opus.c | 449 +++++ code/client/snd_codec_wav.c | 291 ++++ code/client/snd_dma.c | 1181 +++++-------- code/client/snd_local.h | 108 +- code/client/snd_main.c | 365 +--- code/client/snd_mem.c | 262 +-- code/client/snd_mix.c | 308 ++-- code/client/snd_openal.c | 2734 ++++++++++++++++++++++++++++++ code/client/snd_public.h | 69 +- code/client/snd_wavelet.c | 35 +- 17 files changed, 5594 insertions(+), 1596 deletions(-) create mode 100644 code/client/new/snd_main_new.cpp create mode 100644 code/client/new/snd_public_new.h create mode 100644 code/client/snd_altivec.c create mode 100644 code/client/snd_codec.c create mode 100644 code/client/snd_codec.h create mode 100644 code/client/snd_codec_ogg.c create mode 100644 code/client/snd_codec_opus.c create mode 100644 code/client/snd_codec_wav.c create mode 100644 code/client/snd_openal.c diff --git a/code/client/new/snd_main_new.cpp b/code/client/new/snd_main_new.cpp new file mode 100644 index 00000000..bea055b5 --- /dev/null +++ b/code/client/new/snd_main_new.cpp @@ -0,0 +1,292 @@ +#include "../snd_local.h" + +/* +================= +S_IsSoundPlaying +================= +*/ +int S_IsSoundPlaying(int channelNumber, const char* name) +{ + // FIXME: stub + STUB(); +} + +/* +================= +MUSIC_Pause +================= +*/ +void MUSIC_Pause() +{ + // FIXME: stub + STUB(); +} + +/* +================= +MUSIC_Unpause +================= +*/ +void MUSIC_Unpause() +{ + // FIXME: stub + STUB(); +} + +/* +================= +MUSIC_LoadSoundtrackFile +================= +*/ +qboolean MUSIC_LoadSoundtrackFile(const char* filename) +{ + // FIXME: stub + STUB(); + return qfalse; +} + +/* +================= +MUSIC_SongValid +================= +*/ +qboolean MUSIC_SongValid(const char* mood) +{ + // FIXME: stub + STUB(); + return qfalse; +} + +/* +================= +MUSIC_Loaded +================= +*/ +qboolean MUSIC_Loaded(void) +{ + // FIXME: stub + STUB(); + return qfalse; +} + +/* +================= +Music_Update +================= +*/ +void Music_Update(void) +{ + // FIXME: stub + STUB(); +} + +/* +================= +MUSIC_SongEnded +================= +*/ +void MUSIC_SongEnded(void) +{ + // FIXME: stub + STUB(); +} + +/* +================= +S_StartSound +================= +*/ +void MUSIC_NewSoundtrack(const char* name) +{ + // FIXME: stub + STUB(); +} + +/* +================= +MUSIC_UpdateMood +================= +*/ +void MUSIC_UpdateMood(int current, int fallback) +{ + // FIXME: stub + //STUB(); +} + +/* +================= +MUSIC_UpdateVolume +================= +*/ +void MUSIC_UpdateVolume(float volume, float fade_time) +{ + // FIXME: stub + STUB(); +} + +/* +================= +MUSIC_StopAllSongs +================= +*/ +void MUSIC_StopAllSongs(void) +{ + // FIXME: stub + STUB(); +} + +/* +================= +MUSIC_FreeAllSongs +================= +*/ +void MUSIC_FreeAllSongs(void) +{ + // FIXME: stub + STUB(); +} + +/* +================= +MUSIC_Playing +================= +*/ +qboolean MUSIC_Playing(void) +{ + // FIXME: stub + STUB(); + return qfalse; +} + +/* +================= +MUSIC_FindSong +================= +*/ +int MUSIC_FindSong(const char* name) +{ + // FIXME: stub + STUB(); + return 0; +} + +/* +================= +MUSIC_CurrentSongChannel +================= +*/ +int MUSIC_CurrentSongChannel(void) +{ + // FIXME: stub + STUB(); + return 0; +} + +/* +================= +MUSIC_StopChannel +================= +*/ +void MUSIC_StopChannel(int channel_number) +{ + // FIXME: stub + STUB(); +} + +/* +================= +MUSIC_PlaySong +================= +*/ +qboolean MUSIC_PlaySong(const char* alias) +{ + // FIXME: stub + STUB(); + return qfalse; +} + +/* +================= +MUSIC_UpdateMusicVolumes +================= +*/ +void MUSIC_UpdateMusicVolumes(void) +{ + // FIXME: stub + STUB(); +} + +/* +================= +MUSIC_CheckForStoppedSongs +================= +*/ +void MUSIC_CheckForStoppedSongs(void) +{ + // FIXME: stub + STUB(); +} + +/* +================= +S_IsSoundRegistered +================= +*/ +qboolean S_IsSoundRegistered(const char* name) +{ + // FIXME: stub + return qfalse; +} +/* +================= +S_GetSoundTime +================= +*/ +float S_GetSoundTime(sfxHandle_t handle) +{ + // FIXME: stub + STUB(); + return 0.0; +} + +/* +================= +S_SetGlobalAmbientVolumeLevel +================= +*/ +void S_SetGlobalAmbientVolumeLevel(float volume) +{ + // FIXME: stub + STUB(); +} + +/* +================= +S_SetReverb +================= +*/ +void S_SetReverb(int reverb_type, float reverb_level) +{ + // FIXME: stub + STUB(); +} + +/* +================= +S_EndRegistration +================= +*/ +void S_EndRegistration(void) +{ + // FIXME: stub +} + +void S_UpdateEntity(int entityNum, const vec3_t origin, const vec3_t velocity, qboolean use_listener) +{ + // FIXME: stub +} + +void S_FadeSound(float fTime) +{ + // FIXME: stub +} \ No newline at end of file diff --git a/code/client/new/snd_public_new.h b/code/client/new/snd_public_new.h new file mode 100644 index 00000000..a82f6d58 --- /dev/null +++ b/code/client/new/snd_public_new.h @@ -0,0 +1,40 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +// Music soundtrack + +void MUSIC_Pause(); +void MUSIC_Unpause(); +qboolean MUSIC_LoadSoundtrackFile(const char* filename); +qboolean MUSIC_SongValid(const char* mood); +qboolean MUSIC_Loaded(void); +void Music_Update(void); +void MUSIC_SongEnded(void); +void MUSIC_NewSoundtrack(const char* name); +void MUSIC_UpdateMood(int current, int fallback); +void MUSIC_UpdateVolume(float volume, float fade_time); +void MUSIC_StopAllSongs(void); +void MUSIC_FreeAllSongs(void); +qboolean MUSIC_Playing(void); +int MUSIC_FindSong(const char* name); +int MUSIC_CurrentSongChannel(void); +void MUSIC_StopChannel(int channel_number); +qboolean MUSIC_PlaySong(const char* alias); +void MUSIC_UpdateMusicVolumes(void); +void MUSIC_CheckForStoppedSongs(void); + +float S_GetSoundTime(sfxHandle_t handle); +void S_SetGlobalAmbientVolumeLevel(float volume); +void S_SetReverb(int reverb_type, float reverb_level); +int S_IsSoundPlaying(int channelNumber, const char* name); + +void S_RespatializeOld(int entityNum, const vec3_t origin, vec3_t axis[3]); +void S_UpdateEntity(int entityNum, const vec3_t origin, const vec3_t velocity, qboolean use_listener); +void S_FadeSound(float fTime); + +#ifdef __cplusplus +} +#endif diff --git a/code/client/snd_adpcm.c b/code/client/snd_adpcm.c index 3e335fb4..1a114edf 100644 --- a/code/client/snd_adpcm.c +++ b/code/client/snd_adpcm.c @@ -25,7 +25,7 @@ OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. /* ** Intel/DVI ADPCM coder/decoder. ** -** The algorithm for this coder was taken from the IMA Compatability Project +** The algorithm for this coder was taken from the IMA Compatibility Project ** proceedings, Vol 2, Number 2; May 1992. ** ** Version 1.2, 18-Dec-92. @@ -278,7 +278,7 @@ void S_AdpcmGetSamples(sndBuffer *chunk, short *to) { out = (byte *)chunk->sndChunk; // get samples - S_AdpcmDecode( out, to, SND_CHUNK_SIZE_BYTE*2, &state ); + S_AdpcmDecode((char *) out, to, SND_CHUNK_SIZE_BYTE*2, &state ); } @@ -310,7 +310,7 @@ void S_AdpcmEncodeSound( sfx_t *sfx, short *samples ) { newchunk = SND_malloc(); if (sfx->soundData == NULL) { sfx->soundData = newchunk; - } else { + } else if (chunk != NULL) { chunk->next = newchunk; } chunk = newchunk; @@ -322,7 +322,7 @@ void S_AdpcmEncodeSound( sfx_t *sfx, short *samples ) { out = (byte *)chunk->sndChunk; // encode the samples - S_AdpcmEncode( samples + inOffset, out, n, &state ); + S_AdpcmEncode( samples + inOffset, (char *) out, n, &state ); inOffset += n; count -= n; diff --git a/code/client/snd_altivec.c b/code/client/snd_altivec.c new file mode 100644 index 00000000..e051dda9 --- /dev/null +++ b/code/client/snd_altivec.c @@ -0,0 +1,229 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena 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. + +Quake III Arena 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 Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +/* This file is only compiled for PowerPC builds with Altivec support. + Altivec intrinsics need to be in a separate file, so GCC's -maltivec + command line can enable them, but give us the option to _not_ use that + on other files, where the compiler might then generate Altivec + instructions for normal floating point, crashing on G3 (etc) processors. */ + +#include "client.h" +#include "snd_local.h" + +#if idppc_altivec + +#if !defined(__APPLE__) +#include +#endif + +void S_PaintChannelFrom16_altivec( portable_samplepair_t paintbuffer[PAINTBUFFER_SIZE], int snd_vol, channel_t *ch, const sfx_t *sc, int count, int sampleOffset, int bufferOffset ) { + int data, aoff, boff; + int leftvol, rightvol; + int i, j; + portable_samplepair_t *samp; + sndBuffer *chunk; + short *samples; + float ooff, fdata[2], fdiv, fleftvol, frightvol; + + if (sc->soundChannels <= 0) { + return; + } + + samp = &paintbuffer[ bufferOffset ]; + + if (ch->doppler) { + sampleOffset = sampleOffset*ch->oldDopplerScale; + } + + if ( sc->soundChannels == 2 ) { + sampleOffset *= sc->soundChannels; + + if ( sampleOffset & 1 ) { + sampleOffset &= ~1; + } + } + + chunk = sc->soundData; + while (sampleOffset>=SND_CHUNK_SIZE) { + chunk = chunk->next; + sampleOffset -= SND_CHUNK_SIZE; + if (!chunk) { + chunk = sc->soundData; + } + } + + if (!ch->doppler || ch->dopplerScale==1.0f) { + vector signed short volume_vec; + vector unsigned int volume_shift; + int vectorCount, samplesLeft, chunkSamplesLeft; + leftvol = ch->leftvol*snd_vol; + rightvol = ch->rightvol*snd_vol; + samples = chunk->sndChunk; + ((short *)&volume_vec)[0] = leftvol; + ((short *)&volume_vec)[1] = leftvol; + ((short *)&volume_vec)[4] = leftvol; + ((short *)&volume_vec)[5] = leftvol; + ((short *)&volume_vec)[2] = rightvol; + ((short *)&volume_vec)[3] = rightvol; + ((short *)&volume_vec)[6] = rightvol; + ((short *)&volume_vec)[7] = rightvol; + volume_shift = vec_splat_u32(8); + i = 0; + + while(i < count) { + /* Try to align destination to 16-byte boundary */ + while(i < count && (((unsigned long)&samp[i] & 0x1f) || ((count-i) < 8) || ((SND_CHUNK_SIZE - sampleOffset) < 8))) { + data = samples[sampleOffset++]; + samp[i].left += (data * leftvol)>>8; + + if ( sc->soundChannels == 2 ) { + data = samples[sampleOffset++]; + } + samp[i].right += (data * rightvol)>>8; + + if (sampleOffset == SND_CHUNK_SIZE) { + chunk = chunk->next; + samples = chunk->sndChunk; + sampleOffset = 0; + } + i++; + } + /* Destination is now aligned. Process as many 8-sample + chunks as we can before we run out of room from the current + sound chunk. We do 8 per loop to avoid extra source data reads. */ + samplesLeft = count - i; + chunkSamplesLeft = SND_CHUNK_SIZE - sampleOffset; + if(samplesLeft > chunkSamplesLeft) + samplesLeft = chunkSamplesLeft; + + vectorCount = samplesLeft / 8; + + if(vectorCount) + { + vector unsigned char tmp; + vector short s0, s1, sampleData0, sampleData1; + vector signed int merge0, merge1; + vector signed int d0, d1, d2, d3; + vector unsigned char samplePermute0 = + VECCONST_UINT8(0, 1, 4, 5, 0, 1, 4, 5, 2, 3, 6, 7, 2, 3, 6, 7); + vector unsigned char samplePermute1 = + VECCONST_UINT8(8, 9, 12, 13, 8, 9, 12, 13, 10, 11, 14, 15, 10, 11, 14, 15); + vector unsigned char loadPermute0, loadPermute1; + + // Rather than permute the vectors after we load them to do the sample + // replication and rearrangement, we permute the alignment vector so + // we do everything in one step below and avoid data shuffling. + tmp = vec_lvsl(0,&samples[sampleOffset]); + loadPermute0 = vec_perm(tmp,tmp,samplePermute0); + loadPermute1 = vec_perm(tmp,tmp,samplePermute1); + + s0 = *(vector short *)&samples[sampleOffset]; + while(vectorCount) + { + /* Load up source (16-bit) sample data */ + s1 = *(vector short *)&samples[sampleOffset+7]; + + /* Load up destination sample data */ + d0 = *(vector signed int *)&samp[i]; + d1 = *(vector signed int *)&samp[i+2]; + d2 = *(vector signed int *)&samp[i+4]; + d3 = *(vector signed int *)&samp[i+6]; + + sampleData0 = vec_perm(s0,s1,loadPermute0); + sampleData1 = vec_perm(s0,s1,loadPermute1); + + merge0 = vec_mule(sampleData0,volume_vec); + merge0 = vec_sra(merge0,volume_shift); /* Shift down to proper range */ + + merge1 = vec_mulo(sampleData0,volume_vec); + merge1 = vec_sra(merge1,volume_shift); + + d0 = vec_add(merge0,d0); + d1 = vec_add(merge1,d1); + + merge0 = vec_mule(sampleData1,volume_vec); + merge0 = vec_sra(merge0,volume_shift); /* Shift down to proper range */ + + merge1 = vec_mulo(sampleData1,volume_vec); + merge1 = vec_sra(merge1,volume_shift); + + d2 = vec_add(merge0,d2); + d3 = vec_add(merge1,d3); + + /* Store destination sample data */ + *(vector signed int *)&samp[i] = d0; + *(vector signed int *)&samp[i+2] = d1; + *(vector signed int *)&samp[i+4] = d2; + *(vector signed int *)&samp[i+6] = d3; + + i += 8; + vectorCount--; + s0 = s1; + sampleOffset += 8; + } + if (sampleOffset == SND_CHUNK_SIZE) { + chunk = chunk->next; + samples = chunk->sndChunk; + sampleOffset = 0; + } + } + } + } else { + fleftvol = ch->leftvol*snd_vol; + frightvol = ch->rightvol*snd_vol; + + ooff = sampleOffset; + samples = chunk->sndChunk; + + for ( i=0 ; idopplerScale * sc->soundChannels; + boff = ooff; + fdata[0] = fdata[1] = 0; + for (j=aoff; jsoundChannels) { + if (j == SND_CHUNK_SIZE) { + chunk = chunk->next; + if (!chunk) { + chunk = sc->soundData; + } + samples = chunk->sndChunk; + ooff -= SND_CHUNK_SIZE; + } + if ( sc->soundChannels == 2 ) { + fdata[0] += samples[j&(SND_CHUNK_SIZE-1)]; + fdata[1] += samples[(j+1)&(SND_CHUNK_SIZE-1)]; + } else { + fdata[0] += samples[j&(SND_CHUNK_SIZE-1)]; + fdata[1] += samples[j&(SND_CHUNK_SIZE-1)]; + } + } + fdiv = 256 * (boff-aoff) / sc->soundChannels; + samp[i].left += (fdata[0] * fleftvol)/fdiv; + samp[i].right += (fdata[1] * frightvol)/fdiv; + } + } +} + + +#endif + diff --git a/code/client/snd_codec.c b/code/client/snd_codec.c new file mode 100644 index 00000000..b1923277 --- /dev/null +++ b/code/client/snd_codec.c @@ -0,0 +1,237 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2005 Stuart Dalton (badcdev@gmail.com) + +This file is part of Quake III Arena source code. + +Quake III Arena 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. + +Quake III Arena 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 Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "client.h" +#include "snd_codec.h" + +static snd_codec_t *codecs; + +/* +================= +S_CodecGetSound + +Opens/loads a sound, tries codec based on the sound's file extension +then tries all supported codecs. +================= +*/ +static void *S_CodecGetSound(const char *filename, snd_info_t *info) +{ + snd_codec_t *codec; + snd_codec_t *orgCodec = NULL; + qboolean orgNameFailed = qfalse; + char localName[ MAX_QPATH ]; + const char *ext; + char altName[ MAX_QPATH ]; + void *rtn = NULL; + + Q_strncpyz(localName, filename, MAX_QPATH); + + ext = COM_GetExtension(localName); + + if( *ext ) + { + // Look for the correct loader and use it + for( codec = codecs; codec; codec = codec->next ) + { + if( !Q_stricmp( ext, codec->ext ) ) + { + // Load + if( info ) + rtn = codec->load(localName, info); + else + rtn = codec->open(localName); + break; + } + } + + // A loader was found + if( codec ) + { + if( !rtn ) + { + // Loader failed, most likely because the file isn't there; + // try again without the extension + orgNameFailed = qtrue; + orgCodec = codec; + COM_StripExtension( filename, localName, MAX_QPATH ); + } + else + { + // Something loaded + return rtn; + } + } + } + + // Try and find a suitable match using all + // the sound codecs supported + for( codec = codecs; codec; codec = codec->next ) + { + if( codec == orgCodec ) + continue; + + Com_sprintf( altName, sizeof (altName), "%s.%s", localName, codec->ext ); + + // Load + if( info ) + rtn = codec->load(altName, info); + else + rtn = codec->open(altName); + + if( rtn ) + { + if( orgNameFailed ) + { + Com_DPrintf(S_COLOR_YELLOW "WARNING: %s not present, using %s instead\n", + filename, altName ); + } + + return rtn; + } + } + + Com_Printf(S_COLOR_YELLOW "WARNING: Failed to %s sound %s!\n", info ? "load" : "open", filename); + + return NULL; +} + +/* +================= +S_CodecInit +================= +*/ +void S_CodecInit() +{ + codecs = NULL; + +#ifdef USE_CODEC_OPUS + S_CodecRegister(&opus_codec); +#endif + +#ifdef USE_CODEC_VORBIS + S_CodecRegister(&ogg_codec); +#endif + +// Register wav codec last so that it is always tried first when a file extension was not found + S_CodecRegister(&wav_codec); +} + +/* +================= +S_CodecShutdown +================= +*/ +void S_CodecShutdown() +{ + codecs = NULL; +} + +/* +================= +S_CodecRegister +================= +*/ +void S_CodecRegister(snd_codec_t *codec) +{ + codec->next = codecs; + codecs = codec; +} + +/* +================= +S_CodecLoad +================= +*/ +void *S_CodecLoad(const char *filename, snd_info_t *info) +{ + return S_CodecGetSound(filename, info); +} + +/* +================= +S_CodecOpenStream +================= +*/ +snd_stream_t *S_CodecOpenStream(const char *filename) +{ + return S_CodecGetSound(filename, NULL); +} + +void S_CodecCloseStream(snd_stream_t *stream) +{ + stream->codec->close(stream); +} + +int S_CodecReadStream(snd_stream_t *stream, int bytes, void *buffer) +{ + return stream->codec->read(stream, bytes, buffer); +} + +//======================================================================= +// Util functions (used by codecs) + +/* +================= +S_CodecUtilOpen +================= +*/ +snd_stream_t *S_CodecUtilOpen(const char *filename, snd_codec_t *codec) +{ + snd_stream_t *stream; + fileHandle_t hnd; + int length; + + // Try to open the file + length = FS_FOpenFileRead(filename, &hnd, qtrue); + if(!hnd) + { + Com_DPrintf("Can't read sound file %s\n", filename); + return NULL; + } + + // Allocate a stream + stream = Z_Malloc(sizeof(snd_stream_t)); + if(!stream) + { + FS_FCloseFile(hnd); + return NULL; + } + + // Copy over, return + stream->codec = codec; + stream->file = hnd; + stream->length = length; + return stream; +} + +/* +================= +S_CodecUtilClose +================= +*/ +void S_CodecUtilClose(snd_stream_t **stream) +{ + FS_FCloseFile((*stream)->file); + Z_Free(*stream); + *stream = NULL; +} diff --git a/code/client/snd_codec.h b/code/client/snd_codec.h new file mode 100644 index 00000000..5ab1202f --- /dev/null +++ b/code/client/snd_codec.h @@ -0,0 +1,107 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2005 Stuart Dalton (badcdev@gmail.com) + +This file is part of Quake III Arena source code. + +Quake III Arena 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. + +Quake III Arena 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 Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#ifndef _SND_CODEC_H_ +#define _SND_CODEC_H_ + +#include "../qcommon/q_shared.h" +#include "../qcommon/qcommon.h" + +typedef struct snd_info_s +{ + int rate; + int width; + int channels; + int samples; + int size; + int dataofs; +} snd_info_t; + +typedef struct snd_codec_s snd_codec_t; + +typedef struct snd_stream_s +{ + snd_codec_t *codec; + fileHandle_t file; + snd_info_t info; + int length; + int pos; + void *ptr; +} snd_stream_t; + +// Codec functions +typedef void *(*CODEC_LOAD)(const char *filename, snd_info_t *info); +typedef snd_stream_t *(*CODEC_OPEN)(const char *filename); +typedef int (*CODEC_READ)(snd_stream_t *stream, int bytes, void *buffer); +typedef void (*CODEC_CLOSE)(snd_stream_t *stream); + +// Codec data structure +struct snd_codec_s +{ + char *ext; + CODEC_LOAD load; + CODEC_OPEN open; + CODEC_READ read; + CODEC_CLOSE close; + snd_codec_t *next; +}; + +// Codec management +void S_CodecInit( void ); +void S_CodecShutdown( void ); +void S_CodecRegister(snd_codec_t *codec); +void *S_CodecLoad(const char *filename, snd_info_t *info); +snd_stream_t *S_CodecOpenStream(const char *filename); +void S_CodecCloseStream(snd_stream_t *stream); +int S_CodecReadStream(snd_stream_t *stream, int bytes, void *buffer); + +// Util functions (used by codecs) +snd_stream_t *S_CodecUtilOpen(const char *filename, snd_codec_t *codec); +void S_CodecUtilClose(snd_stream_t **stream); + +// WAV Codec +extern snd_codec_t wav_codec; +void *S_WAV_CodecLoad(const char *filename, snd_info_t *info); +snd_stream_t *S_WAV_CodecOpenStream(const char *filename); +void S_WAV_CodecCloseStream(snd_stream_t *stream); +int S_WAV_CodecReadStream(snd_stream_t *stream, int bytes, void *buffer); + +// Ogg Vorbis codec +#ifdef USE_CODEC_VORBIS +extern snd_codec_t ogg_codec; +void *S_OGG_CodecLoad(const char *filename, snd_info_t *info); +snd_stream_t *S_OGG_CodecOpenStream(const char *filename); +void S_OGG_CodecCloseStream(snd_stream_t *stream); +int S_OGG_CodecReadStream(snd_stream_t *stream, int bytes, void *buffer); +#endif // USE_CODEC_VORBIS + +// Ogg Opus codec +#ifdef USE_CODEC_OPUS +extern snd_codec_t opus_codec; +void *S_OggOpus_CodecLoad(const char *filename, snd_info_t *info); +snd_stream_t *S_OggOpus_CodecOpenStream(const char *filename); +void S_OggOpus_CodecCloseStream(snd_stream_t *stream); +int S_OggOpus_CodecReadStream(snd_stream_t *stream, int bytes, void *buffer); +#endif // USE_CODEC_OPUS + +#endif // !_SND_CODEC_H_ diff --git a/code/client/snd_codec_ogg.c b/code/client/snd_codec_ogg.c new file mode 100644 index 00000000..2fa6abf0 --- /dev/null +++ b/code/client/snd_codec_ogg.c @@ -0,0 +1,475 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2005 Stuart Dalton (badcdev@gmail.com) +Copyright (C) 2005-2006 Joerg Dietrich + +This file is part of Quake III Arena source code. + +Quake III Arena 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. + +Quake III Arena 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 Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +// OGG support is enabled by this define +#ifdef USE_CODEC_VORBIS + +// includes for the Q3 sound system +#include "client.h" +#include "snd_codec.h" + +// includes for the OGG codec +#include +#define OV_EXCLUDE_STATIC_CALLBACKS +#include + +// The OGG codec can return the samples in a number of different formats, +// we use the standard signed short format. +#define OGG_SAMPLEWIDTH 2 + +// Q3 OGG codec +snd_codec_t ogg_codec = +{ + "ogg", + S_OGG_CodecLoad, + S_OGG_CodecOpenStream, + S_OGG_CodecReadStream, + S_OGG_CodecCloseStream, + NULL +}; + +// callbacks for vobisfile + +// fread() replacement +size_t S_OGG_Callback_read(void *ptr, size_t size, size_t nmemb, void *datasource) +{ + snd_stream_t *stream; + int byteSize = 0; + int bytesRead = 0; + size_t nMembRead = 0; + + // check if input is valid + if(!ptr) + { + errno = EFAULT; + return 0; + } + + if(!(size && nmemb)) + { + // It's not an error, caller just wants zero bytes! + errno = 0; + return 0; + } + + if(!datasource) + { + errno = EBADF; + return 0; + } + + // we use a snd_stream_t in the generic pointer to pass around + stream = (snd_stream_t *) datasource; + + // FS_Read does not support multi-byte elements + byteSize = nmemb * size; + + // read it with the Q3 function FS_Read() + bytesRead = FS_Read(ptr, byteSize, stream->file); + + // update the file position + stream->pos += bytesRead; + + // this function returns the number of elements read not the number of bytes + nMembRead = bytesRead / size; + + // even if the last member is only read partially + // it is counted as a whole in the return value + if(bytesRead % size) + { + nMembRead++; + } + + return nMembRead; +} + +// fseek() replacement +int S_OGG_Callback_seek(void *datasource, ogg_int64_t offset, int whence) +{ + snd_stream_t *stream; + int retVal = 0; + + // check if input is valid + if(!datasource) + { + errno = EBADF; + return -1; + } + + // snd_stream_t in the generic pointer + stream = (snd_stream_t *) datasource; + + // we must map the whence to its Q3 counterpart + switch(whence) + { + case SEEK_SET : + { + // set the file position in the actual file with the Q3 function + retVal = FS_Seek(stream->file, (long) offset, FS_SEEK_SET); + + // something has gone wrong, so we return here + if(retVal < 0) + { + return retVal; + } + + // keep track of file position + stream->pos = (int) offset; + break; + } + + case SEEK_CUR : + { + // set the file position in the actual file with the Q3 function + retVal = FS_Seek(stream->file, (long) offset, FS_SEEK_CUR); + + // something has gone wrong, so we return here + if(retVal < 0) + { + return retVal; + } + + // keep track of file position + stream->pos += (int) offset; + break; + } + + case SEEK_END : + { + // set the file position in the actual file with the Q3 function + retVal = FS_Seek(stream->file, (long) offset, FS_SEEK_END); + + // something has gone wrong, so we return here + if(retVal < 0) + { + return retVal; + } + + // keep track of file position + stream->pos = stream->length + (int) offset; + break; + } + + default : + { + // unknown whence, so we return an error + errno = EINVAL; + return -1; + } + } + + // stream->pos shouldn't be smaller than zero or bigger than the filesize + stream->pos = (stream->pos < 0) ? 0 : stream->pos; + stream->pos = (stream->pos > stream->length) ? stream->length : stream->pos; + + return 0; +} + +// fclose() replacement +int S_OGG_Callback_close(void *datasource) +{ + // we do nothing here and close all things manually in S_OGG_CodecCloseStream() + return 0; +} + +// ftell() replacement +long S_OGG_Callback_tell(void *datasource) +{ + snd_stream_t *stream; + + // check if input is valid + if(!datasource) + { + errno = EBADF; + return -1; + } + + // snd_stream_t in the generic pointer + stream = (snd_stream_t *) datasource; + + return (long) FS_FTell(stream->file); +} + +// the callback structure +const ov_callbacks S_OGG_Callbacks = +{ + &S_OGG_Callback_read, + &S_OGG_Callback_seek, + &S_OGG_Callback_close, + &S_OGG_Callback_tell +}; + +/* +================= +S_OGG_CodecOpenStream +================= +*/ +snd_stream_t *S_OGG_CodecOpenStream(const char *filename) +{ + snd_stream_t *stream; + + // OGG codec control structure + OggVorbis_File *vf; + + // some variables used to get informations about the OGG + vorbis_info *OGGInfo; + ogg_int64_t numSamples; + + // check if input is valid + if(!filename) + { + return NULL; + } + + // Open the stream + stream = S_CodecUtilOpen(filename, &ogg_codec); + if(!stream) + { + return NULL; + } + + // alloctate the OggVorbis_File + vf = Z_Malloc(sizeof(OggVorbis_File)); + if(!vf) + { + S_CodecUtilClose(&stream); + + return NULL; + } + + // open the codec with our callbacks and stream as the generic pointer + if(ov_open_callbacks(stream, vf, NULL, 0, S_OGG_Callbacks) != 0) + { + Z_Free(vf); + + S_CodecUtilClose(&stream); + + return NULL; + } + + // the stream must be seekable + if(!ov_seekable(vf)) + { + ov_clear(vf); + + Z_Free(vf); + + S_CodecUtilClose(&stream); + + return NULL; + } + + // we only support OGGs with one substream + if(ov_streams(vf) != 1) + { + ov_clear(vf); + + Z_Free(vf); + + S_CodecUtilClose(&stream); + + return NULL; + } + + // get the info about channels and rate + OGGInfo = ov_info(vf, 0); + if(!OGGInfo) + { + ov_clear(vf); + + Z_Free(vf); + + S_CodecUtilClose(&stream); + + return NULL; + } + + // get the number of sample-frames in the OGG + numSamples = ov_pcm_total(vf, 0); + + // fill in the info-structure in the stream + stream->info.rate = OGGInfo->rate; + stream->info.width = OGG_SAMPLEWIDTH; + stream->info.channels = OGGInfo->channels; + stream->info.samples = numSamples; + stream->info.size = stream->info.samples * stream->info.channels * stream->info.width; + stream->info.dataofs = 0; + + // We use stream->pos for the file pointer in the compressed ogg file + stream->pos = 0; + + // We use the generic pointer in stream for the OGG codec control structure + stream->ptr = vf; + + return stream; +} + +/* +================= +S_OGG_CodecCloseStream +================= +*/ +void S_OGG_CodecCloseStream(snd_stream_t *stream) +{ + // check if input is valid + if(!stream) + { + return; + } + + // let the OGG codec cleanup its stuff + ov_clear((OggVorbis_File *) stream->ptr); + + // free the OGG codec control struct + Z_Free(stream->ptr); + + // close the stream + S_CodecUtilClose(&stream); +} + +/* +================= +S_OGG_CodecReadStream +================= +*/ +int S_OGG_CodecReadStream(snd_stream_t *stream, int bytes, void *buffer) +{ + // buffer handling + int bytesRead, bytesLeft, c; + char *bufPtr; + + // Bitstream for the decoder + int BS = 0; + + // big endian machines want their samples in big endian order + int IsBigEndian = 0; + +# ifdef Q3_BIG_ENDIAN + IsBigEndian = 1; +# endif // Q3_BIG_ENDIAN + + // check if input is valid + if(!(stream && buffer)) + { + return 0; + } + + if(bytes <= 0) + { + return 0; + } + + bytesRead = 0; + bytesLeft = bytes; + bufPtr = buffer; + + // cycle until we have the requested or all available bytes read + while(-1) + { + // read some bytes from the OGG codec + c = ov_read((OggVorbis_File *) stream->ptr, bufPtr, bytesLeft, IsBigEndian, OGG_SAMPLEWIDTH, 1, &BS); + + // no more bytes are left + if(c <= 0) + { + break; + } + + bytesRead += c; + bytesLeft -= c; + bufPtr += c; + + // we have enough bytes + if(bytesLeft <= 0) + { + break; + } + } + + return bytesRead; +} + +/* +===================================================================== +S_OGG_CodecLoad + +We handle S_OGG_CodecLoad as a special case of the streaming functions +where we read the whole stream at once. +====================================================================== +*/ +void *S_OGG_CodecLoad(const char *filename, snd_info_t *info) +{ + snd_stream_t *stream; + byte *buffer; + int bytesRead; + + // check if input is valid + if(!(filename && info)) + { + return NULL; + } + + // open the file as a stream + stream = S_OGG_CodecOpenStream(filename); + if(!stream) + { + return NULL; + } + + // copy over the info + info->rate = stream->info.rate; + info->width = stream->info.width; + info->channels = stream->info.channels; + info->samples = stream->info.samples; + info->size = stream->info.size; + info->dataofs = stream->info.dataofs; + + // allocate a buffer + // this buffer must be free-ed by the caller of this function + buffer = Hunk_AllocateTempMemory(info->size); + if(!buffer) + { + S_OGG_CodecCloseStream(stream); + + return NULL; + } + + // fill the buffer + bytesRead = S_OGG_CodecReadStream(stream, info->size, buffer); + + // we don't even have read a single byte + if(bytesRead <= 0) + { + Hunk_FreeTempMemory(buffer); + S_OGG_CodecCloseStream(stream); + + return NULL; + } + + S_OGG_CodecCloseStream(stream); + + return buffer; +} + +#endif // USE_CODEC_VORBIS diff --git a/code/client/snd_codec_opus.c b/code/client/snd_codec_opus.c new file mode 100644 index 00000000..b43dea44 --- /dev/null +++ b/code/client/snd_codec_opus.c @@ -0,0 +1,449 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2005 Stuart Dalton (badcdev@gmail.com) +Copyright (C) 2005-2006 Joerg Dietrich + +This file is part of Quake III Arena source code. + +Quake III Arena 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. + +Quake III Arena 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 Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +// Ogg Opus support is enabled by this define +#ifdef USE_CODEC_OPUS + +// includes for the Q3 sound system +#include "client.h" +#include "snd_codec.h" + +// includes for the Ogg Opus codec +#include +#include + +// samples are 16 bit +#define OPUS_SAMPLEWIDTH 2 + +// Q3 Ogg Opus codec +snd_codec_t opus_codec = +{ + "opus", + S_OggOpus_CodecLoad, + S_OggOpus_CodecOpenStream, + S_OggOpus_CodecReadStream, + S_OggOpus_CodecCloseStream, + NULL +}; + +// callbacks for opusfile + +// fread() replacement +int S_OggOpus_Callback_read(void *datasource, unsigned char *ptr, int size ) +{ + snd_stream_t *stream; + int bytesRead = 0; + + // check if input is valid + if(!ptr) + { + errno = EFAULT; + return -1; + } + + if(!size) + { + // It's not an error, caller just wants zero bytes! + errno = 0; + return 0; + } + + if (size < 0) + { + errno = EINVAL; + return -1; + } + + if(!datasource) + { + errno = EBADF; + return -1; + } + + // we use a snd_stream_t in the generic pointer to pass around + stream = (snd_stream_t *) datasource; + + // read it with the Q3 function FS_Read() + bytesRead = FS_Read(ptr, size, stream->file); + + // update the file position + stream->pos += bytesRead; + + return bytesRead; +} + +// fseek() replacement +int S_OggOpus_Callback_seek(void *datasource, opus_int64 offset, int whence) +{ + snd_stream_t *stream; + int retVal = 0; + + // check if input is valid + if(!datasource) + { + errno = EBADF; + return -1; + } + + // snd_stream_t in the generic pointer + stream = (snd_stream_t *) datasource; + + // we must map the whence to its Q3 counterpart + switch(whence) + { + case SEEK_SET : + { + // set the file position in the actual file with the Q3 function + retVal = FS_Seek(stream->file, (long) offset, FS_SEEK_SET); + + // something has gone wrong, so we return here + if(retVal < 0) + { + return retVal; + } + + // keep track of file position + stream->pos = (int) offset; + break; + } + + case SEEK_CUR : + { + // set the file position in the actual file with the Q3 function + retVal = FS_Seek(stream->file, (long) offset, FS_SEEK_CUR); + + // something has gone wrong, so we return here + if(retVal < 0) + { + return retVal; + } + + // keep track of file position + stream->pos += (int) offset; + break; + } + + case SEEK_END : + { + // set the file position in the actual file with the Q3 function + retVal = FS_Seek(stream->file, (long) offset, FS_SEEK_END); + + // something has gone wrong, so we return here + if(retVal < 0) + { + return retVal; + } + + // keep track of file position + stream->pos = stream->length + (int) offset; + break; + } + + default : + { + // unknown whence, so we return an error + errno = EINVAL; + return -1; + } + } + + // stream->pos shouldn't be smaller than zero or bigger than the filesize + stream->pos = (stream->pos < 0) ? 0 : stream->pos; + stream->pos = (stream->pos > stream->length) ? stream->length : stream->pos; + + return 0; +} + +// fclose() replacement +int S_OggOpus_Callback_close(void *datasource) +{ + // we do nothing here and close all things manually in S_OggOpus_CodecCloseStream() + return 0; +} + +// ftell() replacement +opus_int64 S_OggOpus_Callback_tell(void *datasource) +{ + snd_stream_t *stream; + + // check if input is valid + if(!datasource) + { + errno = EBADF; + return -1; + } + + // snd_stream_t in the generic pointer + stream = (snd_stream_t *) datasource; + + return (opus_int64) FS_FTell(stream->file); +} + +// the callback structure +const OpusFileCallbacks S_OggOpus_Callbacks = +{ + &S_OggOpus_Callback_read, + &S_OggOpus_Callback_seek, + &S_OggOpus_Callback_tell, + &S_OggOpus_Callback_close +}; + +/* +================= +S_OggOpus_CodecOpenStream +================= +*/ +snd_stream_t *S_OggOpus_CodecOpenStream(const char *filename) +{ + snd_stream_t *stream; + + // Opus codec control structure + OggOpusFile *of; + + // some variables used to get informations about the file + const OpusHead *opusInfo; + ogg_int64_t numSamples; + + // check if input is valid + if(!filename) + { + return NULL; + } + + // Open the stream + stream = S_CodecUtilOpen(filename, &opus_codec); + if(!stream) + { + return NULL; + } + + // open the codec with our callbacks and stream as the generic pointer + of = op_open_callbacks(stream, &S_OggOpus_Callbacks, NULL, 0, NULL ); + if (!of) + { + S_CodecUtilClose(&stream); + + return NULL; + } + + // the stream must be seekable + if(!op_seekable(of)) + { + op_free(of); + + S_CodecUtilClose(&stream); + + return NULL; + } + + // get the info about channels and rate + opusInfo = op_head(of, -1); + if(!opusInfo) + { + op_free(of); + + S_CodecUtilClose(&stream); + + return NULL; + } + + if(opusInfo->stream_count != 1) + { + op_free(of); + + S_CodecUtilClose(&stream); + + Com_Printf("Only Ogg Opus files with one stream are support\n"); + return NULL; + } + + if(opusInfo->channel_count != 1 && opusInfo->channel_count != 2) + { + op_free(of); + + S_CodecUtilClose(&stream); + + Com_Printf("Only mono and stereo Ogg Opus files are supported\n"); + return NULL; + } + + // get the number of sample-frames in the file + numSamples = op_pcm_total(of, -1); + + // fill in the info-structure in the stream + stream->info.rate = 48000; + stream->info.width = OPUS_SAMPLEWIDTH; + stream->info.channels = opusInfo->channel_count; + stream->info.samples = numSamples; + stream->info.size = stream->info.samples * stream->info.channels * stream->info.width; + stream->info.dataofs = 0; + + // We use stream->pos for the file pointer in the compressed ogg file + stream->pos = 0; + + // We use the generic pointer in stream for the opus codec control structure + stream->ptr = of; + + return stream; +} + +/* +================= +S_OggOpus_CodecCloseStream +================= +*/ +void S_OggOpus_CodecCloseStream(snd_stream_t *stream) +{ + // check if input is valid + if(!stream) + { + return; + } + + // let the opus codec cleanup its stuff + op_free((OggOpusFile *) stream->ptr); + + // close the stream + S_CodecUtilClose(&stream); +} + +/* +================= +S_OggOpus_CodecReadStream +================= +*/ +int S_OggOpus_CodecReadStream(snd_stream_t *stream, int bytes, void *buffer) +{ + // buffer handling + int samplesRead, samplesLeft, c; + opus_int16 *bufPtr; + + // check if input is valid + if(!(stream && buffer)) + { + return 0; + } + + if(bytes <= 0) + { + return 0; + } + + samplesRead = 0; + samplesLeft = bytes / stream->info.channels / stream->info.width; + bufPtr = buffer; + + if(samplesLeft <= 0) + { + return 0; + } + + // cycle until we have the requested or all available bytes read + while(-1) + { + // read some samples from the opus codec + c = op_read((OggOpusFile *) stream->ptr, bufPtr + samplesRead * stream->info.channels, samplesLeft * stream->info.channels, NULL); + + // no more samples are left + if(c <= 0) + { + break; + } + + samplesRead += c; + samplesLeft -= c; + + // we have enough samples + if(samplesLeft <= 0) + { + break; + } + } + + return samplesRead * stream->info.channels * stream->info.width; +} + +/* +===================================================================== +S_OggOpus_CodecLoad + +We handle S_OggOpus_CodecLoad as a special case of the streaming functions +where we read the whole stream at once. +====================================================================== +*/ +void *S_OggOpus_CodecLoad(const char *filename, snd_info_t *info) +{ + snd_stream_t *stream; + byte *buffer; + int bytesRead; + + // check if input is valid + if(!(filename && info)) + { + return NULL; + } + + // open the file as a stream + stream = S_OggOpus_CodecOpenStream(filename); + if(!stream) + { + return NULL; + } + + // copy over the info + info->rate = stream->info.rate; + info->width = stream->info.width; + info->channels = stream->info.channels; + info->samples = stream->info.samples; + info->size = stream->info.size; + info->dataofs = stream->info.dataofs; + + // allocate a buffer + // this buffer must be free-ed by the caller of this function + buffer = Hunk_AllocateTempMemory(info->size); + if(!buffer) + { + S_OggOpus_CodecCloseStream(stream); + + return NULL; + } + + // fill the buffer + bytesRead = S_OggOpus_CodecReadStream(stream, info->size, buffer); + + // we don't even have read a single byte + if(bytesRead <= 0) + { + Hunk_FreeTempMemory(buffer); + S_OggOpus_CodecCloseStream(stream); + + return NULL; + } + + S_OggOpus_CodecCloseStream(stream); + + return buffer; +} + +#endif // USE_CODEC_OPUS diff --git a/code/client/snd_codec_wav.c b/code/client/snd_codec_wav.c new file mode 100644 index 00000000..2fd6aa53 --- /dev/null +++ b/code/client/snd_codec_wav.c @@ -0,0 +1,291 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2005 Stuart Dalton (badcdev@gmail.com) + +This file is part of Quake III Arena source code. + +Quake III Arena 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. + +Quake III Arena 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 Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "client.h" +#include "snd_codec.h" + +/* +================= +FGetLittleLong +================= +*/ +static int FGetLittleLong( fileHandle_t f ) { + int v; + + FS_Read( &v, sizeof(v), f ); + + return LittleLong( v); +} + +/* +================= +FGetLittleShort +================= +*/ +static short FGetLittleShort( fileHandle_t f ) { + short v; + + FS_Read( &v, sizeof(v), f ); + + return LittleShort( v); +} + +/* +================= +S_ReadChunkInfo +================= +*/ +static int S_ReadChunkInfo(fileHandle_t f, char *name) +{ + int len, r; + + name[4] = 0; + + r = FS_Read(name, 4, f); + if(r != 4) + return -1; + + len = FGetLittleLong(f); + if( len < 0 ) { + Com_Printf( S_COLOR_YELLOW "WARNING: Negative chunk length\n" ); + return -1; + } + + return len; +} + +/* +================= +S_FindRIFFChunk + +Returns the length of the data in the chunk, or -1 if not found +================= +*/ +static int S_FindRIFFChunk( fileHandle_t f, char *chunk ) { + char name[5]; + int len; + + while( ( len = S_ReadChunkInfo(f, name) ) >= 0 ) + { + // If this is the right chunk, return + if( !Q_strncmp( name, chunk, 4 ) ) + return len; + + len = PAD( len, 2 ); + + // Not the right chunk - skip it + FS_Seek( f, len, FS_SEEK_CUR ); + } + + return -1; +} + +/* +================= +S_ByteSwapRawSamples +================= +*/ +static void S_ByteSwapRawSamples( int samples, int width, int s_channels, const byte *data ) { + int i; + + if ( width != 2 ) { + return; + } + if ( LittleShort( 256 ) == 256 ) { + return; + } + + if ( s_channels == 2 ) { + samples <<= 1; + } + for ( i = 0 ; i < samples ; i++ ) { + ((short *)data)[i] = LittleShort( ((short *)data)[i] ); + } +} + +/* +================= +S_ReadRIFFHeader +================= +*/ +static qboolean S_ReadRIFFHeader(fileHandle_t file, snd_info_t *info) +{ + char dump[16]; + int bits; + int fmtlen = 0; + + // skip the riff wav header + FS_Read(dump, 12, file); + + // Scan for the format chunk + if((fmtlen = S_FindRIFFChunk(file, "fmt ")) < 0) + { + Com_Printf( S_COLOR_RED "ERROR: Couldn't find \"fmt\" chunk\n"); + return qfalse; + } + + // Save the parameters + FGetLittleShort(file); // wav_format + info->channels = FGetLittleShort(file); + info->rate = FGetLittleLong(file); + FGetLittleLong(file); + FGetLittleShort(file); + bits = FGetLittleShort(file); + + if( bits < 8 ) + { + Com_Printf( S_COLOR_RED "ERROR: Less than 8 bit sound is not supported\n"); + return qfalse; + } + + info->width = bits / 8; + info->dataofs = 0; + + // Skip the rest of the format chunk if required + if(fmtlen > 16) + { + fmtlen -= 16; + FS_Seek( file, fmtlen, FS_SEEK_CUR ); + } + + // Scan for the data chunk + if( (info->size = S_FindRIFFChunk(file, "data")) < 0) + { + Com_Printf( S_COLOR_RED "ERROR: Couldn't find \"data\" chunk\n"); + return qfalse; + } + info->samples = (info->size / info->width) / info->channels; + + return qtrue; +} + +// WAV codec +snd_codec_t wav_codec = +{ + "wav", + S_WAV_CodecLoad, + S_WAV_CodecOpenStream, + S_WAV_CodecReadStream, + S_WAV_CodecCloseStream, + NULL +}; + +/* +================= +S_WAV_CodecLoad +================= +*/ +void *S_WAV_CodecLoad(const char *filename, snd_info_t *info) +{ + fileHandle_t file; + void *buffer; + + // Try to open the file + FS_FOpenFileRead(filename, &file, qtrue); + if(!file) + { + return NULL; + } + + // Read the RIFF header + if(!S_ReadRIFFHeader(file, info)) + { + FS_FCloseFile(file); + Com_Printf( S_COLOR_RED "ERROR: Incorrect/unsupported format in \"%s\"\n", + filename); + return NULL; + } + + // Allocate some memory + buffer = Hunk_AllocateTempMemory(info->size); + if(!buffer) + { + FS_FCloseFile(file); + Com_Printf( S_COLOR_RED "ERROR: Out of memory reading \"%s\"\n", + filename); + return NULL; + } + + // Read, byteswap + FS_Read(buffer, info->size, file); + S_ByteSwapRawSamples(info->samples, info->width, info->channels, (byte *)buffer); + + // Close and return + FS_FCloseFile(file); + return buffer; +} + +/* +================= +S_WAV_CodecOpenStream +================= +*/ +snd_stream_t *S_WAV_CodecOpenStream(const char *filename) +{ + snd_stream_t *rv; + + // Open + rv = S_CodecUtilOpen(filename, &wav_codec); + if(!rv) + return NULL; + + // Read the RIFF header + if(!S_ReadRIFFHeader(rv->file, &rv->info)) + { + S_CodecUtilClose(&rv); + return NULL; + } + + return rv; +} + +/* +================= +S_WAV_CodecCloseStream +================= +*/ +void S_WAV_CodecCloseStream(snd_stream_t *stream) +{ + S_CodecUtilClose(&stream); +} + +/* +================= +S_WAV_CodecReadStream +================= +*/ +int S_WAV_CodecReadStream(snd_stream_t *stream, int bytes, void *buffer) +{ + int remaining = stream->info.size - stream->pos; + int samples; + + if(remaining <= 0) + return 0; + if(bytes > remaining) + bytes = remaining; + stream->pos += bytes; + samples = (bytes / stream->info.width) / stream->info.channels; + FS_Read(buffer, bytes, stream->file); + S_ByteSwapRawSamples(samples, stream->info.width, stream->info.channels, buffer); + return bytes; +} diff --git a/code/client/snd_dma.c b/code/client/snd_dma.c index f48053b6..ba4ac961 100644 --- a/code/client/snd_dma.c +++ b/code/client/snd_dma.c @@ -15,7 +15,7 @@ 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 Foobar; if not, write to the Free Software +along with Quake III Arena source code; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ @@ -30,19 +30,14 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA *****************************************************************************/ #include "snd_local.h" +#include "snd_codec.h" #include "client.h" -void S_Play_f(void); -void S_SoundList_f(void); -void S_Music_f(void); +void S_Update_( void ); +void S_Base_StopAllSounds(void); +void S_Base_StopBackgroundTrack( void ); -void S_Update_(); -void S_UpdateBackgroundTrack( void ); - -static fileHandle_t s_backgroundFile; -static wavinfo_t s_backgroundInfo; -//int s_nextWavChunk; -static int s_backgroundSamples; +snd_stream_t *s_backgroundStream = NULL; static char s_backgroundLoop[MAX_QPATH]; //static char s_backgroundMusic[MAX_QPATH]; //TTimo: unused @@ -81,21 +76,16 @@ int s_numSfx = 0; #define LOOP_HASH 128 static sfx_t *sfxHash[LOOP_HASH]; -cvar_t *s_volume; cvar_t *s_testsound; -cvar_t *s_khz; cvar_t *s_show; cvar_t *s_mixahead; cvar_t *s_mixPreStep; -cvar_t *s_musicVolume; -cvar_t *s_separation; -cvar_t *s_doppler; static loopSound_t loopSounds[MAX_GENTITIES]; static channel_t *freelist = NULL; -int s_rawend; -portable_samplepair_t s_rawsamples[MAX_RAW_SAMPLES]; +int s_rawend[MAX_RAW_STREAMS]; +portable_samplepair_t s_rawsamples[MAX_RAW_STREAMS][MAX_RAW_SAMPLES]; // ==================================================================== @@ -103,22 +93,18 @@ portable_samplepair_t s_rawsamples[MAX_RAW_SAMPLES]; // ==================================================================== -void S_SoundInfo_f(void) { +void S_Base_SoundInfo(void) { Com_Printf("----- Sound Info -----\n" ); if (!s_soundStarted) { Com_Printf ("sound system not started\n"); } else { - if ( s_soundMuted ) { - Com_Printf ("sound system is muted\n"); - } - - Com_Printf("%5d stereo\n", dma.channels - 1); + Com_Printf("%5d channels\n", dma.channels); Com_Printf("%5d samples\n", dma.samples); - Com_Printf("%5d samplebits\n", dma.samplebits); + Com_Printf("%5d samplebits (%s)\n", dma.samplebits, dma.isfloat ? "float" : "int"); Com_Printf("%5d submission_chunk\n", dma.submission_chunk); Com_Printf("%5d speed\n", dma.speed); - Com_Printf("0x%x dma buffer\n", dma.buffer); - if ( s_backgroundFile ) { + Com_Printf("%p dma buffer\n", dma.buffer); + if ( s_backgroundStream ) { Com_Printf("Background file: %s\n", s_backgroundLoop ); } else { Com_Printf("No background file.\n" ); @@ -129,71 +115,78 @@ void S_SoundInfo_f(void) { } +#ifdef USE_VOIP +static +void S_Base_StartCapture( void ) +{ + SNDDMA_StartCapture(); +} + +static +int S_Base_AvailableCaptureSamples( void ) +{ + return SNDDMA_AvailableCaptureSamples(); +} + +static +void S_Base_Capture( int samples, byte *data ) +{ + SNDDMA_Capture(samples, data); +} + +static +void S_Base_StopCapture( void ) +{ + SNDDMA_StopCapture(); +} + +static +void S_Base_MasterGain( float val ) +{ + SNDDMA_MasterGain(val); +} +#endif + + /* -================ -S_Init -================ +================= +S_Base_SoundList +================= */ -void S_Init( void ) { - cvar_t *cv; - qboolean r; +void S_Base_SoundList( void ) { + int i; + sfx_t *sfx; + int size, total; + char type[4][16]; + char mem[2][16]; - Com_Printf("\n------- sound initialization -------\n"); - - s_volume = Cvar_Get ("s_volume", "0.8", CVAR_ARCHIVE); - s_musicVolume = Cvar_Get ("s_musicvolume", "0.25", CVAR_ARCHIVE); - s_separation = Cvar_Get ("s_separation", "0.5", CVAR_ARCHIVE); - s_doppler = Cvar_Get ("s_doppler", "1", CVAR_ARCHIVE); - s_khz = Cvar_Get ("s_khz", "22", CVAR_ARCHIVE); - s_mixahead = Cvar_Get ("s_mixahead", "0.2", CVAR_ARCHIVE); - - s_mixPreStep = Cvar_Get ("s_mixPreStep", "0.05", CVAR_ARCHIVE); - s_show = Cvar_Get ("s_show", "0", CVAR_CHEAT); - s_testsound = Cvar_Get ("s_testsound", "0", CVAR_CHEAT); - - cv = Cvar_Get ("s_initsound", "1", 0); - if ( !cv->integer ) { - Com_Printf ("not initializing.\n"); - Com_Printf("------------------------------------\n"); - return; + strcpy(type[0], "16bit"); + strcpy(type[1], "adpcm"); + strcpy(type[2], "daub4"); + strcpy(type[3], "mulaw"); + strcpy(mem[0], "paged out"); + strcpy(mem[1], "resident "); + total = 0; + for (sfx=s_knownSfx, i=0 ; isoundLength; + total += size; + Com_Printf("%6i[%s] : %s[%s]\n", size, type[sfx->soundCompressionMethod], + sfx->soundName, mem[sfx->inMemory] ); } - - Cmd_AddCommand("play", S_Play_f); - Cmd_AddCommand("music", S_Music_f); - Cmd_AddCommand("s_list", S_SoundList_f); - Cmd_AddCommand("s_info", S_SoundInfo_f); - Cmd_AddCommand("s_stop", S_StopAllSounds); - - r = SNDDMA_Init(); - Com_Printf("------------------------------------\n"); - - if ( r ) { - s_soundStarted = 1; -// s_soundMuted = 1; -// s_numSfx = 0; - SND_setup(); - - Com_Memset(sfxHash, 0, sizeof(sfx_t *)*LOOP_HASH); - - s_soundtime = 0; - s_paintedtime = 0; - - S_StopAllSounds (qtrue); - - S_SoundInfo_f(); - } - + Com_Printf ("Total resident: %i\n", total); + S_DisplayFreeMemory(); } + void S_ChannelFree(channel_t *v) { v->thesfx = NULL; *(channel_t **)v = freelist; freelist = (channel_t*)v; } -channel_t* S_ChannelMalloc() { +channel_t* S_ChannelMalloc( void ) { channel_t *v; if (freelist == NULL) { return NULL; @@ -204,7 +197,7 @@ channel_t* S_ChannelMalloc() { return v; } -void S_ChannelSetup() { +void S_ChannelSetup( void ) { channel_t *p, *q; // clear all the sounds so they don't @@ -221,25 +214,6 @@ void S_ChannelSetup() { Com_DPrintf("Channel memory manager started\n"); } -// ======================================================================= -// Shutdown sound engine -// ======================================================================= - -void S_Shutdown( void ) { - if ( !s_soundStarted ) { - return; - } - - SNDDMA_Shutdown(); - - s_soundStarted = 0; - - Cmd_RemoveCommand("play"); - Cmd_RemoveCommand("music"); - Cmd_RemoveCommand("stopsound"); - Cmd_RemoveCommand("soundlist"); - Cmd_RemoveCommand("soundinfo"); -} // ======================================================================= @@ -283,14 +257,22 @@ static sfx_t *S_FindName( const char *name ) { sfx_t *sfx; if (!name) { - Com_Error (ERR_FATAL, "S_FindName: NULL\n"); + Com_Error(ERR_FATAL, "Sound name is NULL"); } + if (!name[0]) { - Com_Error (ERR_FATAL, "S_FindName: empty name\n"); + Com_Printf( S_COLOR_YELLOW "WARNING: Sound name is empty\n" ); + return NULL; } if (strlen(name) >= MAX_QPATH) { - Com_Error (ERR_FATAL, "Sound name too long: %s", name); + Com_Printf( S_COLOR_YELLOW "WARNING: Sound name is too long: %s\n", name ); + return NULL; + } + + if (name[0] == '*') { + Com_Printf( S_COLOR_YELLOW "WARNING: Tried to load player sound directly: %s\n", name ); + return NULL; } hash = S_HashSFXName(name); @@ -356,32 +338,11 @@ This is called when the hunk is cleared and the sounds are no longer valid. =================== */ -void S_DisableSounds( void ) { - S_StopAllSounds(qtrue); +void S_Base_DisableSounds( void ) { + S_Base_StopAllSounds(); s_soundMuted = qtrue; } -/* -===================== -S_BeginRegistration - -===================== -*/ -void S_BeginRegistration( void ) { - s_soundMuted = qfalse; // we can play again - - if (s_numSfx == 0) { - SND_setup(); - - s_numSfx = 0; - Com_Memset( s_knownSfx, 0, sizeof( s_knownSfx ) ); - Com_Memset(sfxHash, 0, sizeof(sfx_t *)*LOOP_HASH); - - S_RegisterSound("sound/feedback/hit.wav", qfalse, qfalse); // changed to a sound in baseq3 - } -} - - /* ================== S_RegisterSound @@ -389,7 +350,7 @@ S_RegisterSound Creates a default buzz sound if the file can't be loaded ================== */ -sfxHandle_t S_RegisterSound( const char *name, qboolean compressed, qboolean streamed ) { +sfxHandle_t S_Base_RegisterSound( const char *name, qboolean compressed ) { sfx_t *sfx; compressed = qfalse; @@ -397,12 +358,11 @@ sfxHandle_t S_RegisterSound( const char *name, qboolean compressed, qboolean str return 0; } - if ( strlen( name ) >= MAX_QPATH ) { - Com_Printf( "Sound name exceeds MAX_QPATH\n" ); + sfx = S_FindName( name ); + if ( !sfx ) { return 0; } - sfx = S_FindName( name ); if ( sfx->soundData ) { if ( sfx->defaultSound ) { Com_Printf( S_COLOR_YELLOW "WARNING: could not find %s - using default\n", sfx->soundName ); @@ -424,6 +384,25 @@ sfxHandle_t S_RegisterSound( const char *name, qboolean compressed, qboolean str return sfx - s_knownSfx; } +/* +===================== +S_BeginRegistration + +===================== +*/ +void S_Base_BeginRegistration( void ) { + s_soundMuted = qfalse; // we can play again + + if (s_numSfx == 0) { + SND_setup(); + + Com_Memset(s_knownSfx, '\0', sizeof(s_knownSfx)); + Com_Memset(sfxHash, '\0', sizeof(sfx_t *) * LOOP_HASH); + + S_Base_RegisterSound("sound/feedback/hit.wav", qfalse); // changed to a sound in baseq3 + } +} + void S_memoryLoad(sfx_t *sfx) { // load the sound file if ( !S_LoadSound ( sfx ) ) { @@ -452,7 +431,7 @@ void S_SpatializeOrigin (vec3_t origin, int master_vol, int *left_vol, int *righ const float dist_mult = SOUND_ATTENUATE; - // calculate stereo seperation and distance attenuation + // calculate stereo separation and distance attenuation VectorSubtract(origin, listener_origin, source_vec); dist = VectorNormalize(source_vec); @@ -474,8 +453,6 @@ void S_SpatializeOrigin (vec3_t origin, int master_vol, int *left_vol, int *righ { rscale = 0.5 * (1.0 + dot); lscale = 0.5 * (1.0 - dot); - //rscale = s_separation->value + ( 1.0 - s_separation->value ) * dot; - //lscale = s_separation->value - ( 1.0 - s_separation->value ) * dot; if ( rscale < 0 ) { rscale = 0; } @@ -500,31 +477,70 @@ void S_SpatializeOrigin (vec3_t origin, int master_vol, int *left_vol, int *righ // Start a sound effect // ======================================================================= +/* +================= +S_Base_HearingThroughEntity + +Also see S_AL_HearingThroughEntity +================= +*/ +static qboolean S_Base_HearingThroughEntity( int entityNum, vec3_t origin ) +{ + float distanceSq; + vec3_t sorigin; + + if (origin) + VectorCopy(origin, sorigin); + else + VectorCopy(loopSounds[entityNum].origin, sorigin); + + if( listener_number == entityNum ) + { + // This is an outrageous hack to detect + // whether or not the player is rendering in third person or not. We can't + // ask the renderer because the renderer has no notion of entities and we + // can't ask cgame since that would involve changing the API and hence mod + // compatibility. I don't think there is any way around this, but I'll leave + // the FIXME just in case anyone has a bright idea. + distanceSq = DistanceSquared( + sorigin, + listener_origin ); + + if( distanceSq > THIRD_PERSON_THRESHOLD_SQ ) + return qfalse; //we're the player, but third person + else + return qtrue; //we're the player + } + else + return qfalse; //not the player +} + /* ==================== -S_StartSound +S_Base_StartSoundEx Validates the parms and ques the sound up -if pos is NULL, the sound will be dynamically sourced from the entity +if origin is NULL, the sound will be dynamically sourced from the entity Entchannel 0 will never override a playing sound ==================== */ -void S_StartSound(const vec3_t origin, int entNum, int entChannel, sfxHandle_t sfxHandle, float volume, float minDist, float pitch, float maxDist, int streamed) { +static void S_Base_StartSoundEx( vec3_t origin, int entityNum, int entchannel, sfxHandle_t sfxHandle, qboolean localSound ) { channel_t *ch; sfx_t *sfx; int i, oldest, chosen, time; int inplay, allowed; + qboolean fullVolume; if ( !s_soundStarted || s_soundMuted ) { return; } - if ( !origin && ( entNum < 0 || entNum > MAX_GENTITIES ) ) { - Com_Error( ERR_DROP, "S_StartSound: bad entNum %i", entNum ); + if ( !origin && ( entityNum < 0 || entityNum >= MAX_GENTITIES ) ) { + Com_Error( ERR_DROP, "S_StartSound: bad entitynum %i", entityNum ); } if ( sfxHandle < 0 || sfxHandle >= s_numSfx ) { - Com_Printf( S_COLOR_YELLOW, "S_StartSound: handle %i out of range\n", sfxHandle ); + Com_Printf( S_COLOR_YELLOW "S_StartSound: handle %i out of range\n", sfxHandle ); return; } @@ -544,15 +560,20 @@ void S_StartSound(const vec3_t origin, int entNum, int entChannel, sfxHandle_t s // pick a channel to play on allowed = 4; - if (entNum == listener_number) { + if (entityNum == listener_number) { allowed = 8; } + fullVolume = qfalse; + if (localSound || S_Base_HearingThroughEntity(entityNum, origin)) { + fullVolume = qtrue; + } + ch = s_channels; inplay = 0; for ( i = 0; i < MAX_CHANNELS ; i++, ch++ ) { - if (ch[i].entnum == entNum && ch[i].thesfx == sfx) { - if (time - ch[i].allocTime < 50) { + if (ch->entnum == entityNum && ch->thesfx == sfx) { + if (time - ch->allocTime < 50) { // if (Cvar_VariableValue( "cg_showmiss" )) { // Com_Printf("double sound start\n"); // } @@ -568,14 +589,14 @@ void S_StartSound(const vec3_t origin, int entNum, int entChannel, sfxHandle_t s sfx->lastTimeUsed = time; - ch = S_ChannelMalloc(); // entNum, entChannel); + ch = S_ChannelMalloc(); // entityNum, entchannel); if (!ch) { ch = s_channels; oldest = sfx->lastTimeUsed; chosen = -1; for ( i = 0 ; i < MAX_CHANNELS ; i++, ch++ ) { - if (ch->entnum != listener_number && ch->entnum == entNum && ch->allocTimeentchannel != CHAN_ANNOUNCER) { + if (ch->entnum != listener_number && ch->entnum == entityNum && ch->allocTimeentchannel != CHAN_ANNOUNCER) { oldest = ch->allocTime; chosen = i; } @@ -589,6 +610,7 @@ void S_StartSound(const vec3_t origin, int entNum, int entChannel, sfxHandle_t s } } if (chosen == -1) { + ch = s_channels; if (ch->entnum == listener_number) { for ( i = 0 ; i < MAX_CHANNELS ; i++, ch++ ) { if (ch->allocTimemaster_vol = 127; - ch->entnum = entNum; + ch->entnum = entityNum; ch->thesfx = sfx; ch->startSample = START_SAMPLE_IMMEDIATE; - ch->entchannel = entChannel; + ch->entchannel = entchannel; ch->leftvol = ch->master_vol; // these will get calced at next spatialize ch->rightvol = ch->master_vol; // unless the game isn't running ch->doppler = qfalse; + ch->fullVolume = fullVolume; } +/* +==================== +S_StartSound + +if origin is NULL, the sound will be dynamically sourced from the entity +==================== +*/ +void S_Base_StartSound( vec3_t origin, int entityNum, int entchannel, sfxHandle_t sfxHandle ) { + S_Base_StartSoundEx( origin, entityNum, entchannel, sfxHandle, qfalse ); +} /* ================== S_StartLocalSound ================== */ -void S_StartLocalSound( sfxHandle_t sfxHandle, int channelNum ) { +void S_Base_StartLocalSound( sfxHandle_t sfxHandle, int channelNum ) { if ( !s_soundStarted || s_soundMuted ) { return; } if ( sfxHandle < 0 || sfxHandle >= s_numSfx ) { - Com_Printf( S_COLOR_YELLOW, "S_StartLocalSound: handle %i out of range\n", sfxHandle ); + Com_Printf( S_COLOR_YELLOW "S_StartLocalSound: handle %i out of range\n", sfxHandle ); return; } - S_StartSound (NULL, listener_number, channelNum, sfxHandle, 1.f, -1, 1.f, -1, qfalse); + S_Base_StartSoundEx( NULL, listener_number, channelNum, sfxHandle, qtrue ); } @@ -652,7 +685,7 @@ If we are about to perform file access, clear the buffer so sound doesn't stutter. ================== */ -void S_ClearSoundBuffer( void ) { +void S_Base_ClearSoundBuffer( void ) { int clear; if (!s_soundStarted) @@ -665,7 +698,7 @@ void S_ClearSoundBuffer( void ) { S_ChannelSetup(); - s_rawend = 0; + Com_Memset(s_rawend, '\0', sizeof (s_rawend)); if (dma.samplebits == 8) clear = 0x80; @@ -674,11 +707,7 @@ void S_ClearSoundBuffer( void ) { SNDDMA_BeginPainting (); if (dma.buffer) - // TTimo: due to a particular bug workaround in linux sound code, - // have to optionally use a custom C implementation of Com_Memset - // not affecting win32, we have #define Snd_Memset Com_Memset - // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=371 - Snd_Memset(dma.buffer, clear, dma.samples * dma.samplebits/8); + Com_Memset(dma.buffer, clear, dma.samples * dma.samplebits/8); SNDDMA_Submit (); } @@ -687,15 +716,15 @@ void S_ClearSoundBuffer( void ) { S_StopAllSounds ================== */ -void S_StopAllSounds(qboolean stop_music) { +void S_Base_StopAllSounds(void) { if ( !s_soundStarted ) { return; } // stop the background music - S_StopBackgroundTrack(); + S_Base_StopBackgroundTrack(); - S_ClearSoundBuffer (); + S_Base_ClearSoundBuffer (); } /* @@ -706,7 +735,7 @@ continuous looping sounds are added each frame ============================================================== */ -void S_StopLoopingSound(int entityNum) { +void S_Base_StopLoopingSound(int entityNum) { loopSounds[entityNum].active = qfalse; // loopSounds[entityNum].sfx = 0; loopSounds[entityNum].kill = qfalse; @@ -718,12 +747,11 @@ S_ClearLoopingSounds ================== */ -void S_ClearLoopingSounds( qboolean killall ) { +void S_Base_ClearLoopingSounds( qboolean killall ) { int i; for ( i = 0 ; i < MAX_GENTITIES ; i++) { if (killall || loopSounds[i].kill == qtrue || (loopSounds[i].sfx && loopSounds[i].sfx->soundLength == 0)) { - loopSounds[i].kill = qfalse; - S_StopLoopingSound(i); + S_Base_StopLoopingSound(i); } } numLoopChannels = 0; @@ -737,16 +765,15 @@ Called during entity generation for a frame Include velocity in case I get around to doing doppler... ================== */ -void S_AddLoopingSound(const vec3_t origin, const vec3_t velocity, sfxHandle_t sfxHandle, float volume, float minDist, float maxDist, float pitch, int flags) { +void S_Base_AddLoopingSound( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfxHandle ) { sfx_t *sfx; - int entityNum = 0; if ( !s_soundStarted || s_soundMuted ) { return; } if ( sfxHandle < 0 || sfxHandle >= s_numSfx ) { - Com_Printf( S_COLOR_YELLOW, "S_AddLoopingSound: handle %i out of range\n", sfxHandle ); + Com_Printf( S_COLOR_YELLOW "S_AddLoopingSound: handle %i out of range\n", sfxHandle ); return; } @@ -785,6 +812,8 @@ void S_AddLoopingSound(const vec3_t origin, const vec3_t velocity, sfxHandle_t s loopSounds[entityNum].dopplerScale = lenb/(lena*100); if (loopSounds[entityNum].dopplerScale<=1.0) { loopSounds[entityNum].doppler = qfalse; // don't bother doing the math + } else if (loopSounds[entityNum].dopplerScale>MAX_DOPPLER_SCALE) { + loopSounds[entityNum].dopplerScale = MAX_DOPPLER_SCALE; } } @@ -799,7 +828,7 @@ Called during entity generation for a frame Include velocity in case I get around to doing doppler... ================== */ -void S_AddRealLoopingSound( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfxHandle ) { +void S_Base_AddRealLoopingSound( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfxHandle ) { sfx_t *sfx; if ( !s_soundStarted || s_soundMuted ) { @@ -807,7 +836,7 @@ void S_AddRealLoopingSound( int entityNum, const vec3_t origin, const vec3_t vel } if ( sfxHandle < 0 || sfxHandle >= s_numSfx ) { - Com_Printf( S_COLOR_YELLOW, "S_AddRealLoopingSound: handle %i out of range\n", sfxHandle ); + Com_Printf( S_COLOR_YELLOW "S_AddRealLoopingSound: handle %i out of range\n", sfxHandle ); return; } @@ -904,6 +933,7 @@ void S_AddLoopSounds (void) { ch->doppler = loop->doppler; ch->dopplerScale = loop->dopplerScale; ch->oldDopplerScale = loop->oldDopplerScale; + ch->fullVolume = qfalse; numLoopChannels++; if (numLoopChannels == MAX_CHANNELS) { return; @@ -939,47 +969,65 @@ void S_ByteSwapRawSamples( int samples, int width, int s_channels, const byte *d } } -portable_samplepair_t *S_GetRawSamplePointer() { - return s_rawsamples; -} - /* ============ -S_RawSamples +S_Base_RawSamples Music streaming ============ */ -void S_RawSamples( int samples, int rate, int width, int s_channels, const byte *data, float volume ) { +void S_Base_RawSamples( int stream, int samples, int rate, int width, int s_channels, const byte *data, float volume, int entityNum) +{ int i; int src, dst; float scale; - int intVolume; + int intVolumeLeft, intVolumeRight; + portable_samplepair_t *rawsamples; if ( !s_soundStarted || s_soundMuted ) { return; } - intVolume = 256 * volume; + if ( (stream < 0) || (stream >= MAX_RAW_STREAMS) ) { + return; + } - if ( s_rawend < s_soundtime ) { - Com_DPrintf( "S_RawSamples: resetting minimum: %i < %i\n", s_rawend, s_soundtime ); - s_rawend = s_soundtime; + rawsamples = s_rawsamples[stream]; + + if ( s_muted->integer ) { + intVolumeLeft = intVolumeRight = 0; + } else { + int leftvol, rightvol; + + if ( entityNum >= 0 && entityNum < MAX_GENTITIES ) { + // support spatialized raw streams, e.g. for VoIP + S_SpatializeOrigin( loopSounds[ entityNum ].origin, 256, &leftvol, &rightvol ); + } else { + leftvol = rightvol = 256; + } + + intVolumeLeft = leftvol * volume * s_volume->value; + intVolumeRight = rightvol * volume * s_volume->value; + } + + if ( s_rawend[stream] < s_soundtime ) { + Com_DPrintf( "S_Base_RawSamples: resetting minimum: %i < %i\n", s_rawend[stream], s_soundtime ); + s_rawend[stream] = s_soundtime; } scale = (float)rate / dma.speed; -//Com_Printf ("%i < %i < %i\n", s_soundtime, s_paintedtime, s_rawend); +//Com_Printf ("%i < %i < %i\n", s_soundtime, s_paintedtime, s_rawend[stream]); if (s_channels == 2 && width == 2) { if (scale == 1.0) { // optimized case for (i=0 ; i= samples) break; - dst = s_rawend&(MAX_RAW_SAMPLES-1); - s_rawend++; - s_rawsamples[dst].left = ((short *)data)[src*2] * intVolume; - s_rawsamples[dst].right = ((short *)data)[src*2+1] * intVolume; + dst = s_rawend[stream]&(MAX_RAW_SAMPLES-1); + s_rawend[stream]++; + rawsamples[dst].left = ((short *)data)[src*2] * intVolumeLeft; + rawsamples[dst].right = ((short *)data)[src*2+1] * intVolumeRight; } } } @@ -1003,45 +1051,47 @@ void S_RawSamples( int samples, int rate, int width, int s_channels, const byte src = i*scale; if (src >= samples) break; - dst = s_rawend&(MAX_RAW_SAMPLES-1); - s_rawend++; - s_rawsamples[dst].left = ((short *)data)[src] * intVolume; - s_rawsamples[dst].right = ((short *)data)[src] * intVolume; + dst = s_rawend[stream]&(MAX_RAW_SAMPLES-1); + s_rawend[stream]++; + rawsamples[dst].left = ((short *)data)[src] * intVolumeLeft; + rawsamples[dst].right = ((short *)data)[src] * intVolumeRight; } } else if (s_channels == 2 && width == 1) { - intVolume *= 256; + intVolumeLeft *= 256; + intVolumeRight *= 256; for (i=0 ; ; i++) { src = i*scale; if (src >= samples) break; - dst = s_rawend&(MAX_RAW_SAMPLES-1); - s_rawend++; - s_rawsamples[dst].left = ((char *)data)[src*2] * intVolume; - s_rawsamples[dst].right = ((char *)data)[src*2+1] * intVolume; + dst = s_rawend[stream]&(MAX_RAW_SAMPLES-1); + s_rawend[stream]++; + rawsamples[dst].left = ((char *)data)[src*2] * intVolumeLeft; + rawsamples[dst].right = ((char *)data)[src*2+1] * intVolumeRight; } } else if (s_channels == 1 && width == 1) { - intVolume *= 256; + intVolumeLeft *= 256; + intVolumeRight *= 256; for (i=0 ; ; i++) { src = i*scale; if (src >= samples) break; - dst = s_rawend&(MAX_RAW_SAMPLES-1); - s_rawend++; - s_rawsamples[dst].left = (((byte *)data)[src]-128) * intVolume; - s_rawsamples[dst].right = (((byte *)data)[src]-128) * intVolume; + dst = s_rawend[stream]&(MAX_RAW_SAMPLES-1); + s_rawend[stream]++; + rawsamples[dst].left = (((byte *)data)[src]-128) * intVolumeLeft; + rawsamples[dst].right = (((byte *)data)[src]-128) * intVolumeRight; } } - if ( s_rawend > s_soundtime + MAX_RAW_SAMPLES ) { - Com_DPrintf( "S_RawSamples: overflowed %i > %i\n", s_rawend, s_soundtime ); + if ( s_rawend[stream] > s_soundtime + MAX_RAW_SAMPLES ) { + Com_DPrintf( "S_Base_RawSamples: overflowed %i > %i\n", s_rawend[stream], s_soundtime ); } } @@ -1054,8 +1104,8 @@ S_UpdateEntityPosition let the sound system know where an entity currently is ====================== */ -void S_UpdateEntityPosition( int entityNum, const vec3_t origin ) { - if ( entityNum < 0 || entityNum > MAX_GENTITIES ) { +void S_Base_UpdateEntityPosition( int entityNum, const vec3_t origin ) { + if ( entityNum < 0 || entityNum >= MAX_GENTITIES ) { Com_Error( ERR_DROP, "S_UpdateEntityPosition: bad entitynum %i", entityNum ); } VectorCopy( origin, loopSounds[entityNum].origin ); @@ -1069,7 +1119,7 @@ S_Respatialize Change the volumes of all the playing sounds for changes in their positions ============ */ -void S_Respatialize( int entityNum, const vec3_t head, vec3_t axis[3], int inwater ) { +void S_Base_Respatialize( int entityNum, const vec3_t head, vec3_t axis[3], int inwater ) { int i; channel_t *ch; vec3_t origin; @@ -1090,8 +1140,8 @@ void S_Respatialize( int entityNum, const vec3_t head, vec3_t axis[3], int inwat if ( !ch->thesfx ) { continue; } - // anything coming from the view entity will always be full volume - if (ch->entnum == listener_number) { + // local and first person sounds will always be full volume + if (ch->fullVolume) { ch->leftvol = ch->master_vol; ch->rightvol = ch->master_vol; } else { @@ -1154,13 +1204,13 @@ S_Update Called once each time through the main loop ============ */ -void S_Update( void ) { +void S_Base_Update( void ) { int i; int total; channel_t *ch; if ( !s_soundStarted || s_soundMuted ) { - Com_DPrintf ("not started or muted\n"); +// Com_DPrintf ("not started or muted\n"); return; } @@ -1172,7 +1222,7 @@ void S_Update( void ) { ch = s_channels; for (i=0 ; ithesfx && (ch->leftvol || ch->rightvol) ) { - Com_Printf ("%f %f %s\n", ch->leftvol, ch->rightvol, ch->thesfx->soundName); + Com_Printf ("%d %d %s\n", ch->leftvol, ch->rightvol, ch->thesfx->soundName); total++; } } @@ -1192,9 +1242,18 @@ void S_GetSoundtime(void) int samplepos; static int buffers; static int oldsamplepos; - int fullsamples; - - fullsamples = dma.samples / dma.channels; + + if( CL_VideoRecording( ) ) + { + float fps = MIN(cl_aviFrameRate->value, 1000.0f); + float frameDuration = MAX(dma.speed / fps, 1.0f) + clc.aviSoundFrameRemainder; + + int msec = (int)frameDuration; + s_soundtime += msec; + clc.aviSoundFrameRemainder = frameDuration - msec; + + return; + } // it is possible to miscount buffers if it has wrapped twice between // calls to S_Update. Oh well. @@ -1206,13 +1265,13 @@ void S_GetSoundtime(void) if (s_paintedtime > 0x40000000) { // time to chop things off to avoid 32 bit limits buffers = 0; - s_paintedtime = fullsamples; - S_StopAllSounds (qtrue); + s_paintedtime = dma.fullsamples; + S_Base_StopAllSounds (); } } oldsamplepos = samplepos; - s_soundtime = buffers*fullsamples + samplepos/dma.channels; + s_soundtime = buffers*dma.fullsamples + samplepos/dma.channels; #if 0 // check to make sure that we haven't overshot @@ -1233,7 +1292,6 @@ void S_GetSoundtime(void) void S_Update_(void) { unsigned endtime; - int samps; static float lastTime = 0.0f; float ma, op; float thisTime, sane; @@ -1277,9 +1335,8 @@ void S_Update_(void) { & ~(dma.submission_chunk-1); // never mix more than the complete buffer - samps = dma.samples >> (dma.channels-1); - if (endtime - s_soundtime > samps) - endtime = s_soundtime + samps; + if (endtime - s_soundtime > dma.fullsamples) + endtime = s_soundtime + dma.fullsamples; @@ -1292,73 +1349,6 @@ void S_Update_(void) { lastTime = thisTime; } -/* -=============================================================================== - -console functions - -=============================================================================== -*/ - -void S_Play_f( void ) { - int i; - sfxHandle_t h; - char name[256]; - - i = 1; - while ( i [loopfile]\n"); - return; - } - -} - -void S_SoundList_f( void ) { - int i; - sfx_t *sfx; - int size, total; - char type[4][16]; - char mem[2][16]; - - strcpy(type[0], "16bit"); - strcpy(type[1], "adpcm"); - strcpy(type[2], "daub4"); - strcpy(type[3], "mulaw"); - strcpy(mem[0], "paged out"); - strcpy(mem[1], "resident "); - total = 0; - for (sfx=s_knownSfx, i=0 ; isoundLength; - total += size; - Com_Printf("%6i[%s] : %s[%s]\n", size, type[sfx->soundCompressionMethod], sfx->soundName, mem[sfx->inMemory] ); - } - Com_Printf ("Total resident: %i\n", total); - S_DisplayFreeMemory(); -} /* @@ -1369,62 +1359,43 @@ background music functions =============================================================================== */ -int FGetLittleLong( fileHandle_t f ) { - int v; - - FS_Read( &v, sizeof(v), f ); - - return LittleLong( v); -} - -int FGetLittleShort( fileHandle_t f ) { - short v; - - FS_Read( &v, sizeof(v), f ); - - return LittleShort( v); -} - -// returns the length of the data in the chunk, or 0 if not found -int S_FindWavChunk( fileHandle_t f, char *chunk ) { - char name[5]; - int len; - int r; - - name[4] = 0; - len = 0; - r = FS_Read( name, 4, f ); - if ( r != 4 ) { - return 0; - } - len = FGetLittleLong( f ); - if ( len < 0 || len > 0xfffffff ) { - len = 0; - return 0; - } - len = (len + 1 ) & ~1; // pad to word boundary -// s_nextWavChunk += len + 8; - - if ( strcmp( name, chunk ) ) { - return 0; - } - - return len; -} - /* ====================== S_StopBackgroundTrack ====================== */ -void S_StopBackgroundTrack( void ) { - if ( !s_backgroundFile ) { +void S_Base_StopBackgroundTrack( void ) { + if(!s_backgroundStream) + return; + S_CodecCloseStream(s_backgroundStream); + s_backgroundStream = NULL; + s_rawend[0] = 0; +} + +/* +====================== +S_OpenBackgroundStream +====================== +*/ +static void S_OpenBackgroundStream( const char *filename ) { + // close the background track, but DON'T reset s_rawend + // if restarting the same back ground track + if(s_backgroundStream) + { + S_CodecCloseStream(s_backgroundStream); + s_backgroundStream = NULL; + } + + // Open stream + s_backgroundStream = S_CodecOpenStream(filename); + if(!s_backgroundStream) { + Com_Printf( S_COLOR_YELLOW "WARNING: couldn't open music file %s\n", filename ); return; } - Sys_EndStreamedFile( s_backgroundFile ); - FS_FCloseFile( s_backgroundFile ); - s_backgroundFile = 0; - s_rawend = 0; + + if(s_backgroundStream->info.channels != 2 || s_backgroundStream->info.rate != 22050) { + Com_Printf(S_COLOR_YELLOW "WARNING: music file %s is not 22k stereo\n", filename ); + } } /* @@ -1432,11 +1403,7 @@ void S_StopBackgroundTrack( void ) { S_StartBackgroundTrack ====================== */ -void S_StartBackgroundTrack( const char *intro, const char *loop ){ - int len; - char dump[16]; - char name[MAX_QPATH]; - +void S_Base_StartBackgroundTrack( const char *intro, const char *loop ){ if ( !intro ) { intro = ""; } @@ -1445,77 +1412,15 @@ void S_StartBackgroundTrack( const char *intro, const char *loop ){ } Com_DPrintf( "S_StartBackgroundTrack( %s, %s )\n", intro, loop ); - Q_strncpyz( name, intro, sizeof( name ) - 4 ); - COM_DefaultExtension( name, sizeof( name ), ".wav" ); - - if ( !intro[0] ) { + if(!*intro) + { + S_Base_StopBackgroundTrack(); return; } Q_strncpyz( s_backgroundLoop, loop, sizeof( s_backgroundLoop ) ); - // close the background track, but DON'T reset s_rawend - // if restarting the same back ground track - if ( s_backgroundFile ) { - Sys_EndStreamedFile( s_backgroundFile ); - FS_FCloseFile( s_backgroundFile ); - s_backgroundFile = 0; - } - - // - // open up a wav file and get all the info - // - FS_FOpenFileRead( name, &s_backgroundFile, qtrue, qtrue ); - if ( !s_backgroundFile ) { - Com_Printf( S_COLOR_YELLOW "WARNING: couldn't open music file %s\n", name ); - return; - } - - // skip the riff wav header - - FS_Read(dump, 12, s_backgroundFile); - - if ( !S_FindWavChunk( s_backgroundFile, "fmt " ) ) { - Com_Printf( "No fmt chunk in %s\n", name ); - FS_FCloseFile( s_backgroundFile ); - s_backgroundFile = 0; - return; - } - - // save name for soundinfo - s_backgroundInfo.format = FGetLittleShort( s_backgroundFile ); - s_backgroundInfo.channels = FGetLittleShort( s_backgroundFile ); - s_backgroundInfo.rate = FGetLittleLong( s_backgroundFile ); - FGetLittleLong( s_backgroundFile ); - FGetLittleShort( s_backgroundFile ); - s_backgroundInfo.width = FGetLittleShort( s_backgroundFile ) / 8; - - if ( s_backgroundInfo.format != WAV_FORMAT_PCM ) { - FS_FCloseFile( s_backgroundFile ); - s_backgroundFile = 0; - Com_Printf("Not a microsoft PCM format wav: %s\n", name); - return; - } - - if ( s_backgroundInfo.channels != 2 || s_backgroundInfo.rate != 22050 ) { - Com_Printf(S_COLOR_YELLOW "WARNING: music file %s is not 22k stereo\n", name ); - } - - if ( ( len = S_FindWavChunk( s_backgroundFile, "data" ) ) == 0 ) { - FS_FCloseFile( s_backgroundFile ); - s_backgroundFile = 0; - Com_Printf("No data chunk in %s\n", name); - return; - } - - s_backgroundInfo.samples = len / (s_backgroundInfo.width * s_backgroundInfo.channels); - - s_backgroundSamples = s_backgroundInfo.samples; - - // - // start the background streaming - // - Sys_BeginStreamedFile( s_backgroundFile, 0x10000 ); + S_OpenBackgroundStream( intro ); } /* @@ -1529,73 +1434,66 @@ void S_UpdateBackgroundTrack( void ) { byte raw[30000]; // just enough to fit in a mac stack frame int fileBytes; int r; - static float musicVolume = 0.5f; - if ( !s_backgroundFile ) { + if(!s_backgroundStream) { return; } - // graeme see if this is OK - musicVolume = (musicVolume + (s_musicVolume->value * 2))/4.0f; - // don't bother playing anything if musicvolume is 0 - if ( musicVolume <= 0 ) { + if ( s_musicVolume->value <= 0 ) { return; } // see how many samples should be copied into the raw buffer - if ( s_rawend < s_soundtime ) { - s_rawend = s_soundtime; + if ( s_rawend[0] < s_soundtime ) { + s_rawend[0] = s_soundtime; } - while ( s_rawend < s_soundtime + MAX_RAW_SAMPLES ) { - bufferSamples = MAX_RAW_SAMPLES - (s_rawend - s_soundtime); + while ( s_rawend[0] < s_soundtime + MAX_RAW_SAMPLES ) { + bufferSamples = MAX_RAW_SAMPLES - (s_rawend[0] - s_soundtime); // decide how much data needs to be read from the file - fileSamples = bufferSamples * s_backgroundInfo.rate / dma.speed; + fileSamples = bufferSamples * s_backgroundStream->info.rate / dma.speed; - // don't try and read past the end of the file - if ( fileSamples > s_backgroundSamples ) { - fileSamples = s_backgroundSamples; - } + if (!fileSamples) + return; // our max buffer size - fileBytes = fileSamples * (s_backgroundInfo.width * s_backgroundInfo.channels); + fileBytes = fileSamples * (s_backgroundStream->info.width * s_backgroundStream->info.channels); if ( fileBytes > sizeof(raw) ) { fileBytes = sizeof(raw); - fileSamples = fileBytes / (s_backgroundInfo.width * s_backgroundInfo.channels); + fileSamples = fileBytes / (s_backgroundStream->info.width * s_backgroundStream->info.channels); } - r = Sys_StreamedRead( raw, 1, fileBytes, s_backgroundFile ); - if ( r != fileBytes ) { - Com_Printf("StreamedRead failure on music track\n"); - S_StopBackgroundTrack(); - return; + // Read + r = S_CodecReadStream(s_backgroundStream, fileBytes, raw); + if(r < fileBytes) + { + fileSamples = r / (s_backgroundStream->info.width * s_backgroundStream->info.channels); } - // byte swap if needed - S_ByteSwapRawSamples( fileSamples, s_backgroundInfo.width, s_backgroundInfo.channels, raw ); - - // add to raw buffer - S_RawSamples( fileSamples, s_backgroundInfo.rate, - s_backgroundInfo.width, s_backgroundInfo.channels, raw, musicVolume ); - - s_backgroundSamples -= fileSamples; - if ( !s_backgroundSamples ) { + if(r > 0) + { + // add to raw buffer + S_Base_RawSamples(0, fileSamples, s_backgroundStream->info.rate, + s_backgroundStream->info.width, s_backgroundStream->info.channels, raw, s_musicVolume->value, -1); + } + else + { // loop - if (s_backgroundLoop[0]) { - Sys_EndStreamedFile( s_backgroundFile ); - FS_FCloseFile( s_backgroundFile ); - s_backgroundFile = 0; - S_StartBackgroundTrack( s_backgroundLoop, s_backgroundLoop ); - if ( !s_backgroundFile ) { - return; // loop failed to restart - } - } else { - s_backgroundFile = 0; + if(s_backgroundLoop[0]) + { + S_OpenBackgroundStream( s_backgroundLoop ); + if(!s_backgroundStream) + return; + } + else + { + S_Base_StopBackgroundTrack(); return; } } + } } @@ -1606,7 +1504,7 @@ S_FreeOldestSound ====================== */ -void S_FreeOldestSound() { +void S_FreeOldestSound( void ) { int i, oldest, used; sfx_t *sfx; sndBuffer *buffer, *nbuffer; @@ -1636,335 +1534,86 @@ void S_FreeOldestSound() { sfx->soundData = NULL; } -/* -================= -S_IsSoundPlaying -================= -*/ -int S_IsSoundPlaying(int channelNumber, const char* name) -{ - // FIXME: stub - STUB(); - return 0; +// ======================================================================= +// Shutdown sound engine +// ======================================================================= + +void S_Base_Shutdown( void ) { + if ( !s_soundStarted ) { + return; + } + + SNDDMA_Shutdown(); + SND_shutdown(); + + s_soundStarted = 0; + s_numSfx = 0; + + Cmd_RemoveCommand("s_info"); } /* -================= -MUSIC_Pause -================= +================ +S_Init +================ */ -void MUSIC_Pause() -{ - // FIXME: stub - STUB(); -} +qboolean S_Base_Init( soundInterface_t *si ) { + qboolean r; -/* -================= -MUSIC_Unpause -================= -*/ -void MUSIC_Unpause() -{ - // FIXME: stub - STUB(); -} + if( !si ) { + return qfalse; + } -/* -================= -MUSIC_LoadSoundtrackFile -================= -*/ -qboolean MUSIC_LoadSoundtrackFile(const char* filename) -{ - // FIXME: stub - STUB(); - return qfalse; -} + s_mixahead = Cvar_Get ("s_mixahead", "0.2", CVAR_ARCHIVE); + s_mixPreStep = Cvar_Get ("s_mixPreStep", "0.05", CVAR_ARCHIVE); + s_show = Cvar_Get ("s_show", "0", CVAR_CHEAT); + s_testsound = Cvar_Get ("s_testsound", "0", CVAR_CHEAT); -/* -================= -MUSIC_SongValid -================= -*/ -qboolean MUSIC_SongValid(const char* mood) -{ - // FIXME: stub - STUB(); - return qfalse; -} + r = SNDDMA_Init(); -/* -================= -MUSIC_Loaded -================= -*/ -qboolean MUSIC_Loaded(void) -{ - // FIXME: stub - STUB(); - return qfalse; -} + if ( r ) { + s_soundStarted = 1; + s_soundMuted = 1; +// s_numSfx = 0; -/* -================= -Music_Update -================= -*/ -void Music_Update(void) -{ - // FIXME: stub - STUB(); -} + Com_Memset(sfxHash, 0, sizeof(sfx_t *)*LOOP_HASH); -/* -================= -MUSIC_SongEnded -================= -*/ -void MUSIC_SongEnded(void) -{ - // FIXME: stub - STUB(); -} + s_soundtime = 0; + s_paintedtime = 0; -/* -================= -S_StartSound -================= -*/ -void MUSIC_NewSoundtrack(const char* name) -{ - // FIXME: stub - STUB(); -} + S_Base_StopAllSounds( ); + } else { + return qfalse; + } -/* -================= -MUSIC_UpdateMood -================= -*/ -void MUSIC_UpdateMood(int current, int fallback) -{ - // FIXME: stub - //STUB(); -} + si->Shutdown = S_Base_Shutdown; + si->StartSound = S_Base_StartSound; + si->StartLocalSound = S_Base_StartLocalSound; + si->StartBackgroundTrack = S_Base_StartBackgroundTrack; + si->StopBackgroundTrack = S_Base_StopBackgroundTrack; + si->RawSamples = S_Base_RawSamples; + si->StopAllSounds = S_Base_StopAllSounds; + si->ClearLoopingSounds = S_Base_ClearLoopingSounds; + si->AddLoopingSound = S_Base_AddLoopingSound; + si->AddRealLoopingSound = S_Base_AddRealLoopingSound; + si->StopLoopingSound = S_Base_StopLoopingSound; + si->Respatialize = S_Base_Respatialize; + si->UpdateEntityPosition = S_Base_UpdateEntityPosition; + si->Update = S_Base_Update; + si->DisableSounds = S_Base_DisableSounds; + si->BeginRegistration = S_Base_BeginRegistration; + si->RegisterSound = S_Base_RegisterSound; + si->ClearSoundBuffer = S_Base_ClearSoundBuffer; + si->SoundInfo = S_Base_SoundInfo; + si->SoundList = S_Base_SoundList; -/* -================= -MUSIC_UpdateVolume -================= -*/ -void MUSIC_UpdateVolume(float volume, float fade_time) -{ - // FIXME: stub - STUB(); -} +#ifdef USE_VOIP + si->StartCapture = S_Base_StartCapture; + si->AvailableCaptureSamples = S_Base_AvailableCaptureSamples; + si->Capture = S_Base_Capture; + si->StopCapture = S_Base_StopCapture; + si->MasterGain = S_Base_MasterGain; +#endif -/* -================= -MUSIC_StopAllSongs -================= -*/ -void MUSIC_StopAllSongs(void) -{ - // FIXME: stub - STUB(); -} - -/* -================= -MUSIC_FreeAllSongs -================= -*/ -void MUSIC_FreeAllSongs(void) -{ - // FIXME: stub - STUB(); -} - -/* -================= -MUSIC_Playing -================= -*/ -qboolean MUSIC_Playing(void) -{ - // FIXME: stub - STUB(); - return qfalse; -} - -/* -================= -MUSIC_FindSong -================= -*/ -int MUSIC_FindSong(const char* name) -{ - // FIXME: stub - STUB(); - return 0; -} - -/* -================= -MUSIC_CurrentSongChannel -================= -*/ -int MUSIC_CurrentSongChannel(void) -{ - // FIXME: stub - STUB(); - return 0; -} - -/* -================= -MUSIC_StopChannel -================= -*/ -void MUSIC_StopChannel(int channel_number) -{ - // FIXME: stub - STUB(); -} - -/* -================= -MUSIC_PlaySong -================= -*/ -qboolean MUSIC_PlaySong(const char* alias) -{ - // FIXME: stub - STUB(); - return qfalse; -} - -/* -================= -MUSIC_UpdateMusicVolumes -================= -*/ -void MUSIC_UpdateMusicVolumes(void) -{ - // FIXME: stub - STUB(); -} - -/* -================= -MUSIC_CheckForStoppedSongs -================= -*/ -void MUSIC_CheckForStoppedSongs(void) -{ - // FIXME: stub - STUB(); -} - -/* -================= -S_IsSoundRegistered -================= -*/ -qboolean S_IsSoundRegistered(const char* name) -{ - // FIXME: stub - return qfalse; -} -/* -================= -S_GetSoundTime -================= -*/ -float S_GetSoundTime(sfxHandle_t handle) -{ - // FIXME: stub - STUB(); - return 0.0; -} - -/* -================= -S_SetGlobalAmbientVolumeLevel -================= -*/ -void S_SetGlobalAmbientVolumeLevel(float volume) -{ - // FIXME: stub - STUB(); -} - -/* -================= -S_SetReverb -================= -*/ -void S_SetReverb(int reverb_type, float reverb_level) -{ - // FIXME: stub - STUB(); -} - -/* -================= -S_EndRegistration -================= -*/ -void S_EndRegistration(void) -{ - // FIXME: stub -} - -void S_UpdateEntity(int entityNum, const vec3_t origin, const vec3_t velocity, qboolean use_listener) -{ - // FIXME: stub -} - -void S_FadeSound(float fTime) -{ - // FIXME: stub -} - -/* -================= -S_StartLocalSound -================= -*/ -void S_StartLocalSoundByName(const char* sound_name, qboolean force_load) -{ - // FIXME: unimplemented -} - -/* -================= -S_StopSound -================= -*/ -void S_StopSound(int entnum, int channel) -{ - // FIXME: unimplemented -} - -/* -================= -S_ClearLoopingSoundsNoParam -================= -*/ -void S_ClearLoopingSoundsNoParam(void) -{ - S_ClearLoopingSounds(qtrue); -} - -/* -================= -S_Respatialize -================= -*/ -void S_RespatializeOld(int entityNum, const vec3_t origin, - vec3_t axis[3]) -{ - S_Respatialize(entityNum, origin, axis, 0); + return qtrue; } diff --git a/code/client/snd_local.h b/code/client/snd_local.h index f31b4853..65392e36 100644 --- a/code/client/snd_local.h +++ b/code/client/snd_local.h @@ -15,21 +15,17 @@ 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 Foobar; if not, write to the Free Software +along with Quake III Arena source code; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ // snd_local.h -- private sound definations -#include "q_shared.h" +#include "../qcommon/q_shared.h" #include "../qcommon/qcommon.h" #include "snd_public.h" -#ifdef __cplusplus -extern "C" { -#endif - #define PAINTBUFFER_SIZE 4096 // this is in samples #define SND_CHUNK_SIZE 1024 // samples @@ -60,6 +56,7 @@ typedef struct sfx_s { qboolean soundCompressed; // not in Memory int soundCompressionMethod; int soundLength; + int soundChannels; char soundName[MAX_QPATH]; int lastTimeUsed; struct sfx_s *next; @@ -68,14 +65,20 @@ typedef struct sfx_s { typedef struct { int channels; int samples; // mono samples in buffer + int fullsamples; // samples with all channels in buffer (samples divided by channels) int submission_chunk; // don't mix less than this # int samplebits; + int isfloat; int speed; byte *buffer; } dma_t; #define START_SAMPLE_IMMEDIATE 0x7fffffff +#define MAX_DOPPLER_SCALE 50.0f //arbitrary + +#define THIRD_PERSON_THRESHOLD_SQ (48.0f*48.0f) + typedef struct loopSound_s { vec3_t origin; vec3_t velocity; @@ -104,6 +107,7 @@ typedef struct qboolean fixed_origin; // use origin instead of fetching entnum's origin sfx_t *thesfx; // sfx structure qboolean doppler; + qboolean fullVolume; } channel_t; @@ -119,6 +123,38 @@ typedef struct { int dataofs; // chunk starts this many bytes from file start } wavinfo_t; +// Interface between Q3 sound "api" and the sound backend +typedef struct +{ + void (*Shutdown)(void); + void (*StartSound)( vec3_t origin, int entnum, int entchannel, sfxHandle_t sfx ); + void (*StartLocalSound)( sfxHandle_t sfx, int channelNum ); + void (*StartBackgroundTrack)( const char *intro, const char *loop ); + void (*StopBackgroundTrack)( void ); + void (*RawSamples)(int stream, int samples, int rate, int width, int channels, const byte *data, float volume, int entityNum); + void (*StopAllSounds)( void ); + void (*ClearLoopingSounds)( qboolean killall ); + void (*AddLoopingSound)( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfx ); + void (*AddRealLoopingSound)( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfx ); + void (*StopLoopingSound)(int entityNum ); + void (*Respatialize)( int entityNum, const vec3_t origin, vec3_t axis[3], int inwater ); + void (*UpdateEntityPosition)( int entityNum, const vec3_t origin ); + void (*Update)( void ); + void (*DisableSounds)( void ); + void (*BeginRegistration)( void ); + sfxHandle_t (*RegisterSound)( const char *sample, qboolean compressed ); + void (*ClearSoundBuffer)( void ); + void (*SoundInfo)( void ); + void (*SoundList)( void ); +#ifdef USE_VOIP + void (*StartCapture)( void ); + int (*AvailableCaptureSamples)( void ); + void (*Capture)( int samples, byte *data ); + void (*StopCapture)( void ); + void (*MasterGain)( float gain ); +#endif +} soundInterface_t; + /* ==================================================================== @@ -141,6 +177,15 @@ void SNDDMA_BeginPainting (void); void SNDDMA_Submit(void); +#ifdef USE_VOIP +void SNDDMA_StartCapture(void); +int SNDDMA_AvailableCaptureSamples(void); +void SNDDMA_Capture(int samples, byte *data); +void SNDDMA_StopCapture(void); +void SNDDMA_MasterGain(float val); +#endif + + //==================================================================== #define MAX_CHANNELS 96 @@ -150,34 +195,33 @@ extern channel_t loop_channels[MAX_CHANNELS]; extern int numLoopChannels; extern int s_paintedtime; -extern int s_rawend; extern vec3_t listener_forward; extern vec3_t listener_right; extern vec3_t listener_up; extern dma_t dma; #define MAX_RAW_SAMPLES 16384 -extern portable_samplepair_t s_rawsamples[MAX_RAW_SAMPLES]; +#define MAX_RAW_STREAMS (MAX_CLIENTS * 2 + 1) +extern portable_samplepair_t s_rawsamples[MAX_RAW_STREAMS][MAX_RAW_SAMPLES]; +extern int s_rawend[MAX_RAW_STREAMS]; -extern cvar_t *s_volume; -extern cvar_t *s_nosound; -extern cvar_t *s_khz; -extern cvar_t *s_show; -extern cvar_t *s_mixahead; +extern cvar_t *s_volume; +extern cvar_t *s_musicVolume; +extern cvar_t *s_muted; +extern cvar_t *s_doppler; -extern cvar_t *s_testsound; -extern cvar_t *s_separation; +extern cvar_t *s_testsound; qboolean S_LoadSound( sfx_t *sfx ); void SND_free(sndBuffer *v); -sndBuffer* SND_malloc(); -void SND_setup(); +sndBuffer* SND_malloc( void ); +void SND_setup( void ); +void SND_shutdown(void); void S_PaintChannels(int endtime); void S_memoryLoad(sfx_t *sfx); -portable_samplepair_t *S_GetRawSamplePointer(); // spatializes a channel void S_Spatialize(channel_t *ch); @@ -192,7 +236,7 @@ void S_AdpcmGetSamples(sndBuffer *chunk, short *to); #define SENTINEL_MULAW_ZERO_RUN 127 #define SENTINEL_MULAW_FOUR_BIT_RUN 126 -void S_FreeOldestSound(); +void S_FreeOldestSound( void ); #define NXStream byte @@ -206,14 +250,22 @@ extern short *sfxScratchBuffer; extern sfx_t *sfxScratchPointer; extern int sfxScratchIndex; -#ifdef __linux__ -// https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=371 -// custom Snd_Memset implementation for glibc memset bug workaround -void Snd_Memset(void* dest, const int val, const size_t count); -#else -#define Snd_Memset Com_Memset -#endif +qboolean S_Base_Init( soundInterface_t *si ); -#ifdef __cplusplus -} +// OpenAL stuff +typedef enum +{ + SRCPRI_AMBIENT = 0, // Ambient sound effects + SRCPRI_ENTITY, // Entity sound effects + SRCPRI_ONESHOT, // One-shot sounds + SRCPRI_LOCAL, // Local sounds + SRCPRI_STREAM // Streams (music, cutscenes) +} alSrcPriority_t; + +typedef int srcHandle_t; + +qboolean S_AL_Init( soundInterface_t *si ); + +#ifdef idppc_altivec +void S_PaintChannelFrom16_altivec( portable_samplepair_t paintbuffer[PAINTBUFFER_SIZE], int snd_vol, channel_t *ch, const sfx_t *sc, int count, int sampleOffset, int bufferOffset ); #endif diff --git a/code/client/snd_main.c b/code/client/snd_main.c index fadab8f1..f9e6941f 100644 --- a/code/client/snd_main.c +++ b/code/client/snd_main.c @@ -22,19 +22,18 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include "client.h" +#include "snd_codec.h" #include "snd_local.h" #include "snd_public.h" +cvar_t *s_volume; cvar_t *s_muted; +cvar_t *s_musicVolume; +cvar_t *s_doppler; cvar_t *s_backend; cvar_t *s_muteWhenMinimized; cvar_t *s_muteWhenUnfocused; -#if 0 -cvar_t* s_volume; -cvar_t* s_musicVolume; -cvar_t* s_doppler; - static soundInterface_t si; /* @@ -81,23 +80,13 @@ static qboolean S_ValidSoundInterface( soundInterface_t *si ) S_StartSound ================= */ -void S_StartSound(const vec3_t origin, int entNum, int entChannel, sfxHandle_t sfxHandle, float volume, float minDist, float pitch, float maxDist, int streamed) +void S_StartSound( vec3_t origin, int entnum, int entchannel, sfxHandle_t sfx ) { if( si.StartSound ) { - si.StartSound( origin, entNum, entChannel, sfxHandle); + si.StartSound( origin, entnum, entchannel, sfx ); } } -/* -================= -S_StopSound -================= -*/ -void S_StopSound(int entnum, int channel) -{ - // FIXME: unimplemented -} - /* ================= S_StartLocalSound @@ -110,16 +99,6 @@ void S_StartLocalSound( sfxHandle_t sfx, int channelNum ) } } -/* -================= -S_StartLocalSound -================= -*/ -void S_StartLocalSoundByName(const char* sound_name, qboolean force_load) -{ - // FIXME: unimplemented -} - /* ================= S_StartBackgroundTrack @@ -161,23 +140,11 @@ void S_RawSamples (int stream, int samples, int rate, int width, int channels, S_StopAllSounds ================= */ -void S_StopAllSounds( qboolean stop_music ) +void S_StopAllSounds( void ) { if( si.StopAllSounds ) { si.StopAllSounds( ); } - - // FIXME: stop music -} - -/* -================= -S_ClearLoopingSoundsNoParam -================= -*/ -void S_ClearLoopingSoundsNoParam(void) -{ - S_ClearLoopingSounds(qtrue); } /* @@ -197,15 +164,12 @@ void S_ClearLoopingSounds( qboolean killall ) S_AddLoopingSound ================= */ -void S_AddLoopingSound(const vec3_t origin, const vec3_t velocity, sfxHandle_t sfxHandle, float volume, float minDist, float maxDist, float pitch, int flags) +void S_AddLoopingSound( int entityNum, const vec3_t origin, + const vec3_t velocity, sfxHandle_t sfx ) { - /* if( si.AddLoopingSound ) { si.AddLoopingSound( entityNum, origin, velocity, sfx ); } - */ - - // FIXME: unimplemented } /* @@ -238,27 +202,14 @@ void S_StopLoopingSound( int entityNum ) S_Respatialize ================= */ -void S_Respatialize(int entityNum, const vec3_t origin, - vec3_t axis[3], int inwater) +void S_Respatialize( int entityNum, const vec3_t origin, + vec3_t axis[3], int inwater ) { if( si.Respatialize ) { si.Respatialize( entityNum, origin, axis, inwater ); } } -/* -================= -S_Respatialize -================= -*/ -void S_RespatializeOld(int entityNum, const vec3_t origin, - vec3_t axis[3]) -{ - if (si.Respatialize) { - si.Respatialize(entityNum, origin, axis, 0); - } -} - /* ================= S_UpdateEntityPosition @@ -331,14 +282,13 @@ void S_BeginRegistration( void ) S_RegisterSound ================= */ -sfxHandle_t S_RegisterSound(const char *sample, qboolean compressed, qboolean streamed) +sfxHandle_t S_RegisterSound( const char *sample, qboolean compressed ) { if( si.RegisterSound ) { return si.RegisterSound( sample, compressed ); } else { return 0; } - // FIXME: infinite loop } /* @@ -598,294 +548,3 @@ void S_Shutdown( void ) S_CodecShutdown( ); } -/* -================= -S_IsSoundPlaying -================= -*/ -int S_IsSoundPlaying(int channelNumber, const char* name) -{ - // FIXME: stub - STUB(); -} - -/* -================= -MUSIC_Pause -================= -*/ -void MUSIC_Pause() -{ - // FIXME: stub - STUB(); -} - -/* -================= -MUSIC_Unpause -================= -*/ -void MUSIC_Unpause() -{ - // FIXME: stub - STUB(); -} - -/* -================= -MUSIC_LoadSoundtrackFile -================= -*/ -qboolean MUSIC_LoadSoundtrackFile(const char* filename) -{ - // FIXME: stub - STUB(); - return qfalse; -} - -/* -================= -MUSIC_SongValid -================= -*/ -qboolean MUSIC_SongValid(const char* mood) -{ - // FIXME: stub - STUB(); - return qfalse; -} - -/* -================= -MUSIC_Loaded -================= -*/ -qboolean MUSIC_Loaded(void) -{ - // FIXME: stub - STUB(); - return qfalse; -} - -/* -================= -Music_Update -================= -*/ -void Music_Update(void) -{ - // FIXME: stub - STUB(); -} - -/* -================= -MUSIC_SongEnded -================= -*/ -void MUSIC_SongEnded(void) -{ - // FIXME: stub - STUB(); -} - -/* -================= -S_StartSound -================= -*/ -void MUSIC_NewSoundtrack(const char* name) -{ - // FIXME: stub - STUB(); -} - -/* -================= -MUSIC_UpdateMood -================= -*/ -void MUSIC_UpdateMood(int current, int fallback) -{ - // FIXME: stub - //STUB(); -} - -/* -================= -MUSIC_UpdateVolume -================= -*/ -void MUSIC_UpdateVolume(float volume, float fade_time) -{ - // FIXME: stub - STUB(); -} - -/* -================= -MUSIC_StopAllSongs -================= -*/ -void MUSIC_StopAllSongs(void) -{ - // FIXME: stub - STUB(); -} - -/* -================= -MUSIC_FreeAllSongs -================= -*/ -void MUSIC_FreeAllSongs(void) -{ - // FIXME: stub - STUB(); -} - -/* -================= -MUSIC_Playing -================= -*/ -qboolean MUSIC_Playing(void) -{ - // FIXME: stub - STUB(); - return qfalse; -} - -/* -================= -MUSIC_FindSong -================= -*/ -int MUSIC_FindSong(const char* name) -{ - // FIXME: stub - STUB(); - return 0; -} - -/* -================= -MUSIC_CurrentSongChannel -================= -*/ -int MUSIC_CurrentSongChannel(void) -{ - // FIXME: stub - STUB(); - return 0; -} - -/* -================= -MUSIC_StopChannel -================= -*/ -void MUSIC_StopChannel(int channel_number) -{ - // FIXME: stub - STUB(); -} - -/* -================= -MUSIC_PlaySong -================= -*/ -qboolean MUSIC_PlaySong(const char* alias) -{ - // FIXME: stub - STUB(); - return qfalse; -} - -/* -================= -MUSIC_UpdateMusicVolumes -================= -*/ -void MUSIC_UpdateMusicVolumes(void) -{ - // FIXME: stub - STUB(); -} - -/* -================= -MUSIC_CheckForStoppedSongs -================= -*/ -void MUSIC_CheckForStoppedSongs(void) -{ - // FIXME: stub - STUB(); -} - -/* -================= -S_IsSoundRegistered -================= -*/ -qboolean S_IsSoundRegistered(const char* name) -{ - // FIXME: stub - return qfalse; -} -/* -================= -S_GetSoundTime -================= -*/ -float S_GetSoundTime(sfxHandle_t handle) -{ - // FIXME: stub - STUB(); - return 0.0; -} - -/* -================= -S_SetGlobalAmbientVolumeLevel -================= -*/ -void S_SetGlobalAmbientVolumeLevel(float volume) -{ - // FIXME: stub - STUB(); -} - -/* -================= -S_SetReverb -================= -*/ -void S_SetReverb(int reverb_type, float reverb_level) -{ - // FIXME: stub - STUB(); -} - -/* -================= -S_EndRegistration -================= -*/ -void S_EndRegistration(void) -{ - // FIXME: stub -} - -void S_UpdateEntity(int entityNum, const vec3_t origin, const vec3_t velocity, qboolean use_listener) -{ - // FIXME: stub -} - -void S_FadeSound(float fTime) -{ - // FIXME: stub -} -#endif diff --git a/code/client/snd_mem.c b/code/client/snd_mem.c index 1899f195..b692eb57 100644 --- a/code/client/snd_mem.c +++ b/code/client/snd_mem.c @@ -15,7 +15,7 @@ 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 Foobar; if not, write to the Free Software +along with Quake III Arena source code; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ @@ -30,6 +30,7 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA *****************************************************************************/ #include "snd_local.h" +#include "snd_codec.h" #define DEF_COMSOUNDMEGS "8" @@ -56,7 +57,7 @@ void SND_free(sndBuffer *v) { inUse += sizeof(sndBuffer); } -sndBuffer* SND_malloc() { +sndBuffer* SND_malloc(void) { sndBuffer *v; redo: if (freelist == NULL) { @@ -73,7 +74,7 @@ redo: return v; } -void SND_setup() { +void SND_setup(void) { sndBuffer *p, *q; cvar_t *cv; int scs; @@ -99,137 +100,12 @@ void SND_setup() { Com_Printf("Sound memory manager started\n"); } -/* -=============================================================================== - -WAV loading - -=============================================================================== -*/ - -static byte *data_p; -static byte *iff_end; -static byte *last_chunk; -static byte *iff_data; -static int iff_chunk_len; - -static short GetLittleShort(void) +void SND_shutdown(void) { - short val = 0; - val = *data_p; - val = val + (*(data_p+1)<<8); - data_p += 2; - return val; + free(sfxScratchBuffer); + free(buffer); } -static int GetLittleLong(void) -{ - int val = 0; - val = *data_p; - val = val + (*(data_p+1)<<8); - val = val + (*(data_p+2)<<16); - val = val + (*(data_p+3)<<24); - data_p += 4; - return val; -} - -static void FindNextChunk(char *name) -{ - while (1) - { - data_p=last_chunk; - - if (data_p >= iff_end) - { // didn't find the chunk - data_p = NULL; - return; - } - - data_p += 4; - iff_chunk_len = GetLittleLong(); - if (iff_chunk_len < 0) - { - data_p = NULL; - return; - } - data_p -= 8; - last_chunk = data_p + 8 + ( (iff_chunk_len + 1) & ~1 ); - if (!strncmp((char *)data_p, name, 4)) - return; - } -} - -static void FindChunk(char *name) -{ - last_chunk = iff_data; - FindNextChunk (name); -} - -/* -============ -GetWavinfo -============ -*/ -static wavinfo_t GetWavinfo (char *name, byte *wav, int wavlength) -{ - wavinfo_t info; - - Com_Memset (&info, 0, sizeof(info)); - - if (!wav) - return info; - - iff_data = wav; - iff_end = wav + wavlength; - -// find "RIFF" chunk - FindChunk("RIFF"); - if (!(data_p && !strncmp((char *)data_p+8, "WAVE", 4))) - { - Com_Printf("Missing RIFF/WAVE chunks\n"); - return info; - } - -// get "fmt " chunk - iff_data = data_p + 12; -// DumpChunks (); - - FindChunk("fmt "); - if (!data_p) - { - Com_Printf("Missing fmt chunk\n"); - return info; - } - data_p += 8; - info.format = GetLittleShort(); - info.channels = GetLittleShort(); - info.rate = GetLittleLong(); - data_p += 4+2; - info.width = GetLittleShort() / 8; - - if (info.format != 1) - { - Com_Printf("Microsoft PCM format only\n"); - return info; - } - - -// find data chunk - FindChunk("data"); - if (!data_p) - { - Com_Printf("Missing data chunk\n"); - return info; - } - - data_p += 4; - info.samples = GetLittleLong () / info.width; - info.dataofs = data_p - wav; - - return info; -} - - /* ================ ResampleSfx @@ -237,47 +113,53 @@ ResampleSfx resample / decimate to the current source rate ================ */ -static void ResampleSfx( sfx_t *sfx, int inrate, int inwidth, byte *data, qboolean compressed ) { +static int ResampleSfx( sfx_t *sfx, int channels, int inrate, int inwidth, int samples, byte *data, qboolean compressed ) { int outcount; int srcsample; float stepscale; - int i; + int i, j; int sample, samplefrac, fracstep; int part; sndBuffer *chunk; stepscale = (float)inrate / dma.speed; // this is usually 0.5, 1, or 2 - outcount = sfx->soundLength / stepscale; - sfx->soundLength = outcount; + outcount = samples / stepscale; + srcsample = 0; samplefrac = 0; - fracstep = stepscale * 256; + fracstep = stepscale * 256 * channels; chunk = sfx->soundData; for (i=0 ; i> 8; + srcsample += samplefrac >> 8; + samplefrac &= 255; samplefrac += fracstep; - if( inwidth == 2 ) { - sample = LittleShort ( ((short *)data)[srcsample] ); - } else { - sample = (int)( (unsigned char)(data[srcsample]) - 128) << 8; - } - part = (i&(SND_CHUNK_SIZE-1)); - if (part == 0) { - sndBuffer *newchunk; - newchunk = SND_malloc(); - if (chunk == NULL) { - sfx->soundData = newchunk; + for (j=0 ; jnext = newchunk; + sample = (unsigned int)( (unsigned char)(data[srcsample+j]) - 128) << 8; + } + part = (i*channels+j)&(SND_CHUNK_SIZE-1); + if (part == 0) { + sndBuffer *newchunk; + newchunk = SND_malloc(); + if (chunk == NULL) { + sfx->soundData = newchunk; + } else { + chunk->next = newchunk; + } + chunk = newchunk; } - chunk = newchunk; - } - chunk->sndChunk[part] = sample; + chunk->sndChunk[part] = sample; + } } + + return outcount; } /* @@ -287,35 +169,39 @@ ResampleSfx resample / decimate to the current source rate ================ */ -static int ResampleSfxRaw( short *sfx, int inrate, int inwidth, int samples, byte *data ) { +static int ResampleSfxRaw( short *sfx, int channels, int inrate, int inwidth, int samples, byte *data ) { int outcount; int srcsample; float stepscale; - int i; + int i, j; int sample, samplefrac, fracstep; stepscale = (float)inrate / dma.speed; // this is usually 0.5, 1, or 2 outcount = samples / stepscale; + srcsample = 0; samplefrac = 0; - fracstep = stepscale * 256; + fracstep = stepscale * 256 * channels; for (i=0 ; i> 8; + srcsample += samplefrac >> 8; + samplefrac &= 255; samplefrac += fracstep; - if( inwidth == 2 ) { - sample = LittleShort ( ((short *)data)[srcsample] ); - } else { - sample = (int)( (unsigned char)(data[srcsample]) - 128) << 8; + for (j=0 ; jsoundName[0] == '*') { - return qfalse; - } + snd_info_t info; +// int size; // load it in - size = FS_ReadFile( sfx->soundName, (void **)&data ); - if ( !data ) { + data = S_CodecLoad(sfx->soundName, &info); + if(!data) return qfalse; - } - - info = GetWavinfo( sfx->soundName, data, size ); - if ( info.channels != 1 ) { - Com_Printf ("%s is a stereo wav file\n", sfx->soundName); - FS_FreeFile (data); - return qfalse; - } if ( info.width == 1 ) { - Com_DPrintf(S_COLOR_YELLOW "WARNING: %s is a 8 bit wav file\n", sfx->soundName); + Com_DPrintf(S_COLOR_YELLOW "WARNING: %s is a 8 bit audio file\n", sfx->soundName); } if ( info.rate != 22050 ) { - Com_DPrintf(S_COLOR_YELLOW "WARNING: %s is not a 22kHz wav file\n", sfx->soundName); + Com_DPrintf(S_COLOR_YELLOW "WARNING: %s is not a 22kHz audio file\n", sfx->soundName); } - if (!info.samples) { - return qfalse; - } - - samples = Hunk_AllocateTempMemory(info.samples * sizeof(short) * 2); + samples = Hunk_AllocateTempMemory(info.channels * info.samples * sizeof(short) * 2); sfx->lastTimeUsed = Com_Milliseconds()+1; @@ -373,36 +242,37 @@ qboolean S_LoadSound( sfx_t *sfx ) // manager to do the right thing for us and page // sound in as needed - if( sfx->soundCompressed == qtrue) { + if( info.channels == 1 && sfx->soundCompressed == qtrue) { sfx->soundCompressionMethod = 1; sfx->soundData = NULL; - sfx->soundLength = ResampleSfxRaw( samples, info.rate, info.width, info.samples, (data + info.dataofs) ); + sfx->soundLength = ResampleSfxRaw( samples, info.channels, info.rate, info.width, info.samples, data + info.dataofs ); S_AdpcmEncodeSound(sfx, samples); #if 0 - } else if (info.samples>(SND_CHUNK_SIZE*16) && info.width >1) { + } else if (info.channels == 1 && info.samples>(SND_CHUNK_SIZE*16) && info.width >1) { sfx->soundCompressionMethod = 3; sfx->soundData = NULL; - sfx->soundLength = ResampleSfxRaw( samples, info.rate, info.width, info.samples, (data + info.dataofs) ); + sfx->soundLength = ResampleSfxRaw( samples, info.channels, info.rate, info.width, info.samples, (data + info.dataofs) ); encodeMuLaw( sfx, samples); - } else if (info.samples>(SND_CHUNK_SIZE*6400) && info.width >1) { + } else if (info.channels == 1 && info.samples>(SND_CHUNK_SIZE*6400) && info.width >1) { sfx->soundCompressionMethod = 2; sfx->soundData = NULL; - sfx->soundLength = ResampleSfxRaw( samples, info.rate, info.width, info.samples, (data + info.dataofs) ); + sfx->soundLength = ResampleSfxRaw( samples, info.channels, info.rate, info.width, info.samples, (data + info.dataofs) ); encodeWavelet( sfx, samples); #endif } else { sfx->soundCompressionMethod = 0; - sfx->soundLength = info.samples; sfx->soundData = NULL; - ResampleSfx( sfx, info.rate, info.width, data + info.dataofs, qfalse ); + sfx->soundLength = ResampleSfx( sfx, info.channels, info.rate, info.width, info.samples, data + info.dataofs, qfalse ); } + + sfx->soundChannels = info.channels; Hunk_FreeTempMemory(samples); - FS_FreeFile( data ); + Hunk_FreeTempMemory(data); return qtrue; } -void S_DisplayFreeMemory() { +void S_DisplayFreeMemory(void) { Com_Printf("%d bytes free sound buffer memory, %d total used\n", inUse, totalInUse); } diff --git a/code/client/snd_mix.c b/code/client/snd_mix.c index 02be59c5..b381861a 100644 --- a/code/client/snd_mix.c +++ b/code/client/snd_mix.c @@ -15,24 +15,23 @@ 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 Foobar; if not, write to the Free Software +along with Quake III Arena source code; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ // snd_mix.c -- portable code to mix sounds for snd_dma.c +#include "client.h" #include "snd_local.h" static portable_samplepair_t paintbuffer[PAINTBUFFER_SIZE]; static int snd_vol; -// bk001119 - these not static, required by unix/snd_mixa.s int* snd_p; int snd_linear_count; short* snd_out; -#if !( (defined __linux__ || defined __FreeBSD__ ) && (defined __i386__) ) // rb010123 -#if !id386 +#if !id386 // if configured not to use asm void S_WriteLinearBlastStereo16 (void) { @@ -58,6 +57,9 @@ void S_WriteLinearBlastStereo16 (void) snd_out[i+1] = val; } } +#elif defined(__GNUC__) +// uses snd_mixa.s +void S_WriteLinearBlastStereo16 (void); #else __declspec( naked ) void S_WriteLinearBlastStereo16 (void) @@ -104,10 +106,6 @@ LClampDone2: } } -#endif -#else -// forward declare, implementation somewhere else -void S_WriteLinearBlastStereo16 (void); #endif void S_TransferStereo16 (unsigned long *pbuf, int endtime) @@ -121,21 +119,24 @@ void S_TransferStereo16 (unsigned long *pbuf, int endtime) while (ls_paintedtime < endtime) { // handle recirculating buffer issues - lpos = ls_paintedtime & ((dma.samples>>1)-1); + lpos = ls_paintedtime % dma.fullsamples; - snd_out = (short *) pbuf + (lpos<<1); + snd_out = (short *) pbuf + (lpos<<1); // lpos * dma.channels - snd_linear_count = (dma.samples>>1) - lpos; + snd_linear_count = dma.fullsamples - lpos; if (ls_paintedtime + snd_linear_count > endtime) snd_linear_count = endtime - ls_paintedtime; - snd_linear_count <<= 1; + snd_linear_count <<= 1; // snd_linear_count *= dma.channels // write a linear blast of samples S_WriteLinearBlastStereo16 (); snd_p += snd_linear_count; - ls_paintedtime += (snd_linear_count>>1); + ls_paintedtime += (snd_linear_count>>1); // snd_linear_count / dma.channels + + if( CL_VideoRecording( ) ) + CL_WriteAVIAudioFrame( (byte *)snd_out, snd_linear_count << 1 ); // snd_linear_count * (dma.samplebits/8) } } @@ -149,19 +150,16 @@ void S_TransferPaintBuffer(int endtime) { int out_idx; int count; - int out_mask; int *p; int step; int val; + int i; unsigned long *pbuf; pbuf = (unsigned long *)dma.buffer; if ( s_testsound->integer ) { - int i; - int count; - // write a fixed sine wave count = (endtime - s_paintedtime); for (i=0 ; i= 2) + { + val = 0; + } + else + { + val = *p >> 8; + p+= step; + } + if (val > 0x7fff) + val = 0x7fff; + else if (val < -32767) /* clamp to one less than max to make division max out at -1.0f. */ + val = -32767; + out[out_idx] = ((float) val) / 32767.0f; + out_idx = (out_idx + 1) % dma.samples; + } + } + else if (dma.samplebits == 16) { short *out = (short *) pbuf; - while (count--) + for (i=0 ; i> 8; - p+= step; + if ((i % dma.channels) >= 2) + { + val = 0; + } + else + { + val = *p >> 8; + p+= step; + } if (val > 0x7fff) val = 0x7fff; else if (val < -32768) val = -32768; out[out_idx] = val; - out_idx = (out_idx + 1) & out_mask; + out_idx = (out_idx + 1) % dma.samples; } } else if (dma.samplebits == 8) { unsigned char *out = (unsigned char *) pbuf; - while (count--) + for (i=0 ; i> 8; - p+= step; + if ((i % dma.channels) >= 2) + { + val = 0; + } + else + { + val = *p >> 8; + p+= step; + } if (val > 0x7fff) val = 0x7fff; else if (val < -32768) val = -32768; out[out_idx] = (val>>8) + 128; - out_idx = (out_idx + 1) & out_mask; + out_idx = (out_idx + 1) % dma.samples; } } } @@ -223,14 +256,18 @@ CHANNEL MIXING =============================================================================== */ -static void S_PaintChannelFrom16( channel_t *ch, const sfx_t *sc, int count, int sampleOffset, int bufferOffset ) { +static void S_PaintChannelFrom16_scalar( channel_t *ch, const sfx_t *sc, int count, int sampleOffset, int bufferOffset ) { int data, aoff, boff; int leftvol, rightvol; int i, j; portable_samplepair_t *samp; sndBuffer *chunk; short *samples; - float ooff, fdata, fdiv, fleftvol, frightvol; + float ooff, fdata[2], fdiv, fleftvol, frightvol; + + if (sc->soundChannels <= 0) { + return; + } samp = &paintbuffer[ bufferOffset ]; @@ -238,6 +275,14 @@ static void S_PaintChannelFrom16( channel_t *ch, const sfx_t *sc, int count, int sampleOffset = sampleOffset*ch->oldDopplerScale; } + if ( sc->soundChannels == 2 ) { + sampleOffset *= sc->soundChannels; + + if ( sampleOffset & 1 ) { + sampleOffset &= ~1; + } + } + chunk = sc->soundData; while (sampleOffset>=SND_CHUNK_SIZE) { chunk = chunk->next; @@ -248,126 +293,16 @@ static void S_PaintChannelFrom16( channel_t *ch, const sfx_t *sc, int count, int } if (!ch->doppler || ch->dopplerScale==1.0f) { -#if idppc_altivec - vector signed short volume_vec; - vector unsigned int volume_shift; - int vectorCount, samplesLeft, chunkSamplesLeft; -#endif leftvol = ch->leftvol*snd_vol; rightvol = ch->rightvol*snd_vol; samples = chunk->sndChunk; -#if idppc_altivec - ((short *)&volume_vec)[0] = leftvol; - ((short *)&volume_vec)[1] = leftvol; - ((short *)&volume_vec)[4] = leftvol; - ((short *)&volume_vec)[5] = leftvol; - ((short *)&volume_vec)[2] = rightvol; - ((short *)&volume_vec)[3] = rightvol; - ((short *)&volume_vec)[6] = rightvol; - ((short *)&volume_vec)[7] = rightvol; - volume_shift = vec_splat_u32(8); - i = 0; - - while(i < count) { - /* Try to align destination to 16-byte boundary */ - while(i < count && (((unsigned long)&samp[i] & 0x1f) || ((count-i) < 8) || ((SND_CHUNK_SIZE - sampleOffset) < 8))) { - data = samples[sampleOffset++]; - samp[i].left += (data * leftvol)>>8; - samp[i].right += (data * rightvol)>>8; - - if (sampleOffset == SND_CHUNK_SIZE) { - chunk = chunk->next; - samples = chunk->sndChunk; - sampleOffset = 0; - } - i++; - } - /* Destination is now aligned. Process as many 8-sample - chunks as we can before we run out of room from the current - sound chunk. We do 8 per loop to avoid extra source data reads. */ - samplesLeft = count - i; - chunkSamplesLeft = SND_CHUNK_SIZE - sampleOffset; - if(samplesLeft > chunkSamplesLeft) - samplesLeft = chunkSamplesLeft; - - vectorCount = samplesLeft / 8; - - if(vectorCount) - { - vector unsigned char tmp; - vector short s0, s1, sampleData0, sampleData1; - vector short samples0, samples1; - vector signed int left0, right0; - vector signed int merge0, merge1; - vector signed int d0, d1, d2, d3; - vector unsigned char samplePermute0 = - (vector unsigned char)(0, 1, 4, 5, 0, 1, 4, 5, 2, 3, 6, 7, 2, 3, 6, 7); - vector unsigned char samplePermute1 = - (vector unsigned char)(8, 9, 12, 13, 8, 9, 12, 13, 10, 11, 14, 15, 10, 11, 14, 15); - vector unsigned char loadPermute0, loadPermute1; - - // Rather than permute the vectors after we load them to do the sample - // replication and rearrangement, we permute the alignment vector so - // we do everything in one step below and avoid data shuffling. - tmp = vec_lvsl(0,&samples[sampleOffset]); - loadPermute0 = vec_perm(tmp,tmp,samplePermute0); - loadPermute1 = vec_perm(tmp,tmp,samplePermute1); - - s0 = *(vector short *)&samples[sampleOffset]; - while(vectorCount) - { - /* Load up source (16-bit) sample data */ - s1 = *(vector short *)&samples[sampleOffset+7]; - - /* Load up destination sample data */ - d0 = *(vector signed int *)&samp[i]; - d1 = *(vector signed int *)&samp[i+2]; - d2 = *(vector signed int *)&samp[i+4]; - d3 = *(vector signed int *)&samp[i+6]; - - sampleData0 = vec_perm(s0,s1,loadPermute0); - sampleData1 = vec_perm(s0,s1,loadPermute1); - - merge0 = vec_mule(sampleData0,volume_vec); - merge0 = vec_sra(merge0,volume_shift); /* Shift down to proper range */ - - merge1 = vec_mulo(sampleData0,volume_vec); - merge1 = vec_sra(merge1,volume_shift); - - d0 = vec_add(merge0,d0); - d1 = vec_add(merge1,d1); - - merge0 = vec_mule(sampleData1,volume_vec); - merge0 = vec_sra(merge0,volume_shift); /* Shift down to proper range */ - - merge1 = vec_mulo(sampleData1,volume_vec); - merge1 = vec_sra(merge1,volume_shift); - - d2 = vec_add(merge0,d2); - d3 = vec_add(merge1,d3); - - /* Store destination sample data */ - *(vector signed int *)&samp[i] = d0; - *(vector signed int *)&samp[i+2] = d1; - *(vector signed int *)&samp[i+4] = d2; - *(vector signed int *)&samp[i+6] = d3; - - i += 8; - vectorCount--; - s0 = s1; - sampleOffset += 8; - } - if (sampleOffset == SND_CHUNK_SIZE) { - chunk = chunk->next; - samples = chunk->sndChunk; - sampleOffset = 0; - } - } - } -#else for ( i=0 ; i>8; + + if ( sc->soundChannels == 2 ) { + data = samples[sampleOffset++]; + } samp[i].right += (data * rightvol)>>8; if (sampleOffset == SND_CHUNK_SIZE) { @@ -376,7 +311,6 @@ static void S_PaintChannelFrom16( channel_t *ch, const sfx_t *sc, int count, int sampleOffset = 0; } } -#endif } else { fleftvol = ch->leftvol*snd_vol; frightvol = ch->rightvol*snd_vol; @@ -390,10 +324,10 @@ static void S_PaintChannelFrom16( channel_t *ch, const sfx_t *sc, int count, int for ( i=0 ; idopplerScale; + ooff = ooff + ch->dopplerScale * sc->soundChannels; boff = ooff; - fdata = 0; - for (j=aoff; jsoundChannels) { if (j == SND_CHUNK_SIZE) { chunk = chunk->next; if (!chunk) { @@ -402,15 +336,32 @@ static void S_PaintChannelFrom16( channel_t *ch, const sfx_t *sc, int count, int samples = chunk->sndChunk; ooff -= SND_CHUNK_SIZE; } - fdata += samples[j&(SND_CHUNK_SIZE-1)]; + if ( sc->soundChannels == 2 ) { + fdata[0] += samples[j&(SND_CHUNK_SIZE-1)]; + fdata[1] += samples[(j+1)&(SND_CHUNK_SIZE-1)]; + } else { + fdata[0] += samples[j&(SND_CHUNK_SIZE-1)]; + fdata[1] += samples[j&(SND_CHUNK_SIZE-1)]; + } } - fdiv = 256 * (boff-aoff); - samp[i].left += (fdata * fleftvol)/fdiv; - samp[i].right += (fdata * frightvol)/fdiv; + fdiv = 256 * (boff-aoff) / sc->soundChannels; + samp[i].left += (fdata[0] * fleftvol)/fdiv; + samp[i].right += (fdata[1] * frightvol)/fdiv; } } } +static void S_PaintChannelFrom16( channel_t *ch, const sfx_t *sc, int count, int sampleOffset, int bufferOffset ) { +#if idppc_altivec + if (com_altivec->integer) { + // must be in a separate translation unit or G3 systems will crash. + S_PaintChannelFrom16_altivec( paintbuffer, snd_vol, ch, sc, count, sampleOffset, bufferOffset ); + return; + } +#endif + S_PaintChannelFrom16_scalar( ch, sc, count, sampleOffset, bufferOffset ); +} + void S_PaintChannelFromWavelet( channel_t *ch, sfx_t *sc, int count, int sampleOffset, int bufferOffset ) { int data; int leftvol, rightvol; @@ -432,7 +383,7 @@ void S_PaintChannelFromWavelet( channel_t *ch, sfx_t *sc, int count, int sampleO } if (i!=sfxScratchIndex || sfxScratchPointer != sc) { - S_AdpcmGetSamples( chunk, sfxScratchBuffer ); + decodeWavelet(chunk, sfxScratchBuffer); sfxScratchIndex = i; sfxScratchPointer = sc; } @@ -529,7 +480,7 @@ void S_PaintChannelFromMuLaw( channel_t *ch, sfx_t *sc, int count, int sampleOff samp[i].left += (data * leftvol)>>8; samp[i].right += (data * rightvol)>>8; samples++; - if (samples == (byte *)chunk->sndChunk+(SND_CHUNK_SIZE*2)) { + if (chunk != NULL && samples == (byte *)chunk->sndChunk+(SND_CHUNK_SIZE*2)) { chunk = chunk->next; samples = (byte *)chunk->sndChunk; } @@ -562,13 +513,16 @@ S_PaintChannels void S_PaintChannels( int endtime ) { int i; int end; + int stream; channel_t *ch; sfx_t *sc; int ltime, count; int sampleOffset; - - snd_vol = s_volume->value*255; + if(s_muted->integer) + snd_vol = 0; + else + snd_vol = s_volume->value*255; //Com_Printf ("%i to %i\n", s_paintedtime, endtime); while ( s_paintedtime < endtime ) { @@ -579,30 +533,18 @@ void S_PaintChannels( int endtime ) { end = s_paintedtime + PAINTBUFFER_SIZE; } - // clear the paint buffer to either music or zeros - if ( s_rawend < s_paintedtime ) { - if ( s_rawend ) { - //Com_DPrintf ("background sound underrun\n"); - } - Com_Memset(paintbuffer, 0, (end - s_paintedtime) * sizeof(portable_samplepair_t)); - } else { - // copy from the streaming sound source - int s; - int stop; - - stop = (end < s_rawend) ? end : s_rawend; - - for ( i = s_paintedtime ; i < stop ; i++ ) { - s = i&(MAX_RAW_SAMPLES-1); - paintbuffer[i-s_paintedtime] = s_rawsamples[s]; - } -// if (i != end) -// Com_Printf ("partial stream\n"); -// else -// Com_Printf ("full stream\n"); - for ( ; i < end ; i++ ) { - paintbuffer[i-s_paintedtime].left = - paintbuffer[i-s_paintedtime].right = 0; + // clear the paint buffer and mix any raw samples... + Com_Memset(paintbuffer, 0, sizeof (paintbuffer)); + for (stream = 0; stream < MAX_RAW_STREAMS; stream++) { + if ( s_rawend[stream] >= s_paintedtime ) { + // copy from the streaming sound source + const portable_samplepair_t *rawsamples = s_rawsamples[stream]; + const int stop = (end < s_rawend[stream]) ? end : s_rawend[stream]; + for ( i = s_paintedtime ; i < stop ; i++ ) { + const int s = i&(MAX_RAW_SAMPLES-1); + paintbuffer[i-s_paintedtime].left += rawsamples[s].left; + paintbuffer[i-s_paintedtime].right += rawsamples[s].right; + } } } @@ -616,6 +558,10 @@ void S_PaintChannels( int endtime ) { ltime = s_paintedtime; sc = ch->thesfx; + if (sc->soundData==NULL || sc->soundLength==0) { + continue; + } + sampleOffset = ltime - ch->startSample; count = end - ltime; if ( sampleOffset + count > sc->soundLength ) { diff --git a/code/client/snd_openal.c b/code/client/snd_openal.c new file mode 100644 index 00000000..8184b074 --- /dev/null +++ b/code/client/snd_openal.c @@ -0,0 +1,2734 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2005 Stuart Dalton (badcdev@gmail.com) + +This file is part of Quake III Arena source code. + +Quake III Arena 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. + +Quake III Arena 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 Quake III Arena 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_codec.h" +#include "client.h" + +#ifdef USE_OPENAL + +#include "qal.h" + +// Console variables specific to OpenAL +cvar_t *s_alPrecache; +cvar_t *s_alGain; +cvar_t *s_alSources; +cvar_t *s_alDopplerFactor; +cvar_t *s_alDopplerSpeed; +cvar_t *s_alMinDistance; +cvar_t *s_alMaxDistance; +cvar_t *s_alRolloff; +cvar_t *s_alGraceDistance; +cvar_t *s_alDriver; +cvar_t *s_alDevice; +cvar_t *s_alInputDevice; +cvar_t *s_alAvailableDevices; +cvar_t *s_alAvailableInputDevices; + +static qboolean enumeration_ext = qfalse; +static qboolean enumeration_all_ext = qfalse; +#ifdef USE_VOIP +static qboolean capture_ext = qfalse; +#endif + +/* +================= +S_AL_Format +================= +*/ +static +ALuint S_AL_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_AL_ErrorMsg +================= +*/ +static const char *S_AL_ErrorMsg(ALenum error) +{ + switch(error) + { + case AL_NO_ERROR: + return "No error"; + case AL_INVALID_NAME: + return "Invalid name"; + case AL_INVALID_ENUM: + return "Invalid enumerator"; + case AL_INVALID_VALUE: + return "Invalid value"; + case AL_INVALID_OPERATION: + return "Invalid operation"; + case AL_OUT_OF_MEMORY: + return "Out of memory"; + default: + return "Unknown error"; + } +} + +/* +================= +S_AL_ClearError +================= +*/ +static void S_AL_ClearError( qboolean quiet ) +{ + int error = qalGetError(); + + if( quiet ) + return; + if(error != AL_NO_ERROR) + { + Com_Printf(S_COLOR_YELLOW "WARNING: unhandled AL error: %s\n", + S_AL_ErrorMsg(error)); + } +} + + +//=========================================================================== + + +typedef struct alSfx_s +{ + char filename[MAX_QPATH]; + ALuint buffer; // OpenAL buffer + snd_info_t info; // information for this sound like rate, sample count.. + + qboolean isDefault; // Couldn't be loaded - use default FX + qboolean isDefaultChecked; // Sound has been check if it isDefault + qboolean inMemory; // Sound is stored in memory + qboolean isLocked; // Sound is locked (can not be unloaded) + int lastUsedTime; // Time last used + + int loopCnt; // number of loops using this sfx + int loopActiveCnt; // number of playing loops using this sfx + int masterLoopSrc; // All other sources looping this buffer are synced to this master src +} alSfx_t; + +static qboolean alBuffersInitialised = qfalse; + +// Sound effect storage, data structures +#define MAX_SFX 4096 +static alSfx_t knownSfx[MAX_SFX]; +static sfxHandle_t numSfx = 0; + +static sfxHandle_t default_sfx; + +/* +================= +S_AL_BufferFindFree + +Find a free handle +================= +*/ +static sfxHandle_t S_AL_BufferFindFree( void ) +{ + int i; + + for(i = 0; i < MAX_SFX; i++) + { + // Got one + if(knownSfx[i].filename[0] == '\0') + { + if(i >= numSfx) + numSfx = i + 1; + return i; + } + } + + // Shit... + Com_Error(ERR_FATAL, "S_AL_BufferFindFree: No free sound handles"); + return -1; +} + +/* +================= +S_AL_BufferFind + +Find a sound effect if loaded, set up a handle otherwise +================= +*/ +static sfxHandle_t S_AL_BufferFind(const char *filename) +{ + // Look it up in the table + sfxHandle_t sfx = -1; + int i; + + if ( !filename ) { + Com_Error( ERR_FATAL, "Sound name is NULL" ); + } + + if ( !filename[0] ) { + Com_Printf( S_COLOR_YELLOW "WARNING: Sound name is empty\n" ); + return 0; + } + + if ( strlen( filename ) >= MAX_QPATH ) { + Com_Printf( S_COLOR_YELLOW "WARNING: Sound name is too long: %s\n", filename ); + return 0; + } + + if ( filename[0] == '*' ) { + Com_Printf( S_COLOR_YELLOW "WARNING: Tried to load player sound directly: %s\n", filename ); + return 0; + } + + for(i = 0; i < numSfx; i++) + { + if(!Q_stricmp(knownSfx[i].filename, filename)) + { + sfx = i; + break; + } + } + + // Not found in table? + if(sfx == -1) + { + alSfx_t *ptr; + + sfx = S_AL_BufferFindFree(); + + // Clear and copy the filename over + ptr = &knownSfx[sfx]; + memset(ptr, 0, sizeof(*ptr)); + ptr->masterLoopSrc = -1; + strcpy(ptr->filename, filename); + } + + // Return the handle + return sfx; +} + +/* +================= +S_AL_BufferUseDefault +================= +*/ +static void S_AL_BufferUseDefault(sfxHandle_t sfx) +{ + if(sfx == default_sfx) + Com_Error(ERR_FATAL, "Can't load default sound effect %s", knownSfx[sfx].filename); + + Com_Printf( S_COLOR_YELLOW "WARNING: Using default sound for %s\n", knownSfx[sfx].filename); + knownSfx[sfx].isDefault = qtrue; + knownSfx[sfx].buffer = knownSfx[default_sfx].buffer; +} + +/* +================= +S_AL_BufferUnload +================= +*/ +static void S_AL_BufferUnload(sfxHandle_t sfx) +{ + if(knownSfx[sfx].filename[0] == '\0') + return; + + if(!knownSfx[sfx].inMemory) + return; + + // Delete it + S_AL_ClearError( qfalse ); + qalDeleteBuffers(1, &knownSfx[sfx].buffer); + if(qalGetError() != AL_NO_ERROR) + Com_Printf( S_COLOR_RED "ERROR: Can't delete sound buffer for %s\n", + knownSfx[sfx].filename); + + knownSfx[sfx].inMemory = qfalse; +} + +/* +================= +S_AL_BufferEvict +================= +*/ +static qboolean S_AL_BufferEvict( void ) +{ + int i, oldestBuffer = -1; + int oldestTime = Sys_Milliseconds( ); + + for( i = 0; i < numSfx; i++ ) + { + if( !knownSfx[ i ].filename[ 0 ] ) + continue; + + if( !knownSfx[ i ].inMemory ) + continue; + + if( knownSfx[ i ].lastUsedTime < oldestTime ) + { + oldestTime = knownSfx[ i ].lastUsedTime; + oldestBuffer = i; + } + } + + if( oldestBuffer >= 0 ) + { + S_AL_BufferUnload( oldestBuffer ); + return qtrue; + } + else + return qfalse; +} + +/* +================= +S_AL_GenBuffers +================= +*/ +static qboolean S_AL_GenBuffers(ALsizei numBuffers, ALuint *buffers, const char *name) +{ + ALenum error; + + S_AL_ClearError( qfalse ); + qalGenBuffers( numBuffers, buffers ); + error = qalGetError(); + + // If we ran out of buffers, start evicting the least recently used sounds + while( error == AL_INVALID_VALUE ) + { + if( !S_AL_BufferEvict( ) ) + { + Com_Printf( S_COLOR_RED "ERROR: Out of audio buffers\n"); + return qfalse; + } + + // Try again + S_AL_ClearError( qfalse ); + qalGenBuffers( numBuffers, buffers ); + error = qalGetError(); + } + + if( error != AL_NO_ERROR ) + { + Com_Printf( S_COLOR_RED "ERROR: Can't create a sound buffer for %s - %s\n", + name, S_AL_ErrorMsg(error)); + return qfalse; + } + + return qtrue; +} + +/* +================= +S_AL_BufferLoad +================= +*/ +static void S_AL_BufferLoad(sfxHandle_t sfx, qboolean cache) +{ + ALenum error; + ALuint format; + + void *data; + snd_info_t info; + alSfx_t *curSfx = &knownSfx[sfx]; + + // Nothing? + if(curSfx->filename[0] == '\0') + return; + + // Already done? + if((curSfx->inMemory) || (curSfx->isDefault) || (!cache && curSfx->isDefaultChecked)) + return; + + // Try to load + data = S_CodecLoad(curSfx->filename, &info); + if(!data) + { + S_AL_BufferUseDefault(sfx); + return; + } + + curSfx->isDefaultChecked = qtrue; + + if (!cache) + { + // Don't create AL cache + Hunk_FreeTempMemory(data); + return; + } + + format = S_AL_Format(info.width, info.channels); + + // Create a buffer + if (!S_AL_GenBuffers(1, &curSfx->buffer, curSfx->filename)) + { + S_AL_BufferUseDefault(sfx); + Hunk_FreeTempMemory(data); + return; + } + + // Fill the buffer + if( info.size == 0 ) + { + // We have no data to buffer, so buffer silence + byte dummyData[ 2 ] = { 0 }; + + qalBufferData(curSfx->buffer, AL_FORMAT_MONO16, (void *)dummyData, 2, 22050); + } + else + qalBufferData(curSfx->buffer, format, data, info.size, info.rate); + + error = qalGetError(); + + // If we ran out of memory, start evicting the least recently used sounds + while(error == AL_OUT_OF_MEMORY) + { + if( !S_AL_BufferEvict( ) ) + { + qalDeleteBuffers(1, &curSfx->buffer); + S_AL_BufferUseDefault(sfx); + Hunk_FreeTempMemory(data); + Com_Printf( S_COLOR_RED "ERROR: Out of memory loading %s\n", curSfx->filename); + return; + } + + // Try load it again + qalBufferData(curSfx->buffer, format, data, info.size, info.rate); + error = qalGetError(); + } + + // Some other error condition + if(error != AL_NO_ERROR) + { + qalDeleteBuffers(1, &curSfx->buffer); + S_AL_BufferUseDefault(sfx); + Hunk_FreeTempMemory(data); + Com_Printf( S_COLOR_RED "ERROR: Can't fill sound buffer for %s - %s\n", + curSfx->filename, S_AL_ErrorMsg(error)); + return; + } + + curSfx->info = info; + + // Free the memory + Hunk_FreeTempMemory(data); + + // Woo! + curSfx->inMemory = qtrue; +} + +/* +================= +S_AL_BufferUse +================= +*/ +static +void S_AL_BufferUse(sfxHandle_t sfx) +{ + if(knownSfx[sfx].filename[0] == '\0') + return; + + if((!knownSfx[sfx].inMemory) && (!knownSfx[sfx].isDefault)) + S_AL_BufferLoad(sfx, qtrue); + knownSfx[sfx].lastUsedTime = Sys_Milliseconds(); +} + +/* +================= +S_AL_BufferInit +================= +*/ +static +qboolean S_AL_BufferInit( void ) +{ + if(alBuffersInitialised) + return qtrue; + + // Clear the hash table, and SFX table + memset(knownSfx, 0, sizeof(knownSfx)); + numSfx = 0; + + // Load the default sound, and lock it + default_sfx = S_AL_BufferFind("sound/feedback/hit.wav"); + S_AL_BufferUse(default_sfx); + knownSfx[default_sfx].isLocked = qtrue; + + // All done + alBuffersInitialised = qtrue; + return qtrue; +} + +/* +================= +S_AL_BufferShutdown +================= +*/ +static +void S_AL_BufferShutdown( void ) +{ + int i; + + if(!alBuffersInitialised) + return; + + // Unlock the default sound effect + knownSfx[default_sfx].isLocked = qfalse; + + // Free all used effects + for(i = 0; i < numSfx; i++) + S_AL_BufferUnload(i); + + // Clear the tables + numSfx = 0; + + // All undone + alBuffersInitialised = qfalse; +} + +/* +================= +S_AL_RegisterSound +================= +*/ +static +sfxHandle_t S_AL_RegisterSound( const char *sample, qboolean compressed ) +{ + sfxHandle_t sfx = S_AL_BufferFind(sample); + + if((!knownSfx[sfx].inMemory) && (!knownSfx[sfx].isDefault)) + S_AL_BufferLoad(sfx, s_alPrecache->integer); + knownSfx[sfx].lastUsedTime = Com_Milliseconds(); + + if (knownSfx[sfx].isDefault) { + return 0; + } + + return sfx; +} + +/* +================= +S_AL_BufferGet + +Return's a sfx's buffer +================= +*/ +static +ALuint S_AL_BufferGet(sfxHandle_t sfx) +{ + return knownSfx[sfx].buffer; +} + + +//=========================================================================== + + +typedef struct src_s +{ + ALuint alSource; // OpenAL source object + sfxHandle_t sfx; // Sound effect in use + + int lastUsedTime; // Last time used + alSrcPriority_t priority; // Priority + int entity; // Owning entity (-1 if none) + int channel; // Associated channel (-1 if none) + + qboolean isActive; // Is this source currently in use? + qboolean isPlaying; // Is this source currently playing, or stopped? + qboolean isLocked; // This is locked (un-allocatable) + qboolean isLooping; // Is this a looping effect (attached to an entity) + qboolean isTracking; // Is this object tracking its owner + qboolean isStream; // Is this source a stream + + float curGain; // gain employed if source is within maxdistance. + float scaleGain; // Last gain value for this source. 0 if muted. + + float lastTimePos; // On stopped loops, the last position in the buffer + int lastSampleTime; // Time when this was stopped + vec3_t loopSpeakerPos; // Origin of the loop speaker + + qboolean local; // Is this local (relative to the cam) +} src_t; + +#ifdef __APPLE__ + #define MAX_SRC 64 +#else + #define MAX_SRC 128 +#endif +static src_t srcList[MAX_SRC]; +static int srcCount = 0; +static int srcActiveCnt = 0; +static qboolean alSourcesInitialised = qfalse; +static int lastListenerNumber = -1; +static vec3_t lastListenerOrigin = { 0.0f, 0.0f, 0.0f }; + +typedef struct sentity_s +{ + vec3_t origin; + + qboolean srcAllocated; // If a src_t has been allocated to this entity + int srcIndex; + + qboolean loopAddedThisFrame; + alSrcPriority_t loopPriority; + sfxHandle_t loopSfx; + qboolean startLoopingSound; +} sentity_t; + +static sentity_t entityList[MAX_GENTITIES]; + +/* +================= +S_AL_SanitiseVector +================= +*/ +#define S_AL_SanitiseVector(v) _S_AL_SanitiseVector(v,__LINE__) +static void _S_AL_SanitiseVector( vec3_t v, int line ) +{ + if( Q_isnan( v[ 0 ] ) || Q_isnan( v[ 1 ] ) || Q_isnan( v[ 2 ] ) ) + { + Com_DPrintf( S_COLOR_YELLOW "WARNING: vector with one or more NaN components " + "being passed to OpenAL at %s:%d -- zeroing\n", __FILE__, line ); + VectorClear( v ); + } +} + +/* +================= +S_AL_Gain +Set gain to 0 if muted, otherwise set it to given value. +================= +*/ + +static void S_AL_Gain(ALuint source, float gainval) +{ + if(s_muted->integer) + qalSourcef(source, AL_GAIN, 0.0f); + else + qalSourcef(source, AL_GAIN, gainval); +} + +/* +================= +S_AL_ScaleGain +Adapt the gain if necessary to get a quicker fadeout when the source is too far away. +================= +*/ + +static void S_AL_ScaleGain(src_t *chksrc, vec3_t origin) +{ + float distance; + + if(!chksrc->local) + distance = Distance(origin, lastListenerOrigin); + + // If we exceed a certain distance, scale the gain linearly until the sound + // vanishes into nothingness. + if(!chksrc->local && (distance -= s_alMaxDistance->value) > 0) + { + float scaleFactor; + + if(distance >= s_alGraceDistance->value) + scaleFactor = 0.0f; + else + scaleFactor = 1.0f - distance / s_alGraceDistance->value; + + scaleFactor *= chksrc->curGain; + + if(chksrc->scaleGain != scaleFactor) + { + chksrc->scaleGain = scaleFactor; + S_AL_Gain(chksrc->alSource, chksrc->scaleGain); + } + } + else if(chksrc->scaleGain != chksrc->curGain) + { + chksrc->scaleGain = chksrc->curGain; + S_AL_Gain(chksrc->alSource, chksrc->scaleGain); + } +} + +/* +================= +S_AL_HearingThroughEntity + +Also see S_Base_HearingThroughEntity +================= +*/ +static qboolean S_AL_HearingThroughEntity( int entityNum ) +{ + float distanceSq; + + if( lastListenerNumber == entityNum ) + { + // This is an outrageous hack to detect + // whether or not the player is rendering in third person or not. We can't + // ask the renderer because the renderer has no notion of entities and we + // can't ask cgame since that would involve changing the API and hence mod + // compatibility. I don't think there is any way around this, but I'll leave + // the FIXME just in case anyone has a bright idea. + distanceSq = DistanceSquared( + entityList[ entityNum ].origin, + lastListenerOrigin ); + + if( distanceSq > THIRD_PERSON_THRESHOLD_SQ ) + return qfalse; //we're the player, but third person + else + return qtrue; //we're the player + } + else + return qfalse; //not the player +} + +/* +================= +S_AL_SrcInit +================= +*/ +static +qboolean S_AL_SrcInit( void ) +{ + int i; + int limit; + + // Clear the sources data structure + memset(srcList, 0, sizeof(srcList)); + srcCount = 0; + srcActiveCnt = 0; + + // Cap s_alSources to MAX_SRC + limit = s_alSources->integer; + if(limit > MAX_SRC) + limit = MAX_SRC; + else if(limit < 16) + limit = 16; + + S_AL_ClearError( qfalse ); + // Allocate as many sources as possible + for(i = 0; i < limit; i++) + { + qalGenSources(1, &srcList[i].alSource); + if(qalGetError() != AL_NO_ERROR) + break; + srcCount++; + } + + // All done. Print this for informational purposes + Com_Printf( "Allocated %d sources.\n", srcCount); + alSourcesInitialised = qtrue; + return qtrue; +} + +/* +================= +S_AL_SrcShutdown +================= +*/ +static +void S_AL_SrcShutdown( void ) +{ + int i; + src_t *curSource; + + if(!alSourcesInitialised) + return; + + // Destroy all the sources + for(i = 0; i < srcCount; i++) + { + curSource = &srcList[i]; + + if(curSource->isLocked) + Com_DPrintf( S_COLOR_YELLOW "WARNING: Source %d is locked\n", i); + + if(curSource->entity > 0) + entityList[curSource->entity].srcAllocated = qfalse; + + qalSourceStop(srcList[i].alSource); + qalDeleteSources(1, &srcList[i].alSource); + } + + memset(srcList, 0, sizeof(srcList)); + + alSourcesInitialised = qfalse; +} + +/* +================= +S_AL_SrcSetup +================= +*/ +static void S_AL_SrcSetup(srcHandle_t src, sfxHandle_t sfx, alSrcPriority_t priority, + int entity, int channel, qboolean local) +{ + src_t *curSource; + + // Set up src struct + curSource = &srcList[src]; + + curSource->lastUsedTime = Sys_Milliseconds(); + curSource->sfx = sfx; + curSource->priority = priority; + curSource->entity = entity; + curSource->channel = channel; + curSource->isPlaying = qfalse; + curSource->isLocked = qfalse; + curSource->isLooping = qfalse; + curSource->isTracking = qfalse; + curSource->isStream = qfalse; + curSource->curGain = s_alGain->value * s_volume->value; + curSource->scaleGain = curSource->curGain; + curSource->local = local; + + // Set up OpenAL source + if(sfx >= 0) + { + // Mark the SFX as used, and grab the raw AL buffer + S_AL_BufferUse(sfx); + qalSourcei(curSource->alSource, AL_BUFFER, S_AL_BufferGet(sfx)); + } + + qalSourcef(curSource->alSource, AL_PITCH, 1.0f); + S_AL_Gain(curSource->alSource, curSource->curGain); + qalSourcefv(curSource->alSource, AL_POSITION, vec3_origin); + qalSourcefv(curSource->alSource, AL_VELOCITY, vec3_origin); + qalSourcei(curSource->alSource, AL_LOOPING, AL_FALSE); + qalSourcef(curSource->alSource, AL_REFERENCE_DISTANCE, s_alMinDistance->value); + + if(local) + { + qalSourcei(curSource->alSource, AL_SOURCE_RELATIVE, AL_TRUE); + qalSourcef(curSource->alSource, AL_ROLLOFF_FACTOR, 0.0f); + } + else + { + qalSourcei(curSource->alSource, AL_SOURCE_RELATIVE, AL_FALSE); + qalSourcef(curSource->alSource, AL_ROLLOFF_FACTOR, s_alRolloff->value); + } +} + +/* +================= +S_AL_SaveLoopPos +Remove given source as loop master if it is the master and hand off master status to another source in this case. +================= +*/ + +static void S_AL_SaveLoopPos(src_t *dest, ALuint alSource) +{ + int error; + + S_AL_ClearError(qfalse); + + qalGetSourcef(alSource, AL_SEC_OFFSET, &dest->lastTimePos); + if((error = qalGetError()) != AL_NO_ERROR) + { + // Old OpenAL implementations don't support AL_SEC_OFFSET + + if(error != AL_INVALID_ENUM) + { + Com_Printf(S_COLOR_YELLOW "WARNING: Could not get time offset for alSource %d: %s\n", + alSource, S_AL_ErrorMsg(error)); + } + + dest->lastTimePos = -1; + } + else + dest->lastSampleTime = Sys_Milliseconds(); +} + +/* +================= +S_AL_NewLoopMaster +Remove given source as loop master if it is the master and hand off master status to another source in this case. +================= +*/ + +static void S_AL_NewLoopMaster(src_t *rmSource, qboolean iskilled) +{ + int index; + src_t *curSource = NULL; + alSfx_t *curSfx; + + curSfx = &knownSfx[rmSource->sfx]; + + if(rmSource->isPlaying) + curSfx->loopActiveCnt--; + if(iskilled) + curSfx->loopCnt--; + + if(curSfx->loopCnt) + { + if(rmSource->priority == SRCPRI_ENTITY) + { + if(!iskilled && rmSource->isPlaying) + { + // only sync ambient loops... + // It makes more sense to have sounds for weapons/projectiles unsynced + S_AL_SaveLoopPos(rmSource, rmSource->alSource); + } + } + else if(curSfx->masterLoopSrc != -1 && + rmSource == &srcList[curSfx->masterLoopSrc]) + { + int firstInactive = -1; + + // Only if rmSource was the master and if there are still playing loops for + // this sound will we need to find a new master. + + if(iskilled || curSfx->loopActiveCnt) + { + for(index = 0; index < srcCount; index++) + { + curSource = &srcList[index]; + + if(curSource->sfx == rmSource->sfx && curSource != rmSource && + curSource->isActive && curSource->isLooping && curSource->priority == SRCPRI_AMBIENT) + { + if(curSource->isPlaying) + { + curSfx->masterLoopSrc = index; + break; + } + else if(firstInactive < 0) + firstInactive = index; + } + } + } + + if(!curSfx->loopActiveCnt) + { + if(firstInactive < 0) + { + if(iskilled) + { + curSfx->masterLoopSrc = -1; + return; + } + else + curSource = rmSource; + } + else + curSource = &srcList[firstInactive]; + + if(rmSource->isPlaying) + { + // this was the last not stopped source, save last sample position + time + S_AL_SaveLoopPos(curSource, rmSource->alSource); + } + else + { + // second case: all loops using this sound have stopped due to listener being of of range, + // and now the inactive master gets deleted. Just move over the soundpos settings to the + // new master. + curSource->lastTimePos = rmSource->lastTimePos; + curSource->lastSampleTime = rmSource->lastSampleTime; + } + } + } + } + else + curSfx->masterLoopSrc = -1; +} + +/* +================= +S_AL_SrcKill +================= +*/ +static void S_AL_SrcKill(srcHandle_t src) +{ + src_t *curSource = &srcList[src]; + + // I'm not touching it. Unlock it first. + if(curSource->isLocked) + return; + + // Remove the entity association and loop master status + if(curSource->isLooping) + { + curSource->isLooping = qfalse; + + if(curSource->entity != -1) + { + sentity_t *curEnt = &entityList[curSource->entity]; + + curEnt->srcAllocated = qfalse; + curEnt->srcIndex = -1; + curEnt->loopAddedThisFrame = qfalse; + curEnt->startLoopingSound = qfalse; + } + + S_AL_NewLoopMaster(curSource, qtrue); + } + + // Stop it if it's playing + if(curSource->isPlaying) + { + qalSourceStop(curSource->alSource); + curSource->isPlaying = qfalse; + } + + // Detach any buffers + qalSourcei(curSource->alSource, AL_BUFFER, 0); + + curSource->sfx = 0; + curSource->lastUsedTime = 0; + curSource->priority = 0; + curSource->entity = -1; + curSource->channel = -1; + if(curSource->isActive) + { + curSource->isActive = qfalse; + srcActiveCnt--; + } + curSource->isLocked = qfalse; + curSource->isTracking = qfalse; + curSource->local = qfalse; +} + +/* +================= +S_AL_SrcAlloc +================= +*/ +static +srcHandle_t S_AL_SrcAlloc( alSrcPriority_t priority, int entnum, int channel ) +{ + int i; + int empty = -1; + int weakest = -1; + int weakest_time = Sys_Milliseconds(); + int weakest_pri = 999; + float weakest_gain = 1000.0; + qboolean weakest_isplaying = qtrue; + int weakest_numloops = 0; + src_t *curSource; + + for(i = 0; i < srcCount; i++) + { + curSource = &srcList[i]; + + // If it's locked, we aren't even going to look at it + if(curSource->isLocked) + continue; + + // Is it empty or not? + if(!curSource->isActive) + { + empty = i; + break; + } + + if(curSource->isPlaying) + { + if(weakest_isplaying && curSource->priority < priority && + (curSource->priority < weakest_pri || + (!curSource->isLooping && (curSource->scaleGain < weakest_gain || curSource->lastUsedTime < weakest_time)))) + { + // If it has lower priority, is fainter or older, flag it as weak + // the last two values are only compared if it's not a looping sound, because we want to prevent two + // loops (loops are added EVERY frame) fighting for a slot + weakest_pri = curSource->priority; + weakest_time = curSource->lastUsedTime; + weakest_gain = curSource->scaleGain; + weakest = i; + } + } + else + { + weakest_isplaying = qfalse; + + if(weakest < 0 || + knownSfx[curSource->sfx].loopCnt > weakest_numloops || + curSource->priority < weakest_pri || + curSource->lastUsedTime < weakest_time) + { + // Sources currently not playing of course have lowest priority + // also try to always keep at least one loop master for every loop sound + weakest_pri = curSource->priority; + weakest_time = curSource->lastUsedTime; + weakest_numloops = knownSfx[curSource->sfx].loopCnt; + weakest = i; + } + } + + // The channel system is not actually adhered to by baseq3, and not + // implemented in snd_dma.c, so while the following is strictly correct, it + // causes incorrect behaviour versus defacto baseq3 +#if 0 + // Is it an exact match, and not on channel 0? + if((curSource->entity == entnum) && (curSource->channel == channel) && (channel != 0)) + { + S_AL_SrcKill(i); + return i; + } +#endif + } + + if(empty == -1) + empty = weakest; + + if(empty >= 0) + { + S_AL_SrcKill(empty); + srcList[empty].isActive = qtrue; + srcActiveCnt++; + } + + return empty; +} + +/* +================= +S_AL_SrcFind + +Finds an active source with matching entity and channel numbers +Returns -1 if there isn't one +================= +*/ +#if 0 +static +srcHandle_t S_AL_SrcFind(int entnum, int channel) +{ + int i; + for(i = 0; i < srcCount; i++) + { + if(!srcList[i].isActive) + continue; + if((srcList[i].entity == entnum) && (srcList[i].channel == channel)) + return i; + } + return -1; +} +#endif + +/* +================= +S_AL_SrcLock + +Locked sources will not be automatically reallocated or managed +================= +*/ +static +void S_AL_SrcLock(srcHandle_t src) +{ + srcList[src].isLocked = qtrue; +} + +/* +================= +S_AL_SrcUnlock + +Once unlocked, the source may be reallocated again +================= +*/ +static +void S_AL_SrcUnlock(srcHandle_t src) +{ + srcList[src].isLocked = qfalse; +} + +/* +================= +S_AL_UpdateEntityPosition +================= +*/ +static +void S_AL_UpdateEntityPosition( int entityNum, const vec3_t origin ) +{ + vec3_t sanOrigin; + + VectorCopy( origin, sanOrigin ); + S_AL_SanitiseVector( sanOrigin ); + if ( entityNum < 0 || entityNum >= MAX_GENTITIES ) + Com_Error( ERR_DROP, "S_UpdateEntityPosition: bad entitynum %i", entityNum ); + VectorCopy( sanOrigin, entityList[entityNum].origin ); +} + +/* +================= +S_AL_CheckInput +Check whether input values from mods are out of range. +Necessary for i.g. Western Quake3 mod which is buggy. +================= +*/ +static qboolean S_AL_CheckInput(int entityNum, sfxHandle_t sfx) +{ + if (entityNum < 0 || entityNum >= MAX_GENTITIES) + Com_Error(ERR_DROP, "ERROR: S_AL_CheckInput: bad entitynum %i", entityNum); + + if (sfx < 0 || sfx >= numSfx) + { + Com_Printf(S_COLOR_RED "ERROR: S_AL_CheckInput: handle %i out of range\n", sfx); + return qtrue; + } + + return qfalse; +} + +/* +================= +S_AL_StartLocalSound + +Play a local (non-spatialized) sound effect +================= +*/ +static +void S_AL_StartLocalSound(sfxHandle_t sfx, int channel) +{ + srcHandle_t src; + + if(S_AL_CheckInput(0, sfx)) + return; + + // Try to grab a source + src = S_AL_SrcAlloc(SRCPRI_LOCAL, -1, channel); + + if(src == -1) + return; + + // Set up the effect + S_AL_SrcSetup(src, sfx, SRCPRI_LOCAL, -1, channel, qtrue); + + // Start it playing + srcList[src].isPlaying = qtrue; + qalSourcePlay(srcList[src].alSource); +} + +/* +================= +S_AL_StartSound + +Play a one-shot sound effect +================= +*/ +static void S_AL_StartSound( vec3_t origin, int entnum, int entchannel, sfxHandle_t sfx ) +{ + vec3_t sorigin; + srcHandle_t src; + src_t *curSource; + + if(origin) + { + if(S_AL_CheckInput(0, sfx)) + return; + + VectorCopy(origin, sorigin); + } + else + { + if(S_AL_CheckInput(entnum, sfx)) + return; + + if(S_AL_HearingThroughEntity(entnum)) + { + S_AL_StartLocalSound(sfx, entchannel); + return; + } + + VectorCopy(entityList[entnum].origin, sorigin); + } + + S_AL_SanitiseVector(sorigin); + + if((srcActiveCnt > 5 * srcCount / 3) && + (DistanceSquared(sorigin, lastListenerOrigin) >= + (s_alMaxDistance->value + s_alGraceDistance->value) * (s_alMaxDistance->value + s_alGraceDistance->value))) + { + // We're getting tight on sources and source is not within hearing distance so don't add it + return; + } + + // Try to grab a source + src = S_AL_SrcAlloc(SRCPRI_ONESHOT, entnum, entchannel); + if(src == -1) + return; + + S_AL_SrcSetup(src, sfx, SRCPRI_ONESHOT, entnum, entchannel, qfalse); + + curSource = &srcList[src]; + + if(!origin) + curSource->isTracking = qtrue; + + qalSourcefv(curSource->alSource, AL_POSITION, sorigin ); + S_AL_ScaleGain(curSource, sorigin); + + // Start it playing + curSource->isPlaying = qtrue; + qalSourcePlay(curSource->alSource); +} + +/* +================= +S_AL_ClearLoopingSounds +================= +*/ +static +void S_AL_ClearLoopingSounds( qboolean killall ) +{ + int i; + for(i = 0; i < srcCount; i++) + { + if((srcList[i].isLooping) && (srcList[i].entity != -1)) + entityList[srcList[i].entity].loopAddedThisFrame = qfalse; + } +} + +/* +================= +S_AL_SrcLoop +================= +*/ +static void S_AL_SrcLoop( alSrcPriority_t priority, sfxHandle_t sfx, + const vec3_t origin, const vec3_t velocity, int entityNum ) +{ + int src; + sentity_t *sent = &entityList[ entityNum ]; + src_t *curSource; + vec3_t sorigin, svelocity; + + if(S_AL_CheckInput(entityNum, sfx)) + return; + + // Do we need to allocate a new source for this entity + if( !sent->srcAllocated ) + { + // Try to get a channel + src = S_AL_SrcAlloc( priority, entityNum, -1 ); + if( src == -1 ) + { + Com_DPrintf( S_COLOR_YELLOW "WARNING: Failed to allocate source " + "for loop sfx %d on entity %d\n", sfx, entityNum ); + return; + } + + curSource = &srcList[src]; + + sent->startLoopingSound = qtrue; + + curSource->lastTimePos = -1.0; + curSource->lastSampleTime = Sys_Milliseconds(); + } + else + { + src = sent->srcIndex; + curSource = &srcList[src]; + } + + sent->srcAllocated = qtrue; + sent->srcIndex = src; + + sent->loopPriority = priority; + sent->loopSfx = sfx; + + // If this is not set then the looping sound is stopped. + sent->loopAddedThisFrame = qtrue; + + // UGH + // These lines should be called via S_AL_SrcSetup, but we + // can't call that yet as it buffers sfxes that may change + // with subsequent calls to S_AL_SrcLoop + curSource->entity = entityNum; + curSource->isLooping = qtrue; + + if( S_AL_HearingThroughEntity( entityNum ) ) + { + curSource->local = qtrue; + + VectorClear(sorigin); + + qalSourcefv(curSource->alSource, AL_POSITION, sorigin); + qalSourcefv(curSource->alSource, AL_VELOCITY, vec3_origin); + } + else + { + curSource->local = qfalse; + + if(origin) + VectorCopy(origin, sorigin); + else + VectorCopy(sent->origin, sorigin); + + S_AL_SanitiseVector(sorigin); + + VectorCopy(sorigin, curSource->loopSpeakerPos); + + if(velocity) + { + VectorCopy(velocity, svelocity); + S_AL_SanitiseVector(svelocity); + } + else + VectorClear(svelocity); + + qalSourcefv(curSource->alSource, AL_POSITION, (ALfloat *) sorigin); + qalSourcefv(curSource->alSource, AL_VELOCITY, (ALfloat *) svelocity); + } +} + +/* +================= +S_AL_AddLoopingSound +================= +*/ +static void S_AL_AddLoopingSound(int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfx) +{ + S_AL_SrcLoop(SRCPRI_ENTITY, sfx, origin, velocity, entityNum); +} + +/* +================= +S_AL_AddRealLoopingSound +================= +*/ +static void S_AL_AddRealLoopingSound(int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfx) +{ + S_AL_SrcLoop(SRCPRI_AMBIENT, sfx, origin, velocity, entityNum); +} + +/* +================= +S_AL_StopLoopingSound +================= +*/ +static +void S_AL_StopLoopingSound(int entityNum ) +{ + if(entityList[entityNum].srcAllocated) + S_AL_SrcKill(entityList[entityNum].srcIndex); +} + +/* +================= +S_AL_SrcUpdate + +Update state (move things around, manage sources, and so on) +================= +*/ +static +void S_AL_SrcUpdate( void ) +{ + int i; + int entityNum; + ALint state; + src_t *curSource; + + for(i = 0; i < srcCount; i++) + { + entityNum = srcList[i].entity; + curSource = &srcList[i]; + + if(curSource->isLocked) + continue; + + if(!curSource->isActive) + continue; + + // Update source parameters + if((s_alGain->modified) || (s_volume->modified)) + curSource->curGain = s_alGain->value * s_volume->value; + if((s_alRolloff->modified) && (!curSource->local)) + qalSourcef(curSource->alSource, AL_ROLLOFF_FACTOR, s_alRolloff->value); + if(s_alMinDistance->modified) + qalSourcef(curSource->alSource, AL_REFERENCE_DISTANCE, s_alMinDistance->value); + + if(curSource->isLooping) + { + sentity_t *sent = &entityList[ entityNum ]; + + // If a looping effect hasn't been touched this frame, pause or kill it + if(sent->loopAddedThisFrame) + { + alSfx_t *curSfx; + + // The sound has changed without an intervening removal + if(curSource->isActive && !sent->startLoopingSound && + curSource->sfx != sent->loopSfx) + { + S_AL_NewLoopMaster(curSource, qtrue); + + curSource->isPlaying = qfalse; + qalSourceStop(curSource->alSource); + qalSourcei(curSource->alSource, AL_BUFFER, 0); + sent->startLoopingSound = qtrue; + } + + // The sound hasn't been started yet + if(sent->startLoopingSound) + { + S_AL_SrcSetup(i, sent->loopSfx, sent->loopPriority, + entityNum, -1, curSource->local); + curSource->isLooping = qtrue; + + knownSfx[curSource->sfx].loopCnt++; + sent->startLoopingSound = qfalse; + } + + curSfx = &knownSfx[curSource->sfx]; + + S_AL_ScaleGain(curSource, curSource->loopSpeakerPos); + if(!curSource->scaleGain) + { + if(curSource->isPlaying) + { + // Sound is mute, stop playback until we are in range again + S_AL_NewLoopMaster(curSource, qfalse); + qalSourceStop(curSource->alSource); + curSource->isPlaying = qfalse; + } + else if(!curSfx->loopActiveCnt && curSfx->masterLoopSrc < 0) + curSfx->masterLoopSrc = i; + + continue; + } + + if(!curSource->isPlaying) + { + qalSourcei(curSource->alSource, AL_LOOPING, AL_TRUE); + curSource->isPlaying = qtrue; + qalSourcePlay(curSource->alSource); + + if(curSource->priority == SRCPRI_AMBIENT) + { + // If there are other ambient looping sources with the same sound, + // make sure the sound of these sources are in sync. + + if(curSfx->loopActiveCnt) + { + int offset, error; + + // we already have a master loop playing, get buffer position. + S_AL_ClearError(qfalse); + qalGetSourcei(srcList[curSfx->masterLoopSrc].alSource, AL_SAMPLE_OFFSET, &offset); + if((error = qalGetError()) != AL_NO_ERROR) + { + if(error != AL_INVALID_ENUM) + { + Com_Printf(S_COLOR_YELLOW "WARNING: Cannot get sample offset from source %d: " + "%s\n", i, S_AL_ErrorMsg(error)); + } + } + else + qalSourcei(curSource->alSource, AL_SAMPLE_OFFSET, offset); + } + else if(curSfx->loopCnt && curSfx->masterLoopSrc >= 0) + { + float secofs; + + src_t *master = &srcList[curSfx->masterLoopSrc]; + // This loop sound used to be played, but all sources are stopped. Use last sample position/time + // to calculate offset so the player thinks the sources continued playing while they were inaudible. + + if(master->lastTimePos >= 0) + { + secofs = master->lastTimePos + (Sys_Milliseconds() - master->lastSampleTime) / 1000.0f; + secofs = fmodf(secofs, (float) curSfx->info.samples / curSfx->info.rate); + + qalSourcef(curSource->alSource, AL_SEC_OFFSET, secofs); + } + + // I be the master now + curSfx->masterLoopSrc = i; + } + else + curSfx->masterLoopSrc = i; + } + else if(curSource->lastTimePos >= 0) + { + float secofs; + + // For unsynced loops (SRCPRI_ENTITY) just carry on playing as if the sound was never stopped + + secofs = curSource->lastTimePos + (Sys_Milliseconds() - curSource->lastSampleTime) / 1000.0f; + secofs = fmodf(secofs, (float) curSfx->info.samples / curSfx->info.rate); + qalSourcef(curSource->alSource, AL_SEC_OFFSET, secofs); + } + + curSfx->loopActiveCnt++; + } + + // Update locality + if(curSource->local) + { + qalSourcei(curSource->alSource, AL_SOURCE_RELATIVE, AL_TRUE); + qalSourcef(curSource->alSource, AL_ROLLOFF_FACTOR, 0.0f); + } + else + { + qalSourcei(curSource->alSource, AL_SOURCE_RELATIVE, AL_FALSE); + qalSourcef(curSource->alSource, AL_ROLLOFF_FACTOR, s_alRolloff->value); + } + + } + else if(curSource->priority == SRCPRI_AMBIENT) + { + if(curSource->isPlaying) + { + S_AL_NewLoopMaster(curSource, qfalse); + qalSourceStop(curSource->alSource); + curSource->isPlaying = qfalse; + } + } + else + S_AL_SrcKill(i); + + continue; + } + + if(!curSource->isStream) + { + // Check if it's done, and flag it + qalGetSourcei(curSource->alSource, AL_SOURCE_STATE, &state); + if(state == AL_STOPPED) + { + curSource->isPlaying = qfalse; + S_AL_SrcKill(i); + continue; + } + } + + // Query relativity of source, don't move if it's true + qalGetSourcei(curSource->alSource, AL_SOURCE_RELATIVE, &state); + + // See if it needs to be moved + if(curSource->isTracking && !state) + { + qalSourcefv(curSource->alSource, AL_POSITION, entityList[entityNum].origin); + S_AL_ScaleGain(curSource, entityList[entityNum].origin); + } + } +} + +/* +================= +S_AL_SrcShutup +================= +*/ +static +void S_AL_SrcShutup( void ) +{ + int i; + for(i = 0; i < srcCount; i++) + S_AL_SrcKill(i); +} + +/* +================= +S_AL_SrcGet +================= +*/ +static +ALuint S_AL_SrcGet(srcHandle_t src) +{ + return srcList[src].alSource; +} + + +//=========================================================================== + +// Q3A cinematics use up to 12 buffers at once +#define MAX_STREAM_BUFFERS 20 + +static srcHandle_t streamSourceHandles[MAX_RAW_STREAMS]; +static qboolean streamPlaying[MAX_RAW_STREAMS]; +static ALuint streamSources[MAX_RAW_STREAMS]; +static ALuint streamBuffers[MAX_RAW_STREAMS][MAX_STREAM_BUFFERS]; +static int streamNumBuffers[MAX_RAW_STREAMS]; +static int streamBufIndex[MAX_RAW_STREAMS]; + +/* +================= +S_AL_AllocateStreamChannel +================= +*/ +static void S_AL_AllocateStreamChannel(int stream, int entityNum) +{ + srcHandle_t cursrc; + ALuint alsrc; + + if ((stream < 0) || (stream >= MAX_RAW_STREAMS)) + return; + + if(entityNum >= 0) + { + // This is a stream that tracks an entity + // Allocate a streamSource at normal priority + cursrc = S_AL_SrcAlloc(SRCPRI_ENTITY, entityNum, 0); + if(cursrc < 0) + return; + + S_AL_SrcSetup(cursrc, -1, SRCPRI_ENTITY, entityNum, 0, qfalse); + alsrc = S_AL_SrcGet(cursrc); + srcList[cursrc].isTracking = qtrue; + srcList[cursrc].isStream = qtrue; + } + else + { + // Unspatialized stream source + + // Allocate a streamSource at high priority + cursrc = S_AL_SrcAlloc(SRCPRI_STREAM, -2, 0); + if(cursrc < 0) + return; + + alsrc = S_AL_SrcGet(cursrc); + + // Lock the streamSource so nobody else can use it, and get the raw streamSource + S_AL_SrcLock(cursrc); + + // make sure that after unmuting the S_AL_Gain in S_Update() does not turn + // volume up prematurely for this source + srcList[cursrc].scaleGain = 0.0f; + + // Set some streamSource parameters + qalSourcei (alsrc, AL_BUFFER, 0 ); + qalSourcei (alsrc, AL_LOOPING, AL_FALSE ); + qalSource3f(alsrc, AL_POSITION, 0.0, 0.0, 0.0); + qalSource3f(alsrc, AL_VELOCITY, 0.0, 0.0, 0.0); + qalSource3f(alsrc, AL_DIRECTION, 0.0, 0.0, 0.0); + qalSourcef (alsrc, AL_ROLLOFF_FACTOR, 0.0 ); + qalSourcei (alsrc, AL_SOURCE_RELATIVE, AL_TRUE ); + } + + streamSourceHandles[stream] = cursrc; + streamSources[stream] = alsrc; + + streamNumBuffers[stream] = 0; + streamBufIndex[stream] = 0; +} + +/* +================= +S_AL_FreeStreamChannel +================= +*/ +static void S_AL_FreeStreamChannel( int stream ) +{ + if ((stream < 0) || (stream >= MAX_RAW_STREAMS)) + return; + + // Detach any buffers + qalSourcei(streamSources[stream], AL_BUFFER, 0); + + // Delete the buffers + if (streamNumBuffers[stream] > 0) { + qalDeleteBuffers(streamNumBuffers[stream], streamBuffers[stream]); + streamNumBuffers[stream] = 0; + } + + // Release the output streamSource + S_AL_SrcUnlock(streamSourceHandles[stream]); + S_AL_SrcKill(streamSourceHandles[stream]); + streamSources[stream] = 0; + streamSourceHandles[stream] = -1; +} + +/* +================= +S_AL_RawSamples +================= +*/ +static +void S_AL_RawSamples(int stream, int samples, int rate, int width, int channels, const byte *data, float volume, int entityNum) +{ + int numBuffers; + ALuint buffer; + ALuint format; + + if ((stream < 0) || (stream >= MAX_RAW_STREAMS)) + return; + + format = S_AL_Format( width, channels ); + + // Create the streamSource if necessary + if(streamSourceHandles[stream] == -1) + { + S_AL_AllocateStreamChannel(stream, entityNum); + + // Failed? + if(streamSourceHandles[stream] == -1) + { + Com_Printf( S_COLOR_RED "ERROR: Can't allocate streaming streamSource\n"); + return; + } + } + + qalGetSourcei(streamSources[stream], AL_BUFFERS_QUEUED, &numBuffers); + + if (numBuffers == MAX_STREAM_BUFFERS) + { + Com_DPrintf(S_COLOR_RED"WARNING: Steam dropping raw samples, reached MAX_STREAM_BUFFERS\n"); + return; + } + + // Allocate a new AL buffer if needed + if (numBuffers == streamNumBuffers[stream]) + { + ALuint oldBuffers[MAX_STREAM_BUFFERS]; + int i; + + if (!S_AL_GenBuffers(1, &buffer, "stream")) + return; + + Com_Memcpy(oldBuffers, &streamBuffers[stream], sizeof (oldBuffers)); + + // Reorder buffer array in order of oldest to newest + for ( i = 0; i < streamNumBuffers[stream]; ++i ) + streamBuffers[stream][i] = oldBuffers[(streamBufIndex[stream] + i) % streamNumBuffers[stream]]; + + // Add the new buffer to end + streamBuffers[stream][streamNumBuffers[stream]] = buffer; + streamBufIndex[stream] = streamNumBuffers[stream]; + streamNumBuffers[stream]++; + } + + // Select next buffer in loop + buffer = streamBuffers[stream][ streamBufIndex[stream] ]; + streamBufIndex[stream] = (streamBufIndex[stream] + 1) % streamNumBuffers[stream]; + + // Fill buffer + qalBufferData(buffer, format, (ALvoid *)data, (samples * width * channels), rate); + + // Shove the data onto the streamSource + qalSourceQueueBuffers(streamSources[stream], 1, &buffer); + + if(entityNum < 0) + { + // Volume + S_AL_Gain (streamSources[stream], volume * s_volume->value * s_alGain->value); + } + + // Start stream + if(!streamPlaying[stream]) + { + qalSourcePlay( streamSources[stream] ); + streamPlaying[stream] = qtrue; + } +} + +/* +================= +S_AL_StreamUpdate +================= +*/ +static +void S_AL_StreamUpdate( int stream ) +{ + int numBuffers; + ALint state; + + if ((stream < 0) || (stream >= MAX_RAW_STREAMS)) + return; + + if(streamSourceHandles[stream] == -1) + return; + + // Un-queue any buffers + qalGetSourcei( streamSources[stream], AL_BUFFERS_PROCESSED, &numBuffers ); + while( numBuffers-- ) + { + ALuint buffer; + qalSourceUnqueueBuffers(streamSources[stream], 1, &buffer); + } + + // Start the streamSource playing if necessary + qalGetSourcei( streamSources[stream], AL_BUFFERS_QUEUED, &numBuffers ); + + qalGetSourcei(streamSources[stream], AL_SOURCE_STATE, &state); + if(state == AL_STOPPED) + { + streamPlaying[stream] = qfalse; + + // If there are no buffers queued up, release the streamSource + if( !numBuffers ) + S_AL_FreeStreamChannel( stream ); + } + + if( !streamPlaying[stream] && numBuffers ) + { + qalSourcePlay( streamSources[stream] ); + streamPlaying[stream] = qtrue; + } +} + +/* +================= +S_AL_StreamDie +================= +*/ +static +void S_AL_StreamDie( int stream ) +{ + if ((stream < 0) || (stream >= MAX_RAW_STREAMS)) + return; + + if(streamSourceHandles[stream] == -1) + return; + + streamPlaying[stream] = qfalse; + qalSourceStop(streamSources[stream]); + + S_AL_FreeStreamChannel(stream); +} + + +//=========================================================================== + + +#define NUM_MUSIC_BUFFERS 4 +#define MUSIC_BUFFER_SIZE 4096 + +static qboolean musicPlaying = qfalse; +static srcHandle_t musicSourceHandle = -1; +static ALuint musicSource; +static ALuint musicBuffers[NUM_MUSIC_BUFFERS]; + +static snd_stream_t *mus_stream; +static snd_stream_t *intro_stream; +static char s_backgroundLoop[MAX_QPATH]; + +static byte decode_buffer[MUSIC_BUFFER_SIZE]; + +/* +================= +S_AL_MusicSourceGet +================= +*/ +static void S_AL_MusicSourceGet( void ) +{ + // Allocate a musicSource at high priority + musicSourceHandle = S_AL_SrcAlloc(SRCPRI_STREAM, -2, 0); + if(musicSourceHandle == -1) + return; + + // Lock the musicSource so nobody else can use it, and get the raw musicSource + S_AL_SrcLock(musicSourceHandle); + musicSource = S_AL_SrcGet(musicSourceHandle); + + // make sure that after unmuting the S_AL_Gain in S_Update() does not turn + // volume up prematurely for this source + srcList[musicSourceHandle].scaleGain = 0.0f; + + // Set some musicSource parameters + qalSource3f(musicSource, AL_POSITION, 0.0, 0.0, 0.0); + qalSource3f(musicSource, AL_VELOCITY, 0.0, 0.0, 0.0); + qalSource3f(musicSource, AL_DIRECTION, 0.0, 0.0, 0.0); + qalSourcef (musicSource, AL_ROLLOFF_FACTOR, 0.0 ); + qalSourcei (musicSource, AL_SOURCE_RELATIVE, AL_TRUE ); +} + +/* +================= +S_AL_MusicSourceFree +================= +*/ +static void S_AL_MusicSourceFree( void ) +{ + // Release the output musicSource + S_AL_SrcUnlock(musicSourceHandle); + S_AL_SrcKill(musicSourceHandle); + musicSource = 0; + musicSourceHandle = -1; +} + +/* +================= +S_AL_CloseMusicFiles +================= +*/ +static void S_AL_CloseMusicFiles(void) +{ + if(intro_stream) + { + S_CodecCloseStream(intro_stream); + intro_stream = NULL; + } + + if(mus_stream) + { + S_CodecCloseStream(mus_stream); + mus_stream = NULL; + } +} + +/* +================= +S_AL_StopBackgroundTrack +================= +*/ +static +void S_AL_StopBackgroundTrack( void ) +{ + if(!musicPlaying) + return; + + // Stop playing + qalSourceStop(musicSource); + + // Detach any buffers + qalSourcei(musicSource, AL_BUFFER, 0); + + // Delete the buffers + qalDeleteBuffers(NUM_MUSIC_BUFFERS, musicBuffers); + + // Free the musicSource + S_AL_MusicSourceFree(); + + // Unload the stream + S_AL_CloseMusicFiles(); + + musicPlaying = qfalse; +} + +/* +================= +S_AL_MusicProcess +================= +*/ +static +void S_AL_MusicProcess(ALuint b) +{ + ALenum error; + int l; + ALuint format; + snd_stream_t *curstream; + + S_AL_ClearError( qfalse ); + + if(intro_stream) + curstream = intro_stream; + else + curstream = mus_stream; + + if(!curstream) + return; + + l = S_CodecReadStream(curstream, MUSIC_BUFFER_SIZE, decode_buffer); + + // Run out data to read, start at the beginning again + if(l == 0) + { + S_CodecCloseStream(curstream); + + // the intro stream just finished playing so we don't need to reopen + // the music stream. + if(intro_stream) + intro_stream = NULL; + else + mus_stream = S_CodecOpenStream(s_backgroundLoop); + + curstream = mus_stream; + + if(!curstream) + { + S_AL_StopBackgroundTrack(); + return; + } + + l = S_CodecReadStream(curstream, MUSIC_BUFFER_SIZE, decode_buffer); + } + + format = S_AL_Format(curstream->info.width, curstream->info.channels); + + if( l == 0 ) + { + // We have no data to buffer, so buffer silence + byte dummyData[ 2 ] = { 0 }; + + qalBufferData( b, AL_FORMAT_MONO16, (void *)dummyData, 2, 22050 ); + } + else + qalBufferData(b, format, decode_buffer, l, curstream->info.rate); + + if( ( error = qalGetError( ) ) != AL_NO_ERROR ) + { + S_AL_StopBackgroundTrack( ); + Com_Printf( S_COLOR_RED "ERROR: while buffering data for music stream - %s\n", + S_AL_ErrorMsg( error ) ); + return; + } +} + +/* +================= +S_AL_StartBackgroundTrack +================= +*/ +static +void S_AL_StartBackgroundTrack( const char *intro, const char *loop ) +{ + int i; + qboolean issame; + + // Stop any existing music that might be playing + S_AL_StopBackgroundTrack(); + + if((!intro || !*intro) && (!loop || !*loop)) + return; + + // Allocate a musicSource + S_AL_MusicSourceGet(); + if(musicSourceHandle == -1) + return; + + if (!loop || !*loop) + { + loop = intro; + issame = qtrue; + } + else if(intro && *intro && !strcmp(intro, loop)) + issame = qtrue; + else + issame = qfalse; + + // Copy the loop over + Q_strncpyz( s_backgroundLoop, loop, sizeof( s_backgroundLoop ) ); + + if(!issame) + { + // Open the intro and don't mind whether it succeeds. + // The important part is the loop. + intro_stream = S_CodecOpenStream(intro); + } + else + intro_stream = NULL; + + mus_stream = S_CodecOpenStream(s_backgroundLoop); + if(!mus_stream) + { + S_AL_CloseMusicFiles(); + S_AL_MusicSourceFree(); + return; + } + + // Generate the musicBuffers + if (!S_AL_GenBuffers(NUM_MUSIC_BUFFERS, musicBuffers, "music")) + return; + + // Queue the musicBuffers up + for(i = 0; i < NUM_MUSIC_BUFFERS; i++) + { + S_AL_MusicProcess(musicBuffers[i]); + } + + qalSourceQueueBuffers(musicSource, NUM_MUSIC_BUFFERS, musicBuffers); + + // Set the initial gain property + S_AL_Gain(musicSource, s_alGain->value * s_musicVolume->value); + + // Start playing + qalSourcePlay(musicSource); + + musicPlaying = qtrue; +} + +/* +================= +S_AL_MusicUpdate +================= +*/ +static +void S_AL_MusicUpdate( void ) +{ + int numBuffers; + ALint state; + + if(!musicPlaying) + return; + + qalGetSourcei( musicSource, AL_BUFFERS_PROCESSED, &numBuffers ); + while( numBuffers-- ) + { + ALuint b; + qalSourceUnqueueBuffers(musicSource, 1, &b); + S_AL_MusicProcess(b); + qalSourceQueueBuffers(musicSource, 1, &b); + } + + // Hitches can cause OpenAL to be starved of buffers when streaming. + // If this happens, it will stop playback. This restarts the source if + // it is no longer playing, and if there are buffers available + qalGetSourcei( musicSource, AL_SOURCE_STATE, &state ); + qalGetSourcei( musicSource, AL_BUFFERS_QUEUED, &numBuffers ); + if( state == AL_STOPPED && numBuffers ) + { + Com_DPrintf( S_COLOR_YELLOW "Restarted OpenAL music\n" ); + qalSourcePlay(musicSource); + } + + // Set the gain property + S_AL_Gain(musicSource, s_alGain->value * s_musicVolume->value); +} + + +//=========================================================================== + + +// Local state variables +static ALCdevice *alDevice; +static ALCcontext *alContext; + +#ifdef USE_VOIP +static ALCdevice *alCaptureDevice; +static cvar_t *s_alCapture; +#endif + +#if defined(_WIN64) +#define ALDRIVER_DEFAULT "OpenAL64.dll" +#elif defined(_WIN32) +#define ALDRIVER_DEFAULT "OpenAL32.dll" +#elif defined(__APPLE__) +#define ALDRIVER_DEFAULT "/System/Library/Frameworks/OpenAL.framework/OpenAL" +#elif defined(__OpenBSD__) +#define ALDRIVER_DEFAULT "libopenal.so" +#else +#define ALDRIVER_DEFAULT "libopenal.so.1" +#endif + +/* +================= +S_AL_StopAllSounds +================= +*/ +static +void S_AL_StopAllSounds( void ) +{ + int i; + S_AL_SrcShutup(); + S_AL_StopBackgroundTrack(); + for (i = 0; i < MAX_RAW_STREAMS; i++) + S_AL_StreamDie(i); +} + +/* +================= +S_AL_Respatialize +================= +*/ +static +void S_AL_Respatialize( int entityNum, const vec3_t origin, vec3_t axis[3], int inwater ) +{ + float orientation[6]; + vec3_t sorigin; + + VectorCopy( origin, sorigin ); + S_AL_SanitiseVector( sorigin ); + + S_AL_SanitiseVector( axis[ 0 ] ); + S_AL_SanitiseVector( axis[ 1 ] ); + S_AL_SanitiseVector( axis[ 2 ] ); + + orientation[0] = axis[0][0]; orientation[1] = axis[0][1]; orientation[2] = axis[0][2]; + orientation[3] = axis[2][0]; orientation[4] = axis[2][1]; orientation[5] = axis[2][2]; + + lastListenerNumber = entityNum; + VectorCopy( sorigin, lastListenerOrigin ); + + // Set OpenAL listener paramaters + qalListenerfv(AL_POSITION, (ALfloat *)sorigin); + qalListenerfv(AL_VELOCITY, vec3_origin); + qalListenerfv(AL_ORIENTATION, orientation); +} + +/* +================= +S_AL_Update +================= +*/ +static +void S_AL_Update( void ) +{ + int i; + + if(s_muted->modified) + { + // muted state changed. Let S_AL_Gain turn up all sources again. + for(i = 0; i < srcCount; i++) + { + if(srcList[i].isActive) + S_AL_Gain(srcList[i].alSource, srcList[i].scaleGain); + } + + s_muted->modified = qfalse; + } + + // Update SFX channels + S_AL_SrcUpdate(); + + // Update streams + for (i = 0; i < MAX_RAW_STREAMS; i++) + S_AL_StreamUpdate(i); + S_AL_MusicUpdate(); + + // Doppler + if(s_doppler->modified) + { + s_alDopplerFactor->modified = qtrue; + s_doppler->modified = qfalse; + } + + // Doppler parameters + if(s_alDopplerFactor->modified) + { + if(s_doppler->integer) + qalDopplerFactor(s_alDopplerFactor->value); + else + qalDopplerFactor(0.0f); + s_alDopplerFactor->modified = qfalse; + } + if(s_alDopplerSpeed->modified) + { + qalSpeedOfSound(s_alDopplerSpeed->value); + s_alDopplerSpeed->modified = qfalse; + } + + // Clear the modified flags on the other cvars + s_alGain->modified = qfalse; + s_volume->modified = qfalse; + s_musicVolume->modified = qfalse; + s_alMinDistance->modified = qfalse; + s_alRolloff->modified = qfalse; +} + +/* +================= +S_AL_DisableSounds +================= +*/ +static +void S_AL_DisableSounds( void ) +{ + S_AL_StopAllSounds(); +} + +/* +================= +S_AL_BeginRegistration +================= +*/ +static +void S_AL_BeginRegistration( void ) +{ + if(!numSfx) + S_AL_BufferInit(); +} + +/* +================= +S_AL_ClearSoundBuffer +================= +*/ +static +void S_AL_ClearSoundBuffer( void ) +{ +} + +/* +================= +S_AL_SoundList +================= +*/ +static +void S_AL_SoundList( void ) +{ +} + +#ifdef USE_VOIP +static +void S_AL_StartCapture( void ) +{ + if (alCaptureDevice != NULL) + qalcCaptureStart(alCaptureDevice); +} + +static +int S_AL_AvailableCaptureSamples( void ) +{ + int retval = 0; + if (alCaptureDevice != NULL) + { + ALint samples = 0; + qalcGetIntegerv(alCaptureDevice, ALC_CAPTURE_SAMPLES, sizeof (samples), &samples); + retval = (int) samples; + } + return retval; +} + +static +void S_AL_Capture( int samples, byte *data ) +{ + if (alCaptureDevice != NULL) + qalcCaptureSamples(alCaptureDevice, data, samples); +} + +void S_AL_StopCapture( void ) +{ + if (alCaptureDevice != NULL) + qalcCaptureStop(alCaptureDevice); +} + +void S_AL_MasterGain( float gain ) +{ + qalListenerf(AL_GAIN, gain); +} +#endif + + +/* +================= +S_AL_SoundInfo +================= +*/ +static void S_AL_SoundInfo(void) +{ + Com_Printf( "OpenAL info:\n" ); + Com_Printf( " Vendor: %s\n", qalGetString( AL_VENDOR ) ); + Com_Printf( " Version: %s\n", qalGetString( AL_VERSION ) ); + Com_Printf( " Renderer: %s\n", qalGetString( AL_RENDERER ) ); + Com_Printf( " AL Extensions: %s\n", qalGetString( AL_EXTENSIONS ) ); + Com_Printf( " ALC Extensions: %s\n", qalcGetString( alDevice, ALC_EXTENSIONS ) ); + + if(enumeration_all_ext) + Com_Printf(" Device: %s\n", qalcGetString(alDevice, ALC_ALL_DEVICES_SPECIFIER)); + else if(enumeration_ext) + Com_Printf(" Device: %s\n", qalcGetString(alDevice, ALC_DEVICE_SPECIFIER)); + + if(enumeration_all_ext || enumeration_ext) + Com_Printf(" Available Devices:\n%s", s_alAvailableDevices->string); + +#ifdef USE_VOIP + if(capture_ext) + { + Com_Printf(" Input Device: %s\n", qalcGetString(alCaptureDevice, ALC_CAPTURE_DEVICE_SPECIFIER)); + Com_Printf(" Available Input Devices:\n%s", s_alAvailableInputDevices->string); + } +#endif +} + + + +/* +================= +S_AL_Shutdown +================= +*/ +static +void S_AL_Shutdown( void ) +{ + // Shut down everything + int i; + for (i = 0; i < MAX_RAW_STREAMS; i++) + S_AL_StreamDie(i); + S_AL_StopBackgroundTrack( ); + S_AL_SrcShutdown( ); + S_AL_BufferShutdown( ); + + qalcDestroyContext(alContext); + qalcCloseDevice(alDevice); + +#ifdef USE_VOIP + if (alCaptureDevice != NULL) { + qalcCaptureStop(alCaptureDevice); + qalcCaptureCloseDevice(alCaptureDevice); + alCaptureDevice = NULL; + Com_Printf( "OpenAL capture device closed.\n" ); + } +#endif + + for (i = 0; i < MAX_RAW_STREAMS; i++) { + streamSourceHandles[i] = -1; + streamPlaying[i] = qfalse; + streamSources[i] = 0; + } + + QAL_Shutdown(); +} + +#endif + +/* +================= +S_AL_Init +================= +*/ +qboolean S_AL_Init( soundInterface_t *si ) +{ +#ifdef USE_OPENAL + const char* device = NULL; + const char* inputdevice = NULL; + int i; + + if( !si ) { + return qfalse; + } + + for (i = 0; i < MAX_RAW_STREAMS; i++) { + streamSourceHandles[i] = -1; + streamPlaying[i] = qfalse; + streamSources[i] = 0; + streamNumBuffers[i] = 0; + streamBufIndex[i] = 0; + } + + // New console variables + s_alPrecache = Cvar_Get( "s_alPrecache", "1", CVAR_ARCHIVE ); + s_alGain = Cvar_Get( "s_alGain", "1.0", CVAR_ARCHIVE ); + s_alSources = Cvar_Get( "s_alSources", "96", CVAR_ARCHIVE ); + s_alDopplerFactor = Cvar_Get( "s_alDopplerFactor", "1.0", CVAR_ARCHIVE ); + s_alDopplerSpeed = Cvar_Get( "s_alDopplerSpeed", "9000", CVAR_ARCHIVE ); + s_alMinDistance = Cvar_Get( "s_alMinDistance", "120", CVAR_CHEAT ); + s_alMaxDistance = Cvar_Get("s_alMaxDistance", "1024", CVAR_CHEAT); + s_alRolloff = Cvar_Get( "s_alRolloff", "2", CVAR_CHEAT); + s_alGraceDistance = Cvar_Get("s_alGraceDistance", "512", CVAR_CHEAT); + + s_alDriver = Cvar_Get( "s_alDriver", ALDRIVER_DEFAULT, CVAR_ARCHIVE | CVAR_LATCH | CVAR_PROTECTED ); + + s_alInputDevice = Cvar_Get( "s_alInputDevice", "", CVAR_ARCHIVE | CVAR_LATCH ); + s_alDevice = Cvar_Get("s_alDevice", "", CVAR_ARCHIVE | CVAR_LATCH); + + // Load QAL + if( !QAL_Init( s_alDriver->string ) ) + { + Com_Printf( "Failed to load library: \"%s\".\n", s_alDriver->string ); + if( !Q_stricmp( s_alDriver->string, ALDRIVER_DEFAULT ) || !QAL_Init( ALDRIVER_DEFAULT ) ) { + return qfalse; + } + } + + device = s_alDevice->string; + if(device && !*device) + device = NULL; + + inputdevice = s_alInputDevice->string; + if(inputdevice && !*inputdevice) + inputdevice = 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 + // 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(!device && defaultdevice && !strcmp(defaultdevice, "Generic Hardware")) + device = "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; + } + } + + s_alAvailableDevices = Cvar_Get("s_alAvailableDevices", devicenames, CVAR_ROM | CVAR_NORESTART); + } + + alDevice = qalcOpenDevice(device); + if( !alDevice && device ) + { + Com_Printf( "Failed to open OpenAL device '%s', trying default.\n", device ); + alDevice = qalcOpenDevice(NULL); + } + + if( !alDevice ) + { + QAL_Shutdown( ); + Com_Printf( "Failed to open OpenAL device.\n" ); + return qfalse; + } + + // Create OpenAL context + alContext = qalcCreateContext( alDevice, NULL ); + if( !alContext ) + { + QAL_Shutdown( ); + qalcCloseDevice( alDevice ); + Com_Printf( "Failed to create OpenAL context.\n" ); + return qfalse; + } + qalcMakeContextCurrent( alContext ); + + // Initialize sources, buffers, music + S_AL_BufferInit( ); + S_AL_SrcInit( ); + + // Set up OpenAL parameters (doppler, etc) + qalDistanceModel(AL_INVERSE_DISTANCE_CLAMPED); + qalDopplerFactor( s_alDopplerFactor->value ); + qalSpeedOfSound( s_alDopplerSpeed->value ); + +#ifdef USE_VOIP + // !!! FIXME: some of these alcCaptureOpenDevice() values should be cvars. + // !!! FIXME: add support for capture device enumeration. + // !!! FIXME: add some better error reporting. + s_alCapture = Cvar_Get( "s_alCapture", "1", CVAR_ARCHIVE | CVAR_LATCH ); + if (!s_alCapture->integer) + { + Com_Printf("OpenAL capture support disabled by user ('+set s_alCapture 1' to enable)\n"); + } +#if USE_MUMBLE + else if (cl_useMumble->integer) + { + Com_Printf("OpenAL capture support disabled for Mumble support\n"); + } +#endif + else + { +#ifdef __APPLE__ + // !!! FIXME: Apple has a 1.1-compliant OpenAL, which includes + // !!! FIXME: capture support, but they don't list it in the + // !!! FIXME: extension string. We need to check the version string, + // !!! FIXME: then the extension string, but that's too much trouble, + // !!! FIXME: so we'll just check the function pointer for now. + if (qalcCaptureOpenDevice == NULL) +#else + if (!qalcIsExtensionPresent(NULL, "ALC_EXT_capture")) +#endif + { + Com_Printf("No ALC_EXT_capture support, can't record audio.\n"); + } + else + { + char inputdevicenames[16384] = ""; + const char *inputdevicelist; + const char *defaultinputdevice; + int curlen; + + capture_ext = qtrue; + + // get all available input devices + the default input device name. + inputdevicelist = qalcGetString(NULL, ALC_CAPTURE_DEVICE_SPECIFIER); + defaultinputdevice = qalcGetString(NULL, ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER); + + // dump a list of available devices to a cvar for the user to see. + if (inputdevicelist) + { + while((curlen = strlen(inputdevicelist))) + { + Q_strcat(inputdevicenames, sizeof(inputdevicenames), inputdevicelist); + Q_strcat(inputdevicenames, sizeof(inputdevicenames), "\n"); + inputdevicelist += curlen + 1; + } + } + + s_alAvailableInputDevices = Cvar_Get("s_alAvailableInputDevices", inputdevicenames, CVAR_ROM | CVAR_NORESTART); + + Com_Printf("OpenAL default capture device is '%s'\n", defaultinputdevice ? defaultinputdevice : "none"); + alCaptureDevice = qalcCaptureOpenDevice(inputdevice, 48000, AL_FORMAT_MONO16, VOIP_MAX_PACKET_SAMPLES*4); + if( !alCaptureDevice && inputdevice ) + { + Com_Printf( "Failed to open OpenAL Input device '%s', trying default.\n", inputdevice ); + alCaptureDevice = qalcCaptureOpenDevice(NULL, 48000, AL_FORMAT_MONO16, VOIP_MAX_PACKET_SAMPLES*4); + } + Com_Printf( "OpenAL capture device %s.\n", + (alCaptureDevice == NULL) ? "failed to open" : "opened"); + } + } +#endif + + si->Shutdown = S_AL_Shutdown; + si->StartSound = S_AL_StartSound; + si->StartLocalSound = S_AL_StartLocalSound; + si->StartBackgroundTrack = S_AL_StartBackgroundTrack; + si->StopBackgroundTrack = S_AL_StopBackgroundTrack; + si->RawSamples = S_AL_RawSamples; + si->StopAllSounds = S_AL_StopAllSounds; + si->ClearLoopingSounds = S_AL_ClearLoopingSounds; + si->AddLoopingSound = S_AL_AddLoopingSound; + si->AddRealLoopingSound = S_AL_AddRealLoopingSound; + si->StopLoopingSound = S_AL_StopLoopingSound; + si->Respatialize = S_AL_Respatialize; + si->UpdateEntityPosition = S_AL_UpdateEntityPosition; + si->Update = S_AL_Update; + si->DisableSounds = S_AL_DisableSounds; + si->BeginRegistration = S_AL_BeginRegistration; + si->RegisterSound = S_AL_RegisterSound; + si->ClearSoundBuffer = S_AL_ClearSoundBuffer; + si->SoundInfo = S_AL_SoundInfo; + si->SoundList = S_AL_SoundList; + +#ifdef USE_VOIP + si->StartCapture = S_AL_StartCapture; + si->AvailableCaptureSamples = S_AL_AvailableCaptureSamples; + si->Capture = S_AL_Capture; + si->StopCapture = S_AL_StopCapture; + si->MasterGain = S_AL_MasterGain; +#endif + + return qtrue; +#else + return qfalse; +#endif +} + diff --git a/code/client/snd_public.h b/code/client/snd_public.h index fc9400d0..ef9cce71 100644 --- a/code/client/snd_public.h +++ b/code/client/snd_public.h @@ -15,44 +15,38 @@ 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 Foobar; if not, write to the Free Software +along with Quake III Arena source code; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ -#ifdef __cplusplus -extern "C" { -#endif void S_Init( void ); void S_Shutdown( void ); // if origin is NULL, the sound will be dynamically sourced from the entity -void S_StartSound(const vec3_t origin, int entNum, int entChannel, sfxHandle_t sfxHandle, float volume, float minDist, float pitch, float maxDist, int streamed); -void S_StopSound(int entnum, int channel); -void S_StartLocalSound(sfxHandle_t sfx, int channelNum); -void S_StartLocalSoundByName(const char* sound_name, qboolean force_load); +void S_StartSound( vec3_t origin, int entnum, int entchannel, sfxHandle_t sfx ); +void S_StartLocalSound( sfxHandle_t sfx, int channelNum ); void S_StartBackgroundTrack( const char *intro, const char *loop ); void S_StopBackgroundTrack( void ); // cinematics and voice-over-network will send raw samples // 1.0 volume will be direct output of source samples -void S_RawSamples (int samples, int rate, int width, int channels, - const byte *data, float volume); +void S_RawSamples(int stream, int samples, int rate, int width, int channels, + const byte *data, float volume, int entityNum); // stop all sounds and the background track -void S_StopAllSounds(qboolean stop_music); +void S_StopAllSounds( void ); // all continuous looping sounds must be added before calling S_Update -void S_ClearLoopingSoundsNoParam(void); void S_ClearLoopingSounds( qboolean killall ); -void S_AddLoopingSound(const vec3_t origin, const vec3_t velocity, sfxHandle_t sfxHandle, float volume, float minDist, float maxDist, float pitch, int flags); +void S_AddLoopingSound( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfx ); void S_AddRealLoopingSound( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfx ); void S_StopLoopingSound(int entityNum ); -// recompute the reletive volumes for all running sounds -// reletive to the given entityNum / orientation +// recompute the relative volumes for all running sounds +// relative to the given entityNum / orientation void S_Respatialize( int entityNum, const vec3_t origin, vec3_t axis[3], int inwater ); // let the sound system know where an entity currently is @@ -63,12 +57,11 @@ void S_Update( void ); void S_DisableSounds( void ); void S_BeginRegistration( void ); -void S_EndRegistration(void); -qboolean S_IsSoundRegistered(const char* name); + // RegisterSound will allways return a valid sample, even if it // has to create a placeholder. This prevents continuous filesystem // checks for missing files -sfxHandle_t S_RegisterSound(const char *sample, qboolean compressed, qboolean streamed); +sfxHandle_t S_RegisterSound( const char *sample, qboolean compressed ); void S_DisplayFreeMemory(void); @@ -78,37 +71,13 @@ void SNDDMA_Activate( void ); void S_UpdateBackgroundTrack( void ); -// Music soundtrack -void MUSIC_Pause(); -void MUSIC_Unpause(); -qboolean MUSIC_LoadSoundtrackFile(const char* filename); -qboolean MUSIC_SongValid(const char* mood); -qboolean MUSIC_Loaded(void); -void Music_Update(void); -void MUSIC_SongEnded(void); -void MUSIC_NewSoundtrack(const char* name); -void MUSIC_UpdateMood(int current, int fallback); -void MUSIC_UpdateVolume(float volume, float fade_time); -void MUSIC_StopAllSongs(void); -void MUSIC_FreeAllSongs(void); -qboolean MUSIC_Playing(void); -int MUSIC_FindSong(const char* name); -int MUSIC_CurrentSongChannel(void); -void MUSIC_StopChannel(int channel_number); -qboolean MUSIC_PlaySong(const char* alias); -void MUSIC_UpdateMusicVolumes(void); -void MUSIC_CheckForStoppedSongs(void); - -float S_GetSoundTime(sfxHandle_t handle); -void S_SetGlobalAmbientVolumeLevel(float volume); -void S_SetReverb(int reverb_type, float reverb_level); -int S_IsSoundPlaying(int channelNumber, const char* name); - -void S_RespatializeOld(int entityNum, const vec3_t origin, vec3_t axis[3]); -void S_UpdateEntity(int entityNum, const vec3_t origin, const vec3_t velocity, qboolean use_listener); -void S_FadeSound(float fTime); - -#ifdef __cplusplus -} +#ifdef USE_VOIP +void S_StartCapture(void); +int S_AvailableCaptureSamples(void); +void S_Capture(int samples, byte* data); +void S_StopCapture(void); +void S_MasterGain(float gain); #endif + +#include "new/snd_public_new.h" diff --git a/code/client/snd_wavelet.c b/code/client/snd_wavelet.c index 29b26085..e051da29 100644 --- a/code/client/snd_wavelet.c +++ b/code/client/snd_wavelet.c @@ -15,15 +15,13 @@ 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 Foobar; if not, write to the Free Software +along with Quake III Arena 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" -long myftol( float f ); - #define C0 0.4829629131445341 #define C1 0.8365163037378079 #define C2 0.2241438680420134 @@ -31,8 +29,8 @@ long myftol( float f ); void daub4(float b[], unsigned long n, int isign) { - float wksp[4097]; - float *a=b-1; // numerical recipies so a[1] = b[0] + float wksp[4097] = { 0.0f }; +#define a(x) b[(x)-1] // numerical recipies so a[1] = b[0] unsigned long nh,nh1,i,j; @@ -41,22 +39,23 @@ void daub4(float b[], unsigned long n, int isign) nh1=(nh=n >> 1)+1; if (isign >= 0) { for (i=1,j=1;j<=n-3;j+=2,i++) { - wksp[i] = C0*a[j]+C1*a[j+1]+C2*a[j+2]+C3*a[j+3]; - wksp[i+nh] = C3*a[j]-C2*a[j+1]+C1*a[j+2]-C0*a[j+3]; + wksp[i] = C0*a(j)+C1*a(j+1)+C2*a(j+2)+C3*a(j+3); + wksp[i+nh] = C3*a(j)-C2*a(j+1)+C1*a(j+2)-C0*a(j+3); } - wksp[i ] = C0*a[n-1]+C1*a[n]+C2*a[1]+C3*a[2]; - wksp[i+nh] = C3*a[n-1]-C2*a[n]+C1*a[1]-C0*a[2]; + wksp[i ] = C0*a(n-1)+C1*a(n)+C2*a(1)+C3*a(2); + wksp[i+nh] = C3*a(n-1)-C2*a(n)+C1*a(1)-C0*a(2); } else { - wksp[1] = C2*a[nh]+C1*a[n]+C0*a[1]+C3*a[nh1]; - wksp[2] = C3*a[nh]-C0*a[n]+C1*a[1]-C2*a[nh1]; + wksp[1] = C2*a(nh)+C1*a(n)+C0*a(1)+C3*a(nh1); + wksp[2] = C3*a(nh)-C0*a(n)+C1*a(1)-C2*a(nh1); for (i=1,j=3;isoundData == NULL) { sfx->soundData = newchunk; - } else { + } else if (chunk != NULL) { chunk->next = newchunk; } chunk = newchunk; @@ -171,7 +170,7 @@ void encodeWavelet( sfx_t *sfx, short *packets) { } void decodeWavelet(sndBuffer *chunk, short *to) { - float wksp[4097]; + float wksp[4097] = {0}; int i; byte *out; @@ -217,7 +216,7 @@ void encodeMuLaw( sfx_t *sfx, short *packets) { newchunk = SND_malloc(); if (sfx->soundData == NULL) { sfx->soundData = newchunk; - } else { + } else if (chunk != NULL) { chunk->next = newchunk; } chunk = newchunk;