diff options
Diffstat (limited to 'src/libs/sound')
41 files changed, 10496 insertions, 0 deletions
diff --git a/src/libs/sound/Makeinfo b/src/libs/sound/Makeinfo new file mode 100644 index 0000000..28ee5cc --- /dev/null +++ b/src/libs/sound/Makeinfo @@ -0,0 +1,9 @@ +if [ "$uqm_SOUNDMODULE" = "openal" ]; then + uqm_SUBDIRS="openal mixer decoders" + uqm_CFLAGS="$uqm_CFLAGS -DHAVE_OPENAL" +else + uqm_SUBDIRS="mixer decoders" +fi + +uqm_CFILES="audiocore.c fileinst.c resinst.c sound.c sfx.c music.c stream.c trackplayer.c" +uqm_HFILES="audiocore.h sndintrn.h sound.h stream.h trackint.h trackplayer.h" diff --git a/src/libs/sound/audiocore.c b/src/libs/sound/audiocore.c new file mode 100644 index 0000000..440f63f --- /dev/null +++ b/src/libs/sound/audiocore.c @@ -0,0 +1,272 @@ +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* Audio Core API (derived from OpenAL) + */ + +#include <stdio.h> +#include <stdlib.h> +#include "audiocore.h" +#include "sound.h" +#include "libs/log.h" + +static audio_Driver audiodrv; + +/* The globals that control the sound drivers. */ +int snddriver, soundflags; + +volatile bool audio_inited = false; + +/* + * Declarations for driver init funcs + */ + +#ifdef HAVE_OPENAL +sint32 openAL_Init (audio_Driver *driver, sint32 flags); +#endif +sint32 mixSDL_Init (audio_Driver *driver, sint32 flags); +sint32 noSound_Init (audio_Driver *driver, sint32 flags); + + +/* + * Initialization + */ + +sint32 +initAudio (sint32 driver, sint32 flags) +{ + sint32 ret; + +#ifdef HAVE_OPENAL + if (driver == audio_DRIVER_MIXSDL) + ret = mixSDL_Init (&audiodrv, flags); + else if (driver == audio_DRIVER_OPENAL) + ret = openAL_Init (&audiodrv, flags); + else + ret = noSound_Init (&audiodrv, flags); +#else + if (driver == audio_DRIVER_OPENAL) + { + log_add (log_Warning, "OpenAL driver not compiled in, so using MixSDL"); + driver = audio_DRIVER_MIXSDL; + } + if (driver == audio_DRIVER_MIXSDL) + ret = mixSDL_Init (&audiodrv, flags); + else + ret = noSound_Init (&audiodrv, flags); +#endif + + if (ret != 0) + { + log_add (log_Fatal, "Sound driver initialization failed.\n" + "This may happen when a soundcard is " + "not present or not available.\n" + "NOTICE: Try running UQM with '--sound=none' option"); + exit (EXIT_FAILURE); + } + + SetSFXVolume (sfxVolumeScale); + SetSpeechVolume (speechVolumeScale); + SetMusicVolume (musicVolume); + + audio_inited = true; + + return ret; +} + +void +unInitAudio (void) +{ + if (!audio_inited) + return; + + audio_inited = false; + audiodrv.Uninitialize (); +} + + +/* + * General + */ + +sint32 +audio_GetError (void) +{ + return audiodrv.GetError (); +} + + +/* + * Sources + */ + +void +audio_GenSources (uint32 n, audio_Object *psrcobj) +{ + audiodrv.GenSources (n, psrcobj); +} + +void +audio_DeleteSources (uint32 n, audio_Object *psrcobj) +{ + audiodrv.DeleteSources (n, psrcobj); +} + +bool +audio_IsSource (audio_Object srcobj) +{ + return audiodrv.IsSource (srcobj); +} + +void +audio_Sourcei (audio_Object srcobj, audio_SourceProp pname, + audio_IntVal value) + +{ + audiodrv.Sourcei (srcobj, audiodrv.EnumLookup[pname], value); +} + +void +audio_Sourcef (audio_Object srcobj, audio_SourceProp pname, + float value) +{ + audiodrv.Sourcef (srcobj, audiodrv.EnumLookup[pname], value); +} + +void +audio_Sourcefv (audio_Object srcobj, audio_SourceProp pname, + float *value) +{ + audiodrv.Sourcefv (srcobj, audiodrv.EnumLookup[pname], value); +} + +void +audio_GetSourcei (audio_Object srcobj, audio_SourceProp pname, + audio_IntVal *value) +{ + audiodrv.GetSourcei (srcobj, audiodrv.EnumLookup[pname], value); +} + +void +audio_GetSourcef (audio_Object srcobj, audio_SourceProp pname, + float *value) +{ + audiodrv.GetSourcef (srcobj, audiodrv.EnumLookup[pname], value); +} + +void +audio_SourceRewind (audio_Object srcobj) +{ + audiodrv.SourceRewind (srcobj); +} + +void +audio_SourcePlay (audio_Object srcobj) +{ + audiodrv.SourcePlay (srcobj); +} + +void +audio_SourcePause (audio_Object srcobj) +{ + audiodrv.SourcePause (srcobj); +} + +void +audio_SourceStop (audio_Object srcobj) +{ + audiodrv.SourceStop (srcobj); +} + +void +audio_SourceQueueBuffers (audio_Object srcobj, uint32 n, + audio_Object* pbufobj) +{ + audiodrv.SourceQueueBuffers (srcobj, n, pbufobj); +} + +void +audio_SourceUnqueueBuffers (audio_Object srcobj, uint32 n, + audio_Object* pbufobj) +{ + audiodrv.SourceUnqueueBuffers (srcobj, n, pbufobj); +} + + +/* + * Buffers + */ + +void +audio_GenBuffers (uint32 n, audio_Object *pbufobj) +{ + audiodrv.GenBuffers (n, pbufobj); +} + +void +audio_DeleteBuffers (uint32 n, audio_Object *pbufobj) +{ + audiodrv.DeleteBuffers (n, pbufobj); +} + +bool +audio_IsBuffer (audio_Object bufobj) +{ + return audiodrv.IsBuffer (bufobj); +} + +void +audio_GetBufferi (audio_Object bufobj, audio_BufferProp pname, + audio_IntVal *value) +{ + audiodrv.GetBufferi (bufobj, audiodrv.EnumLookup[pname], value); +} + +void +audio_BufferData (audio_Object bufobj, uint32 format, void* data, + uint32 size, uint32 freq) +{ + audiodrv.BufferData (bufobj, audiodrv.EnumLookup[format], data, size, + freq); +} + +bool +audio_GetFormatInfo (uint32 format, int *channels, int *sample_size) +{ + switch (format) + { + case audio_FORMAT_MONO8: + *channels = 1; + *sample_size = sizeof (uint8); + return true; + + case audio_FORMAT_STEREO8: + *channels = 2; + *sample_size = sizeof (uint8); + return true; + + case audio_FORMAT_MONO16: + *channels = 1; + *sample_size = sizeof (sint16); + return true; + + case audio_FORMAT_STEREO16: + *channels = 2; + *sample_size = sizeof (sint16); + return true; + } + return false; +} diff --git a/src/libs/sound/audiocore.h b/src/libs/sound/audiocore.h new file mode 100644 index 0000000..6f48b26 --- /dev/null +++ b/src/libs/sound/audiocore.h @@ -0,0 +1,169 @@ +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* Audio Core API (derived from OpenAL) + */ + +#ifndef LIBS_SOUND_AUDIOCORE_H_ +#define LIBS_SOUND_AUDIOCORE_H_ + +#include "config.h" +#include "types.h" + + +/* Available drivers */ +enum +{ + audio_DRIVER_MIXSDL, + audio_DRIVER_NOSOUND, + audio_DRIVER_OPENAL +}; + +/* Initialization flags */ +#define audio_QUALITY_HIGH (1 << 0) +#define audio_QUALITY_MEDIUM (1 << 1) +#define audio_QUALITY_LOW (1 << 2) + + +/* Interface Types */ +typedef uintptr_t audio_Object; +typedef intptr_t audio_IntVal; +typedef const sint32 audio_SourceProp; +typedef const sint32 audio_BufferProp; + +enum +{ + /* Errors */ + audio_NO_ERROR = 0, + audio_INVALID_NAME, + audio_INVALID_ENUM, + audio_INVALID_VALUE, + audio_INVALID_OPERATION, + audio_OUT_OF_MEMORY, + audio_DRIVER_FAILURE, + + /* Source properties */ + audio_POSITION, + audio_LOOPING, + audio_BUFFER, + audio_GAIN, + audio_SOURCE_STATE, + audio_BUFFERS_QUEUED, + audio_BUFFERS_PROCESSED, + + /* Source state information */ + audio_INITIAL, + audio_STOPPED, + audio_PLAYING, + audio_PAUSED, + + /* Sound buffer properties */ + audio_FREQUENCY, + audio_BITS, + audio_CHANNELS, + audio_SIZE, + audio_FORMAT_MONO16, + audio_FORMAT_STEREO16, + audio_FORMAT_MONO8, + audio_FORMAT_STEREO8, + audio_ENUM_SIZE +}; + +extern int snddriver, soundflags; + +typedef struct { + /* General */ + void (* Uninitialize) (void); + sint32 (* GetError) (void); + sint32 driverID; + sint32 EnumLookup[audio_ENUM_SIZE]; + + /* Sources */ + void (* GenSources) (uint32 n, audio_Object *psrcobj); + void (* DeleteSources) (uint32 n, audio_Object *psrcobj); + bool (* IsSource) (audio_Object srcobj); + void (* Sourcei) (audio_Object srcobj, audio_SourceProp pname, + audio_IntVal value); + void (* Sourcef) (audio_Object srcobj, audio_SourceProp pname, + float value); + void (* Sourcefv) (audio_Object srcobj, audio_SourceProp pname, + float *value); + void (* GetSourcei) (audio_Object srcobj, audio_SourceProp pname, + audio_IntVal *value); + void (* GetSourcef) (audio_Object srcobj, audio_SourceProp pname, + float *value); + void (* SourceRewind) (audio_Object srcobj); + void (* SourcePlay) (audio_Object srcobj); + void (* SourcePause) (audio_Object srcobj); + void (* SourceStop) (audio_Object srcobj); + void (* SourceQueueBuffers) (audio_Object srcobj, uint32 n, + audio_Object* pbufobj); + void (* SourceUnqueueBuffers) (audio_Object srcobj, uint32 n, + audio_Object* pbufobj); + + /* Buffers */ + void (* GenBuffers) (uint32 n, audio_Object *pbufobj); + void (* DeleteBuffers) (uint32 n, audio_Object *pbufobj); + bool (* IsBuffer) (audio_Object bufobj); + void (* GetBufferi) (audio_Object bufobj, audio_BufferProp pname, + audio_IntVal *value); + void (* BufferData) (audio_Object bufobj, uint32 format, void* data, + uint32 size, uint32 freq); +} audio_Driver; + + +/* Initialization */ +sint32 initAudio (sint32 driver, sint32 flags); +void unInitAudio (void); + +/* General */ +sint32 audio_GetError (void); + +/* Sources */ +void audio_GenSources (uint32 n, audio_Object *psrcobj); +void audio_DeleteSources (uint32 n, audio_Object *psrcobj); +bool audio_IsSource (audio_Object srcobj); +void audio_Sourcei (audio_Object srcobj, audio_SourceProp pname, + audio_IntVal value); +void audio_Sourcef (audio_Object srcobj, audio_SourceProp pname, + float value); +void audio_Sourcefv (audio_Object srcobj, audio_SourceProp pname, + float *value); +void audio_GetSourcei (audio_Object srcobj, audio_SourceProp pname, + audio_IntVal *value); +void audio_GetSourcef (audio_Object srcobj, audio_SourceProp pname, + float *value); +void audio_SourceRewind (audio_Object srcobj); +void audio_SourcePlay (audio_Object srcobj); +void audio_SourcePause (audio_Object srcobj); +void audio_SourceStop (audio_Object srcobj); +void audio_SourceQueueBuffers (audio_Object srcobj, uint32 n, + audio_Object* pbufobj); +void audio_SourceUnqueueBuffers (audio_Object srcobj, uint32 n, + audio_Object* pbufobj); + +/* Buffers */ +void audio_GenBuffers (uint32 n, audio_Object *pbufobj); +void audio_DeleteBuffers (uint32 n, audio_Object *pbufobj); +bool audio_IsBuffer (audio_Object bufobj); +void audio_GetBufferi (audio_Object bufobj, audio_BufferProp pname, + audio_IntVal *value); +void audio_BufferData (audio_Object bufobj, uint32 format, void* data, + uint32 size, uint32 freq); + +bool audio_GetFormatInfo (uint32 format, int *channels, int *sample_size); + +#endif /* LIBS_SOUND_AUDIOCORE_H_ */ diff --git a/src/libs/sound/decoders/Makeinfo b/src/libs/sound/decoders/Makeinfo new file mode 100644 index 0000000..e1735a1 --- /dev/null +++ b/src/libs/sound/decoders/Makeinfo @@ -0,0 +1,8 @@ +uqm_CFILES="decoder.c aiffaud.c wav.c dukaud.c modaud.c" +uqm_HFILES="aiffaud.h decoder.h dukaud.h modaud.h wav.h" + +if [ "$uqm_OGGVORBIS" '!=' "none" ]; then + uqm_CFILES="$uqm_CFILES oggaud.c" + uqm_HFILES="$uqm_HFILES oggaud.h" +fi + diff --git a/src/libs/sound/decoders/aiffaud.c b/src/libs/sound/decoders/aiffaud.c new file mode 100644 index 0000000..102a78e --- /dev/null +++ b/src/libs/sound/decoders/aiffaud.c @@ -0,0 +1,650 @@ +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* Portions (C) Serge van den Boom (svdb at stack.nl) */ +/* Portions (C) Alex Volkov (codepro at usa.net) */ + +/* AIFF decoder (.aif) + * + * Doesn't work on *all* aiff files in general, only 8/16 PCM and + * 16-bit AIFF-C SDX2-compressed. + */ + +#include <stdio.h> +#include <stdlib.h> + // for abs() +#include <errno.h> +#ifndef _WIN32_WCE +# include <memory.h> +#endif +#include <string.h> +#include "port.h" +#include "types.h" +#include "libs/uio.h" +#include "endian_uqm.h" +#include "libs/log.h" +#include "aiffaud.h" + +typedef uint32 aiff_ID; + +#define aiff_MAKE_ID(x1, x2, x3, x4) \ + (((x1) << 24) | ((x2) << 16) | ((x3) << 8) | (x4)) + +#define aiff_FormID aiff_MAKE_ID('F', 'O', 'R', 'M') +#define aiff_FormVersionID aiff_MAKE_ID('F', 'V', 'E', 'R') +#define aiff_CommonID aiff_MAKE_ID('C', 'O', 'M', 'M') +#define aiff_SoundDataID aiff_MAKE_ID('S', 'S', 'N', 'D') + +#define aiff_FormTypeAIFF aiff_MAKE_ID('A', 'I', 'F', 'F') +#define aiff_FormTypeAIFC aiff_MAKE_ID('A', 'I', 'F', 'C') + +#define aiff_CompressionTypeSDX2 aiff_MAKE_ID('S', 'D', 'X', '2') + + +typedef struct +{ + aiff_ID id; + uint32 size; +} aiff_ChunkHeader; + +#define AIFF_CHUNK_HDR_SIZE (4+4) + +typedef struct +{ + aiff_ChunkHeader chunk; + aiff_ID type; +} aiff_FileHeader; + +typedef struct +{ + uint32 version; /* format version, in Mac format */ +} aiff_FormatVersionChunk; + +typedef struct +{ + uint16 channels; /* number of channels */ + uint32 sampleFrames; /* number of sample frames */ + uint16 sampleSize; /* number of bits per sample */ + sint32 sampleRate; /* number of frames per second */ + /* this is actually stored as IEEE-754 80bit in files */ +} aiff_CommonChunk; + +#define AIFF_COMM_SIZE (2+4+2+10) + +typedef struct +{ + uint16 channels; /* number of channels */ + uint32 sampleFrames; /* number of sample frames */ + uint16 sampleSize; /* number of bits per sample */ + sint32 sampleRate; /* number of frames per second */ + aiff_ID extTypeID; /* compression type ID */ + char extName[32]; /* compression type name */ +} aiff_ExtCommonChunk; + +#define AIFF_EXT_COMM_SIZE (AIFF_COMM_SIZE+4) + +typedef struct +{ + uint32 offset; /* offset to sound data */ + uint32 blockSize; /* size of alignment blocks */ +} aiff_SoundDataChunk; + +#define AIFF_SSND_SIZE (4+4) + + +#define THIS_PTR TFB_SoundDecoder* This + +static const char* aifa_GetName (void); +static bool aifa_InitModule (int flags, const TFB_DecoderFormats*); +static void aifa_TermModule (void); +static uint32 aifa_GetStructSize (void); +static int aifa_GetError (THIS_PTR); +static bool aifa_Init (THIS_PTR); +static void aifa_Term (THIS_PTR); +static bool aifa_Open (THIS_PTR, uio_DirHandle *dir, const char *filename); +static void aifa_Close (THIS_PTR); +static int aifa_Decode (THIS_PTR, void* buf, sint32 bufsize); +static uint32 aifa_Seek (THIS_PTR, uint32 pcm_pos); +static uint32 aifa_GetFrame (THIS_PTR); + +TFB_SoundDecoderFuncs aifa_DecoderVtbl = +{ + aifa_GetName, + aifa_InitModule, + aifa_TermModule, + aifa_GetStructSize, + aifa_GetError, + aifa_Init, + aifa_Term, + aifa_Open, + aifa_Close, + aifa_Decode, + aifa_Seek, + aifa_GetFrame, +}; + + +typedef enum +{ + aifc_None, + aifc_Sdx2, +} aiff_CompressionType; + +#define MAX_CHANNELS 4 + +typedef struct tfb_wavesounddecoder +{ + // always the first member + TFB_SoundDecoder decoder; + + // private + sint32 last_error; + uio_Stream *fp; + aiff_ExtCommonChunk fmtHdr; + aiff_CompressionType comp_type; + unsigned bits_per_sample; + unsigned block_align; + unsigned file_block; + uint32 data_ofs; + uint32 data_size; + uint32 max_pcm; + uint32 cur_pcm; + sint32 prev_val[MAX_CHANNELS]; + +} TFB_AiffSoundDecoder; + +static const TFB_DecoderFormats* aifa_formats = NULL; + +static int aifa_DecodePCM (TFB_AiffSoundDecoder*, void* buf, sint32 bufsize); +static int aifa_DecodeSDX2 (TFB_AiffSoundDecoder*, void* buf, sint32 bufsize); + + +static const char* +aifa_GetName (void) +{ + return "AIFF"; +} + +static bool +aifa_InitModule (int flags, const TFB_DecoderFormats* fmts) +{ + aifa_formats = fmts; + return true; + + (void)flags; // laugh at compiler warning +} + +static void +aifa_TermModule (void) +{ + // no specific module term +} + +static uint32 +aifa_GetStructSize (void) +{ + return sizeof (TFB_AiffSoundDecoder); +} + +static int +aifa_GetError (THIS_PTR) +{ + TFB_AiffSoundDecoder* aifa = (TFB_AiffSoundDecoder*) This; + int ret = aifa->last_error; + aifa->last_error = 0; + return ret; +} + +static bool +aifa_Init (THIS_PTR) +{ + //TFB_AiffSoundDecoder* aifa = (TFB_AiffSoundDecoder*) This; + This->need_swap = !aifa_formats->want_big_endian; + return true; +} + +static void +aifa_Term (THIS_PTR) +{ + //TFB_AiffSoundDecoder* aifa = (TFB_AiffSoundDecoder*) This; + aifa_Close (This); // ensure cleanup +} + +static bool +read_be_16 (uio_Stream *fp, uint16 *v) +{ + if (!uio_fread (v, sizeof(*v), 1, fp)) + return false; + *v = UQM_SwapBE16 (*v); + return true; +} + +static bool +read_be_32 (uio_Stream *fp, uint32 *v) +{ + if (!uio_fread (v, sizeof(*v), 1, fp)) + return false; + *v = UQM_SwapBE32 (*v); + return true; +} + +// Read 80-bit IEEE 754 floating point number. +// We are only interested in values that we can work with, +// so using an sint32 here is fine. +static bool +read_be_f80 (uio_Stream *fp, sint32 *v) +{ + int sign, exp; + int shift; + uint16 se; + uint32 mant, mant_low; + if (!read_be_16 (fp, &se) || + !read_be_32 (fp, &mant) || !read_be_32 (fp, &mant_low)) + return false; + + sign = (se >> 15) & 1; // sign is the highest bit + exp = (se & ((1 << 15) - 1)); // exponent is next highest 15 bits +#if 0 // XXX: 80bit IEEE 754 used in AIFF uses explicit mantissa MS bit + // mantissa has an implied leading bit which is typically 1 + mant >>= 1; + if (exp != 0) + mant |= 0x80000000; +#endif + mant >>= 1; // we also need space for sign + exp -= (1 << 14) - 1; // exponent is biased by (2^(e-1) - 1) + shift = exp - 31 + 1; // mantissa is already 31 bits before decimal pt. + if (shift > 0) + mant = 0x7fffffff; // already too big + else if (shift < 0) + mant >>= -shift; + + *v = sign ? -(sint32)mant : (sint32)mant; + + return true; +} + +static bool +aifa_readFileHeader (TFB_AiffSoundDecoder* aifa, aiff_FileHeader* hdr) +{ + if (!read_be_32 (aifa->fp, &hdr->chunk.id) || + !read_be_32 (aifa->fp, &hdr->chunk.size) || + !read_be_32 (aifa->fp, &hdr->type)) + { + aifa->last_error = errno; + return false; + } + return true; +} + +static bool +aifa_readChunkHeader (TFB_AiffSoundDecoder* aifa, aiff_ChunkHeader* hdr) +{ + if (!read_be_32 (aifa->fp, &hdr->id) || + !read_be_32 (aifa->fp, &hdr->size)) + { + aifa->last_error = errno; + return false; + } + return true; +} + +static int +aifa_readCommonChunk (TFB_AiffSoundDecoder* aifa, uint32 size, + aiff_ExtCommonChunk* fmt) +{ + int bytes; + + memset(fmt, 0, sizeof(*fmt)); + if (size < AIFF_COMM_SIZE) + { + aifa->last_error = aifae_BadFile; + return 0; + } + + if (!read_be_16 (aifa->fp, &fmt->channels) || + !read_be_32 (aifa->fp, &fmt->sampleFrames) || + !read_be_16 (aifa->fp, &fmt->sampleSize) || + !read_be_f80 (aifa->fp, &fmt->sampleRate)) + { + aifa->last_error = errno; + return 0; + } + bytes = AIFF_COMM_SIZE; + + if (size >= AIFF_EXT_COMM_SIZE) + { + if (!read_be_32 (aifa->fp, &fmt->extTypeID)) + { + aifa->last_error = errno; + return 0; + } + bytes += sizeof(fmt->extTypeID); + } + + return bytes; +} + +static bool +aifa_readSoundDataChunk (TFB_AiffSoundDecoder* aifa, + aiff_SoundDataChunk* data) +{ + if (!read_be_32 (aifa->fp, &data->offset) || + !read_be_32 (aifa->fp, &data->blockSize)) + { + aifa->last_error = errno; + return false; + } + return true; +} + +static bool +aifa_Open (THIS_PTR, uio_DirHandle *dir, const char *filename) +{ + TFB_AiffSoundDecoder* aifa = (TFB_AiffSoundDecoder*) This; + aiff_FileHeader fileHdr; + aiff_ChunkHeader chunkHdr; + sint32 remSize; + + aifa->fp = uio_fopen (dir, filename, "rb"); + if (!aifa->fp) + { + aifa->last_error = errno; + return false; + } + + aifa->data_size = 0; + aifa->max_pcm = 0; + aifa->data_ofs = 0; + memset(&aifa->fmtHdr, 0, sizeof(aifa->fmtHdr)); + memset(aifa->prev_val, 0, sizeof(aifa->prev_val)); + + // read wave header + if (!aifa_readFileHeader (aifa, &fileHdr)) + { + aifa->last_error = errno; + aifa_Close (This); + return false; + } + if (fileHdr.chunk.id != aiff_FormID) + { + log_add (log_Warning, "aifa_Open(): not an aiff file, ID 0x%08x", + fileHdr.chunk.id); + aifa_Close (This); + return false; + } + if (fileHdr.type != aiff_FormTypeAIFF && fileHdr.type != aiff_FormTypeAIFC) + { + log_add (log_Warning, "aifa_Open(): unsupported aiff file" + ", Type 0x%08x", fileHdr.type); + aifa_Close (This); + return false; + } + + for (remSize = fileHdr.chunk.size - sizeof(aiff_ID); remSize > 0; + remSize -= ((chunkHdr.size + 1) & ~1) + AIFF_CHUNK_HDR_SIZE) + { + if (!aifa_readChunkHeader (aifa, &chunkHdr)) + { + aifa_Close (This); + return false; + } + + if (chunkHdr.id == aiff_CommonID) + { + int read = aifa_readCommonChunk (aifa, chunkHdr.size, &aifa->fmtHdr); + if (!read) + { + aifa_Close (This); + return false; + } + uio_fseek (aifa->fp, chunkHdr.size - read, SEEK_CUR); + } + else if (chunkHdr.id == aiff_SoundDataID) + { + aiff_SoundDataChunk data; + if (!aifa_readSoundDataChunk (aifa, &data)) + { + aifa_Close (This); + return false; + } + aifa->data_ofs = uio_ftell (aifa->fp) + data.offset; + uio_fseek (aifa->fp, chunkHdr.size - AIFF_SSND_SIZE, SEEK_CUR); + } + else + { // skip uninteresting chunk + uio_fseek (aifa->fp, chunkHdr.size, SEEK_CUR); + } + + // 2-align the file ptr + uio_fseek (aifa->fp, chunkHdr.size & 1, SEEK_CUR); + } + + if (aifa->fmtHdr.sampleFrames == 0) + { + log_add (log_Warning, "aifa_Open(): aiff file has no sound data"); + aifa_Close (This); + return false; + } + + // make bits-per-sample a multiple of 8 + aifa->bits_per_sample = (aifa->fmtHdr.sampleSize + 7) & ~7; + if (aifa->bits_per_sample == 0 || aifa->bits_per_sample > 16) + { // XXX: for now we do not support 24 and 32 bps + log_add (log_Warning, "aifa_Open(): unsupported sample size %u", + aifa->bits_per_sample); + aifa_Close (This); + return false; + } + if (aifa->fmtHdr.channels != 1 && aifa->fmtHdr.channels != 2) + { + log_add (log_Warning, "aifa_Open(): unsupported number of channels %u", + (unsigned)aifa->fmtHdr.channels); + aifa_Close (This); + return false; + } + if (aifa->fmtHdr.sampleRate < 300 || aifa->fmtHdr.sampleRate > 128000) + { + log_add (log_Warning, "aifa_Open(): unsupported sampling rate %ld", + (long)aifa->fmtHdr.sampleRate); + aifa_Close (This); + return false; + } + + aifa->block_align = aifa->bits_per_sample / 8 * aifa->fmtHdr.channels; + aifa->file_block = aifa->block_align; + if (!aifa->data_ofs) + { + log_add (log_Warning, "aifa_Open(): bad aiff file," + " no SSND chunk found"); + aifa_Close (This); + return false; + } + + if (fileHdr.type == aiff_FormTypeAIFF) + { + if (aifa->fmtHdr.extTypeID != 0) + { + log_add (log_Warning, "aifa_Open(): unsupported extension 0x%08x", + aifa->fmtHdr.extTypeID); + aifa_Close (This); + return false; + } + aifa->comp_type = aifc_None; + } + else if (fileHdr.type == aiff_FormTypeAIFC) + { + if (aifa->fmtHdr.extTypeID != aiff_CompressionTypeSDX2) + { + log_add (log_Warning, "aifa_Open(): unsupported compression 0x%08x", + aifa->fmtHdr.extTypeID); + aifa_Close (This); + return false; + } + aifa->comp_type = aifc_Sdx2; + aifa->file_block /= 2; + assert(aifa->fmtHdr.channels <= MAX_CHANNELS); + // after decompression, we will get samples in machine byte order + This->need_swap = (aifa_formats->big_endian + != aifa_formats->want_big_endian); + } + + aifa->data_size = aifa->fmtHdr.sampleFrames * aifa->file_block; + + if (aifa->comp_type == aifc_Sdx2 && aifa->bits_per_sample != 16) + { + log_add (log_Warning, "aifa_Open(): unsupported sample size %u for SDX2", + (unsigned)aifa->fmtHdr.sampleSize); + aifa_Close (This); + return false; + } + + This->format = (aifa->fmtHdr.channels == 1 ? + (aifa->bits_per_sample == 8 ? + aifa_formats->mono8 : aifa_formats->mono16) + : + (aifa->bits_per_sample == 8 ? + aifa_formats->stereo8 : aifa_formats->stereo16) + ); + This->frequency = aifa->fmtHdr.sampleRate; + + uio_fseek (aifa->fp, aifa->data_ofs, SEEK_SET); + aifa->max_pcm = aifa->fmtHdr.sampleFrames; + aifa->cur_pcm = 0; + This->length = (float) aifa->max_pcm / aifa->fmtHdr.sampleRate; + aifa->last_error = 0; + + return true; +} + +static void +aifa_Close (THIS_PTR) +{ + TFB_AiffSoundDecoder* aifa = (TFB_AiffSoundDecoder*) This; + + if (aifa->fp) + { + uio_fclose (aifa->fp); + aifa->fp = NULL; + } +} + +static int +aifa_Decode (THIS_PTR, void* buf, sint32 bufsize) +{ + TFB_AiffSoundDecoder* aifa = (TFB_AiffSoundDecoder*) This; + switch (aifa->comp_type) + { + case aifc_None: + return aifa_DecodePCM (aifa, buf, bufsize); + case aifc_Sdx2: + return aifa_DecodeSDX2 (aifa, buf, bufsize); + default: + assert(false && "Unknown comp_type"); + return 0; + } +} + +static int +aifa_DecodePCM (TFB_AiffSoundDecoder* aifa, void* buf, sint32 bufsize) +{ + uint32 dec_pcm; + uint32 size; + + dec_pcm = bufsize / aifa->block_align; + if (dec_pcm > aifa->max_pcm - aifa->cur_pcm) + dec_pcm = aifa->max_pcm - aifa->cur_pcm; + + dec_pcm = uio_fread (buf, aifa->file_block, dec_pcm, aifa->fp); + aifa->cur_pcm += dec_pcm; + size = dec_pcm * aifa->block_align; + + if (aifa->bits_per_sample == 8) + { // AIFF files store 8-bit data as signed + // and we need it unsigned + uint8* ptr = (uint8*)buf; + uint32 left; + for (left = size; left > 0; --left, ++ptr) + *ptr += 128; + } + + return size; +} + +static int +aifa_DecodeSDX2 (TFB_AiffSoundDecoder* aifa, void* buf, sint32 bufsize) +{ + uint32 dec_pcm; + sint8 *src; + sint16 *dst = buf; + uint32 left; + + dec_pcm = bufsize / aifa->block_align; + if (dec_pcm > aifa->max_pcm - aifa->cur_pcm) + dec_pcm = aifa->max_pcm - aifa->cur_pcm; + + src = (sint8*)buf + bufsize - (dec_pcm * aifa->file_block); + dec_pcm = uio_fread (src, aifa->file_block, dec_pcm, aifa->fp); + aifa->cur_pcm += dec_pcm; + + for (left = dec_pcm; left > 0; --left) + { + int i; + sint32 *prev = aifa->prev_val; + for (i = aifa->fmtHdr.channels; i > 0; --i, ++prev, ++src, ++dst) + { + sint32 v = (*src * abs(*src)) << 1; + if (*src & 1) + v += *prev; + // saturate the value + if (v > 32767) + v = 32767; + else if (v < -32768) + v = -32768; + *prev = v; + *dst = v; + } + } + + return dec_pcm * aifa->block_align; +} + +static uint32 +aifa_Seek (THIS_PTR, uint32 pcm_pos) +{ + TFB_AiffSoundDecoder* aifa = (TFB_AiffSoundDecoder*) This; + + if (pcm_pos > aifa->max_pcm) + pcm_pos = aifa->max_pcm; + aifa->cur_pcm = pcm_pos; + uio_fseek (aifa->fp, + aifa->data_ofs + pcm_pos * aifa->file_block, + SEEK_SET); + + // reset previous values for SDX2 on seek ops + // the delta will recover faster with reset + memset(aifa->prev_val, 0, sizeof(aifa->prev_val)); + + return pcm_pos; +} + +static uint32 +aifa_GetFrame (THIS_PTR) +{ + //TFB_AiffSoundDecoder* aifa = (TFB_AiffSoundDecoder*) This; + return 0; // only 1 frame for now + + (void)This; // laugh at compiler warning +} diff --git a/src/libs/sound/decoders/aiffaud.h b/src/libs/sound/decoders/aiffaud.h new file mode 100644 index 0000000..36c6679 --- /dev/null +++ b/src/libs/sound/decoders/aiffaud.h @@ -0,0 +1,36 @@ +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* AIFF decoder */ + +#ifndef AIFFAUD_H +#define AIFFAUD_H + +#include "decoder.h" + +extern TFB_SoundDecoderFuncs aifa_DecoderVtbl; + +typedef enum +{ + // positive values are the same as in errno + aifae_None = 0, + aifae_Unknown = -1, + aifae_BadFile = -2, + aifae_BadArg = -3, + aifae_Other = -1000, +} aifa_Error; + +#endif /* AIFFAUD_H */ diff --git a/src/libs/sound/decoders/decoder.c b/src/libs/sound/decoders/decoder.c new file mode 100644 index 0000000..8c20877 --- /dev/null +++ b/src/libs/sound/decoders/decoder.c @@ -0,0 +1,936 @@ +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* Sound file decoder for .wav, .mod, .ogg (to be used with OpenAL) + * API is heavily influenced by SDL_sound. + */ + +#include <string.h> +#include <stdlib.h> +#include "port.h" +#include "libs/memlib.h" +#include "libs/file.h" +#include "libs/log.h" +#include "decoder.h" +#include "wav.h" +#include "dukaud.h" +#include "modaud.h" +#ifndef OVCODEC_NONE +# include "oggaud.h" +#endif /* OVCODEC_NONE */ +#include "aiffaud.h" + + +#define MAX_REG_DECODERS 31 + +#define THIS_PTR TFB_SoundDecoder* + +static const char* bufa_GetName (void); +static bool bufa_InitModule (int flags, const TFB_DecoderFormats*); +static void bufa_TermModule (void); +static uint32 bufa_GetStructSize (void); +static int bufa_GetError (THIS_PTR); +static bool bufa_Init (THIS_PTR); +static void bufa_Term (THIS_PTR); +static bool bufa_Open (THIS_PTR, uio_DirHandle *dir, const char *filename); +static void bufa_Close (THIS_PTR); +static int bufa_Decode (THIS_PTR, void* buf, sint32 bufsize); +static uint32 bufa_Seek (THIS_PTR, uint32 pcm_pos); +static uint32 bufa_GetFrame (THIS_PTR); + +TFB_SoundDecoderFuncs bufa_DecoderVtbl = +{ + bufa_GetName, + bufa_InitModule, + bufa_TermModule, + bufa_GetStructSize, + bufa_GetError, + bufa_Init, + bufa_Term, + bufa_Open, + bufa_Close, + bufa_Decode, + bufa_Seek, + bufa_GetFrame, +}; + +typedef struct tfb_bufsounddecoder +{ + // always the first member + TFB_SoundDecoder decoder; + + // private + void* data; + uint32 max_pcm; + uint32 cur_pcm; + +} TFB_BufSoundDecoder; + +#define SD_MIN_SIZE (sizeof (TFB_BufSoundDecoder)) + +static const char* nula_GetName (void); +static bool nula_InitModule (int flags, const TFB_DecoderFormats*); +static void nula_TermModule (void); +static uint32 nula_GetStructSize (void); +static int nula_GetError (THIS_PTR); +static bool nula_Init (THIS_PTR); +static void nula_Term (THIS_PTR); +static bool nula_Open (THIS_PTR, uio_DirHandle *dir, const char *filename); +static void nula_Close (THIS_PTR); +static int nula_Decode (THIS_PTR, void* buf, sint32 bufsize); +static uint32 nula_Seek (THIS_PTR, uint32 pcm_pos); +static uint32 nula_GetFrame (THIS_PTR); + +TFB_SoundDecoderFuncs nula_DecoderVtbl = +{ + nula_GetName, + nula_InitModule, + nula_TermModule, + nula_GetStructSize, + nula_GetError, + nula_Init, + nula_Term, + nula_Open, + nula_Close, + nula_Decode, + nula_Seek, + nula_GetFrame, +}; + +typedef struct tfb_nullsounddecoder +{ + // always the first member + TFB_SoundDecoder decoder; + + // private + uint32 cur_pcm; + +} TFB_NullSoundDecoder; + +#undef THIS_PTR + + +struct TFB_RegSoundDecoder +{ + bool builtin; + bool used; // ever used indicator + const char* ext; + const TFB_SoundDecoderFuncs* funcs; +}; +static TFB_RegSoundDecoder sd_decoders[MAX_REG_DECODERS + 1] = +{ + {true, true, "wav", &wava_DecoderVtbl}, + {true, true, "mod", &moda_DecoderVtbl}, +#ifndef OVCODEC_NONE + {true, true, "ogg", &ova_DecoderVtbl}, +#endif /* OVCODEC_NONE */ + {true, true, "duk", &duka_DecoderVtbl}, + {true, true, "aif", &aifa_DecoderVtbl}, + {false, false, NULL, NULL}, // null term +}; + +static TFB_DecoderFormats decoder_formats; +static int sd_flags = 0; + +/* change endianness of 16bit words + * Only works optimal when 'data' is aligned on a 32 bits boundary. + */ +void +SoundDecoder_SwapWords (uint16* data, uint32 size) +{ + uint32 fsize = size & (~3U); + + size -= fsize; + fsize >>= 2; + for (; fsize; fsize--, data += 2) + { + uint32 v = *(uint32*)data; + *(uint32*)data = ((v & 0x00ff00ff) << 8) + | ((v & 0xff00ff00) >> 8); + } + if (size) + { + /* leftover word */ + *data = ((*data & 0x00ff) << 8) | ((*data & 0xff00) >> 8); + } +} + +const char* +SoundDecoder_GetName (TFB_SoundDecoder *decoder) +{ + if (!decoder || !decoder->funcs) + return "(Null)"; + return decoder->funcs->GetName (); +} + +sint32 +SoundDecoder_Init (int flags, TFB_DecoderFormats *formats) +{ + TFB_RegSoundDecoder* info; + sint32 ret = 0; + + if (!formats) + { + log_add (log_Error, "SoundDecoder_Init(): missing decoder formats"); + return 1; + } + decoder_formats = *formats; + + // init built-in decoders + for (info = sd_decoders; info->ext; info++) + { + if (!info->funcs->InitModule (flags, &decoder_formats)) + { + log_add (log_Error, "SoundDecoder_Init(): " + "%s audio decoder init failed", + info->funcs->GetName ()); + ret = 1; + } + } + + sd_flags = flags; + + return ret; +} + +void +SoundDecoder_Uninit (void) +{ + TFB_RegSoundDecoder* info; + + // uninit all decoders + // and unregister loaded decoders + for (info = sd_decoders; info->used; info++) + { + if (info->ext) // check if present + info->funcs->TermModule (); + + if (!info->builtin) + { + info->used = false; + info->ext = NULL; + } + } +} + +TFB_RegSoundDecoder* +SoundDecoder_Register (const char* fileext, TFB_SoundDecoderFuncs* decvtbl) +{ + TFB_RegSoundDecoder* info; + TFB_RegSoundDecoder* newslot = NULL; + + if (!decvtbl) + { + log_add (log_Warning, "SoundDecoder_Register(): Null decoder table"); + return NULL; + } + if (!fileext) + { + log_add (log_Warning, "SoundDecoder_Register(): Bad file type for %s", + decvtbl->GetName ()); + return NULL; + } + + // check if extension already registered + for (info = sd_decoders; info->used && + (!info->ext || strcmp (info->ext, fileext) != 0); + ++info) + { + // and pick up an empty slot (where available) + if (!newslot && !info->ext) + newslot = info; + } + + if (info >= sd_decoders + MAX_REG_DECODERS) + { + log_add (log_Warning, "SoundDecoder_Register(): Decoders limit reached"); + return NULL; + } + else if (info->ext) + { + log_add (log_Warning, "SoundDecoder_Register(): " + "'%s' decoder already registered (%s denied)", + fileext, decvtbl->GetName ()); + return NULL; + } + + if (!decvtbl->InitModule (sd_flags, &decoder_formats)) + { + log_add (log_Warning, "SoundDecoder_Register(): %s decoder init failed", + decvtbl->GetName ()); + return NULL; + } + + if (!newslot) + { + newslot = info; + newslot->used = true; + // make next one a term + info[1].builtin = false; + info[1].used = false; + info[1].ext = NULL; + } + + newslot->ext = fileext; + newslot->funcs = decvtbl; + + return newslot; +} + +void +SoundDecoder_Unregister (TFB_RegSoundDecoder* regdec) +{ + if (regdec < sd_decoders || regdec >= sd_decoders + MAX_REG_DECODERS || + !regdec->ext || !regdec->funcs) + { + log_add (log_Warning, "SoundDecoder_Unregister(): " + "Invalid or expired decoder passed"); + return; + } + + regdec->funcs->TermModule (); + regdec->ext = NULL; + regdec->funcs = NULL; +} + +const TFB_SoundDecoderFuncs* +SoundDecoder_Lookup (const char* fileext) +{ + TFB_RegSoundDecoder* info; + + for (info = sd_decoders; info->used && + (!info->ext || strcmp (info->ext, fileext) != 0); + ++info) + ; + return info->ext ? info->funcs : NULL; +} + +TFB_SoundDecoder* +SoundDecoder_Load (uio_DirHandle *dir, char *filename, + uint32 buffer_size, uint32 startTime, sint32 runTime) + // runTime < 0 specifies a default length for a nul decoder +{ + const char* pext; + TFB_RegSoundDecoder* info; + const TFB_SoundDecoderFuncs* funcs; + TFB_SoundDecoder* decoder; + uint32 struct_size; + + pext = strrchr (filename, '.'); + if (!pext) + { + log_add (log_Warning, "SoundDecoder_Load(): Unknown file type (%s)", + filename); + return NULL; + } + ++pext; + + for (info = sd_decoders; info->used && + (!info->ext || strcmp (info->ext, pext) != 0); + ++info) + ; + if (!info->ext) + { + log_add (log_Warning, "SoundDecoder_Load(): Unsupported file type (%s)", + filename); + + if (runTime) + { + runTime = abs (runTime); + startTime = 0; + funcs = &nula_DecoderVtbl; + } + else + { + return NULL; + } + } + else + { + funcs = info->funcs; + } + + if (!fileExists2 (dir, filename)) + { + if (runTime) + { + runTime = abs (runTime); + startTime = 0; + funcs = &nula_DecoderVtbl; + } + else + { + log_add (log_Warning, "SoundDecoder_Load(): %s does not exist", + filename); + return NULL; + } + } + + struct_size = funcs->GetStructSize (); + if (struct_size < SD_MIN_SIZE) + struct_size = SD_MIN_SIZE; + + decoder = (TFB_SoundDecoder*) HCalloc (struct_size); + decoder->funcs = funcs; + if (!decoder->funcs->Init (decoder)) + { + log_add (log_Warning, "SoundDecoder_Load(): " + "%s decoder instance failed init", + decoder->funcs->GetName ()); + HFree (decoder); + return NULL; + } + + if (!decoder->funcs->Open (decoder, dir, filename)) + { + log_add (log_Warning, "SoundDecoder_Load(): " + "%s decoder could not load %s", + decoder->funcs->GetName (), filename); + decoder->funcs->Term (decoder); + HFree (decoder); + return NULL; + } + + decoder->buffer = HMalloc (buffer_size); + decoder->buffer_size = buffer_size; + decoder->looping = false; + decoder->error = SOUNDDECODER_OK; + decoder->dir = dir; + decoder->filename = (char *) HMalloc (strlen (filename) + 1); + strcpy (decoder->filename, filename); + + if (decoder->is_null) + { // fake decoder, keeps voiceovers and etc. going + decoder->length = (float) (runTime / 1000.0); + } + + decoder->length -= startTime / 1000.0f; + if (decoder->length < 0) + decoder->length = 0; + else if (runTime > 0 && runTime / 1000.0 < decoder->length) + decoder->length = (float)(runTime / 1000.0); + + decoder->start_sample = (uint32)(startTime / 1000.0f * decoder->frequency); + decoder->end_sample = decoder->start_sample + + (unsigned long)(decoder->length * decoder->frequency); + if (decoder->start_sample != 0) + decoder->funcs->Seek (decoder, decoder->start_sample); + + if (decoder->format == decoder_formats.mono8) + decoder->bytes_per_samp = 1; + else if (decoder->format == decoder_formats.mono16) + decoder->bytes_per_samp = 2; + else if (decoder->format == decoder_formats.stereo8) + decoder->bytes_per_samp = 2; + else if (decoder->format == decoder_formats.stereo16) + decoder->bytes_per_samp = 4; + + decoder->pos = decoder->start_sample * decoder->bytes_per_samp; + + return decoder; +} + +uint32 +SoundDecoder_Decode (TFB_SoundDecoder *decoder) +{ + long decoded_bytes; + long rc; + long buffer_size; + uint32 max_bytes = UINT32_MAX; + uint8 *buffer; + + if (!decoder || !decoder->funcs) + { + log_add (log_Warning, "SoundDecoder_Decode(): null or bad decoder"); + return 0; + } + + buffer = (uint8*) decoder->buffer; + buffer_size = decoder->buffer_size; + if (!decoder->looping && decoder->end_sample > 0) + { + max_bytes = decoder->end_sample * decoder->bytes_per_samp; + if (max_bytes - decoder->pos < decoder->buffer_size) + buffer_size = max_bytes - decoder->pos; + } + + if (buffer_size == 0) + { // nothing more to decode + decoder->error = SOUNDDECODER_EOF; + return 0; + } + + for (decoded_bytes = 0, rc = 1; rc > 0 && decoded_bytes < buffer_size; ) + { + rc = decoder->funcs->Decode (decoder, buffer + decoded_bytes, + buffer_size - decoded_bytes); + if (rc < 0) + { + log_add (log_Warning, "SoundDecoder_Decode(): " + "error decoding %s, code %ld", + decoder->filename, rc); + } + else if (rc == 0) + { // probably EOF + if (decoder->looping) + { + SoundDecoder_Rewind (decoder); + if (decoder->error) + { + log_add (log_Warning, "SoundDecoder_Decode(): " + "tried to loop %s but couldn't rewind, " + "error code %d", + decoder->filename, decoder->error); + } + else + { + log_add (log_Info, "SoundDecoder_Decode(): " + "looping %s", decoder->filename); + rc = 1; // prime the loop again + } + } + else + { + log_add (log_Info, "SoundDecoder_Decode(): eof for %s", + decoder->filename); + } + } + else + { // some bytes decoded + decoded_bytes += rc; + } + } + decoder->pos += decoded_bytes; + if (rc < 0) + decoder->error = SOUNDDECODER_ERROR; + else if (rc == 0 || decoder->pos >= max_bytes) + decoder->error = SOUNDDECODER_EOF; + else + decoder->error = SOUNDDECODER_OK; + + if (decoder->need_swap && decoded_bytes > 0 && + (decoder->format == decoder_formats.stereo16 || + decoder->format == decoder_formats.mono16)) + { + SoundDecoder_SwapWords ( + decoder->buffer, decoded_bytes); + } + + return decoded_bytes; +} + +uint32 +SoundDecoder_DecodeAll (TFB_SoundDecoder *decoder) +{ + uint32 decoded_bytes; + long rc; + uint32 reqbufsize; + + if (!decoder || !decoder->funcs) + { + log_add (log_Warning, "SoundDecoder_DecodeAll(): null or bad decoder"); + return 0; + } + + reqbufsize = decoder->buffer_size; + + if (decoder->looping) + { + log_add (log_Warning, "SoundDecoder_DecodeAll(): " + "called for %s with looping", decoder->filename); + return 0; + } + + if (reqbufsize < 4096) + reqbufsize = 4096; + + for (decoded_bytes = 0, rc = 1; rc > 0; ) + { + if (decoded_bytes >= decoder->buffer_size) + { // need to grow buffer + decoder->buffer_size += reqbufsize; + decoder->buffer = HRealloc ( + decoder->buffer, decoder->buffer_size); + } + + rc = decoder->funcs->Decode (decoder, + (uint8*) decoder->buffer + decoded_bytes, + decoder->buffer_size - decoded_bytes); + + if (rc > 0) + decoded_bytes += rc; + } + decoder->buffer_size = decoded_bytes; + decoder->pos += decoded_bytes; + // Free up some unused memory + decoder->buffer = HRealloc (decoder->buffer, decoded_bytes); + + if (decoder->need_swap && decoded_bytes > 0 && + (decoder->format == decoder_formats.stereo16 || + decoder->format == decoder_formats.mono16)) + { + SoundDecoder_SwapWords ( + decoder->buffer, decoded_bytes); + } + + if (rc < 0) + { + decoder->error = SOUNDDECODER_ERROR; + log_add (log_Warning, "SoundDecoder_DecodeAll(): " + "error decoding %s, code %ld", + decoder->filename, rc); + return decoded_bytes; + } + + // switch to Buffer decoder + decoder->funcs->Close (decoder); + decoder->funcs->Term (decoder); + + decoder->funcs = &bufa_DecoderVtbl; + decoder->funcs->Init (decoder); + decoder->pos = 0; + decoder->start_sample = 0; + decoder->error = SOUNDDECODER_OK; + + return decoded_bytes; +} + +void +SoundDecoder_Rewind (TFB_SoundDecoder *decoder) +{ + SoundDecoder_Seek (decoder, 0); +} + +// seekTime is specified in mili-seconds +void +SoundDecoder_Seek (TFB_SoundDecoder *decoder, uint32 seekTime) +{ + uint32 pcm_pos; + + if (!decoder) + return; + if (!decoder->funcs) + { + log_add (log_Warning, "SoundDecoder_Seek(): bad decoder passed"); + return; + } + + pcm_pos = (uint32) (seekTime / 1000.0f * decoder->frequency); + pcm_pos = decoder->funcs->Seek (decoder, + decoder->start_sample + pcm_pos); + decoder->pos = pcm_pos * decoder->bytes_per_samp; + decoder->error = SOUNDDECODER_OK; +} + +void +SoundDecoder_Free (TFB_SoundDecoder *decoder) +{ + if (!decoder) + return; + if (!decoder->funcs) + { + log_add (log_Warning, "SoundDecoder_Free(): bad decoder passed"); + return; + } + + decoder->funcs->Close (decoder); + decoder->funcs->Term (decoder); + + HFree (decoder->buffer); + HFree (decoder->filename); + HFree (decoder); +} + +float +SoundDecoder_GetTime (TFB_SoundDecoder *decoder) +{ + if (!decoder) + return 0.0f; + if (!decoder->funcs) + { + log_add (log_Warning, "SoundDecoder_GetTime(): bad decoder passed"); + return 0.0f; + } + + return (float) + ((decoder->pos / decoder->bytes_per_samp) + - decoder->start_sample + ) / decoder->frequency; +} + +uint32 +SoundDecoder_GetFrame (TFB_SoundDecoder *decoder) +{ + if (!decoder) + return 0; + if (!decoder->funcs) + { + log_add (log_Warning, "SoundDecoder_GetFrame(): bad decoder passed"); + return 0; + } + + return decoder->funcs->GetFrame (decoder); +} + + +#define THIS_PTR TFB_SoundDecoder* This + +static const char* +bufa_GetName (void) +{ + return "Buffer"; +} + +static bool +bufa_InitModule (int flags, const TFB_DecoderFormats* fmts) +{ + // this should never be called + log_add (log_Debug, "bufa_InitModule(): dead function called"); + return false; + + (void)flags; (void)fmts; // laugh at compiler warning +} + +static void +bufa_TermModule (void) +{ + // this should never be called + log_add (log_Debug, "bufa_TermModule(): dead function called"); +} + +static uint32 +bufa_GetStructSize (void) +{ + return sizeof (TFB_BufSoundDecoder); +} + +static int +bufa_GetError (THIS_PTR) +{ + return 0; // error? what error?! + + (void)This; // laugh at compiler warning +} + +static bool +bufa_Init (THIS_PTR) +{ + TFB_BufSoundDecoder* bufa = (TFB_BufSoundDecoder*) This; + + This->need_swap = false; + // hijack the buffer + bufa->data = This->buffer; + bufa->max_pcm = This->buffer_size / This->bytes_per_samp; + bufa->cur_pcm = bufa->max_pcm; + + return true; +} + +static void +bufa_Term (THIS_PTR) +{ + //TFB_BufSoundDecoder* bufa = (TFB_BufSoundDecoder*) This; + bufa_Close (This); // ensure cleanup +} + +static bool +bufa_Open (THIS_PTR, uio_DirHandle *dir, const char *filename) +{ + // this should never be called + log_add (log_Debug, "bufa_Open(): dead function called"); + return false; + + // laugh at compiler warnings + (void)This; (void)dir; (void)filename; +} + +static void +bufa_Close (THIS_PTR) +{ + TFB_BufSoundDecoder* bufa = (TFB_BufSoundDecoder*) This; + + // restore the status quo + if (bufa->data) + { + This->buffer = bufa->data; + bufa->data = NULL; + } + bufa->cur_pcm = 0; +} + +static int +bufa_Decode (THIS_PTR, void* buf, sint32 bufsize) +{ + TFB_BufSoundDecoder* bufa = (TFB_BufSoundDecoder*) This; + uint32 dec_pcm; + uint32 dec_bytes; + + dec_pcm = bufsize / This->bytes_per_samp; + if (dec_pcm > bufa->max_pcm - bufa->cur_pcm) + dec_pcm = bufa->max_pcm - bufa->cur_pcm; + dec_bytes = dec_pcm * This->bytes_per_samp; + + // Buffer decode is a hack + This->buffer = (uint8*) bufa->data + + bufa->cur_pcm * This->bytes_per_samp; + + if (dec_pcm > 0) + bufa->cur_pcm += dec_pcm; + + return dec_bytes; + + (void)buf; // laugh at compiler warning +} + +static uint32 +bufa_Seek (THIS_PTR, uint32 pcm_pos) +{ + TFB_BufSoundDecoder* bufa = (TFB_BufSoundDecoder*) This; + + if (pcm_pos > bufa->max_pcm) + pcm_pos = bufa->max_pcm; + bufa->cur_pcm = pcm_pos; + + return pcm_pos; +} + +static uint32 +bufa_GetFrame (THIS_PTR) +{ + return 0; // only 1 frame + + (void)This; // laugh at compiler warning +} + + +static const char* +nula_GetName (void) +{ + return "Null"; +} + +static bool +nula_InitModule (int flags, const TFB_DecoderFormats* fmts) +{ + // this should never be called + log_add (log_Debug, "nula_InitModule(): dead function called"); + return false; + + (void)flags; (void)fmts; // laugh at compiler warning +} + +static void +nula_TermModule (void) +{ + // this should never be called + log_add (log_Debug, "nula_TermModule(): dead function called"); +} + +static uint32 +nula_GetStructSize (void) +{ + return sizeof (TFB_NullSoundDecoder); +} + +static int +nula_GetError (THIS_PTR) +{ + return 0; // error? what error?! + + (void)This; // laugh at compiler warning +} + +static bool +nula_Init (THIS_PTR) +{ + TFB_NullSoundDecoder* nula = (TFB_NullSoundDecoder*) This; + + This->need_swap = false; + nula->cur_pcm = 0; + return true; +} + +static void +nula_Term (THIS_PTR) +{ + //TFB_NullSoundDecoder* nula = (TFB_NullSoundDecoder*) This; + nula_Close (This); // ensure cleanup +} + +static bool +nula_Open (THIS_PTR, uio_DirHandle *dir, const char *filename) +{ + This->frequency = 11025; + This->format = decoder_formats.mono16; + This->is_null = true; + return true; + + // laugh at compiler warnings + (void)dir; (void)filename; +} + +static void +nula_Close (THIS_PTR) +{ + TFB_NullSoundDecoder* nula = (TFB_NullSoundDecoder*) This; + + nula->cur_pcm = 0; +} + +static int +nula_Decode (THIS_PTR, void* buf, sint32 bufsize) +{ + TFB_NullSoundDecoder* nula = (TFB_NullSoundDecoder*) This; + uint32 max_pcm; + uint32 dec_pcm; + uint32 dec_bytes; + + max_pcm = (uint32) (This->length * This->frequency); + dec_pcm = bufsize / This->bytes_per_samp; + if (dec_pcm > max_pcm - nula->cur_pcm) + dec_pcm = max_pcm - nula->cur_pcm; + dec_bytes = dec_pcm * This->bytes_per_samp; + + if (dec_pcm > 0) + { + memset (buf, 0, dec_bytes); + nula->cur_pcm += dec_pcm; + } + + return dec_bytes; +} + +static uint32 +nula_Seek (THIS_PTR, uint32 pcm_pos) +{ + TFB_NullSoundDecoder* nula = (TFB_NullSoundDecoder*) This; + uint32 max_pcm; + + max_pcm = (uint32) (This->length * This->frequency); + if (pcm_pos > max_pcm) + pcm_pos = max_pcm; + nula->cur_pcm = pcm_pos; + + return pcm_pos; +} + +static uint32 +nula_GetFrame (THIS_PTR) +{ + return 0; // only 1 frame + + (void)This; // laugh at compiler warning +} diff --git a/src/libs/sound/decoders/decoder.h b/src/libs/sound/decoders/decoder.h new file mode 100644 index 0000000..2d6983c --- /dev/null +++ b/src/libs/sound/decoders/decoder.h @@ -0,0 +1,129 @@ +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* Sound file decoder for .wav, .mod, .ogg + * API is heavily influenced by SDL_sound. + */ + +#ifndef DECODER_H +#define DECODER_H + +#include "port.h" +#include "types.h" +#include "libs/uio.h" + +#ifndef OVCODEC_NONE +# ifdef _MSC_VER +# pragma comment (lib, "vorbisfile.lib") +# endif /* _MSC_VER */ +#endif /* OVCODEC_NONE */ + +typedef struct tfb_decoderformats +{ + bool big_endian; + bool want_big_endian; + uint32 mono8; + uint32 stereo8; + uint32 mono16; + uint32 stereo16; +} TFB_DecoderFormats; + +// forward-declare +typedef struct tfb_sounddecoder TFB_SoundDecoder; + +#define THIS_PTR TFB_SoundDecoder* + +typedef struct tfb_sounddecoderfunc +{ + const char* (* GetName) (void); + bool (* InitModule) (int flags, const TFB_DecoderFormats*); + void (* TermModule) (void); + uint32 (* GetStructSize) (void); + int (* GetError) (THIS_PTR); + bool (* Init) (THIS_PTR); + void (* Term) (THIS_PTR); + bool (* Open) (THIS_PTR, uio_DirHandle *dir, const char *filename); + void (* Close) (THIS_PTR); + int (* Decode) (THIS_PTR, void* buf, sint32 bufsize); + // returns <0 on error, ==0 when no more data, >0 bytes returned + uint32 (* Seek) (THIS_PTR, uint32 pcm_pos); + // returns the pcm position set + uint32 (* GetFrame) (THIS_PTR); + +} TFB_SoundDecoderFuncs; + +#undef THIS_PTR + +struct tfb_sounddecoder +{ + // decoder virtual funcs - R/O + const TFB_SoundDecoderFuncs *funcs; + + // public R/O, set by decoder + uint32 format; + uint32 frequency; + float length; // total length in seconds + bool is_null; + bool need_swap; + + // public R/O, set by wrapper + void *buffer; + uint32 buffer_size; + sint32 error; + uint32 bytes_per_samp; + + // public R/W + bool looping; + + // semi-private + uio_DirHandle *dir; + char *filename; + uint32 pos; + uint32 start_sample; + uint32 end_sample; + +}; + +// return values +enum +{ + SOUNDDECODER_OK, + SOUNDDECODER_ERROR, + SOUNDDECODER_EOF, +}; + +typedef struct TFB_RegSoundDecoder TFB_RegSoundDecoder; + +TFB_RegSoundDecoder* SoundDecoder_Register (const char* fileext, + TFB_SoundDecoderFuncs* decvtbl); +void SoundDecoder_Unregister (TFB_RegSoundDecoder* regdec); +const TFB_SoundDecoderFuncs* SoundDecoder_Lookup (const char* fileext); + +void SoundDecoder_SwapWords (uint16* data, uint32 size); +sint32 SoundDecoder_Init (int flags, TFB_DecoderFormats* formats); +void SoundDecoder_Uninit (void); +TFB_SoundDecoder* SoundDecoder_Load (uio_DirHandle *dir, + char *filename, uint32 buffer_size, uint32 startTime, sint32 runTime); +uint32 SoundDecoder_Decode (TFB_SoundDecoder *decoder); +uint32 SoundDecoder_DecodeAll (TFB_SoundDecoder *decoder); +float SoundDecoder_GetTime (TFB_SoundDecoder *decoder); +uint32 SoundDecoder_GetFrame (TFB_SoundDecoder *decoder); +void SoundDecoder_Seek (TFB_SoundDecoder *decoder, uint32 msecs); +void SoundDecoder_Rewind (TFB_SoundDecoder *decoder); +void SoundDecoder_Free (TFB_SoundDecoder *decoder); +const char* SoundDecoder_GetName (TFB_SoundDecoder *decoder); + +#endif diff --git a/src/libs/sound/decoders/dukaud.c b/src/libs/sound/decoders/dukaud.c new file mode 100644 index 0000000..aeff373 --- /dev/null +++ b/src/libs/sound/decoders/dukaud.c @@ -0,0 +1,546 @@ +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* .duk sound track decoder + */ + +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include "libs/memlib.h" +#include "port.h" +#include "types.h" +#include "libs/uio.h" +#include "dukaud.h" +#include "decoder.h" +#include "endian_uqm.h" + +#define DATA_BUF_SIZE 0x8000 +#define DUCK_GENERAL_FPS 14.622f + + +#define THIS_PTR TFB_SoundDecoder* This + +static const char* duka_GetName (void); +static bool duka_InitModule (int flags, const TFB_DecoderFormats*); +static void duka_TermModule (void); +static uint32 duka_GetStructSize (void); +static int duka_GetError (THIS_PTR); +static bool duka_Init (THIS_PTR); +static void duka_Term (THIS_PTR); +static bool duka_Open (THIS_PTR, uio_DirHandle *dir, const char *filename); +static void duka_Close (THIS_PTR); +static int duka_Decode (THIS_PTR, void* buf, sint32 bufsize); +static uint32 duka_Seek (THIS_PTR, uint32 pcm_pos); +static uint32 duka_GetFrame (THIS_PTR); + +TFB_SoundDecoderFuncs duka_DecoderVtbl = +{ + duka_GetName, + duka_InitModule, + duka_TermModule, + duka_GetStructSize, + duka_GetError, + duka_Init, + duka_Term, + duka_Open, + duka_Close, + duka_Decode, + duka_Seek, + duka_GetFrame, +}; + +typedef struct tfb_ducksounddecoder +{ + // always the first member + TFB_SoundDecoder decoder; + + // public read-only + uint32 iframe; // current frame index + uint32 cframes; // total count of frames + uint32 channels; // number of channels + uint32 pcm_frame; // samples per frame + + // private + sint32 last_error; + uio_Stream* duk; + uint32* frames; + // buffer + void* data; + uint32 maxdata; + uint32 cbdata; + uint32 dataofs; + // decoder stuff + sint32 predictors[2]; + +} TFB_DuckSoundDecoder; + + +typedef struct +{ + uint32 audsize; + uint32 vidsize; +} DukAud_FrameHeader; + +typedef struct +{ + uint16 magic; // always 0xf77f + uint16 numsamples; + uint16 tag; + uint16 indices[2]; // initial indices for channels +} DukAud_AudSubframe; + +static const TFB_DecoderFormats* duka_formats = NULL; + +static sint32 +duka_readAudFrameHeader (TFB_DuckSoundDecoder* duka, uint32 iframe, + DukAud_AudSubframe* aud) +{ + DukAud_FrameHeader hdr; + + uio_fseek (duka->duk, duka->frames[iframe], SEEK_SET); + if (uio_fread (&hdr, sizeof(hdr), 1, duka->duk) != 1) + { + duka->last_error = errno; + return dukae_BadFile; + } + hdr.audsize = UQM_SwapBE32 (hdr.audsize); + + if (uio_fread (aud, sizeof(*aud), 1, duka->duk) != 1) + { + duka->last_error = errno; + return dukae_BadFile; + } + + aud->magic = UQM_SwapBE16 (aud->magic); + if (aud->magic != 0xf77f) + return duka->last_error = dukae_BadFile; + + aud->numsamples = UQM_SwapBE16 (aud->numsamples); + aud->tag = UQM_SwapBE16 (aud->tag); + aud->indices[0] = UQM_SwapBE16 (aud->indices[0]); + aud->indices[1] = UQM_SwapBE16 (aud->indices[1]); + + return 0; +} + +// This table is from one of the files that came with the original 3do source +// It's slightly different from the data used by MPlayer. +static int adpcm_step[89] = { + 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xF, + 0x10, 0x12, 0x13, 0x15, 0x17, 0x1A, 0x1C, 0x1F, + 0x22, 0x26, 0x29, 0x2E, 0x32, 0x37, 0x3D, 0x43, + 0x4A, 0x51, 0x59, 0x62, 0x6C, 0x76, 0x82, 0x8F, + 0x9E, 0xAD, 0xBF, 0xD2, 0xE7, 0xFE, 0x117, 0x133, + 0x152, 0x174, 0x199, 0x1C2, 0x1EF, 0x220, 0x256, 0x292, + 0x2D4, 0x31D, 0x36C, 0x3C4, 0x424, 0x48E, 0x503, 0x583, + 0x610, 0x6AC, 0x756, 0x812, 0x8E1, 0x9C4, 0xABE, 0xBD1, + 0xCFF, 0xE4C, 0xFBA, 0x114D, 0x1308, 0x14EF, 0x1707, 0x1954, + 0x1BDD, 0x1EA6, 0x21B7, 0x2516, + 0x28CB, 0x2CDF, 0x315C, 0x364C, + 0x3BBA, 0x41B2, 0x4844, 0x4F7E, + 0x5771, 0x6030, 0x69CE, 0x7463, + 0x7FFF + }; + + +// *** BEGIN part copied from MPlayer *** +// (some little changes) + +#if 0 +// pertinent tables for IMA ADPCM +static int adpcm_step[89] = { + 7, 8, 9, 10, 11, 12, 13, 14, 16, 17, + 19, 21, 23, 25, 28, 31, 34, 37, 41, 45, + 50, 55, 60, 66, 73, 80, 88, 97, 107, 118, + 130, 143, 157, 173, 190, 209, 230, 253, 279, 307, + 337, 371, 408, 449, 494, 544, 598, 658, 724, 796, + 876, 963, 1060, 1166, 1282, 1411, 1552, 1707, 1878, 2066, + 2272, 2499, 2749, 3024, 3327, 3660, 4026, 4428, 4871, 5358, + 5894, 6484, 7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899, + 15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794, 32767 + }; +#endif + +static int adpcm_index[16] = { + -1, -1, -1, -1, 2, 4, 6, 8, + -1, -1, -1, -1, 2, 4, 6, 8 + }; + +// clamp a number between 0 and 88 +#define CLAMP_0_TO_88(x) \ + if ((x) < 0) (x) = 0; else if ((x) > 88) (x) = 88; + +// clamp a number within a signed 16-bit range +#define CLAMP_S16(x) \ + if ((x) < -32768) \ + (x) = -32768; \ + else if ((x) > 32767) \ + (x) = 32767; + +static void +decode_nibbles (sint16 *output, sint32 output_size, sint32 channels, + sint32* predictors, uint16* indices) +{ + sint32 step[2]; + sint32 index[2]; + sint32 diff; + sint32 i; + int sign; + sint32 delta; + int channel_number = 0; + + channels -= 1; + index[0] = indices[0]; + index[1] = indices[1]; + step[0] = adpcm_step[index[0]]; + step[1] = adpcm_step[index[1]]; + + for (i = 0; i < output_size; i++) + { + delta = output[i]; + + index[channel_number] += adpcm_index[delta]; + CLAMP_0_TO_88(index[channel_number]); + + sign = delta & 8; + delta = delta & 7; + +#if 0 + // fast approximation, used in most decoders + diff = step[channel_number] >> 3; + if (delta & 4) diff += step[channel_number]; + if (delta & 2) diff += step[channel_number] >> 1; + if (delta & 1) diff += step[channel_number] >> 2; +#else + // real thing +// diff = ((signed)delta + 0.5) * step[channel_number] / 4; + diff = (((delta << 1) + 1) * step[channel_number]) >> 3; +#endif + + if (sign) + predictors[channel_number] -= diff; + else + predictors[channel_number] += diff; + + CLAMP_S16(predictors[channel_number]); + output[i] = predictors[channel_number]; + step[channel_number] = adpcm_step[index[channel_number]]; + + // toggle channel + channel_number ^= channels; + } +} +// *** END part copied from MPlayer *** + +static sint32 +duka_decodeFrame (TFB_DuckSoundDecoder* duka, DukAud_AudSubframe* header, + uint8* input) +{ + uint8* inend; + sint16* output; + sint16* outptr; + sint32 outputsize; + + outputsize = header->numsamples * 2 * sizeof (sint16); + outptr = output = (sint16*) ((uint8*)duka->data + duka->cbdata); + + for (inend = input + header->numsamples; input < inend; ++input) + { + *(outptr++) = *input >> 4; + *(outptr++) = *input & 0x0f; + } + + decode_nibbles (output, header->numsamples * 2, duka->channels, + duka->predictors, header->indices); + + duka->cbdata += outputsize; + + return outputsize; +} + + +static sint32 +duka_readNextFrame (TFB_DuckSoundDecoder* duka) +{ + DukAud_FrameHeader hdr; + DukAud_AudSubframe* aud; + uint8* p; + + uio_fseek (duka->duk, duka->frames[duka->iframe], SEEK_SET); + if (uio_fread (&hdr, sizeof(hdr), 1, duka->duk) != 1) + { + duka->last_error = errno; + return dukae_BadFile; + } + hdr.audsize = UQM_SwapBE32 (hdr.audsize); + + // dump encoded data at the end of the buffer aligned on 8-byte + p = ((uint8*)duka->data + duka->maxdata - ((hdr.audsize + 7) & (-8))); + if (uio_fread (p, 1, hdr.audsize, duka->duk) != hdr.audsize) + { + duka->last_error = errno; + return dukae_BadFile; + } + aud = (DukAud_AudSubframe*) p; + p += sizeof(DukAud_AudSubframe); + + aud->magic = UQM_SwapBE16 (aud->magic); + if (aud->magic != 0xf77f) + return duka->last_error = dukae_BadFile; + + aud->numsamples = UQM_SwapBE16 (aud->numsamples); + aud->tag = UQM_SwapBE16 (aud->tag); + aud->indices[0] = UQM_SwapBE16 (aud->indices[0]); + aud->indices[1] = UQM_SwapBE16 (aud->indices[1]); + + duka->iframe++; + + return duka_decodeFrame (duka, aud, p); +} + +static sint32 +duka_stuffBuffer (TFB_DuckSoundDecoder* duka, void* buf, sint32 bufsize) +{ + sint32 dataleft; + + dataleft = duka->cbdata - duka->dataofs; + if (dataleft > 0) + { + if (dataleft > bufsize) + dataleft = bufsize & (-4); + memcpy (buf, (uint8*)duka->data + duka->dataofs, dataleft); + duka->dataofs += dataleft; + } + + if (duka->cbdata > 0 && duka->dataofs >= duka->cbdata) + duka->cbdata = duka->dataofs = 0; // reset for new data + + return dataleft; +} + + +static const char* +duka_GetName (void) +{ + return "DukAud"; +} + +static bool +duka_InitModule (int flags, const TFB_DecoderFormats* fmts) +{ + duka_formats = fmts; + return true; + + (void)flags; // laugh at compiler warning +} + +static void +duka_TermModule (void) +{ + // no specific module term +} + +static uint32 +duka_GetStructSize (void) +{ + return sizeof (TFB_DuckSoundDecoder); +} + +static int +duka_GetError (THIS_PTR) +{ + TFB_DuckSoundDecoder* duka = (TFB_DuckSoundDecoder*) This; + int ret = duka->last_error; + duka->last_error = dukae_None; + return ret; +} + +static bool +duka_Init (THIS_PTR) +{ + //TFB_DuckSoundDecoder* duka = (TFB_DuckSoundDecoder*) This; + This->need_swap = + duka_formats->big_endian != duka_formats->want_big_endian; + return true; +} + +static void +duka_Term (THIS_PTR) +{ + //TFB_DuckSoundDecoder* duka = (TFB_DuckSoundDecoder*) This; + duka_Close (This); // ensure cleanup +} + +static bool +duka_Open (THIS_PTR, uio_DirHandle *dir, const char *file) +{ + TFB_DuckSoundDecoder* duka = (TFB_DuckSoundDecoder*) This; + uio_Stream* duk; + uio_Stream* frm; + DukAud_AudSubframe aud; + char filename[256]; + uint32 filelen; + size_t cread; + uint32 i; + + filelen = strlen (file); + if (filelen > sizeof (filename) - 1) + return false; + strcpy (filename, file); + + duk = uio_fopen (dir, filename, "rb"); + if (!duk) + { + duka->last_error = errno; + return false; + } + + strcpy (filename + filelen - 3, "frm"); + frm = uio_fopen (dir, filename, "rb"); + if (!frm) + { + duka->last_error = errno; + uio_fclose (duk); + return false; + } + + duka->duk = duk; + + uio_fseek (frm, 0, SEEK_END); + duka->cframes = uio_ftell (frm) / sizeof (uint32); + uio_fseek (frm, 0, SEEK_SET); + if (!duka->cframes) + { + duka->last_error = dukae_BadFile; + uio_fclose (frm); + duka_Close (This); + return false; + } + + duka->frames = (uint32*) HMalloc (duka->cframes * sizeof (uint32)); + cread = uio_fread (duka->frames, sizeof (uint32), duka->cframes, frm); + uio_fclose (frm); + if (cread != duka->cframes) + { + duka->last_error = dukae_BadFile; + duka_Close (This); + return false; + } + + for (i = 0; i < duka->cframes; ++i) + duka->frames[i] = UQM_SwapBE32 (duka->frames[i]); + + if (duka_readAudFrameHeader (duka, 0, &aud) < 0) + { + duka_Close (This); + return false; + } + + This->frequency = 22050; + This->format = duka_formats->stereo16; + duka->channels = 2; + duka->pcm_frame = aud.numsamples; + duka->data = HMalloc (DATA_BUF_SIZE); + duka->maxdata = DATA_BUF_SIZE; + + // estimate + This->length = (float) duka->cframes / DUCK_GENERAL_FPS; + + duka->last_error = 0; + + return true; +} + +static void +duka_Close (THIS_PTR) +{ + TFB_DuckSoundDecoder* duka = (TFB_DuckSoundDecoder*) This; + + if (duka->data) + { + HFree (duka->data); + duka->data = NULL; + } + if (duka->frames) + { + HFree (duka->frames); + duka->frames = NULL; + } + if (duka->duk) + { + uio_fclose (duka->duk); + duka->duk = NULL; + } + duka->last_error = 0; +} + +static int +duka_Decode (THIS_PTR, void* buf, sint32 bufsize) +{ + TFB_DuckSoundDecoder* duka = (TFB_DuckSoundDecoder*) This; + sint32 stuffed; + sint32 total = 0; + + if (bufsize <= 0) + return duka->last_error = dukae_BadArg; + + do + { + stuffed = duka_stuffBuffer (duka, buf, bufsize); + buf = (uint8*)buf + stuffed; + bufsize -= stuffed; + total += stuffed; + + if (bufsize > 0 && duka->iframe < duka->cframes) + { + stuffed = duka_readNextFrame (duka); + if (stuffed <= 0) + return stuffed; + } + } while (bufsize > 0 && duka->iframe < duka->cframes); + + return total; +} + +static uint32 +duka_Seek (THIS_PTR, uint32 pcm_pos) +{ + TFB_DuckSoundDecoder* duka = (TFB_DuckSoundDecoder*) This; + uint32 iframe; + + iframe = pcm_pos / duka->pcm_frame; + if (iframe < duka->cframes) + { + duka->iframe = iframe; + duka->cbdata = 0; + duka->dataofs = 0; + duka->predictors[0] = 0; + duka->predictors[1] = 0; + } + return duka->iframe * duka->pcm_frame; +} + +static uint32 +duka_GetFrame (THIS_PTR) +{ + TFB_DuckSoundDecoder* duka = (TFB_DuckSoundDecoder*) This; + + // if there is nothing buffered return the actual current frame + // otherwise return previous + return duka->dataofs == duka->cbdata ? + duka->iframe : duka->iframe - 1; +} diff --git a/src/libs/sound/decoders/dukaud.h b/src/libs/sound/decoders/dukaud.h new file mode 100644 index 0000000..23c4201 --- /dev/null +++ b/src/libs/sound/decoders/dukaud.h @@ -0,0 +1,36 @@ +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* .duk sound track decoder */ + +#ifndef DUKAUD_H +#define DUKAUD_H + +#include "decoder.h" + +extern TFB_SoundDecoderFuncs duka_DecoderVtbl; + +typedef enum +{ + // positive values are the same as in errno + dukae_None = 0, + dukae_Unknown = -1, + dukae_BadFile = -2, + dukae_BadArg = -3, + dukae_Other = -1000, +} DukAud_Error; + +#endif // DUKAUD_H diff --git a/src/libs/sound/decoders/modaud.c b/src/libs/sound/decoders/modaud.c new file mode 100644 index 0000000..18c29a2 --- /dev/null +++ b/src/libs/sound/decoders/modaud.c @@ -0,0 +1,430 @@ +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* MikMod decoder (.mod adapter) + */ + +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include "libs/memlib.h" +#include "port.h" +#include "types.h" +#include "endian_uqm.h" +#include "libs/uio.h" +#include "decoder.h" +#include "libs/sound/audiocore.h" +#include "libs/log.h" +#include "modaud.h" + +#ifdef USE_INTERNAL_MIKMOD +# include "libs/mikmod/mikmod.h" +#else +# include <mikmod.h> +#endif + +#define THIS_PTR TFB_SoundDecoder* This + +static const char* moda_GetName (void); +static bool moda_InitModule (int flags, const TFB_DecoderFormats*); +static void moda_TermModule (void); +static uint32 moda_GetStructSize (void); +static int moda_GetError (THIS_PTR); +static bool moda_Init (THIS_PTR); +static void moda_Term (THIS_PTR); +static bool moda_Open (THIS_PTR, uio_DirHandle *dir, const char *filename); +static void moda_Close (THIS_PTR); +static int moda_Decode (THIS_PTR, void* buf, sint32 bufsize); +static uint32 moda_Seek (THIS_PTR, uint32 pcm_pos); +static uint32 moda_GetFrame (THIS_PTR); + +TFB_SoundDecoderFuncs moda_DecoderVtbl = +{ + moda_GetName, + moda_InitModule, + moda_TermModule, + moda_GetStructSize, + moda_GetError, + moda_Init, + moda_Term, + moda_Open, + moda_Close, + moda_Decode, + moda_Seek, + moda_GetFrame, +}; + +typedef struct tfb_modsounddecoder +{ + // always the first member + TFB_SoundDecoder decoder; + + // private + sint32 last_error; + MODULE* module; + +} TFB_ModSoundDecoder; + + + +// MikMod Output driver +// we provide our own so that we can use MikMod as +// generic decoder + +static void* buffer; +static ULONG bufsize; +static ULONG written; + +static ULONG* +moda_mmout_SetOutputBuffer (void* buf, ULONG size) +{ + buffer = buf; + bufsize = size; + written = 0; + return &written; +} + +static BOOL +moda_mmout_IsThere (void) +{ + return 1; +} + +static BOOL +moda_mmout_Init (void) +{ + md_mode |= DMODE_SOFT_MUSIC | DMODE_SOFT_SNDFX; + return VC_Init (); +} + +static void +moda_mmout_Exit (void) +{ + VC_Exit (); +} + +static void +moda_mmout_Update (void) +{ + written = 0; + if (!buffer || bufsize == 0) + return; + + written = VC_WriteBytes (buffer, bufsize); +} + +static BOOL +moda_mmout_Reset (void) +{ + return 0; +} + +static char MDRIVER_name[] = "Mem Buffer"; +static char MDRIVER_version[] = "Mem Buffer driver v1.1"; +static char MDRIVER_alias[] = "membuf"; + +static MDRIVER moda_mmout_drv = +{ + NULL, + //xxx libmikmod does not declare these fields const; it probably should. + MDRIVER_name, // Name + MDRIVER_version, // Version + 0, 255, // Voice limits + MDRIVER_alias, // Alias + +// The minimum mikmod version we support is 3.1.8 +#if (LIBMIKMOD_VERSION_MAJOR > 3) || \ + ((LIBMIKMOD_VERSION_MAJOR == 3) && (LIBMIKMOD_VERSION_MINOR >= 2)) + NULL, // Cmdline help +#endif + + NULL, + moda_mmout_IsThere, + VC_SampleLoad, + VC_SampleUnload, + VC_SampleSpace, + VC_SampleLength, + moda_mmout_Init, + moda_mmout_Exit, + moda_mmout_Reset, + VC_SetNumVoices, + VC_PlayStart, + VC_PlayStop, + moda_mmout_Update, + NULL, /* FIXME: Pause */ + VC_VoiceSetVolume, + VC_VoiceGetVolume, + VC_VoiceSetFrequency, + VC_VoiceGetFrequency, + VC_VoiceSetPanning, + VC_VoiceGetPanning, + VC_VoicePlay, + VC_VoiceStop, + VC_VoiceStopped, + VC_VoiceGetPosition, + VC_VoiceRealVolume +}; + + +static const TFB_DecoderFormats* moda_formats = NULL; + +// MikMod READER interface +// we provide our own so that we can do loading via uio +// +typedef struct MUIOREADER +{ + MREADER core; + uio_Stream* file; + +} MUIOREADER; + +static BOOL +moda_uioReader_Eof (MREADER* reader) +{ + return uio_feof (((MUIOREADER*)reader)->file); +} + +static BOOL +moda_uioReader_Read (MREADER* reader, void* ptr, size_t size) +{ + return uio_fread (ptr, size, 1, ((MUIOREADER*)reader)->file); +} + +static int +moda_uioReader_Get (MREADER* reader) +{ + return uio_fgetc (((MUIOREADER*)reader)->file); +} + +static BOOL +moda_uioReader_Seek (MREADER* reader, long offset, int whence) +{ + return uio_fseek (((MUIOREADER*)reader)->file, offset, whence); +} + +static long +moda_uioReader_Tell (MREADER* reader) +{ + return uio_ftell (((MUIOREADER*)reader)->file); +} + +static MREADER* +moda_new_uioReader (uio_Stream* fp) +{ + MUIOREADER* reader = (MUIOREADER*) HMalloc (sizeof(MUIOREADER)); + if (reader) + { + reader->core.Eof = &moda_uioReader_Eof; + reader->core.Read = &moda_uioReader_Read; + reader->core.Get = &moda_uioReader_Get; + reader->core.Seek = &moda_uioReader_Seek; + reader->core.Tell = &moda_uioReader_Tell; + reader->file = fp; + } + return (MREADER*)reader; +} + +static void +moda_delete_uioReader (MREADER* reader) +{ + if (reader) + HFree (reader); +} + + +static const char* +moda_GetName (void) +{ + return "MikMod"; +} + +static bool +moda_InitModule (int flags, const TFB_DecoderFormats* fmts) +{ + MikMod_RegisterDriver (&moda_mmout_drv); + MikMod_RegisterAllLoaders (); + + if (flags & audio_QUALITY_HIGH) + { + md_mode = DMODE_HQMIXER|DMODE_STEREO|DMODE_16BITS|DMODE_INTERP|DMODE_SURROUND; + md_mixfreq = 44100; + md_reverb = 1; + } + else if (flags & audio_QUALITY_LOW) + { + md_mode = DMODE_SOFT_MUSIC|DMODE_STEREO|DMODE_16BITS; +#ifdef __SYMBIAN32__ + md_mixfreq = 11025; +#else + md_mixfreq = 22050; +#endif + md_reverb = 0; + } + else + { + md_mode = DMODE_SOFT_MUSIC|DMODE_STEREO|DMODE_16BITS|DMODE_INTERP; + md_mixfreq = 44100; + md_reverb = 0; + } + + md_pansep = 64; + + if (MikMod_Init (NULL)) + { + log_add (log_Error, "MikMod_Init() failed, %s", + MikMod_strerror (MikMod_errno)); + return false; + } + + moda_formats = fmts; + + return true; +} + +static void +moda_TermModule (void) +{ + MikMod_Exit (); +} + +static uint32 +moda_GetStructSize (void) +{ + return sizeof (TFB_ModSoundDecoder); +} + +static int +moda_GetError (THIS_PTR) +{ + TFB_ModSoundDecoder* moda = (TFB_ModSoundDecoder*) This; + int ret = moda->last_error; + moda->last_error = 0; + return ret; +} + +static bool +moda_Init (THIS_PTR) +{ + //TFB_ModSoundDecoder* moda = (TFB_ModSoundDecoder*) This; + This->need_swap = + moda_formats->big_endian != moda_formats->want_big_endian; + return true; +} + +static void +moda_Term (THIS_PTR) +{ + //TFB_ModSoundDecoder* moda = (TFB_ModSoundDecoder*) This; + moda_Close (This); // ensure cleanup +} + +static bool +moda_Open (THIS_PTR, uio_DirHandle *dir, const char *filename) +{ + TFB_ModSoundDecoder* moda = (TFB_ModSoundDecoder*) This; + uio_Stream *fp; + MREADER* reader; + MODULE* mod; + + fp = uio_fopen (dir, filename, "rb"); + if (!fp) + { + moda->last_error = errno; + return false; + } + + reader = moda_new_uioReader (fp); + if (!reader) + { + moda->last_error = -1; + uio_fclose (fp); + return false; + } + + mod = Player_LoadGeneric (reader, 8, 0); + + // can already dispose of reader and fileh + moda_delete_uioReader (reader); + uio_fclose (fp); + if (!mod) + { + log_add (log_Warning, "moda_Open(): could not load %s", filename); + return false; + } + + moda->module = mod; + mod->extspd = 1; + mod->panflag = 1; + mod->wrap = 0; + mod->loop = 1; + + This->format = moda_formats->stereo16; + This->frequency = md_mixfreq; + This->length = 0; // FIXME way to obtain this from mikmod? + + moda->last_error = 0; + + return true; +} + +static void +moda_Close (THIS_PTR) +{ + TFB_ModSoundDecoder* moda = (TFB_ModSoundDecoder*) This; + + if (moda->module) + { + Player_Free (moda->module); + moda->module = NULL; + } +} + +static int +moda_Decode (THIS_PTR, void* buf, sint32 bufsize) +{ + TFB_ModSoundDecoder* moda = (TFB_ModSoundDecoder*) This; + volatile ULONG* poutsize; + + Player_Start (moda->module); + if (!Player_Active()) + return 0; + + poutsize = moda_mmout_SetOutputBuffer (buf, bufsize); + MikMod_Update (); + + return *poutsize; +} + +static uint32 +moda_Seek (THIS_PTR, uint32 pcm_pos) +{ + TFB_ModSoundDecoder* moda = (TFB_ModSoundDecoder*) This; + + Player_Start (moda->module); + if (pcm_pos) + log_add (log_Debug, "moda_Seek(): " + "non-zero seek positions not supported for mod"); + Player_SetPosition (0); + + return 0; +} + +static uint32 +moda_GetFrame (THIS_PTR) +{ + TFB_ModSoundDecoder* moda = (TFB_ModSoundDecoder*) This; + return moda->module->sngpos; +} diff --git a/src/libs/sound/decoders/modaud.h b/src/libs/sound/decoders/modaud.h new file mode 100644 index 0000000..3b0eb86 --- /dev/null +++ b/src/libs/sound/decoders/modaud.h @@ -0,0 +1,26 @@ +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* MikMod adapter */ + +#ifndef MODAUD_H +#define MODAUD_H + +#include "decoder.h" + +extern TFB_SoundDecoderFuncs moda_DecoderVtbl; + +#endif // MODAUD_H diff --git a/src/libs/sound/decoders/oggaud.c b/src/libs/sound/decoders/oggaud.c new file mode 100644 index 0000000..6227120 --- /dev/null +++ b/src/libs/sound/decoders/oggaud.c @@ -0,0 +1,278 @@ +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* Ogg Vorbis decoder (.ogg adapter) + */ + +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include "libs/log.h" +#include "port.h" +#include "types.h" +#include "libs/uio.h" +#include "decoder.h" +#ifdef OVCODEC_TREMOR +# include <tremor/ivorbiscodec.h> +# include <tremor/ivorbisfile.h> +#else +# include <vorbis/codec.h> +# include <vorbis/vorbisfile.h> +#endif /* OVCODEC_TREMOR */ +#include "oggaud.h" + + +#define THIS_PTR TFB_SoundDecoder* This + +static const char* ova_GetName (void); +static bool ova_InitModule (int flags, const TFB_DecoderFormats*); +static void ova_TermModule (void); +static uint32 ova_GetStructSize (void); +static int ova_GetError (THIS_PTR); +static bool ova_Init (THIS_PTR); +static void ova_Term (THIS_PTR); +static bool ova_Open (THIS_PTR, uio_DirHandle *dir, const char *filename); +static void ova_Close (THIS_PTR); +static int ova_Decode (THIS_PTR, void* buf, sint32 bufsize); +static uint32 ova_Seek (THIS_PTR, uint32 pcm_pos); +static uint32 ova_GetFrame (THIS_PTR); + +TFB_SoundDecoderFuncs ova_DecoderVtbl = +{ + ova_GetName, + ova_InitModule, + ova_TermModule, + ova_GetStructSize, + ova_GetError, + ova_Init, + ova_Term, + ova_Open, + ova_Close, + ova_Decode, + ova_Seek, + ova_GetFrame, +}; + +typedef struct tfb_oggsounddecoder +{ + // always the first member + TFB_SoundDecoder decoder; + + // private + sint32 last_error; + OggVorbis_File vf; + +} TFB_OggSoundDecoder; + +static const TFB_DecoderFormats* ova_formats = NULL; + +static size_t +ogg_read (void *ptr, size_t size, size_t nmemb, void *datasource) +{ + return uio_fread (ptr, size, nmemb, (uio_Stream *) datasource); +} + +static int +ogg_seek (void *datasource, ogg_int64_t offset, int whence) +{ + long off = (long) offset; + return uio_fseek ((uio_Stream *) datasource, off, whence); +} + +static int +ogg_close (void *datasource) +{ + return uio_fclose ((uio_Stream *) datasource); +} + +static long +ogg_tell (void *datasource) +{ + return uio_ftell ((uio_Stream *) datasource); +} + +static const ov_callbacks ogg_callbacks = +{ + ogg_read, + ogg_seek, + ogg_close, + ogg_tell, +}; + +static const char* +ova_GetName (void) +{ + return "Ogg Vorbis"; +} + +static bool +ova_InitModule (int flags, const TFB_DecoderFormats* fmts) +{ + ova_formats = fmts; + return true; + + (void)flags; // laugh at compiler warning +} + +static void +ova_TermModule (void) +{ + // no specific module term +} + +static uint32 +ova_GetStructSize (void) +{ + return sizeof (TFB_OggSoundDecoder); +} + +static int +ova_GetError (THIS_PTR) +{ + TFB_OggSoundDecoder* ova = (TFB_OggSoundDecoder*) This; + int ret = ova->last_error; + ova->last_error = 0; + return ret; +} + +static bool +ova_Init (THIS_PTR) +{ + //TFB_OggSoundDecoder* ova = (TFB_OggSoundDecoder*) This; + This->need_swap = false; + return true; +} + +static void +ova_Term (THIS_PTR) +{ + //TFB_OggSoundDecoder* ova = (TFB_OggSoundDecoder*) This; + ova_Close (This); // ensure cleanup +} + +static bool +ova_Open (THIS_PTR, uio_DirHandle *dir, const char *filename) +{ + TFB_OggSoundDecoder* ova = (TFB_OggSoundDecoder*) This; + int rc; + uio_Stream *fp; + vorbis_info *vinfo; + + fp = uio_fopen (dir, filename, "rb"); + if (fp == NULL) + { + log_add (log_Warning, "ova_Open(): could not open %s", filename); + return false; + } + + rc = ov_open_callbacks (fp, &ova->vf, NULL, 0, ogg_callbacks); + if (rc != 0) + { + log_add (log_Warning, "ova_Open(): " + "ov_open_callbacks failed for %s, error code %d", + filename, rc); + uio_fclose (fp); + return false; + } + + vinfo = ov_info (&ova->vf, -1); + if (!vinfo) + { + log_add (log_Warning, "ova_Open(): " + "failed to retrieve ogg bitstream info for %s", + filename); + ov_clear (&ova->vf); + return false; + } + + This->frequency = vinfo->rate; +#ifdef OVCODEC_TREMOR + // With tremor ov_time_total returns an integer, in milliseconds. + This->length = ((float) ov_time_total (&ova->vf, -1)) / 1000.0f; +#else + // With libvorbis ov_time_total returns a double, in seconds. + This->length = (float) ov_time_total (&ova->vf, -1); +#endif /* OVCODEC_TREMOR */ + + if (vinfo->channels == 1) + This->format = ova_formats->mono16; + else + This->format = ova_formats->stereo16; + + ova->last_error = 0; + + return true; +} + +static void +ova_Close (THIS_PTR) +{ + TFB_OggSoundDecoder* ova = (TFB_OggSoundDecoder*) This; + + ov_clear (&ova->vf); +} + +static int +ova_Decode (THIS_PTR, void* buf, sint32 bufsize) +{ + TFB_OggSoundDecoder* ova = (TFB_OggSoundDecoder*) This; + long rc; + int bitstream; + +#ifdef OVCODEC_TREMOR + rc = ov_read (&ova->vf, buf, bufsize, &bitstream); +#else + rc = ov_read (&ova->vf, buf, bufsize, ova_formats->want_big_endian, + 2, 1, &bitstream); +#endif /* OVCODEC_TREMOR */ + + if (rc < 0) + ova->last_error = rc; + else + ova->last_error = 0; + + return rc; +} + +static uint32 +ova_Seek (THIS_PTR, uint32 pcm_pos) +{ + TFB_OggSoundDecoder* ova = (TFB_OggSoundDecoder*) This; + int ret; + + ret = ov_pcm_seek (&ova->vf, pcm_pos); + if (ret != 0) + { + ova->last_error = ret; + return (uint32) ov_pcm_tell (&ova->vf); + } + else + return pcm_pos; +} + +static uint32 +ova_GetFrame (THIS_PTR) +{ + TFB_OggSoundDecoder* ova = (TFB_OggSoundDecoder*) This; + // this is the closest to a frame there is in ogg vorbis stream + // doesn't seem to be a func to retrive it +#ifdef OVCODEC_TREMOR + return ova->vf.os->pageno; +#else + return ova->vf.os.pageno; +#endif /* OVCODEC_TREMOR */ +} + diff --git a/src/libs/sound/decoders/oggaud.h b/src/libs/sound/decoders/oggaud.h new file mode 100644 index 0000000..4e443c4 --- /dev/null +++ b/src/libs/sound/decoders/oggaud.h @@ -0,0 +1,26 @@ +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* Ogg Vorbis adapter */ + +#ifndef OGGAUD_H +#define OGGAUD_H + +#include "decoder.h" + +extern TFB_SoundDecoderFuncs ova_DecoderVtbl; + +#endif // OGGAUD_H diff --git a/src/libs/sound/decoders/wav.c b/src/libs/sound/decoders/wav.c new file mode 100644 index 0000000..c22f63f --- /dev/null +++ b/src/libs/sound/decoders/wav.c @@ -0,0 +1,385 @@ +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* Wave decoder (.wav adapter) + * Code is based on Creative's Win32 OpenAL implementation. + */ + +#include <stdio.h> +#include <errno.h> +#include "port.h" +#include "types.h" +#include "libs/uio.h" +#include "endian_uqm.h" +#include "libs/log.h" +#include "wav.h" + +#define wave_MAKE_ID(x1, x2, x3, x4) \ + (((x4) << 24) | ((x3) << 16) | ((x2) << 8) | (x1)) + +#define wave_RiffID wave_MAKE_ID('R', 'I', 'F', 'F') +#define wave_WaveID wave_MAKE_ID('W', 'A', 'V', 'E') +#define wave_FmtID wave_MAKE_ID('f', 'm', 't', ' ') +#define wave_DataID wave_MAKE_ID('d', 'a', 't', 'a') + +typedef struct +{ + uint32 id; + uint32 size; + uint32 type; +} wave_FileHeader; + +typedef struct +{ + uint16 format; + uint16 channels; + uint32 samplesPerSec; + uint32 bytesPerSec; + uint16 blockAlign; + uint16 bitsPerSample; +} wave_FormatHeader; + +typedef struct +{ + uint32 id; + uint32 size; +} wave_ChunkHeader; + + +#define THIS_PTR TFB_SoundDecoder* This + +static const char* wava_GetName (void); +static bool wava_InitModule (int flags, const TFB_DecoderFormats*); +static void wava_TermModule (void); +static uint32 wava_GetStructSize (void); +static int wava_GetError (THIS_PTR); +static bool wava_Init (THIS_PTR); +static void wava_Term (THIS_PTR); +static bool wava_Open (THIS_PTR, uio_DirHandle *dir, const char *filename); +static void wava_Close (THIS_PTR); +static int wava_Decode (THIS_PTR, void* buf, sint32 bufsize); +static uint32 wava_Seek (THIS_PTR, uint32 pcm_pos); +static uint32 wava_GetFrame (THIS_PTR); + +TFB_SoundDecoderFuncs wava_DecoderVtbl = +{ + wava_GetName, + wava_InitModule, + wava_TermModule, + wava_GetStructSize, + wava_GetError, + wava_Init, + wava_Term, + wava_Open, + wava_Close, + wava_Decode, + wava_Seek, + wava_GetFrame, +}; + +typedef struct tfb_wavesounddecoder +{ + // always the first member + TFB_SoundDecoder decoder; + + // private + sint32 last_error; + uio_Stream *fp; + wave_FormatHeader fmtHdr; + uint32 data_ofs; + uint32 data_size; + uint32 max_pcm; + uint32 cur_pcm; + +} TFB_WaveSoundDecoder; + +static const TFB_DecoderFormats* wava_formats = NULL; + + +static const char* +wava_GetName (void) +{ + return "Wave"; +} + +static bool +wava_InitModule (int flags, const TFB_DecoderFormats* fmts) +{ + wava_formats = fmts; + return true; + + (void)flags; // laugh at compiler warning +} + +static void +wava_TermModule (void) +{ + // no specific module term +} + +static uint32 +wava_GetStructSize (void) +{ + return sizeof (TFB_WaveSoundDecoder); +} + +static int +wava_GetError (THIS_PTR) +{ + TFB_WaveSoundDecoder* wava = (TFB_WaveSoundDecoder*) This; + int ret = wava->last_error; + wava->last_error = 0; + return ret; +} + +static bool +wava_Init (THIS_PTR) +{ + //TFB_WaveSoundDecoder* wava = (TFB_WaveSoundDecoder*) This; + This->need_swap = wava_formats->want_big_endian; + return true; +} + +static void +wava_Term (THIS_PTR) +{ + //TFB_WaveSoundDecoder* wava = (TFB_WaveSoundDecoder*) This; + wava_Close (This); // ensure cleanup +} + +static bool +read_le_16 (uio_Stream *fp, uint16 *v) +{ + if (!uio_fread (v, sizeof(*v), 1, fp)) + return false; + *v = UQM_SwapLE16 (*v); + return true; +} + +static bool +read_le_32 (uio_Stream *fp, uint32 *v) +{ + if (!uio_fread (v, sizeof(*v), 1, fp)) + return false; + *v = UQM_SwapLE32 (*v); + return true; +} + +static bool +wava_readFileHeader (TFB_WaveSoundDecoder* wava, wave_FileHeader* hdr) +{ + if (!read_le_32 (wava->fp, &hdr->id) || + !read_le_32 (wava->fp, &hdr->size) || + !read_le_32 (wava->fp, &hdr->type)) + { + wava->last_error = errno; + return false; + } + return true; +} + +static bool +wava_readChunkHeader (TFB_WaveSoundDecoder* wava, wave_ChunkHeader* chunk) +{ + if (!read_le_32 (wava->fp, &chunk->id) || + !read_le_32 (wava->fp, &chunk->size)) + { + wava->last_error = errno; + return false; + } + return true; +} + +static bool +wava_readFormatHeader (TFB_WaveSoundDecoder* wava, wave_FormatHeader* fmt) +{ + if (!read_le_16 (wava->fp, &fmt->format) || + !read_le_16 (wava->fp, &fmt->channels) || + !read_le_32 (wava->fp, &fmt->samplesPerSec) || + !read_le_32 (wava->fp, &fmt->bytesPerSec) || + !read_le_16 (wava->fp, &fmt->blockAlign) || + !read_le_16 (wava->fp, &fmt->bitsPerSample)) + { + wava->last_error = errno; + return false; + } + return true; +} + +static bool +wava_Open (THIS_PTR, uio_DirHandle *dir, const char *filename) +{ + TFB_WaveSoundDecoder* wava = (TFB_WaveSoundDecoder*) This; + wave_FileHeader fileHdr; + wave_ChunkHeader chunkHdr; + long dataLeft; + + wava->fp = uio_fopen (dir, filename, "rb"); + if (!wava->fp) + { + wava->last_error = errno; + return false; + } + + wava->data_size = 0; + wava->data_ofs = 0; + + // read wave header + if (!wava_readFileHeader (wava, &fileHdr)) + { + wava->last_error = errno; + wava_Close (This); + return false; + } + if (fileHdr.id != wave_RiffID || fileHdr.type != wave_WaveID) + { + log_add (log_Warning, "wava_Open(): " + "not a wave file, ID 0x%08x, Type 0x%08x", + fileHdr.id, fileHdr.type); + wava_Close (This); + return false; + } + + for (dataLeft = ((fileHdr.size + 1) & ~1) - 4; dataLeft > 0; + dataLeft -= (((chunkHdr.size + 1) & ~1) + 8)) + { + if (!wava_readChunkHeader (wava, &chunkHdr)) + { + wava_Close (This); + return false; + } + + if (chunkHdr.id == wave_FmtID) + { + if (!wava_readFormatHeader (wava, &wava->fmtHdr)) + { + wava_Close (This); + return false; + } + uio_fseek (wava->fp, chunkHdr.size - 16, SEEK_CUR); + } + else + { + if (chunkHdr.id == wave_DataID) + { + wava->data_size = chunkHdr.size; + wava->data_ofs = uio_ftell (wava->fp); + } + uio_fseek (wava->fp, chunkHdr.size, SEEK_CUR); + } + + // 2-align the file ptr + // XXX: I do not think this is necessary in WAVE files; + // possibly a remnant of ported AIFF reader + uio_fseek (wava->fp, chunkHdr.size & 1, SEEK_CUR); + } + + if (!wava->data_size || !wava->data_ofs) + { + log_add (log_Warning, "wava_Open(): bad wave file," + " no DATA chunk found"); + wava_Close (This); + return false; + } + + if (wava->fmtHdr.format != 0x0001) + { // not a PCM format + log_add (log_Warning, "wava_Open(): unsupported format %x", + wava->fmtHdr.format); + wava_Close (This); + return false; + } + if (wava->fmtHdr.channels != 1 && wava->fmtHdr.channels != 2) + { + log_add (log_Warning, "wava_Open(): unsupported number of channels %u", + (unsigned)wava->fmtHdr.channels); + wava_Close (This); + return false; + } + + if (dataLeft != 0) + log_add (log_Warning, "wava_Open(): bad or unsupported wave file, " + "size in header does not match read chunks"); + + This->format = (wava->fmtHdr.channels == 1 ? + (wava->fmtHdr.bitsPerSample == 8 ? + wava_formats->mono8 : wava_formats->mono16) + : + (wava->fmtHdr.bitsPerSample == 8 ? + wava_formats->stereo8 : wava_formats->stereo16) + ); + This->frequency = wava->fmtHdr.samplesPerSec; + + uio_fseek (wava->fp, wava->data_ofs, SEEK_SET); + wava->max_pcm = wava->data_size / wava->fmtHdr.blockAlign; + wava->cur_pcm = 0; + This->length = (float) wava->max_pcm / wava->fmtHdr.samplesPerSec; + wava->last_error = 0; + + return true; +} + +static void +wava_Close (THIS_PTR) +{ + TFB_WaveSoundDecoder* wava = (TFB_WaveSoundDecoder*) This; + + if (wava->fp) + { + uio_fclose (wava->fp); + wava->fp = NULL; + } +} + +static int +wava_Decode (THIS_PTR, void* buf, sint32 bufsize) +{ + TFB_WaveSoundDecoder* wava = (TFB_WaveSoundDecoder*) This; + uint32 dec_pcm; + + dec_pcm = bufsize / wava->fmtHdr.blockAlign; + if (dec_pcm > wava->max_pcm - wava->cur_pcm) + dec_pcm = wava->max_pcm - wava->cur_pcm; + + dec_pcm = uio_fread (buf, wava->fmtHdr.blockAlign, dec_pcm, wava->fp); + wava->cur_pcm += dec_pcm; + + return dec_pcm * wava->fmtHdr.blockAlign; +} + +static uint32 +wava_Seek (THIS_PTR, uint32 pcm_pos) +{ + TFB_WaveSoundDecoder* wava = (TFB_WaveSoundDecoder*) This; + + if (pcm_pos > wava->max_pcm) + pcm_pos = wava->max_pcm; + wava->cur_pcm = pcm_pos; + uio_fseek (wava->fp, + wava->data_ofs + pcm_pos * wava->fmtHdr.blockAlign, + SEEK_SET); + + return pcm_pos; +} + +static uint32 +wava_GetFrame (THIS_PTR) +{ + //TFB_WaveSoundDecoder* wava = (TFB_WaveSoundDecoder*) This; + return 0; // only 1 frame for now + + (void)This; // laugh at compiler warning +} diff --git a/src/libs/sound/decoders/wav.h b/src/libs/sound/decoders/wav.h new file mode 100644 index 0000000..9aaf347 --- /dev/null +++ b/src/libs/sound/decoders/wav.h @@ -0,0 +1,26 @@ +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* Wave decoder */ + +#ifndef WAV_H +#define WAV_H + +#include "decoder.h" + +extern TFB_SoundDecoderFuncs wava_DecoderVtbl; + +#endif diff --git a/src/libs/sound/fileinst.c b/src/libs/sound/fileinst.c new file mode 100644 index 0000000..cafbb8f --- /dev/null +++ b/src/libs/sound/fileinst.c @@ -0,0 +1,87 @@ +//Copyright Paul Reiche, Fred Ford. 1992-2002 + +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "sound.h" +#include "sndintrn.h" +#include "options.h" +#include "libs/reslib.h" +#include <string.h> + + +SOUND_REF +LoadSoundFile (const char *pStr) +{ + uio_Stream *fp; + + // FIXME: this theoretically needs a mechanism to prevent races + if (_cur_resfile_name) + // something else is loading resources atm + return 0; + + fp = res_OpenResFile (contentDir, pStr, "rb"); + if (fp) + { + SOUND_REF hData; + + _cur_resfile_name = pStr; + hData = (SOUND_REF)_GetSoundBankData (fp, LengthResFile (fp)); + _cur_resfile_name = 0; + + res_CloseResFile (fp); + + return hData; + } + + return NULL; +} + +MUSIC_REF +LoadMusicFile (const char *pStr) +{ + uio_Stream *fp; + char filename[256]; + + // FIXME: this theoretically needs a mechanism to prevent races + if (_cur_resfile_name) + // something else is loading resources atm + return 0; + + strncpy (filename, pStr, sizeof(filename) - 1); + filename[sizeof(filename) - 1] = '\0'; + CheckMusicResName (filename); + + // Opening the res file is not technically necessary right now + // since _GetMusicData() completely ignores the arguments + // But just for the sake of correctness + fp = res_OpenResFile (contentDir, filename, "rb"); + if (fp) + { + MUSIC_REF hData; + + _cur_resfile_name = filename; + hData = (MUSIC_REF)_GetMusicData (fp, LengthResFile (fp)); + _cur_resfile_name = 0; + + res_CloseResFile (fp); + + return hData; + } + + return (0); +} + diff --git a/src/libs/sound/mixer/Makeinfo b/src/libs/sound/mixer/Makeinfo new file mode 100644 index 0000000..66f960d --- /dev/null +++ b/src/libs/sound/mixer/Makeinfo @@ -0,0 +1,3 @@ +uqm_SUBDIRS="sdl nosound" +uqm_CFILES="mixer.c" +uqm_HFILES="mixer.h mixerint.h" diff --git a/src/libs/sound/mixer/mixer.c b/src/libs/sound/mixer/mixer.c new file mode 100644 index 0000000..3e14ddd --- /dev/null +++ b/src/libs/sound/mixer/mixer.c @@ -0,0 +1,1760 @@ +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* Mixer for low-level sound output drivers + */ + +#include <stdio.h> +#include <string.h> +#include <math.h> +#include "mixer.h" +#include "mixerint.h" +#include "libs/misc.h" +#include "libs/threadlib.h" +#include "libs/log.h" +#include "libs/memlib.h" + +static uint32 mixer_initialized = 0; +static uint32 mixer_format; +static uint32 mixer_chansize; +static uint32 mixer_sampsize; +static uint32 mixer_freq; +static uint32 mixer_channels; +static uint32 last_error = MIX_NO_ERROR; +static mixer_Quality mixer_quality; +static mixer_Resampling mixer_resampling; +static mixer_Flags mixer_flags; + +/* when locking more than one mutex + * you must lock them in this order + */ +static RecursiveMutex src_mutex; +static RecursiveMutex buf_mutex; +static RecursiveMutex act_mutex; + +#define MAX_SOURCES 8 +mixer_Source *active_sources[MAX_SOURCES]; + + +/************************************************* + * Internals + */ + +static void +mixer_SetError (uint32 error) +{ + last_error = error; +} + + +/************************************************* + * General interface + */ + +uint32 +mixer_GetError (void) +{ + uint32 error = last_error; + last_error = MIX_NO_ERROR; + return error; +} + +/* Initialize the mixer with a certain audio format */ +bool +mixer_Init (uint32 frequency, uint32 format, mixer_Quality quality, + mixer_Flags flags) +{ + if (mixer_initialized) + mixer_Uninit (); + + last_error = MIX_NO_ERROR; + memset (active_sources, 0, sizeof(mixer_Source*) * MAX_SOURCES); + + mixer_chansize = MIX_FORMAT_BPC (format); + mixer_channels = MIX_FORMAT_CHANS (format); + mixer_sampsize = MIX_FORMAT_SAMPSIZE (format); + mixer_freq = frequency; + mixer_quality = quality; + mixer_format = format; + mixer_flags = flags; + + mixer_resampling.None = mixer_ResampleNone; + mixer_resampling.Downsample = mixer_ResampleNearest; + if (mixer_quality == MIX_QUALITY_DEFAULT) + mixer_resampling.Upsample = mixer_UpsampleLinear; + else if (mixer_quality == MIX_QUALITY_HIGH) + mixer_resampling.Upsample = mixer_UpsampleCubic; + else + mixer_resampling.Upsample = mixer_ResampleNearest; + + src_mutex = CreateRecursiveMutex("mixer_SourceMutex", SYNC_CLASS_AUDIO); + buf_mutex = CreateRecursiveMutex("mixer_BufferMutex", SYNC_CLASS_AUDIO); + act_mutex = CreateRecursiveMutex("mixer_ActiveMutex", SYNC_CLASS_AUDIO); + + mixer_initialized = 1; + + return true; +} + +/* Uninitialize the mixer */ +void +mixer_Uninit (void) +{ + if (mixer_initialized) + { + DestroyRecursiveMutex (src_mutex); + DestroyRecursiveMutex (buf_mutex); + DestroyRecursiveMutex (act_mutex); + mixer_initialized = 0; + } +} + + +/********************************************************** + * THE mixer + * + */ + +void +mixer_MixChannels (void *userdata, uint8 *stream, sint32 len) +{ + uint8 *end_stream = stream + len; + bool left = true; + + /* keep this order or die */ + LockRecursiveMutex (src_mutex); + LockRecursiveMutex (buf_mutex); + LockRecursiveMutex (act_mutex); + + for (; stream < end_stream; stream += mixer_chansize) + { + uint32 i; + float fullsamp = 0; + + for (i = 0; i < MAX_SOURCES; i++) + { + mixer_Source *src; + float samp = 0; + + /* find next source */ + for (; i < MAX_SOURCES && ( + (src = active_sources[i]) == 0 + || src->state != MIX_PLAYING + || !mixer_SourceGetNextSample (src, &samp, left)); + i++) + ; + + if (i < MAX_SOURCES) + { + /* sample acquired */ + fullsamp += samp; + } + } + + /* clip the sample */ + if (mixer_chansize == 2) + { + /* check S16 clipping */ + if (fullsamp > SINT16_MAX) + fullsamp = SINT16_MAX; + else if (fullsamp < SINT16_MIN) + fullsamp = SINT16_MIN; + } + else + { + /* check S8 clipping */ + if (fullsamp > SINT8_MAX) + fullsamp = SINT8_MAX; + else if (fullsamp < SINT8_MIN) + fullsamp = SINT8_MIN; + } + + mixer_PutSampleExt (stream, mixer_chansize, (sint32)fullsamp); + if (mixer_channels == 2) + left = !left; + } + + /* keep this order or die */ + UnlockRecursiveMutex (act_mutex); + UnlockRecursiveMutex (buf_mutex); + UnlockRecursiveMutex (src_mutex); + + (void) userdata; // satisfying compiler - unused arg +} + +/* fake mixer -- only process buffer and source states */ +void +mixer_MixFake (void *userdata, uint8 *stream, sint32 len) +{ + uint8 *end_stream = stream + len; + bool left = true; + + /* keep this order or die */ + LockRecursiveMutex (src_mutex); + LockRecursiveMutex (buf_mutex); + LockRecursiveMutex (act_mutex); + + for (; stream < end_stream; stream += mixer_chansize) + { + uint32 i; + + for (i = 0; i < MAX_SOURCES; i++) + { + mixer_Source *src; + float samp; + + /* find next source */ + for (; i < MAX_SOURCES && ( + (src = active_sources[i]) == 0 + || src->state != MIX_PLAYING + || !mixer_SourceGetFakeSample (src, &samp, left)); + i++) + ; + } + if (mixer_channels == 2) + left = !left; + } + + /* keep this order or die */ + UnlockRecursiveMutex (act_mutex); + UnlockRecursiveMutex (buf_mutex); + UnlockRecursiveMutex (src_mutex); + + (void) userdata; // satisfying compiler - unused arg +} + + +/************************************************* + * Sources interface + */ + +/* generate n sources */ +void +mixer_GenSources (uint32 n, mixer_Object *psrcobj) +{ + if (n == 0) + return; /* do nothing per OpenAL */ + + if (!psrcobj) + { + mixer_SetError (MIX_INVALID_NAME); + log_add (log_Debug, "mixer_GenSources() called with null ptr"); + return; + } + for (; n; n--, psrcobj++) + { + mixer_Source *src; + + src = (mixer_Source *) HMalloc (sizeof (mixer_Source)); + src->magic = mixer_srcMagic; + src->locked = false; + src->state = MIX_INITIAL; + src->looping = false; + src->gain = MIX_GAIN_ADJ; + src->cqueued = 0; + src->cprocessed = 0; + src->firstqueued = 0; + src->nextqueued = 0; + src->prevqueued = 0; + src->lastqueued = 0; + src->pos = 0; + src->count = 0; + + *psrcobj = (mixer_Object) src; + } +} + +/* delete n sources */ +void +mixer_DeleteSources (uint32 n, mixer_Object *psrcobj) +{ + uint32 i; + mixer_Object *pcurobj; + + if (n == 0) + return; /* do nothing per OpenAL */ + + if (!psrcobj) + { + mixer_SetError (MIX_INVALID_NAME); + log_add (log_Debug, "mixer_DeleteSources() called with null ptr"); + return; + } + + LockRecursiveMutex (src_mutex); + + /* check to make sure we can delete all sources */ + for (i = n, pcurobj = psrcobj; i && pcurobj; i--, pcurobj++) + { + mixer_Source *src = (mixer_Source *) *pcurobj; + + if (!src) + continue; + + if (src->magic != mixer_srcMagic) + break; + } + + if (i) + { /* some source failed */ + mixer_SetError (MIX_INVALID_NAME); + log_add (log_Debug, "mixer_DeleteSources(): not a source"); + } + else + { /* all sources checked out */ + for (; n; n--, psrcobj++) + { + mixer_Source *src = (mixer_Source *) *psrcobj; + + if (!src) + continue; + + /* stopping should not be necessary + * under ideal circumstances + */ + if (src->state != MIX_INITIAL) + mixer_SourceStop_internal (src); + + /* unqueueing should not be necessary + * under ideal circumstances + */ + mixer_SourceUnqueueAll (src); + HFree (src); + *psrcobj = 0; + } + } + + UnlockRecursiveMutex (src_mutex); +} + +/* check if really is a source */ +bool +mixer_IsSource (mixer_Object srcobj) +{ + mixer_Source *src = (mixer_Source *) srcobj; + bool ret; + + if (!src) + return false; + + LockRecursiveMutex (src_mutex); + ret = src->magic == mixer_srcMagic; + UnlockRecursiveMutex (src_mutex); + + return ret; +} + +/* set source integer property */ +void +mixer_Sourcei (mixer_Object srcobj, mixer_SourceProp pname, + mixer_IntVal value) +{ + mixer_Source *src = (mixer_Source *) srcobj; + + if (!src) + { + mixer_SetError (MIX_INVALID_NAME); + log_add (log_Debug, "mixer_Sourcei() called with null source"); + return; + } + + LockRecursiveMutex (src_mutex); + + if (src->magic != mixer_srcMagic) + { + mixer_SetError (MIX_INVALID_NAME); + log_add (log_Debug, "mixer_Sourcei(): not a source"); + } + else + { + switch (pname) + { + case MIX_LOOPING: + src->looping = value; + break; + case MIX_BUFFER: + { + mixer_Buffer *buf = (mixer_Buffer *) value; + + if (src->cqueued > 0) + mixer_SourceUnqueueAll (src); + + if (buf && !mixer_CheckBufferState (buf, "mixer_Sourcei")) + break; + + src->firstqueued = buf; + src->nextqueued = src->firstqueued; + src->prevqueued = 0; + src->lastqueued = src->nextqueued; + if (src->lastqueued) + src->lastqueued->next = 0; + src->cqueued = 1; + } + break; + case MIX_SOURCE_STATE: + if (value == MIX_INITIAL) + { + mixer_SourceRewind_internal (src); + } + else + { + log_add (log_Debug, "mixer_Sourcei(MIX_SOURCE_STATE): " + "unsupported state, call ignored"); + } + break; + default: + mixer_SetError (MIX_INVALID_ENUM); + log_add (log_Debug, "mixer_Sourcei() called " + "with unsupported property %u", pname); + } + } + + UnlockRecursiveMutex (src_mutex); +} + +/* set source float property */ +void +mixer_Sourcef (mixer_Object srcobj, mixer_SourceProp pname, float value) +{ + mixer_Source *src = (mixer_Source *) srcobj; + + if (!src) + { + mixer_SetError (MIX_INVALID_NAME); + log_add (log_Debug, "mixer_Sourcef() called with null source"); + return; + } + + LockRecursiveMutex (src_mutex); + + if (src->magic != mixer_srcMagic) + { + mixer_SetError (MIX_INVALID_NAME); + log_add (log_Debug, "mixer_Sourcef(): not a source"); + } + else + { + switch (pname) + { + case MIX_GAIN: + src->gain = value * MIX_GAIN_ADJ; + break; + default: + log_add (log_Debug, "mixer_Sourcei() called " + "with unsupported property %u", pname); + } + } + + UnlockRecursiveMutex (src_mutex); +} + +/* set source float array property (CURRENTLY NOT IMPLEMENTED) */ +void mixer_Sourcefv (mixer_Object srcobj, mixer_SourceProp pname, float *value) +{ + (void)srcobj; + (void)pname; + (void)value; +} + + +/* get source integer property */ +void +mixer_GetSourcei (mixer_Object srcobj, mixer_SourceProp pname, + mixer_IntVal *value) +{ + mixer_Source *src = (mixer_Source *) srcobj; + + if (!src || !value) + { + mixer_SetError (src ? MIX_INVALID_VALUE : MIX_INVALID_NAME); + log_add (log_Debug, "mixer_GetSourcei() called with null param"); + return; + } + + LockRecursiveMutex (src_mutex); + + if (src->magic != mixer_srcMagic) + { + mixer_SetError (MIX_INVALID_NAME); + log_add (log_Debug, "mixer_GetSourcei(): not a source"); + } + else + { + switch (pname) + { + case MIX_LOOPING: + *value = src->looping; + break; + case MIX_BUFFER: + *value = (mixer_IntVal) src->firstqueued; + break; + case MIX_SOURCE_STATE: + *value = src->state; + break; + case MIX_BUFFERS_QUEUED: + *value = src->cqueued; + break; + case MIX_BUFFERS_PROCESSED: + *value = src->cprocessed; + break; + default: + mixer_SetError (MIX_INVALID_ENUM); + log_add (log_Debug, "mixer_GetSourcei() called " + "with unsupported property %u", pname); + } + } + + UnlockRecursiveMutex (src_mutex); +} + +/* get source float property */ +void +mixer_GetSourcef (mixer_Object srcobj, mixer_SourceProp pname, + float *value) +{ + mixer_Source *src = (mixer_Source *) srcobj; + + if (!src || !value) + { + mixer_SetError (src ? MIX_INVALID_VALUE : MIX_INVALID_NAME); + log_add (log_Debug, "mixer_GetSourcef() called with null param"); + return; + } + + LockRecursiveMutex (src_mutex); + + if (src->magic != mixer_srcMagic) + { + mixer_SetError (MIX_INVALID_NAME); + log_add (log_Debug, "mixer_GetSourcef(): not a source"); + } + else + { + switch (pname) + { + case MIX_GAIN: + *value = src->gain / MIX_GAIN_ADJ; + break; + default: + log_add (log_Debug, "mixer_GetSourcef() called " + "with unsupported property %u", pname); + } + } + + UnlockRecursiveMutex (src_mutex); +} + +/* start the source; add it to active array */ +void +mixer_SourcePlay (mixer_Object srcobj) +{ + mixer_Source *src = (mixer_Source *) srcobj; + + if (!src) + { + mixer_SetError (MIX_INVALID_NAME); + log_add (log_Debug, "mixer_SourcePlay() called with null source"); + return; + } + + LockRecursiveMutex (src_mutex); + + if (src->magic != mixer_srcMagic) + { + mixer_SetError (MIX_INVALID_NAME); + log_add (log_Debug, "mixer_SourcePlay(): not a source"); + } + else /* should make the source active */ + { + if (src->state < MIX_PLAYING) + { + if (src->firstqueued && !src->nextqueued) + mixer_SourceRewind_internal (src); + mixer_SourceActivate (src); + } + src->state = MIX_PLAYING; + } + + UnlockRecursiveMutex (src_mutex); +} + +/* stop the source; remove it from active array and requeue buffers */ +void +mixer_SourceRewind (mixer_Object srcobj) +{ + mixer_Source *src = (mixer_Source *) srcobj; + + if (!src) + { + mixer_SetError (MIX_INVALID_NAME); + log_add (log_Debug, "mixer_SourceRewind() called with null source"); + return; + } + + LockRecursiveMutex (src_mutex); + + if (src->magic != mixer_srcMagic) + { + mixer_SetError (MIX_INVALID_NAME); + log_add (log_Debug, "mixer_SourcePlay(): not a source"); + } + else + { + mixer_SourceRewind_internal (src); + } + + UnlockRecursiveMutex (src_mutex); +} + +/* pause the source; keep in active array */ +void +mixer_SourcePause (mixer_Object srcobj) +{ + mixer_Source *src = (mixer_Source *) srcobj; + + if (!src) + { + mixer_SetError (MIX_INVALID_NAME); + log_add (log_Debug, "mixer_SourcePause() called with null source"); + return; + } + + LockRecursiveMutex (src_mutex); + + if (src->magic != mixer_srcMagic) + { + mixer_SetError (MIX_INVALID_NAME); + log_add (log_Debug, "mixer_SourcePause(): not a source"); + } + else /* should keep all buffers and offsets */ + { + if (src->state < MIX_PLAYING) + mixer_SourceActivate (src); + src->state = MIX_PAUSED; + } + + UnlockRecursiveMutex (src_mutex); +} + +/* stop the source; remove it from active array + * and unqueue 'queued' buffers + */ +void +mixer_SourceStop (mixer_Object srcobj) +{ + mixer_Source *src = (mixer_Source *) srcobj; + + if (!src) + { + mixer_SetError (MIX_INVALID_NAME); + log_add (log_Debug, "mixer_SourceStop() called with null source"); + return; + } + + LockRecursiveMutex (src_mutex); + + if (src->magic != mixer_srcMagic) + { + mixer_SetError (MIX_INVALID_NAME); + log_add (log_Debug, "mixer_SourceStop(): not a source"); + } + else /* should remove queued buffers */ + { + if (src->state >= MIX_PLAYING) + mixer_SourceDeactivate (src); + mixer_SourceStop_internal (src); + src->state = MIX_STOPPED; + } + + UnlockRecursiveMutex (src_mutex); +} + +/* queue buffers on the source */ +void +mixer_SourceQueueBuffers (mixer_Object srcobj, uint32 n, + mixer_Object* pbufobj) +{ + uint32 i; + mixer_Object* pobj; + mixer_Source *src = (mixer_Source *) srcobj; + + if (!src || !pbufobj) + { + mixer_SetError (MIX_INVALID_NAME); + log_add (log_Debug, "mixer_SourceQueueBuffers() called " + "with null param"); + return; + } + + LockRecursiveMutex (buf_mutex); + /* check to make sure we can safely queue all buffers */ + for (i = n, pobj = pbufobj; i; i--, pobj++) + { + mixer_Buffer *buf = (mixer_Buffer *) *pobj; + if (!buf || !mixer_CheckBufferState (buf, + "mixer_SourceQueueBuffers")) + { + break; + } + } + UnlockRecursiveMutex (buf_mutex); + + if (i == 0) + { /* all buffers checked out */ + LockRecursiveMutex (src_mutex); + LockRecursiveMutex (buf_mutex); + + if (src->magic != mixer_srcMagic) + { + mixer_SetError (MIX_INVALID_NAME); + log_add (log_Debug, "mixer_SourceQueueBuffers(): not a source"); + } + else + { + for (i = n, pobj = pbufobj; i; i--, pobj++) + { + mixer_Buffer *buf = (mixer_Buffer *) *pobj; + + /* add buffer to the chain */ + if (src->lastqueued) + src->lastqueued->next = buf; + src->lastqueued = buf; + + if (!src->firstqueued) + { + src->firstqueued = buf; + src->nextqueued = buf; + src->prevqueued = 0; + } + src->cqueued++; + buf->state = MIX_BUF_QUEUED; + } + } + + UnlockRecursiveMutex (buf_mutex); + UnlockRecursiveMutex (src_mutex); + } +} + +/* unqueue buffers from the source */ +void +mixer_SourceUnqueueBuffers (mixer_Object srcobj, uint32 n, + mixer_Object* pbufobj) +{ + uint32 i; + mixer_Source *src = (mixer_Source *) srcobj; + mixer_Buffer *curbuf = 0; + + if (!src || !pbufobj) + { + mixer_SetError (MIX_INVALID_NAME); + log_add (log_Debug, "mixer_SourceUnqueueBuffers() called " + "with null source"); + return; + } + + LockRecursiveMutex (src_mutex); + + if (src->magic != mixer_srcMagic) + { + mixer_SetError (MIX_INVALID_NAME); + log_add (log_Debug, "mixer_SourceUnqueueBuffers(): not a source"); + } + else if (n > src->cqueued) + { + mixer_SetError (MIX_INVALID_OPERATION); + } + else + { + LockRecursiveMutex (buf_mutex); + + /* check to make sure we can unqueue all buffers */ + for (i = n, curbuf = src->firstqueued; + i && curbuf && curbuf->state != MIX_BUF_PLAYING; + i--, curbuf = curbuf->next) + ; + + if (i) + { + mixer_SetError (MIX_INVALID_OPERATION); + log_add (log_Debug, "mixer_SourceUnqueueBuffers(): " + "active buffer attempted"); + } + else + { /* all buffers checked out */ + for (i = n; i; i--, pbufobj++) + { + mixer_Buffer *buf = src->firstqueued; + + /* remove buffer from the chain */ + if (src->nextqueued == buf) + src->nextqueued = buf->next; + if (src->prevqueued == buf) + src->prevqueued = 0; + if (src->lastqueued == buf) + src->lastqueued = 0; + src->firstqueued = buf->next; + src->cqueued--; + + if (buf->state == MIX_BUF_PROCESSED) + src->cprocessed--; + + buf->state = MIX_BUF_FILLED; + buf->next = 0; + *pbufobj = (mixer_Object) buf; + } + } + + UnlockRecursiveMutex (buf_mutex); + } + + UnlockRecursiveMutex (src_mutex); +} + +/************************************************* + * Sources internals + */ + +static void +mixer_SourceUnqueueAll (mixer_Source *src) +{ + mixer_Buffer *buf; + mixer_Buffer *nextbuf; + + if (!src) + { + log_add (log_Debug, "mixer_SourceUnqueueAll() called " + "with null source"); + return; + } + + LockRecursiveMutex (buf_mutex); + + for (buf = src->firstqueued; buf; buf = nextbuf) + { + if (buf->state == MIX_BUF_PLAYING) + { + log_add (log_Debug, "mixer_SourceUnqueueAll(): " + "attempted on active buffer"); + } + nextbuf = buf->next; + buf->state = MIX_BUF_FILLED; + buf->next = 0; + } + + UnlockRecursiveMutex (buf_mutex); + + src->firstqueued = 0; + src->nextqueued = 0; + src->prevqueued = 0; + src->lastqueued = 0; + src->cqueued = 0; + src->cprocessed = 0; + src->pos = 0; + src->count = 0; +} + +/* add the source to the active array */ +static void +mixer_SourceActivate (mixer_Source* src) +{ + uint32 i; + + LockRecursiveMutex (act_mutex); + + /* check active sources, see if this source is there already */ + for (i = 0; i < MAX_SOURCES && active_sources[i] != src; i++) + ; + if (i < MAX_SOURCES) + { /* source found */ + log_add (log_Debug, "mixer_SourceActivate(): " + "source already active in slot %u", i); + UnlockRecursiveMutex (act_mutex); + return; + } + + /* find an empty slot */ + for (i = 0; i < MAX_SOURCES && active_sources[i] != 0; i++) + ; + if (i < MAX_SOURCES) + { /* slot found */ + active_sources[i] = src; + } + else + { + log_add (log_Debug, "mixer_SourceActivate(): " + "no more slots available (max=%d)", MAX_SOURCES); + } + + UnlockRecursiveMutex (act_mutex); +} + +/* remove the source from the active array */ +static void +mixer_SourceDeactivate (mixer_Source* src) +{ + uint32 i; + + LockRecursiveMutex (act_mutex); + + /* check active sources, see if this source is there */ + for (i = 0; i < MAX_SOURCES && active_sources[i] != src; i++) + ; + if (i < MAX_SOURCES) + { /* source found */ + active_sources[i] = 0; + } + else + { /* source not found */ + log_add (log_Debug, "mixer_SourceDeactivate(): source not active"); + } + + UnlockRecursiveMutex (act_mutex); +} + +static void +mixer_SourceStop_internal (mixer_Source *src) +{ + mixer_Buffer *buf; + mixer_Buffer *nextbuf; + + if (!src->firstqueued) + return; + + /* assert the source buffers state */ + if (!src->lastqueued) + { + log_add (log_Debug, "mixer_SourceStop_internal(): " + "desynced source state"); +#ifdef DEBUG + explode (); +#endif + } + + LockRecursiveMutex (buf_mutex); + + /* find last 'processed' buffer */ + for (buf = src->firstqueued; + buf && buf->next && buf->next != src->nextqueued; + buf = buf->next) + ; + src->lastqueued = buf; + if (buf) + buf->next = 0; /* break the chain */ + + /* unqueue all 'queued' buffers */ + for (buf = src->nextqueued; buf; buf = nextbuf) + { + nextbuf = buf->next; + buf->state = MIX_BUF_FILLED; + buf->next = 0; + src->cqueued--; + } + + if (src->cqueued == 0) + { /* all buffers were removed */ + src->firstqueued = 0; + src->lastqueued = 0; + } + src->nextqueued = 0; + src->prevqueued = 0; + src->pos = 0; + src->count = 0; + + UnlockRecursiveMutex (buf_mutex); +} + +static void +mixer_SourceRewind_internal (mixer_Source *src) +{ + /* should change the processed buffers to queued */ + mixer_Buffer *buf; + + if (src->state >= MIX_PLAYING) + mixer_SourceDeactivate (src); + + LockRecursiveMutex (buf_mutex); + + for (buf = src->firstqueued; + buf && buf->state != MIX_BUF_QUEUED; + buf = buf->next) + { + buf->state = MIX_BUF_QUEUED; + } + + UnlockRecursiveMutex (buf_mutex); + + src->pos = 0; + src->count = 0; + src->cprocessed = 0; + src->nextqueued = src->firstqueued; + src->prevqueued = 0; + src->state = MIX_INITIAL; +} + +/* get the sample next in queue in internal format */ +static inline bool +mixer_SourceGetNextSample (mixer_Source *src, float *psamp, bool left) +{ + /* fake the data if requested */ + if (mixer_flags & MIX_FAKE_DATA) + return mixer_SourceGetFakeSample (src, psamp, left); + + while (src->nextqueued) + { + mixer_Buffer *buf = src->nextqueued; + + if (!buf->data || buf->size < mixer_sampsize) + { + /* buffer invalid, go next */ + buf->state = MIX_BUF_PROCESSED; + src->pos = 0; + src->nextqueued = src->nextqueued->next; + src->cprocessed++; + continue; + } + + if (!left && buf->orgchannels == 1) + { + /* mono source so we can copy left channel to right */ + *psamp = src->samplecache; + } + else + { + *psamp = src->samplecache = buf->Resample(src, left) * src->gain; + } + + if (src->pos < buf->size || + (left && buf->sampsize != mixer_sampsize)) + { + buf->state = MIX_BUF_PLAYING; + } + else + { + /* buffer exhausted, go next */ + buf->state = MIX_BUF_PROCESSED; + src->pos = 0; + src->prevqueued = src->nextqueued; + src->nextqueued = src->nextqueued->next; + src->cprocessed++; + } + + return true; + } + + /* no more playable buffers */ + if (src->state >= MIX_PLAYING) + mixer_SourceDeactivate (src); + + src->state = MIX_STOPPED; + + return false; +} + +/* fake the next sample, but process buffers and states */ +static inline bool +mixer_SourceGetFakeSample (mixer_Source *src, float *psamp, bool left) +{ + while (src->nextqueued) + { + mixer_Buffer *buf = src->nextqueued; + + if (left || buf->orgchannels != 1) + { + if (mixer_freq == buf->orgfreq) + src->pos += mixer_chansize; + else + mixer_SourceAdvance(src, left); + } + *psamp = 0; + + if (src->pos < buf->size || + (left && buf->sampsize != mixer_sampsize)) + { + buf->state = MIX_BUF_PLAYING; + } + else + { + /* buffer exhausted, go next */ + buf->state = MIX_BUF_PROCESSED; + src->pos = 0; + src->prevqueued = src->nextqueued; + src->nextqueued = src->nextqueued->next; + src->cprocessed++; + } + + return true; + } + + /* no more playable buffers */ + if (src->state >= MIX_PLAYING) + mixer_SourceDeactivate (src); + + src->state = MIX_STOPPED; + + return false; +} + +/* advance position in currently queued buffer */ +static inline uint32 +mixer_SourceAdvance (mixer_Source *src, bool left) +{ + mixer_Buffer *curr = src->nextqueued; + if (curr->orgchannels == 2 && mixer_channels == 2) + { + if (!left) + { + src->pos += curr->high; + src->count += curr->low; + if (src->count > UINT16_MAX) + { + src->count -= UINT16_MAX; + src->pos += curr->sampsize; + } + return mixer_chansize; + } + } + else + { + src->pos += curr->high; + src->count += curr->low; + if (src->count > UINT16_MAX) + { + src->count -= UINT16_MAX; + src->pos += curr->sampsize; + } + } + return 0; +} + + +/************************************************* + * Buffers interface + */ + +/* generate n buffer objects */ +void +mixer_GenBuffers (uint32 n, mixer_Object *pbufobj) +{ + if (n == 0) + return; /* do nothing per OpenAL */ + + if (!pbufobj) + { + mixer_SetError (MIX_INVALID_VALUE); + log_add (log_Debug, "mixer_GenBuffers() called with null ptr"); + return; + } + for (; n; n--, pbufobj++) + { + mixer_Buffer *buf; + + buf = (mixer_Buffer *) HMalloc (sizeof (mixer_Buffer)); + buf->magic = mixer_bufMagic; + buf->locked = false; + buf->state = MIX_BUF_INITIAL; + buf->data = 0; + buf->size = 0; + buf->next = 0; + buf->orgdata = 0; + buf->orgfreq = 0; + buf->orgsize = 0; + buf->orgchannels = 0; + buf->orgchansize = 0; + + *pbufobj = (mixer_Object) buf; + } +} + +/* delete n buffer objects */ +void +mixer_DeleteBuffers (uint32 n, mixer_Object *pbufobj) +{ + uint32 i; + mixer_Object *pcurobj; + + if (n == 0) + return; /* do nothing per OpenAL */ + + if (!pbufobj) + { + mixer_SetError (MIX_INVALID_NAME); + log_add (log_Debug, "mixer_DeleteBuffers() called with null ptr"); + return; + } + + LockRecursiveMutex (buf_mutex); + + /* check to make sure we can delete all buffers */ + for (i = n, pcurobj = pbufobj; i && pcurobj; i--, pcurobj++) + { + mixer_Buffer *buf = (mixer_Buffer *) *pcurobj; + + if (!buf) + continue; + + if (buf->magic != mixer_bufMagic) + { + mixer_SetError (MIX_INVALID_NAME); + log_add (log_Debug, "mixer_DeleteBuffers(): not a buffer"); + break; + } + else if (buf->locked) + { + mixer_SetError (MIX_INVALID_OPERATION); + log_add (log_Debug, "mixer_DeleteBuffers(): locked buffer"); + break; + } + else if (buf->state >= MIX_BUF_QUEUED) + { + mixer_SetError (MIX_INVALID_OPERATION); + log_add (log_Debug, "mixer_DeleteBuffers(): " + "attempted on queued/active buffer"); + break; + } + } + + if (i == 0) + { + /* all buffers check out */ + for (; n; n--, pbufobj++) + { + mixer_Buffer *buf = (mixer_Buffer *) *pbufobj; + + if (!buf) + continue; + + if (buf->data) + HFree (buf->data); + HFree (buf); + + *pbufobj = 0; + } + } + UnlockRecursiveMutex (buf_mutex); +} + +/* check if really a buffer object */ +bool +mixer_IsBuffer (mixer_Object bufobj) +{ + mixer_Buffer *buf = (mixer_Buffer *) bufobj; + bool ret; + + if (!buf) + return false; + + LockRecursiveMutex (buf_mutex); + ret = buf->magic == mixer_bufMagic; + UnlockRecursiveMutex (buf_mutex); + + return ret; +} + +/* get buffer property */ +void +mixer_GetBufferi (mixer_Object bufobj, mixer_BufferProp pname, + mixer_IntVal *value) +{ + mixer_Buffer *buf = (mixer_Buffer *) bufobj; + + if (!buf || !value) + { + mixer_SetError (buf ? MIX_INVALID_VALUE : MIX_INVALID_NAME); + log_add (log_Debug, "mixer_GetBufferi() called with null param"); + return; + } + + LockRecursiveMutex (buf_mutex); + + if (buf->locked) + { + UnlockRecursiveMutex (buf_mutex); + mixer_SetError (MIX_INVALID_OPERATION); + log_add (log_Debug, "mixer_GetBufferi() called with locked buffer"); + return; + } + + if (buf->magic != mixer_bufMagic) + { + mixer_SetError (MIX_INVALID_NAME); + log_add (log_Debug, "mixer_GetBufferi(): not a buffer"); + } + else + { + /* Return original buffer values + */ + switch (pname) + { + case MIX_FREQUENCY: + *value = buf->orgfreq; + break; + case MIX_BITS: + *value = buf->orgchansize << 3; + break; + case MIX_CHANNELS: + *value = buf->orgchannels; + break; + case MIX_SIZE: + *value = buf->orgsize; + break; + case MIX_DATA: + *value = (mixer_IntVal) buf->orgdata; + break; + default: + mixer_SetError (MIX_INVALID_ENUM); + log_add (log_Debug, "mixer_GetBufferi() called " + "with invalid property %u", pname); + } + } + + UnlockRecursiveMutex (buf_mutex); +} + +/* fill buffer with external data */ +void +mixer_BufferData (mixer_Object bufobj, uint32 format, void* data, + uint32 size, uint32 freq) +{ + mixer_Buffer *buf = (mixer_Buffer *) bufobj; + mixer_Convertion conv; + uint32 dstsize; + + if (!buf || !data || !size) + { + mixer_SetError (buf ? MIX_INVALID_VALUE : MIX_INVALID_NAME); + log_add (log_Debug, "mixer_BufferData() called with bad param"); + return; + } + + LockRecursiveMutex (buf_mutex); + + if (buf->locked) + { + UnlockRecursiveMutex (buf_mutex); + mixer_SetError (MIX_INVALID_OPERATION); + log_add (log_Debug, "mixer_BufferData() called " + "with locked buffer"); + return; + } + + if (buf->magic != mixer_bufMagic) + { + mixer_SetError (MIX_INVALID_NAME); + log_add (log_Debug, "mixer_BufferData(): not a buffer"); + } + else if (buf->state > MIX_BUF_FILLED) + { + mixer_SetError (MIX_INVALID_OPERATION); + log_add (log_Debug, "mixer_BufferData() attempted " + "on in-use buffer"); + } + else + { + if (buf->data) + HFree (buf->data); + buf->data = 0; + buf->size = 0; + + /* Store original buffer values for OpenAL compatibility */ + buf->orgdata = data; + buf->orgfreq = freq; + buf->orgsize = size; + buf->orgchannels = MIX_FORMAT_CHANS (format); + buf->orgchansize = MIX_FORMAT_BPC (format); + + conv.srcsamples = conv.dstsamples = + size / MIX_FORMAT_SAMPSIZE (format); + + if (conv.dstsamples > + UINT32_MAX / MIX_FORMAT_SAMPSIZE (format)) + { + mixer_SetError (MIX_INVALID_VALUE); + } + else + { + if (MIX_FORMAT_CHANS (format) < MIX_FORMAT_CHANS (mixer_format)) + buf->sampsize = MIX_FORMAT_BPC (mixer_format) * + MIX_FORMAT_CHANS (format); + else + buf->sampsize = MIX_FORMAT_SAMPSIZE (mixer_format); + buf->size = dstsize = conv.dstsamples * buf->sampsize; + + /* only copy/convert the data if not faking */ + if (! (mixer_flags & MIX_FAKE_DATA)) + { + buf->data = HMalloc (dstsize); + + if (MIX_FORMAT_BPC (format) == MIX_FORMAT_BPC (mixer_format) && + MIX_FORMAT_CHANS (format) <= MIX_FORMAT_CHANS (mixer_format)) + { + /* format is compatible with internal */ + buf->locked = true; + UnlockRecursiveMutex (buf_mutex); + + memcpy (buf->data, data, size); + if (MIX_FORMAT_BPC (format) == 1) + { + /* convert buffer to S8 format internally */ + uint8* dst; + for (dst = buf->data; dstsize; dstsize--, dst++) + *dst ^= 0x80; + } + + LockRecursiveMutex (buf_mutex); + buf->locked = false; + } + else + { + /* needs convertion */ + conv.srcfmt = format; + conv.srcdata = data; + conv.srcsize = size; + + if (MIX_FORMAT_CHANS (format) < MIX_FORMAT_CHANS (mixer_format)) + conv.dstfmt = MIX_FORMAT_MAKE (mixer_chansize, + MIX_FORMAT_CHANS (format)); + else + conv.dstfmt = mixer_format; + conv.dstdata = buf->data; + conv.dstsize = dstsize; + + buf->locked = true; + UnlockRecursiveMutex (buf_mutex); + + mixer_ConvertBuffer_internal (&conv); + + LockRecursiveMutex (buf_mutex); + buf->locked = false; + } + } + + buf->state = MIX_BUF_FILLED; + buf->high = (buf->orgfreq / mixer_freq) * buf->sampsize; + buf->low = (((buf->orgfreq % mixer_freq) << 16) / mixer_freq); + if (mixer_freq == buf->orgfreq) + buf->Resample = mixer_resampling.None; + else if (mixer_freq > buf->orgfreq) + buf->Resample = mixer_resampling.Upsample; + else + buf->Resample = mixer_resampling.Downsample; + } + } + + UnlockRecursiveMutex (buf_mutex); +} + + +/************************************************* + * Buffer internals + */ + +static inline bool +mixer_CheckBufferState (mixer_Buffer *buf, const char* FuncName) +{ + if (!buf) + return false; + + if (buf->magic != mixer_bufMagic) + { + mixer_SetError (MIX_INVALID_NAME); + log_add (log_Debug, "%s(): not a buffer", FuncName); + return false; + } + + if (buf->locked) + { + mixer_SetError (MIX_INVALID_OPERATION); + log_add (log_Debug, "%s(): locked buffer attempted", FuncName); + return false; + } + + if (buf->state != MIX_BUF_FILLED) + { + mixer_SetError (MIX_INVALID_OPERATION); + log_add (log_Debug, "%s: invalid buffer attempted", FuncName); + return false; + } + return true; +} + +static void +mixer_ConvertBuffer_internal (mixer_Convertion *conv) +{ + conv->srcbpc = MIX_FORMAT_BPC (conv->srcfmt); + conv->srcchans = MIX_FORMAT_CHANS (conv->srcfmt); + conv->dstbpc = MIX_FORMAT_BPC (conv->dstfmt); + conv->dstchans = MIX_FORMAT_CHANS (conv->dstfmt); + + conv->flags = 0; + if (conv->srcbpc > conv->dstbpc) + conv->flags |= mixConvSizeDown; + else if (conv->srcbpc < conv->dstbpc) + conv->flags |= mixConvSizeUp; + if (conv->srcchans > conv->dstchans) + conv->flags |= mixConvStereoDown; + else if (conv->srcchans < conv->dstchans) + conv->flags |= mixConvStereoUp; + + mixer_ResampleFlat (conv); +} + +/************************************************* + * Resampling routines + */ + +/* get a sample from external buffer + * in internal format + */ +static inline sint32 +mixer_GetSampleExt (void *src, uint32 bpc) +{ + if (bpc == 2) + return *(sint16 *)src; + else + return (*(uint8 *)src) - 128; +} + +/* get a sample from internal buffer */ +static inline sint32 +mixer_GetSampleInt (void *src, uint32 bpc) +{ + if (bpc == 2) + return *(sint16 *)src; + else + return *(sint8 *)src; +} + +/* put a sample into an external buffer + * from internal format + */ +static inline void +mixer_PutSampleExt (void *dst, uint32 bpc, sint32 samp) +{ + if (bpc == 2) + *(sint16 *)dst = samp; + else + *(uint8 *)dst = samp ^ 0x80; +} + +/* put a sample into an internal buffer + * in internal format + */ +static inline void +mixer_PutSampleInt (void *dst, uint32 bpc, sint32 samp) +{ + if (bpc == 2) + *(sint16 *)dst = samp; + else + *(sint8 *)dst = samp; +} + +/* get a sample from source */ +static float +mixer_ResampleNone (mixer_Source *src, bool left) +{ + uint8 *d0 = src->nextqueued->data + src->pos; + src->pos += mixer_chansize; + (void) left; // satisfying compiler - unused arg + return mixer_GetSampleInt (d0, mixer_chansize); +} + +/* get a resampled (up/down) sample from source (nearest neighbor) */ +static float +mixer_ResampleNearest (mixer_Source *src, bool left) +{ + uint8 *d0 = src->nextqueued->data + src->pos; + d0 += mixer_SourceAdvance (src, left); + return mixer_GetSampleInt (d0, mixer_chansize); +} + +/* get an upsampled sample from source (linear interpolation) */ +static float +mixer_UpsampleLinear (mixer_Source *src, bool left) +{ + mixer_Buffer *curr = src->nextqueued; + mixer_Buffer *next = src->nextqueued->next; + uint8 *d0, *d1; + float s0, s1, t; + + t = src->count / 65536.0f; + d0 = curr->data + src->pos; + d0 += mixer_SourceAdvance (src, left); + + if (d0 + curr->sampsize >= curr->data + curr->size) + { + if (next && next->data && next->size >= curr->sampsize) + { + d1 = next->data; + if (!left) + d1 += mixer_chansize; + } + else + d1 = d0; + } + else + d1 = d0 + curr->sampsize; + + s0 = mixer_GetSampleInt (d0, mixer_chansize); + s1 = mixer_GetSampleInt (d1, mixer_chansize); + return s0 + t * (s1 - s0); +} + +/* get an upsampled sample from source (cubic interpolation) */ +static float +mixer_UpsampleCubic (mixer_Source *src, bool left) +{ + mixer_Buffer *prev = src->prevqueued; + mixer_Buffer *curr = src->nextqueued; + mixer_Buffer *next = src->nextqueued->next; + uint8 *d0, *d1, *d2, *d3; /* prev, curr, next, next + 1 */ + float t, t2, a, b, c, s0, s1, s2, s3; + + t = src->count / 65536.0f; + t2 = t * t; + d1 = curr->data + src->pos; + d1 += mixer_SourceAdvance (src, left); + + if (d1 - curr->sampsize < curr->data) + { + if (prev && prev->data && prev->size >= curr->sampsize) + { + d0 = prev->data + prev->size - curr->sampsize; + if (!left) + d0 += mixer_chansize; + } + else + d0 = d1; + } + else + d0 = d1 - curr->sampsize; + + if (d1 + curr->sampsize >= curr->data + curr->size) + { + if (next && next->data && next->size >= curr->sampsize * 2) + { + d2 = next->data; + if (!left) + d2 += mixer_chansize; + d3 = d2 + curr->sampsize; + } + else + d2 = d3 = d1; + } + else + { + d2 = d1 + curr->sampsize; + if (d2 + curr->sampsize >= curr->data + curr->size) + { + if (next && next->data && next->size >= curr->sampsize) + { + d3 = next->data; + if (!left) + d3 += mixer_chansize; + } + else + d3 = d2; + } + else + d3 = d2 + curr->sampsize; + } + + s0 = mixer_GetSampleInt (d0, mixer_chansize); + s1 = mixer_GetSampleInt (d1, mixer_chansize); + s2 = mixer_GetSampleInt (d2, mixer_chansize); + s3 = mixer_GetSampleInt (d3, mixer_chansize); + + a = (3.0f * (s1 - s2) - s0 + s3) * 0.5f; + b = 2.0f * s2 + s0 - ((5.0f * s1 + s3) * 0.5f); + c = (s2 - s0) * 0.5f; + + return a * t2 * t + b * t2 + c * t + s1; +} + +/* get next sample from external buffer + * in internal format, while performing + * convertion if necessary + */ +static inline sint32 +mixer_GetConvSample (uint8 **psrc, uint32 bpc, uint32 flags) +{ + sint32 samp; + + samp = mixer_GetSampleExt (*psrc, bpc); + *psrc += bpc; + if (flags & mixConvStereoDown) + { + /* downmix to mono - average up channels */ + samp = (samp + mixer_GetSampleExt (*psrc, bpc)) / 2; + *psrc += bpc; + } + + if (flags & mixConvSizeUp) + { + /* convert S8 to S16 */ + samp <<= 8; + } + else if (flags & mixConvSizeDown) + { + /* convert S16 to S8 + * if arithmetic shift is available to the compiler + * it will use it to optimize this + */ + samp /= 0x100; + } + + return samp; +} + +/* put next sample into an internal buffer + * in internal format, while performing + * convertion if necessary + */ +static inline void +mixer_PutConvSample (uint8 **pdst, uint32 bpc, uint32 flags, sint32 samp) +{ + mixer_PutSampleInt (*pdst, bpc, samp); + *pdst += bpc; + if (flags & mixConvStereoUp) + { + mixer_PutSampleInt (*pdst, bpc, samp); + *pdst += bpc; + } +} + +/* resampling with respect to sample size only */ +static void +mixer_ResampleFlat (mixer_Convertion *conv) +{ + mixer_ConvFlags flags = conv->flags; + uint8 *src = conv->srcdata; + uint8 *dst = conv->dstdata; + uint32 srcbpc = conv->srcbpc; + uint32 dstbpc = conv->dstbpc; + uint32 samples; + + samples = conv->srcsamples; + if ( !(conv->flags & (mixConvStereoUp | mixConvStereoDown))) + samples *= conv->srcchans; + + for (; samples; samples--) + { + sint32 samp; + + samp = mixer_GetConvSample (&src, srcbpc, flags); + mixer_PutConvSample (&dst, dstbpc, flags, samp); + } +} diff --git a/src/libs/sound/mixer/mixer.h b/src/libs/sound/mixer/mixer.h new file mode 100644 index 0000000..71e7878 --- /dev/null +++ b/src/libs/sound/mixer/mixer.h @@ -0,0 +1,274 @@ +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* Mixer for low-level sound output drivers + */ + +#ifndef LIBS_SOUND_MIXER_MIXER_H_ +#define LIBS_SOUND_MIXER_MIXER_H_ + +#include "config.h" +#include "types.h" +#include "endian_uqm.h" + +/** + * The interface heavily influenced by OpenAL + * to the point where you should use OpenAL's + * documentation when programming the mixer. + * (some source properties are not supported) + * + * EXCEPTION: You may not queue the same buffer + * on more than one source + */ + +#ifdef WORDS_BIGENDIAN +# define MIX_IS_BIG_ENDIAN true +# define MIX_WANT_BIG_ENDIAN true +#else +# define MIX_IS_BIG_ENDIAN false +# define MIX_WANT_BIG_ENDIAN false +#endif + +/** + * Mixer errors (see OpenAL errors) + */ +enum +{ + MIX_NO_ERROR = 0, + MIX_INVALID_NAME = 0xA001U, + MIX_INVALID_ENUM = 0xA002U, + MIX_INVALID_VALUE = 0xA003U, + MIX_INVALID_OPERATION = 0xA004U, + MIX_OUT_OF_MEMORY = 0xA005U, + + MIX_DRIVER_FAILURE = 0xA101U +}; + +/** + * Source properties (see OpenAL) + */ +typedef enum +{ + MIX_POSITION = 0x1004, + MIX_LOOPING = 0x1007, + MIX_BUFFER = 0x1009, + MIX_GAIN = 0x100A, + MIX_SOURCE_STATE = 0x1010, + + MIX_BUFFERS_QUEUED = 0x1015, + MIX_BUFFERS_PROCESSED = 0x1016 + +} mixer_SourceProp; + +/** + * Source state information + */ +typedef enum +{ + MIX_INITIAL = 0, + MIX_STOPPED, + MIX_PLAYING, + MIX_PAUSED, + +} mixer_SourceState; + +/** + * Sound buffer properties + */ +typedef enum +{ + MIX_FREQUENCY = 0x2001, + MIX_BITS = 0x2002, + MIX_CHANNELS = 0x2003, + MIX_SIZE = 0x2004, + MIX_DATA = 0x2005 + +} mixer_BufferProp; + +/** + * Buffer states: semi-private + */ +typedef enum +{ + MIX_BUF_INITIAL = 0, + MIX_BUF_FILLED, + MIX_BUF_QUEUED, + MIX_BUF_PLAYING, + MIX_BUF_PROCESSED + +} mixer_BufferState; + +/** Sound buffers: format specifier. + * bits 00..07: bytes per sample + * bits 08..15: channels + * bits 15..31: meaningless + */ +#define MIX_FORMAT_DUMMYID 0x00170000 +#define MIX_FORMAT_BPC(f) ((f) & 0xff) +#define MIX_FORMAT_CHANS(f) (((f) >> 8) & 0xff) +#define MIX_FORMAT_BPC_MAX 2 +#define MIX_FORMAT_CHANS_MAX 2 +#define MIX_FORMAT_MAKE(b, c) \ + ( MIX_FORMAT_DUMMYID | ((b) & 0xff) | (((c) & 0xff) << 8) ) + +#define MIX_FORMAT_SAMPSIZE(f) \ + ( MIX_FORMAT_BPC(f) * MIX_FORMAT_CHANS(f) ) + +typedef enum +{ + MIX_FORMAT_MONO8 = MIX_FORMAT_MAKE (1, 1), + MIX_FORMAT_STEREO8 = MIX_FORMAT_MAKE (1, 2), + MIX_FORMAT_MONO16 = MIX_FORMAT_MAKE (2, 1), + MIX_FORMAT_STEREO16 = MIX_FORMAT_MAKE (2, 2) + +} mixer_Format; + +typedef enum +{ + MIX_QUALITY_LOW = 0, + MIX_QUALITY_MEDIUM, + MIX_QUALITY_HIGH, + MIX_QUALITY_DEFAULT = MIX_QUALITY_MEDIUM, + MIX_QUALITY_COUNT + +} mixer_Quality; + +typedef enum +{ + MIX_NOFLAGS = 0, + MIX_FAKE_DATA = 1 +} mixer_Flags; + +/************************************************* + * Interface Types + */ + +typedef intptr_t mixer_Object; +typedef intptr_t mixer_IntVal; + +typedef struct _mixer_Source mixer_Source; + +typedef struct _mixer_Buffer +{ + uint32 magic; + bool locked; + mixer_BufferState state; + uint8 *data; + uint32 size; + uint32 sampsize; + uint32 high; + uint32 low; + float (* Resample) (mixer_Source *src, bool left); + /* original buffer values for OpenAL compat */ + void* orgdata; + uint32 orgfreq; + uint32 orgsize; + uint32 orgchannels; + uint32 orgchansize; + /* next buffer in chain */ + struct _mixer_Buffer *next; + +} mixer_Buffer; + +#define mixer_bufMagic 0x4258494DU /* MIXB in LSB */ + +struct _mixer_Source +{ + uint32 magic; + bool locked; + mixer_SourceState state; + bool looping; + float gain; + uint32 cqueued; + uint32 cprocessed; + mixer_Buffer *firstqueued; /* first buf in the queue */ + mixer_Buffer *nextqueued; /* next to play, or 0 */ + mixer_Buffer *prevqueued; /* previously played */ + mixer_Buffer *lastqueued; /* last in queue */ + uint32 pos; /* position in current buffer */ + uint32 count; /* fractional part of pos */ + + float samplecache; + +}; + +#define mixer_srcMagic 0x5358494DU /* MIXS in LSB */ + +/************************************************* + * General interface + */ +uint32 mixer_GetError (void); + +bool mixer_Init (uint32 frequency, uint32 format, mixer_Quality quality, + mixer_Flags flags); +void mixer_Uninit (void); +void mixer_MixChannels (void *userdata, uint8 *stream, sint32 len); +void mixer_MixFake (void *userdata, uint8 *stream, sint32 len); + +/************************************************* + * Sources + */ +void mixer_GenSources (uint32 n, mixer_Object *psrcobj); +void mixer_DeleteSources (uint32 n, mixer_Object *psrcobj); +bool mixer_IsSource (mixer_Object srcobj); +void mixer_Sourcei (mixer_Object srcobj, mixer_SourceProp pname, + mixer_IntVal value); +void mixer_Sourcef (mixer_Object srcobj, mixer_SourceProp pname, + float value); +void mixer_Sourcefv (mixer_Object srcobj, mixer_SourceProp pname, + float *value); +void mixer_GetSourcei (mixer_Object srcobj, mixer_SourceProp pname, + mixer_IntVal *value); +void mixer_GetSourcef (mixer_Object srcobj, mixer_SourceProp pname, + float *value); +void mixer_SourceRewind (mixer_Object srcobj); +void mixer_SourcePlay (mixer_Object srcobj); +void mixer_SourcePause (mixer_Object srcobj); +void mixer_SourceStop (mixer_Object srcobj); +void mixer_SourceQueueBuffers (mixer_Object srcobj, uint32 n, + mixer_Object* pbufobj); +void mixer_SourceUnqueueBuffers (mixer_Object srcobj, uint32 n, + mixer_Object* pbufobj); + +/************************************************* + * Buffers + */ +void mixer_GenBuffers (uint32 n, mixer_Object *pbufobj); +void mixer_DeleteBuffers (uint32 n, mixer_Object *pbufobj); +bool mixer_IsBuffer (mixer_Object bufobj); +void mixer_GetBufferi (mixer_Object bufobj, mixer_BufferProp pname, + mixer_IntVal *value); +void mixer_BufferData (mixer_Object bufobj, uint32 format, void* data, + uint32 size, uint32 freq); + + +/* Make sure the prop-value type is of suitable size + * it must be able to store both int and void* + * Adapted from SDL + * This will generate "negative subscript or subscript is too large" + * error during compile, if the actual size of a type is wrong + */ +#define MIX_COMPILE_TIME_ASSERT(name, x) \ + typedef int mixer_dummy_##name [(x) * 2 - 1] + +MIX_COMPILE_TIME_ASSERT (mixer_Object, + sizeof(mixer_Object) >= sizeof(void*)); +MIX_COMPILE_TIME_ASSERT (mixer_IntVal, + sizeof(mixer_IntVal) >= sizeof(mixer_Object)); + +#undef MIX_COMPILE_TIME_ASSERT + +#endif /* LIBS_SOUND_MIXER_MIXER_H_ */ diff --git a/src/libs/sound/mixer/mixerint.h b/src/libs/sound/mixer/mixerint.h new file mode 100644 index 0000000..0605161 --- /dev/null +++ b/src/libs/sound/mixer/mixerint.h @@ -0,0 +1,110 @@ +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* Mixer for low-level sound output drivers + * Internals + */ + +#ifndef LIBS_SOUND_MIXER_MIXERINT_H_ +#define LIBS_SOUND_MIXER_MIXERINT_H_ + +#include "port.h" +#include "types.h" + +/************************************************* + * Internals + */ + +/* Conversion info types and funcs */ +typedef enum +{ + mixConvNone = 0, + mixConvStereoUp = 1, + mixConvStereoDown = 2, + mixConvSizeUp = 4, + mixConvSizeDown = 8 + +} mixer_ConvFlags; + +typedef struct +{ + uint32 srcfmt; + void *srcdata; + uint32 srcsize; + uint32 srcbpc; /* bytes/sample for 1 chan */ + uint32 srcchans; + uint32 srcsamples; + + uint32 dstfmt; + void *dstdata; + uint32 dstsize; + uint32 dstbpc; /* bytes/sample for 1 chan */ + uint32 dstchans; + uint32 dstsamples; + + mixer_ConvFlags flags; + +} mixer_Convertion; + +typedef struct +{ + float (* Upsample) (mixer_Source *src, bool left); + float (* Downsample) (mixer_Source *src, bool left); + float (* None) (mixer_Source *src, bool left); +} mixer_Resampling; + +static void mixer_ConvertBuffer_internal (mixer_Convertion *conv); +static void mixer_ResampleFlat (mixer_Convertion *conv); + +static inline sint32 mixer_GetSampleExt (void *src, uint32 bpc); +static inline sint32 mixer_GetSampleInt (void *src, uint32 bpc); +static inline void mixer_PutSampleInt (void *dst, uint32 bpc, + sint32 samp); +static inline void mixer_PutSampleExt (void *dst, uint32 bpc, + sint32 samp); + +static float mixer_ResampleNone (mixer_Source *src, bool left); +static float mixer_ResampleNearest (mixer_Source *src, bool left); +static float mixer_UpsampleLinear (mixer_Source *src, bool left); +static float mixer_UpsampleCubic (mixer_Source *src, bool left); + +/* Source manipulation */ +static void mixer_SourceUnqueueAll (mixer_Source *src); +static void mixer_SourceStop_internal (mixer_Source *src); +static void mixer_SourceRewind_internal (mixer_Source *src); +static void mixer_SourceActivate (mixer_Source* src); +static void mixer_SourceDeactivate (mixer_Source* src); + +static inline bool mixer_CheckBufferState (mixer_Buffer *buf, + const char* FuncName); + +/* Clipping boundaries */ +#define MIX_S16_MAX ((float) SINT16_MAX) +#define MIX_S16_MIN ((float) SINT16_MIN) +#define MIX_S8_MAX ((float) SINT8_MAX) +#define MIX_S8_MIN ((float) SINT8_MIN) + +/* Channel gain adjustment for clipping reduction */ +#define MIX_GAIN_ADJ (0.75f) + +/* The Mixer */ +static inline bool mixer_SourceGetNextSample (mixer_Source *src, + float *psamp, bool left); +static inline bool mixer_SourceGetFakeSample (mixer_Source *src, + float *psamp, bool left); +static inline uint32 mixer_SourceAdvance (mixer_Source *src, bool left); + +#endif /* LIBS_SOUND_MIXER_MIXERINT_H_ */ diff --git a/src/libs/sound/mixer/nosound/Makeinfo b/src/libs/sound/mixer/nosound/Makeinfo new file mode 100644 index 0000000..17f0c34 --- /dev/null +++ b/src/libs/sound/mixer/nosound/Makeinfo @@ -0,0 +1,2 @@ +uqm_CFILES="audiodrv_nosound.c" +uqm_HFILES="audiodrv_nosound.h" diff --git a/src/libs/sound/mixer/nosound/audiodrv_nosound.c b/src/libs/sound/mixer/nosound/audiodrv_nosound.c new file mode 100644 index 0000000..005bb44 --- /dev/null +++ b/src/libs/sound/mixer/nosound/audiodrv_nosound.c @@ -0,0 +1,410 @@ +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* Nosound audio driver + */ + +#include "audiodrv_nosound.h" +#include "../../sndintrn.h" +#include "libs/tasklib.h" +#include "libs/log.h" +#include "libs/memlib.h" +#include <stdlib.h> + + +static Task PlaybackTask; +static uint32 nosound_freq = 22050; + +static const audio_Driver noSound_Driver = +{ + noSound_Uninit, + noSound_GetError, + audio_DRIVER_NOSOUND, + { + /* Errors */ + MIX_NO_ERROR, + MIX_INVALID_NAME, + MIX_INVALID_ENUM, + MIX_INVALID_VALUE, + MIX_INVALID_OPERATION, + MIX_OUT_OF_MEMORY, + MIX_DRIVER_FAILURE, + + /* Source properties */ + MIX_POSITION, + MIX_LOOPING, + MIX_BUFFER, + MIX_GAIN, + MIX_SOURCE_STATE, + MIX_BUFFERS_QUEUED, + MIX_BUFFERS_PROCESSED, + + /* Source state information */ + MIX_INITIAL, + MIX_STOPPED, + MIX_PLAYING, + MIX_PAUSED, + + /* Sound buffer properties */ + MIX_FREQUENCY, + MIX_BITS, + MIX_CHANNELS, + MIX_SIZE, + MIX_FORMAT_MONO16, + MIX_FORMAT_STEREO16, + MIX_FORMAT_MONO8, + MIX_FORMAT_STEREO8 + }, + + /* Sources */ + noSound_GenSources, + noSound_DeleteSources, + noSound_IsSource, + noSound_Sourcei, + noSound_Sourcef, + noSound_Sourcefv, + noSound_GetSourcei, + noSound_GetSourcef, + noSound_SourceRewind, + noSound_SourcePlay, + noSound_SourcePause, + noSound_SourceStop, + noSound_SourceQueueBuffers, + noSound_SourceUnqueueBuffers, + + /* Buffers */ + noSound_GenBuffers, + noSound_DeleteBuffers, + noSound_IsBuffer, + noSound_GetBufferi, + noSound_BufferData +}; + + +/* + * Initialization + */ + +sint32 +noSound_Init (audio_Driver *driver, sint32 flags) +{ + int i; + TFB_DecoderFormats formats = + { + 0, 0, + audio_FORMAT_MONO8, audio_FORMAT_STEREO8, + audio_FORMAT_MONO16, audio_FORMAT_STEREO16 + }; + + log_add (log_Info, "Using nosound audio driver."); + log_add (log_Info, "Initializing mixer."); + + if (!mixer_Init (nosound_freq, MIX_FORMAT_MAKE (1, 1), + MIX_QUALITY_LOW, MIX_FAKE_DATA)) + { + log_add (log_Error, "Mixer initialization failed: %x", + mixer_GetError ()); + return -1; + } + log_add (log_Info, "Mixer initialized."); + + log_add (log_Info, "Initializing sound decoders."); + if (SoundDecoder_Init (flags, &formats)) + { + log_add (log_Error, "Sound decoders initialization failed."); + mixer_Uninit (); + return -1; + } + log_add (log_Info, "Sound decoders initialized."); + + *driver = noSound_Driver; + for (i = 0; i < NUM_SOUNDSOURCES; ++i) + { + audio_GenSources (1, &soundSource[i].handle); + soundSource[i].stream_mutex = CreateMutex ("Nosound stream mutex", SYNC_CLASS_AUDIO); + } + + if (InitStreamDecoder ()) + { + log_add (log_Error, "Stream decoder initialization failed."); + // TODO: cleanup source mutexes [or is it "muti"? :) ] + SoundDecoder_Uninit (); + mixer_Uninit (); + return -1; + } + + PlaybackTask = AssignTask (PlaybackTaskFunc, 1024, + "nosound audio playback"); + + return 0; +} + +void +noSound_Uninit (void) +{ + int i; + + UninitStreamDecoder (); + + for (i = 0; i < NUM_SOUNDSOURCES; ++i) + { + if (soundSource[i].sample && soundSource[i].sample->decoder) + { + StopStream (i); + } + if (soundSource[i].sbuffer) + { + void *sbuffer = soundSource[i].sbuffer; + soundSource[i].sbuffer = NULL; + HFree (sbuffer); + } + DestroyMutex (soundSource[i].stream_mutex); + + noSound_DeleteSources (1, &soundSource[i].handle); + } + + if (PlaybackTask) + { + ConcludeTask (PlaybackTask); + PlaybackTask = 0; + } + + mixer_Uninit (); + SoundDecoder_Uninit (); +} + + +/* + * Playback task + */ + +int +PlaybackTaskFunc (void *data) +{ + Task task = (Task)data; + uint8 *stream; + uint32 entryTime; + sint32 period, delay; + uint32 len = 2048; + + stream = (uint8 *) HMalloc (len); + period = (sint32)((len / (double)nosound_freq) * ONE_SECOND); + + while (!Task_ReadState (task, TASK_EXIT)) + { + entryTime = GetTimeCounter (); + mixer_MixFake (NULL, stream, len); + delay = period - (GetTimeCounter () - entryTime); + if (delay > 0) + HibernateThread (delay); + } + + HFree (stream); + FinishTask (task); + return 0; +} + + +/* + * General + */ + +sint32 +noSound_GetError (void) +{ + sint32 value = mixer_GetError (); + switch (value) + { + case MIX_NO_ERROR: + return audio_NO_ERROR; + case MIX_INVALID_NAME: + return audio_INVALID_NAME; + case MIX_INVALID_ENUM: + return audio_INVALID_ENUM; + case MIX_INVALID_VALUE: + return audio_INVALID_VALUE; + case MIX_INVALID_OPERATION: + return audio_INVALID_OPERATION; + case MIX_OUT_OF_MEMORY: + return audio_OUT_OF_MEMORY; + default: + log_add (log_Debug, "noSound_GetError: unknown value %x", + value); + return audio_DRIVER_FAILURE; + break; + } +} + + +/* + * Sources + */ + +void +noSound_GenSources (uint32 n, audio_Object *psrcobj) +{ + mixer_GenSources (n, (mixer_Object *) psrcobj); +} + +void +noSound_DeleteSources (uint32 n, audio_Object *psrcobj) +{ + mixer_DeleteSources (n, (mixer_Object *) psrcobj); +} + +bool +noSound_IsSource (audio_Object srcobj) +{ + return mixer_IsSource ((mixer_Object) srcobj); +} + +void +noSound_Sourcei (audio_Object srcobj, audio_SourceProp pname, + audio_IntVal value) + +{ + mixer_Sourcei ((mixer_Object) srcobj, (mixer_SourceProp) pname, + (mixer_IntVal) value); +} + +void +noSound_Sourcef (audio_Object srcobj, audio_SourceProp pname, + float value) +{ + mixer_Sourcef ((mixer_Object) srcobj, (mixer_SourceProp) pname, value); +} + +void +noSound_Sourcefv (audio_Object srcobj, audio_SourceProp pname, + float *value) +{ + mixer_Sourcefv ((mixer_Object) srcobj, (mixer_SourceProp) pname, value); +} + +void +noSound_GetSourcei (audio_Object srcobj, audio_SourceProp pname, + audio_IntVal *value) +{ + mixer_GetSourcei ((mixer_Object) srcobj, (mixer_SourceProp) pname, + (mixer_IntVal *) value); + if (pname == MIX_SOURCE_STATE) + { + switch (*value) + { + case MIX_INITIAL: + *value = audio_INITIAL; + break; + case MIX_STOPPED: + *value = audio_STOPPED; + break; + case MIX_PLAYING: + *value = audio_PLAYING; + break; + case MIX_PAUSED: + *value = audio_PAUSED; + break; + default: + log_add (log_Debug, "noSound_GetSourcei(): unknown value %lx", + (long int) *value); + *value = audio_DRIVER_FAILURE; + } + } +} + +void +noSound_GetSourcef (audio_Object srcobj, audio_SourceProp pname, + float *value) +{ + mixer_GetSourcef ((mixer_Object) srcobj, (mixer_SourceProp) pname, value); +} + +void +noSound_SourceRewind (audio_Object srcobj) +{ + mixer_SourceRewind ((mixer_Object) srcobj); +} + +void +noSound_SourcePlay (audio_Object srcobj) +{ + mixer_SourcePlay ((mixer_Object) srcobj); +} + +void +noSound_SourcePause (audio_Object srcobj) +{ + mixer_SourcePause ((mixer_Object) srcobj); +} + +void +noSound_SourceStop (audio_Object srcobj) +{ + mixer_SourceStop ((mixer_Object) srcobj); +} + +void +noSound_SourceQueueBuffers (audio_Object srcobj, uint32 n, + audio_Object* pbufobj) +{ + mixer_SourceQueueBuffers ((mixer_Object) srcobj, n, + (mixer_Object *) pbufobj); +} + +void +noSound_SourceUnqueueBuffers (audio_Object srcobj, uint32 n, + audio_Object* pbufobj) +{ + mixer_SourceUnqueueBuffers ((mixer_Object) srcobj, n, + (mixer_Object *) pbufobj); +} + + +/* + * Buffers + */ + +void +noSound_GenBuffers (uint32 n, audio_Object *pbufobj) +{ + mixer_GenBuffers (n, (mixer_Object *) pbufobj); +} + +void +noSound_DeleteBuffers (uint32 n, audio_Object *pbufobj) +{ + mixer_DeleteBuffers (n, (mixer_Object *) pbufobj); +} + +bool +noSound_IsBuffer (audio_Object bufobj) +{ + return mixer_IsBuffer ((mixer_Object) bufobj); +} + +void +noSound_GetBufferi (audio_Object bufobj, audio_BufferProp pname, + audio_IntVal *value) +{ + mixer_GetBufferi ((mixer_Object) bufobj, (mixer_BufferProp) pname, + (mixer_IntVal *) value); +} + +void +noSound_BufferData (audio_Object bufobj, uint32 format, void* data, + uint32 size, uint32 freq) +{ + mixer_BufferData ((mixer_Object) bufobj, format, data, size, freq); +} diff --git a/src/libs/sound/mixer/nosound/audiodrv_nosound.h b/src/libs/sound/mixer/nosound/audiodrv_nosound.h new file mode 100644 index 0000000..173b706 --- /dev/null +++ b/src/libs/sound/mixer/nosound/audiodrv_nosound.h @@ -0,0 +1,69 @@ +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* Nosound audio driver + */ + +#ifndef LIBS_SOUND_MIXER_NOSOUND_AUDIODRV_NOSOUND_H_ +#define LIBS_SOUND_MIXER_NOSOUND_AUDIODRV_NOSOUND_H_ + +#include "config.h" +#include "libs/sound/sound.h" +#include "libs/sound/mixer/mixer.h" + + +/* Playback task */ +int PlaybackTaskFunc (void *data); + +/* General */ +sint32 noSound_Init (audio_Driver *driver, sint32 flags); +void noSound_Uninit (void); +sint32 noSound_GetError (void); + +/* Sources */ +void noSound_GenSources (uint32 n, audio_Object *psrcobj); +void noSound_DeleteSources (uint32 n, audio_Object *psrcobj); +bool noSound_IsSource (audio_Object srcobj); +void noSound_Sourcei (audio_Object srcobj, audio_SourceProp pname, + audio_IntVal value); +void noSound_Sourcef (audio_Object srcobj, audio_SourceProp pname, + float value); +void noSound_Sourcefv (audio_Object srcobj, audio_SourceProp pname, + float *value); +void noSound_GetSourcei (audio_Object srcobj, audio_SourceProp pname, + audio_IntVal *value); +void noSound_GetSourcef (audio_Object srcobj, audio_SourceProp pname, + float *value); +void noSound_SourceRewind (audio_Object srcobj); +void noSound_SourcePlay (audio_Object srcobj); +void noSound_SourcePause (audio_Object srcobj); +void noSound_SourceStop (audio_Object srcobj); +void noSound_SourceQueueBuffers (audio_Object srcobj, uint32 n, + audio_Object* pbufobj); +void noSound_SourceUnqueueBuffers (audio_Object srcobj, uint32 n, + audio_Object* pbufobj); + +/* Buffers */ +void noSound_GenBuffers (uint32 n, audio_Object *pbufobj); +void noSound_DeleteBuffers (uint32 n, audio_Object *pbufobj); +bool noSound_IsBuffer (audio_Object bufobj); +void noSound_GetBufferi (audio_Object bufobj, audio_BufferProp pname, + audio_IntVal *value); +void noSound_BufferData (audio_Object bufobj, uint32 format, void* data, + uint32 size, uint32 freq); + + +#endif /* LIBS_SOUND_MIXER_NOSOUND_AUDIODRV_NOSOUND_H_ */ diff --git a/src/libs/sound/mixer/sdl/Makeinfo b/src/libs/sound/mixer/sdl/Makeinfo new file mode 100644 index 0000000..64c22c0 --- /dev/null +++ b/src/libs/sound/mixer/sdl/Makeinfo @@ -0,0 +1,2 @@ +uqm_CFILES="audiodrv_sdl.c" +uqm_HFILES="audiodrv_sdl.h" diff --git a/src/libs/sound/mixer/sdl/audiodrv_sdl.c b/src/libs/sound/mixer/sdl/audiodrv_sdl.c new file mode 100644 index 0000000..7ef522e --- /dev/null +++ b/src/libs/sound/mixer/sdl/audiodrv_sdl.c @@ -0,0 +1,486 @@ +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* SDL audio driver + */ + +#include "audiodrv_sdl.h" +#include "../../sndintrn.h" +#include "libs/log.h" +#include "libs/memlib.h" +#include <stdlib.h> + + +/* SDL2 wants to talk to a specific device. We'll let SDL1 use the same + * function names and just throw the device argument away. */ +#if SDL_MAJOR_VERSION > 1 +static SDL_AudioDeviceID dev; +#else +#define SDL_CloseAudioDevice(x) SDL_CloseAudio () +#define SDL_PauseAudioDevice(x, y) SDL_PauseAudio (y) +#endif +static const audio_Driver mixSDL_Driver = +{ + mixSDL_Uninit, + mixSDL_GetError, + audio_DRIVER_MIXSDL, + { + /* Errors */ + MIX_NO_ERROR, + MIX_INVALID_NAME, + MIX_INVALID_ENUM, + MIX_INVALID_VALUE, + MIX_INVALID_OPERATION, + MIX_OUT_OF_MEMORY, + MIX_DRIVER_FAILURE, + + /* Source properties */ + MIX_POSITION, + MIX_LOOPING, + MIX_BUFFER, + MIX_GAIN, + MIX_SOURCE_STATE, + MIX_BUFFERS_QUEUED, + MIX_BUFFERS_PROCESSED, + + /* Source state information */ + MIX_INITIAL, + MIX_STOPPED, + MIX_PLAYING, + MIX_PAUSED, + + /* Sound buffer properties */ + MIX_FREQUENCY, + MIX_BITS, + MIX_CHANNELS, + MIX_SIZE, + MIX_FORMAT_MONO16, + MIX_FORMAT_STEREO16, + MIX_FORMAT_MONO8, + MIX_FORMAT_STEREO8 + }, + + /* Sources */ + mixSDL_GenSources, + mixSDL_DeleteSources, + mixSDL_IsSource, + mixSDL_Sourcei, + mixSDL_Sourcef, + mixSDL_Sourcefv, + mixSDL_GetSourcei, + mixSDL_GetSourcef, + mixSDL_SourceRewind, + mixSDL_SourcePlay, + mixSDL_SourcePause, + mixSDL_SourceStop, + mixSDL_SourceQueueBuffers, + mixSDL_SourceUnqueueBuffers, + + /* Buffers */ + mixSDL_GenBuffers, + mixSDL_DeleteBuffers, + mixSDL_IsBuffer, + mixSDL_GetBufferi, + mixSDL_BufferData +}; + + +static void audioCallback (void *userdata, Uint8 *stream, int len); + +/* + * Initialization + */ + +sint32 +mixSDL_Init (audio_Driver *driver, sint32 flags) +{ + int i; + SDL_AudioSpec desired, obtained; + mixer_Quality quality; + TFB_DecoderFormats formats = + { + MIX_IS_BIG_ENDIAN, MIX_WANT_BIG_ENDIAN, + audio_FORMAT_MONO8, audio_FORMAT_STEREO8, + audio_FORMAT_MONO16, audio_FORMAT_STEREO16 + }; + + log_add (log_Info, "Initializing SDL audio subsystem."); + if ((SDL_InitSubSystem(SDL_INIT_AUDIO)) == -1) + { + log_add (log_Error, "Couldn't initialize audio subsystem: %s", + SDL_GetError()); + return -1; + } + log_add (log_Info, "SDL audio subsystem initialized."); + + if (flags & audio_QUALITY_HIGH) + { + quality = MIX_QUALITY_HIGH; + desired.freq = 44100; + desired.samples = 4096; + } + else if (flags & audio_QUALITY_LOW) + { + quality = MIX_QUALITY_LOW; +#ifdef __SYMBIAN32__ + desired.freq = 11025; + desired.samples = 4096; +#else + desired.freq = 22050; + desired.samples = 2048; +#endif + } + else + { + quality = MIX_QUALITY_DEFAULT; + desired.freq = 44100; + desired.samples = 4096; + } + + desired.format = AUDIO_S16SYS; + desired.channels = 2; + desired.callback = audioCallback; + + log_add (log_Info, "Opening SDL audio device."); +#if SDL_MAJOR_VERSION > 1 + dev = SDL_OpenAudioDevice (NULL, 0, &desired, &obtained, + SDL_AUDIO_ALLOW_FREQUENCY_CHANGE | SDL_AUDIO_ALLOW_CHANNELS_CHANGE +#ifdef SDL_AUDIO_ALLOW_SAMPLES_CHANGE + | SDL_AUDIO_ALLOW_SAMPLES_CHANGE +#endif + ); + if (dev != 0 && obtained.channels != 1 && obtained.channels != 2) + { + /* Try again without SDL_AUDIO_ALLOW_CHANNELS_CHANGE + * in case the device only supports >2 channels for some + * reason */ + SDL_CloseAudioDevice (dev); + dev = SDL_OpenAudioDevice (NULL, 0, &desired, &obtained, + SDL_AUDIO_ALLOW_FREQUENCY_CHANGE +#ifdef SDL_AUDIO_ALLOW_SAMPLES_CHANGE + | SDL_AUDIO_ALLOW_SAMPLES_CHANGE +#endif + ); + } + if (dev == 0) +#else + if (SDL_OpenAudio (&desired, &obtained) < 0) +#endif + { + log_add (log_Error, "Unable to open audio device: %s", + SDL_GetError ()); + SDL_QuitSubSystem (SDL_INIT_AUDIO); + return -1; + } + if (obtained.format != desired.format || + (obtained.channels != 1 && obtained.channels != 2)) + { + log_add (log_Error, "Unable to obtain desired audio format."); + SDL_CloseAudio (); + SDL_QuitSubSystem (SDL_INIT_AUDIO); + return -1; + } + + { +#if SDL_MAJOR_VERSION == 1 + char devicename[256]; + SDL_AudioDriverName (devicename, sizeof (devicename)); +#else + const char *devicename = SDL_GetCurrentAudioDriver (); +#endif + log_add (log_Info, " using %s at %d Hz 16 bit %s, " + "%d samples audio buffer", + devicename, obtained.freq, + obtained.channels > 1 ? "stereo" : "mono", + obtained.samples); + } + + log_add (log_Info, "Initializing mixer."); + if (!mixer_Init (obtained.freq, MIX_FORMAT_MAKE (2, obtained.channels), + quality, 0)) + { + log_add (log_Error, "Mixer initialization failed: %x", + mixer_GetError ()); + SDL_CloseAudioDevice (dev); + SDL_QuitSubSystem (SDL_INIT_AUDIO); + return -1; + } + log_add (log_Info, "Mixer initialized."); + + log_add (log_Info, "Initializing sound decoders."); + if (SoundDecoder_Init (flags, &formats)) + { + log_add (log_Error, "Sound decoders initialization failed."); + SDL_CloseAudioDevice (dev); + mixer_Uninit (); + SDL_QuitSubSystem (SDL_INIT_AUDIO); + return -1; + } + log_add (log_Info, "Sound decoders initialized."); + + *driver = mixSDL_Driver; + for (i = 0; i < NUM_SOUNDSOURCES; ++i) + { + audio_GenSources (1, &soundSource[i].handle); + soundSource[i].stream_mutex = CreateMutex ("MixSDL stream mutex", SYNC_CLASS_AUDIO); + } + + if (InitStreamDecoder ()) + { + log_add (log_Error, "Stream decoder initialization failed."); + // TODO: cleanup source mutexes [or is it "muti"? :) ] + SDL_CloseAudioDevice (dev); + SoundDecoder_Uninit (); + mixer_Uninit (); + SDL_QuitSubSystem (SDL_INIT_AUDIO); + return -1; + } + + atexit (unInitAudio); + + SetSFXVolume (sfxVolumeScale); + SetSpeechVolume (speechVolumeScale); + SetMusicVolume ((COUNT)musicVolume); + + SDL_PauseAudioDevice (dev, 0); + + return 0; +} + +void +mixSDL_Uninit (void) +{ + int i; + + UninitStreamDecoder (); + + for (i = 0; i < NUM_SOUNDSOURCES; ++i) + { + if (soundSource[i].sample && soundSource[i].sample->decoder) + { + StopStream (i); + } + if (soundSource[i].sbuffer) + { + void *sbuffer = soundSource[i].sbuffer; + soundSource[i].sbuffer = NULL; + HFree (sbuffer); + } + DestroyMutex (soundSource[i].stream_mutex); + soundSource[i].stream_mutex = 0; + + mixSDL_DeleteSources (1, &soundSource[i].handle); + } + + SDL_CloseAudioDevice (dev); + mixer_Uninit (); + SoundDecoder_Uninit (); + SDL_QuitSubSystem (SDL_INIT_AUDIO); +} + +static void +audioCallback (void *userdata, Uint8 *stream, int len) +{ + mixer_MixChannels (userdata, stream, len); +} + +/* + * General + */ + +sint32 +mixSDL_GetError (void) +{ + sint32 value = mixer_GetError (); + switch (value) + { + case MIX_NO_ERROR: + return audio_NO_ERROR; + case MIX_INVALID_NAME: + return audio_INVALID_NAME; + case MIX_INVALID_ENUM: + return audio_INVALID_ENUM; + case MIX_INVALID_VALUE: + return audio_INVALID_VALUE; + case MIX_INVALID_OPERATION: + return audio_INVALID_OPERATION; + case MIX_OUT_OF_MEMORY: + return audio_OUT_OF_MEMORY; + default: + log_add (log_Debug, "mixSDL_GetError: unknown value %x", value); + return audio_DRIVER_FAILURE; + break; + } +} + + +/* + * Sources + */ + +void +mixSDL_GenSources (uint32 n, audio_Object *psrcobj) +{ + mixer_GenSources (n, (mixer_Object *) psrcobj); +} + +void +mixSDL_DeleteSources (uint32 n, audio_Object *psrcobj) +{ + mixer_DeleteSources (n, (mixer_Object *) psrcobj); +} + +bool +mixSDL_IsSource (audio_Object srcobj) +{ + return mixer_IsSource ((mixer_Object) srcobj); +} + +void +mixSDL_Sourcei (audio_Object srcobj, audio_SourceProp pname, + audio_IntVal value) + +{ + mixer_Sourcei ((mixer_Object) srcobj, (mixer_SourceProp) pname, + (mixer_IntVal) value); +} + +void +mixSDL_Sourcef (audio_Object srcobj, audio_SourceProp pname, + float value) +{ + mixer_Sourcef ((mixer_Object) srcobj, (mixer_SourceProp) pname, value); +} + +void +mixSDL_Sourcefv (audio_Object srcobj, audio_SourceProp pname, + float *value) +{ + mixer_Sourcefv ((mixer_Object) srcobj, (mixer_SourceProp) pname, value); +} + +void +mixSDL_GetSourcei (audio_Object srcobj, audio_SourceProp pname, + audio_IntVal *value) +{ + mixer_GetSourcei ((mixer_Object) srcobj, (mixer_SourceProp) pname, + (mixer_IntVal *) value); + if (pname == MIX_SOURCE_STATE) + { + switch (*value) + { + case MIX_INITIAL: + *value = audio_INITIAL; + break; + case MIX_STOPPED: + *value = audio_STOPPED; + break; + case MIX_PLAYING: + *value = audio_PLAYING; + break; + case MIX_PAUSED: + *value = audio_PAUSED; + break; + default: + *value = audio_DRIVER_FAILURE; + } + } +} + +void +mixSDL_GetSourcef (audio_Object srcobj, audio_SourceProp pname, + float *value) +{ + mixer_GetSourcef ((mixer_Object) srcobj, (mixer_SourceProp) pname, value); +} + +void +mixSDL_SourceRewind (audio_Object srcobj) +{ + mixer_SourceRewind ((mixer_Object) srcobj); +} + +void +mixSDL_SourcePlay (audio_Object srcobj) +{ + mixer_SourcePlay ((mixer_Object) srcobj); +} + +void +mixSDL_SourcePause (audio_Object srcobj) +{ + mixer_SourcePause ((mixer_Object) srcobj); +} + +void +mixSDL_SourceStop (audio_Object srcobj) +{ + mixer_SourceStop ((mixer_Object) srcobj); +} + +void +mixSDL_SourceQueueBuffers (audio_Object srcobj, uint32 n, + audio_Object* pbufobj) +{ + mixer_SourceQueueBuffers ((mixer_Object) srcobj, n, + (mixer_Object *) pbufobj); +} + +void +mixSDL_SourceUnqueueBuffers (audio_Object srcobj, uint32 n, + audio_Object* pbufobj) +{ + mixer_SourceUnqueueBuffers ((mixer_Object) srcobj, n, + (mixer_Object *) pbufobj); +} + + +/* + * Buffers + */ + +void +mixSDL_GenBuffers (uint32 n, audio_Object *pbufobj) +{ + mixer_GenBuffers (n, (mixer_Object *) pbufobj); +} + +void +mixSDL_DeleteBuffers (uint32 n, audio_Object *pbufobj) +{ + mixer_DeleteBuffers (n, (mixer_Object *) pbufobj); +} + +bool +mixSDL_IsBuffer (audio_Object bufobj) +{ + return mixer_IsBuffer ((mixer_Object) bufobj); +} + +void +mixSDL_GetBufferi (audio_Object bufobj, audio_BufferProp pname, + audio_IntVal *value) +{ + mixer_GetBufferi ((mixer_Object) bufobj, (mixer_BufferProp) pname, + (mixer_IntVal *) value); +} + +void +mixSDL_BufferData (audio_Object bufobj, uint32 format, void* data, + uint32 size, uint32 freq) +{ + mixer_BufferData ((mixer_Object) bufobj, format, data, size, freq); +} diff --git a/src/libs/sound/mixer/sdl/audiodrv_sdl.h b/src/libs/sound/mixer/sdl/audiodrv_sdl.h new file mode 100644 index 0000000..d74301b --- /dev/null +++ b/src/libs/sound/mixer/sdl/audiodrv_sdl.h @@ -0,0 +1,66 @@ +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* SDL audio driver + */ + +#ifndef LIBS_SOUND_MIXER_SDL_AUDIODRV_SDL_H_ +#define LIBS_SOUND_MIXER_SDL_AUDIODRV_SDL_H_ + +#include "port.h" +#include "libs/sound/sound.h" +#include "libs/sound/mixer/mixer.h" +#include SDL_INCLUDE(SDL.h) + +/* General */ +sint32 mixSDL_Init (audio_Driver *driver, sint32 flags); +void mixSDL_Uninit (void); +sint32 mixSDL_GetError (void); + +/* Sources */ +void mixSDL_GenSources (uint32 n, audio_Object *psrcobj); +void mixSDL_DeleteSources (uint32 n, audio_Object *psrcobj); +bool mixSDL_IsSource (audio_Object srcobj); +void mixSDL_Sourcei (audio_Object srcobj, audio_SourceProp pname, + audio_IntVal value); +void mixSDL_Sourcef (audio_Object srcobj, audio_SourceProp pname, + float value); +void mixSDL_Sourcefv (audio_Object srcobj, audio_SourceProp pname, + float *value); +void mixSDL_GetSourcei (audio_Object srcobj, audio_SourceProp pname, + audio_IntVal *value); +void mixSDL_GetSourcef (audio_Object srcobj, audio_SourceProp pname, + float *value); +void mixSDL_SourceRewind (audio_Object srcobj); +void mixSDL_SourcePlay (audio_Object srcobj); +void mixSDL_SourcePause (audio_Object srcobj); +void mixSDL_SourceStop (audio_Object srcobj); +void mixSDL_SourceQueueBuffers (audio_Object srcobj, uint32 n, + audio_Object* pbufobj); +void mixSDL_SourceUnqueueBuffers (audio_Object srcobj, uint32 n, + audio_Object* pbufobj); + +/* Buffers */ +void mixSDL_GenBuffers (uint32 n, audio_Object *pbufobj); +void mixSDL_DeleteBuffers (uint32 n, audio_Object *pbufobj); +bool mixSDL_IsBuffer (audio_Object bufobj); +void mixSDL_GetBufferi (audio_Object bufobj, audio_BufferProp pname, + audio_IntVal *value); +void mixSDL_BufferData (audio_Object bufobj, uint32 format, void* data, + uint32 size, uint32 freq); + + +#endif /* LIBS_SOUND_MIXER_SDL_AUDIODRV_SDL_H_ */ diff --git a/src/libs/sound/music.c b/src/libs/sound/music.c new file mode 100644 index 0000000..c3f92f0 --- /dev/null +++ b/src/libs/sound/music.c @@ -0,0 +1,233 @@ +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include <string.h> +#include "libs/file.h" +#include "options.h" +#include "sound.h" +#include "sndintrn.h" +#include "libs/reslib.h" +#include "libs/log.h" +#include "libs/memlib.h" + + +static MUSIC_REF curMusicRef; +static MUSIC_REF curSpeechRef; + +void +PLRPlaySong (MUSIC_REF MusicRef, BOOLEAN Continuous, BYTE Priority) +{ + TFB_SoundSample **pmus = MusicRef; + + if (pmus) + { + LockMutex (soundSource[MUSIC_SOURCE].stream_mutex); + // Always scope the music data, we may need it + PlayStream ((*pmus), MUSIC_SOURCE, Continuous, true, true); + UnlockMutex (soundSource[MUSIC_SOURCE].stream_mutex); + + curMusicRef = MusicRef; + } + + (void) Priority; /* Satisfy compiler because of unused variable */ +} + +void +PLRStop (MUSIC_REF MusicRef) +{ + if (MusicRef == curMusicRef || MusicRef == (MUSIC_REF)~0) + { + LockMutex (soundSource[MUSIC_SOURCE].stream_mutex); + StopStream (MUSIC_SOURCE); + UnlockMutex (soundSource[MUSIC_SOURCE].stream_mutex); + + curMusicRef = 0; + } +} + +BOOLEAN +PLRPlaying (MUSIC_REF MusicRef) +{ + if (curMusicRef && (MusicRef == curMusicRef || MusicRef == (MUSIC_REF)~0)) + { + BOOLEAN playing; + + LockMutex (soundSource[MUSIC_SOURCE].stream_mutex); + playing = PlayingStream (MUSIC_SOURCE); + UnlockMutex (soundSource[MUSIC_SOURCE].stream_mutex); + + return playing; + } + + return FALSE; +} + +void +PLRSeek (MUSIC_REF MusicRef, DWORD pos) +{ + if (MusicRef == curMusicRef || MusicRef == (MUSIC_REF)~0) + { + LockMutex (soundSource[MUSIC_SOURCE].stream_mutex); + SeekStream (MUSIC_SOURCE, pos); + UnlockMutex (soundSource[MUSIC_SOURCE].stream_mutex); + } +} + +void +PLRPause (MUSIC_REF MusicRef) +{ + if (MusicRef == curMusicRef || MusicRef == (MUSIC_REF)~0) + { + LockMutex (soundSource[MUSIC_SOURCE].stream_mutex); + PauseStream (MUSIC_SOURCE); + UnlockMutex (soundSource[MUSIC_SOURCE].stream_mutex); + } +} + +void +PLRResume (MUSIC_REF MusicRef) +{ + if (MusicRef == curMusicRef || MusicRef == (MUSIC_REF)~0) + { + LockMutex (soundSource[MUSIC_SOURCE].stream_mutex); + ResumeStream (MUSIC_SOURCE); + UnlockMutex (soundSource[MUSIC_SOURCE].stream_mutex); + } +} + +void +snd_PlaySpeech (MUSIC_REF SpeechRef) +{ + TFB_SoundSample **pmus = SpeechRef; + + if (pmus) + { + LockMutex (soundSource[SPEECH_SOURCE].stream_mutex); + // Do not need to scope the music-as-speech as of now + PlayStream (*pmus, SPEECH_SOURCE, false, false, true); + UnlockMutex (soundSource[SPEECH_SOURCE].stream_mutex); + + curSpeechRef = SpeechRef; + } +} + +void +snd_StopSpeech (void) +{ + if (!curSpeechRef) + return; + + LockMutex (soundSource[SPEECH_SOURCE].stream_mutex); + StopStream (SPEECH_SOURCE); + UnlockMutex (soundSource[SPEECH_SOURCE].stream_mutex); + + curSpeechRef = 0; +} + +BOOLEAN +DestroyMusic (MUSIC_REF MusicRef) +{ + return _ReleaseMusicData (MusicRef); +} + +void +SetMusicVolume (COUNT Volume) +{ + float f = (Volume / (float)MAX_VOLUME) * musicVolumeScale; + musicVolume = Volume; + audio_Sourcef (soundSource[MUSIC_SOURCE].handle, audio_GAIN, f); +} + +char* +CheckMusicResName (char* fileName) +{ + if (!fileExists2 (contentDir, fileName)) + log_add (log_Warning, "Requested track '%s' not found.", fileName); + return fileName; +} + +void * +_GetMusicData (uio_Stream *fp, DWORD length) +{ + MUSIC_REF h; + TFB_SoundSample *sample; + TFB_SoundDecoder *decoder; + char filename[256]; + + if (!_cur_resfile_name) + return NULL; + + strncpy (filename, _cur_resfile_name, sizeof(filename) - 1); + filename[sizeof(filename) - 1] = '\0'; + CheckMusicResName (filename); + + log_add (log_Info, "_GetMusicData(): loading %s", filename); + decoder = SoundDecoder_Load (contentDir, filename, 4096, 0, 0); + if (!decoder) + { + log_add (log_Warning, "_GetMusicData(): couldn't load %s", filename); + return NULL; + } + + h = AllocMusicData (sizeof (void *)); + if (!h) + { + SoundDecoder_Free (decoder); + return NULL; + } + + sample = TFB_CreateSoundSample (decoder, 64, NULL); + *h = sample; + + log_add (log_Info, " decoder: %s, rate %d format %x", + SoundDecoder_GetName (sample->decoder), + sample->decoder->frequency, sample->decoder->format); + + (void) fp; /* satisfy compiler (unused parameter) */ + (void) length; /* satisfy compiler (unused parameter) */ + return (h); +} + +BOOLEAN +_ReleaseMusicData (void *data) +{ + TFB_SoundSample **pmus = data; + TFB_SoundSample *sample; + + if (pmus == NULL) + return (FALSE); + + sample = *pmus; + assert (sample != 0); + if (sample->decoder) + { + TFB_SoundDecoder *decoder = sample->decoder; + LockMutex (soundSource[MUSIC_SOURCE].stream_mutex); + if (soundSource[MUSIC_SOURCE].sample == sample) + { // Currently playing this sample! Not good. + StopStream (MUSIC_SOURCE); + } + UnlockMutex (soundSource[MUSIC_SOURCE].stream_mutex); + + sample->decoder = NULL; + SoundDecoder_Free (decoder); + } + TFB_DestroySoundSample (sample); + FreeMusicData (data); + + return (TRUE); +} + diff --git a/src/libs/sound/openal/Makeinfo b/src/libs/sound/openal/Makeinfo new file mode 100644 index 0000000..1469461 --- /dev/null +++ b/src/libs/sound/openal/Makeinfo @@ -0,0 +1,2 @@ +uqm_CFILES="audiodrv_openal.c" +uqm_HFILES="audiodrv_openal.h" diff --git a/src/libs/sound/openal/audiodrv_openal.c b/src/libs/sound/openal/audiodrv_openal.c new file mode 100644 index 0000000..eee1cd1 --- /dev/null +++ b/src/libs/sound/openal/audiodrv_openal.c @@ -0,0 +1,420 @@ +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* OpenAL audio driver + */ + +#ifdef HAVE_OPENAL + + +#include "audiodrv_openal.h" +#include "../sndintrn.h" +#include "libs/log.h" +#include "libs/memlib.h" +#include <stdlib.h> + + +ALCcontext *alcContext = NULL; +ALCdevice *alcDevice = NULL; +ALfloat defaultPos[] = {0.0f, 0.0f, -1.0f}; +ALfloat listenerPos[] = {0.0f, 0.0f, 0.0f}; +ALfloat listenerVel[] = {0.0f, 0.0f, 0.0f}; +ALfloat listenerOri[] = {0.0f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f}; + +static const audio_Driver openAL_Driver = +{ + openAL_Uninit, + openAL_GetError, + audio_DRIVER_OPENAL, + { + /* Errors */ + AL_FALSE, + AL_INVALID_NAME, + AL_INVALID_ENUM, + AL_INVALID_VALUE, + AL_INVALID_OPERATION, + AL_OUT_OF_MEMORY, + audio_DRIVER_FAILURE, + + /* Source properties */ + AL_POSITION, + AL_LOOPING, + AL_BUFFER, + AL_GAIN, + AL_SOURCE_STATE, + AL_BUFFERS_QUEUED, + AL_BUFFERS_PROCESSED, + + /* Source state information */ + AL_INITIAL, + AL_STOPPED, + AL_PLAYING, + AL_PAUSED, + + /* Sound buffer properties */ + AL_FREQUENCY, + AL_BITS, + AL_CHANNELS, + AL_SIZE, + AL_FORMAT_MONO16, + AL_FORMAT_STEREO16, + AL_FORMAT_MONO8, + AL_FORMAT_STEREO8 + }, + + /* Sources */ + openAL_GenSources, + openAL_DeleteSources, + openAL_IsSource, + openAL_Sourcei, + openAL_Sourcef, + openAL_Sourcefv, + openAL_GetSourcei, + openAL_GetSourcef, + openAL_SourceRewind, + openAL_SourcePlay, + openAL_SourcePause, + openAL_SourceStop, + openAL_SourceQueueBuffers, + openAL_SourceUnqueueBuffers, + + /* Buffers */ + openAL_GenBuffers, + openAL_DeleteBuffers, + openAL_IsBuffer, + openAL_GetBufferi, + openAL_BufferData +}; + + +/* + * Initialization + */ + +sint32 +openAL_Init (audio_Driver *driver, sint32 flags) +{ + int i; + TFB_DecoderFormats formats = + { + MIX_IS_BIG_ENDIAN, MIX_WANT_BIG_ENDIAN, + audio_FORMAT_MONO8, audio_FORMAT_STEREO8, + audio_FORMAT_MONO16, audio_FORMAT_STEREO16 + }; + + log_add (log_Info, "Initializing OpenAL."); + alcDevice = alcOpenDevice (NULL); + + if (!alcDevice) + { + log_add (log_Error, "Couldn't initialize OpenAL: %d", + alcGetError (NULL)); + return -1; + } + + *driver = openAL_Driver; + + alcContext = alcCreateContext (alcDevice, NULL); + if (!alcContext) + { + log_add (log_Error, "Couldn't create OpenAL context: %d", + alcGetError (alcDevice)); + alcCloseDevice (alcDevice); + alcDevice = NULL; + return -1; + } + + alcMakeContextCurrent (alcContext); + + log_add (log_Info, "OpenAL initialized.\n" + " version: %s\n" + " vendor: %s\n" + " renderer: %s\n" + " device: %s", + alGetString (AL_VERSION), alGetString (AL_VENDOR), + alGetString (AL_RENDERER), + alcGetString (alcDevice, ALC_DEFAULT_DEVICE_SPECIFIER)); + //log_add (log_Info, " extensions: %s", alGetString (AL_EXTENSIONS)); + + log_add (log_Info, "Initializing sound decoders."); + if (SoundDecoder_Init (flags, &formats)) + { + log_add (log_Error, "Sound decoders initialization failed."); + alcMakeContextCurrent (NULL); + alcDestroyContext (alcContext); + alcContext = NULL; + alcCloseDevice (alcDevice); + alcDevice = NULL; + return -1; + } + log_add (log_Error, "Sound decoders initialized."); + + alListenerfv (AL_POSITION, listenerPos); + alListenerfv (AL_VELOCITY, listenerVel); + alListenerfv (AL_ORIENTATION, listenerOri); + + for (i = 0; i < NUM_SOUNDSOURCES; ++i) + { + float zero[3] = {0.0f, 0.0f, 0.0f}; + + alGenSources (1, &soundSource[i].handle); + alSourcei (soundSource[i].handle, AL_LOOPING, AL_FALSE); + alSourcefv (soundSource[i].handle, AL_POSITION, defaultPos); + alSourcefv (soundSource[i].handle, AL_VELOCITY, zero); + alSourcefv (soundSource[i].handle, AL_DIRECTION, zero); + + soundSource[i].stream_mutex = CreateMutex ("OpenAL stream mutex", SYNC_CLASS_AUDIO); + } + + if (InitStreamDecoder ()) + { + log_add (log_Error, "Stream decoder initialization failed."); + // TODO: cleanup source mutexes [or is it "muti"? :) ] + SoundDecoder_Uninit (); + alcMakeContextCurrent (NULL); + alcDestroyContext (alcContext); + alcContext = NULL; + alcCloseDevice (alcDevice); + alcDevice = NULL; + return -1; + } + + alDistanceModel (AL_INVERSE_DISTANCE); + + (void) driver; // eat compiler warning + + return 0; +} + +void +openAL_Uninit (void) +{ + int i; + + UninitStreamDecoder (); + + for (i = 0; i < NUM_SOUNDSOURCES; ++i) + { + if (soundSource[i].sample && soundSource[i].sample->decoder) + { + StopStream (i); + } + if (soundSource[i].sbuffer) + { + void *sbuffer = soundSource[i].sbuffer; + soundSource[i].sbuffer = NULL; + HFree (sbuffer); + } + DestroyMutex (soundSource[i].stream_mutex); + } + + alcMakeContextCurrent (NULL); + alcDestroyContext (alcContext); + alcContext = NULL; + alcCloseDevice (alcDevice); + alcDevice = NULL; + + SoundDecoder_Uninit (); +} + + +/* + * General + */ + +sint32 +openAL_GetError (void) +{ + ALint value = alGetError (); + switch (value) + { + case AL_FALSE: + return audio_NO_ERROR; + case AL_INVALID_NAME: + return audio_INVALID_NAME; + case AL_INVALID_ENUM: + return audio_INVALID_ENUM; + case AL_INVALID_VALUE: + return audio_INVALID_VALUE; + case AL_INVALID_OPERATION: + return audio_INVALID_OPERATION; + case AL_OUT_OF_MEMORY: + return audio_OUT_OF_MEMORY; + default: + log_add (log_Debug, "openAL_GetError: unknown value %x", value); + return audio_DRIVER_FAILURE; + break; + } +} + + +/* + * Sources + */ + +void +openAL_GenSources (uint32 n, audio_Object *psrcobj) +{ + alGenSources ((ALsizei) n, (ALuint *) psrcobj); +} + +void +openAL_DeleteSources (uint32 n, audio_Object *psrcobj) +{ + alDeleteSources ((ALsizei) n, (ALuint *) psrcobj); +} + +bool +openAL_IsSource (audio_Object srcobj) +{ + return alIsSource ((ALuint) srcobj); +} + +void +openAL_Sourcei (audio_Object srcobj, audio_SourceProp pname, + audio_IntVal value) + +{ + alSourcei ((ALuint) srcobj, (ALenum) pname, (ALint) value); +} + +void +openAL_Sourcef (audio_Object srcobj, audio_SourceProp pname, + float value) +{ + alSourcef ((ALuint) srcobj, (ALenum) pname, (ALfloat) value); +} + +void +openAL_Sourcefv (audio_Object srcobj, audio_SourceProp pname, + float *value) +{ + alSourcefv ((ALuint) srcobj, (ALenum) pname, (ALfloat *) value); +} + +void +openAL_GetSourcei (audio_Object srcobj, audio_SourceProp pname, + audio_IntVal *value) +{ + alGetSourcei ((ALuint) srcobj, (ALenum) pname, (ALint *) value); + if (pname == AL_SOURCE_STATE) + { + switch (*value) + { + case AL_INITIAL: + *value = audio_INITIAL; + break; + case AL_STOPPED: + *value = audio_STOPPED; + break; + case AL_PLAYING: + *value = audio_PLAYING; + break; + case AL_PAUSED: + *value = audio_PAUSED; + break; + default: + log_add (log_Debug, "openAL_GetSourcei(): unknown value %x", + *value); + *value = audio_DRIVER_FAILURE; + } + } +} + +void +openAL_GetSourcef (audio_Object srcobj, audio_SourceProp pname, + float *value) +{ + alGetSourcef ((ALuint) srcobj, (ALenum) pname, (ALfloat *) value); +} + +void +openAL_SourceRewind (audio_Object srcobj) +{ + alSourceRewind ((ALuint) srcobj); +} + +void +openAL_SourcePlay (audio_Object srcobj) +{ + alSourcePlay ((ALuint) srcobj); +} + +void +openAL_SourcePause (audio_Object srcobj) +{ + alSourcePause ((ALuint) srcobj); +} + +void +openAL_SourceStop (audio_Object srcobj) +{ + alSourceStop ((ALuint) srcobj); +} + +void +openAL_SourceQueueBuffers (audio_Object srcobj, uint32 n, + audio_Object* pbufobj) +{ + alSourceQueueBuffers ((ALuint) srcobj, (ALsizei) n, (ALuint *) pbufobj); +} + +void +openAL_SourceUnqueueBuffers (audio_Object srcobj, uint32 n, + audio_Object* pbufobj) +{ + alSourceUnqueueBuffers ((ALuint) srcobj, (ALsizei) n, (ALuint *) pbufobj); +} + + +/* + * Buffers + */ + +void +openAL_GenBuffers (uint32 n, audio_Object *pbufobj) +{ + alGenBuffers ((ALsizei) n, (ALuint *) pbufobj); +} + +void +openAL_DeleteBuffers (uint32 n, audio_Object *pbufobj) +{ + alDeleteBuffers ((ALsizei) n, (ALuint *) pbufobj); +} + +bool +openAL_IsBuffer (audio_Object bufobj) +{ + return alIsBuffer ((ALuint) bufobj); +} + +void +openAL_GetBufferi (audio_Object bufobj, audio_BufferProp pname, + audio_IntVal *value) +{ + alGetBufferi ((ALuint) bufobj, (ALenum) pname, (ALint *) value); +} + +void +openAL_BufferData (audio_Object bufobj, uint32 format, void* data, + uint32 size, uint32 freq) +{ + alBufferData ((ALuint) bufobj, (ALenum) format, (ALvoid *) data, + (ALsizei) size, (ALsizei) freq); +} + +#endif diff --git a/src/libs/sound/openal/audiodrv_openal.h b/src/libs/sound/openal/audiodrv_openal.h new file mode 100644 index 0000000..c1a3eff --- /dev/null +++ b/src/libs/sound/openal/audiodrv_openal.h @@ -0,0 +1,86 @@ +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* OpenAL audio driver + */ + +#ifndef LIBS_SOUND_OPENAL_AUDIODRV_OPENAL_H_ +#define LIBS_SOUND_OPENAL_AUDIODRV_OPENAL_H_ + +#include "config.h" +#include "libs/sound/sound.h" +#include "endian_uqm.h" + +#if defined (__APPLE__) +# include <OpenAL/al.h> +# include <OpenAL/alc.h> +#else +# include <AL/al.h> +# include <AL/alc.h> +# ifdef _MSC_VER +# pragma comment (lib, "OpenAL32.lib") +# endif +#endif + +/* This is just a simple endianness setup for decoders */ +#ifdef WORDS_BIGENDIAN +# define MIX_IS_BIG_ENDIAN true +# define MIX_WANT_BIG_ENDIAN true +#else +# define MIX_IS_BIG_ENDIAN false +# define MIX_WANT_BIG_ENDIAN false +#endif + + +/* General */ +sint32 openAL_Init (audio_Driver *driver, sint32 flags); +void openAL_Uninit (void); +sint32 openAL_GetError (void); + +/* Sources */ +void openAL_GenSources (uint32 n, audio_Object *psrcobj); +void openAL_DeleteSources (uint32 n, audio_Object *psrcobj); +bool openAL_IsSource (audio_Object srcobj); +void openAL_Sourcei (audio_Object srcobj, audio_SourceProp pname, + audio_IntVal value); +void openAL_Sourcef (audio_Object srcobj, audio_SourceProp pname, + float value); +void openAL_Sourcefv (audio_Object srcobj, audio_SourceProp pname, + float *value); +void openAL_GetSourcei (audio_Object srcobj, audio_SourceProp pname, + audio_IntVal *value); +void openAL_GetSourcef (audio_Object srcobj, audio_SourceProp pname, + float *value); +void openAL_SourceRewind (audio_Object srcobj); +void openAL_SourcePlay (audio_Object srcobj); +void openAL_SourcePause (audio_Object srcobj); +void openAL_SourceStop (audio_Object srcobj); +void openAL_SourceQueueBuffers (audio_Object srcobj, uint32 n, + audio_Object* pbufobj); +void openAL_SourceUnqueueBuffers (audio_Object srcobj, uint32 n, + audio_Object* pbufobj); + +/* Buffers */ +void openAL_GenBuffers (uint32 n, audio_Object *pbufobj); +void openAL_DeleteBuffers (uint32 n, audio_Object *pbufobj); +bool openAL_IsBuffer (audio_Object bufobj); +void openAL_GetBufferi (audio_Object bufobj, audio_BufferProp pname, + audio_IntVal *value); +void openAL_BufferData (audio_Object bufobj, uint32 format, void* data, + uint32 size, uint32 freq); + + +#endif /* LIBS_SOUND_OPENAL_AUDIODRV_OPENAL_H_ */ diff --git a/src/libs/sound/resinst.c b/src/libs/sound/resinst.c new file mode 100644 index 0000000..dc44c49 --- /dev/null +++ b/src/libs/sound/resinst.c @@ -0,0 +1,65 @@ +//Copyright Paul Reiche, Fred Ford. 1992-2002 + +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "sound.h" +#include "sndintrn.h" + +static void +GetSoundBankFileData (const char *pathname, RESOURCE_DATA *resdata) +{ + resdata->ptr = LoadResourceFromPath (pathname, _GetSoundBankData); +} + +static void +GetMusicFileData (const char *pathname, RESOURCE_DATA *resdata) +{ + resdata->ptr = LoadResourceFromPath (pathname, _GetMusicData); +} + +BOOLEAN +InstallAudioResTypes (void) +{ + InstallResTypeVectors ("SNDRES", GetSoundBankFileData, _ReleaseSoundBankData, NULL); + InstallResTypeVectors ("MUSICRES", GetMusicFileData, _ReleaseMusicData, NULL); + return (TRUE); +} + +SOUND_REF +LoadSoundInstance (RESOURCE res) +{ + void *hData; + + hData = res_GetResource (res); + if (hData) + res_DetachResource (res); + + return ((SOUND_REF)hData); +} + +MUSIC_REF +LoadMusicInstance (RESOURCE res) +{ + void *hData; + + hData = res_GetResource (res); + if (hData) + res_DetachResource (res); + + return ((MUSIC_REF)hData); +} + diff --git a/src/libs/sound/sfx.c b/src/libs/sound/sfx.c new file mode 100644 index 0000000..3060434 --- /dev/null +++ b/src/libs/sound/sfx.c @@ -0,0 +1,306 @@ +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "options.h" +#include "sound.h" +#include "sndintrn.h" +#include "libs/reslib.h" +#include "libs/log.h" +#include "libs/strlib.h" + // for GetStringAddress() +#include "libs/strings/strintrn.h" + // for AllocStringTable(), FreeStringTable() +#include "libs/memlib.h" +#include <math.h> + + +static void CheckFinishedChannels (void); + +static const SoundPosition notPositional = {FALSE, 0, 0}; + +void +PlayChannel (COUNT channel, SOUND snd, SoundPosition pos, + void *positional_object, unsigned char priority) +{ + SOUNDPTR snd_ptr = GetSoundAddress (snd); + TFB_SoundSample *sample; + + StopSource (channel); + // all finished (stopped) channels can be cleaned up at this point + // since this is the only func that can initiate an sfx sound + CheckFinishedChannels (); + + if (!snd_ptr) + return; // nothing to play + + sample = *(TFB_SoundSample**) snd_ptr; + + soundSource[channel].sample = sample; + soundSource[channel].positional_object = positional_object; + + UpdateSoundPosition (channel, optStereoSFX ? pos : notPositional); + + audio_Sourcei (soundSource[channel].handle, audio_BUFFER, + sample->buffer[0]); + audio_SourcePlay (soundSource[channel].handle); + (void) priority; +} + +void +StopChannel (COUNT channel, unsigned char Priority) +{ + StopSource (channel); + (void)Priority; // ignored +} + +static void +CheckFinishedChannels (void) +{ + int i; + + for (i = FIRST_SFX_SOURCE; i <= LAST_SFX_SOURCE; ++i) + { + audio_IntVal state; + + audio_GetSourcei (soundSource[i].handle, audio_SOURCE_STATE, + &state); + if (state == audio_STOPPED) + { + CleanSource (i); + // and if it failed... we still dont care + audio_GetError(); + } + } +} + +BOOLEAN +ChannelPlaying (COUNT WhichChannel) +{ + audio_IntVal state; + + audio_GetSourcei (soundSource[WhichChannel].handle, + audio_SOURCE_STATE, &state); + if (state == audio_PLAYING) + return TRUE; + return FALSE; +} + +void * +GetPositionalObject (COUNT channel) +{ + return soundSource[channel].positional_object; +} + +void +SetPositionalObject (COUNT channel, void *positional_object) +{ + soundSource[channel].positional_object = positional_object; +} + +void +UpdateSoundPosition (COUNT channel, SoundPosition pos) +{ + const float ATTENUATION = 160.0f; + const float MIN_DISTANCE = 0.5f; + float fpos[3]; + + if (pos.positional) + { + float dist; + + fpos[0] = pos.x / ATTENUATION; + fpos[1] = 0.0f; + fpos[2] = pos.y / ATTENUATION; + dist = (float) sqrt (fpos[0] * fpos[0] + fpos[2] * fpos[2]); + if (dist < MIN_DISTANCE) + { // object is too close to listener + // move it away along the same vector + float scale = MIN_DISTANCE / dist; + fpos[0] *= scale; + fpos[2] *= scale; + } + + audio_Sourcefv (soundSource[channel].handle, audio_POSITION, fpos); + //log_add (log_Debug, "UpdateSoundPosition(): channel %d, pos %d %d, posobj %x", + // channel, pos.x, pos.y, (unsigned int)soundSource[channel].positional_object); + } + else + { + fpos[0] = fpos[1] = 0.0f; + fpos[2] = -1.0f; + audio_Sourcefv (soundSource[channel].handle, audio_POSITION, fpos); + } +} + +void +SetChannelVolume (COUNT channel, COUNT volume, BYTE priority) + // I wonder what this whole priority business is... + // I can probably ignore it. +{ + audio_Sourcef (soundSource[channel].handle, audio_GAIN, + (volume / (float)MAX_VOLUME) * sfxVolumeScale); + (void)priority; // ignored +} + +void * +_GetSoundBankData (uio_Stream *fp, DWORD length) +{ + int snd_ct, n; + DWORD opos; + char CurrentLine[1024], filename[1024]; +#define MAX_FX 256 + TFB_SoundSample *sndfx[MAX_FX]; + STRING_TABLE Snd; + STRING str; + int i; + + (void) length; // ignored + opos = uio_ftell (fp); + + { + char *s1, *s2; + + if (_cur_resfile_name == 0 + || (((s2 = 0), (s1 = strrchr (_cur_resfile_name, '/')) == 0) + && (s2 = strrchr (_cur_resfile_name, '\\')) == 0)) + n = 0; + else + { + if (s2 > s1) + s1 = s2; + n = s1 - _cur_resfile_name + 1; + strncpy (filename, _cur_resfile_name, n); + } + } + + snd_ct = 0; + while (uio_fgets (CurrentLine, sizeof (CurrentLine), fp) && + snd_ct < MAX_FX) + { + TFB_SoundSample* sample; + TFB_SoundDecoder* decoder; + uint32 decoded_bytes; + + if (sscanf (CurrentLine, "%s", &filename[n]) != 1) + { + log_add (log_Warning, "_GetSoundBankData: bad line: '%s'", + CurrentLine); + continue; + } + + log_add (log_Info, "_GetSoundBankData(): loading %s", filename); + + decoder = SoundDecoder_Load (contentDir, filename, 4096, 0, 0); + if (!decoder) + { + log_add (log_Warning, "_GetSoundBankData(): couldn't load %s", + filename); + continue; + } + + // SFX samples don't have decoders, everything is pre-decoded below + sample = TFB_CreateSoundSample (NULL, 1, NULL); + + // Decode everything and stash it in 1 buffer + decoded_bytes = SoundDecoder_DecodeAll (decoder); + log_add (log_Info, "_GetSoundBankData(): decoded bytes %d", + decoded_bytes); + + audio_BufferData (sample->buffer[0], decoder->format, + decoder->buffer, decoded_bytes, decoder->frequency); + // just for informational purposes + sample->length = decoder->length; + + SoundDecoder_Free (decoder); + + sndfx[snd_ct] = sample; + ++snd_ct; + } + + if (!snd_ct) + return NULL; // no sounds decoded + + Snd = AllocStringTable (snd_ct, 0); + if (!Snd) + { // Oops, have to delete everything now + while (snd_ct--) + TFB_DestroySoundSample (sndfx[snd_ct]); + + return NULL; + } + + // Populate the STRING_TABLE with ptrs to sample + for (i = 0, str = Snd->strings; i < snd_ct; ++i, ++str) + { + TFB_SoundSample **target = HMalloc (sizeof (sndfx[0])); + *target = sndfx[i]; + str->data = (STRINGPTR)target; + str->length = sizeof (sndfx[0]); + } + + return Snd; +} + +BOOLEAN +_ReleaseSoundBankData (void *Snd) +{ + STRING_TABLE fxTab = Snd; + int index; + + if (!fxTab) + return FALSE; + + for (index = 0; index < fxTab->size; ++index) + { + int i; + void **sptr = (void**)fxTab->strings[index].data; + TFB_SoundSample *sample = (TFB_SoundSample*)*sptr; + + // Check all sources and see if we are currently playing this sample + for (i = 0; i < NUM_SOUNDSOURCES; ++i) + { + if (soundSource[i].sample == sample) + { // Playing this sample. Have to stop it. + StopSource (i); + soundSource[i].sample = NULL; + } + } + + if (sample->decoder) + SoundDecoder_Free (sample->decoder); + sample->decoder = NULL; + TFB_DestroySoundSample (sample); + // sptr will be deleted by FreeStringTable() below + } + + FreeStringTable (fxTab); + + return TRUE; +} + +BOOLEAN +DestroySound(SOUND_REF target) +{ + return _ReleaseSoundBankData (target); +} + +// The type conversions are implicit and will generate errors +// or warnings if types change imcompatibly +SOUNDPTR +GetSoundAddress (SOUND sound) +{ + return GetStringAddress (sound); +} diff --git a/src/libs/sound/sndintrn.h b/src/libs/sound/sndintrn.h new file mode 100644 index 0000000..179028c --- /dev/null +++ b/src/libs/sound/sndintrn.h @@ -0,0 +1,76 @@ +//Copyright Paul Reiche, Fred Ford. 1992-2002 + +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef LIBS_SOUND_SNDINTRN_H_ +#define LIBS_SOUND_SNDINTRN_H_ + +#include <stdio.h> +#include "types.h" +#include "libs/reslib.h" +#include "libs/memlib.h" + +#define PAD_SCOPE_BYTES 256 + +extern void *_GetMusicData (uio_Stream *fp, DWORD length); +extern BOOLEAN _ReleaseMusicData (void *handle); + +extern void *_GetSoundBankData (uio_Stream *fp, DWORD length); +extern BOOLEAN _ReleaseSoundBankData (void *handle); + +#define AllocMusicData HMalloc +#define FreeMusicData HFree + +extern char* CheckMusicResName (char* filename); + +// audio data +struct tfb_soundsample +{ + TFB_SoundDecoder *decoder; // decoder to read from + float length; // total length of decoder chain in seconds + audio_Object *buffer; + uint32 num_buffers; + TFB_SoundTag *buffer_tag; + sint32 offset; // initial offset + void* data; // user-defined data + TFB_SoundCallbacks callbacks; // user-defined callbacks +}; + +// equivalent to channel in legacy sound code +typedef struct tfb_soundsource +{ + TFB_SoundSample *sample; + audio_Object handle; + bool stream_should_be_playing; + Mutex stream_mutex; + sint32 start_time; // for tracks played-time math + uint32 pause_time; // keep track for paused tracks + void *positional_object; + + audio_Object last_q_buf; // for callbacks processing + + // Cyclic waveform buffer for oscilloscope + void *sbuffer; + uint32 sbuf_size; + uint32 sbuf_tail; + uint32 sbuf_head; + uint32 sbuf_lasttime; // timestamp of the first queued buffer +} TFB_SoundSource; + +extern TFB_SoundSource soundSource[]; + +#endif /* LIBS_SOUND_SNDINTRN_H_ */ diff --git a/src/libs/sound/sound.c b/src/libs/sound/sound.c new file mode 100644 index 0000000..f2a790e --- /dev/null +++ b/src/libs/sound/sound.c @@ -0,0 +1,178 @@ +//Copyright Paul Reiche, Fred Ford. 1992-2002 + +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "sound.h" +#include "sndintrn.h" +#include "libs/compiler.h" +#include "libs/inplib.h" +#include "libs/memlib.h" + + +int musicVolume = NORMAL_VOLUME; +float musicVolumeScale; +float sfxVolumeScale; +float speechVolumeScale; +TFB_SoundSource soundSource[NUM_SOUNDSOURCES]; + + +void +StopSound (void) +{ + int i; + + for (i = FIRST_SFX_SOURCE; i <= LAST_SFX_SOURCE; ++i) + { + StopSource (i); + } +} + +void +CleanSource (int iSource) +{ +#define MAX_STACK_BUFFERS 64 + audio_IntVal processed; + + soundSource[iSource].positional_object = NULL; + audio_GetSourcei (soundSource[iSource].handle, + audio_BUFFERS_PROCESSED, &processed); + if (processed != 0) + { + audio_Object stack_bufs[MAX_STACK_BUFFERS]; + audio_Object *bufs; + + if (processed > MAX_STACK_BUFFERS) + bufs = (audio_Object *) HMalloc ( + sizeof (audio_Object) * processed); + else + bufs = stack_bufs; + + audio_SourceUnqueueBuffers (soundSource[iSource].handle, + processed, bufs); + + if (processed > MAX_STACK_BUFFERS) + HFree (bufs); + } + // set the source state to 'initial' + audio_SourceRewind (soundSource[iSource].handle); +} + +void +StopSource (int iSource) +{ + audio_SourceStop (soundSource[iSource].handle); + CleanSource (iSource); +} + +BOOLEAN +SoundPlaying (void) +{ + int i; + + for (i = 0; i < NUM_SOUNDSOURCES; ++i) + { + TFB_SoundSample *sample; + sample = soundSource[i].sample; + if (sample && sample->decoder) + { + BOOLEAN result; + LockMutex (soundSource[i].stream_mutex); + result = PlayingStream (i); + UnlockMutex (soundSource[i].stream_mutex); + if (result) + return TRUE; + } + else + { + audio_IntVal state; + audio_GetSourcei (soundSource[i].handle, audio_SOURCE_STATE, &state); + if (state == audio_PLAYING) + return TRUE; + } + } + + return FALSE; +} + +// for now just spin in a sleep() loop +// perhaps later change to condvar implementation +void +WaitForSoundEnd (COUNT Channel) +{ + while (Channel == TFBSOUND_WAIT_ALL ? + SoundPlaying () : ChannelPlaying (Channel)) + { + SleepThread (ONE_SECOND / 20); + if (QuitPosted) // Don't make users wait for sounds to end + break; + } +} + + +// Status: Ignored +BOOLEAN +InitSound (int argc, char* argv[]) +{ + /* Quell compiler warnings */ + (void)argc; + (void)argv; + return TRUE; +} + +// Status: Ignored +void +UninitSound (void) +{ +} + +void +SetSFXVolume (float volume) +{ + int i; + for (i = FIRST_SFX_SOURCE; i <= LAST_SFX_SOURCE; ++i) + { + audio_Sourcef (soundSource[i].handle, audio_GAIN, volume); + } +} + +void +SetSpeechVolume (float volume) +{ + audio_Sourcef (soundSource[SPEECH_SOURCE].handle, audio_GAIN, volume); +} + +DWORD +FadeMusic (BYTE end_vol, SIZE TimeInterval) +{ + if (QuitPosted) // Don't make users wait for fades + TimeInterval = 0; + + if (TimeInterval < 0) + TimeInterval = 0; + + if (!SetMusicStreamFade (TimeInterval, end_vol)) + { // fade rejected, maybe due to TimeInterval==0 + SetMusicVolume (end_vol); + return GetTimeCounter (); + } + else + { + return GetTimeCounter () + TimeInterval + 1; + } +} + + diff --git a/src/libs/sound/sound.h b/src/libs/sound/sound.h new file mode 100644 index 0000000..2a4f447 --- /dev/null +++ b/src/libs/sound/sound.h @@ -0,0 +1,81 @@ +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef LIBS_SOUND_SOUND_H_ // try avoiding collisions on id +#define LIBS_SOUND_SOUND_H_ + +#include "types.h" +#include "audiocore.h" +#include "decoders/decoder.h" +#include "libs/threadlib.h" +#include "libs/sndlib.h" + + +#define FIRST_SFX_SOURCE 0 +#define LAST_SFX_SOURCE (FIRST_SFX_SOURCE + NUM_SFX_CHANNELS - 1) +#define MUSIC_SOURCE (LAST_SFX_SOURCE + 1) +#define SPEECH_SOURCE (MUSIC_SOURCE + 1) +#define NUM_SOUNDSOURCES (SPEECH_SOURCE + 1) + +typedef struct +{ + int in_use; + audio_Object buf_name; + intptr_t data; // user-defined data +} TFB_SoundTag; + +typedef struct tfb_soundcallbacks +{ + // return TRUE to continue, FALSE to abort + bool (* OnStartStream) (TFB_SoundSample*); + // return TRUE to continue, FALSE to abort + bool (* OnEndChunk) (TFB_SoundSample*, audio_Object); + // return TRUE to continue, FALSE to abort + void (* OnEndStream) (TFB_SoundSample*); + // tagged buffer callback + void (* OnTaggedBuffer) (TFB_SoundSample*, TFB_SoundTag*); + // buffer just queued + void (* OnQueueBuffer) (TFB_SoundSample*, audio_Object); +} TFB_SoundCallbacks; + + +extern int musicVolume; +extern float musicVolumeScale; +extern float sfxVolumeScale; +extern float speechVolumeScale; + +void StopSource (int iSource); +void CleanSource (int iSource); + +void SetSFXVolume (float volume); +void SetSpeechVolume (float volume); + +TFB_SoundSample *TFB_CreateSoundSample (TFB_SoundDecoder*, uint32 num_buffers, + const TFB_SoundCallbacks* /* can be NULL */); +void TFB_DestroySoundSample (TFB_SoundSample*); +void TFB_SetSoundSampleData (TFB_SoundSample*, void* data); +void* TFB_GetSoundSampleData (TFB_SoundSample*); +void TFB_SetSoundSampleCallbacks (TFB_SoundSample*, + const TFB_SoundCallbacks* /* can be NULL */); +TFB_SoundDecoder* TFB_GetSoundSampleDecoder (TFB_SoundSample*); + +TFB_SoundTag* TFB_FindTaggedBuffer (TFB_SoundSample*, audio_Object buffer); +void TFB_ClearBufferTag (TFB_SoundTag*); +bool TFB_TagBuffer (TFB_SoundSample*, audio_Object buffer, intptr_t data); + +#include "stream.h" + +#endif // LIBS_SOUND_SOUND_H_ diff --git a/src/libs/sound/stream.c b/src/libs/sound/stream.c new file mode 100644 index 0000000..b7d2718 --- /dev/null +++ b/src/libs/sound/stream.c @@ -0,0 +1,814 @@ +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include <assert.h> +#include <string.h> +#include <stdlib.h> + // for abs() +#include "sound.h" +#include "sndintrn.h" +#include "libs/tasklib.h" +#include "libs/timelib.h" +#include "libs/threadlib.h" +#include "libs/log.h" +#include "libs/memlib.h" + + +static Task decoderTask; + +static TimeCount musicFadeStartTime; +static sint32 musicFadeInterval; +static int musicFadeStartVolume; +static int musicFadeDelta; +// Mutex protects fade structures +static Mutex fade_mutex; + +static void add_scope_data (TFB_SoundSource *source, uint32 bytes); + + +void +PlayStream (TFB_SoundSample *sample, uint32 source, bool looping, bool scope, + bool rewind) +{ + uint32 i; + sint32 offset; + TFB_SoundDecoder *decoder; + + if (!sample) + return; + + StopStream (source); + if (sample->callbacks.OnStartStream && + !sample->callbacks.OnStartStream (sample)) + return; // callback failed + + if (sample->buffer_tag) + memset (sample->buffer_tag, 0, + sample->num_buffers * sizeof (sample->buffer_tag[0])); + + decoder = sample->decoder; + offset = sample->offset; + if (rewind) + SoundDecoder_Rewind (decoder); + else + offset += (sint32)(SoundDecoder_GetTime (decoder) * ONE_SECOND); + + soundSource[source].sample = sample; + decoder->looping = looping; + audio_Sourcei (soundSource[source].handle, audio_LOOPING, false); + + if (scope) + { // Prealloc the scope buffer in advance so that we do not + // realloc it a zillion times + soundSource[source].sbuf_size = sample->num_buffers * + decoder->buffer_size + PAD_SCOPE_BYTES; + soundSource[source].sbuffer = HCalloc (soundSource[source].sbuf_size); + } + + for (i = 0; i < sample->num_buffers; ++i) + { + uint32 decoded_bytes; + + decoded_bytes = SoundDecoder_Decode (decoder); +#if 0 + log_add (log_Debug, "PlayStream(): source:%d filename:%s start:%d " + "position:%d bytes:%d\n", + source, decoder->filename, decoder->start_sample, + decoder->pos, decoded_bytes); +#endif + if (decoded_bytes == 0) + break; + + audio_BufferData (sample->buffer[i], decoder->format, + decoder->buffer, decoded_bytes, decoder->frequency); + audio_SourceQueueBuffers (soundSource[source].handle, 1, + &sample->buffer[i]); + if (sample->callbacks.OnQueueBuffer) + sample->callbacks.OnQueueBuffer (sample, sample->buffer[i]); + + if (scope) + add_scope_data (&soundSource[source], decoded_bytes); + + if (decoder->error != SOUNDDECODER_OK) + { + if (decoder->error != SOUNDDECODER_EOF || + !sample->callbacks.OnEndChunk || + !sample->callbacks.OnEndChunk (sample, sample->buffer[i])) + { // Decoder probably run out of data before we could fill + // all buffers, and OnEndChunk() did not set a new one + break; + } + else + { // OnEndChunk() probably set a new decoder, get it + decoder = sample->decoder; + } + } + } + + soundSource[source].sbuf_lasttime = GetTimeCounter (); + // Adjust the start time so it looks like the stream has been playing + // from the very beginning + soundSource[source].start_time = GetTimeCounter () - offset; + soundSource[source].pause_time = 0; + soundSource[source].stream_should_be_playing = TRUE; + audio_SourcePlay (soundSource[source].handle); +} + +void +StopStream (uint32 source) +{ + StopSource (source); + + soundSource[source].stream_should_be_playing = FALSE; + soundSource[source].sample = NULL; + + if (soundSource[source].sbuffer) + { + void *sbuffer = soundSource[source].sbuffer; + soundSource[source].sbuffer = NULL; + HFree (sbuffer); + } + soundSource[source].sbuf_size = 0; + soundSource[source].sbuf_head = 0; + soundSource[source].sbuf_tail = 0; + soundSource[source].pause_time = 0; +} + +void +PauseStream (uint32 source) +{ + soundSource[source].stream_should_be_playing = FALSE; + if (!soundSource[source].pause_time) + soundSource[source].pause_time = GetTimeCounter (); + audio_SourcePause (soundSource[source].handle); +} + +void +ResumeStream (uint32 source) +{ + if (soundSource[source].pause_time) + { // Adjust the start time so it looks like the stream has + // been playing all this time non-stop + soundSource[source].start_time += GetTimeCounter () + - soundSource[source].pause_time; + } + soundSource[source].pause_time = 0; + soundSource[source].stream_should_be_playing = TRUE; + audio_SourcePlay (soundSource[source].handle); +} + +void +SeekStream (uint32 source, uint32 pos) +{ + TFB_SoundSample* sample = soundSource[source].sample; + bool looping; + bool scope; + + if (!sample) + return; + looping = sample->decoder->looping; + scope = soundSource[source].sbuffer != NULL; + + StopSource (source); + SoundDecoder_Seek (sample->decoder, pos); + PlayStream (sample, source, looping, scope, false); +} + +BOOLEAN +PlayingStream (uint32 source) +{ + return soundSource[source].stream_should_be_playing; +} + + +TFB_SoundSample * +TFB_CreateSoundSample (TFB_SoundDecoder *decoder, uint32 num_buffers, + const TFB_SoundCallbacks *pcbs /* can be NULL */) +{ + TFB_SoundSample *sample; + + sample = HCalloc (sizeof (*sample)); + sample->decoder = decoder; + sample->num_buffers = num_buffers; + sample->buffer = HCalloc (sizeof (audio_Object) * num_buffers); + audio_GenBuffers (num_buffers, sample->buffer); + if (pcbs) + sample->callbacks = *pcbs; + + return sample; +} + +// Deletes all TFB_SoundSample data structures, except decoder +void +TFB_DestroySoundSample (TFB_SoundSample *sample) +{ + if (sample->buffer) + { + audio_DeleteBuffers (sample->num_buffers, sample->buffer); + HFree (sample->buffer); + } + HFree (sample->buffer_tag); + HFree (sample); +} + +void +TFB_SetSoundSampleData (TFB_SoundSample *sample, void* data) +{ + sample->data = data; +} + +void* +TFB_GetSoundSampleData (TFB_SoundSample *sample) +{ + return sample->data; +} + +void +TFB_SetSoundSampleCallbacks (TFB_SoundSample *sample, + const TFB_SoundCallbacks *pcbs /* can be NULL */) +{ + if (pcbs) + sample->callbacks = *pcbs; + else + memset (&sample->callbacks, 0, sizeof (sample->callbacks)); +} + +TFB_SoundDecoder* +TFB_GetSoundSampleDecoder (TFB_SoundSample *sample) +{ + return sample->decoder; +} + +TFB_SoundTag* +TFB_FindTaggedBuffer (TFB_SoundSample *sample, audio_Object buffer) +{ + uint32 buf_num; + + if (!sample->buffer_tag) + return NULL; // do not have any tags + + for (buf_num = 0; + buf_num < sample->num_buffers && + (!sample->buffer_tag[buf_num].in_use || + sample->buffer_tag[buf_num].buf_name != buffer + ); + buf_num++) + ; + + return buf_num < sample->num_buffers ? + &sample->buffer_tag[buf_num] : NULL; +} + +bool +TFB_TagBuffer (TFB_SoundSample *sample, audio_Object buffer, intptr_t data) +{ + uint32 buf_num; + + if (!sample->buffer_tag) + sample->buffer_tag = HCalloc (sizeof (TFB_SoundTag) * + sample->num_buffers); + + for (buf_num = 0; + buf_num < sample->num_buffers && + sample->buffer_tag[buf_num].in_use && + sample->buffer_tag[buf_num].buf_name != buffer; + buf_num++) + ; + + if (buf_num >= sample->num_buffers) + return false; // no empty slot + + sample->buffer_tag[buf_num].in_use = 1; + sample->buffer_tag[buf_num].buf_name = buffer; + sample->buffer_tag[buf_num].data = data; + + return true; +} + +void +TFB_ClearBufferTag (TFB_SoundTag *ptag) +{ + ptag->in_use = 0; + ptag->buf_name = 0; +} + +static void +remove_scope_data (TFB_SoundSource *source, audio_Object buffer) +{ + audio_IntVal buf_size; + + audio_GetBufferi (buffer, audio_SIZE, &buf_size); + source->sbuf_head += buf_size; + // the buffer is cyclic + source->sbuf_head %= source->sbuf_size; + + source->sbuf_lasttime = GetTimeCounter (); +} + +static void +add_scope_data (TFB_SoundSource *source, uint32 bytes) +{ + uint8 *sbuffer = source->sbuffer; + uint8 *dec_buf = source->sample->decoder->buffer; + uint32 tail_bytes; + uint32 wrap_bytes; + + if (source->sbuf_tail + bytes > source->sbuf_size) + { // does not fit at the tail, have to split it up + tail_bytes = source->sbuf_size - source->sbuf_tail; + wrap_bytes = bytes - tail_bytes; + } + else + { // all fits at the tail + tail_bytes = bytes; + wrap_bytes = 0; + } + + if (tail_bytes) + { + memcpy (sbuffer + source->sbuf_tail, dec_buf, tail_bytes); + source->sbuf_tail += tail_bytes; + } + + if (wrap_bytes) + { + memcpy (sbuffer, dec_buf + tail_bytes, wrap_bytes); + source->sbuf_tail = wrap_bytes; + } +} + +static void +process_stream (TFB_SoundSource *source) +{ + TFB_SoundSample *sample = source->sample; + TFB_SoundDecoder *decoder = sample->decoder; + bool end_chunk_failed = false; + audio_IntVal processed; + audio_IntVal queued; + + audio_GetSourcei (source->handle, audio_BUFFERS_PROCESSED, &processed); + audio_GetSourcei (source->handle, audio_BUFFERS_QUEUED, &queued); + + if (processed == 0) + { // Nothing was played + audio_IntVal state; + + audio_GetSourcei (source->handle, audio_SOURCE_STATE, &state); + if (state != audio_PLAYING) + { + if (queued == 0 && decoder->error == SOUNDDECODER_EOF) + { // The stream has reached the end + log_add (log_Info, "StreamDecoderTaskFunc(): " + "finished playing %s", decoder->filename); + source->stream_should_be_playing = FALSE; + + if (sample->callbacks.OnEndStream) + sample->callbacks.OnEndStream (sample); + } + else + { + log_add (log_Warning, "StreamDecoderTaskFunc(): " + "buffer underrun playing %s", decoder->filename); + audio_SourcePlay (source->handle); + } + } + } + + // Unqueue processed buffers and replace them with new ones + for (; processed > 0; --processed) + { + uint32 error; + audio_Object buffer; + uint32 decoded_bytes; + + audio_GetError (); // clear error state + + // Get the buffer that finished playing + audio_SourceUnqueueBuffers (source->handle, 1, &buffer); + error = audio_GetError(); + if (error != audio_NO_ERROR) + { + log_add (log_Warning, "StreamDecoderTaskFunc(): " + "error after audio_SourceUnqueueBuffers: %x, file %s", + error, decoder->filename); + break; + } + + // Process a callback on a tagged buffer, if any + if (sample->callbacks.OnTaggedBuffer) + { + TFB_SoundTag* tag = TFB_FindTaggedBuffer (sample, buffer); + if (tag) + sample->callbacks.OnTaggedBuffer (sample, tag); + } + + if (source->sbuffer) + remove_scope_data (source, buffer); + + // See what state the decoder was left in last time around + if (decoder->error != SOUNDDECODER_OK) + { + if (decoder->error == SOUNDDECODER_EOF) + { + if (end_chunk_failed) + continue; // should not do it again + + if (!sample->callbacks.OnEndChunk || + !sample->callbacks.OnEndChunk (sample, source->last_q_buf)) + { // Reached the end of the current stream and we did not + // get another sample to play (relevant for Trackplayer) + end_chunk_failed = true; + continue; + } + else + { // OnEndChunk succeeded, so someone (read: Trackplayer) + // wants to keep going, probably with a new decoder. + // Get the new decoder + decoder = sample->decoder; + } + } + else + { // Decoder returned a real error, keep going +#if 0 + log_add (log_Debug, "StreamDecoderTaskFunc(): " + "decoder->error is %d for %s", decoder->error, + decoder->filename); +#endif + continue; + } + } + + // Now replace the unqueued buffer with a new one + decoded_bytes = SoundDecoder_Decode (decoder); + if (decoder->error == SOUNDDECODER_ERROR) + { + log_add (log_Warning, "StreamDecoderTaskFunc(): " + "SoundDecoder_Decode error %d, file %s", + decoder->error, decoder->filename); + source->stream_should_be_playing = FALSE; + continue; + } + + if (decoded_bytes == 0) + { // Nothing was decoded, keep going + continue; + // This loses a stream buffer, which we cannot get back + // w/o restarting the stream, but we should never get here. + } + + // And a new buffer is born + audio_BufferData (buffer, decoder->format, decoder->buffer, + decoded_bytes, decoder->frequency); + error = audio_GetError(); + if (error != audio_NO_ERROR) + { + log_add (log_Warning, "StreamDecoderTaskFunc(): " + "error after audio_BufferData: %x, file %s, decoded %d", + error, decoder->filename, decoded_bytes); + continue; + } + + // Now queue the buffer + audio_SourceQueueBuffers (source->handle, 1, &buffer); + error = audio_GetError(); + if (error != audio_NO_ERROR) + { + log_add (log_Warning, "StreamDecoderTaskFunc(): " + "error after audio_SourceQueueBuffers: %x, file %s, " + "decoded %d", error, decoder->filename, decoded_bytes); + continue; + } + + // Remember the last queued buffer so we can pass it to callbacks + source->last_q_buf = buffer; + if (sample->callbacks.OnQueueBuffer) + sample->callbacks.OnQueueBuffer (sample, buffer); + + if (source->sbuffer) + add_scope_data (source, decoded_bytes); + } +} + +static void +processMusicFade (void) +{ + TimeCount Now; + sint32 elapsed; + int newVolume; + + LockMutex (fade_mutex); + + if (!musicFadeInterval) + { // there is no fade set + UnlockMutex (fade_mutex); + return; + } + + Now = GetTimeCounter (); + elapsed = Now - musicFadeStartTime; + if (elapsed > musicFadeInterval) + elapsed = musicFadeInterval; + + newVolume = musicFadeStartVolume + (long)musicFadeDelta * elapsed + / musicFadeInterval; + SetMusicVolume (newVolume); + + if (elapsed >= musicFadeInterval) + musicFadeInterval = 0; // fade is over + + UnlockMutex (fade_mutex); +} + +static int +StreamDecoderTaskFunc (void *data) +{ + Task task = (Task)data; + int active_streams; + int i; + + while (!Task_ReadState (task, TASK_EXIT)) + { + active_streams = 0; + + processMusicFade (); + + for (i = MUSIC_SOURCE; i < NUM_SOUNDSOURCES; ++i) + { + TFB_SoundSource *source = &soundSource[i]; + + LockMutex (source->stream_mutex); + + if (!source->sample || + !source->sample->decoder || + !source->stream_should_be_playing || + source->sample->decoder->error == SOUNDDECODER_ERROR) + { + UnlockMutex (source->stream_mutex); + continue; + } + + process_stream (source); + active_streams++; + + UnlockMutex (source->stream_mutex); + } + + if (active_streams == 0) + { // Throttle down the thread when there are no active streams + HibernateThread (ONE_SECOND / 10); + } + else + TaskSwitch (); + } + + FinishTask (task); + return 0; +} + +static inline sint32 +readSoundSample (void *ptr, int sample_size) +{ + if (sample_size == sizeof (uint8)) + return (*(uint8*)ptr - 128) << 8; + else + return *(sint16*)ptr; +} + +// Graphs the current sound data for the oscilloscope. +// Includes a rudimentary automatic gain control (AGC) to properly graph +// the streams at different gain levels (based on running average). +// We use AGC because different pieces of music and speech can easily be +// at very different gain levels, because the game is moddable. +int +GraphForegroundStream (uint8 *data, sint32 width, sint32 height, + bool wantSpeech) +{ + int source_num; + TFB_SoundSource *source; + TFB_SoundDecoder *decoder; + int channels; + int sample_size; + int full_sample; + int step; + long played_time; + long delta; + uint8 *sbuffer; + unsigned long pos; + int scale; + sint32 i; + // AGC variables +#define DEF_PAGE_MAX 28000 +#define AGC_PAGE_COUNT 16 + static int page_sum = DEF_PAGE_MAX * AGC_PAGE_COUNT; + static int pages[AGC_PAGE_COUNT] = + { + DEF_PAGE_MAX, DEF_PAGE_MAX, DEF_PAGE_MAX, DEF_PAGE_MAX, + DEF_PAGE_MAX, DEF_PAGE_MAX, DEF_PAGE_MAX, DEF_PAGE_MAX, + DEF_PAGE_MAX, DEF_PAGE_MAX, DEF_PAGE_MAX, DEF_PAGE_MAX, + DEF_PAGE_MAX, DEF_PAGE_MAX, DEF_PAGE_MAX, DEF_PAGE_MAX, + }; + static int page_head; +#define AGC_FRAME_COUNT 8 + static int frame_sum; + static int frames; + static int avg_amp = DEF_PAGE_MAX; // running amplitude (sort of) average + int target_amp; + int max_a; +#define VAD_MIN_ENERGY 100 + long energy; + + + // Prefer speech to music + source_num = SPEECH_SOURCE; + source = &soundSource[source_num]; + LockMutex (source->stream_mutex); + if (wantSpeech && (!source->sample || + !source->sample->decoder || !source->sample->decoder->is_null)) + { // Use speech waveform, since it's available + // Step is picked experimentally. Using step of 1 sample at 11025Hz, + // because human speech is mostly in the low frequencies, and it looks + // better this way. + step = 1; + } + else + { // We do not have speech -- use music waveform + UnlockMutex (source->stream_mutex); + + source_num = MUSIC_SOURCE; + source = &soundSource[source_num]; + LockMutex (source->stream_mutex); + + // Step is picked experimentally. Using step of 4 samples at 11025Hz. + // It looks better this way. + step = 4; + } + + if (!PlayingStream (source_num) || !source->sample + || !source->sample->decoder || !source->sbuffer + || source->sbuf_size == 0) + { // We don't have data to return, oh well. + UnlockMutex (source->stream_mutex); + return 0; + } + decoder = source->sample->decoder; + + if (!audio_GetFormatInfo (decoder->format, &channels, &sample_size)) + { + UnlockMutex (source->stream_mutex); + log_add (log_Debug, "GraphForegroundStream(): uknown format %u", + (unsigned)decoder->format); + return 0; + } + full_sample = channels * sample_size; + + // See how far into the buffer we should be now + played_time = GetTimeCounter () - source->sbuf_lasttime; + delta = played_time * decoder->frequency * full_sample / ONE_SECOND; + // align delta to sample start + delta = delta & ~(full_sample - 1); + + if (delta < 0) + { + log_add (log_Debug, "GraphForegroundStream(): something is messed" + " with timing, delta %ld", delta); + delta = 0; + } + else if (delta > (long)source->sbuf_size) + { // Stream decoder task has just had a heart attack, not much we can do + delta = 0; + } + + // Step is in 11025 Hz units, so we need to adjust to source frequency + step = decoder->frequency * step / 11025; + if (step == 0) + step = 1; + step *= full_sample; + + sbuffer = source->sbuffer; + pos = source->sbuf_head + delta; + + // We are not basing the scaling factor on signal energy, because we + // want it to *look* pretty instead of sounding nice and even + target_amp = (height >> 1) >> 1; + scale = avg_amp / target_amp; + + max_a = 0; + energy = 0; + for (i = 0; i < width; ++i, pos += step) + { + sint32 s; + int t; + + pos %= source->sbuf_size; + + s = readSoundSample (sbuffer + pos, sample_size); + if (channels > 1) + s += readSoundSample (sbuffer + pos + sample_size, sample_size); + + energy += (s * s) / 0x10000; + t = abs(s); + if (t > max_a) + max_a = t; + + s = (s / scale) + (height >> 1); + if (s < 0) + s = 0; + else if (s > height - 1) + s = height - 1; + + data[i] = s; + } + energy /= width; + + // Very basic VAD. We don't want to count speech pauses in the average + if (energy > VAD_MIN_ENERGY) + { + // Record the maximum amplitude (sort of) + frame_sum += max_a; + ++frames; + if (frames == AGC_FRAME_COUNT) + { // Got a full page + frame_sum /= AGC_FRAME_COUNT; + // Record the page + page_sum -= pages[page_head]; + page_sum += frame_sum; + pages[page_head] = frame_sum; + page_head = (page_head + 1) % AGC_PAGE_COUNT; + + frame_sum = 0; + frames = 0; + + avg_amp = page_sum / AGC_PAGE_COUNT; + } + } + + UnlockMutex (source->stream_mutex); + return 1; +} + +// This function is normally called on the Starcon2Main thread +bool +SetMusicStreamFade (sint32 howLong, int endVolume) +{ + bool ret = true; + + LockMutex (fade_mutex); + + if (howLong < 0) + howLong = 0; + + musicFadeStartTime = GetTimeCounter (); + musicFadeInterval = howLong; + musicFadeStartVolume = musicVolume; + musicFadeDelta = endVolume - musicFadeStartVolume; + if (!musicFadeInterval) + ret = false; // reject + + UnlockMutex (fade_mutex); + + return ret; +} + +int +InitStreamDecoder (void) +{ + fade_mutex = CreateMutex ("Stream fade mutex", SYNC_CLASS_AUDIO); + if (!fade_mutex) + return -1; + + decoderTask = AssignTask (StreamDecoderTaskFunc, 1024, + "audio stream decoder"); + if (!decoderTask) + return -1; + + return 0; +} + +void +UninitStreamDecoder (void) +{ + if (decoderTask) + { + ConcludeTask (decoderTask); + decoderTask = NULL; + } + + if (fade_mutex) + { + DestroyMutex (fade_mutex); + fade_mutex = NULL; + } +} diff --git a/src/libs/sound/stream.h b/src/libs/sound/stream.h new file mode 100644 index 0000000..5766132 --- /dev/null +++ b/src/libs/sound/stream.h @@ -0,0 +1,37 @@ +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef STREAM_H +#define STREAM_H + +int InitStreamDecoder (void); +void UninitStreamDecoder (void); + +void PlayStream (TFB_SoundSample *sample, uint32 source, bool looping, + bool scope, bool rewind); +void StopStream (uint32 source); +void PauseStream (uint32 source); +void ResumeStream (uint32 source); +void SeekStream (uint32 source, uint32 pos); +BOOLEAN PlayingStream (uint32 source); + +int GraphForegroundStream (uint8 *data, sint32 width, sint32 height, + bool wantSpeech); + +// returns TRUE if the fade was accepted by stream decoder +bool SetMusicStreamFade (sint32 howLong, int endVolume); + +#endif diff --git a/src/libs/sound/trackint.h b/src/libs/sound/trackint.h new file mode 100644 index 0000000..fe39740 --- /dev/null +++ b/src/libs/sound/trackint.h @@ -0,0 +1,41 @@ +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef TRACKINT_H +#define TRACKINT_H + +#include "libs/callback.h" + +struct tfb_soundchunk +{ + TFB_SoundDecoder *decoder; // decoder for this chunk + float start_time; // relative time from track start + int tag_me; // set for chunks with subtitles + uint32 track_num; // logical track #, comm code needs this + UNICODE *text; // subtitle text + CallbackFunction callback; // comm callback, executed on chunk start + struct tfb_soundchunk *next; +}; + +typedef struct tfb_soundchunk TFB_SoundChunk; + +TFB_SoundChunk *create_SoundChunk (TFB_SoundDecoder *decoder, float start_time); +void destroy_SoundChunk_list (TFB_SoundChunk *chain); +TFB_SoundChunk *find_next_page (TFB_SoundChunk *cur); +TFB_SoundChunk *find_prev_page (TFB_SoundChunk *cur); + + +#endif // TRACKINT_H diff --git a/src/libs/sound/trackplayer.c b/src/libs/sound/trackplayer.c new file mode 100644 index 0000000..8068fc3 --- /dev/null +++ b/src/libs/sound/trackplayer.c @@ -0,0 +1,874 @@ +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "sound.h" +#include "sndintrn.h" +#include "libs/sound/trackplayer.h" +#include "trackint.h" +#include "libs/log.h" +#include "libs/memlib.h" +#include "options.h" +#include <ctype.h> +#include <stdlib.h> +#include <string.h> +#include <memory.h> + + +static int track_count; // total number of tracks +static int no_page_break; // set when combining several tracks into one + +// The one and only sample we play. Track switching is done by modifying +// this sample while it is playing. StreamDecoderTaskFunc() picks up the +// changes *mostly* seamlessly (keyword: mostly). +// This is technically a hack, but a decent one ;) +static TFB_SoundSample *sound_sample; + +static volatile uint32 tracks_length; // total length of tracks in game units + +static TFB_SoundChunk *chunks_head; // first decoder in linked list +static TFB_SoundChunk *chunks_tail; // last decoder in linked list +static TFB_SoundChunk *last_sub; // last chunk in the list with a subtitle + +static TFB_SoundChunk *cur_chunk; // currently playing chunk +static TFB_SoundChunk *cur_sub_chunk; // currently displayed subtitle chunk + +// Accesses to cur_chunk and cur_sub_chunk are guarded by stream_mutex, +// because these should only be accesses by the DoInput and the +// stream player threads. Any other accesses would go unguarded. +// Other data structures are unguarded and should only be accessed from +// the DoInput thread at certain times, i.e. nothing can be modified +// between StartTrack() and JumpTrack()/StopTrack() calls. +// Use caution when changing code, as you may need to guard other data +// structures the same way. + +static void seek_track (sint32 offset); + +// stream callbacks +static bool OnStreamStart (TFB_SoundSample* sample); +static bool OnChunkEnd (TFB_SoundSample* sample, audio_Object buffer); +static void OnStreamEnd (TFB_SoundSample* sample); +static void OnBufferTag (TFB_SoundSample* sample, TFB_SoundTag* tag); + +static TFB_SoundCallbacks trackCBs = +{ + OnStreamStart, + OnChunkEnd, + OnStreamEnd, + OnBufferTag, + NULL +}; + +static inline sint32 +chunk_end_time (TFB_SoundChunk *chunk) +{ + return (sint32) ((chunk->start_time + chunk->decoder->length) + * ONE_SECOND); +} + +static inline sint32 +tracks_end_time (void) +{ + return chunk_end_time (chunks_tail); +} + +//JumpTrack currently aborts the current track. However, it doesn't clear the +//data-structures as StopTrack does. this allows for rewind even after the +//track has finished playing +//Question: Should 'abort' call StopTrack? +void +JumpTrack (void) +{ + if (!sound_sample) + return; // nothing to skip + + LockMutex (soundSource[SPEECH_SOURCE].stream_mutex); + seek_track (tracks_length + 1); + UnlockMutex (soundSource[SPEECH_SOURCE].stream_mutex); +} + +// This should just start playing a stream +void +PlayTrack (void) +{ + if (!sound_sample) + return; // nothing to play + + LockMutex (soundSource[SPEECH_SOURCE].stream_mutex); + tracks_length = tracks_end_time (); + // decoder will be set in OnStreamStart() + cur_chunk = chunks_head; + // Always scope the speech data, we may need it + PlayStream (sound_sample, SPEECH_SOURCE, false, true, true); + UnlockMutex (soundSource[SPEECH_SOURCE].stream_mutex); +} + +void +PauseTrack (void) +{ + if (!sound_sample) + return; // nothing to pause + + LockMutex (soundSource[SPEECH_SOURCE].stream_mutex); + PauseStream (SPEECH_SOURCE); + UnlockMutex (soundSource[SPEECH_SOURCE].stream_mutex); +} + +// ResumeTrack should resume a paused track, and do nothing for a playing track +void +ResumeTrack (void) +{ + audio_IntVal state; + + if (!sound_sample) + return; // nothing to resume + + LockMutex (soundSource[SPEECH_SOURCE].stream_mutex); + + if (!cur_chunk) + { // not playing anything, so no resuming + UnlockMutex (soundSource[SPEECH_SOURCE].stream_mutex); + return; + } + + audio_GetSourcei (soundSource[SPEECH_SOURCE].handle, audio_SOURCE_STATE, &state); + if (state == audio_PAUSED) + ResumeStream (SPEECH_SOURCE); + + UnlockMutex (soundSource[SPEECH_SOURCE].stream_mutex); +} + +COUNT +PlayingTrack (void) +{ + // This ignores the paused state and simply returns what track + // *should* be playing + COUNT result = 0; // default is none + + if (!sound_sample) + return 0; // not playing anything + + LockMutex (soundSource[SPEECH_SOURCE].stream_mutex); + if (cur_chunk) + result = cur_chunk->track_num + 1; + UnlockMutex (soundSource[SPEECH_SOURCE].stream_mutex); + + return result; +} + +void +StopTrack (void) +{ + LockMutex (soundSource[SPEECH_SOURCE].stream_mutex); + StopStream (SPEECH_SOURCE); + track_count = 0; + tracks_length = 0; + cur_chunk = NULL; + cur_sub_chunk = NULL; + UnlockMutex (soundSource[SPEECH_SOURCE].stream_mutex); + + if (chunks_head) + { + chunks_tail = NULL; + destroy_SoundChunk_list (chunks_head); + chunks_head = NULL; + last_sub = NULL; + } + if (sound_sample) + { + // We delete the decoders ourselves + sound_sample->decoder = NULL; + TFB_DestroySoundSample (sound_sample); + sound_sample = NULL; + } +} + +static void +DoTrackTag (TFB_SoundChunk *chunk) +{ + LockMutex (soundSource[SPEECH_SOURCE].stream_mutex); + if (chunk->callback) + chunk->callback(0); + + cur_sub_chunk = chunk; + UnlockMutex (soundSource[SPEECH_SOURCE].stream_mutex); +} + +// This func is called by PlayStream() when stream is about +// to start. We have a chance to tweak the stream here. +// This is called on the DoInput thread. +static bool +OnStreamStart (TFB_SoundSample* sample) +{ + if (sample != sound_sample) + return false; // Huh? Why did we get called on this? + + if (!cur_chunk) + return false; // Stream shouldn't be playing at all + + // Adjust the sample to play what we want + sample->decoder = cur_chunk->decoder; + sample->offset = (sint32) (cur_chunk->start_time * ONE_SECOND); + + if (cur_chunk->tag_me) + DoTrackTag (cur_chunk); + + return true; +} + +// This func is called by StreamDecoderTaskFunc() when the last buffer +// of the current chunk has been decoded (not when it has been *played*). +// This is called on the stream task thread. +static bool +OnChunkEnd (TFB_SoundSample* sample, audio_Object buffer) +{ + if (sample != sound_sample) + return false; // Huh? Why did we get called on this? + + if (!cur_chunk || !cur_chunk->next) + { // all chunks and tracks are done + return false; + } + + // Move on to the next chunk + cur_chunk = cur_chunk->next; + // Adjust the sample to play what we want + sample->decoder = cur_chunk->decoder; + SoundDecoder_Rewind (sample->decoder); + + log_add (log_Info, "Switching to stream %s at pos %d", + sample->decoder->filename, sample->decoder->start_sample); + + if (cur_chunk->tag_me) + { // Tag the last buffer of the chunk with the next chunk + TFB_TagBuffer (sample, buffer, (intptr_t)cur_chunk); + } + + return true; +} + +// This func is called by StreamDecoderTaskFunc() when stream has ended +// This is called on the stream task thread. +static void +OnStreamEnd (TFB_SoundSample* sample) +{ + if (sample != sound_sample) + return; // Huh? Why did we get called on this? + + cur_chunk = NULL; + cur_sub_chunk = NULL; +} + +// This func is called by StreamDecoderTaskFunc() when a tagged buffer +// has finished playing. +// This is called on the stream task thread. +static void +OnBufferTag (TFB_SoundSample* sample, TFB_SoundTag* tag) +{ + TFB_SoundChunk* chunk = (TFB_SoundChunk*) tag->data; + + assert (sizeof (tag->data) >= sizeof (chunk)); + + if (sample != sound_sample) + return; // Huh? Why did we get called on this? + + TFB_ClearBufferTag (tag); + DoTrackTag (chunk); +} + +// Parse the timestamps string into an int array. +// Rerturns number of timestamps parsed. +static int +GetTimeStamps (UNICODE *TimeStamps, sint32 *time_stamps) +{ + int pos; + int num = 0; + + while (*TimeStamps && (pos = strcspn (TimeStamps, ",\r\n"))) + { + UNICODE valStr[32]; + uint32 val; + + memcpy (valStr, TimeStamps, pos); + valStr[pos] = '\0'; + val = strtoul (valStr, NULL, 10); + if (val) + { + *time_stamps = val; + num++; + time_stamps++; + } + TimeStamps += pos; + TimeStamps += strspn (TimeStamps, ",\r\n"); + } + return (num); +} + +#define TEXT_SPEED 80 +// Returns number of parsed pages +static int +SplitSubPages (UNICODE *text, UNICODE *pages[], sint32 timestamp[], int size) +{ + int lead_ellips = 0; + COUNT page; + + for (page = 0; page < size && *text != '\0'; ++page) + { + int aft_ellips; + int pos; + + // seek to EOL or end of the string + pos = strcspn (text, "\r\n"); + // XXX: this will only work when ASCII punctuation and spaces + // are used exclusively + aft_ellips = 3 * (text[pos] != '\0' && pos > 0 && + !ispunct (text[pos - 1]) && !isspace (text[pos - 1])); + pages[page] = HMalloc (sizeof (UNICODE) * + (lead_ellips + pos + aft_ellips + 1)); + if (lead_ellips) + strcpy (pages[page], ".."); + memcpy (pages[page] + lead_ellips, text, pos); + pages[page][lead_ellips + pos] = '\0'; // string term + if (aft_ellips) + strcpy (pages[page] + lead_ellips + pos, "..."); + timestamp[page] = pos * TEXT_SPEED; + if (timestamp[page] < 1000) + timestamp[page] = 1000; + lead_ellips = aft_ellips ? 2 : 0; + text += pos; + // Skip any EOL + text += strspn (text, "\r\n"); + } + + return page; +} + +// decodes several tracks into one and adds it to queue +// track list is NULL-terminated +// May only be called after at least one SpliceTrack(). This is a limitation +// for the sake of timestamps, but it does not have to be so. +void +SpliceMultiTrack (UNICODE *TrackNames[], UNICODE *TrackText) +{ +#define MAX_MULTI_TRACKS 20 +#define MAX_MULTI_BUFFERS 100 + TFB_SoundDecoder* track_decs[MAX_MULTI_TRACKS + 1]; + int tracks; + int slen1, slen2; + + if (!TrackText) + { + log_add (log_Debug, "SpliceMultiTrack(): no track text"); + return; + } + + if (!sound_sample || !chunks_tail) + { + log_add (log_Warning, "SpliceMultiTrack(): Cannot be called before SpliceTrack()"); + return; + } + + log_add (log_Info, "SpliceMultiTrack(): loading..."); + for (tracks = 0; *TrackNames && tracks < MAX_MULTI_TRACKS; TrackNames++, tracks++) + { + track_decs[tracks] = SoundDecoder_Load (contentDir, *TrackNames, + 32768, 0, - 3 * TEXT_SPEED); + if (track_decs[tracks]) + { + log_add (log_Info, " track: %s, decoder: %s, rate %d format %x", + *TrackNames, + SoundDecoder_GetName (track_decs[tracks]), + track_decs[tracks]->frequency, + track_decs[tracks]->format); + SoundDecoder_DecodeAll (track_decs[tracks]); + + chunks_tail->next = create_SoundChunk (track_decs[tracks], sound_sample->length); + chunks_tail = chunks_tail->next; + chunks_tail->track_num = track_count - 1; + sound_sample->length += track_decs[tracks]->length; + } + else + { + log_add (log_Warning, "SpliceMultiTrack(): couldn't load %s\n", + *TrackNames); + tracks--; + } + } + track_decs[tracks] = 0; // term + + if (tracks == 0) + { + log_add (log_Warning, "SpliceMultiTrack(): no tracks loaded"); + return; + } + + slen1 = strlen (last_sub->text); + slen2 = strlen (TrackText); + last_sub->text = HRealloc (last_sub->text, slen1 + slen2 + 1); + strcpy (last_sub->text + slen1, TrackText); + + no_page_break = 1; +} + +// XXX: This code and the entire trackplayer are begging to be overhauled +void +SpliceTrack (UNICODE *TrackName, UNICODE *TrackText, UNICODE *TimeStamp, CallbackFunction cb) +{ + static UNICODE last_track_name[128] = ""; + static unsigned long dec_offset = 0; +#define MAX_PAGES 50 + UNICODE *pages[MAX_PAGES]; + sint32 time_stamps[MAX_PAGES]; + int num_pages; + int page; + + if (!TrackText) + return; + + if (!TrackName) + { // Appending a piece of subtitles to the last track + int slen1, slen2; + + if (track_count == 0) + { + log_add (log_Warning, "SpliceTrack(): Tried to append a subtitle," + " but no current track"); + return; + } + + if (!last_sub || !last_sub->text) + { + log_add (log_Warning, "SpliceTrack(): Tried to append a subtitle" + " to a NULL string"); + return; + } + + num_pages = SplitSubPages (TrackText, pages, time_stamps, MAX_PAGES); + if (num_pages == 0) + { + log_add (log_Warning, "SpliceTrack(): Failed to parse subtitles"); + return; + } + // The last page's stamp is a suggested value. The track should + // actually play to the end. + time_stamps[num_pages - 1] = -time_stamps[num_pages - 1]; + + // Add the first piece to the last subtitle page + slen1 = strlen (last_sub->text); + slen2 = strlen (pages[0]); + last_sub->text = HRealloc (last_sub->text, slen1 + slen2 + 1); + strcpy (last_sub->text + slen1, pages[0]); + HFree (pages[0]); + + // Add the rest of the pages + for (page = 1; page < num_pages; ++page) + { + TFB_SoundChunk *next_sub = find_next_page (last_sub); + if (next_sub) + { // nodes prepared by previous call, just fill in the subs + next_sub->text = pages[page]; + last_sub = next_sub; + } + else + { // probably no timestamps were provided, so need more work + TFB_SoundDecoder *decoder = SoundDecoder_Load (contentDir, + last_track_name, 4096, dec_offset, time_stamps[page]); + if (!decoder) + { + log_add (log_Warning, "SpliceTrack(): couldn't load %s", TrackName); + break; + } + dec_offset += (unsigned long)(decoder->length * 1000); + chunks_tail->next = create_SoundChunk (decoder, sound_sample->length); + chunks_tail = chunks_tail->next; + chunks_tail->tag_me = 1; + chunks_tail->track_num = track_count - 1; + chunks_tail->text = pages[page]; + chunks_tail->callback = cb; + // TODO: We may have to tag only one page with a callback + //cb = NULL; + last_sub = chunks_tail; + sound_sample->length += decoder->length; + } + } + } + else + { // Adding a new track + int num_timestamps = 0; + + utf8StringCopy (last_track_name, sizeof (last_track_name), TrackName); + + num_pages = SplitSubPages (TrackText, pages, time_stamps, MAX_PAGES); + if (num_pages == 0) + { + log_add (log_Warning, "SpliceTrack(): Failed to parse sutitles"); + return; + } + // The last page's stamp is a suggested value. The track should + // actually play to the end. + time_stamps[num_pages - 1] = -time_stamps[num_pages - 1]; + + if (no_page_break && track_count) + { + int slen1, slen2; + + slen1 = strlen (last_sub->text); + slen2 = strlen (pages[0]); + last_sub->text = HRealloc (last_sub->text, slen1 + slen2 + 1); + strcpy (last_sub->text + slen1, pages[0]); + HFree (pages[0]); + } + else + track_count++; + + log_add (log_Info, "SpliceTrack(): loading %s", TrackName); + + if (TimeStamp) + { + num_timestamps = GetTimeStamps (TimeStamp, time_stamps) + 1; + if (num_timestamps < num_pages) + { + log_add (log_Warning, "SpliceTrack(): number of timestamps" + " doesn't match number of pages!"); + } + else if (num_timestamps > num_pages) + { // We most likely will get more subtitles appended later + // Set the last page to the rest of the track + time_stamps[num_timestamps - 1] = -100000; + } + } + else + { + num_timestamps = num_pages; + } + + // Reset the offset for the new track + dec_offset = 0; + for (page = 0; page < num_timestamps; ++page) + { + TFB_SoundDecoder *decoder = SoundDecoder_Load (contentDir, + TrackName, 4096, dec_offset, time_stamps[page]); + if (!decoder) + { + log_add (log_Warning, "SpliceTrack(): couldn't load %s", TrackName); + break; + } + + if (!sound_sample) + { + sound_sample = TFB_CreateSoundSample (NULL, 8, &trackCBs); + chunks_head = create_SoundChunk (decoder, 0.0); + chunks_tail = chunks_head; + } + else + { + chunks_tail->next = create_SoundChunk (decoder, sound_sample->length); + chunks_tail = chunks_tail->next; + } + dec_offset += (unsigned long)(decoder->length * 1000); +#if 0 + log_add (log_Debug, "page (%d of %d): %d ts: %d", + page, num_pages, + dec_offset, time_stamps[page]); +#endif + sound_sample->length += decoder->length; + chunks_tail->track_num = track_count - 1; + if (!no_page_break) + { + chunks_tail->tag_me = 1; + if (page < num_pages) + { + chunks_tail->text = pages[page]; + last_sub = chunks_tail; + } + chunks_tail->callback = cb; + // TODO: We may have to tag only one page with a callback + //cb = NULL; + } + no_page_break = 0; + } + } +} + +// This function figures out the chunk that should be playing based on +// 'offset' into the total playing time of all tracks. It then sets +// the speech source's sample to the necessary decoder and seeks the +// decoder to the proper point. +// XXX: This means that whatever speech has already been queued on the +// source will continue playing, so we may need some small timing +// adjustments. It may be simpler to just call PlayStream(). +static void +seek_track (sint32 offset) +{ + TFB_SoundChunk *cur; + TFB_SoundChunk *last_tag = NULL; + + if (!sound_sample) + return; // nothing to recompute + + if (offset < 0) + offset = 0; + else if ((uint32)offset > tracks_length) + offset = tracks_length + 1; + + // Adjusting the stream start time is the only way we can arbitrarily + // seek the stream right now + soundSource[SPEECH_SOURCE].start_time = GetTimeCounter () - offset; + + // Find the chunk that should be playing at this time offset + for (cur = chunks_head; cur && offset >= chunk_end_time (cur); + cur = cur->next) + { + // .. looking for the last callback as we go along + // XXX: this effectively set the last point where Fot is looking at. + // TODO: this should be somehow changed if we implement more + // callbacks, like Melnorme trading, offloading at Starbase, etc. + if (cur->tag_me) + last_tag = cur; + } + + if (cur) + { + cur_chunk = cur; + SoundDecoder_Seek (cur->decoder, (uint32) (((float)offset / ONE_SECOND + - cur->start_time) * 1000)); + sound_sample->decoder = cur->decoder; + + if (cur->tag_me) + last_tag = cur; + if (last_tag) + DoTrackTag (last_tag); + } + else + { // The offset is beyond the length of all tracks + StopStream (SPEECH_SOURCE); + cur_chunk = NULL; + cur_sub_chunk = NULL; + } +} + +static sint32 +get_current_track_pos (void) +{ + sint32 start_time = soundSource[SPEECH_SOURCE].start_time; + sint32 pos = GetTimeCounter () - start_time; + if (pos < 0) + pos = 0; + else if ((uint32)pos > tracks_length) + pos = tracks_length; + return pos; +} + +void +FastReverse_Smooth (void) +{ + sint32 offset; + + if (!sound_sample) + return; // nothing is playing, so.. bye! + + LockMutex (soundSource[SPEECH_SOURCE].stream_mutex); + + offset = get_current_track_pos (); + offset -= ACCEL_SCROLL_SPEED; + seek_track (offset); + + // Restart the stream in case it ended previously + if (!PlayingStream (SPEECH_SOURCE)) + PlayStream (sound_sample, SPEECH_SOURCE, false, true, false); + UnlockMutex (soundSource[SPEECH_SOURCE].stream_mutex); +} + +void +FastForward_Smooth (void) +{ + sint32 offset; + + if (!sound_sample) + return; // nothing is playing, so.. bye! + + LockMutex (soundSource[SPEECH_SOURCE].stream_mutex); + + offset = get_current_track_pos (); + offset += ACCEL_SCROLL_SPEED; + seek_track (offset); + + UnlockMutex (soundSource[SPEECH_SOURCE].stream_mutex); +} + +void +FastReverse_Page (void) +{ + TFB_SoundChunk *prev; + + if (!sound_sample) + return; // nothing is playing, so.. bye! + + LockMutex (soundSource[SPEECH_SOURCE].stream_mutex); + prev = find_prev_page (cur_sub_chunk); + if (prev) + { // Set the chunk to be played + cur_chunk = prev; + cur_sub_chunk = prev; + // Decoder will be set in OnStreamStart() + PlayStream (sound_sample, SPEECH_SOURCE, false, true, true); + } + UnlockMutex (soundSource[SPEECH_SOURCE].stream_mutex); +} + +void +FastForward_Page (void) +{ + TFB_SoundChunk *next; + + if (!sound_sample) + return; // nothing is playing, so.. bye! + + LockMutex (soundSource[SPEECH_SOURCE].stream_mutex); + next = find_next_page (cur_sub_chunk); + if (next) + { // Set the chunk to be played + cur_chunk = next; + cur_sub_chunk = next; + // Decoder will be set in OnStreamStart() + PlayStream (sound_sample, SPEECH_SOURCE, false, true, true); + } + else + { // End of the tracks (pun intended) + seek_track (tracks_length + 1); + } + UnlockMutex (soundSource[SPEECH_SOURCE].stream_mutex); +} + +// Tells current position of streaming speech in the units +// specified by the caller. +// This is normally called on the ambient_anim_task thread. +int +GetTrackPosition (int in_units) +{ + uint32 offset; + uint32 length = tracks_length; + // detach from the static one, otherwise, we can race for + // it and thus divide by 0 + + if (!sound_sample || length == 0) + return 0; // nothing is playing + + LockMutex (soundSource[SPEECH_SOURCE].stream_mutex); + offset = get_current_track_pos (); + UnlockMutex (soundSource[SPEECH_SOURCE].stream_mutex); + + return in_units * offset / length; +} + +TFB_SoundChunk * +create_SoundChunk (TFB_SoundDecoder *decoder, float start_time) +{ + TFB_SoundChunk *chunk; + chunk = HCalloc (sizeof (*chunk)); + chunk->decoder = decoder; + chunk->start_time = start_time; + return chunk; +} + +void +destroy_SoundChunk_list (TFB_SoundChunk *chunk) +{ + TFB_SoundChunk *next = NULL; + for ( ; chunk; chunk = next) + { + next = chunk->next; + if (chunk->decoder) + SoundDecoder_Free (chunk->decoder); + HFree (chunk->text); + HFree (chunk); + } +} + +// Returns the next chunk with a subtitle +TFB_SoundChunk * +find_next_page (TFB_SoundChunk *cur) +{ + if (!cur) + return NULL; + for (cur = cur->next; cur && !cur->tag_me; cur = cur->next) + ; + return cur; +} + +// Returns the previous chunk with a subtitle. +// cur == 0 is treated as end of the list. +TFB_SoundChunk * +find_prev_page (TFB_SoundChunk *cur) +{ + TFB_SoundChunk *prev; + TFB_SoundChunk *last_valid = chunks_head; + + if (cur == chunks_head) + return cur; // cannot go below the first track + + for (prev = chunks_head; prev && prev != cur; prev = prev->next) + { + if (prev->tag_me) + last_valid = prev; + } + return last_valid; +} + + +// External access to the chunks list +SUBTITLE_REF +GetFirstTrackSubtitle (void) +{ + return chunks_head; +} + +// External access to the chunks list +SUBTITLE_REF +GetNextTrackSubtitle (SUBTITLE_REF LastRef) +{ + if (!LastRef) + return NULL; // enumeration already ended + + return find_next_page (LastRef); +} + +// External access to the chunk subtitles +const UNICODE * +GetTrackSubtitleText (SUBTITLE_REF SubRef) +{ + if (!SubRef) + return NULL; + + return SubRef->text; +} + +// External access to currently active subtitle text +// Returns NULL is none is active +const UNICODE * +GetTrackSubtitle (void) +{ + const UNICODE *cur_sub = NULL; + + if (!sound_sample) + return NULL; // not playing anything + + LockMutex (soundSource[SPEECH_SOURCE].stream_mutex); + if (cur_sub_chunk) + cur_sub = cur_sub_chunk->text; + UnlockMutex (soundSource[SPEECH_SOURCE].stream_mutex); + + return cur_sub; +} diff --git a/src/libs/sound/trackplayer.h b/src/libs/sound/trackplayer.h new file mode 100644 index 0000000..5964e65 --- /dev/null +++ b/src/libs/sound/trackplayer.h @@ -0,0 +1,52 @@ +//Copyright Paul Reiche, Fred Ford. 1992-2002 + +/* + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef TRACKPLAYER_H +#define TRACKPLAYER_H + +#include "libs/compiler.h" +#include "libs/callback.h" + +#define ACCEL_SCROLL_SPEED 300 + +extern void PlayTrack (void); +extern void StopTrack (void); +extern void JumpTrack (void); +extern void PauseTrack (void); +extern void ResumeTrack (void); +extern COUNT PlayingTrack (void); + +extern void FastReverse_Smooth (void); +extern void FastForward_Smooth (void); +extern void FastReverse_Page (void); +extern void FastForward_Page (void); + +extern void SpliceTrack (UNICODE *filespec, UNICODE *textspec, UNICODE *TimeStamp, CallbackFunction cb); +extern void SpliceMultiTrack (UNICODE *TrackNames[], UNICODE *TrackText); + +extern int GetTrackPosition (int in_units); + +typedef struct tfb_soundchunk *SUBTITLE_REF; + +extern SUBTITLE_REF GetFirstTrackSubtitle (void); +extern SUBTITLE_REF GetNextTrackSubtitle (SUBTITLE_REF LastRef); +extern const UNICODE *GetTrackSubtitleText (SUBTITLE_REF SubRef); + +extern const UNICODE *GetTrackSubtitle (void); + +#endif |