summaryrefslogtreecommitdiff
path: root/src/libs/sound
diff options
context:
space:
mode:
Diffstat (limited to 'src/libs/sound')
-rw-r--r--src/libs/sound/Makeinfo9
-rw-r--r--src/libs/sound/audiocore.c272
-rw-r--r--src/libs/sound/audiocore.h169
-rw-r--r--src/libs/sound/decoders/Makeinfo8
-rw-r--r--src/libs/sound/decoders/aiffaud.c650
-rw-r--r--src/libs/sound/decoders/aiffaud.h36
-rw-r--r--src/libs/sound/decoders/decoder.c936
-rw-r--r--src/libs/sound/decoders/decoder.h129
-rw-r--r--src/libs/sound/decoders/dukaud.c546
-rw-r--r--src/libs/sound/decoders/dukaud.h36
-rw-r--r--src/libs/sound/decoders/modaud.c430
-rw-r--r--src/libs/sound/decoders/modaud.h26
-rw-r--r--src/libs/sound/decoders/oggaud.c278
-rw-r--r--src/libs/sound/decoders/oggaud.h26
-rw-r--r--src/libs/sound/decoders/wav.c385
-rw-r--r--src/libs/sound/decoders/wav.h26
-rw-r--r--src/libs/sound/fileinst.c87
-rw-r--r--src/libs/sound/mixer/Makeinfo3
-rw-r--r--src/libs/sound/mixer/mixer.c1760
-rw-r--r--src/libs/sound/mixer/mixer.h274
-rw-r--r--src/libs/sound/mixer/mixerint.h110
-rw-r--r--src/libs/sound/mixer/nosound/Makeinfo2
-rw-r--r--src/libs/sound/mixer/nosound/audiodrv_nosound.c410
-rw-r--r--src/libs/sound/mixer/nosound/audiodrv_nosound.h69
-rw-r--r--src/libs/sound/mixer/sdl/Makeinfo2
-rw-r--r--src/libs/sound/mixer/sdl/audiodrv_sdl.c486
-rw-r--r--src/libs/sound/mixer/sdl/audiodrv_sdl.h66
-rw-r--r--src/libs/sound/music.c233
-rw-r--r--src/libs/sound/openal/Makeinfo2
-rw-r--r--src/libs/sound/openal/audiodrv_openal.c420
-rw-r--r--src/libs/sound/openal/audiodrv_openal.h86
-rw-r--r--src/libs/sound/resinst.c65
-rw-r--r--src/libs/sound/sfx.c306
-rw-r--r--src/libs/sound/sndintrn.h76
-rw-r--r--src/libs/sound/sound.c178
-rw-r--r--src/libs/sound/sound.h81
-rw-r--r--src/libs/sound/stream.c814
-rw-r--r--src/libs/sound/stream.h37
-rw-r--r--src/libs/sound/trackint.h41
-rw-r--r--src/libs/sound/trackplayer.c874
-rw-r--r--src/libs/sound/trackplayer.h52
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