diff --git a/MacOSX/QuakeSpasm.xcodeproj/project.pbxproj b/MacOSX/QuakeSpasm.xcodeproj/project.pbxproj index 42aaa451..c054f6ad 100644 --- a/MacOSX/QuakeSpasm.xcodeproj/project.pbxproj +++ b/MacOSX/QuakeSpasm.xcodeproj/project.pbxproj @@ -103,6 +103,12 @@ 48FE585B0D3A82C8006BB491 /* QuakeArguments.m in Sources */ = {isa = PBXBuildFile; fileRef = 48FE585A0D3A82C8006BB491 /* QuakeArguments.m */; }; 66A5467212E3AF2300FFA7D5 /* cd_shared.c in Sources */ = {isa = PBXBuildFile; fileRef = 66A5467012E3AF2300FFA7D5 /* cd_shared.c */; }; 66A5467312E3AF2300FFA7D5 /* cd_ogg.c in Sources */ = {isa = PBXBuildFile; fileRef = 66A5467112E3AF2300FFA7D5 /* cd_ogg.c */; }; + 66A5470D12E3CF8100FFA7D5 /* snd_codec.c in Sources */ = {isa = PBXBuildFile; fileRef = 66A5470A12E3CF8100FFA7D5 /* snd_codec.c */; }; + 66A5470E12E3CF8100FFA7D5 /* snd_codec_wav.c in Sources */ = {isa = PBXBuildFile; fileRef = 66A5470B12E3CF8100FFA7D5 /* snd_codec_wav.c */; }; + 66A5470F12E3CF8100FFA7D5 /* snd_codec_ogg.c in Sources */ = {isa = PBXBuildFile; fileRef = 66A5470C12E3CF8100FFA7D5 /* snd_codec_ogg.c */; }; + 66A5473512E3D14B00FFA7D5 /* libogg.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 66A5473412E3D14B00FFA7D5 /* libogg.dylib */; }; + 66A5473712E3D15F00FFA7D5 /* libvorbis.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 66A5473612E3D15F00FFA7D5 /* libvorbis.dylib */; }; + 66A5473912E3D17100FFA7D5 /* libvorbisfile.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 66A5473812E3D17100FFA7D5 /* libvorbisfile.dylib */; }; 8D11072B0486CEB800E47090 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 089C165CFE840E0CC02AAC07 /* InfoPlist.strings */; }; 8D11072F0486CEB800E47090 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */; }; /* End PBXBuildFile section */ @@ -277,6 +283,13 @@ 48FE585A0D3A82C8006BB491 /* QuakeArguments.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = QuakeArguments.m; sourceTree = ""; }; 66A5467012E3AF2300FFA7D5 /* cd_shared.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = cd_shared.c; path = ../Quake/cd_shared.c; sourceTree = SOURCE_ROOT; }; 66A5467112E3AF2300FFA7D5 /* cd_ogg.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = cd_ogg.c; path = ../Quake/cd_ogg.c; sourceTree = SOURCE_ROOT; }; + 66A5470912E3CF6B00FFA7D5 /* snd_codec.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = snd_codec.h; path = ../Quake/snd_codec.h; sourceTree = SOURCE_ROOT; }; + 66A5470A12E3CF8100FFA7D5 /* snd_codec.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = snd_codec.c; path = ../Quake/snd_codec.c; sourceTree = SOURCE_ROOT; }; + 66A5470B12E3CF8100FFA7D5 /* snd_codec_wav.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = snd_codec_wav.c; path = ../Quake/snd_codec_wav.c; sourceTree = SOURCE_ROOT; }; + 66A5470C12E3CF8100FFA7D5 /* snd_codec_ogg.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = snd_codec_ogg.c; path = ../Quake/snd_codec_ogg.c; sourceTree = SOURCE_ROOT; }; + 66A5473412E3D14B00FFA7D5 /* libogg.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libogg.dylib; path = /usr/local/lib/libogg.dylib; sourceTree = ""; }; + 66A5473612E3D15F00FFA7D5 /* libvorbis.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libvorbis.dylib; path = /usr/local/lib/libvorbis.dylib; sourceTree = ""; }; + 66A5473812E3D17100FFA7D5 /* libvorbisfile.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libvorbisfile.dylib; path = /usr/local/lib/libvorbisfile.dylib; sourceTree = ""; }; 8D1107310486CEB800E47090 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; 8D1107320486CEB800E47090 /* QuakeSpasm.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = QuakeSpasm.app; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ @@ -291,6 +304,9 @@ 8D11072F0486CEB800E47090 /* Cocoa.framework in Frameworks */, 002F3C0109D093BD00EBEB88 /* OpenGL.framework in Frameworks */, 48348943120595B1004184BC /* Sparkle.framework in Frameworks */, + 66A5473512E3D14B00FFA7D5 /* libogg.dylib in Frameworks */, + 66A5473712E3D15F00FFA7D5 /* libvorbis.dylib in Frameworks */, + 66A5473912E3D17100FFA7D5 /* libvorbisfile.dylib in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -337,6 +353,9 @@ 19C28FACFE9D520D11CA2CBB /* Products */, 4830B79D0D464CAE00EF4498 /* Changelog.txt */, 4830B79E0D464CAE00EF4498 /* Todo.txt */, + 66A5473412E3D14B00FFA7D5 /* libogg.dylib */, + 66A5473612E3D15F00FFA7D5 /* libvorbis.dylib */, + 66A5473812E3D17100FFA7D5 /* libvorbisfile.dylib */, ); name = QuakeSpasm; sourceTree = ""; @@ -477,6 +496,9 @@ 486577C90D31A22A00E7920A /* snd_mem.c */, 486577CA0D31A22A00E7920A /* snd_mix.c */, 483A78540D2EEAC300CB2E4C /* snd_sdl.c */, + 66A5470A12E3CF8100FFA7D5 /* snd_codec.c */, + 66A5470B12E3CF8100FFA7D5 /* snd_codec_wav.c */, + 66A5470C12E3CF8100FFA7D5 /* snd_codec_ogg.c */, ); name = Sound; sourceTree = ""; @@ -556,6 +578,7 @@ 483A77E10D2EE91000CB2E4C /* Headers */ = { isa = PBXGroup; children = ( + 66A5470912E3CF6B00FFA7D5 /* snd_codec.h */, 483A77FD0D2EE9BD00CB2E4C /* cdaudio.h */, 483A77FE0D2EE9BD00CB2E4C /* sound.h */, ); @@ -748,6 +771,9 @@ 48134A1912102F400015BF15 /* net_udp.c in Sources */, 66A5467212E3AF2300FFA7D5 /* cd_shared.c in Sources */, 66A5467312E3AF2300FFA7D5 /* cd_ogg.c in Sources */, + 66A5470D12E3CF8100FFA7D5 /* snd_codec.c in Sources */, + 66A5470E12E3CF8100FFA7D5 /* snd_codec_wav.c in Sources */, + 66A5470F12E3CF8100FFA7D5 /* snd_codec_ogg.c in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -818,6 +844,7 @@ GCC_MODEL_TUNING = G5; GCC_OPTIMIZATION_LEVEL = 0; HEADER_SEARCH_PATHS = ( + /usr/local/include, /Library/Frameworks/SDL.framework/Headers, /Library/Frameworks/SDL_net.framework/Headers, "$(HEADER_SEARCH_PATHS)", diff --git a/Quake/snd_codec.c b/Quake/snd_codec.c new file mode 100644 index 00000000..6cd26ae0 --- /dev/null +++ b/Quake/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 "quakedef.h" +#include "snd_codec.h" + +static snd_codec_t *codecs; + +/* +================= +S_FileExtension +================= +*/ +static char *S_FileExtension(const char *fni) +{ + // we should search from the ending to the last '/' + + char *fn = (char *) fni + strlen(fni) - 1; + char *eptr = NULL; + + while(*fn != '/' && fn != fni) + { + if(*fn == '.') + { + eptr = fn; + break; + } + fn--; + } + + return eptr; +} + +/* +================= +S_FindCodecForFile + +Select an appropriate codec for a file based on its extension +================= +*/ +static snd_codec_t *S_FindCodecForFile(const char *filename) +{ + char *ext = S_FileExtension(filename); + snd_codec_t *codec = codecs; + + if(!ext) + { + // No extension - auto-detect + while(codec) + { + char fn[MAX_QPATH]; + + // there is no extension so we do not need to subtract 4 chars + Q_strncpyz(fn, filename, MAX_QPATH); + COM_DefaultExtension(fn, MAX_QPATH, codec->ext); + + // Check it exists + if(FS_ReadFile(fn, NULL) != -1) + return codec; + + // Nope. Next! + codec = codec->next; + } + + // Nothin' + return NULL; + } + + while(codec) + { + if(!Q_stricmp(ext, codec->ext)) + return codec; + codec = codec->next; + } + + return NULL; +} + +/* +================= +S_CodecInit +================= +*/ +void S_CodecInit() +{ + codecs = NULL; + S_CodecRegister(&wav_codec); +#ifdef USE_CODEC_VORBIS + S_CodecRegister(&ogg_codec); +#endif +} + +/* +================= +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) +{ + snd_codec_t *codec; + char fn[MAX_QPATH]; + + codec = S_FindCodecForFile(filename); + if(!codec) + { + Com_Printf("Unknown extension for %s\n", filename); + return NULL; + } + + strncpy(fn, filename, sizeof(fn)); + COM_DefaultExtension(fn, sizeof(fn), codec->ext); + + return codec->load(fn, info); +} + +/* +================= +S_CodecOpenStream +================= +*/ +snd_stream_t *S_CodecOpenStream(const char *filename) +{ + snd_codec_t *codec; + char fn[MAX_QPATH]; + + codec = S_FindCodecForFile(filename); + if(!codec) + { + Com_Printf("Unknown extension for %s\n", filename); + return NULL; + } + + strncpy(fn, filename, sizeof(fn)); + COM_DefaultExtension(fn, sizeof(fn), codec->ext); + + return codec->open(fn); +} + +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_Printf("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/Quake/snd_codec.h b/Quake/snd_codec.h new file mode 100644 index 00000000..4adf19c0 --- /dev/null +++ b/Quake/snd_codec.h @@ -0,0 +1,95 @@ +/* +=========================================================================== +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_ + +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; + FILE *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 + +#endif // !_SND_CODEC_H_ diff --git a/Quake/snd_codec_ogg.c b/Quake/snd_codec_ogg.c new file mode 100644 index 00000000..2e811ac6 --- /dev/null +++ b/Quake/snd_codec_ogg.c @@ -0,0 +1,480 @@ +/* +=========================================================================== +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 +=========================================================================== +*/ + +// FIXME: +#define USE_CODEC_VORBIS + +// OGG support is enabled by this define +#ifdef USE_CODEC_VORBIS + +// includes for the Q3 sound system +#include "quakedef.h" +#include "snd_codec.h" + +// includes for the OGG codec +#include +#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 : + { + // Quake 3 seems to have trouble with FS_SEEK_END + // so we use the file length and FS_SEEK_SET + + // set the file position in the actual file with the Q3 function + retVal = FS_Seek(stream->file, (long) stream->length + (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 = 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 = Z_Malloc(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) + { + Z_Free(buffer); + S_OGG_CodecCloseStream(stream); + + return NULL; + } + + S_OGG_CodecCloseStream(stream); + + return buffer; +} + +#endif // USE_CODEC_VORBIS diff --git a/Quake/snd_codec_wav.c b/Quake/snd_codec_wav.c new file mode 100644 index 00000000..b6c56fa9 --- /dev/null +++ b/Quake/snd_codec_wav.c @@ -0,0 +1,294 @@ +/* +=========================================================================== +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 "quakedef.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 wav_format; + 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 + wav_format = FGetLittleShort(file); + 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) + { + Com_Printf( S_COLOR_RED "ERROR: Could not open \"%s\"\n", + filename); + 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 = Z_Malloc(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/Quake/snd_dma.c b/Quake/snd_dma.c index 2ce3f587..aa2f5103 100644 --- a/Quake/snd_dma.c +++ b/Quake/snd_dma.c @@ -22,6 +22,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. // snd_dma.c -- main control for any streaming sound output device #include "quakedef.h" +#include "snd_codec.h" void S_Play(void); void S_PlayVol(void); @@ -30,6 +31,10 @@ void S_Update_(); void S_StopAllSounds(qboolean clear); void S_StopAllSoundsC(void); +snd_stream_t *s_backgroundStream = NULL; +static char s_backgroundLoop[MAX_QPATH]; + + // ======================================================================= // Internal sound data & structures // ======================================================================= @@ -62,6 +67,10 @@ sfx_t *ambient_sfx[NUM_AMBIENTS]; qboolean sound_started = false; +int s_rawend[MAX_RAW_STREAMS]; +portable_samplepair_t s_rawsamples[MAX_RAW_STREAMS][MAX_RAW_SAMPLES]; + + cvar_t bgmvolume = {"bgmvolume", "1", true}; cvar_t sfxvolume = {"volume", "0.7", true}; @@ -564,6 +573,142 @@ void S_StaticSound (sfx_t *sfx, vec3_t origin, float vol, float attenuation) } +//============================================================================= + +/* + ================= + S_ByteSwapRawSamples + + If raw data has been loaded in little endien binary form, this must be done. + If raw data was calculated, as with ADPCM, this should not be called. + ================= + */ +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_Base_RawSamples + + Music streaming + ============ + */ +void S_Base_RawSamples( int stream, int samples, int rate, int width, int s_channels, const byte *data, float volume ) { + int i; + int src, dst; + float scale; + int intVolume; + portable_samplepair_t *rawsamples; + + if ( !sound_started ) { + return; + } + + if ( (stream < 0) || (stream >= MAX_RAW_STREAMS) ) { + return; + } + rawsamples = s_rawsamples[stream]; + + intVolume = 256 * volume; + + if ( s_rawend[stream] < soundtime ) { + Com_DPrintf( "S_Base_RawSamples: resetting minimum: %i < %i\n", s_rawend[stream], soundtime ); + s_rawend[stream] = soundtime; + } + + scale = (float)rate / shm->speed; + + //Com_Printf ("%i < %i < %i\n", 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[stream]&(MAX_RAW_SAMPLES-1); + s_rawend[stream]++; + rawsamples[dst].left = ((short *)data)[src*2] * intVolume; + rawsamples[dst].right = ((short *)data)[src*2+1] * intVolume; + } + } + } + else if (s_channels == 1 && width == 2) + { + for (i=0 ; ; i++) + { + src = i*scale; + if (src >= samples) + break; + dst = s_rawend[stream]&(MAX_RAW_SAMPLES-1); + s_rawend[stream]++; + rawsamples[dst].left = ((short *)data)[src] * intVolume; + rawsamples[dst].right = ((short *)data)[src] * intVolume; + } + } + else if (s_channels == 2 && width == 1) + { + intVolume *= 256; + + for (i=0 ; ; i++) + { + src = i*scale; + if (src >= samples) + break; + dst = s_rawend[stream]&(MAX_RAW_SAMPLES-1); + s_rawend[stream]++; + rawsamples[dst].left = ((char *)data)[src*2] * intVolume; + rawsamples[dst].right = ((char *)data)[src*2+1] * intVolume; + } + } + else if (s_channels == 1 && width == 1) + { + intVolume *= 256; + + for (i=0 ; ; i++) + { + src = i*scale; + if (src >= samples) + break; + dst = s_rawend[stream]&(MAX_RAW_SAMPLES-1); + s_rawend[stream]++; + rawsamples[dst].left = (((byte *)data)[src]-128) * intVolume; + rawsamples[dst].right = (((byte *)data)[src]-128) * intVolume; + } + } + + if ( s_rawend[stream] > soundtime + MAX_RAW_SAMPLES ) { + Com_DPrintf( "S_Base_RawSamples: overflowed %i > %i\n", s_rawend[stream], soundtime ); + } +} + //============================================================================= /* @@ -716,6 +861,9 @@ void S_Update (vec3_t origin, vec3_t forward, vec3_t right, vec3_t up) Con_Printf ("----(%i)----\n", total); } + // add raw data from streamed samples + S_UpdateBackgroundTrack(); + // mix some sound S_Update_(); } @@ -796,6 +944,152 @@ void S_Update_(void) #endif } + + +/* + =============================================================================== + + background music functions + + =============================================================================== + */ + +/* + ====================== + S_StopBackgroundTrack + ====================== + */ +void S_Base_StopBackgroundTrack( void ) { + if(!s_backgroundStream) + return; + S_CodecCloseStream(s_backgroundStream); + s_backgroundStream = NULL; + s_rawend[0] = 0; +} + +/* + ====================== + S_StartBackgroundTrack + ====================== + */ +void S_Base_StartBackgroundTrack( const char *intro, const char *loop ){ + if ( !intro ) { + intro = ""; + } + if ( !loop || !loop[0] ) { + loop = intro; + } + Com_DPrintf( "S_StartBackgroundTrack( %s, %s )\n", intro, loop ); + + if(!*intro) + { + S_Base_StopBackgroundTrack(); + return; + } + + if( !loop ) { + s_backgroundLoop[0] = 0; + } else { + 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_backgroundStream) + { + S_CodecCloseStream(s_backgroundStream); + s_backgroundStream = NULL; + } + + // Open stream + s_backgroundStream = S_CodecOpenStream(intro); + if(!s_backgroundStream) { + Com_Printf( "WARNING: couldn't open music file %s\n", intro ); + return; + } + + if(s_backgroundStream->info.channels != 2 || s_backgroundStream->info.rate != 22050) { + Com_Printf( "WARNING: music file %s is not 22k stereo\n", intro ); + } +} + +/* + ====================== + S_UpdateBackgroundTrack + ====================== + */ +void S_UpdateBackgroundTrack( void ) { + int bufferSamples; + int fileSamples; + byte raw[30000]; // just enough to fit in a mac stack frame + int fileBytes; + int r; + + if(!s_backgroundStream) { + return; + } + + // don't bother playing anything if musicvolume is 0 + if ( bgmvolume.value <= 0 ) { + return; + } + + // see how many samples should be copied into the raw buffer + if ( s_rawend[0] < soundtime ) { + s_rawend[0] = soundtime; + } + + while ( s_rawend[0] < soundtime + MAX_RAW_SAMPLES ) { + bufferSamples = MAX_RAW_SAMPLES - (s_rawend[0] - soundtime); + + // decide how much data needs to be read from the file + fileSamples = bufferSamples * s_backgroundStream->info.rate / shm->speed; + + if (!fileSamples) + return; + + // our max buffer size + fileBytes = fileSamples * (s_backgroundStream->info.width * s_backgroundStream->info.channels); + if ( fileBytes > sizeof(raw) ) { + fileBytes = sizeof(raw); + fileSamples = fileBytes / (s_backgroundStream->info.width * s_backgroundStream->info.channels); + } + + // Read + r = S_CodecReadStream(s_backgroundStream, fileBytes, raw); + if(r < fileBytes) + { + fileBytes = r; + fileSamples = r / (s_backgroundStream->info.width * s_backgroundStream->info.channels); + } + + 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, bgmvolume.value ); + } + else + { + // loop + if(s_backgroundLoop[0]) + { + S_CodecCloseStream(s_backgroundStream); + s_backgroundStream = NULL; + S_Base_StartBackgroundTrack( s_backgroundLoop, s_backgroundLoop ); + if(!s_backgroundStream) + return; + } + else + { + S_Base_StopBackgroundTrack(); + return; + } + } + + } +} + void S_BlockSound (void) { /* FIXME: do we really need the blocking at the diff --git a/Quake/snd_mix.c b/Quake/snd_mix.c index e362b902..53732762 100644 --- a/Quake/snd_mix.c +++ b/Quake/snd_mix.c @@ -155,6 +155,7 @@ void S_PaintChannels (int endtime) { int i; int end, ltime, count; + int stream; channel_t *ch; sfxcache_t *sc; @@ -167,28 +168,20 @@ void S_PaintChannels (int endtime) if (endtime - paintedtime > PAINTBUFFER_SIZE) end = paintedtime + PAINTBUFFER_SIZE; - // clear the paint buffer + // clear the paint buffer and mix any raw samples... (e.g. OGG music) memset(paintbuffer, 0, sizeof(paintbuffer)); - - // paint the OGG music - - { - ltime = paintedtime; - - int volume = 255 * bgmvolume.value; - - count = end - ltime; - - int i; - - for (i = 0; i < count; i++) - { - int data = 32767 - (rand() % 65535); - paintbuffer[i].left += data * volume; - paintbuffer[i].right += data * volume; - } - - } + for (stream = 0; stream < MAX_RAW_STREAMS; stream++) { + if ( s_rawend[stream] >= 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 = paintedtime ; i < stop ; i++ ) { + const int s = i&(MAX_RAW_SAMPLES-1); + paintbuffer[i-paintedtime].left += rawsamples[s].left; + paintbuffer[i-paintedtime].right += rawsamples[s].right; + } + } + } // paint in the sfx channels. ch = snd_channels; diff --git a/Quake/sound.h b/Quake/sound.h index 5a773aa7..240fffcb 100644 --- a/Quake/sound.h +++ b/Quake/sound.h @@ -171,6 +171,11 @@ extern vec3_t listener_up; extern volatile dma_t *shm; extern volatile dma_t sn; +#define MAX_RAW_SAMPLES 16384 +#define MAX_RAW_STREAMS 128 +extern portable_samplepair_t s_rawsamples[MAX_RAW_STREAMS][MAX_RAW_SAMPLES]; +extern int s_rawend[MAX_RAW_STREAMS]; + extern cvar_t loadas8bit; extern cvar_t bgmvolume; extern cvar_t sfxvolume;