diff options
Diffstat (limited to 'src/libs/sound/decoders')
-rw-r--r-- | src/libs/sound/decoders/Makeinfo | 8 | ||||
-rw-r--r-- | src/libs/sound/decoders/aiffaud.c | 650 | ||||
-rw-r--r-- | src/libs/sound/decoders/aiffaud.h | 36 | ||||
-rw-r--r-- | src/libs/sound/decoders/decoder.c | 936 | ||||
-rw-r--r-- | src/libs/sound/decoders/decoder.h | 129 | ||||
-rw-r--r-- | src/libs/sound/decoders/dukaud.c | 546 | ||||
-rw-r--r-- | src/libs/sound/decoders/dukaud.h | 36 | ||||
-rw-r--r-- | src/libs/sound/decoders/modaud.c | 430 | ||||
-rw-r--r-- | src/libs/sound/decoders/modaud.h | 26 | ||||
-rw-r--r-- | src/libs/sound/decoders/oggaud.c | 278 | ||||
-rw-r--r-- | src/libs/sound/decoders/oggaud.h | 26 | ||||
-rw-r--r-- | src/libs/sound/decoders/wav.c | 385 | ||||
-rw-r--r-- | src/libs/sound/decoders/wav.h | 26 |
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 |