summaryrefslogtreecommitdiff
path: root/src/libs/sound/decoders/decoder.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/libs/sound/decoders/decoder.c')
-rw-r--r--src/libs/sound/decoders/decoder.c936
1 files changed, 936 insertions, 0 deletions
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
+}