summaryrefslogtreecommitdiff
path: root/src/libs/sound/decoders
diff options
context:
space:
mode:
Diffstat (limited to 'src/libs/sound/decoders')
-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
13 files changed, 3512 insertions, 0 deletions
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