# HG changeset patch # User Eric Wing # Date 1304032950 25200 # Node ID 71b465ff062254871d194b0a268c8786a36404f3 # Parent b346b6608eab0598977d292cb18e7ba03fe6737c Added support files. diff -r b346b6608eab -r 71b465ff0622 Isolated/ALmixer_RWops.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Isolated/ALmixer_RWops.c Thu Apr 28 16:22:30 2011 -0700 @@ -0,0 +1,108 @@ +#ifndef ALMIXER_COMPILED_WITH_SDL + +#include "ALmixer_RWops.h" +#include /* malloc, free */ +#include /* fopen, fseek, fread, fclose */ +#include /* strerror */ +#include /* errno */ + +/* (Note this is different than stdio's seek. This returns ftell.) + */ +static int stdio_seek(ALmixer_RWops* the_context, long offset, int whence) +{ + if(0 == fseek(the_context->hidden.stdio.fp, offset, whence)) + { + return(ftell(the_context->hidden.stdio.fp)); + } + else + { +/* ALmixer_SetError("ALmixer_RWops seek failed: %s", strerror(errno)); */ + return (-1); + } +} + +static size_t stdio_read(ALmixer_RWops* the_context, void* ptr, size_t size, size_t nitems) +{ + size_t bytes_read; + + bytes_read = fread(ptr, size, nitems, the_context->hidden.stdio.fp); + if(0 == bytes_read && ferror(the_context->hidden.stdio.fp)) + { + /* not sure if strerror can convert ferror */ +/* ALmixer_SetError("ALmixer_RWops read failed: %s", strerror(ferror(the_context->hidden.stdio.fp))); */ + } + return bytes_read; +} + +static size_t stdio_write(ALmixer_RWops* the_context, const void* ptr, size_t size, size_t nitems) +{ + size_t bytes_written; + + bytes_written = fwrite(ptr, size, nitems, the_context->hidden.stdio.fp); + if(0 == bytes_written && ferror(the_context->hidden.stdio.fp)) + { +/* ALmixer_SetError("ALmixer_RWops write failed: %s", strerror(ferror(the_context->hidden.stdio.fp))); */ + } + return bytes_written; +} + +static int stdio_close(ALmixer_RWops* the_context) +{ + int return_status = 0; + if(NULL != the_context) + { + if(0 != the_context->hidden.stdio.autoclose) + { + if(0 != fclose(the_context->hidden.stdio.fp)) + { +/* ALmixer_SetError("ALmixer_RWops close failed: %s", strerror(errno)); */ + return_status = -1; + } + } + free(the_context); + } + return return_status; +} + +ALmixer_RWops* ALmixer_RWFromFP(FILE* file_pointer, char autoclose_flag) +{ + ALmixer_RWops* rw_ops = NULL; + + rw_ops = (ALmixer_RWops*)malloc(sizeof(ALmixer_RWops)); + if(NULL == rw_ops) + { +/* ALmixer_SetError("ALmixer_RWFromFP: Out of memory"); */ + return NULL; + } + + rw_ops->seek = stdio_seek; + rw_ops->read = stdio_read; + rw_ops->write = stdio_write; + rw_ops->close = stdio_close; + rw_ops->hidden.stdio.fp = file_pointer; + rw_ops->hidden.stdio.autoclose = autoclose_flag; + return rw_ops; +} + +ALmixer_RWops* ALmixer_RWFromFile(const char* file_name, const char* file_mode) +{ + ALmixer_RWops* rw_ops = NULL; + FILE* file_pointer = NULL; + if(NULL == file_name || NULL == file_mode) + { +/* ALmixer_SetError("ALmixer_RWFromFile: No file or mode specified"); */ + return NULL; + } + file_pointer = fopen(file_name, file_mode); + if(NULL == file_pointer) + { +/* ALmixer_SetError("ALmixer_RWFromFile: Could not open file: %s", strerror(errno)); */ + return NULL; + } + + rw_ops = ALmixer_RWFromFP(file_pointer, 1); + return rw_ops; +} + + +#endif /* ALMIXER_COMPILED_WITH_SDL */ diff -r b346b6608eab -r 71b465ff0622 Isolated/ALmixer_RWops.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Isolated/ALmixer_RWops.h Thu Apr 28 16:22:30 2011 -0700 @@ -0,0 +1,117 @@ +#ifndef ALMIXER_RWOPS +#define ALMIXER_RWOPS + + #if defined(_WIN32) + #if defined(ALMIXER_RWOPS_BUILD_LIBRARY) + #define ALMIXER_RWOPS_DECLSPEC __declspec(dllexport) + #else + #define ALMIXER_RWOPS_DECLSPEC __declspec(dllimport) + #endif + #else + #if defined(ALMIXER_RWOPS_BUILD_LIBRARY) + #if defined (__GNUC__) && __GNUC__ >= 4 + #define ALMIXER_RWOPS_DECLSPEC __attribute__((visibility("default"))) + #else + #define ALMIXER_RWOPS_DECLSPEC + #endif + #else + #define ALMIXER_RWOPS_DECLSPEC + #endif + #endif + + #if defined(_WIN32) + #define ALMIXER_RWOPS_CALL __cdecl + #else + #define ALMIXER_RWOPS_CALL + #endif + +#ifdef __cplusplus +extern "C" { +#endif + + /* Trying to keep compatibility with SDL_RWops, + * but I don't plan on reimplementing everything. + */ + #include + #include + /* The 'type' parameter needs to be 32-bits to match SDL_RWops. + * has been problematic for Visual Studio for years. + * It appears that Visual Studio 2010 finally includes this. + */ + #include + typedef struct ALmixer_RWops + { + /** Seek to 'offset' relative to whence, one of stdio's whence values: + * SEEK_SET, SEEK_CUR, SEEK_END + * Returns the final offset in the data source. + * (Note this is different than stdio's seek. This returns ftell.) + */ + int (ALMIXER_RWOPS_CALL *seek)(struct ALmixer_RWops* the_context, long offset, int whence); + + /** Read up to 'nitems' objects each of size 'size' from the data + * source to the area pointed at by 'ptr'. + * Returns the number of objects read, or -1 if the read failed. + */ + size_t (ALMIXER_RWOPS_CALL *read)(struct ALmixer_RWops* the_context, void* ptr, size_t size, size_t nitems); + + /** Write exactly 'nitems' objects each of size 'size' from the area + * pointed at by 'ptr' to data source. + * Returns 'num', or -1 if the write failed. + */ + size_t (ALMIXER_RWOPS_CALL *write)(struct ALmixer_RWops* the_context, const void* ptr, size_t size, size_t nitems); + + /** Close and free an allocated ALmixer_RWops structure */ + int (ALMIXER_RWOPS_CALL *close)(struct ALmixer_RWops* the_context); + + /* type Needs to be a 32-bit to be compatible with SDL */ + uint32_t type; + union + { +#if defined(__WIN32__) && !defined(__SYMBIAN32__) + /* Chances are that I will not implement this path. + * But I want the padding to be the same as SDL. + */ + struct + { + int append; + void *h; + struct + { + void *data; + int size; + int left; + } buffer; + } win32io; +#endif + struct + { + int autoclose; + FILE* fp; + } stdio; + struct + { + unsigned char* base; + unsigned char* here; + unsigned char* stop; + } mem; + struct + { + void* data1; + } unknown; + } hidden; + } ALmixer_RWops; + + extern ALMIXER_RWOPS_DECLSPEC ALmixer_RWops* ALMIXER_RWOPS_CALL ALmixer_RWFromFile(const char* file_name, const char* file_mode); + extern ALMIXER_RWOPS_DECLSPEC ALmixer_RWops* ALMIXER_RWOPS_CALL ALmixer_RWFromFP(FILE* file_pointer, char autoclose_flag); + +#define ALmixer_RWseek(rwops, offset, whence) (rwops)->seek(rwops, offset, whence) +#define ALmixer_RWtell(rwops) (rwops)->seek(rwops, 0, SEEK_CUR) +#define ALmixer_RWread(rwops, ptr, size, nitems) (rwops)->read(rwops, ptr, size, nitems) + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +} +#endif + +#endif + diff -r b346b6608eab -r 71b465ff0622 Isolated/LGPL/SDL_endian_minimal.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Isolated/LGPL/SDL_endian_minimal.h Thu Apr 28 16:22:30 2011 -0700 @@ -0,0 +1,99 @@ +/* + SDL - Simple DirectMedia Layer + Copyright (C) 1997-2009 Sam Lantinga + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + Sam Lantinga + slouken@libsdl.org + */ + +/* + Attention: This is a stripped down file of SDL_endian for our purposes. + This code is licensed under the LGPL. + This means we must not compile this code into anything that we are not willing to + publicly release source code. + You should compile this into a separate dynamic library that is isolated from proprietary code. + */ + + +#ifndef SDL_endian_minimal_h +#define SDL_endian_minimal_h + +#ifdef ANDROID_NDK +#include + +#define SDL_BYTEORDER _BYTE_ORDER +#define SDL_BIG_ENDIAN _BIG_ENDIAN +#define SDL_LITTLE_ENDIAN _LITTLE_ENDIAN +#endif + +#include + + +#if defined(__GNUC__) && defined(__i386__) && \ + !(__GNUC__ == 2 && __GNUC_MINOR__ <= 95 /* broken gcc version */) +static __inline__ uint32_t SDL_Swap32(uint32_t x) +{ + __asm__("bswap %0" : "=r" (x) : "0" (x)); + return x; +} +#elif defined(__GNUC__) && defined(__x86_64__) +static __inline__ uint32_t SDL_Swap32(uint32_t x) +{ + __asm__("bswapl %0" : "=r" (x) : "0" (x)); + return x; +} +#elif defined(__GNUC__) && (defined(__powerpc__) || defined(__ppc__)) +static __inline__ uint32_t SDL_Swap32(uint32_t x) +{ + uint32_t result; + + __asm__("rlwimi %0,%2,24,16,23" : "=&r" (result) : "0" (x>>24), "r" (x)); + __asm__("rlwimi %0,%2,8,8,15" : "=&r" (result) : "0" (result), "r" (x)); + __asm__("rlwimi %0,%2,24,0,7" : "=&r" (result) : "0" (result), "r" (x)); + return result; +} +#elif defined(__GNUC__) && (defined(__M68000__) || defined(__M68020__)) +static __inline__ uint32_t SDL_Swap32(uint32_t x) +{ + __asm__("rorw #8,%0\n\tswap %0\n\trorw #8,%0" : "=d" (x) : "0" (x) : "cc"); + return x; +} +#else +static __inline__ uint32_t SDL_Swap32(uint32_t x) { + return((x<<24)|((x<<8)&0x00FF0000)|((x>>8)&0x0000FF00)|(x>>24)); +} +#endif + + +#if SDL_BYTEORDER == SDL_BIG_ENDIAN +#define SDL_SwapLE16(X) SDL_Swap16(X) +#define SDL_SwapLE32(X) SDL_Swap32(X) +#define SDL_SwapLE64(X) SDL_Swap64(X) +#define SDL_SwapBE16(X) (X) +#define SDL_SwapBE32(X) (X) +#define SDL_SwapBE64(X) (X) +#else +#define SDL_SwapLE16(X) (X) +#define SDL_SwapLE32(X) (X) +#define SDL_SwapLE64(X) (X) +#define SDL_SwapBE16(X) SDL_Swap16(X) +#define SDL_SwapBE32(X) SDL_Swap32(X) +#define SDL_SwapBE64(X) SDL_Swap64(X) +#endif + + +#endif // SDL_endian_minimal_h diff -r b346b6608eab -r 71b465ff0622 Isolated/LGPL/SDL_sound_minimal.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Isolated/LGPL/SDL_sound_minimal.c Thu Apr 28 16:22:30 2011 -0700 @@ -0,0 +1,41 @@ +/* + * SDL_sound -- An abstract sound format decoding API. + * Copyright (C) 2001 Ryan C. Gordon. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + Attention: This is a stripped down file of SDL_endian for our purposes. + This code is licensed under the LGPL. + This means we must not compile this code into anything that we are not willing to + publicly release source code. + You should compile this into a separate dynamic library that is isolated from proprietary code. + */ + + +#include +#include "SoundDecoder.h" + +uint32_t __Sound_convertMsToBytePos(Sound_AudioInfo *info, uint32_t ms) +{ + /* "frames" == "sample frames" */ + float frames_per_ms = ((float) info->rate) / 1000.0f; + uint32_t frame_offset = (uint32_t) (frames_per_ms * ((float) ms)); + uint32_t frame_size = (uint32_t) ((info->format & 0xFF) / 8) * info->channels; + return(frame_offset * frame_size); +} /* __Sound_convertMsToBytePos */ + + diff -r b346b6608eab -r 71b465ff0622 Isolated/LGPL/mpg123.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Isolated/LGPL/mpg123.c Thu Apr 28 16:22:30 2011 -0700 @@ -0,0 +1,393 @@ +/* + * SDL_sound -- An abstract sound format decoding API. + * Copyright (C) 2001 Ryan C. Gordon. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * libmpg123 decoder for SDL_sound. This is a very lightweight MP3 decoder, + * which is included with the SDL_sound source, so that it doesn't rely on + * unnecessary external libraries. + * + * libmpg123 is part of mpg123, and can be found in its original + * form at: http://www.mpg123.org/ + * + * Please see the file LICENSE.txt in the source's root directory. The included + * source code for libmpg123 falls under the LGPL, which is the same license + * as SDL_sound (so you can consider it a single work). + * + * This file written by Ryan C. Gordon. (icculus@icculus.org) + */ + +#if HAVE_CONFIG_H +# include +#endif + +#ifdef SOUND_SUPPORTS_MPG123 + +#include +#include +#include +#include + +#include "mpg123.h" + +#ifdef ALMIXER_COMPILE_WITHOUT_SDL +#include "SoundDecoder.h" +#include "SoundDecoder_Internal.h" +#include "SimpleMutex.h" +#include "ALmixer_RWops.h" +#include + typedef struct SimpleMutex SDL_mutex; + typedef struct ALmixer_RWops SDL_RWops; + #define SDL_CreateMutex SimpleMutex_CreateMutex + #define SDL_DestroyMutex SimpleMutex_DestroyMutex + #define SDL_LockMutex SimpleMutex_LockMutex + #define SDL_UnlockMutex SimpleMutex_UnlockMutex + typedef uint32_t Uint32; + #define SDL_RWseek ALmixer_RWseek + #define SDL_RWread ALmixer_RWread + +#else +#include "SDL_sound.h" + +#define __SDL_SOUND_INTERNAL__ +#include "SDL_sound_internal.h" +#endif + +#ifdef _MSC_VER +#define snprintf _snprintf +#endif + + +static int MPG123_init(void); +static void MPG123_quit(void); +static int MPG123_open(Sound_Sample *sample, const char *ext); +static void MPG123_close(Sound_Sample *sample); +static Uint32 MPG123_read(Sound_Sample *sample); +static int MPG123_rewind(Sound_Sample *sample); +static int MPG123_seek(Sound_Sample *sample, Uint32 ms); + +/* !!! FIXME: MPEG and MPG extensions? */ +static const char *extensions_mpg123[] = { "MP3", NULL }; +const Sound_DecoderFunctions __Sound_DecoderFunctions_MPG123 = +{ + { + extensions_mpg123, + "MP3 decoding via internal libmpg123", + "Ryan C. Gordon ", + "http://www.icculus.org/SDL_sound/" + }, + + MPG123_init, /* init() method */ + MPG123_quit, /* quit() method */ + MPG123_open, /* open() method */ + MPG123_close, /* close() method */ + MPG123_read, /* read() method */ + MPG123_rewind, /* rewind() method */ + MPG123_seek /* seek() method */ +}; + + +/* this is what we store in our internal->decoder_private field... */ +typedef mpg123_handle mpg123_t; + +static SDL_mutex *mpg123_mutex = NULL; +static int mpg123_rwops_count = 0; +static SDL_RWops **mpg123_rwops = NULL; + +static void print_decoders(const char *kind, char **decoders) +{ + SNDDBG(("%s:", kind)); + if (*decoders == NULL) + { + SNDDBG((" [none]")); + } + else + { + do + { + SNDDBG((" %s", *decoders)); + } while (*(++decoders)); + } /* else */ + SNDDBG(("\n")); +} /* print_decoders */ + + +static int MPG123_init(void) +{ + int retval = 0; + assert(mpg123_mutex == NULL); + if (mpg123_init() == MPG123_OK) + { + char **supported = mpg123_supported_decoders(); + print_decoders("ALL MPG123 DECODERS", mpg123_decoders()); + print_decoders("SUPPORTED MPG123 DECODERS", mpg123_supported_decoders()); + if ((supported != NULL) && (*supported != NULL)) + { + mpg123_mutex = SDL_CreateMutex(); + if (mpg123_mutex != NULL) + retval = 1; /* at least one decoder available. */ + } /* if */ + } /* if */ + + return retval; +} /* MPG123_init */ + + +static void MPG123_quit(void) +{ + mpg123_exit(); + SDL_DestroyMutex(mpg123_mutex); + mpg123_mutex = NULL; + free(mpg123_rwops); + mpg123_rwops = NULL; + mpg123_rwops_count = 0; +} /* MPG123_quit */ + + +/* bridge rwops reading to libmpg123 hooks. */ +static ssize_t rwread(int fd, void *buf, size_t len) +{ + SDL_RWops *rw = NULL; + SDL_LockMutex(mpg123_mutex); + rw = mpg123_rwops[fd]; + SDL_UnlockMutex(mpg123_mutex); + return (ssize_t) SDL_RWread(rw, buf, 1, len); +} /* rwread */ + + +/* bridge rwops seeking to libmpg123 hooks. */ +static off_t rwseek(int fd, off_t pos, int whence) +{ + SDL_RWops *rw = NULL; + SDL_LockMutex(mpg123_mutex); + rw = mpg123_rwops[fd]; + SDL_UnlockMutex(mpg123_mutex); + return (off_t) SDL_RWseek(rw, pos, whence); +} /* rwseek */ + + +static const char *set_error(mpg123_handle *mp, const int err) +{ + char buffer[128]; + const char *str = NULL; + if ((err == MPG123_ERR) && (mp != NULL)) + str = mpg123_strerror(mp); + else + str = mpg123_plain_strerror(err); + + snprintf(buffer, sizeof (buffer), "MPG123: %s", str); + __Sound_SetError(buffer); + + return(NULL); /* this is for BAIL_MACRO to not try to reset the string. */ +} /* set_error */ + + +/* Make sure we are only given decoded data in a format we can handle. */ +static int set_formats(mpg123_handle *mp) +{ + int rc = 0; + const long *rates = NULL; + size_t ratecount = 0; + const int channels = MPG123_STEREO | MPG123_MONO; + const int encodings = /* !!! FIXME: SDL 1.3 can do sint32 and float32. + MPG123_ENC_SIGNED_32 | MPG123_ENC_FLOAT_32 | */ + MPG123_ENC_SIGNED_8 | MPG123_ENC_UNSIGNED_8 | + MPG123_ENC_SIGNED_16 | MPG123_ENC_UNSIGNED_16; + + mpg123_rates(&rates, &ratecount); + + rc = mpg123_format_none(mp); + while ((ratecount--) && (rc == MPG123_OK)) + rc = mpg123_format(mp, *(rates++), channels, encodings); + + return(rc); +} /* set_formats */ + + +static int MPG123_open(Sound_Sample *sample, const char *ext) +{ + Sound_SampleInternal *internal = (Sound_SampleInternal *) sample->opaque; + mpg123_handle *mp = NULL; + long rate = 0; + int channels = 0; + int fmt = 0; + int rc = 0; + off_t len = 0; + int seekable = 0; + void *ptr = NULL; + int rwops = 0; + + /* !!! FIXME: so much tapdance because we need a pointer, not an int. */ + SDL_LockMutex(mpg123_mutex); + for (rwops = 0; rwops < mpg123_rwops_count; rwops++) + { + if (mpg123_rwops[rwops] == NULL) + break; + } /* for */ + if (rwops < mpg123_rwops_count) + ptr = mpg123_rwops; + else + { + mpg123_rwops_count++; + ptr = realloc(mpg123_rwops, sizeof (SDL_RWops *) * mpg123_rwops_count); + if (ptr != NULL) + mpg123_rwops = (SDL_RWops **) ptr; + } /* else */ + if (ptr != NULL) + mpg123_rwops[rwops] = internal->rw; + SDL_UnlockMutex(mpg123_mutex); + BAIL_IF_MACRO(ptr == NULL, ERR_OUT_OF_MEMORY, 0); + + if ((mp = mpg123_new(NULL, &rc)) == NULL) + goto mpg123_open_failed; + else if ((rc = set_formats(mp)) != MPG123_OK) + goto mpg123_open_failed; + else if ((rc = mpg123_replace_reader(mp, rwread, rwseek)) != MPG123_OK) + goto mpg123_open_failed; + else if ((rc = mpg123_open_fd(mp, rwops)) != MPG123_OK) + goto mpg123_open_failed; + else if ((rc = mpg123_scan(mp)) != MPG123_OK) + goto mpg123_open_failed; /* !!! FIXME: this may be wrong. */ + else if ((rc = mpg123_getformat(mp, &rate, &channels, &fmt)) != MPG123_OK) + goto mpg123_open_failed; + + if (mpg123_seek(mp, 0, SEEK_END) >= 0) /* can seek? */ + { + len = mpg123_tell(mp); + if ((rc = (int) mpg123_seek(mp, 0, SEEK_SET)) < 0) + goto mpg123_open_failed; + seekable = 1; + } /* if */ + + internal->decoder_private = mp; + sample->actual.rate = rate; + sample->actual.channels = channels; + + rc = MPG123_BAD_OUTFORMAT; /* in case this fails... */ + if (fmt == MPG123_ENC_SIGNED_8) + sample->actual.format = AUDIO_S8; + else if (fmt == MPG123_ENC_UNSIGNED_8) + sample->actual.format = AUDIO_U8; + else if (fmt == MPG123_ENC_SIGNED_16) + sample->actual.format = AUDIO_S16SYS; + else if (fmt == MPG123_ENC_UNSIGNED_16) + sample->actual.format = AUDIO_U16SYS; + /* !!! FIXME: SDL 1.3 can do sint32 and float32 ... + else if (fmt == MPG123_ENC_SIGNED_32) + sample->actual.format = AUDIO_S32SYS; + else if (fmt == MPG123_ENC_FLOAT_32) + sample->actual.format = AUDIO_F32SYS; + */ + else + goto mpg123_open_failed; + + SNDDBG(("MPG123: Accepting data stream.\n")); + + sample->flags = SOUND_SAMPLEFLAG_NONE; + internal->total_time = -1; + if (seekable) + { + sample->flags |= SOUND_SAMPLEFLAG_CANSEEK; + internal->total_time = ((len / rate) * 1000) + + (((len % rate) * 1000) / rate); + } /* if */ + + return(1); /* we'll handle this data. */ + +mpg123_open_failed: + SDL_LockMutex(mpg123_mutex); + mpg123_rwops[rwops] = NULL; + if (rwops == mpg123_rwops_count) + mpg123_rwops_count--; + SDL_UnlockMutex(mpg123_mutex); + set_error(mp, rc); + mpg123_delete(mp); /* NULL is safe. */ + return(0); +} /* MPG123_open */ + + +static void MPG123_close(Sound_Sample *sample) +{ + Sound_SampleInternal *internal = (Sound_SampleInternal *) sample->opaque; + mpg123_t *mp = ((mpg123_t *) internal->decoder_private); + int i; + + SDL_LockMutex(mpg123_mutex); + for (i = 0; i < mpg123_rwops_count; i++) + { + if (mpg123_rwops[i] == internal->rw) + mpg123_rwops[i] = NULL; + } /* for */ + + for (i = mpg123_rwops_count-1; i >= 0; i--) + { + if (mpg123_rwops[i] != NULL) + break; + } /* for */ + mpg123_rwops_count = i + 1; + SDL_UnlockMutex(mpg123_mutex); + + mpg123_close(mp); /* don't need this at the moment, but it's safe. */ + mpg123_delete(mp); +} /* MPG123_close */ + + +static Uint32 MPG123_read(Sound_Sample *sample) +{ + Sound_SampleInternal *internal = (Sound_SampleInternal *) sample->opaque; + mpg123_t *mp = ((mpg123_t *) internal->decoder_private); + size_t bw = 0; + const int rc = mpg123_read(mp, (unsigned char *) internal->buffer, + internal->buffer_size, &bw); + if (rc == MPG123_DONE) + sample->flags |= SOUND_SAMPLEFLAG_EOF; + else if (rc != MPG123_OK) + { + sample->flags |= SOUND_SAMPLEFLAG_ERROR; + set_error(mp, rc); + } /* else if */ + + return((Uint32) bw); +} /* MPG123_read */ + + +static int MPG123_rewind(Sound_Sample *sample) +{ + Sound_SampleInternal *internal = (Sound_SampleInternal *) sample->opaque; + mpg123_t *mp = ((mpg123_t *) internal->decoder_private); + const int rc = (int) mpg123_seek(mp, 0, SEEK_SET); + BAIL_IF_MACRO(rc < 0, set_error(mp, rc), 0); + return(1); +} /* MPG123_rewind */ + + +static int MPG123_seek(Sound_Sample *sample, Uint32 ms) +{ + Sound_SampleInternal *internal = (Sound_SampleInternal *) sample->opaque; + mpg123_t *mp = ((mpg123_t *) internal->decoder_private); + const float frames_per_ms = ((float) sample->actual.rate) / 1000.0f; + const off_t frame_offset = (off_t) (frames_per_ms * ((float) ms)); + const int rc = (int) mpg123_seek(mp, frame_offset , SEEK_SET); + BAIL_IF_MACRO(rc < 0, set_error(mp, rc), 0); + return(1); +} /* MPG123_seek */ + +#endif /* SOUND_SUPPORTS_MPG123 */ + +/* end of mpg123.c ... */ + diff -r b346b6608eab -r 71b465ff0622 Isolated/LGPL/oggtremor.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Isolated/LGPL/oggtremor.c Thu Apr 28 16:22:30 2011 -0700 @@ -0,0 +1,379 @@ +/* + * SDL_sound -- An abstract sound format decoding API. + * Copyright (C) 2001 Ryan C. Gordon. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Ogg Vorbis decoder for SDL_sound. + * + * This driver handles .OGG audio files, and depends on libvorbisfile to + * do the actual decoding work. libvorbisfile is part of libvorbis, which + * is part of the Ogg Vorbis project. + * + * Ogg Vorbis: http://www.xiph.org/ogg/vorbis/ + * vorbisfile documentation: http://www.xiph.org/ogg/vorbis/doc/vorbisfile/ + * + * Please see the file LICENSE.txt in the source's root directory. + * + * This file written by Ryan C. Gordon. (icculus@icculus.org) + */ + +#if HAVE_CONFIG_H +# include +#endif + +#ifdef SOUND_SUPPORTS_OGG + +#include +#include +#include +#include + +#include "SoundDecoder.h" + +#include "SoundDecoder_Internal.h" +#include "SDL_endian_minimal.h" +#include "ALmixer_RWops.h" + +//#include +#include +#define ERR_IO_ERROR "I/O error" + + +static int OGG_init(void); +static void OGG_quit(void); +static int OGG_open(Sound_Sample *sample, const char *ext); +static void OGG_close(Sound_Sample *sample); +static uint32_t OGG_read(Sound_Sample *sample); +static int OGG_rewind(Sound_Sample *sample); +static int OGG_seek(Sound_Sample *sample, uint32_t ms); + +static const char *extensions_ogg[] = { "OGG", NULL }; +const Sound_DecoderFunctions __Sound_DecoderFunctions_OGG = +{ + { + extensions_ogg, + "Ogg Vorbis audio through VorbisFile", + "Ryan C. Gordon ", + "http://www.icculus.org/SDL_sound/" + }, + + OGG_init, /* init() method */ + OGG_quit, /* quit() method */ + OGG_open, /* open() method */ + OGG_close, /* close() method */ + OGG_read, /* read() method */ + OGG_rewind, /* rewind() method */ + OGG_seek /* seek() method */ +}; + + +static int OGG_init(void) +{ + return(1); /* always succeeds. */ +} /* OGG_init */ + + +static void OGG_quit(void) +{ + /* it's a no-op. */ +} /* OGG_quit */ + + + + /* + * These are callbacks from vorbisfile that let them read data from + * a RWops... + */ + +static size_t RWops_ogg_read(void *ptr, size_t size, size_t nmemb, void *datasource) +{ + return((size_t) ALmixer_RWread((ALmixer_RWops *) datasource, ptr, size, nmemb)); +} /* RWops_ogg_read */ + +static int RWops_ogg_seek(void *datasource, ogg_int64_t offset, int whence) +{ + return(ALmixer_RWseek((ALmixer_RWops *) datasource, offset, whence)); +} /* RWops_ogg_seek */ + +static int RWops_ogg_close(void *datasource) +{ + /* do nothing; SDL_sound will delete the RWops at a higher level. */ + return(0); /* this is success in fclose(), so I guess that's okay. */ +} /* RWops_ogg_close */ + +static long RWops_ogg_tell(void *datasource) +{ + return((long) ALmixer_RWtell((ALmixer_RWops *) datasource)); +} /* RWops_ogg_tell */ + +static const ov_callbacks RWops_ogg_callbacks = +{ + RWops_ogg_read, + RWops_ogg_seek, + RWops_ogg_close, + RWops_ogg_tell +}; + + + /* Return a human readable version of an VorbisFile error code... */ +#if (defined DEBUG_CHATTER) +static const char *ogg_error(int errnum) +{ + switch(errnum) + { + case OV_EREAD: + return("i/o error"); + case OV_ENOTVORBIS: + return("not a vorbis file"); + case OV_EVERSION: + return("Vorbis version mismatch"); + case OV_EBADHEADER: + return("invalid Vorbis bitstream header"); + case OV_EFAULT: + return("internal logic fault in Vorbis library"); + } /* switch */ + + return("unknown error"); +} /* ogg_error */ +#endif + +static __inline__ void output_ogg_comments(OggVorbis_File *vf) +{ +#if (defined DEBUG_CHATTER) + int i; + vorbis_comment *vc = ov_comment(vf, -1); + + if (vc == NULL) + return; + + SNDDBG(("OGG: vendor == [%s].\n", vc->vendor)); + for (i = 0; i < vc->comments; i++) + { + SNDDBG(("OGG: user comment [%s].\n", vc->user_comments[i])); + } /* for */ +#endif +} /* output_ogg_comments */ + + +static int OGG_open(Sound_Sample *sample, const char *ext) +{ + int rc; + double total_time; + OggVorbis_File *vf; + vorbis_info *info; + Sound_SampleInternal *internal = (Sound_SampleInternal *) sample->opaque; + + vf = (OggVorbis_File *) malloc(sizeof (OggVorbis_File)); + BAIL_IF_MACRO(vf == NULL, ERR_OUT_OF_MEMORY, 0); + + rc = ov_open_callbacks(internal->rw, vf, NULL, 0, RWops_ogg_callbacks); + if (rc != 0) + { +#if (defined DEBUG_CHATTER) + SNDDBG(("OGG: can't grok data. reason: [%s].\n", ogg_error(rc))); +#endif + free(vf); + BAIL_MACRO("OGG: Not valid Ogg Vorbis data.", 0); + } /* if */ + + info = ov_info(vf, -1); + if (info == NULL) + { + ov_clear(vf); + free(vf); + BAIL_MACRO("OGG: failed to retrieve bitstream info", 0); + } /* if */ + + output_ogg_comments(vf); + + SNDDBG(("OGG: bitstream version == (%d).\n", info->version)); + SNDDBG(("OGG: bitstream channels == (%d).\n", info->channels)); + SNDDBG(("OGG: bitstream sampling rate == (%ld).\n", info->rate)); + SNDDBG(("OGG: seekable == {%s}.\n", ov_seekable(vf) ? "TRUE" : "FALSE")); + SNDDBG(("OGG: number of logical bitstreams == (%ld).\n", ov_streams(vf))); + SNDDBG(("OGG: serial number == (%ld).\n", ov_serialnumber(vf, -1))); + SNDDBG(("OGG: total seconds of sample == (%f).\n", ov_time_total(vf, -1))); + + internal->decoder_private = vf; + sample->flags = SOUND_SAMPLEFLAG_CANSEEK; + sample->actual.rate = (uint32_t) info->rate; + sample->actual.channels = (uint8_t) info->channels; + total_time = ov_time_total(vf, -1); + if (OV_EINVAL == total_time) + internal->total_time = -1; + else + internal->total_time = (int32_t)(total_time * 1000.0 + 0.5); + + + /* + * Since we might have more than one logical bitstream in the OGG file, + * and these bitstreams may be in different formats, we might be + * converting two or three times: once in vorbisfile, once again in + * SDL_sound, and perhaps a third time to get it to the sound device's + * format. That's wickedly inefficient. + * + * To combat this a little, if the user specified a desired format, we + * claim that to be the "actual" format of the collection of logical + * bitstreams. This means that VorbisFile will do a conversion as + * necessary, and SDL_sound will not. If the user didn't specify a + * desired format, then we pretend the "actual" format is something that + * OGG files are apparently commonly encoded in. + */ + sample->actual.format = (sample->desired.format == 0) ? + AUDIO_S16SYS : sample->desired.format; + return(1); +} /* OGG_open */ + + +static void OGG_close(Sound_Sample *sample) +{ + Sound_SampleInternal *internal = (Sound_SampleInternal *) sample->opaque; + OggVorbis_File *vf = (OggVorbis_File *) internal->decoder_private; + ov_clear(vf); + free(vf); +} /* OGG_close */ + +/* Note: According to the Vorbis documentation: + * "ov_read() will decode at most one vorbis packet per invocation, + * so the value returned will generally be less than length." + * Due to this, for buffer sizes like 16384, SDL_Sound was always getting + * an underfilled buffer and always setting the EAGAIN flag. + * Since the SDL_Sound API implies that the entire buffer + * should be filled unless EOF, additional code has been added + * to this function to call ov_read() until the buffer is filled. + * However, there may still be some corner cases where the buffer + * cannot be entirely filled. So be aware. + */ +static uint32_t OGG_read(Sound_Sample *sample) +{ + int rc; + int bitstream; + Sound_SampleInternal *internal = (Sound_SampleInternal *) sample->opaque; + OggVorbis_File *vf = (OggVorbis_File *) internal->decoder_private; + + rc = ov_read(vf, internal->buffer, internal->buffer_size, + &bitstream); + + /* Make sure the read went smoothly... */ + if (rc == 0) + sample->flags |= SOUND_SAMPLEFLAG_EOF; + + else if (rc < 0) + sample->flags |= SOUND_SAMPLEFLAG_ERROR; + + /* If the buffer isn't filled, keep trying to fill it + * until no more data can be grabbed */ + else if ((uint32_t) rc < internal->buffer_size) + { + /* Creating a pointer to the buffer that denotes where to start + * writing new data. */ + uint8_t* buffer_start_point = NULL; + int total_bytes_read = rc; + int bytes_remaining = internal->buffer_size - rc; + + /* Keep grabbing data until something prevents + * us from getting more. (Could be EOF, + * packets are too large to fit in remaining + * space, or an error.) + */ + while( (rc > 0) && (bytes_remaining > 0) ) + { + /* Set buffer pointer to end of last write */ + /* All the messiness is to get rid of the warning for + * dereferencing a void* + */ + buffer_start_point = &(((uint8_t*)internal->buffer)[total_bytes_read]); + rc = ov_read(vf, buffer_start_point, bytes_remaining, + &bitstream); + /* Make sure rc > 0 because we don't accidently want + * to change the counters if there was an error + */ + if(rc > 0) + { + total_bytes_read += rc; + bytes_remaining = bytes_remaining - rc; + } + } + /* I think the minimum read size is 2, though I'm + * not sure about this. (I've hit cases where I + * couldn't read less than 4.) What I don't want to do is + * accidently claim we hit EOF when the reason rc == 0 + * is because the requested amount of data was smaller + * than the minimum packet size. + * For now, I will be conservative + * and not set the EOF flag, and let the next call to + * this function figure it out. + * I think the ERROR flag is safe to set because + * it looks like OGG simply returns 0 if the + * read size is too small. + * And in most cases for sensible buffer sizes, + * this fix will fill the buffer, + * so we can set the EAGAIN flag without worrying + * that it will always be set. + */ + if(rc < 0) + { + sample->flags |= SOUND_SAMPLEFLAG_ERROR; + } + else if(rc == 0) + { + /* Do nothing for now until there is a better solution */ + /* sample->flags |= SOUND_SAMPLEFLAG_EOF; */ + } + + /* Test for a buffer underrun. It should occur less frequently + * now, but it still may happen and not necessarily mean + * anything useful. */ + if ((uint32_t) total_bytes_read < internal->buffer_size) + { + sample->flags |= SOUND_SAMPLEFLAG_EAGAIN; + } + /* change rc to the total bytes read so function + * can return the correct value. + */ + rc = total_bytes_read; + } + + return((uint32_t) rc); +} /* OGG_read */ + + +static int OGG_rewind(Sound_Sample *sample) +{ + Sound_SampleInternal *internal = (Sound_SampleInternal *) sample->opaque; + OggVorbis_File *vf = (OggVorbis_File *) internal->decoder_private; + + BAIL_IF_MACRO(ov_raw_seek(vf, 0) < 0, ERR_IO_ERROR, 0); + return(1); +} /* OGG_rewind */ + + +static int OGG_seek(Sound_Sample *sample, uint32_t ms) +{ + Sound_SampleInternal *internal = (Sound_SampleInternal *) sample->opaque; + OggVorbis_File *vf = (OggVorbis_File *) internal->decoder_private; + double timepos = (((double) ms) / 1000.0); + BAIL_IF_MACRO(ov_time_seek(vf, timepos) < 0, ERR_IO_ERROR, 0); + return(1); +} /* OGG_seek */ + +#endif /* SOUND_SUPPORTS_OGG */ + + +/* end of ogg.c ... */ + diff -r b346b6608eab -r 71b465ff0622 Isolated/LGPL/wav.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Isolated/LGPL/wav.c Thu Apr 28 16:22:30 2011 -0700 @@ -0,0 +1,846 @@ +/* + * SDL_sound -- An abstract sound format decoding API. + * Copyright (C) 2001 Ryan C. Gordon. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + Attention: This is a stripped down file of SDL_endian for our purposes. + This code is licensed under the LGPL. + This means we must not compile this code into anything that we are not willing to + publicly release source code. + You should compile this into a separate dynamic library that is isolated from proprietary code. + */ + +/* + * WAV decoder for SDL_sound. + * + * This driver handles Microsoft .WAVs, in as many of the thousands of + * variations as we can. + * + * Please see the file LICENSE.txt in the source's root directory. + * + * This file written by Ryan C. Gordon. (icculus@icculus.org) + */ + +#if HAVE_CONFIG_H +# include +#endif + +#ifdef SOUND_SUPPORTS_WAV + +#include +#include +#include + +//#include "SDL_sound.h" + +//#define __SDL_SOUND_INTERNAL__ +//#include "SDL_sound_internal.h" + + +#include "SoundDecoder.h" + +#include "SoundDecoder_Internal.h" +#include "SDL_endian_minimal.h" +#include "ALmixer_RWops.h" + +#define ERR_IO_ERROR "I/O error" +#define assert(x) + +static int WAV_init(void); +static void WAV_quit(void); +static int WAV_open(Sound_Sample *sample, const char *ext); +static void WAV_close(Sound_Sample *sample); +static uint32_t WAV_read(Sound_Sample *sample); +static int WAV_rewind(Sound_Sample *sample); +static int WAV_seek(Sound_Sample *sample, uint32_t ms); + +static const char *extensions_wav[] = { "WAV", NULL }; +const Sound_DecoderFunctions __Sound_DecoderFunctions_WAV = +{ + { + extensions_wav, + "Microsoft WAVE audio format", + "Ryan C. Gordon ", + "http://www.icculus.org/SDL_sound/" + }, + + WAV_init, /* init() method */ + WAV_quit, /* quit() method */ + WAV_open, /* open() method */ + WAV_close, /* close() method */ + WAV_read, /* read() method */ + WAV_rewind, /* rewind() method */ + WAV_seek /* seek() method */ +}; + + +/* Better than SDL_ReadLE16, since you can detect i/o errors... */ +static __inline__ int read_le16(ALmixer_RWops *rw, uint16_t *ui16) +{ + int rc = ALmixer_RWread(rw, ui16, sizeof (uint16_t), 1); + BAIL_IF_MACRO(rc != 1, ERR_IO_ERROR, 0); + *ui16 = SDL_SwapLE16(*ui16); + return(1); +} /* read_le16 */ + + +/* Better than SDL_ReadLE32, since you can detect i/o errors... */ +static __inline__ int read_le32(ALmixer_RWops *rw, uint32_t *ui32) +{ + int rc = ALmixer_RWread(rw, ui32, sizeof (uint32_t), 1); + BAIL_IF_MACRO(rc != 1, ERR_IO_ERROR, 0); + *ui32 = SDL_SwapLE32(*ui32); + return(1); +} /* read_le32 */ + + +/* This is just cleaner on the caller's end... */ +static __inline__ int read_uint8_t(ALmixer_RWops *rw, uint8_t *ui8) +{ + int rc = ALmixer_RWread(rw, ui8, sizeof (uint8_t), 1); + BAIL_IF_MACRO(rc != 1, ERR_IO_ERROR, 0); + return(1); +} /* read_uint8_t */ + + +static __inline__ uint16_t SDL_ReadLE16( ALmixer_RWops *rw ) +{ + uint16_t result = 0; + + int rc = read_le16( rw, &result ); + + return result; +} +static __inline__ uint32_t SDL_ReadLE32( ALmixer_RWops *rw ) +{ + uint32_t result = 0; + + int rc = read_le32( rw, &result ); + + return result; +} + + /* Chunk management code... */ + +#define riffID 0x46464952 /* "RIFF", in ascii. */ +#define waveID 0x45564157 /* "WAVE", in ascii. */ +#define factID 0x74636166 /* "fact", in ascii. */ + + +/***************************************************************************** + * The FORMAT chunk... * + *****************************************************************************/ + +#define fmtID 0x20746D66 /* "fmt ", in ascii. */ + +#define FMT_NORMAL 0x0001 /* Uncompressed waveform data. */ +#define FMT_ADPCM 0x0002 /* ADPCM compressed waveform data. */ + +typedef struct +{ + int16_t iCoef1; + int16_t iCoef2; +} ADPCMCOEFSET; + +typedef struct +{ + uint8_t bPredictor; + uint16_t iDelta; + int16_t iSamp1; + int16_t iSamp2; +} ADPCMBLOCKHEADER; + +typedef struct S_WAV_FMT_T +{ + uint32_t chunkID; + int32_t chunkSize; + int16_t wFormatTag; + uint16_t wChannels; + uint32_t dwSamplesPerSec; + uint32_t dwAvgBytesPerSec; + uint16_t wBlockAlign; + uint16_t wBitsPerSample; + + uint32_t next_chunk_offset; + + uint32_t sample_frame_size; + uint32_t data_starting_offset; + uint32_t total_bytes; + + void (*free)(struct S_WAV_FMT_T *fmt); + uint32_t (*read_sample)(Sound_Sample *sample); + int (*rewind_sample)(Sound_Sample *sample); + int (*seek_sample)(Sound_Sample *sample, uint32_t ms); + + union + { + struct + { + uint16_t cbSize; + uint16_t wSamplesPerBlock; + uint16_t wNumCoef; + ADPCMCOEFSET *aCoef; + ADPCMBLOCKHEADER *blockheaders; + uint32_t samples_left_in_block; + int nibble_state; + int8_t nibble; + } adpcm; + + /* put other format-specific data here... */ + } fmt; +} fmt_t; + + +/* + * Read in a fmt_t from disk. This makes this process safe regardless of + * the processor's byte order or how the fmt_t structure is packed. + * Note that the union "fmt" is not read in here; that is handled as + * needed in the read_fmt_* functions. + */ +static int read_fmt_chunk(ALmixer_RWops *rw, fmt_t *fmt) +{ + /* skip reading the chunk ID, since it was already read at this point... */ + fmt->chunkID = fmtID; + + BAIL_IF_MACRO(!read_le32(rw, &fmt->chunkSize), NULL, 0); + BAIL_IF_MACRO(fmt->chunkSize < 16, "WAV: Invalid chunk size", 0); + fmt->next_chunk_offset = ALmixer_RWtell(rw) + fmt->chunkSize; + + BAIL_IF_MACRO(!read_le16(rw, &fmt->wFormatTag), NULL, 0); + BAIL_IF_MACRO(!read_le16(rw, &fmt->wChannels), NULL, 0); + BAIL_IF_MACRO(!read_le32(rw, &fmt->dwSamplesPerSec), NULL, 0); + BAIL_IF_MACRO(!read_le32(rw, &fmt->dwAvgBytesPerSec), NULL, 0); + BAIL_IF_MACRO(!read_le16(rw, &fmt->wBlockAlign), NULL, 0); + BAIL_IF_MACRO(!read_le16(rw, &fmt->wBitsPerSample), NULL, 0); + + return(1); +} /* read_fmt_chunk */ + + + +/***************************************************************************** + * The DATA chunk... * + *****************************************************************************/ + +#define dataID 0x61746164 /* "data", in ascii. */ + +typedef struct +{ + uint32_t chunkID; + int32_t chunkSize; + /* Then, (chunkSize) bytes of waveform data... */ +} data_t; + + +/* + * Read in a data_t from disk. This makes this process safe regardless of + * the processor's byte order or how the fmt_t structure is packed. + */ +static int read_data_chunk(ALmixer_RWops *rw, data_t *data) +{ + /* skip reading the chunk ID, since it was already read at this point... */ + data->chunkID = dataID; + BAIL_IF_MACRO(!read_le32(rw, &data->chunkSize), NULL, 0); + return(1); +} /* read_data_chunk */ + + + + +/***************************************************************************** + * this is what we store in our internal->decoder_private field... * + *****************************************************************************/ + +typedef struct +{ + fmt_t *fmt; + int32_t bytesLeft; +} wav_t; + + + + +/***************************************************************************** + * Normal, uncompressed waveform handler... * + *****************************************************************************/ + +/* + * Sound_Decode() lands here for uncompressed WAVs... + */ +static uint32_t read_sample_fmt_normal(Sound_Sample *sample) +{ + uint32_t retval; + Sound_SampleInternal *internal = (Sound_SampleInternal *) sample->opaque; + wav_t *w = (wav_t *) internal->decoder_private; + uint32_t max = (internal->buffer_size < (uint32_t) w->bytesLeft) ? + internal->buffer_size : (uint32_t) w->bytesLeft; + + assert(max > 0); + + /* + * We don't actually do any decoding, so we read the wav data + * directly into the internal buffer... + */ + retval = ALmixer_RWread(internal->rw, internal->buffer, 1, max); + + w->bytesLeft -= retval; + + /* Make sure the read went smoothly... */ + if ((retval == 0) || (w->bytesLeft == 0)) + sample->flags |= SOUND_SAMPLEFLAG_EOF; + + else if (retval == -1) + sample->flags |= SOUND_SAMPLEFLAG_ERROR; + + /* (next call this EAGAIN may turn into an EOF or error.) */ + else if (retval < internal->buffer_size) + sample->flags |= SOUND_SAMPLEFLAG_EAGAIN; + + return(retval); +} /* read_sample_fmt_normal */ + + +static int seek_sample_fmt_normal(Sound_Sample *sample, uint32_t ms) +{ + Sound_SampleInternal *internal = (Sound_SampleInternal *) sample->opaque; + wav_t *w = (wav_t *) internal->decoder_private; + fmt_t *fmt = w->fmt; + int offset = __Sound_convertMsToBytePos(&sample->actual, ms); + int pos = (int) (fmt->data_starting_offset + offset); + int rc = ALmixer_RWseek(internal->rw, pos, SEEK_SET); + BAIL_IF_MACRO(rc != pos, ERR_IO_ERROR, 0); + w->bytesLeft = fmt->total_bytes - offset; + return(1); /* success. */ +} /* seek_sample_fmt_normal */ + + +static int rewind_sample_fmt_normal(Sound_Sample *sample) +{ + /* no-op. */ + return(1); +} /* rewind_sample_fmt_normal */ + + +static int read_fmt_normal(ALmixer_RWops *rw, fmt_t *fmt) +{ + /* (don't need to read more from the RWops...) */ + fmt->free = NULL; + fmt->read_sample = read_sample_fmt_normal; + fmt->rewind_sample = rewind_sample_fmt_normal; + fmt->seek_sample = seek_sample_fmt_normal; + return(1); +} /* read_fmt_normal */ + + + +/***************************************************************************** + * ADPCM compression handler... * + *****************************************************************************/ + +#define FIXED_POINT_COEF_BASE 256 +#define FIXED_POINT_ADAPTION_BASE 256 +#define SMALLEST_ADPCM_DELTA 16 + + +static __inline__ int read_adpcm_block_headers(Sound_Sample *sample) +{ + Sound_SampleInternal *internal = (Sound_SampleInternal *) sample->opaque; + ALmixer_RWops *rw = internal->rw; + wav_t *w = (wav_t *) internal->decoder_private; + fmt_t *fmt = w->fmt; + ADPCMBLOCKHEADER *headers = fmt->fmt.adpcm.blockheaders; + int i; + int max = fmt->wChannels; + + if (w->bytesLeft < fmt->wBlockAlign) + { + sample->flags |= SOUND_SAMPLEFLAG_EOF; + return(0); + } /* if */ + + w->bytesLeft -= fmt->wBlockAlign; + + for (i = 0; i < max; i++) + BAIL_IF_MACRO(!read_uint8_t(rw, &headers[i].bPredictor), NULL, 0); + + for (i = 0; i < max; i++) + BAIL_IF_MACRO(!read_le16(rw, &headers[i].iDelta), NULL, 0); + + for (i = 0; i < max; i++) + BAIL_IF_MACRO(!read_le16(rw, &headers[i].iSamp1), NULL, 0); + + for (i = 0; i < max; i++) + BAIL_IF_MACRO(!read_le16(rw, &headers[i].iSamp2), NULL, 0); + + fmt->fmt.adpcm.samples_left_in_block = fmt->fmt.adpcm.wSamplesPerBlock; + fmt->fmt.adpcm.nibble_state = 0; + return(1); +} /* read_adpcm_block_headers */ + + +static __inline__ void do_adpcm_nibble(uint8_t nib, + ADPCMBLOCKHEADER *header, + int32_t lPredSamp) +{ + static const int32_t max_audioval = ((1<<(16-1))-1); + static const int32_t min_audioval = -(1<<(16-1)); + static const int32_t AdaptionTable[] = + { + 230, 230, 230, 230, 307, 409, 512, 614, + 768, 614, 512, 409, 307, 230, 230, 230 + }; + + int32_t lNewSamp; + int32_t delta; + + if (nib & 0x08) + lNewSamp = lPredSamp + (header->iDelta * (nib - 0x10)); + else + lNewSamp = lPredSamp + (header->iDelta * nib); + + /* clamp value... */ + if (lNewSamp < min_audioval) + lNewSamp = min_audioval; + else if (lNewSamp > max_audioval) + lNewSamp = max_audioval; + + delta = ((int32_t) header->iDelta * AdaptionTable[nib]) / + FIXED_POINT_ADAPTION_BASE; + + if (delta < SMALLEST_ADPCM_DELTA) + delta = SMALLEST_ADPCM_DELTA; + + header->iDelta = delta; + header->iSamp2 = header->iSamp1; + header->iSamp1 = lNewSamp; +} /* do_adpcm_nibble */ + + +static __inline__ int decode_adpcm_sample_frame(Sound_Sample *sample) +{ + Sound_SampleInternal *internal = (Sound_SampleInternal *) sample->opaque; + wav_t *w = (wav_t *) internal->decoder_private; + fmt_t *fmt = w->fmt; + ADPCMBLOCKHEADER *headers = fmt->fmt.adpcm.blockheaders; + ALmixer_RWops *rw = internal->rw; + int i; + int max = fmt->wChannels; + int32_t delta; + uint8_t nib = fmt->fmt.adpcm.nibble; + + for (i = 0; i < max; i++) + { + uint8_t byte; + int16_t iCoef1 = fmt->fmt.adpcm.aCoef[headers[i].bPredictor].iCoef1; + int16_t iCoef2 = fmt->fmt.adpcm.aCoef[headers[i].bPredictor].iCoef2; + int32_t lPredSamp = ((headers[i].iSamp1 * iCoef1) + + (headers[i].iSamp2 * iCoef2)) / + FIXED_POINT_COEF_BASE; + + if (fmt->fmt.adpcm.nibble_state == 0) + { + BAIL_IF_MACRO(!read_uint8_t(rw, &nib), NULL, 0); + fmt->fmt.adpcm.nibble_state = 1; + do_adpcm_nibble(nib >> 4, &headers[i], lPredSamp); + } /* if */ + else + { + fmt->fmt.adpcm.nibble_state = 0; + do_adpcm_nibble(nib & 0x0F, &headers[i], lPredSamp); + } /* else */ + } /* for */ + + fmt->fmt.adpcm.nibble = nib; + return(1); +} /* decode_adpcm_sample_frame */ + + +static __inline__ void put_adpcm_sample_frame1(void *_buf, fmt_t *fmt) +{ + uint16_t *buf = (uint16_t *) _buf; + ADPCMBLOCKHEADER *headers = fmt->fmt.adpcm.blockheaders; + int i; + for (i = 0; i < fmt->wChannels; i++) + *(buf++) = headers[i].iSamp1; +} /* put_adpcm_sample_frame1 */ + + +static __inline__ void put_adpcm_sample_frame2(void *_buf, fmt_t *fmt) +{ + uint16_t *buf = (uint16_t *) _buf; + ADPCMBLOCKHEADER *headers = fmt->fmt.adpcm.blockheaders; + int i; + for (i = 0; i < fmt->wChannels; i++) + *(buf++) = headers[i].iSamp2; +} /* put_adpcm_sample_frame2 */ + + +/* + * Sound_Decode() lands here for ADPCM-encoded WAVs... + */ +static uint32_t read_sample_fmt_adpcm(Sound_Sample *sample) +{ + Sound_SampleInternal *internal = (Sound_SampleInternal *) sample->opaque; + wav_t *w = (wav_t *) internal->decoder_private; + fmt_t *fmt = w->fmt; + uint32_t bw = 0; + + while (bw < internal->buffer_size) + { + /* write ongoing sample frame before reading more data... */ + switch (fmt->fmt.adpcm.samples_left_in_block) + { + case 0: /* need to read a new block... */ + if (!read_adpcm_block_headers(sample)) + { + if ((sample->flags & SOUND_SAMPLEFLAG_EOF) == 0) + sample->flags |= SOUND_SAMPLEFLAG_ERROR; + return(bw); + } /* if */ + + /* only write first sample frame for now. */ + put_adpcm_sample_frame2((uint8_t *) internal->buffer + bw, fmt); + fmt->fmt.adpcm.samples_left_in_block--; + bw += fmt->sample_frame_size; + break; + + case 1: /* output last sample frame of block... */ + put_adpcm_sample_frame1((uint8_t *) internal->buffer + bw, fmt); + fmt->fmt.adpcm.samples_left_in_block--; + bw += fmt->sample_frame_size; + break; + + default: /* output latest sample frame and read a new one... */ + put_adpcm_sample_frame1((uint8_t *) internal->buffer + bw, fmt); + fmt->fmt.adpcm.samples_left_in_block--; + bw += fmt->sample_frame_size; + + if (!decode_adpcm_sample_frame(sample)) + { + sample->flags |= SOUND_SAMPLEFLAG_ERROR; + return(bw); + } /* if */ + } /* switch */ + } /* while */ + + return(bw); +} /* read_sample_fmt_adpcm */ + + +/* + * Sound_FreeSample() lands here for ADPCM-encoded WAVs... + */ +static void free_fmt_adpcm(fmt_t *fmt) +{ + if (fmt->fmt.adpcm.aCoef != NULL) + free(fmt->fmt.adpcm.aCoef); + + if (fmt->fmt.adpcm.blockheaders != NULL) + free(fmt->fmt.adpcm.blockheaders); +} /* free_fmt_adpcm */ + + +static int rewind_sample_fmt_adpcm(Sound_Sample *sample) +{ + Sound_SampleInternal *internal = (Sound_SampleInternal *) sample->opaque; + wav_t *w = (wav_t *) internal->decoder_private; + w->fmt->fmt.adpcm.samples_left_in_block = 0; + return(1); +} /* rewind_sample_fmt_adpcm */ + + +static int seek_sample_fmt_adpcm(Sound_Sample *sample, uint32_t ms) +{ + Sound_SampleInternal *internal = (Sound_SampleInternal *) sample->opaque; + wav_t *w = (wav_t *) internal->decoder_private; + fmt_t *fmt = w->fmt; + uint32_t origsampsleft = fmt->fmt.adpcm.samples_left_in_block; + int origpos = ALmixer_RWtell(internal->rw); + int offset = __Sound_convertMsToBytePos(&sample->actual, ms); + int bpb = (fmt->fmt.adpcm.wSamplesPerBlock * fmt->sample_frame_size); + int skipsize = (offset / bpb) * fmt->wBlockAlign; + int pos = skipsize + fmt->data_starting_offset; + int rc = ALmixer_RWseek(internal->rw, pos, SEEK_SET); + BAIL_IF_MACRO(rc != pos, ERR_IO_ERROR, 0); + + /* The offset we need is in this block, so we need to decode to there. */ + skipsize += (offset % bpb); + rc = (offset % bpb); /* bytes into this block we need to decode */ + if (!read_adpcm_block_headers(sample)) + { + ALmixer_RWseek(internal->rw, origpos, SEEK_SET); /* try to make sane. */ + return(0); + } /* if */ + + /* first sample frame of block is a freebie. :) */ + fmt->fmt.adpcm.samples_left_in_block--; + rc -= fmt->sample_frame_size; + while (rc > 0) + { + if (!decode_adpcm_sample_frame(sample)) + { + ALmixer_RWseek(internal->rw, origpos, SEEK_SET); + fmt->fmt.adpcm.samples_left_in_block = origsampsleft; + return(0); + } /* if */ + + fmt->fmt.adpcm.samples_left_in_block--; + rc -= fmt->sample_frame_size; + } /* while */ + + w->bytesLeft = fmt->total_bytes - skipsize; + return(1); /* success. */ +} /* seek_sample_fmt_adpcm */ + + +/* + * Read in the adpcm-specific info from disk. This makes this process + * safe regardless of the processor's byte order or how the fmt_t + * structure is packed. + */ +static int read_fmt_adpcm(ALmixer_RWops *rw, fmt_t *fmt) +{ + size_t i; + + memset(&fmt->fmt.adpcm, '\0', sizeof (fmt->fmt.adpcm)); + fmt->free = free_fmt_adpcm; + fmt->read_sample = read_sample_fmt_adpcm; + fmt->rewind_sample = rewind_sample_fmt_adpcm; + fmt->seek_sample = seek_sample_fmt_adpcm; + + BAIL_IF_MACRO(!read_le16(rw, &fmt->fmt.adpcm.cbSize), NULL, 0); + BAIL_IF_MACRO(!read_le16(rw, &fmt->fmt.adpcm.wSamplesPerBlock), NULL, 0); + BAIL_IF_MACRO(!read_le16(rw, &fmt->fmt.adpcm.wNumCoef), NULL, 0); + + /* fmt->free() is always called, so these malloc()s will be cleaned up. */ + + i = sizeof (ADPCMCOEFSET) * fmt->fmt.adpcm.wNumCoef; + fmt->fmt.adpcm.aCoef = (ADPCMCOEFSET *) malloc(i); + BAIL_IF_MACRO(fmt->fmt.adpcm.aCoef == NULL, ERR_OUT_OF_MEMORY, 0); + + for (i = 0; i < fmt->fmt.adpcm.wNumCoef; i++) + { + BAIL_IF_MACRO(!read_le16(rw, &fmt->fmt.adpcm.aCoef[i].iCoef1), NULL, 0); + BAIL_IF_MACRO(!read_le16(rw, &fmt->fmt.adpcm.aCoef[i].iCoef2), NULL, 0); + } /* for */ + + i = sizeof (ADPCMBLOCKHEADER) * fmt->wChannels; + fmt->fmt.adpcm.blockheaders = (ADPCMBLOCKHEADER *) malloc(i); + BAIL_IF_MACRO(fmt->fmt.adpcm.blockheaders == NULL, ERR_OUT_OF_MEMORY, 0); + + return(1); +} /* read_fmt_adpcm */ + + + +/***************************************************************************** + * Everything else... * + *****************************************************************************/ + +static int WAV_init(void) +{ + return(1); /* always succeeds. */ +} /* WAV_init */ + + +static void WAV_quit(void) +{ + /* it's a no-op. */ +} /* WAV_quit */ + + +static int read_fmt(ALmixer_RWops *rw, fmt_t *fmt) +{ + /* if it's in this switch statement, we support the format. */ + switch (fmt->wFormatTag) + { + case FMT_NORMAL: + SNDDBG(("WAV: Appears to be uncompressed audio.\n")); + return(read_fmt_normal(rw, fmt)); + + case FMT_ADPCM: + SNDDBG(("WAV: Appears to be ADPCM compressed audio.\n")); + return(read_fmt_adpcm(rw, fmt)); + + /* add other types here. */ + + default: +#ifdef ANDROID_NDK + SNDDBG(("WAV: Format is unknown.\n")); +#else + SNDDBG(("WAV: Format 0x%X is unknown.\n", + (unsigned int) fmt->wFormatTag)); +#endif + BAIL_MACRO("WAV: Unsupported format", 0); + } /* switch */ + + assert(0); /* shouldn't hit this point. */ + return(0); +} /* read_fmt */ + + +/* + * Locate a specific chunk in the WAVE file by ID... + */ +static int find_chunk(ALmixer_RWops *rw, uint32_t id) +{ + int32_t siz = 0; + uint32_t _id = 0; + uint32_t pos = ALmixer_RWtell(rw); + + while (1) + { + BAIL_IF_MACRO(!read_le32(rw, &_id), NULL, 0); + if (_id == id) + return(1); + + /* skip ahead and see what next chunk is... */ + BAIL_IF_MACRO(!read_le32(rw, &siz), NULL, 0); + assert(siz >= 0); + pos += (sizeof (uint32_t) * 2) + siz; + if (siz > 0) + BAIL_IF_MACRO(ALmixer_RWseek(rw, pos, SEEK_SET) != pos, NULL, 0); + } /* while */ + + return(0); /* shouldn't hit this, but just in case... */ +} /* find_chunk */ + + +static int WAV_open_internal(Sound_Sample *sample, const char *ext, fmt_t *fmt) +{ + Sound_SampleInternal *internal = (Sound_SampleInternal *) sample->opaque; + ALmixer_RWops *rw = internal->rw; + data_t d; + wav_t *w; + uint32_t pos; + + BAIL_IF_MACRO(SDL_ReadLE32(rw) != riffID, "WAV: Not a RIFF file.", 0); + SDL_ReadLE32(rw); /* throw the length away; we get this info later. */ + BAIL_IF_MACRO(SDL_ReadLE32(rw) != waveID, "WAV: Not a WAVE file.", 0); + BAIL_IF_MACRO(!find_chunk(rw, fmtID), "WAV: No format chunk.", 0); + BAIL_IF_MACRO(!read_fmt_chunk(rw, fmt), "WAV: Can't read format chunk.", 0); + + sample->actual.channels = (uint8_t) fmt->wChannels; + sample->actual.rate = fmt->dwSamplesPerSec; + if ((fmt->wBitsPerSample == 4) /*|| (fmt->wBitsPerSample == 0) */ ) + sample->actual.format = AUDIO_S16SYS; + else if (fmt->wBitsPerSample == 8) + sample->actual.format = AUDIO_U8; + else if (fmt->wBitsPerSample == 16) + sample->actual.format = AUDIO_S16LSB; + else + { +#ifdef ANDROID_NDK + SNDDBG(("WAV: unsupported sample size.\n")); +#else + SNDDBG(("WAV: %d bits per sample!?\n", (int) fmt->wBitsPerSample)); +#endif + BAIL_MACRO("WAV: Unsupported sample size.", 0); + } /* else */ + + BAIL_IF_MACRO(!read_fmt(rw, fmt), NULL, 0); + ALmixer_RWseek(rw, fmt->next_chunk_offset, SEEK_SET); + BAIL_IF_MACRO(!find_chunk(rw, dataID), "WAV: No data chunk.", 0); + BAIL_IF_MACRO(!read_data_chunk(rw, &d), "WAV: Can't read data chunk.", 0); + + w = (wav_t *) malloc(sizeof(wav_t)); + BAIL_IF_MACRO(w == NULL, ERR_OUT_OF_MEMORY, 0); + w->fmt = fmt; + fmt->total_bytes = w->bytesLeft = d.chunkSize; + fmt->data_starting_offset = ALmixer_RWtell(rw); + fmt->sample_frame_size = ( ((sample->actual.format & 0xFF) / 8) * + sample->actual.channels ); + internal->decoder_private = (void *) w; + + internal->total_time = (fmt->total_bytes / fmt->dwAvgBytesPerSec) * 1000; + internal->total_time += (fmt->total_bytes % fmt->dwAvgBytesPerSec) + * 1000 / fmt->dwAvgBytesPerSec; + + sample->flags = SOUND_SAMPLEFLAG_NONE; + if (fmt->seek_sample != NULL) + sample->flags |= SOUND_SAMPLEFLAG_CANSEEK; + + SNDDBG(("WAV: Accepting data stream.\n")); + return(1); /* we'll handle this data. */ +} /* WAV_open_internal */ + + +static int WAV_open(Sound_Sample *sample, const char *ext) +{ + int rc; + + fmt_t *fmt = (fmt_t *) malloc(sizeof (fmt_t)); + BAIL_IF_MACRO(fmt == NULL, ERR_OUT_OF_MEMORY, 0); + memset(fmt, '\0', sizeof (fmt_t)); + + rc = WAV_open_internal(sample, ext, fmt); + if (!rc) + { + if (fmt->free != NULL) + fmt->free(fmt); + free(fmt); + } /* if */ + + return(rc); +} /* WAV_open */ + + +static void WAV_close(Sound_Sample *sample) +{ + Sound_SampleInternal *internal = (Sound_SampleInternal *) sample->opaque; + wav_t *w = (wav_t *) internal->decoder_private; + + if (w->fmt->free != NULL) + w->fmt->free(w->fmt); + + free(w->fmt); + free(w); +} /* WAV_close */ + + +static uint32_t WAV_read(Sound_Sample *sample) +{ + Sound_SampleInternal *internal = (Sound_SampleInternal *) sample->opaque; + wav_t *w = (wav_t *) internal->decoder_private; + return(w->fmt->read_sample(sample)); +} /* WAV_read */ + + +static int WAV_rewind(Sound_Sample *sample) +{ + Sound_SampleInternal *internal = (Sound_SampleInternal *) sample->opaque; + wav_t *w = (wav_t *) internal->decoder_private; + fmt_t *fmt = w->fmt; + int rc = ALmixer_RWseek(internal->rw, fmt->data_starting_offset, SEEK_SET); + BAIL_IF_MACRO(rc != fmt->data_starting_offset, ERR_IO_ERROR, 0); + w->bytesLeft = fmt->total_bytes; + return(fmt->rewind_sample(sample)); +} /* WAV_rewind */ + + +static int WAV_seek(Sound_Sample *sample, uint32_t ms) +{ + Sound_SampleInternal *internal = (Sound_SampleInternal *) sample->opaque; + wav_t *w = (wav_t *) internal->decoder_private; + return(w->fmt->seek_sample(sample, ms)); +} /* WAV_seek */ + +#endif /* SOUND_SUPPORTS_WAV */ + +/* end of wav.c ... */ + diff -r b346b6608eab -r 71b465ff0622 Isolated/SimpleMutex.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Isolated/SimpleMutex.c Thu Apr 28 16:22:30 2011 -0700 @@ -0,0 +1,155 @@ +#include "SimpleMutex.h" +#include + +#if defined(DEBUG) +#include +#define MUTEXDBG(x) printf x +#else +#define MUTEXDBG(x) +#endif + +#if defined(_WIN32) && !defined(__CYGWIN32__) + #include + #include /* For CreateMutex(), LockFile() */ + + struct SimpleMutex + { + HANDLE nativeMutex; + }; + + + SimpleMutex* SimpleMutex_CreateMutex() + { + SimpleMutex* simple_mutex = (SimpleMutex*)malloc(sizeof(SimpleMutex)); + if(NULL == simple_mutex) + { + MUTEXDBG(("Out of memory.\n")); + return NULL; + } + simple_mutex->nativeMutex = CreateMutex(NULL, FALSE, NULL); + if(NULL == simple_mutex->nativeMutex) + { + MUTEXDBG(("Out of memory.\n")); + free(simple_mutex); + return NULL; + } + return simple_mutex; + } + void SimpleMutex_DestroyMutex(SimpleMutex* simple_mutex) + { + if(NULL == simple_mutex) + { + return; + } + CloseHandle(simple_mutex->nativeMutex); + free(simple_mutex); + } + /* This will return true if locking is successful, false if not. + */ + int SimpleMutex_LockMutex(SimpleMutex* simple_mutex) + { +#ifdef DEBUG + if(NULL == simple_mutex) + { + MUTEXDBG(("SimpleMutex_LockMutex was passed NULL\n")); + return 0; + } +#endif + return( + WaitForSingleObject( + simple_mutex->nativeMutex, + INFINITE + ) != WAIT_FAILED + ); + } + void SimpleMutex_UnlockMutex(SimpleMutex* simple_mutex) + { +#ifdef DEBUG + if(NULL == simple_mutex) + { + MUTEXDBG(("SimpleMutex_UnlockMutex was passed NULL\n")); + return; + } +#endif + ReleaseMutex( + simple_mutex->nativeMutex + ); + } +#else /* Assuming POSIX...maybe not a good assumption. */ + #include + + struct SimpleMutex + { + pthread_mutex_t* nativeMutex; + }; + + SimpleMutex* SimpleMutex_CreateMutex() + { + int ret_val; + SimpleMutex* simple_mutex = (SimpleMutex*)malloc(sizeof(SimpleMutex)); + if(NULL == simple_mutex) + { + MUTEXDBG(("Out of memory.\n")); + return NULL; + } + simple_mutex->nativeMutex = (pthread_mutex_t*)malloc(sizeof(pthread_mutex_t)); + if(NULL == simple_mutex->nativeMutex) + { + MUTEXDBG(("Out of memory.\n")); + free(simple_mutex); + return NULL; + } + + ret_val = pthread_mutex_init(simple_mutex->nativeMutex, NULL); + if(0 != ret_val) + { + free(simple_mutex->nativeMutex); + free(simple_mutex); + return NULL; + } + return simple_mutex; + } + void SimpleMutex_DestroyMutex(SimpleMutex* simple_mutex) + { + if(NULL != simple_mutex) + { + pthread_mutex_destroy(simple_mutex->nativeMutex); + free(simple_mutex->nativeMutex); + free(simple_mutex); + } + } + /* This will return true if locking is successful, false if not. + * (This is the opposite of pthread_mutex_lock which returns + * 0 for success.) + */ + int SimpleMutex_LockMutex(SimpleMutex* simple_mutex) + { +#ifdef DEBUG + if(NULL == simple_mutex) + { + MUTEXDBG(("SimpleMutex_LockMutex was passed NULL\n")); + return 0; + } +#endif + return( + pthread_mutex_lock( + simple_mutex->nativeMutex + ) == 0 + ); + } + void SimpleMutex_UnlockMutex(SimpleMutex* simple_mutex) + { +#ifdef DEBUG + if(NULL == simple_mutex) + { + MUTEXDBG(("SimpleMutex_LockMutex was passed NULL\n")); + return; + } +#endif + pthread_mutex_unlock( + simple_mutex->nativeMutex + ); + } +#endif + + diff -r b346b6608eab -r 71b465ff0622 Isolated/SimpleMutex.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Isolated/SimpleMutex.h Thu Apr 28 16:22:30 2011 -0700 @@ -0,0 +1,25 @@ +/* Copyright: Eric Wing 2003 */ + +#ifndef SIMPLE_MUTEX_H +#define SIMPLE_MUTEX_H + +#ifdef __cplusplus +extern "C" { +#endif + + +typedef struct SimpleMutex SimpleMutex; + +SimpleMutex* SimpleMutex_CreateMutex(void); +void SimpleMutex_DestroyMutex(SimpleMutex* simple_mutex); +int SimpleMutex_LockMutex(SimpleMutex* simple_mutex); +void SimpleMutex_UnlockMutex(SimpleMutex* simple_mutex); + + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +} +#endif + +#endif + diff -r b346b6608eab -r 71b465ff0622 Isolated/SimpleThread.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Isolated/SimpleThread.h Thu Apr 28 16:22:30 2011 -0700 @@ -0,0 +1,30 @@ +#ifndef SIMPLE_THREAD +#define SIMPLE_THREAD + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +typedef struct SimpleThread SimpleThread; + + +SimpleThread* SimpleThread_CreateThread(int (*user_function)(void*), void* user_data); + +size_t SimpleThread_GetCurrentThreadID(void); +size_t SimpleThread_GetThreadID(SimpleThread* simple_thread); + +void SimpleThread_WaitThread(SimpleThread* simple_thread, int* thread_status); + + +int SimpleThread_GetThreadPriority(SimpleThread* simple_thread); +void SimpleThread_SetThreadPriority(SimpleThread* simple_thread, int priority_level); + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +} +#endif + +#endif + diff -r b346b6608eab -r 71b465ff0622 Isolated/SimpleThreadPosix.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Isolated/SimpleThreadPosix.c Thu Apr 28 16:22:30 2011 -0700 @@ -0,0 +1,214 @@ +#include "SimpleThread.h" +#include +#include +#include +#include + +#if defined(DEBUG) +#include +#define THRDDBG(x) printf x +#else +#define THRDDBG(x) +#endif + + +struct SimpleThread +{ + size_t threadID; + pthread_t nativeThread; + int threadStatus; +// void* userData; +}; + +typedef struct SimpleThreadArguments +{ + int (*userFunction)(void*); + void* userData; + SimpleThread* simpleThread; +} SimpleThreadArguments; + + + +static void* Internal_RunThread(void* user_data) +{ + int (*user_function)(void*); + void* function_user_data; + int* status_val; + +#if 0 + /* disable signals */ + sigset_t disable_set; + + /* + in the main thread, set up the desired signal mask, common to most threads + any newly created threads will inherit this signal mask + */ + sigemptyset(&disable_set); + sigaddset(&disable_set, SIGHUP); + sigaddset(&disable_set, SIGINT); + sigaddset(&disable_set, SIGUSR1); + sigaddset(&disable_set, SIGUSR2); + sigaddset(&disable_set, SIGALRM); + sigaddset(&disable_set, SIGQUIT); + sigaddset(&disable_set, SIGPIPE); + sigaddset(&disable_set, SIGTERM); + sigaddset(&disable_set, SIGCHLD); + sigaddset(&disable_set, SIGWINCH); + sigaddset(&disable_set, SIGVTALRM); + sigaddset(&disable_set, SIGPROF); + + + /* block out these signals */ + sigprocmask(SIG_BLOCK, &disable_set, NULL); +#endif + + SimpleThreadArguments* simple_thread_arguments = (SimpleThreadArguments*)user_data; + simple_thread_arguments->simpleThread->threadID = SimpleThread_GetCurrentThreadID(); + + user_function = simple_thread_arguments->userFunction; + function_user_data = simple_thread_arguments->userData; + status_val = &simple_thread_arguments->simpleThread->threadStatus; + + + /* I hope this is safe to delete on a different thread than it was created for. */ + free(simple_thread_arguments); + + *status_val = user_function(function_user_data); + + pthread_exit(NULL); + return NULL; +} + + +SimpleThread* SimpleThread_CreateThread(int (*user_function)(void*), void* user_data) +{ + pthread_attr_t thread_attributes; + int ret_val; + SimpleThread* new_thread; + SimpleThreadArguments* simple_thread_arguments; + + new_thread = (SimpleThread*)malloc(sizeof(SimpleThread)); + if(NULL == new_thread) + { + THRDDBG(("Out of memory.\n")); + return NULL; + } + + ret_val = pthread_attr_init(&thread_attributes); + if(0 != ret_val) + { + /* failed */ + THRDDBG(("pthread_attr_init failed with: %d\n", ret_val)); + free(new_thread); + return 0; + } + ret_val = pthread_attr_setdetachstate(&thread_attributes, PTHREAD_CREATE_JOINABLE); + if(0 != ret_val) + { + THRDDBG(("pthread_attr_setdetachstate failed with: %d\n", ret_val)); + free(new_thread); + return NULL; + } + + simple_thread_arguments = (SimpleThreadArguments*)malloc(sizeof(SimpleThreadArguments)); + if(NULL == simple_thread_arguments) + { + THRDDBG(("Out of memory.\n")); + free(new_thread); + return NULL; + } + simple_thread_arguments->userFunction = user_function; + simple_thread_arguments->userData = user_data; + simple_thread_arguments->simpleThread = new_thread; + + ret_val = pthread_create(&new_thread->nativeThread, &thread_attributes, Internal_RunThread, simple_thread_arguments); + if(0 != ret_val) + { + THRDDBG(("pthread_create failed with: %d\n", ret_val)); + free(simple_thread_arguments); + free(new_thread); + return NULL; + } + + return new_thread; +} + + + +size_t SimpleThread_GetCurrentThreadID() +{ + return (size_t)pthread_self(); +} + +void SimpleThread_WaitThread(SimpleThread* simple_thread, int* thread_status) +{ + int ret_val; + if(NULL == simple_thread) + { + THRDDBG(("SimpleThread_WaitThread was passed NULL\n")); + return; + } + + + ret_val = pthread_join(simple_thread->nativeThread, 0); + if(0 != ret_val) + { + THRDDBG(("pthread_join failed with: %d\n", ret_val)); + } + if(NULL != thread_status) + { + *thread_status = simple_thread->threadStatus; + } + free(simple_thread); +} + +size_t SimpleThread_GetThreadID(SimpleThread* simple_thread) +{ + if(NULL == simple_thread) + { + THRDDBG(("SimpleThread_GetThreadID was passed NULL\n")); + return 0; + } + return simple_thread->threadID; +} + + +int SimpleThread_GetThreadPriority(SimpleThread* simple_thread) +{ + struct sched_param schedule_param; + int sched_policy; + int ret_val; + + if(NULL == simple_thread) + { + THRDDBG(("SimpleThread_GetThreadPriority was passed NULL\n")); + return -1; /* Not sure what to return. Do other platforms use negative numbers? */ + } + ret_val = pthread_getschedparam(simple_thread->nativeThread, &sched_policy, &schedule_param); + if(0 != ret_val) + { + THRDDBG(("SimpleThread_GetThreadPriority pthread_getschedparam failed with: %d\n", ret_val)); + return -1; + } + return schedule_param.sched_priority; +} + +void SimpleThread_SetThreadPriority(SimpleThread* simple_thread, int priority_level) +{ + struct sched_param schedule_param; + int ret_val; + + if(NULL == simple_thread) + { + THRDDBG(("SimpleThread_SetThreadPriority was passed NULL\n")); + return; + } + schedule_param.sched_priority = priority_level; /* PTHREAD_MIN_PRIORITY=0 to PTHREAD_MAX_PRIORITY=31 */ + ret_val = pthread_setschedparam(simple_thread->nativeThread, SCHED_OTHER, &schedule_param); + if(0 != ret_val) + { + THRDDBG(("SimpleThread_SetThreadPriority pthread_setschedparam failed with: %d\n", ret_val)); + return; + } +} + diff -r b346b6608eab -r 71b465ff0622 Isolated/SoundDecoder.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Isolated/SoundDecoder.c Thu Apr 28 16:22:30 2011 -0700 @@ -0,0 +1,711 @@ +#ifndef ALMIXER_COMPILED_WITH_SDL + +#include "SoundDecoder.h" +#include "SoundDecoder_Internal.h" +#include "tErrorLib.h" +#include "LinkedList.h" +#include +#include +#include + +#ifdef ANDROID_NDK +#include +#endif + +/* A partial shim reimplementation of SDL_sound to work around the LGPL issues. + * This implementation is more limited than SDL_sound. + * For example, there is no generic software conversion routines. + * Functions are also not necessarily thread safe. + * This implementation relies on the back-end decoder much more heavily + * than SDL_sound. (For example, I bypass the internal->buffer.) + */ + +static LinkedList* s_listOfLoadedSamples = NULL; + +static signed char s_isInitialized = 0; +static TErrorPool* s_errorPool = NULL; + +static const SoundDecoder_DecoderInfo** s_availableDecoders = NULL; + +#ifdef __APPLE__ +//extern const SoundDecoder_DecoderFunctions __SoundDecoder_DecoderFunctions_CoreAudio; +extern const Sound_DecoderFunctions __Sound_DecoderFunctions_CoreAudio; +#endif +#ifdef ANDROID_NDK +#ifdef SOUND_SUPPORTS_WAV +extern const Sound_DecoderFunctions __Sound_DecoderFunctions_WAV; +#endif +#ifdef SOUND_SUPPORTS_MPG123 +extern const Sound_DecoderFunctions __Sound_DecoderFunctions_MPG123; +#endif +#ifdef SOUND_SUPPORTS_OGG +extern const Sound_DecoderFunctions __Sound_DecoderFunctions_OGG; +#endif +#endif + +typedef struct +{ + int available; + const SoundDecoder_DecoderFunctions* funcs; +} SoundElement; + +static SoundElement s_linkedDecoders[] = +{ +#if defined(__APPLE__) + { 0, &__Sound_DecoderFunctions_CoreAudio }, +#endif +#if defined(ANDROID_NDK) +#ifdef SOUND_SUPPORTS_WAV + { 0, &__Sound_DecoderFunctions_WAV }, +#endif +#ifdef SOUND_SUPPORTS_MPG123 + { 0, &__Sound_DecoderFunctions_MPG123 }, +#endif +#ifdef SOUND_SUPPORTS_OGG + { 0, &__Sound_DecoderFunctions_OGG }, +#endif +#endif + { 0, NULL } +}; + + +#include +int SoundDecoder_strcasecmp(const char* str1, const char* str2) +{ + int the_char1; + int the_char2; + int i = 0; + if(str1 == str2) + { + return 0; + } + if(NULL == str1) + { + return -1; + } + if(NULL == str2) + { + return 1; + } + + do + { + the_char1 = tolower(str1[i]); + the_char2 = tolower(str2[i]); + if(the_char1 < the_char2) + { + return -1; + } + else if(the_char1 > the_char2) + { + return 1; + } + i++; + } while( (0 != the_char1) && (0 != the_char2) ); + + return 0; +} + + +#ifdef ANDROID_NDK +#include +#include +int SoundDecoder_DebugPrint(const char* format, ...) +{ + va_list arg_list; + int ret_val; + + va_start(arg_list, format); + ret_val = __android_log_vprint(ANDROID_LOG_INFO, "SoundDecoder", format, arg_list); + va_end(arg_list); + return ret_val; +} +#endif + +const char* SoundDecoder_GetError() +{ + const char* error_string = NULL; + if(NULL == s_errorPool) + { + return "Error: You should not call SoundDecoder_GetError while Sound is not initialized"; + } + error_string = TError_GetLastErrorStr(s_errorPool); + /* SDL returns empty strings instead of NULL */ + if(NULL == error_string) + { + return ""; + } + else + { + return error_string; + } +} + +void SoundDecoder_ClearError() +{ + if(NULL == s_errorPool) + { + return; + } + TError_SetError(s_errorPool, 0, NULL); +} + +void SoundDecoder_SetError(const char* err_str, ...) +{ + if(NULL == s_errorPool) + { + fprintf(stderr, "Error: You should not call SoundDecoder_SetError while Sound is not initialized\n"); + return; + } + va_list argp; + va_start(argp, err_str); + // SDL_SetError which I'm emulating has no number parameter. + TError_SetErrorv(s_errorPool, 1, err_str, argp); + va_end(argp); +#ifdef ANDROID_NDK + __android_log_print(ANDROID_LOG_INFO, "SoundDecoder_SetError", TError_GetLastErrorStr(s_errorPool)); +#endif +} + + +void SoundDecoder_GetLinkedVersion(SoundDecoder_Version* the_version) +{ + if(NULL != the_version) + { + the_version->major = SOUNDDECODER_VER_MAJOR; + the_version->minor = SOUNDDECODER_VER_MINOR; + the_version->patch = SOUNDDECODER_VER_PATCH; + } +} + + +const SoundDecoder_DecoderInfo** SoundDecoder_AvailableDecoders() +{ + return(s_availableDecoders); +} + +int SoundDecoder_Init() +{ + size_t total_number_of_decoders; + size_t i; + size_t current_pos = 0; + if(1 == s_isInitialized) + { + return 1; + } + if(NULL == s_errorPool) + { + s_errorPool = TError_CreateErrorPool(); + } + if(NULL == s_errorPool) + { + return 0; + } + + total_number_of_decoders = sizeof(s_linkedDecoders) / sizeof(s_linkedDecoders[0]); + s_availableDecoders = (const SoundDecoder_DecoderInfo **)malloc((total_number_of_decoders) * sizeof(SoundDecoder_DecoderInfo*)); + if(NULL == s_availableDecoders) + { + SoundDecoder_SetError(ERR_OUT_OF_MEMORY); + return 0; + } + + /* Allocate memory for linked list of sound samples. */ + s_listOfLoadedSamples = LinkedList_Create(); + if(NULL == s_listOfLoadedSamples) + { + free(s_availableDecoders); + s_availableDecoders = NULL; + SoundDecoder_SetError(ERR_OUT_OF_MEMORY); + return 0; + } + + for(i = 0; s_linkedDecoders[i].funcs != NULL; i++) + { + s_linkedDecoders[i].available = s_linkedDecoders[i].funcs->init(); + if(s_linkedDecoders[i].available) + { + s_availableDecoders[current_pos] = &(s_linkedDecoders[i].funcs->info); + current_pos++; + } + } + + s_availableDecoders[current_pos] = NULL; + s_isInitialized = 1; + return 1; +} + +void SoundDecoder_Quit() +{ + size_t i; + if(0 == s_isInitialized) + { + return; + } + + /* + * SDL_sound actually embeds the linked list in the internal data structure. + * So any sample can potentially reach any other sample. + * But I'm keeping my own separate list. + */ + while(LinkedList_Size(s_listOfLoadedSamples) > 0) + { + SoundDecoder_Sample* sound_sample = (SoundDecoder_Sample*)LinkedList_PopBack(s_listOfLoadedSamples); + SoundDecoder_FreeSample(sound_sample); + } + LinkedList_Free(s_listOfLoadedSamples); + s_listOfLoadedSamples = NULL; + + + for(i = 0; s_linkedDecoders[i].funcs != NULL; i++) + { + if (s_linkedDecoders[i].available) + { + s_linkedDecoders[i].funcs->quit(); + s_linkedDecoders[i].available = 0; + } + } + + if(NULL != s_availableDecoders) + { + free(s_availableDecoders); + } + s_availableDecoders = NULL; + + + /* Remember: ALmixer_SetError/GetError calls will not work while this is gone. */ + TError_FreeErrorPool(s_errorPool); + s_errorPool = NULL; + + s_isInitialized = 0; +} + + +void SoundDecoder_FreeSample(SoundDecoder_Sample* sound_sample) +{ + SoundDecoder_SampleInternal* sample_internal; + + /* Quit unloads all samples, so it is not possible to free a sample + * when not initialized. + */ + if(0 == s_isInitialized) + { + return; + } + + if(sound_sample == NULL) + { + return; + } + + /* SDL_sound keeps a linked list of all the loaded samples. + * We want to remove the current sample from that list. + */ + LinkedListNode* the_node = LinkedList_Find(s_listOfLoadedSamples, sound_sample, NULL); + if(NULL == the_node) + { + SoundDecoder_SetError("SoundDecoder_FreeSample: Internal Error, sample does not exist in linked list."); + return; + } + LinkedList_Remove(s_listOfLoadedSamples, the_node); + + sample_internal = (SoundDecoder_SampleInternal*)sound_sample->opaque; + + /* Ugh...SDL_sound has a lot of pointers. + * I hope I didn't miss any dynamic memory. + */ + + /* Call close on the decoder */ + sample_internal->funcs->close(sound_sample); + + if(NULL != sample_internal->rw) + { + sample_internal->rw->close(sample_internal->rw); + } + + /* Ooops. The public buffer might be shared with the internal buffer. + * Make sure to not accidentally double delete. + */ + if((NULL != sample_internal->buffer) + && (sample_internal->buffer != sound_sample->buffer) + ) + { + free(sample_internal->buffer); + } + free(sample_internal); + + if(NULL != sound_sample->buffer) + { + free(sound_sample->buffer); + } + free(sound_sample); +} + + + +static int Internal_LoadSample(const SoundDecoder_DecoderFunctions *funcs, + SoundDecoder_Sample* sound_sample, const char *ext +) +{ + SoundDecoder_SampleInternal* internal_sample = (SoundDecoder_SampleInternal*)sound_sample->opaque; + long current_file_position = internal_sample->rw->seek(internal_sample->rw, 0, SEEK_CUR); + + /* fill in the funcs for this decoder... */ + sound_sample->decoder = &funcs->info; + internal_sample->funcs = funcs; + if (!funcs->open(sound_sample, ext)) + { + internal_sample->rw->seek(internal_sample->rw, current_file_position, SEEK_SET); + return 0; + } + + /* we found a compatible decoder */ + + /* SDL_sound normally goes on to setup a bunch of things to + * support format conversion via SDL APIs. + * I am not porting any of that stuff. + * My goal is to simply setup the struct properties to values + * that will not cause any confusion with other parts of the implementation. + */ + + + if(0 == sound_sample->desired.format) + { + sound_sample->desired.format = sound_sample->actual.format; + } + if(0 == sound_sample->desired.channels) + { + sound_sample->desired.channels = sound_sample->actual.channels; + } + if(0 == sound_sample->desired.rate) + { + sound_sample->desired.rate = sound_sample->actual.rate; + } + + /* I'm a little confused at the difference between the internal + * public buffer. I am going to make them the same. + * I assume I already allocated the public buffer. + */ + internal_sample->buffer = sound_sample->buffer; + internal_sample->buffer_size = sound_sample->buffer_size; + + /* Insert the new sample into the linked list of samples. */ + LinkedList_PushBack(s_listOfLoadedSamples, sound_sample); + + return 1; +} + + + +SoundDecoder_Sample* SoundDecoder_NewSampleFromFile(const char* file_name, + SoundDecoder_AudioInfo* desired_format, + size_t buffer_size) +{ + + const char* file_extension; + ALmixer_RWops* rw_ops; + + + if(0 == s_isInitialized) + { + SoundDecoder_SetError(ERR_NOT_INITIALIZED); + return NULL; + } + if(NULL == file_name) + { + SoundDecoder_SetError("No file specified"); + return NULL; + } + + file_extension = strrchr(file_name, '.'); + if(NULL != file_extension) + { + file_extension++; + } + + rw_ops = ALmixer_RWFromFile(file_name, "rb"); + + return SoundDecoder_NewSample(rw_ops, file_extension, desired_format, buffer_size); +} + + +SoundDecoder_Sample* SoundDecoder_NewSample(ALmixer_RWops* rw_ops, const char* file_extension, SoundDecoder_AudioInfo* desired_format, size_t buffer_size) +{ + SoundDecoder_Sample* new_sample; + SoundDecoder_SampleInternal* internal_sample; + SoundElement* current_decoder; + + if(0 == s_isInitialized) + { + SoundDecoder_SetError(ERR_NOT_INITIALIZED); + return NULL; + } + if(NULL == rw_ops) + { + SoundDecoder_SetError("No file specified"); + return NULL; + } + + new_sample = (SoundDecoder_Sample*)calloc(1, sizeof(SoundDecoder_Sample)); + if(NULL == new_sample) + { + SoundDecoder_SetError(ERR_OUT_OF_MEMORY); + return NULL; + } + internal_sample = (SoundDecoder_SampleInternal*)calloc(1, sizeof(SoundDecoder_SampleInternal)); + if(NULL == internal_sample) + { + free(new_sample); + SoundDecoder_SetError(ERR_OUT_OF_MEMORY); + return NULL; + } + new_sample->buffer = calloc(1, buffer_size); + if(NULL == new_sample->buffer) + { + free(internal_sample); + free(new_sample); + SoundDecoder_SetError(ERR_OUT_OF_MEMORY); + return NULL; + } + + new_sample->buffer_size = buffer_size; + + if(NULL != desired_format) + { + memcpy(&new_sample->desired, desired_format, sizeof(SoundDecoder_AudioInfo)); + } + + internal_sample->rw = rw_ops; + new_sample->opaque = internal_sample; + + + if(NULL != file_extension) + { + for(current_decoder = &s_linkedDecoders[0]; current_decoder->funcs != NULL; current_decoder++) + { + if(current_decoder->available) + { + const char** decoder_file_extension = current_decoder->funcs->info.extensions; + while(*decoder_file_extension) + { + if(0 == (SoundDecoder_strcasecmp(*decoder_file_extension, file_extension))) + { + if(Internal_LoadSample(current_decoder->funcs, new_sample, file_extension)) + { + return(new_sample); + } + break; /* go to the next decoder */ + } + decoder_file_extension++; + } + } + } + } + + /* no direct file_extensionension match? Try everything we've got... */ + for(current_decoder = &s_linkedDecoders[0]; current_decoder->funcs != NULL; current_decoder++) + { + if(current_decoder->available) + { + int already_tried_decoder = 0; + const char** decoder_file_extension = current_decoder->funcs->info.extensions; + + /* skip decoders we already tried from the above loop */ + while(*decoder_file_extension) + { + if(SoundDecoder_strcasecmp(*decoder_file_extension, file_extension) == 0) + { + already_tried_decoder = 1; + break; + } + decoder_file_extension++; + } + + if(0 == already_tried_decoder) + { + if (Internal_LoadSample(current_decoder->funcs, new_sample, file_extension)) + { + return new_sample; + } + } + } + } + + /* could not find a decoder */ + SoundDecoder_SetError(ERR_UNSUPPORTED_FORMAT); + /* clean up the memory */ + free(new_sample->opaque); + if(NULL != new_sample->buffer) + { + free(new_sample->buffer); + } + free(new_sample); + + rw_ops->close(rw_ops); + return NULL; +} + + +int SoundDecoder_SetBufferSize(SoundDecoder_Sample* sound_sample, size_t new_buffer_size) +{ + SoundDecoder_SampleInternal* internal_sample = NULL; + void* new_buffer_ptr = NULL; + + BAIL_IF_MACRO(!s_isInitialized, ERR_NOT_INITIALIZED, 0); + BAIL_IF_MACRO(NULL == sound_sample, ERR_NULL_SAMPLE, 0); + + internal_sample = ((SoundDecoder_SampleInternal*)sound_sample->opaque); + + + new_buffer_ptr = realloc(sound_sample->buffer, new_buffer_size); + BAIL_IF_MACRO(NULL == new_buffer_ptr, ERR_OUT_OF_MEMORY, 0); + + sound_sample->buffer = new_buffer_ptr; + sound_sample->buffer_size = new_buffer_size; + internal_sample->buffer = sound_sample->buffer; + internal_sample->buffer_size = sound_sample->buffer_size; + + return 1; +} + + +size_t SoundDecoder_Decode(SoundDecoder_Sample* sound_sample) +{ + SoundDecoder_SampleInternal* internal_sample = NULL; + size_t bytes_read = 0; + + BAIL_IF_MACRO(!s_isInitialized, ERR_NOT_INITIALIZED, 0); + BAIL_IF_MACRO(NULL == sound_sample, ERR_NULL_SAMPLE, 0); + BAIL_IF_MACRO(sound_sample->flags & SOUND_SAMPLEFLAG_ERROR, ERR_PREVIOUS_SAMPLE_ERROR, 0); + BAIL_IF_MACRO(sound_sample->flags & SOUND_SAMPLEFLAG_EOF, ERR_ALREADY_AT_EOF_ERROR, 0); + + internal_sample = (SoundDecoder_SampleInternal*)sound_sample->opaque; + + assert(sound_sample->buffer != NULL); + assert(sound_sample->buffer_size > 0); + assert(internal_sample->buffer != NULL); + assert(internal_sample->buffer_size > 0); + + /* reset EAGAIN. Decoder can flip it back on if it needs to. */ + sound_sample->flags &= ~SOUND_SAMPLEFLAG_EAGAIN; + bytes_read = internal_sample->funcs->read(sound_sample); + return bytes_read; +} + + +size_t SoundDecoder_DecodeAll(SoundDecoder_Sample* sound_sample) +{ + SoundDecoder_SampleInternal* internal_sample = NULL; + void* data_buffer = NULL; + size_t updated_buffer_size = 0; + + BAIL_IF_MACRO(!s_isInitialized, ERR_NOT_INITIALIZED, 0); + BAIL_IF_MACRO(NULL == sound_sample, ERR_NULL_SAMPLE, 0); + + /* My original thought was to call SetBufferSize and resize to + * the size needed to hold the entire decoded file utilizing total_time, + * but it appears SDL_sound simply loops on SoundDecoder_Decode. + * I suppose it is possible to partially decode or seek a file, and then + * call DecodeAll so you won't have the whole thing in which case + * my idea would waste memory. + */ + while( (0 == (sound_sample->flags & SOUND_SAMPLEFLAG_EOF) ) + && (0 == (sound_sample->flags & SOUND_SAMPLEFLAG_ERROR)) + ) + { + size_t bytes_decoded = SoundDecoder_Decode(sound_sample); + void* realloced_ptr = realloc(data_buffer, updated_buffer_size + bytes_decoded); + if(NULL == realloced_ptr) + { + sound_sample->flags |= SOUND_SAMPLEFLAG_ERROR; + SoundDecoder_SetError(ERR_OUT_OF_MEMORY); + if(NULL != data_buffer) + { + free(data_buffer); + } + return bytes_decoded; + } + data_buffer = realloced_ptr; + /* copy the chunk of decoded PCM to the end of our new data buffer */ + memcpy( ((char*)data_buffer) + updated_buffer_size, sound_sample->buffer, bytes_decoded ); + updated_buffer_size += bytes_decoded; + } + + internal_sample = (SoundDecoder_SampleInternal*)sound_sample->opaque; + if(internal_sample->buffer != sound_sample->buffer) + { + free(internal_sample->buffer); + } + free(sound_sample->buffer); + + sound_sample->buffer = data_buffer; + sound_sample->buffer_size = updated_buffer_size; + internal_sample->buffer = sound_sample->buffer; + internal_sample->buffer_size = sound_sample->buffer_size; + + return sound_sample->buffer_size; +} + + +int SoundDecoder_Rewind(SoundDecoder_Sample* sound_sample) +{ + SoundDecoder_SampleInternal* internal_sample; + int ret_val; + + BAIL_IF_MACRO(!s_isInitialized, ERR_NOT_INITIALIZED, 0); + BAIL_IF_MACRO(NULL == sound_sample, ERR_NULL_SAMPLE, 0); + + internal_sample = (SoundDecoder_SampleInternal*)sound_sample->opaque; + ret_val = internal_sample->funcs->rewind(sound_sample); + if(0 == ret_val) + { + sound_sample->flags |= SOUND_SAMPLEFLAG_ERROR; + SoundDecoder_SetError("Rewind failed"); + return 0; + } + /* reset flags */ + sound_sample->flags &= ~SOUND_SAMPLEFLAG_EAGAIN; + sound_sample->flags &= ~SOUND_SAMPLEFLAG_ERROR; + sound_sample->flags &= ~SOUND_SAMPLEFLAG_EOF; + + return 1; +} + + +int SoundDecoder_Seek(SoundDecoder_Sample* sound_sample, size_t ms) +{ + SoundDecoder_SampleInternal* internal_sample; + int ret_val; + + BAIL_IF_MACRO(!s_isInitialized, ERR_NOT_INITIALIZED, 0); + BAIL_IF_MACRO(NULL == sound_sample, ERR_NULL_SAMPLE, 0); + + BAIL_IF_MACRO(!(sound_sample->flags & SOUND_SAMPLEFLAG_CANSEEK), "Sound sample is not seekable", 0); + + internal_sample = (SoundDecoder_SampleInternal*)sound_sample->opaque; + ret_val = internal_sample->funcs->seek(sound_sample, ms); + if(0 == ret_val) + { + sound_sample->flags |= SOUND_SAMPLEFLAG_ERROR; + SoundDecoder_SetError("Seek failed"); + return 0; + } + /* reset flags */ + sound_sample->flags &= ~SOUND_SAMPLEFLAG_EAGAIN; + sound_sample->flags &= ~SOUND_SAMPLEFLAG_ERROR; + sound_sample->flags &= ~SOUND_SAMPLEFLAG_EOF; + + return 1; +} + + +ptrdiff_t SoundDecoder_GetDuration(SoundDecoder_Sample* sound_sample) +{ + SoundDecoder_SampleInternal* internal_sample; + BAIL_IF_MACRO(!s_isInitialized, ERR_NOT_INITIALIZED, -1); + BAIL_IF_MACRO(NULL == sound_sample, ERR_NULL_SAMPLE, 0); + internal_sample = (SoundDecoder_SampleInternal*)sound_sample->opaque; + return internal_sample->total_time; +} + +#endif diff -r b346b6608eab -r 71b465ff0622 Isolated/SoundDecoder.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Isolated/SoundDecoder.h Thu Apr 28 16:22:30 2011 -0700 @@ -0,0 +1,200 @@ + +/* + * This is a wrapper interface that tries to provide a similar + * front-end interface to SDL_sound. + */ + +#ifndef SOUNDDECODER_H +#define SOUNDDECODER_H + +#ifdef __cplusplus +extern "C" { +#endif + + +#include +#include + +#include "al.h" /* OpenAL */ + +/* Compatibility defines for SDL */ +#define AUDIO_U8 0x0008 +#define AUDIO_S8 0x8008 +#define AUDIO_U16LSB 0x0010 +#define AUDIO_S16LSB 0x8010 +#define AUDIO_U16MSB 0x1010 +#define AUDIO_S16MSB 0x9010 +#define AUDIO_U16 AUDIO_U16LSB +#define AUDIO_S16 AUDIO_S16LSB + +#ifdef ANDROID_NDK + #include + #if _BYTE_ORDER == _BIG_ENDIAN + #define __BIG_ENDIAN__ 1 + #elif _BYTE_ORDER == _LITTLE_ENDIAN + #define __LITTLE_ENDIAN__ 1 + #else + #warning "Android falling back to __LITTLE_ENDIAN__" + #define __LITTLE_ENDIAN__ 1 + #endif +#endif + +#if __BIG_ENDIAN__ +#warning "Using __BIG_ENDIAN__" +#define AUDIO_U16SYS AUDIO_U16MSB +#define AUDIO_S16SYS AUDIO_S16MSB +#elif __LITTLE_ENDIAN__ +#define AUDIO_U16SYS AUDIO_U16LSB +#define AUDIO_S16SYS AUDIO_S16LSB +#else +#warning "Using __LITTLE_ENDIAN__ as fallback" +#define AUDIO_U16SYS AUDIO_U16LSB +#define AUDIO_S16SYS AUDIO_S16LSB +#endif + +struct ALmixer_RWops; + +typedef enum +{ + SOUND_SAMPLEFLAG_NONE = 0, + SOUND_SAMPLEFLAG_CANSEEK = 1, + SOUND_SAMPLEFLAG_EOF = 1 << 29, + SOUND_SAMPLEFLAG_ERROR = 1 << 30, + SOUND_SAMPLEFLAG_EAGAIN = 1 << 31 +} SoundDecoder_SampleFlags; + +#define Sound_SampleFlags SoundDecoder_SampleFlags; + +typedef struct SoundDecoder_AudioInfo +{ + //uint16_t format; /**< Equivalent of SDL_AudioSpec.format. */ + ALushort format; /**< Equivalent of SDL_AudioSpec.format. */ + ALubyte channels; + // uint8_t channels; + //uint32_t rate; + ALuint rate; +} SoundDecoder_AudioInfo; + +//#define Sound_AudioInfo SoundDecoder_AudioInfo; +typedef struct SoundDecoder_AudioInfo Sound_AudioInfo; + + + +typedef struct SoundDecoder_DecoderInfo +{ + const char** extensions; + const char* description; + const char* author; + const char* url; +} SoundDecoder_DecoderInfo; + +//#define Sound_DecoderInfo SoundDecoder_DecoderInfo; +typedef struct SoundDecoder_DecoderInfo Sound_DecoderInfo; + + + +typedef struct SoundDecoder_Sample +{ + void* opaque; + const SoundDecoder_DecoderInfo* decoder; + SoundDecoder_AudioInfo desired; + SoundDecoder_AudioInfo actual; + void *buffer; + size_t buffer_size; + SoundDecoder_SampleFlags flags; +} SoundDecoder_Sample; + +//#define Sound_Sample SoundDecoder_Sample; +typedef struct SoundDecoder_Sample Sound_Sample; + + +typedef struct SoundDecoder_Version +{ + int major; + int minor; + int patch; +} SoundDecoder_Version; + +//#define Sound_Version SoundDecoder_Version; +typedef struct SoundDecoder_Version Sound_Version; + + +#define SOUNDDECODER_VER_MAJOR 0 +#define SOUNDDECODER_VER_MINOR 0 +#define SOUNDDECODER_VER_PATCH 1 + +#define SOUNDDECODER_VERSION(x) \ +{ \ + (x)->major = SOUNDDECODER_VER_MAJOR; \ + (x)->minor = SOUNDDECODER_VER_MINOR; \ + (x)->patch = SOUNDDECODER_VER_PATCH; \ +} + +#define SOUND_VERSION SOUNDDECODER_VERSION + +void SoundDecoder_GetLinkedVersion(SoundDecoder_Version *ver); +#define Sound_GetLinkedVersion SoundDecoder_GetLinkedVersion + +int SoundDecoder_Init(void); +#define Sound_Init SoundDecoder_Init + +void SoundDecoder_Quit(void); +#define Sound_Quit SoundDecoder_Quit + + +const SoundDecoder_DecoderInfo** SoundDecoder_AvailableDecoders(void); +#define Sound_AvailableDecoders SoundDecoder_AvailableDecoders + + +const char* SoundDecoder_GetError(void); +#define Sound_GetError SoundDecoder_GetError + + +void SoundDecoder_ClearError(void); +#define Sound_ClearError SoundDecoder_ClearError + + + +SoundDecoder_Sample* SoundDecoder_NewSample( + struct ALmixer_RWops* rw_ops, + const char* ext, + SoundDecoder_AudioInfo* desired, + size_t buffer_size); +#define Sound_NewSample SoundDecoder_NewSample + +SoundDecoder_Sample* SoundDecoder_NewSampleFromFile(const char* file_name, + SoundDecoder_AudioInfo* desired, + size_t bufferSize); +#define Sound_NewSampleFromFile SoundDecoder_NewSampleFromFile + + +void SoundDecoder_FreeSample(SoundDecoder_Sample* sound_sample); +#define Sound_FreeSample SoundDecoder_FreeSample + + +ptrdiff_t SoundDecoder_GetDuration(SoundDecoder_Sample* sound_sample); +#define Sound_GetDuration SoundDecoder_GetDuration + +int SoundDecoder_SetBufferSize(SoundDecoder_Sample* sound_sample, size_t new_buffer_size); +#define Sound_SetBufferSize SoundDecoder_SetBufferSize + +size_t SoundDecoder_Decode(SoundDecoder_Sample* sound_sample); +#define Sound_Decode SoundDecoder_Decode + +size_t SoundDecoder_DecodeAll(SoundDecoder_Sample* sound_sample); +#define Sound_DecodeAll SoundDecoder_DecodeAll + +int SoundDecoder_Rewind(SoundDecoder_Sample* sound_sample); +#define Sound_Rewind SoundDecoder_Rewind + +int SoundDecoder_Seek(SoundDecoder_Sample* sound_sample, size_t ms); +#define Sound_Seek SoundDecoder_Seek + + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +} +#endif + +#endif + diff -r b346b6608eab -r 71b465ff0622 Isolated/SoundDecoder_Internal.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Isolated/SoundDecoder_Internal.h Thu Apr 28 16:22:30 2011 -0700 @@ -0,0 +1,84 @@ +#ifndef SOUNDDECODER_INTERNAL_H +#define SOUNDDECODER_INTERNAL_H + +#include +#include "al.h" /* OpenAL */ +#include "ALmixer_RWops.h" + +#ifdef __cplusplus +extern "C" { +#endif + + +typedef struct SoundDecoder_DecoderFunctions +{ + const SoundDecoder_DecoderInfo info; + int (*init)(void); + void (*quit)(void); + int (*open)(SoundDecoder_Sample *sample, const char *ext); + void (*close)(SoundDecoder_Sample *sample); + size_t (*read)(SoundDecoder_Sample *sample); + int (*rewind)(SoundDecoder_Sample *sample); + int (*seek)(SoundDecoder_Sample *sample, size_t ms); +} SoundDecoder_DecoderFunctions; + +typedef struct SoundDecoder_DecoderFunctions Sound_DecoderFunctions; + + + +typedef struct SoundDecoder_SampleInternal +{ + ALmixer_RWops *rw; + const SoundDecoder_DecoderFunctions *funcs; + void *buffer; + size_t buffer_size; + void *decoder_private; + ptrdiff_t total_time; +} SoundDecoder_SampleInternal; + +typedef struct SoundDecoder_SampleInternal Sound_SampleInternal; + +#define BAIL_MACRO(e, r) { SoundDecoder_SetError(e); return r; } +#define BAIL_IF_MACRO(c, e, r) if (c) { SoundDecoder_SetError(e); return r; } +#define ERR_OUT_OF_MEMORY "Out of memory" +#define ERR_NOT_INITIALIZED "SoundDecoder not initialized" +#define ERR_UNSUPPORTED_FORMAT "Unsupported codec" +#define ERR_NULL_SAMPLE "Sound sample is NULL" +#define ERR_PREVIOUS_SAMPLE_ERROR "Cannot operate on sample due to previous error" +#define ERR_ALREADY_AT_EOF_ERROR "Cannot operate on sample because already at EOF" + +#ifdef ANDROID_NDK + +/* This macro crashes when a format string is used. + * Provide a real function instead. +#include +#define SNDDBG(x) __android_log_print(ANDROID_LOG_INFO, "Corona", x); +*/ +/* Android doesn't seem to understand the 'restrict' qualifier. */ +int SoundDecoder_DebugPrint(const char* format, ...); +#define SNDDBG(x) SoundDecoder_DebugPrint x + +#else + +#if (defined DEBUG_CHATTER) +#define SNDDBG(x) printf x +#else +#define SNDDBG(x) +#endif + +#endif + +void SoundDecoder_SetError(const char* err_str, ...); +#define __Sound_SetError SoundDecoder_SetError + +int SoundDecoder_strcasecmp(const char *x, const char *y); +#define __Sound_strcasecmp SoundDecoder_strcasecmp + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +} +#endif + + +#endif + diff -r b346b6608eab -r 71b465ff0622 Isolated/UNUSED/SimpleSemaphore.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Isolated/UNUSED/SimpleSemaphore.h Thu Apr 28 16:22:30 2011 -0700 @@ -0,0 +1,26 @@ +#ifndef SIMPLE_SEMAPHORE_H +#define SIMPLE_SEMAPHORE_H + +#ifdef __cplusplus +extern "C" { +#endif + + +typedef struct SimpleSemaphore SimpleSemaphore; + +SimpleSemaphore* SimpleSemaphore_CreateSemaphore(int initial_value); + +void SimpleSemaphore_DestroySemaphore(SimpleSemaphore* simple_semaphore); + +int SimpleSemaphore_SemaphoreTryWait(SimpleSemaphore* simple_semaphore); +int SimpleSemaphore_SemaphoreWait(SimpleSemaphore* simple_semaphore); +int SimpleSemaphore_SemaphoreGetValue(SimpleSemaphore* simple_semaphore); +int SimpleSemaphore_SemaphorePost(SimpleSemaphore* simple_semaphore); + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +} +#endif + +#endif + diff -r b346b6608eab -r 71b465ff0622 Isolated/UNUSED/SimpleSemaphorePosix.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Isolated/UNUSED/SimpleSemaphorePosix.c Thu Apr 28 16:22:30 2011 -0700 @@ -0,0 +1,122 @@ +#include "SimpleSemaphore.h" +#include +#include + +#if defined(DEBUG) +#include +#include +#include +#define SEMDBG(x) printf x +#else +#define SEMDBG(x) +#endif + + + +struct SimpleSemaphore +{ + sem_t theSemaphore; +}; + +SimpleSemaphore* SimpleSemaphore_CreateSemaphore(int initial_value) +{ + int ret_val; + SimpleSemaphore* simple_semaphore = (SimpleSemaphore*)malloc(sizeof(SimpleSemaphore)); + if(NULL == simple_semaphore) + { + SEMDBG(("Out of memory.\n")); + return NULL; + } + /* Drat: sem_init isn't available on OS X */ +#ifdef __APPLE__ + #warning "sem_init (unnamed semaphores) do not work on OS X. This code is broken." +#endif + ret_val = sem_init(&simple_semaphore->theSemaphore, 0, initial_value); + if(0 != ret_val) + { + /* failed */ + SEMDBG(("sem_init failed with: %d\n", ret_val)); + free(simple_semaphore); + return 0; + } + + return simple_semaphore; +} + +void SimpleSemaphore_DestroySemaphore(SimpleSemaphore* simple_semaphore) +{ + if(NULL == simple_semaphore) + { + return; + } + sem_destroy(&simple_semaphore->theSemaphore); + free(simple_semaphore); +} + +int SimpleSemaphore_SemaphoreTryWait(SimpleSemaphore* simple_semaphore) +{ + int ret_val; + if(NULL == simple_semaphore) + { + SEMDBG(("SimpleSemaphore_SemTryWait was passed a NULL semaphore\n")); + return 0; + } + ret_val = sem_trywait(&simple_semaphore->theSemaphore); + if(0 == ret_val) + { + return 1; + } + else + { + return 0; + } +} + +int SimpleSemaphore_SemaphoreWait(SimpleSemaphore* simple_semaphore) +{ + int ret_val; + if(NULL == simple_semaphore) + { + SEMDBG(("SimpleSemaphore_SemaphoreWait was passed a NULL semaphore\n")); + return 0; + } + ret_val = sem_wait(&simple_semaphore->theSemaphore); + if(0 == ret_val) + { + return 1; + } + else + { + return 0; + } +} + +int SimpleSemaphore_SemaphoreGetValue(SimpleSemaphore* simple_semaphore) +{ + int ret_val = 0; + if(NULL == simple_semaphore) + { + SEMDBG(("SimpleSemaphore_SemaphoreGetValue was passed a NULL semaphore\n")); + return 0; + } + sem_getvalue(&simple_semaphore->theSemaphore, &ret_val); + return ret_val; +} + +int SimpleSemaphore_SemaphorePost(SimpleSemaphore* simple_semaphore) +{ + int ret_val; + if(NULL == simple_semaphore) + { + SEMDBG(("SimpleSemaphore_SemaphorePost was passed a NULL semaphore\n")); + return 0; + } + + ret_val = sem_post(&simple_semaphore->theSemaphore); + if(-1 == ret_val) + { + SEMDBG(("sem_post failed with: %s\n", strerror(errno))); + } + return ret_val; +} + diff -r b346b6608eab -r 71b465ff0622 Isolated/coreaudio.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Isolated/coreaudio.c Thu Apr 28 16:22:30 2011 -0700 @@ -0,0 +1,784 @@ +#ifdef ALMIXER_COMPILE_WITHOUT_SDL + +/* + * SDL_sound Core Audio backend + * Copyright (C) 2010 Eric Wing + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#if HAVE_CONFIG_H +# include +#endif + +#ifdef __APPLE__ + +#include /* NULL */ +#include /* printf */ +#include /* htonl */ +#include + +#include "SoundDecoder.h" + +#include "SoundDecoder_Internal.h" + +typedef struct CoreAudioFileContainer +{ + AudioFileID* audioFileID; + ExtAudioFileRef extAudioFileRef; + AudioStreamBasicDescription* outputFormat; +} CoreAudioFileContainer; + +static int CoreAudio_init(void); +static void CoreAudio_quit(void); +static int CoreAudio_open(Sound_Sample *sample, const char *ext); +static void CoreAudio_close(Sound_Sample *sample); +static size_t CoreAudio_read(Sound_Sample *sample); +static int CoreAudio_rewind(Sound_Sample *sample); +static int CoreAudio_seek(Sound_Sample *sample, size_t ms); + +static const char *extensions_coreaudio[] = +{ + "aif", + "aiff", + "aifc", + "wav", + "wave", + "mp3", + "mp4", + "m4a", + "aac", + "adts", + "caf", + "Sd2f", + "Sd2", + "au", + "snd", + "next", + "mp2", + "mp1", + "ac3", + "3gpp", + "3gp", + "3gp2", + "3g2", + "amrf", + "amr", + "ima4", + "ima", + NULL +}; +const Sound_DecoderFunctions __Sound_DecoderFunctions_CoreAudio = +{ + { + extensions_coreaudio, + "Decode audio through Core Audio through", + "Eric Wing ", + "http://playcontrol.net" + }, + + CoreAudio_init, /* init() method */ + CoreAudio_quit, /* quit() method */ + CoreAudio_open, /* open() method */ + CoreAudio_close, /* close() method */ + CoreAudio_read, /* read() method */ + CoreAudio_rewind, /* rewind() method */ + CoreAudio_seek /* seek() method */ +}; + + +static int CoreAudio_init(void) +{ + return(1); /* always succeeds. */ +} /* CoreAudio_init */ + + +static void CoreAudio_quit(void) +{ + /* it's a no-op. */ +} /* CoreAudio_quit */ + +/* + http://developer.apple.com/library/ios/#documentation/MusicAudio/Reference/AudioFileConvertRef/Reference/reference.html + kAudioFileAIFFType = 'AIFF', + kAudioFileAIFCType = 'AIFC', + kAudioFileWAVEType = 'WAVE', + kAudioFileSoundDesigner2Type = 'Sd2f', + kAudioFileNextType = 'NeXT', + kAudioFileMP3Type = 'MPG3', + kAudioFileMP2Type = 'MPG2', + kAudioFileMP1Type = 'MPG1', + kAudioFileAC3Type = 'ac-3', + kAudioFileAAC_ADTSType = 'adts', + kAudioFileMPEG4Type = 'mp4f', + kAudioFileM4AType = 'm4af', + kAudioFileCAFType = 'caff', + kAudioFile3GPType = '3gpp', + kAudioFile3GP2Type = '3gp2', + kAudioFileAMRType = 'amrf' +*/ +static AudioFileTypeID CoreAudio_GetAudioTypeForExtension(const char* file_extension) +{ + if( (__Sound_strcasecmp(file_extension, "aif") == 0) + || (__Sound_strcasecmp(file_extension, "aiff") == 0) + || (__Sound_strcasecmp(file_extension, "aifc") == 0) + ) + { + return kAudioFileAIFCType; + } + else if( (__Sound_strcasecmp(file_extension, "wav") == 0) + || (__Sound_strcasecmp(file_extension, "wave") == 0) + ) + { + return kAudioFileWAVEType; + } + else if( (__Sound_strcasecmp(file_extension, "mp3") == 0) + ) + { + return kAudioFileMP3Type; + } + else if( (__Sound_strcasecmp(file_extension, "mp4") == 0) + ) + { + return kAudioFileMPEG4Type; + } + else if( (__Sound_strcasecmp(file_extension, "m4a") == 0) + ) + { + return kAudioFileM4AType; + } + else if( (__Sound_strcasecmp(file_extension, "aac") == 0) + ) + { + return kAudioFileAAC_ADTSType; + } + else if( (__Sound_strcasecmp(file_extension, "adts") == 0) + ) + { + return kAudioFileAAC_ADTSType; + } + else if( (__Sound_strcasecmp(file_extension, "caf") == 0) + || (__Sound_strcasecmp(file_extension, "caff") == 0) + ) + { + return kAudioFileCAFType; + } + else if( (__Sound_strcasecmp(file_extension, "Sd2f") == 0) + || (__Sound_strcasecmp(file_extension, "sd2") == 0) + ) + { + return kAudioFileSoundDesigner2Type; + } + else if( (__Sound_strcasecmp(file_extension, "au") == 0) + || (__Sound_strcasecmp(file_extension, "snd") == 0) + || (__Sound_strcasecmp(file_extension, "next") == 0) + ) + { + return kAudioFileNextType; + } + else if( (__Sound_strcasecmp(file_extension, "mp2") == 0) + ) + { + return kAudioFileMP2Type; + } + else if( (__Sound_strcasecmp(file_extension, "mp1") == 0) + ) + { + return kAudioFileMP1Type; + } + else if( (__Sound_strcasecmp(file_extension, "ac3") == 0) + ) + { + return kAudioFileAC3Type; + } + else if( (__Sound_strcasecmp(file_extension, "3gpp") == 0) + || (__Sound_strcasecmp(file_extension, "3gp") == 0) + ) + { + return kAudioFile3GPType; + } + else if( (__Sound_strcasecmp(file_extension, "3gp2") == 0) + || (__Sound_strcasecmp(file_extension, "3g2") == 0) + ) + { + return kAudioFile3GP2Type; + } + else if( (__Sound_strcasecmp(file_extension, "amrf") == 0) + || (__Sound_strcasecmp(file_extension, "amr") == 0) + ) + { + return kAudioFileAMRType; + } + else if( (__Sound_strcasecmp(file_extension, "ima4") == 0) + || (__Sound_strcasecmp(file_extension, "ima") == 0) + ) + { + /* not sure about this one */ + return kAudioFileCAFType; + } + else + { + return 0; + } + +} + +static const char* CoreAudio_FourCCToString(int32_t error_code) +{ + static char return_string[16]; + uint32_t big_endian_code = htonl(error_code); + char* big_endian_str = (char*)&big_endian_code; + // see if it appears to be a 4-char-code + if(isprint(big_endian_str[0]) + && isprint(big_endian_str[1]) + && isprint(big_endian_str[2]) + && isprint (big_endian_str[3])) + { + return_string[0] = '\''; + return_string[1] = big_endian_str[0]; + return_string[2] = big_endian_str[1]; + return_string[3] = big_endian_str[2]; + return_string[4] = big_endian_str[3]; + return_string[5] = '\''; + return_string[6] = '\0'; + } + else if(error_code > -200000 && error_code < 200000) + { + // no, format it as an integer + snprintf(return_string, 16, "%d", error_code); + } + else + { + // no, format it as an integer but in hex + snprintf(return_string, 16, "0x%x", error_code); + } + return return_string; +} + + + +SInt64 CoreAudio_SizeCallback(void* inClientData) +{ + ALmixer_RWops* rw_ops = (ALmixer_RWops*)inClientData; + SInt64 current_position = ALmixer_RWtell(rw_ops); + SInt64 end_position = ALmixer_RWseek(rw_ops, 0, SEEK_END); + ALmixer_RWseek(rw_ops, current_position, SEEK_SET); +// fprintf(stderr, "CoreAudio_SizeCallback:%d\n", end_position); + + return end_position; +} + +OSStatus CoreAudio_ReadCallback( + void* inClientData, + SInt64 inPosition, + UInt32 requestCount, + void* data_buffer, + UInt32* actualCount +) +{ + ALmixer_RWops* rw_ops = (ALmixer_RWops*)inClientData; + ALmixer_RWseek(rw_ops, inPosition, SEEK_SET); + size_t bytes_actually_read = ALmixer_RWread(rw_ops, data_buffer, 1, requestCount); + // Not sure how to test for a read error with ALmixer_RWops +// fprintf(stderr, "CoreAudio_ReadCallback:%d, %d\n", requestCount, bytes_actually_read); + + *actualCount = bytes_actually_read; + return noErr; +} + + +static int CoreAudio_open(Sound_Sample *sample, const char *ext) +{ + CoreAudioFileContainer* core_audio_file_container; + AudioFileID* audio_file_id; + OSStatus error_result; + Sound_SampleInternal *internal = (Sound_SampleInternal *) sample->opaque; + AudioStreamBasicDescription actual_format; + AudioStreamBasicDescription output_format; + Float64 estimated_duration; + UInt32 format_size; + + + core_audio_file_container = (CoreAudioFileContainer*)malloc(sizeof(CoreAudioFileContainer)); + BAIL_IF_MACRO(core_audio_file_container == NULL, ERR_OUT_OF_MEMORY, 0); + + + audio_file_id = (AudioFileID*)malloc(sizeof(AudioFileID)); + BAIL_IF_MACRO(audio_file_id == NULL, ERR_OUT_OF_MEMORY, 0); + + error_result = AudioFileOpenWithCallbacks( + internal->rw, + CoreAudio_ReadCallback, + NULL, + CoreAudio_SizeCallback, + NULL, + CoreAudio_GetAudioTypeForExtension(ext), + audio_file_id + ); + if (error_result != noErr) + { + AudioFileClose(*audio_file_id); + free(audio_file_id); + free(core_audio_file_container); + SNDDBG(("Core Audio: can't grok data. reason: [%s].\n", CoreAudio_FourCCToString(error_result))); + BAIL_MACRO("Core Audio: Not valid audio data.", 0); + } /* if */ + + format_size = sizeof(actual_format); + error_result = AudioFileGetProperty( + *audio_file_id, + kAudioFilePropertyDataFormat, + &format_size, + &actual_format + ); + if (error_result != noErr) + { + AudioFileClose(*audio_file_id); + free(audio_file_id); + free(core_audio_file_container); + SNDDBG(("Core Audio: AudioFileGetProperty failed. reason: [%s]", CoreAudio_FourCCToString(error_result))); + BAIL_MACRO("Core Audio: Not valid audio data.", 0); + } /* if */ + + format_size = sizeof(estimated_duration); + error_result = AudioFileGetProperty( + *audio_file_id, + kAudioFilePropertyEstimatedDuration, + &format_size, + &estimated_duration + ); + if (error_result != noErr) + { + AudioFileClose(*audio_file_id); + free(audio_file_id); + free(core_audio_file_container); + SNDDBG(("Core Audio: AudioFileGetProperty failed. reason: [%s].\n", CoreAudio_FourCCToString(error_result))); + BAIL_MACRO("Core Audio: Not valid audio data.", 0); + } /* if */ + + + core_audio_file_container->audioFileID = audio_file_id; + + internal->decoder_private = core_audio_file_container; + + sample->flags = SOUND_SAMPLEFLAG_CANSEEK; + sample->actual.rate = (UInt32) actual_format.mSampleRate; + sample->actual.channels = (UInt8)actual_format.mChannelsPerFrame; + internal->total_time = (SInt32)(estimated_duration * 1000.0 + 0.5); + +#if 0 + /* FIXME: Both Core Audio and SDL 1.3 support float and 32-bit formats */ + if(actual_format.mFormatFlags & kAudioFormatFlagIsBigEndian) + { + if(16 == actual_format.mBitsPerChannel) + { + if(kAudioFormatFlagIsSignedInteger & actual_format.mFormatFlags) + { + sample->actual.format = AUDIO_S16MSB; + } + else + { + sample->actual.format = AUDIO_U16MSB; + } + } + else if(8 == actual_format.mBitsPerChannel) + { + if(kAudioFormatFlagIsSignedInteger & actual_format.mFormatFlags) + { + sample->actual.format = AUDIO_S8; + } + else + { + sample->actual.format = AUDIO_U8; + } + } + else // might be 0 for undefined? + { + // This case seems to come up a lot for me. Maybe for file types like .m4a? + sample->actual.format = AUDIO_S16SYS; + SNDDBG(("Core Audio: Unsupported actual_format.mBitsPerChannel: [%d].\n", actual_format.mBitsPerChannel)); + + } + } + else // little endian + { + if(16 == actual_format.mBitsPerChannel) + { + if(kAudioFormatFlagIsSignedInteger & actual_format.mFormatFlags) + { + sample->actual.format = AUDIO_S16LSB; + } + else + { + sample->actual.format = AUDIO_U16LSB; + } + } + else if(8 == actual_format.mBitsPerChannel) + { + if(kAudioFormatFlagIsSignedInteger & actual_format.mFormatFlags) + { + sample->actual.format = AUDIO_S8; + } + else + { + sample->actual.format = AUDIO_U8; + } + } + else // might be 0 for undefined? + { + sample->actual.format = AUDIO_S16SYS; + + SNDDBG(("Core Audio: Unsupported actual_format.mBitsPerChannel: [%d].\n", actual_format.mBitsPerChannel)); + } + + } +#else + + + + /* + * I want to use Core Audio to do conversion and decoding for performance reasons. + * This is particularly important on mobile devices like iOS. + * Taking from the Ogg Vorbis decode, I pretend the "actual" format is the same + * as the desired format. + */ + sample->actual.format = (sample->desired.format == 0) ? + AUDIO_S16SYS : sample->desired.format; +#endif + + + SNDDBG(("CoreAudio: channels == (%d).\n", sample->actual.channels)); + SNDDBG(("CoreAudio: sampling rate == (%d).\n",sample->actual.rate)); + SNDDBG(("CoreAudio: total seconds of sample == (%d).\n", internal->total_time)); + SNDDBG(("CoreAudio: sample->actual.format == (%d).\n", sample->actual.format)); + + + + error_result = ExtAudioFileWrapAudioFileID(*audio_file_id, + false, // set to false for read-only + &core_audio_file_container->extAudioFileRef + ); + if(error_result != noErr) + { + AudioFileClose(*audio_file_id); + free(audio_file_id); + free(core_audio_file_container); + SNDDBG(("Core Audio: can't wrap data. reason: [%s].\n", CoreAudio_FourCCToString(error_result))); + BAIL_MACRO("Core Audio: Failed to wrap data.", 0); + } /* if */ + + + /* The output format must be linear PCM because that's the only type OpenAL knows how to deal with. + * Set the client format to 16 bit signed integer (native-endian) data because that is the most + * optimal format on iPhone/iPod Touch hardware. + * Maintain the channel count and sample rate of the original source format. + */ + output_format.mSampleRate = actual_format.mSampleRate; // preserve the original sample rate + output_format.mChannelsPerFrame = actual_format.mChannelsPerFrame; // preserve the number of channels + output_format.mFormatID = kAudioFormatLinearPCM; // We want linear PCM data + output_format.mFramesPerPacket = 1; // We know for linear PCM, the definition is 1 frame per packet + + if(sample->desired.format == 0) + { + // do AUDIO_S16SYS + output_format.mFormatFlags = kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked; // I seem to read failures problems without kAudioFormatFlagIsPacked. From a mailing list post, this seems to be a Core Audio bug. + output_format.mBitsPerChannel = 16; // We know we want 16-bit + } + else + { + output_format.mFormatFlags = 0; // clear flags + output_format.mFormatFlags |= kAudioFormatFlagIsPacked; // I seem to read failures problems without kAudioFormatFlagIsPacked. From a mailing list post, this seems to be a Core Audio bug. + // Mask against bitsize + if(0xFF & sample->desired.format) + { + output_format.mBitsPerChannel = 16; /* 16-bit */ + } + else + { + output_format.mBitsPerChannel = 8; /* 8-bit */ + } + + // Mask for signed/unsigned + if((1<<15) & sample->desired.format) + { + output_format.mFormatFlags = output_format.mFormatFlags | kAudioFormatFlagIsSignedInteger; + + } + else + { + // no flag set for unsigned + } + // Mask for big/little endian + if((1<<12) & sample->desired.format) + { + output_format.mFormatFlags = output_format.mFormatFlags | kAudioFormatFlagIsBigEndian; + } + else + { + // no flag set for little endian + } + } + + output_format.mBytesPerPacket = output_format.mBitsPerChannel/8 * output_format.mChannelsPerFrame; // e.g. 16-bits/8 * channels => so 2-bytes per channel per frame + output_format.mBytesPerFrame = output_format.mBitsPerChannel/8 * output_format.mChannelsPerFrame; // For PCM, since 1 frame is 1 packet, it is the same as mBytesPerPacket + + +/* + output_format.mSampleRate = actual_format.mSampleRate; // preserve the original sample rate + output_format.mChannelsPerFrame = actual_format.mChannelsPerFrame; // preserve the number of channels + output_format.mFormatID = kAudioFormatLinearPCM; // We want linear PCM data +// output_format.mFormatFlags = kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked | kAudioFormatFlagIsSignedInteger; + output_format.mFormatFlags = kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsSignedInteger; + output_format.mFramesPerPacket = 1; // We know for linear PCM, the definition is 1 frame per packet + output_format.mBitsPerChannel = 16; // We know we want 16-bit + output_format.mBytesPerPacket = 2 * output_format.mChannelsPerFrame; // We know we are using 16-bit, so 2-bytes per channel per frame + output_format.mBytesPerFrame = 2 * output_format.mChannelsPerFrame; // For PCM, since 1 frame is 1 packet, it is the same as mBytesPerPacket +*/ + SNDDBG(("output_format: mSampleRate: %lf\n", output_format.mSampleRate)); + SNDDBG(("output_format: mChannelsPerFrame: %d\n", output_format.mChannelsPerFrame)); + SNDDBG(("output_format: mFormatID: %d\n", output_format.mFormatID)); + SNDDBG(("output_format: mFormatFlags: %d\n", output_format.mFormatFlags)); + SNDDBG(("output_format: mFramesPerPacket: %d\n", output_format.mFramesPerPacket)); + SNDDBG(("output_format: mBitsPerChannel: %d\n", output_format.mBitsPerChannel)); + SNDDBG(("output_format: mBytesPerPacket: %d\n", output_format.mBytesPerPacket)); + SNDDBG(("output_format: mBytesPerFrame: %d\n", output_format.mBytesPerFrame)); + + + /* Set the desired client (output) data format */ + error_result = ExtAudioFileSetProperty(core_audio_file_container->extAudioFileRef, kExtAudioFileProperty_ClientDataFormat, sizeof(output_format), &output_format); + if(noErr != error_result) + { + ExtAudioFileDispose(core_audio_file_container->extAudioFileRef); + AudioFileClose(*audio_file_id); + free(audio_file_id); + free(core_audio_file_container); + SNDDBG(("Core Audio: ExtAudioFileSetProperty(kExtAudioFileProperty_ClientDataFormat) failed, reason: [%s].\n", CoreAudio_FourCCToString(error_result))); + BAIL_MACRO("Core Audio: Not valid audio data.", 0); + } + + + core_audio_file_container->outputFormat = (AudioStreamBasicDescription*)malloc(sizeof(AudioStreamBasicDescription)); + BAIL_IF_MACRO(core_audio_file_container->outputFormat == NULL, ERR_OUT_OF_MEMORY, 0); + + + + /* Copy the output format to the audio_description that was passed in so the + * info will be returned to the user. + */ + memcpy(core_audio_file_container->outputFormat, &output_format, sizeof(AudioStreamBasicDescription)); + + + + return(1); +} /* CoreAudio_open */ + + +static void CoreAudio_close(Sound_Sample *sample) +{ + Sound_SampleInternal *internal = (Sound_SampleInternal *) sample->opaque; + CoreAudioFileContainer* core_audio_file_container = (CoreAudioFileContainer *) internal->decoder_private; + + free(core_audio_file_container->outputFormat); + ExtAudioFileDispose(core_audio_file_container->extAudioFileRef); + AudioFileClose(*core_audio_file_container->audioFileID); + free(core_audio_file_container->audioFileID); + free(core_audio_file_container); + +} /* CoreAudio_close */ + + +static size_t CoreAudio_read(Sound_Sample *sample) +{ + OSStatus error_result = noErr; + /* Documentation/example shows SInt64, but is problematic for big endian + * on 32-bit cast for ExtAudioFileRead() because it takes the upper + * bits which turn to 0. + */ + UInt32 buffer_size_in_frames = 0; + UInt32 buffer_size_in_frames_remaining = 0; + UInt32 total_frames_read = 0; + UInt32 data_buffer_size = 0; + UInt32 bytes_remaining = 0; + size_t total_bytes_read = 0; + Sound_SampleInternal *internal = (Sound_SampleInternal *) sample->opaque; + CoreAudioFileContainer* core_audio_file_container = (CoreAudioFileContainer *) internal->decoder_private; + UInt32 max_buffer_size = internal->buffer_size; + +// printf("internal->buffer_size=%d, internal->buffer=0x%x, sample->buffer_size=%d\n", internal->buffer_size, internal->buffer, sample->buffer_size); +// printf("internal->max_buffer_size=%d\n", max_buffer_size); + + /* Compute how many frames will fit into our max buffer size */ + /* Warning: If this is not evenly divisible, the buffer will not be completely filled which violates the SDL_sound assumption. */ + buffer_size_in_frames = max_buffer_size / core_audio_file_container->outputFormat->mBytesPerFrame; +// printf("buffer_size_in_frames=%ld, internal->buffer_size=%d, internal->buffer=0x%x outputFormat->mBytesPerFrame=%d, sample->buffer_size=%d\n", buffer_size_in_frames, internal->buffer_size, internal->buffer, core_audio_file_container->outputFormat->mBytesPerFrame, sample->buffer_size); + + +// void* temp_buffer = malloc(max_buffer_size); + + AudioBufferList audio_buffer_list; + audio_buffer_list.mNumberBuffers = 1; + audio_buffer_list.mBuffers[0].mDataByteSize = max_buffer_size; + audio_buffer_list.mBuffers[0].mNumberChannels = core_audio_file_container->outputFormat->mChannelsPerFrame; + audio_buffer_list.mBuffers[0].mData = internal->buffer; + + + bytes_remaining = max_buffer_size; + buffer_size_in_frames_remaining = buffer_size_in_frames; + + // oops. Due to the kAudioFormatFlagIsPacked bug, + // I was misled to believe that Core Audio + // was not always filling my entire requested buffer. + // So this while-loop might be unnecessary. + // However, I have not exhaustively tested all formats, + // so maybe it is possible this loop is useful. + // It might also handle the not-evenly disvisible case above. + while(buffer_size_in_frames_remaining > 0 && !(sample->flags & SOUND_SAMPLEFLAG_EOF)) + { + + data_buffer_size = (UInt32)(buffer_size_in_frames * core_audio_file_container->outputFormat->mBytesPerFrame); +// printf("data_buffer_size=%d\n", data_buffer_size); + + buffer_size_in_frames = buffer_size_in_frames_remaining; + +// printf("reading buffer_size_in_frames=%"PRId64"\n", buffer_size_in_frames); + + + audio_buffer_list.mBuffers[0].mDataByteSize = bytes_remaining; + audio_buffer_list.mBuffers[0].mData = &(((UInt8*)internal->buffer)[total_bytes_read]); + + + /* Read the data into an AudioBufferList */ + error_result = ExtAudioFileRead(core_audio_file_container->extAudioFileRef, &buffer_size_in_frames, &audio_buffer_list); + if(error_result == noErr) + { + + + /* Success */ + + total_frames_read += buffer_size_in_frames; + buffer_size_in_frames_remaining = buffer_size_in_frames_remaining - buffer_size_in_frames; + +// printf("read buffer_size_in_frames=%"PRId64", buffer_size_in_frames_remaining=%"PRId64"\n", buffer_size_in_frames, buffer_size_in_frames_remaining); + + /* ExtAudioFileRead returns the number of frames actually read. Need to convert back to bytes. */ + data_buffer_size = (UInt32)(buffer_size_in_frames * core_audio_file_container->outputFormat->mBytesPerFrame); +// printf("data_buffer_size=%d\n", data_buffer_size); + + total_bytes_read += data_buffer_size; + bytes_remaining = bytes_remaining - data_buffer_size; + + /* Note: 0 == buffer_size_in_frames is a legitimate value meaning we are EOF. */ + if(0 == buffer_size_in_frames) + { + sample->flags |= SOUND_SAMPLEFLAG_EOF; + } + + } + else + { + SNDDBG(("Core Audio: ExtAudioFileReadfailed, reason: [%s].\n", CoreAudio_FourCCToString(error_result))); + + sample->flags |= SOUND_SAMPLEFLAG_ERROR; + break; + + } + } + + if( (!(sample->flags & SOUND_SAMPLEFLAG_EOF)) && (total_bytes_read < max_buffer_size)) + { + SNDDBG(("Core Audio: ExtAudioFileReadfailed SOUND_SAMPLEFLAG_EAGAIN, reason: [total_bytes_read < max_buffer_size], %d, %d.\n", total_bytes_read , max_buffer_size)); + + /* Don't know what to do here. */ + sample->flags |= SOUND_SAMPLEFLAG_EAGAIN; + } + return total_bytes_read; +} /* CoreAudio_read */ + + +static int CoreAudio_rewind(Sound_Sample *sample) +{ + OSStatus error_result = noErr; + Sound_SampleInternal *internal = (Sound_SampleInternal *) sample->opaque; + CoreAudioFileContainer* core_audio_file_container = (CoreAudioFileContainer *) internal->decoder_private; + + error_result = ExtAudioFileSeek(core_audio_file_container->extAudioFileRef, 0); + if(error_result != noErr) + { + sample->flags |= SOUND_SAMPLEFLAG_ERROR; + } + return(1); +} /* CoreAudio_rewind */ + +/* Note: I found this tech note: + http://developer.apple.com/library/mac/#qa/qa2009/qa1678.html + I don't know if this applies to us. So far, I haven't noticed the problem, + so I haven't applied any of the techniques. + */ +static int CoreAudio_seek(Sound_Sample *sample, size_t ms) +{ + OSStatus error_result = noErr; + Sound_SampleInternal *internal = (Sound_SampleInternal *) sample->opaque; + CoreAudioFileContainer* core_audio_file_container = (CoreAudioFileContainer *) internal->decoder_private; + SInt64 frame_offset = 0; + + /* I'm confused. The Apple documentation says this: + "Seek position is specified in the sample rate and frame count of the file’s audio data format + — not your application’s audio data format." + My interpretation is that I want to get the "actual format of the file and compute the frame offset. + But when I try that, seeking goes to the wrong place. + When I use outputFormat, things seem to work correctly. + I must be misinterpreting the documentation or doing something wrong. + */ +#if 0 /* not working */ + AudioStreamBasicDescription actual_format; + UInt32 format_size; + format_size = sizeof(AudioStreamBasicDescription); + error_result = AudioFileGetProperty( + *core_audio_file_container->audioFileID, + kAudioFilePropertyDataFormat, + &format_size, + &actual_format + ); + if(error_result != noErr) + { + sample->flags |= SOUND_SAMPLEFLAG_ERROR; + BAIL_MACRO("Core Audio: Could not GetProperty for kAudioFilePropertyDataFormat.", 0); + } /* if */ + + // packetIndex = (pos * sampleRate) / framesPerPacket + frame_offset = (SInt64)((ms/1000.0 * actual_format.mSampleRate) / actual_format.mFramesPerPacket); + // computed against actual format and not the client format + + // packetIndex = (pos * sampleRate) / framesPerPacket + // frame_offset = (SInt64)((ms/1000.0 * actual_format.mSampleRate) / actual_format.mFramesPerPacket); +#else /* seems to work, but I'm confused */ + // packetIndex = (pos * sampleRate) / framesPerPacket + frame_offset = (SInt64)((ms/1000.0 * core_audio_file_container->outputFormat->mSampleRate) / core_audio_file_container->outputFormat->mFramesPerPacket); +#endif + + // computed against actual format and not the client format + error_result = ExtAudioFileSeek(core_audio_file_container->extAudioFileRef, frame_offset); + if(error_result != noErr) + { + sample->flags |= SOUND_SAMPLEFLAG_ERROR; + } + return(1); +} /* CoreAudio_seek */ + +#endif /* __APPLE__ */ + + +#endif /* ALMIXER_COMPILE_WITHOUT_SDL */ + diff -r b346b6608eab -r 71b465ff0622 Isolated/luaal/luaal.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Isolated/luaal/luaal.c Thu Apr 28 16:22:30 2011 -0700 @@ -0,0 +1,671 @@ +/* + LuaOpenAL + Copyright (C) 2010 Eric Wing + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + +*/ +#include "al.h" +/* #include "alc.h" */ + +#include "lua.h" +#include "lauxlib.h" + +#define LUAAL_VERSION "1.1" + +typedef struct luaopenal_Enum +{ + const char* stringName; + ALenum alValue; +} luaopenal_Enum; + + +void luaopenal_initenum(lua_State* lua_state, const luaopenal_Enum* al_enums) +{ + for( ; al_enums->stringName; al_enums++) + { + lua_pushstring(lua_state, al_enums->stringName); + lua_pushinteger(lua_state, al_enums->alValue); + lua_settable(lua_state, -3); + } +} + + +static const luaopenal_Enum s_luaALEnum[] = { + { "INVALID", AL_INVALID }, + { "NONE", AL_NONE }, + { "FALSE", AL_FALSE }, + { "TRUE", AL_TRUE }, + { "SOURCE_RELATIVE", AL_SOURCE_RELATIVE }, + { "CONE_INNER_ANGLE", AL_CONE_INNER_ANGLE }, + { "CONE_OUTER_ANGLE", AL_CONE_OUTER_ANGLE }, + { "PITCH", AL_PITCH }, + { "POSITION", AL_POSITION }, + { "DIRECTION", AL_DIRECTION }, + { "VELOCITY", AL_VELOCITY }, + { "LOOPING", AL_LOOPING }, + { "BUFFER", AL_BUFFER }, + { "GAIN", AL_GAIN }, + { "MIN_GAIN", AL_MIN_GAIN }, + { "MAX_GAIN", AL_MAX_GAIN }, + { "ORIENTATION", AL_ORIENTATION }, +/* { "CHANNEL_MASK", AL_CHANNEL_MASK }, */ + { "SOURCE_STATE", AL_SOURCE_STATE }, + { "INITIAL", AL_INITIAL }, + { "PLAYING", AL_PLAYING }, + { "PAUSED", AL_PAUSED }, + { "STOPPED", AL_STOPPED }, + { "BUFFERS_QUEUED", AL_BUFFERS_QUEUED }, + { "BUFFERS_PROCESSED", AL_BUFFERS_PROCESSED }, + { "SEC_OFFSET", AL_SEC_OFFSET }, + { "SAMPLE_OFFSET", AL_SAMPLE_OFFSET }, + { "BYTE_OFFSET", AL_BYTE_OFFSET }, + { "SOURCE_TYPE", AL_SOURCE_TYPE }, + { "STATIC", AL_STATIC }, + { "STREAMING", AL_STREAMING }, + { "UNDETERMINED", AL_UNDETERMINED }, + { "FORMAT_MONO8", AL_FORMAT_MONO8 }, + { "FORMAT_MONO16", AL_FORMAT_MONO16 }, + { "FORMAT_STEREO8", AL_FORMAT_STEREO8 }, + { "FORMAT_STEREO16", AL_FORMAT_STEREO16 }, + { "REFERENCE_DISTANCE", AL_REFERENCE_DISTANCE }, + { "ROLLOFF_FACTOR", AL_ROLLOFF_FACTOR }, + { "CONE_OUTER_GAIN", AL_CONE_OUTER_GAIN }, + { "MAX_DISTANCE", AL_MAX_DISTANCE }, + { "FREQUENCY", AL_FREQUENCY }, + { "BITS", AL_BITS }, + { "CHANNELS", AL_CHANNELS }, + { "SIZE", AL_SIZE }, +/* + { "UNUSED", AL_UNUSED }, + { "PENDING", AL_PENDING }, + { "PROCESSED", AL_PROCESSED }, +*/ + { "NO_ERROR", AL_NO_ERROR }, + { "INVALID_NAME", AL_INVALID_NAME }, + { "ILLEGAL_ENUM", AL_ILLEGAL_ENUM }, + { "INVALID_VALUE", AL_INVALID_VALUE }, + { "ILLEGAL_COMMAND", AL_ILLEGAL_COMMAND }, + { "INVALID_OPERATION", AL_INVALID_OPERATION }, + { "OUT_OF_MEMORY", AL_OUT_OF_MEMORY }, + { "VENDOR", AL_VENDOR }, + { "VERSION", AL_VERSION }, + { "RENDERER", AL_RENDERER }, + { "EXTENSIONS", AL_EXTENSIONS }, + { "DOPPLER_FACTOR", AL_DOPPLER_FACTOR }, + { "DOPPLER_VELOCITY", AL_DOPPLER_VELOCITY }, + { "SPEED_OF_SOUND", AL_SPEED_OF_SOUND }, + { "DISTANCE_MODEL", AL_DISTANCE_MODEL }, + { "INVERSE_DISTANCE", AL_INVERSE_DISTANCE }, + { "INVERSE_DISTANCE_CLAMPED", AL_INVERSE_DISTANCE_CLAMPED }, + { "LINEAR_DISTANCE", AL_LINEAR_DISTANCE }, + { "LINEAR_DISTANCE_CLAMPED", AL_LINEAR_DISTANCE_CLAMPED }, + { "EXPONENT_DISTANCE", AL_EXPONENT_DISTANCE }, + { "EXPONENT_DISTANCE_CLAMPED", AL_EXPONENT_DISTANCE_CLAMPED }, + { 0, 0 } +}; + + +#define LUAOPENAL_BOOL_TYPE 1 +#define LUAOPENAL_INT_TYPE 2 +#define LUAOPENAL_FLOAT_TYPE 3 + + +#define LUAOPENAL_INT_3_TYPE 4 +#define LUAOPENAL_FLOAT_3_TYPE 5 +#define LUAOPENAL_FLOAT_6_TYPE 6 +#define LUAOPENAL_STRING_TYPE 7 + + +static int lua_getTypeForEnum(ALenum enum_parameter) +{ + switch(enum_parameter) + { + case AL_SOURCE_RELATIVE: + case AL_LOOPING: + { + return LUAOPENAL_BOOL_TYPE; + break; + } + + case AL_SOURCE_TYPE: + case AL_BUFFER: + case AL_BUFFERS_QUEUED: + case AL_BUFFERS_PROCESSED: + { + return LUAOPENAL_INT_TYPE; + break; + } + case AL_GAIN: + case AL_MIN_GAIN: + case AL_MAX_GAIN: + case AL_REFERENCE_DISTANCE: /* could be integer */ + case AL_ROLLOFF_FACTOR: /* could be integer */ + case AL_MAX_DISTANCE: /* could be integer */ + case AL_PITCH: + case AL_CONE_INNER_ANGLE: /* could be integer */ + case AL_CONE_OUTER_ANGLE: /* could be integer */ + case AL_CONE_OUTER_GAIN: /* interesting...only f,fv according to spec */ + case AL_SAMPLE_OFFSET: /* could be integer */ + case AL_BYTE_OFFSET: /* could be integer */ + case AL_SEC_OFFSET: /* could be integer */ + + case AL_DOPPLER_FACTOR: + case AL_DOPPLER_VELOCITY: + case AL_SPEED_OF_SOUND: + { + return LUAOPENAL_FLOAT_TYPE; + break; + } + + case AL_POSITION: /* could be integer */ + case AL_VELOCITY: /* could be integer */ + case AL_DIRECTION: /* could be integer */ + { + return LUAOPENAL_FLOAT_3_TYPE; + break; + } + + case AL_ORIENTATION: /* could be integer */ + { + return LUAOPENAL_FLOAT_6_TYPE; + break; + } + + case AL_NO_ERROR: + case AL_INVALID_NAME: + case AL_ILLEGAL_ENUM: +/* case AL_INVALID_ENUM: */ /* same as AL_ILLEGAL_ENUM */ + case AL_INVALID_VALUE: + case AL_ILLEGAL_COMMAND: +/* case AL_INVALID_OPERATION: */ /* same as AL_ILLEGAL_COMMAND */ + case AL_OUT_OF_MEMORY: + case AL_VENDOR: + case AL_VERSION: + case AL_RENDERER: + case AL_EXTENSIONS: + { + return LUAOPENAL_STRING_TYPE; + break; + } + + } + return 0; +} + +static int lua_alEnable(lua_State* lua_state) +{ + ALenum enum_parameter; + enum_parameter = lua_tointeger(lua_state, 1); + alEnable(enum_parameter); + return 0; +} + +static int lua_alDisable(lua_State* lua_state) +{ + ALenum enum_parameter; + enum_parameter = lua_tointeger(lua_state, 1); + alDisable(enum_parameter); + return 0; +} + +static int lua_alIsEnabled(lua_State* lua_state) +{ + ALenum enum_parameter; + ALboolean ret_val; + enum_parameter = lua_tointeger(lua_state, 1); + ret_val = alIsEnabled(enum_parameter); + lua_pushboolean(lua_state, ret_val); + return 1; +} + +static int lua_alGetError(lua_State* lua_state) +{ + ALenum ret_val; + ret_val = alGetError(); + lua_pushinteger(lua_state, ret_val); + return 1; +} + +static int lua_alIsExtensionPresent(lua_State* lua_state) +{ + const ALchar* string_name; + ALboolean ret_val; + string_name = lua_tostring(lua_state, 1); + ret_val = alIsExtensionPresent(string_name); + lua_pushboolean(lua_state, ret_val); + return 1; +} + +/* TODO: alGetProcAddress */ + +static int lua_alGetEnumValue(lua_State* lua_state) +{ + const ALchar* string_name; + ALenum ret_val; + string_name = lua_tostring(lua_state, 1); + ret_val = alGetEnumValue(string_name); + lua_pushinteger(lua_state, ret_val); + return 1; +} + + + + +static int lua_alGetListener(lua_State* lua_state) +{ + ALenum enum_parameter; + int openal_primitive_type; + + enum_parameter = lua_tointeger(lua_state, 1); + + openal_primitive_type = lua_getTypeForEnum(enum_parameter); + + switch(openal_primitive_type) + { + case LUAOPENAL_BOOL_TYPE: + { + ALint ret_val; + alGetListeneri(enum_parameter, &ret_val); + lua_pushboolean(lua_state, ret_val); + return 1; + break; + } + case LUAOPENAL_INT_TYPE: + { + ALint ret_val; + alGetListeneri(enum_parameter, &ret_val); + lua_pushinteger(lua_state, ret_val); + return 1; + break; + } + case LUAOPENAL_FLOAT_TYPE: + { + ALfloat ret_val; + alGetListenerf(enum_parameter, &ret_val); + lua_pushnumber(lua_state, ret_val); + return 1; + break; + } + case LUAOPENAL_INT_3_TYPE: + { + ALint ret_val[3]; + alGetListeneriv(enum_parameter, ret_val); + lua_pushinteger(lua_state, ret_val[0]); + lua_pushinteger(lua_state, ret_val[1]); + lua_pushinteger(lua_state, ret_val[2]); + return 3; + break; + } + case LUAOPENAL_FLOAT_3_TYPE: + { + ALfloat ret_val[3]; + alGetListenerfv(enum_parameter, ret_val); + lua_pushnumber(lua_state, ret_val[0]); + lua_pushnumber(lua_state, ret_val[1]); + lua_pushnumber(lua_state, ret_val[2]); + return 3; + break; + } + case LUAOPENAL_FLOAT_6_TYPE: + { + ALfloat ret_val[6]; + alGetListenerfv(enum_parameter, ret_val); + lua_pushnumber(lua_state, ret_val[0]); + lua_pushnumber(lua_state, ret_val[1]); + lua_pushnumber(lua_state, ret_val[2]); + lua_pushnumber(lua_state, ret_val[3]); + lua_pushnumber(lua_state, ret_val[4]); + lua_pushnumber(lua_state, ret_val[5]); + return 6; + break; + } + default: + { + /* TODO: Figure out how to handle OpenAL extensions. */ + luaL_error(lua_state, "Unhandled parameter type for alGetSource*"); + } + } + return 0; +} + +static int lua_alListener(lua_State* lua_state) +{ + ALenum enum_parameter; + int openal_primitive_type; + + enum_parameter = lua_tointeger(lua_state, 1); + + + openal_primitive_type = lua_getTypeForEnum(enum_parameter); + + switch(openal_primitive_type) + { + case LUAOPENAL_BOOL_TYPE: + case LUAOPENAL_INT_TYPE: + { + alListeneri(enum_parameter, lua_tointeger(lua_state, 2)); + break; + } + case LUAOPENAL_FLOAT_TYPE: + { + alListenerf(enum_parameter, lua_tonumber(lua_state, 2)); + break; + } + case LUAOPENAL_INT_3_TYPE: + { + alListener3i(enum_parameter, lua_tointeger(lua_state, 2), lua_tointeger(lua_state, 3), lua_tointeger(lua_state, 4)); + break; + } + case LUAOPENAL_FLOAT_3_TYPE: + { + alListener3f(enum_parameter, lua_tonumber(lua_state, 2), lua_tonumber(lua_state, 3), lua_tonumber(lua_state, 4)); + break; + } + case LUAOPENAL_FLOAT_6_TYPE: + { + ALfloat val_array[6] = + { + lua_tonumber(lua_state, 2), + lua_tonumber(lua_state, 3), + lua_tonumber(lua_state, 4), + lua_tonumber(lua_state, 5), + lua_tonumber(lua_state, 6), + lua_tonumber(lua_state, 7) + }; + + alListenerfv(enum_parameter, val_array); + break; + } + default: + { + /* TODO: Figure out how to handle OpenAL extensions. */ + luaL_error(lua_state, "Unhandled parameter type for alSource*"); + } + } + return 0; +} + + + + +static int lua_alGetSource(lua_State* lua_state) +{ + ALuint source_id; + ALenum enum_parameter; + int openal_primitive_type; + + source_id = lua_tointeger(lua_state, 1); + enum_parameter = lua_tointeger(lua_state, 2); + + openal_primitive_type = lua_getTypeForEnum(enum_parameter); + + switch(openal_primitive_type) + { + case LUAOPENAL_BOOL_TYPE: + { + ALint ret_val; + alGetSourcei(source_id, enum_parameter, &ret_val); + lua_pushboolean(lua_state, ret_val); + return 1; + break; + } + case LUAOPENAL_INT_TYPE: + { + ALint ret_val; + alGetSourcei(source_id, enum_parameter, &ret_val); + lua_pushinteger(lua_state, ret_val); + return 1; + break; + } + case LUAOPENAL_FLOAT_TYPE: + { + ALfloat ret_val; + alGetSourcef(source_id, enum_parameter, &ret_val); + lua_pushnumber(lua_state, ret_val); + return 1; + break; + } + case LUAOPENAL_INT_3_TYPE: + { + ALint ret_val[3]; + alGetSourceiv(source_id, enum_parameter, ret_val); + lua_pushinteger(lua_state, ret_val[0]); + lua_pushinteger(lua_state, ret_val[1]); + lua_pushinteger(lua_state, ret_val[2]); + return 3; + break; + } + case LUAOPENAL_FLOAT_3_TYPE: + { + ALfloat ret_val[3]; + alGetSourcefv(source_id, enum_parameter, ret_val); + lua_pushnumber(lua_state, ret_val[0]); + lua_pushnumber(lua_state, ret_val[1]); + lua_pushnumber(lua_state, ret_val[2]); + return 3; + break; + } + default: + { + /* TODO: Figure out how to handle OpenAL extensions. */ + luaL_error(lua_state, "Unhandled parameter type for alGetSource*"); + } + } + return 0; +} + + +static int lua_alSource(lua_State* lua_state) +{ + ALuint source_id; + ALenum enum_parameter; + int openal_primitive_type; + + source_id = lua_tointeger(lua_state, 1); + enum_parameter = lua_tointeger(lua_state, 2); + + openal_primitive_type = lua_getTypeForEnum(enum_parameter); + + switch(openal_primitive_type) + { + case LUAOPENAL_BOOL_TYPE: + case LUAOPENAL_INT_TYPE: + { + alSourcei(source_id, enum_parameter, lua_tointeger(lua_state, 3)); + break; + } + case LUAOPENAL_FLOAT_TYPE: + { + alSourcef(source_id, enum_parameter, lua_tonumber(lua_state, 3)); + break; + } + case LUAOPENAL_INT_3_TYPE: + { + alSource3i(source_id, enum_parameter, lua_tointeger(lua_state, 3), lua_tointeger(lua_state, 4), lua_tointeger(lua_state, 5)); + break; + } + case LUAOPENAL_FLOAT_3_TYPE: + { + alSource3f(source_id, enum_parameter, lua_tonumber(lua_state, 3), lua_tonumber(lua_state, 4), lua_tonumber(lua_state, 5)); + break; + } + default: + { + /* TODO: Figure out how to handle OpenAL extensions. */ + luaL_error(lua_state, "Unhandled parameter type for alSource*"); + } + } + return 0; +} + + +static int lua_alDopplerFactor(lua_State* lua_state) +{ + ALfloat the_value; + the_value = lua_tonumber(lua_state, 1); + alDopplerFactor(the_value); + return 0; +} + +static int lua_alDopplerVelocity(lua_State* lua_state) +{ + ALfloat the_value; + the_value = lua_tonumber(lua_state, 1); + alDopplerVelocity(the_value); + return 0; +} + +static int lua_alSpeedOfSound(lua_State* lua_state) +{ + ALfloat the_value; + the_value = lua_tonumber(lua_state, 1); + alSpeedOfSound(the_value); + return 0; +} + +static int lua_alDistanceModel(lua_State* lua_state) +{ + ALenum enum_parameter; + enum_parameter = lua_tointeger(lua_state, 1); + alDistanceModel(enum_parameter); + return 0; +} + +static int lua_alGet(lua_State* lua_state) +{ + ALenum enum_parameter; + int openal_primitive_type; + + enum_parameter = lua_tointeger(lua_state, 1); + + openal_primitive_type = lua_getTypeForEnum(enum_parameter); + + switch(openal_primitive_type) + { + case LUAOPENAL_BOOL_TYPE: + { + ALboolean ret_val; + ret_val = alGetBoolean(enum_parameter); + lua_pushboolean(lua_state, ret_val); + return 1; + break; + } + case LUAOPENAL_INT_TYPE: + { + ALint ret_val; + ret_val = alGetInteger(enum_parameter); + lua_pushinteger(lua_state, ret_val); + return 1; + break; + } + case LUAOPENAL_FLOAT_TYPE: + { + ALfloat ret_val; + ret_val = alGetFloat(enum_parameter); + lua_pushnumber(lua_state, ret_val); + return 1; + break; + } + case LUAOPENAL_INT_3_TYPE: + { + ALint ret_val[3]; + alGetIntegerv(enum_parameter, ret_val); + lua_pushinteger(lua_state, ret_val[0]); + lua_pushinteger(lua_state, ret_val[1]); + lua_pushinteger(lua_state, ret_val[2]); + return 3; + break; + } + case LUAOPENAL_FLOAT_3_TYPE: + { + ALfloat ret_val[3]; + alGetFloatv(enum_parameter, ret_val); + lua_pushnumber(lua_state, ret_val[0]); + lua_pushnumber(lua_state, ret_val[1]); + lua_pushnumber(lua_state, ret_val[2]); + return 3; + break; + } + case LUAOPENAL_FLOAT_6_TYPE: + { + ALfloat ret_val[6]; + alGetFloatv(enum_parameter, ret_val); + lua_pushnumber(lua_state, ret_val[0]); + lua_pushnumber(lua_state, ret_val[1]); + lua_pushnumber(lua_state, ret_val[2]); + lua_pushnumber(lua_state, ret_val[3]); + lua_pushnumber(lua_state, ret_val[4]); + lua_pushnumber(lua_state, ret_val[5]); + return 6; + break; + } + case LUAOPENAL_STRING_TYPE: + { + const ALchar* ret_val; + ret_val = alGetString(enum_parameter); + lua_pushstring(lua_state, ret_val); + return 1; + break; + } + default: + { + /* TODO: Figure out how to handle OpenAL extensions. */ + luaL_error(lua_state, "Unhandled parameter type for alGetSource*"); + } + } + return 0; +} + +static const luaL_reg luaALFuncs[] = +{ + { "Enable", lua_alEnable }, + { "Disable", lua_alDisable }, + { "IsEnabled", lua_alIsEnabled }, + { "Get", lua_alGet }, + { "GetError", lua_alGetError }, + { "IsExtensionPresent", lua_alIsExtensionPresent }, + { "GetEnumValue", lua_alGetEnumValue }, + { "Listener", lua_alListener }, + { "GetListener", lua_alGetListener }, + { "Source", lua_alSource }, + { "GetSource", lua_alGetSource }, + { "DopplerFactor", lua_alDopplerFactor }, + { "DopplerVelocity", lua_alDopplerVelocity }, + { "SpeedOfSound", lua_alSpeedOfSound }, + { "DistanceModel", lua_alDistanceModel }, + { "DopplerVelocity", lua_alDopplerVelocity }, + {NULL, NULL} +}; + + +int luaopen_luaal(lua_State *L) +{ + luaL_register(L, "al", luaALFuncs); + + luaopenal_initenum(L, s_luaALEnum); + + lua_pushstring(L, "_VERSION"); + lua_pushstring(L, LUAAL_VERSION); + lua_settable(L,-3); + + return 1; +} + + diff -r b346b6608eab -r 71b465ff0622 Isolated/luaal/luaal.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Isolated/luaal/luaal.h Thu Apr 28 16:22:30 2011 -0700 @@ -0,0 +1,40 @@ +/* + LuaOpenAL + Copyright (C) 2010 Eric Wing + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + +*/ + +#ifndef __LUAAL_H__ +#define __LUAAL_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +int luaopen_luaal(lua_State* lua_state); + +#ifdef __cplusplus +} +#endif + + +#endif + diff -r b346b6608eab -r 71b465ff0622 Isolated/tErrorLib.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Isolated/tErrorLib.c Thu Apr 28 16:22:30 2011 -0700 @@ -0,0 +1,1264 @@ +/* See header file for license information. */ + +#include "tErrorLib.h" +#include /* for malloc */ +#include +#include /* for vasprintf */ +#include /* also for vasprintf and for printf family */ +#include /* size_t */ + +/** + * For string-only based usage, this implementation + * still expects an actual error number to be set. + * I am defining 1 as that error value. This might be changable, + * but it is untested. If you change this value, you must recompile + * the entire library. This can really be any integer except what + * TERROR_NOERROR_VALUE (in header) is set to. + */ +#define TERROR_ERROR_VALUE 1 + +#ifdef DONT_USE_VASPRINT + #define TERROR_DEFAULT_STRING_LENGTH 128 + /* Visual Studio doesn't define snprintf but _snprintf */ + #ifdef _MSC_VER + #define snprintf _snprintf + #define vsnprintf _vsnprintf + #endif +#endif + + +#if defined(_WIN32) && !defined(__CYGWIN32__) + #include + #include /* For CreateMutex(), LockFile() */ + + static void* Internal_CreateMutex() + { + return((void*)CreateMutex(NULL, FALSE, NULL)); + } + static void Internal_DestroyMutex(void* mutex) + { + if(NULL != mutex) + { + CloseHandle( (HANDLE)mutex ); + } + } + /* This will return true if locking is successful, false if not. + */ + static int Internal_LockMutex(void* mutex) + { + return( + WaitForSingleObject( + (HANDLE)mutex, + INFINITE + ) != WAIT_FAILED + ); + } + static void Internal_UnlockMutex(void* mutex) + { + ReleaseMutex( + (HANDLE)mutex + ); + } + size_t Internal_PlatformPlatformGetThreadID(void) + { + return((size_t)GetCurrentThreadId()); + } +#else /* Assuming POSIX...maybe not a good assumption. */ + #include + static void* Internal_CreateMutex() + { + int ret_val; + pthread_mutex_t* m = (pthread_mutex_t*)malloc(sizeof(pthread_mutex_t)); + if(NULL == m) + { + return NULL; + } + ret_val = pthread_mutex_init(m, NULL); + if(0 != ret_val) + { + free(m); + return NULL; + } + return((void*)m); + } + static void Internal_DestroyMutex(void* mutex) + { + if(NULL != mutex) + { + pthread_mutex_destroy((pthread_mutex_t*) (mutex)); + free(mutex); + } + } + /* This will return true if locking is successful, false if not. + * (This is the opposite of pthread_mutex_lock which returns + * 0 for success.) + */ + static int Internal_LockMutex(void* mutex) + { + return( + pthread_mutex_lock( + (pthread_mutex_t*)mutex + ) == 0 + ); + } + static void Internal_UnlockMutex(void* mutex) + { + pthread_mutex_unlock( + (pthread_mutex_t*)mutex + ); + } + + size_t Internal_PlatformGetThreadID() + { + /* Basically, we need to convert a pthread_t into an id number. */ + return (size_t)pthread_self(); + } +#endif + + +/** + * Copies a source string, potentially to a target string, and returns + * the pointer to the copied string. + * This function is a intended to be an efficient string copy function. + * It's purpose is to copy a string into a string with preallocated memory + * and avoid dynamic memory allocation if possible. If memory must + * be allocated, then the old string will be destroyed. + * + * This is only to be used where target_string was created with dynamic + * memory. This function will destroy the memory and allocate new memory + * if there is not enough space in the target string. + * + * @param target_string This is the string you would like to try + * to copy into. If there is not enough space, a new string will + * be created and the target_string will be freed. This string + * must have been created dynamically. This may be NULL if you + * wish for this function to dynamically create a new string + * for you. + * + * @param target_max_buffer_size This is a pointer that points to + * an address containing the size of the preallocated target_string. + * This size is the maximum buffer length which includes the '\\0' + * character as part of that count. This pointer may not be NULL. + * If you pass in NULL for the target_string (indicating you want + * a new string allocated for you), then the size should be set to 0. + * When the function completes, the size will be set to the new + * max buffer size of the string if the string needed to be reallocated. + * + * @param source_string This is the string you want to copy. If it's NULL, + * the target_string will have it's memory freed. + * + * @return Will return a pointer to the duplicated string. Be aware + * of several things: + * - The returned pointer address may not be the same address as the + * target string passed in (due to a possible reallocation). + * - If the pointer to the source and target string + * are the same, the pointer to the target string will be returned. + * - If the source string is NULL, the target string + * will be freed and will return NULL. + * - If an error occurs, NULL will be returned. + * + * Also note that the value at the address target_max_buffer_size points + * to will be filled with the new max buffer size for the string. + * + * Example: + * @code + * + * int main() + * { + * const char* original1 = "Hello World"; + * const char* original2 = "Smaller"; + * const char* original3 = "Good-Bye World"; + * char* ret_val; + * char* target = NULL; + * size_t target_max_buffer_size = 0; + * + * ret_val = CopyDynamicString(target, &target_max_buffer_size, original1); + * + * if(ret_val) + * { + * fprintf(stderr, "Target is '%s' with max size = %d\n", ret_val, target_max_buffer_size); + * } + * else + * { + * fprintf(stderr, "Error in function\n"); + * } + * target = ret_val; + * + * ret_val = CopyDynamicString(target, &target_max_buffer_size, original2); + * fprintf(stderr, "Target is '%s' with max size = %d\n", ret_val, target_max_buffer_size); + * + * target = ret_val; * + * ret_val = CopyDynamicString(target, &target_max_buffer_size, original3); + * fprintf(stderr, "Target is '%s' with max size = %d\n", ret_val, target_max_buffer_size); + * + * return 0; + * } + * @endcode + * This outputs: + * @code + * Target is 'Hello World' with max size = 12 + * Target is 'Smaller' with max size = 12 + * Target is 'Good-Bye World' with max size = 15 + * @endcode + */ +static char* Internal_CopyDynamicString(char* target_string, size_t* target_max_buffer_size, const char* source_string) +{ + /* If the pointers are the same, no copy is needed. */ + if(source_string == target_string) + { + /* I don't feel like asserting if the sizes are the same. */ + /* Return 1 instead of 0 because maybe this isn't an error? + */ + return target_string; + } + + /* Make sure the size pointer is valid. */ + if(NULL == target_max_buffer_size) + { + return NULL; + } + + /* Yikes, if the string is NULL, should we make the target string NULL? + * For now, yes, we destroy the string. If you change this, realize that + * their is code that depends on this behavior. + */ + if(NULL == source_string) + { + *target_max_buffer_size = 0; + free(target_string); + target_string = NULL; + return NULL; + } + + /* If target_string is NULL, the *target_max_buffer_size should also be 0. + * Technically, the user should set this and this would be an error, + * but I'll be nice for now. An alternate implementation might suggest + * that the size would be the desired size the user wants for a new string. + */ + if( (NULL == target_string) && (0 != *target_max_buffer_size) ) + { + *target_max_buffer_size = 0; + } + + /* If there is not enough preallocated memory in the target string, + * then we need to reallocate enough memory. + */ + if( *target_max_buffer_size < (strlen(source_string) + 1) ) + { + *target_max_buffer_size = 0; + if(NULL != target_string) + { + free(target_string); + } + target_string = (char*)calloc( (strlen(source_string) + 1), sizeof(char) ); + if(NULL == target_string) + { + return NULL; + } + *target_max_buffer_size = strlen(source_string) + 1; + } + + /* At this point, there should be enough preallocated + * memory to call strncpy. + */ + strncpy(target_string, source_string, *target_max_buffer_size); + + return target_string; +} + +/** + * This is a structure that contains everything needed for an + * error message entry (per thread). The linked list stuff + * is fused in with it because I didn't want to write an entire + * linked list class. + */ +typedef struct TErrorMessageStructType +{ + size_t threadID; /** ThreadID for associated message. */ + int errorAvailable; /** 1 if an error has been set and not been checked. */ + int errorNumber; /**< For the user error number. */ + char* errorString; /**< For the user error message. */ + size_t errorMaxStringLength; /**< Max size of string buffer including \\0. */ + struct TErrorMessageStructType* nextItem; /**< Pointer to next error message in list. */ +} TErrorMessage; + +/** + * This is a private struct that contains all private data for an + * ErrorPool. Currently it is a linked list containing all error message + * structs for every thread. + */ +typedef struct +{ + TErrorMessage* errorMessageListHead; /**< Head of the error list. */ + TErrorMessage* lastErrorMessage; /**< Points to the last set element in the list for GetLastError. */ + /* Mutex */ +} TErrorPoolOpaqueData; + +/** + * This is a private helper function that creates a new TErrorMessage + * and initializes all its values. + * @return Returns a pointer to a newly allocated and initialized + * TErrorMessage or NULL on failure. + */ +static TErrorMessage* Internal_CreateErrorMessageStructure() +{ + TErrorMessage* new_message; + /* Use calloc to create a fully cleared structure, + * so I don't have to set/clear each member. + */ + new_message = (TErrorMessage*)calloc(1, sizeof(TErrorMessage)); + if(NULL == new_message) + { + /* Very bad, but not sure what to do. */ + return NULL; + } + new_message->errorNumber = TERROR_NOERROR_VALUE; + return new_message; +} + +/** + * This is a private helper function that frees a TErrorMessage. + * + * @param err_mesg The pointer to the TErrorMessage to be freed. + */ +static void Internal_FreeErrorMessageStructure(TErrorMessage* err_mesg) +{ + if(NULL == err_mesg) + { + return; + } + if(NULL != err_mesg->errorString) + { + free(err_mesg->errorString); + err_mesg->errorString = NULL; + } + err_mesg->nextItem = NULL; + free(err_mesg); +} + +/** + * This is a private helper function that will search the error pool + * for the last set error message structure in the Linked list. + * If the last error message was on a different thread, the error + * data will be copied to the current thread's memory and the + * lastErrorMessage pointer will be set to the current thread's message. + * (This is because I expect this message to be marked as cleared/read.) + * This function does its own mutex locking. + * + * @param err_pool The error pool to be used. + * @return Returns the a pointer to the TErrorMessage if found, + * NULL if not found. + */ +static TErrorMessage* Internal_GetLastError(TErrorPool* err_pool) +{ + size_t thread_id; + TErrorMessage* current_thread_err_mesg; + TErrorPoolOpaqueData* err_pool_data; + + thread_id = Internal_PlatformGetThreadID(); + + Internal_LockMutex(err_pool->mutexLock); + + err_pool_data = err_pool->opaqueData; + + if(NULL == err_pool_data->errorMessageListHead) + { + Internal_UnlockMutex(err_pool->mutexLock); + return NULL; + } + + /* I think this is actually an assertion failure. + * I do the check here so I don't have to keep checking below. + */ + if(NULL == err_pool_data->lastErrorMessage) + { + Internal_UnlockMutex(err_pool->mutexLock); + return NULL; + } + + /* We need to determine if the lastMessage pointer is pointing + * to data on the current thread. If it is we can just return it. + * Otherwise, we need to copy the message to the current thread's + * error message memory area. + * We should also update the lastMessage pointer to point + * to this message since it will likely be marked cleared once read. + */ + if(thread_id == err_pool_data->lastErrorMessage->threadID) + { + /* Not copy is needed. The last error message already + * points to the memory on the current thread. + * We can short-circuit and return. + */ + Internal_UnlockMutex(err_pool->mutexLock); + return err_pool_data->lastErrorMessage; + } + + /* Sigh, I really should have a dedicated linked list structure, + * but I don't feel like writing it right now. + */ + for(current_thread_err_mesg = err_pool_data->errorMessageListHead; current_thread_err_mesg != NULL; current_thread_err_mesg = current_thread_err_mesg->nextItem) + { + /* First find the message (memory) for the current thread. */ + if(thread_id == current_thread_err_mesg->threadID) + { + /* Now we need to copy the message data from the lastErrorMessage + * to this thread's message (memory). + */ + current_thread_err_mesg->errorNumber = err_pool_data->lastErrorMessage->errorNumber; + current_thread_err_mesg->errorAvailable = err_pool_data->lastErrorMessage->errorAvailable; + /* This will copy the string and set the new errorMaxStringLength as needed. */ + current_thread_err_mesg->errorString = Internal_CopyDynamicString(current_thread_err_mesg->errorString, ¤t_thread_err_mesg->errorMaxStringLength, err_pool_data->lastErrorMessage->errorString); + + + /* Finally, change the last error message to point to + * the current thread since I expect the message to be + * marked cleared and we don't want to accidentally refetched + * the stale, uncleared entry. + */ + err_pool_data->lastErrorMessage = current_thread_err_mesg; + + Internal_UnlockMutex(err_pool->mutexLock); + return current_thread_err_mesg; + } + } + Internal_UnlockMutex(err_pool->mutexLock); + return NULL; +} + +/** + * This is a private helper function that will search the error pool + * for an error message structure in the Linked list (by thread ID) + * and return the pointer if found. This function does its own mutex + * locking. + * @param err_pool The error pool to be used. + * @return Returns the a pointer to the TErrorMessage if found, + * NULL if not found. + */ +static TErrorMessage* Internal_GetErrorOnCurrentThread(TErrorPool* err_pool) +{ + size_t thread_id; + TErrorMessage* current_err_mesg; + TErrorPoolOpaqueData* err_pool_data; + + thread_id = Internal_PlatformGetThreadID(); + + Internal_LockMutex(err_pool->mutexLock); + + err_pool_data = err_pool->opaqueData; + + if(NULL == err_pool_data->errorMessageListHead) + { + Internal_UnlockMutex(err_pool->mutexLock); + return NULL; + } + + /* Sigh, I really should have a dedicated linked list structure, + * but I don't feel like writing it right now. + */ + for(current_err_mesg = err_pool_data->errorMessageListHead; current_err_mesg != NULL; current_err_mesg = current_err_mesg->nextItem) + { + if(thread_id == current_err_mesg->threadID) + { + Internal_UnlockMutex(err_pool->mutexLock); + return current_err_mesg; + } + } + Internal_UnlockMutex(err_pool->mutexLock); + return NULL; +} + +/** + * Given a specific TErrorMessage*, will set the lastErrorMessage pointer to + * the provided error message. + * This function locks. + * + * @param err_pool The error pool to be used. + * @param error_message The error message to set the lastErrorMessage pointer to + */ +static void Internal_SetLastErrorMessagePointerToErrorMessage(TErrorPool* err_pool, TErrorMessage* error_message) +{ + TErrorPoolOpaqueData* err_pool_data; + Internal_LockMutex(err_pool->mutexLock); + err_pool_data = err_pool->opaqueData; + err_pool_data->lastErrorMessage = error_message; + Internal_UnlockMutex(err_pool->mutexLock); +} + + +/** + * This is a private helper function that creates a new error message + * structure for the current thread. + * This currently does not check if an error already exists + * before creating a new entry. Call GetErrorOnCurrentThread first + * to make sure nothing exists or duplicate entries will be created. + * This function does its own mutex locking. + * + * @param err_pool The error pool to be used. + * @return Returns the a pointer to the TErrorMessage if found, + * NULL if there was an allocation error. + */ +static TErrorMessage* Internal_CreateErrorOnCurrentThread(TErrorPool* err_pool) +{ + TErrorMessage* new_err_mesg; + TErrorPoolOpaqueData* err_pool_data; + + new_err_mesg = Internal_CreateErrorMessageStructure(); + if(NULL == new_err_mesg) + { + /* Serious problem, not sure what to do. */ + return NULL; + } + /* Copy the thread id so we can distinguish between entries. */ + new_err_mesg->threadID = Internal_PlatformGetThreadID(); + + Internal_LockMutex(err_pool->mutexLock); + + err_pool_data = err_pool->opaqueData; + /* Add the new message to the top of the list by making + * its next pointer point to the head of the current list. + * (A formal linked list implementation would make this feel + * less hacky.) + * This also (should) handle the case where errorMessageListHead + * is NULL. + */ + new_err_mesg->nextItem = err_pool_data->errorMessageListHead; + /* Now set the head of the list to the new message. + */ + err_pool_data->errorMessageListHead = new_err_mesg; + + Internal_UnlockMutex(err_pool->mutexLock); + + return new_err_mesg; +} + +/** + * This is a private helper function that will clean up all the + * error message structures in the list. This function does its + * own locking. + * @param err_pool The error pool to be used. + */ +static void Internal_FreeErrorMessageList(TErrorPool* err_pool) +{ + TErrorMessage* current_message = NULL; + TErrorMessage* next_message = NULL; + TErrorPoolOpaqueData* err_pool_data; + + Internal_LockMutex(err_pool->mutexLock); + + err_pool_data = err_pool->opaqueData; + + if(NULL == err_pool_data->errorMessageListHead) + { + Internal_UnlockMutex(err_pool->mutexLock); + return; + } + + /* Sigh, I really should have a dedicated linked list structure, + * but I don't feel like writing it right now. + */ + for(current_message = err_pool_data->errorMessageListHead; + current_message != NULL; + current_message = next_message + ) + { + next_message = current_message->nextItem; + Internal_FreeErrorMessageStructure(current_message); + } + err_pool_data->errorMessageListHead = NULL; + err_pool_data->lastErrorMessage = NULL; + + Internal_UnlockMutex(err_pool->mutexLock); +} + +/* + * API functions start below. + * + */ + + +void TError_DeleteEntryOnCurrentThread(TErrorPool* err_pool) +{ + TErrorMessage* prev_message = NULL; + TErrorMessage* current_message = NULL; + TErrorMessage* next_message = NULL; + size_t thread_id; + TErrorPoolOpaqueData* err_pool_data; + + thread_id = Internal_PlatformGetThreadID(); + + Internal_LockMutex(err_pool->mutexLock); + + err_pool_data = err_pool->opaqueData; + + if(NULL == err_pool_data->errorMessageListHead) + { + Internal_UnlockMutex(err_pool->mutexLock); + return; + } + + /* Sigh, I really should have a dedicated linked list structure, + * but I don't feel like writing it right now. + */ + for(current_message = err_pool_data->errorMessageListHead; + current_message != NULL; + /* I'm not going to increment here because I + * may delete the item below which would probably + * cause bad things to happen here. + */ +/* current_message = current_message->nextItem */ + ) + { + next_message = current_message->nextItem; + + if(thread_id == current_message->threadID) + { + /* Special case, current is only item in list: + * Both next and prev are NULL in this case. + * We should delete the item and set the errorMessageListHead + * to NULL. + */ + if((NULL == prev_message) && (NULL == next_message)) + { + Internal_FreeErrorMessageStructure(current_message); + current_message = NULL; + err_pool_data->errorMessageListHead = NULL; + err_pool_data->lastErrorMessage = NULL; + } + /* Special case, current is at head: + * Prev is NULL but next is not NULL in this case. + * We should delete the item and set the errorMessageListHead + * to point to next. + * (The code for the above case would probably work for + * this case too, but for clarity, this remains.) + */ + else if(NULL == prev_message) + { + /* If the current message happened to be the last message + * set, we need to change the lastErrorMessage pointer + * so it is not dangling. + */ + if(current_message == err_pool_data->lastErrorMessage) + { + err_pool_data->lastErrorMessage = NULL; + } + Internal_FreeErrorMessageStructure(current_message); + current_message = NULL; + err_pool_data->errorMessageListHead = next_message; + } + /* Special case, current is at tail. + * Prev is not NULL, but next is NULL in this case. + * We should delete the item and set prev->next to NULL. + */ + else if(NULL == next_message) + { + /* If the current message happened to be the last message + * set, we need to change the lastErrorMessage pointer + * so it is not dangling. + */ + if(current_message == err_pool_data->lastErrorMessage) + { + err_pool_data->lastErrorMessage = NULL; + } + Internal_FreeErrorMessageStructure(current_message); + current_message = NULL; + prev_message->nextItem = NULL; + } + /* Normal case, current is somewhere in the middle of the list. + * The item should be deleted and + * the prev_message->next should connect to + * the next_message. + */ + else + { + /* If the current message happened to be the last message + * set, we need to change the lastErrorMessage pointer + * so it is not dangling. + */ + if(current_message == err_pool_data->lastErrorMessage) + { + err_pool_data->lastErrorMessage = NULL; + } + Internal_FreeErrorMessageStructure(current_message); + current_message = NULL; + prev_message->nextItem = next_message; + } + } + /* It's not this thread, so increment everything for the next loop. */ + else + { + prev_message = current_message; + current_message = next_message; + } + } /* End for-loop */ + + Internal_UnlockMutex(err_pool->mutexLock); +} + + +void TError_GetLinkedVersion(TErrorVersion* ver) +{ + /* Check the pointer */ + if(NULL == ver) + { + /* Do nothing */ + return; + } + ver->major = TERROR_MAJOR_VERSION; + ver->minor = TERROR_MINOR_VERSION; + ver->patch = TERROR_PATCH_VERSION; +} + + +#if 0 +/* This is for global initialization, not pool initialization. */ +int TError_Init() +{ + /* initialize platform? */ + /* initialize mutexes? */ + +} +#endif + +TErrorPool* TError_CreateErrorPool() +{ + TErrorPool* err_pool; + TErrorPoolOpaqueData* err_pool_data; + + err_pool = (TErrorPool*)calloc(1, sizeof(TErrorPool)); + if(NULL == err_pool) + { + /* Very bad, but not sure what to do here. */ + return NULL; + } + err_pool_data = (TErrorPoolOpaqueData*)calloc(1, sizeof(TErrorPoolOpaqueData)); + if(NULL == err_pool_data) + { + /* Very bad, but not sure what to do here. */ + free(err_pool); + return NULL; + } + + /* Create mutex */ + err_pool->mutexLock = Internal_CreateMutex(); + + if(NULL == err_pool->mutexLock) + { + /* Very bad, but not sure what to do here. */ + free(err_pool_data); + free(err_pool); + return NULL; + } + + /* Attach the opaque data to the error pool. */ + err_pool->opaqueData = err_pool_data; + + /* The OpaqueData will hold the error message list, but it is + * allowed to be NULL for an empty list so we don't have to allocate + * it here. + */ + + return err_pool; +} + +/* There better not be any contention when this is called. */ +void TError_FreeErrorPool(TErrorPool* err_pool) +{ + if(NULL == err_pool) + { + return; + } + /* Free all the error messages for each thread. + * This locks and unlocks as it needs. + */ + Internal_FreeErrorMessageList(err_pool); + + /* Free opaque data structure. */ + free(err_pool->opaqueData); + + /* Delete mutex after all locking functions. */ + Internal_DestroyMutex(err_pool->mutexLock); + + /* Free main data structure. */ + free(err_pool); +} + +void TError_SetError(TErrorPool* err_pool, int err_num, const char* err_str, ...) +{ + va_list argp; + va_start(argp, err_str); + TError_SetErrorv(err_pool, err_num, err_str, argp); + va_end(argp); +} + +void TError_SetErrorv(TErrorPool* err_pool, int err_num, const char* err_str, va_list argp) +{ + TErrorMessage* error_message; + int ret_num_chars; + + if(NULL == err_pool) + { + return; + } + + error_message = Internal_GetErrorOnCurrentThread(err_pool); + /* If no error message was found, that means we must allocate + * a new entry for this entry. + */ + if(NULL == error_message) + { + error_message = Internal_CreateErrorOnCurrentThread(err_pool); + /* If this fails, this is bad...not sure what to do though. */ + if(NULL == error_message) + { + return; + } + } + + /* + * I don't think I have to lock here. The [Get|Create]ErrorOnCurrentThread + * functions lock err_pool as they need access. Here, I don't access + * err_pool (which is shared) and error_message should be unique for + * each thread so I don't think there is any contention. (Remember that + * simultaneous calls to SetError would only happen if they are in + * different threads.) + * There *might* be a problem with library calls (strncpy, calloc). + * I'm not sure if the various platforms are reentrant. + * I guess for now, I will assume they won't bite me. + */ + + /* If the err_str is NULL, we need to free our current string + * for consistency. More aggressive optimizations to hold the + * memory might be considered in the future. + */ + if(NULL == err_str) + { + if(NULL != error_message->errorString) + { + free(error_message->errorString); + error_message->errorString = NULL; + error_message->errorMaxStringLength = 0; + } + } + /* Else, copy the string */ + else + { + /* I am using vasprintf which is a GNU extension so it is not + * portable. However, vasprintf makes certain things possible + * which would not be otherwise, which is the reason for my + * use. The main benefit of asprintf/vasprintf is that you can + * create a string using printf style formatters without + * worrying about the buffer size. sprintf should never be + * used because of potential buffer overflows. snprintf + * is safer, but you are limited to a fixed size string + * which from time-to-time, I have exceeded unless you make + * the number really big. + * Furthermore, snprintf itself is not currently terribly portable + * because it is specified only for C99 which some compilers + * still have not have embraced. + * If you can't use the vasprintf implementation, + * you must add -DDONT_USE_VASPRINTF to your compile flags. + */ +#ifdef DONT_USE_VASPRINTF + /* This implementation uses vsnprintf instead of + * vasprintf. It is strongly recommended you use + * the vasprintf implmententation instead. + * Never use vsprintf unless you like + * buffer overflows and security exploits. + */ + + /* If the string was set to NULL, we must reallocate memory first. */ + if(NULL == error_message->errorString) + { + error_message->errorString = (char*)calloc(TERROR_DEFAULT_STRING_LENGTH, sizeof(char)); + if(NULL == error_message->errorString) + { + /* Very bad...what should I do? + */ + error_message->errorMaxStringLength = 0; + } + else + { + error_message->errorMaxStringLength = TERROR_DEFAULT_STRING_LENGTH; + } + } + /* Because of the "Very Bad" situation directly above, + * I need to check again to make sure the string isn't NULL. + * This will let the very bad situation continue on so va_end + * can be called and the error_number still has a chance to be set. + */ + if(NULL != error_message->errorString) + { + ret_num_chars = vsnprintf(error_message->errorString, + error_message->errorMaxStringLength, + err_str, + argp + ); + } + +#else /* DONT_USE_VASPRINTF */ + /* You might be wondering why the #ifdef logic assumes + * asprintf is available instead of requiring an explicit + * #define for that. The reason is because asprintf is the + * better option and I want you to realize that you are not + * using it. Typically, nobody knows or understands the build + * system and/or files get copied into new projects with a + * entirely new build system, so it is easy to forget to + * add a -D flag. So if you compile without asprintf, + * you are encouraged to explicitly know this. + */ + /* There may be a slight performance advantage to using snprintf + * over asprintf depending how asprintf is written. But this + * implementation will completely destroy and reallocate a + * string regardless if a string is set to NULL, so there will + * actually be no performance gains for these cases. + * (This could be optimized but some additional bookkeeping + * might be needed which might not be worth the effort and + * code clutter.) + * As for memory allocation safety, because new messages for + * different threads must be allocated dynamically, there is no + * way for this library to use purely static memory. + * So I don't believe there is anything to be gained using + * snprintf over asprintf and you lose out on arbitrary lengthed + * error messages. + * If memory allocation must be minimized, I recommend just + * using the error number interface by itself which + * will always keep the strings at NULL, and don't mess + * with the asprintf/sprintf code. + */ + + ret_num_chars = vasprintf(&error_message->errorString, err_str, argp); + /* vasprintf returns -1 as an error */ + if(-1 == ret_num_chars) + { + /* Very bad, but not sure what to do here. */ + if(NULL != error_message->errorString) + { + free(error_message->errorString); + error_message->errorString = NULL; + error_message->errorMaxStringLength = 0; + /* Don't return here. Still need to va_end, and + * there is a chance that the err_num might work. + * Plus the availability needs to be set. + */ + } + } + /* else vasprint returns the number of characters in the string + * not including the \0 character. + */ + else + { + /* I actually don't know how much memory vasprintf allocated + * for the string. But it is at least ret_num_chars+1, so + * I will use that as my max string length (which is + * mainly used by CopyDynamicString() for efficiency + * which is becoming less used in this code). + */ + error_message->errorMaxStringLength = ret_num_chars+1; + } +#endif /* DONT_USE_VASPRINTF */ + } + + /* I'm allowing for a user to explicitly clear an error message by + * clearing both attributes. + */ + if((TERROR_NOERROR_VALUE == err_num) && (NULL == err_str)) + { + error_message->errorNumber = TERROR_NOERROR_VALUE; + error_message->errorAvailable = 0; + } + /* This is the normal case, copy the error number + * and mark the error as unread. + */ + else + { + error_message->errorNumber = err_num; + error_message->errorAvailable = 1; + } + + /* Now that the data is set, we also want to denote that this + * thread is the last error message. We need to lock for this + * since the lastError pointer is shared across threads. + */ + Internal_SetLastErrorMessagePointerToErrorMessage(err_pool, error_message); +} + +void TError_SetErrorNoFormat(TErrorPool* err_pool, int err_num, const char* err_str) +{ + TErrorMessage* error_message; + if(NULL == err_pool) + { + return; + } + + error_message = Internal_GetErrorOnCurrentThread(err_pool); + /* If no error message was found, that means we must allocate + * a new entry for this entry. + */ + if(NULL == error_message) + { + error_message = Internal_CreateErrorOnCurrentThread(err_pool); + /* If this fails, this is bad...not sure what to do though. */ + if(NULL == error_message) + { + return; + } + } + + /* + * I don't think I have to lock here. The [Get|Create]ErrorOnCurrentThread + * functions lock err_pool as they need access. Here, I don't access + * err_pool (which is shared) and error_message should be unique for + * each thread so I don't think there is any contention. (Remember that + * simultaneous calls to SetError would only happen if they are in + * different threads.) + * There *might* be a problem with library calls (strncpy, calloc). + * I'm not sure if the various platforms are reentrant. + * I guess for now, I will assume they won't bite me. + */ + error_message->errorNumber = err_num; + /* This will copy the string and set the new errorMaxStringLength as needed. */ + error_message->errorString = Internal_CopyDynamicString(error_message->errorString, &error_message->errorMaxStringLength, err_str); + /* I'm allowing for a user to explicitly clear an error message by + * clearing both attributes. + */ + if((TERROR_NOERROR_VALUE == err_num) && (NULL == err_str)) + { + error_message->errorAvailable = 0; + } + else + { + error_message->errorAvailable = 1; + } + + /* Now that the data is set, we also want to denote that this + * thread is the last error message. We need to lock for this + * since the lastError pointer is shared across threads. + */ + Internal_SetLastErrorMessagePointerToErrorMessage(err_pool, error_message); +} + +void TError_SetErrorNum(TErrorPool* err_pool, int err_num) +{ + TError_SetErrorNoFormat(err_pool, err_num, NULL); +} + +void TError_SetErrorStr(TErrorPool* err_pool, const char* err_str, ...) +{ + va_list argp; + va_start(argp, err_str); + if(NULL == err_str) + { + TError_SetErrorv(err_pool, TERROR_NOERROR_VALUE, err_str, argp); + } + else + { + TError_SetErrorv(err_pool, TERROR_ERROR_VALUE, err_str, argp); + } + va_end(argp); +} + +void TError_SetErrorStrv(TErrorPool* err_pool, const char* err_str, va_list argp) +{ + if(NULL == err_str) + { + TError_SetErrorv(err_pool, TERROR_NOERROR_VALUE, err_str, argp); + } + else + { + TError_SetErrorv(err_pool, TERROR_ERROR_VALUE, err_str, argp); + } +} + +/* If a NULL string is set, then it is presumed no error actually occurred + * and this is a reset. So the err_num will be implicitly set to 0. Otherwise + * the err_num will be set to 1 (for internal consistency and conventions). + */ +void TError_SetErrorStrNoFormat(TErrorPool* err_pool, const char* err_str) +{ + if(NULL == err_str) + { + TError_SetErrorNoFormat(err_pool, TERROR_NOERROR_VALUE, err_str); + } + else + { + TError_SetErrorNoFormat(err_pool, TERROR_ERROR_VALUE, err_str); + } +} + +/* This currently returns 0 as a "no error found" value. + * This could potentially conflict with a user. For now, users + * shouldn't use 0 to represent an error. If this becomes a + * problem, we could introduce a magic number like -999999 and + * define TERROR_NO_ERROR_FOUND. + */ +int TError_GetErrorNumOnCurrentThread(TErrorPool* err_pool) +{ + TErrorMessage* error_message; + + error_message = Internal_GetErrorOnCurrentThread(err_pool); + + /* If no error message was found for the thread. */ + if(NULL == error_message) + { + return 0; + } + /* If an error message was found for the thread, but + * it has already been read/cleared. + */ + if(0 == error_message->errorAvailable) + { + return 0; + } + /* We found a legitimate error message, clear it and return it. */ + error_message->errorAvailable = 0; + return error_message->errorNumber; +} + +const char* TError_GetErrorStrOnCurrentThread(TErrorPool* err_pool) +{ + TErrorMessage* error_message; + + error_message = Internal_GetErrorOnCurrentThread(err_pool); + + /* If no error message was found for the thread. */ + if(NULL == error_message) + { + return 0; + } + /* If an error message was found for the thread, but + * it has already been read/cleared. + */ + if(0 == error_message->errorAvailable) + { + return 0; + } + /* We found a legitimate error message, clear it and return it. */ + error_message->errorAvailable = 0; + return error_message->errorString; +} + +TErrorStatus TError_GetErrorOnCurrentThread(TErrorPool* err_pool) +{ + TErrorMessage* error_message; + TErrorStatus error_container; + error_container.errorNumber = TERROR_NOERROR_VALUE; + error_container.errorString = NULL; + + error_message = Internal_GetErrorOnCurrentThread(err_pool); + + /* If no error message was found for the thread. */ + if(NULL == error_message) + { + return error_container; + } + /* If an error message was found for the thread, but + * it has already been read/cleared. + */ + if(0 == error_message->errorAvailable) + { + return error_container; + } + /* We found a legitimate error message, clear it and return it. */ + error_message->errorAvailable = 0; + + error_container.errorNumber = error_message->errorNumber; + error_container.errorString = error_message->errorString; + return error_container; +} + +/* This function is for alternative usage where you just want one error + * for all threads. The backend will still work the same, but when you + * call this function, it will look up the last set error, copy (with locking) + * the last error to the current thread's memory, and return the object. + * As always, since the returned object is only accessed on this thread, you + * don't have to worry about locking. + */ +TErrorStatus TError_GetLastError(TErrorPool* err_pool) +{ + + // Lock the error pool to get the lastMessage pointer + // if the lastMessage pointer is pointing to data on the current thread, + // we can just return it. + // Otherwise, we need to copy the message + + TErrorMessage* error_message; + TErrorStatus error_container; + error_container.errorNumber = TERROR_NOERROR_VALUE; + error_container.errorString = NULL; + + error_message = Internal_GetLastError(err_pool); + + /* If no error message was found for the thread. */ + if(NULL == error_message) + { + return error_container; + } + /* If an error message was found for the thread, but + * it has already been read/cleared. + */ + if(0 == error_message->errorAvailable) + { + return error_container; + } + /* We found a legitimate error message, clear it and return it. */ + error_message->errorAvailable = 0; + + error_container.errorNumber = error_message->errorNumber; + error_container.errorString = error_message->errorString; + return error_container; +} + +/* This currently returns 0 as a "no error found" value. + * This could potentially conflict with a user. For now, users + * shouldn't use 0 to represent an error. If this becomes a + * problem, we could introduce a magic number like -999999 and + * define TERROR_NO_ERROR_FOUND. + */ +int TError_GetLastErrorNum(TErrorPool* err_pool) +{ + TErrorMessage* error_message; + + error_message = Internal_GetLastError(err_pool); + + /* If no error message was found for the thread. */ + if(NULL == error_message) + { + return 0; + } + /* If an error message was found for the thread, but + * it has already been read/cleared. + */ + if(0 == error_message->errorAvailable) + { + return 0; + } + /* We found a legitimate error message, clear it and return it. */ + error_message->errorAvailable = 0; + return error_message->errorNumber; +} + +const char* TError_GetLastErrorStr(TErrorPool* err_pool) +{ + TErrorMessage* error_message; + + error_message = Internal_GetLastError(err_pool); + + /* If no error message was found for the thread. */ + if(NULL == error_message) + { + return 0; + } + /* If an error message was found for the thread, but + * it has already been read/cleared. + */ + if(0 == error_message->errorAvailable) + { + return 0; + } + /* We found a legitimate error message, clear it and return it. */ + error_message->errorAvailable = 0; + return error_message->errorString; +} + + + diff -r b346b6608eab -r 71b465ff0622 Isolated/tErrorLib.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Isolated/tErrorLib.h Thu Apr 28 16:22:30 2011 -0700 @@ -0,0 +1,714 @@ +/* + * zlib license. + */ +/* + Copyright (c) 2003 Eric Wing + + This software is provided 'as-is', without any express or implied warranty. + In no event will the authors be held liable for any damages arising from + the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software in a + product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source distribution. + +*/ +/** + * @file + * This is a Thread Safe Error handling Library. (How safe is still to be + * determined.) The name ErrorLib was already taken. TSError might work, but + * Terror was more fun to use. Named tErrorLib just in case of + * any conflicts with others who use "terror". + * + * This library is a generalized error flagging library. When an error + * occurs, this library allows you to set an error condition (a number + * and/or string) which can be fetched by at a later time (perhaps in a + * different module). It is analgous to perror or glGetError(), though + * perhaps a closer analogy might be to SDL_SetError() as this library + * allows you to deal with both integer numbers as well as printf-style + * formatted strings. + * + * This library also attempts to implement a certain degree of thread + * safety. The problem with a general access error system used + * by multiple threads is the risk of clobbering error messages set + * in one thread by error messages set in other threads before you + * get a chance to read them. This library solves that problem by + * creating separate error message structures for every unique thread. + * + * This library is unique in several ways. First, this is an entirely + * self-contained, error-handling-only library. Most people seem to at + * best rewrite their own error handling system for each library they write + * (and at worst, don't write any error handling system at all). + * + * Second, because this library is intended for just error handling, + * it was designed with the idea that it could be dropped into any + * library you choose and you are allowed to have separate/isolated + * error "pools". So for example, if you were writing multiple modules, + * such as a graphics library, a sound library, and an application core, + * you might desire to have separate error pools for each system so + * they don't collide. + * In order to accommodate the possible multiple instance of pools, the + * library has been factored into an object-oriented structure. Since + * this library is C-based, it does impose an additional parameter to + * be passed around than what people might be used to, but this + * API could be wrapped easily that hides the object pointer using a + * static or global variable for your module if you choose. + * + * Finally, this library allows the use of either/both integer or string + * errors. Integer error numbers allow for quick access and easy comparison. + * It may also allow for easier internationalization of error messages + * if you maintain a look-up-table. + * String error messages are nice because you know what the problem is + * immediately (don't have to hunt down or figure out what error 427 means). + * They also help make the code self documenting. And if you have errors + * nested down in function call layers, you can easily report the + * function call stack by appending information to the error string + * at each function call. + * + * Keep in mind that this library isn't meant to replace other error + * reporting conventions, but to supplement them. Functions that return + * error values (true/false,int) and logging systems (Logger) are still + * good systems that may address other issues. You might continue + * using error codes for your functions and then use tErrorLib to + * fetch the error string when you need them. And to log the error, + * you might pass the string into Logger. + * + * There are two different ways to use this library with respect to + * retrieving errors. You can treat each thread separately and let each + * live in its own separate world. When you retrieve the error, you only + * retrieve the error for the current thread. You should use the + * TError_GetErrorOnCurrentThread family of functions for this scenario. + * Alternatively, you can use this library to always return you the last + * set error regardless of the thread it was set in. You should use the + * TError_GetLastError family of functions for this scenario. + * + * Sample Usage: + * @code + * TErrorPool* err_pool; + * TErrorStatus error_status; + * + * err_pool = TError_CreateErrorPool(); + * if(NULL == err_pool) + * { + * fprintf(stderr, "Error, could not create error pool\n"); + * exit(1); + * } + * + * // Set a hypothetical error + * TError_SetError(err_pool, -1234, "Demo error #%d: %s", 222, "Hello"); + * + * // Check/get the error using the isolated thread usage model. + * // Might use TError_GetErrorNumOnCurrentThread or TError_GetErrorStrOnCurrentThread instead + * error_status = TError_GetErrorOnCurrentThread(err_pool); + * // Make sure the string is not NULL before printing it with printf. + * // (Some systems let you print NULL, but on Solaris, it seg faults.) + * if(NULL != error_status.errorString) + * { + * printf("%d: %s", error_status.errorNumber, error_status.errorString); + * } + * + * // not really necessary to call since single threaded and + * // going to delete the entire pool right after, but here for show. + * TError_DeleteEntryOnCurrentThread(err_pool); + * + * TError_FreeErrorPool(err_pool); + * + * @endcode + * + * Sample API Wrapping: + * This is an example of how you might want to wrap this library into + * your own API code so you don't have to expose the error pool. + * This example uses the last set error usage model. + * (If you didn't want to use both integers and strings, you could + * also make that decision here.) + * + * @code + * #include "tErrorLib.h" + * static TErrorPool* s_ErrorPool = NULL; // static error pool for this module + * + * int MyLibraryInit() + * { + * // Presumably you have you application specific code here too. + * s_ErrorPool = TError_CreateErrorPool(); + * if(NULL == s_ErrorPool) + * { + * return 0; // couldn't allocate memory + * } + * return 1; + * } + * + * void MyLibraryQuit() + * { + * TError_FreeErrorPool(s_ErrorPool); + * s_ErrorPool = NULL; + * } + * + * void MyLibrarySetError(int err_num, const char* err_str, ...) + * { + * va_list argp; + * va_start(argp, err_str); + * TError_SetErrorv(s_ErrorPool, err_num, err_str, argp); + * va_end(argp); + * } + * + * const char* MyLibraryGetError() + * { + * const char* ret_error = TError_GetLastErrorStr(s_ErrorPool); + * if(NULL == ret_error) + * { + * return ""; // provide an empty string to make it safe for people using printf without checking. + * } + * return ret_error; + * } + * @endcode + * + * @note By default, this library attempts to use vasprintf to generate + * the printf-style formatted strings. vasprintf is a GNU extension + * which solves the problem of having enough memory allocated in a buffer + * to handle an arbitrary length formatted string which you don't know + * in advance. I recommend you use this implementation if your library + * can support it as it will allow you to always generate correct strings. + * (Stack traces can become long and exceed your preallocated buffer sizes.) + * For compatibility, an alternative vsnprintf implementation is provided. + * If a string is too large, it will be truncated. vsnprintf is specified + * in C99, but it critcal for avoid security related issues surrounding + * sprintf and vsprintf. If your library lacks vsnprintf, you're asking + * for trouble. I currently do not try to handle this case. + * + * @note By default, this library assumes 0 is the no-error value. + * In addition, if you are using the string-only based APIs, the integer + * components will be automatically filled to 0 (for no-error) and + * 1 (for error). If these numbers conflict with your conventions, + * you may be able to change these values in the implementation file + * and recompile the library. Look for the defines for TERROR_ERROR_VALUE + * and TERROR_NOERROR_VALUE. + * + * @note This version attempts to provide enough thread safety to get by + * but it may not be totally safe during creation and destruction of + * instances (partly since locking is done inside the object-level), + * so don't create or destroy pools when there is possible contention. + * Strings you pass into the functions are not necessarily locked so + * be careful if you are modifying strings that are shared among + * your threads. + * + * @note Error strings returned are pointers to tErrorLib's internal + * copies of strings. Do not modify these or delete them. Also keep in + * mind that the pointers to these strings may become invalid when + * a new error is set (on a per-thread basis). So if you need a copy + * of the error string, you should make your own copy. + * + * @warning For code that frequently generates and destroys many threads, + * be aware that you should pay attention to memory management with this + * library. This library works by creating a unique error message + * structure for each thread. When the thread dies, the error pool + * will still contain a structure for that thread (if it had called + * this library in that thread). Just before the thread dies (but after + * any final error calls), you should call TError_DeleteEntryOnCurrentThread() + * to free the memory for that thread. Otherwise you will have a + * pseudo-memory-leak. (Pseudo in the sense that once you free the error pool, + * all memory will be freed, regardless of whether you remembered to call + * this function.) + * @see TERROR_NOERROR_VALUE, TError_DeleteEntryOnCurrentThread + * + * @author Eric Wing + */ + +#ifndef TERRORLIB_H +#define TERRORLIB_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include /* for va_list */ + +/** + * This library determines if there is an error by checking + * both the error number and error string. If the error string is NULL + * and the error number is 0 (TERROR_NOERROR_VALUE), then it + * is considered a non-error. Because this library allows you to + * use just numbers or just strings, a value must be filled in internally + * as a place holder. It also must return some default/no-error values + * for GetError if there was no error. In these situations, + * NULL is set for strings and 0 (TERROR_NOERROR_VALUE) + * is set for numbers. This will become a point of confusion if you use + * 0 as an error code to denote a legitimate error and have a NULL error + * string. + * + * To accommodate this problem, this define is provided to let you + * redefine what the no-error value is (though this is untested). + * If you have the opportunity to write code that doesn't rely on 0 denoting + * an error, I recommend using this library as is, instead of trying + * to change this. If you do change this value, remember you must recompile + * the entire library. Also make sure that TERROR_ERROR_VALUE (in + * implementation file) is not equal to your changed value. + * + * For most cases, if you just want to check if there was an + * error, you can check if the error_number == 0. But if + * you are thinking that you may want to redefine what the no-error + * value is in the future (say -99999), then you can use this + * constant name instead (e.g. error_number == TERROR_NOERROR_VALUE). + */ +#define TERROR_NOERROR_VALUE 0 + + +#ifndef DOXYGEN_SHOULD_IGNORE_THIS +/** @cond DOXYGEN_SHOULD_IGNORE_THIS */ + +/* Note: For Doxygen to produce clean output, you should set the + * PREDEFINED option to remove TERROR_TERROR_DECLSPEC, TERROR_CALL, and + * the DOXYGEN_SHOULD_IGNORE_THIS blocks. + * PREDEFINED = DOXYGEN_SHOULD_IGNORE_THIS=1 TERROR_TERROR_DECLSPEC= TERROR_CALL= + */ + +/** Windows needs to know explicitly which functions to export in a DLL. */ +#if defined(_WIN32) + #if defined(TERROR_BUILD_LIBRARY) + #define TERROR_DECLSPEC __declspec(dllexport) + #else + #define TERROR_DECLSPEC __declspec(dllimport) + #endif +#else + #if defined(ALMIXER_BUILD_LIBRARY) + #if defined (__GNUC__) && __GNUC__ >= 4 + #define TERROR_DECLSPEC __attribute__((visibility("default"))) + #else + #define TERROR_DECLSPEC + #endif + #else + #define TERROR_DECLSPEC + #endif +#endif + +/* For Windows, by default, use the C calling convention */ +#if defined(_WIN32) + #define TERROR_CALL __cdecl +#else + #define TERROR_CALL +#endif + + +/* Version number is set here. + * Printable format: "%d.%d.%d", MAJOR, MINOR, PATCHLEVEL + */ +#define TERROR_MAJOR_VERSION 0 +#define TERROR_MINOR_VERSION 1 +#define TERROR_PATCH_VERSION 0 + +/** @endcond DOXYGEN_SHOULD_IGNORE_THIS */ +#endif /* DOXYGEN_SHOULD_IGNORE_THIS */ + + +/** + * Struct that contains the version information of this library. + * This represents the library's version as three levels: major revision + * (increments with massive changes, additions, and enhancements), + * minor revision (increments with backwards-compatible changes to the + * major revision), and patchlevel (increments with fixes to the minor + * revision). + * @see TERROR_GET_COMPILED_VERSION, TError_GetLinkedVersion + */ +typedef struct +{ + int major; /**< major revision. */ + int minor; /**< minor revision. */ + int patch; /**< patch revision. */ +} TErrorVersion; + + +/** + * Struct that contains all the data needed to represent an error (pool) + * instance. This is the object you pass around to all instance + * based error functions. Don't touch the data in the struct directly. + * This should be considered an opaque data type. + */ +typedef struct +{ + void* mutexLock; /**< You probably shouldn't touch this either. */ + void* opaqueData; /**< Don't touch this. */ +} TErrorPool; + +/** + * Struct that contains both the error number and error string. + * This is returned by the TError_GetError functions which + * allows you to fetch both the errorNumber and errorString. + * (Other API functions only let you get one or the other). + * You may read the values directly, but do not modify the string. + * Keep in mind that once a SetError is called again for your thread, + * the pointer may become invalid. Copy this data out if you need + * to keep it. + */ +typedef struct +{ + int errorNumber; /**< The error number. */ + const char* errorString; /**< The error string (read-only). */ +} TErrorStatus; + + +/** + * This macro fills in a TError_Version structure with the version of the + * library you compiled against. This is determined by what header the + * compiler uses. Note that if you dynamically linked the library, you might + * have a slightly newer or older version at runtime. That version can be + * determined with TError_GetLinkedVersion(), which, unlike + * TERROR_GET_COMPILED_VERSION, is not a macro. + * + * @param X A pointer to a TError_Version struct to initialize. + * + * @see TErrorVersion, TError_GetLinkedVersion + */ +#define TERROR_GET_COMPILED_VERSION(X) \ +{ \ + if(NULL != (X)) \ + { \ + (X)->major = TERROR_MAJOR_VERSION; \ + (X)->minor = TERROR_MINOR_VERSION; \ + (X)->patch = TERROR_PATCH_VERSION; \ + } \ +} + +/** + * Gets the library version of tErrorLib you are using. + * This gets the version of tErrorLib that is linked against your program. + * If you are using a shared library (DLL) version of tError, then it is + * possible that it will be different than the version you compiled against. + * + * This is a real function; the macro TERROR_GET_COMPILED_VERSION + * tells you what version of tErrorLib you compiled against: + * + * @code + * TErrorVersion compiled; + * TErrorVersion linked; + * + * TERROR_GET_COMPILED_VERSION(&compiled); + * TError_GetLinkedVersion(&linked); + * printf("We compiled against tError version %d.%d.%d ...\n", + * compiled.major, compiled.minor, compiled.patch); + * printf("But we linked against tError version %d.%d.%d.\n", + * linked.major, linked.minor, linked.patch); + * @endcode + * + * @see TErrorVersion, TERROR_GET_COMPILED_VERSION + */ +extern TERROR_DECLSPEC void TERROR_CALL TError_GetLinkedVersion(TErrorVersion* ver); + +/** + * This creates a new error pool instance. + * An error pool is a self-contained object that holds its own + * errors. You may have multiple error pools to isolate errors for + * different subsystems if you choose. + * + * For most (if not all) other tErrorLib functions, you will + * pass this instance to each function when you call them. + * + * @return Returns a pointer to an error pool which is the + * instance variable (if successful) or NULL on failure. + * + * @see TError_FreeErrorPool + */ +extern TERROR_DECLSPEC TErrorPool* TERROR_CALL TError_CreateErrorPool(void); + +/** + * This frees an error pool instance. + * This properly frees the memory of an error pool instance created by + * CreateErrorPool. Whenever you create a TErrorPool + * instance, you should always remember to balance it with a + * FreeErrorPool() call. + * + * @param err_pool A pointer to the error pool instance you want to free. + * + * @see TError_CreateErrorPool + */ +extern TERROR_DECLSPEC void TERROR_CALL TError_FreeErrorPool(TErrorPool* err_pool); + +/** + * This function will delete the error message memory that has been + * allocated for the thread you call this function in. + * This reflects a deficiency in this library's design. + * If a thread terminates, this library will still have + * allocated memory for it if it had set an error. + * If you plan on killing a thread, call this function in that + * thread before it dies so the memory can be freed. + * Do not call SetError* again in this thread or memory + * will be reallocated. + * It is safe to call if no memory has actually be allocated for the + * error for the current thread. + * If you cannot use this function to free the memory for some reason, + * this is a pseudo-memory-leak. By pseudo, I mean that you won't + * completely leak. When you delete the entire memory pool, it will + * be able to free all error message structures that were associated + * with the pool so you can recover this memory. + * + * @param err_pool The error pool instance you want to use. + */ +extern TERROR_DECLSPEC void TError_DeleteEntryOnCurrentThread(TErrorPool* err_pool); + + +/** + * This function sets an error. + * Calling this function will set an error (for this thread) + * with the specified error number and error string which + * can later be retrieved by a GetError call. + * The function usage is similar to printf. + * If both the err_num is set to 0 (see other notes TERROR_NOERROR_VALUE) + * and the err_str is set to NULL, then this is considered clearing an + * error and no error will be marked. + * + * @param err_pool The error pool instance you want to use. + * + * @param err_num The error number you want to use for this error. + * The value for this number is up to you, based on your own + * conventions. But this library reserves one number to denote "no error". + * This number for initial implementation and documentation purposes is 0, + * and is defined in the implementation as TERROR_NOERROR_VALUE. + * You can still specify this number to be anything you choose + * if and only if you also always specify an error string that is not NULL. + * As long as an error string exists, the system will recognize there + * is an error. But if you have a NULL string and use 0 to denote an + * error, this will be interpreted as a clear error message. + * So I recommend you treat 0 as a no-error value and avoid using it + * as an error value in your code to avoid confusion. + * + * @param err_str This is where your error text goes. It is compatible with + * printf style format strings. There is no imposed limit on how long + * the strings can be if you are using the vasprintf backend. The + * vsnprintf implemention will automatically truncate the string if it + * is too long. + * You if don't wish to specify an error string, you may enter NULL here. + * But remember to note the effect if this string is NULL and the err_num is + * set to 0 (TERROR_NOERROR_VALUE). + * + * @param ... This is the variable argument list used to accomodate + * printf style format strings. + * + * @see TError_SetErrorv, TError_SetErrorNoFormat, TError_SetErrorNum, + * TError_SetErrorStr, TError_SetErrorStrv, TError_SetErrorStrNoFormat, + * TERROR_NOERROR_VALUE + */ +extern TERROR_DECLSPEC void TERROR_CALL TError_SetError(TErrorPool* err_pool, int err_num, const char* err_str, ...); + + +/** + * This function sets an error. + * This is the va_list version of TError_SetError. + * The same rules apply to this as with TError_SetError. + * + * @see TError_SetError + */ +extern TERROR_DECLSPEC void TERROR_CALL TError_SetErrorv(TErrorPool* err_pool, int err_num, const char* err_str, va_list argp); + +/** + * This is a "No Format Strings Allowed" version of SetError. + * This version of SetError disallows the use of format strings. + * This was written if you needed to expose the SetError function to + * an untrusted source because it is quite easy to crash a system (or worse) + * with an invalid format string. An untrusted source might include + * arguments passed through the command line, any user input that + * gets fed to Logger, or strings taken from runtime generated sources + * like scripts. + * I was probably being overly paranoid when I created this function, + * and there may be ways to achive this level of safety without + * this function, but here it is anyway. + * In addition, if your compiler has problems with both vasprintf and + * vsnprintf, (you will have to modify some source code) you might + * consider basing all SetError functions around this function because + * since the strings are known length because there is no argument + * expansion. + * + * @see TError_SetError + */ +extern TERROR_DECLSPEC void TERROR_CALL TError_SetErrorNoFormat(TErrorPool* err_pool, int err_num, const char* err_str); + +/** + * This function sets an error. + * This version only lets you set the error number. The backend + * will automatically set the error string to NULL. + * This API call is intended to be used only if you don't plan on using + * any error strings in your code. + * Also be aware of the notes about TERROR_NOERROR_VALUE. + * + * @see TError_SetError, TERROR_NOERROR_VALUE + */ +extern TERROR_DECLSPEC void TERROR_CALL TError_SetErrorNum(TErrorPool* err_pool, int err_num); + +/** + * This function sets an error. + * This version only lets you set the error string. The backend + * will automatically set the error number to TERROR_NOERROR_VALUE + * which is currently implemented as 0. + * This API call is intended to be used only if you don't plan on using + * any error numbers in your code. + * Also be aware of the notes about TERROR_NOERROR_VALUE if you use NULL + * strings. + * + * @see TError_SetError, TERROR_NOERROR_VALUE + */ +extern TERROR_DECLSPEC void TERROR_CALL TError_SetErrorStr(TErrorPool* err_pool, const char* err_str, ...); + +/** + * This function sets an error. + * This is the va_list version of TError_SetErrorStr. + * + * @see TError_SetError, TError_SetErrorStr, TERROR_NOERROR_VALUE + */ +extern TERROR_DECLSPEC void TERROR_CALL TError_SetErrorStrv(TErrorPool* err_pool, const char* err_str, va_list argp); + +/** + * This function sets an error. + * This is the "No Format Strings Allowed" version of TError_SetErrorStr. + * + * @see TError_SetError, TError_SetErrorNoFormat, + * TError_SetErrorStr, TERROR_NOERROR_VALUE + */ +extern TERROR_DECLSPEC void TERROR_CALL TError_SetErrorStrNoFormat(TErrorPool* err_pool, const char* err_str); + +/** + * This function gets the last error to be set by one of the SetError + * functions (from within your current thread). This version of the function + * returns just the error number. + * After this function is called, the error will be cleared, so your + * next call to a GetError function (with no SetError calls in between) + * will return a struct set with no-error values. + * + * @param err_pool The error pool instance you want to use. + * + * @return Returns the error number. + * If no error was set, the error number will be set + * to 0 (TERROR_NOERROR_VALUE). + * + * @see TError_GetErrorOnCurrentThread, TError_GetErrorStrOnCurrentThread, TERROR_NOERROR_VALUE + */ +extern TERROR_DECLSPEC int TERROR_CALL TError_GetErrorNumOnCurrentThread(TErrorPool* err_pool); + +/** + * This function gets the last error to be set by one of the SetError + * functions (from within your current thread). This version of the function + * returns a pointer to the error string. + * After this function is called, the error will be cleared, so your + * next call to a GetError function (with no SetError calls in between) + * will return a struct set with no-error values. + * + * @param err_pool The error pool instance you want to use. + * + * @return Returns the pointer to the error string. + * The pointer is to tErrorLib's own copy + * of the error message so you should not modify this string. Furthermore, + * the pointer may become invalid at the next call to SetError within this + * same thread, so if you need to keep the string, you must make your + * own copy of it. + * If no error was set, the error string will be set to NULL. + * + * @see TError_GetErrorNumOnCurrentThread, TError_GetErrorOnCurrentThread, TERROR_NOERROR_VALUE + */ +extern TERROR_DECLSPEC const char* TERROR_CALL TError_GetErrorStrOnCurrentThread(TErrorPool* err_pool); + +/** + * This function gets the last error to be set by one of the SetError + * functions (from within your current thread). This version of the function + * returns a struct containing the error number and a pointer to the + * error string. + * After this function is called, the error will be cleared, so your + * next call to a GetError function (with no SetError calls in between) + * will return a struct set with no-error values. + * + * @param err_pool The error pool instance you want to use. + * + * @return Returns (by-value) a struct containing the error number and + * pointer to the error string. The pointer is to tErrorLib's own copy + * of the error message so you should not modify this string. Furthermore, + * the pointer may become invalid at the next call to SetError within this + * same thread, so if you need to keep the string, you must make your + * own copy of it. + * If no error was set, the error number will be set to 0 (TERROR_NOERROR_VALUE) + * and the error string will be set to NULL. + * + * @see TError_GetErrorNumOnCurrentThread, TError_GetErrorStrOnCurrentThread, TERROR_NOERROR_VALUE + */ +extern TERROR_DECLSPEC TErrorStatus TERROR_CALL TError_GetErrorOnCurrentThread(TErrorPool* err_pool); + + +/** + * This function gets the last error to be set by one of the SetError + * functions (regardless of thread). + * This version of the function + * returns just the error number. + * After this function is called, the error will be cleared, so your + * next call to a GetError function (with no SetError calls in between) + * will return a struct set with no-error values. + * + * @param err_pool The error pool instance you want to use. + * + * @return Returns the error number. + * If no error was set, the error number will be set + * to 0 (TERROR_NOERROR_VALUE). + * + * @see TError_GetLastError, TError_GetLastErrorStr, TERROR_NOERROR_VALUE + */ +extern TERROR_DECLSPEC int TERROR_CALL TError_GetLastErrorNum(TErrorPool* err_pool); + +/** + * This function gets the last error to be set by one of the SetError + * functions (regardless of thread). + * This version of the function + * returns a pointer to the error string. + * After this function is called, the error will be cleared, so your + * next call to a GetError function (with no SetError calls in between) + * will return a struct set with no-error values. + * + * @param err_pool The error pool instance you want to use. + * + * @return Returns the pointer to the error string. + * The pointer is to tErrorLib's own copy + * of the error message so you should not modify this string. Furthermore, + * the pointer may become invalid at the next call to SetError within this + * same thread, so if you need to keep the string, you must make your + * own copy of it. + * If no error was set, the error string will be set to NULL. + * + * @see TError_GetLastErrorNum, TError_GetLastError, TERROR_NOERROR_VALUE + */ +extern TERROR_DECLSPEC const char* TERROR_CALL TError_GetLastErrorStr(TErrorPool* err_pool); + +/** + * This function gets the last error to be set by one of the SetError + * functions (regardless of thread). + * This version of the function + * returns a struct containing the error number and a pointer to the + * error string. + * After this function is called, the error will be cleared, so your + * next call to a GetError function (with no SetError calls in between) + * will return a struct set with no-error values. + * + * @param err_pool The error pool instance you want to use. + * + * @return Returns (by-value) a struct containing the error number and + * pointer to the error string. The pointer is to tErrorLib's own copy + * of the error message so you should not modify this string. Furthermore, + * the pointer may become invalid at the next call to SetError within this + * same thread, so if you need to keep the string, you must make your + * own copy of it. + * If no error was set, the error number will be set to 0 (TERROR_NOERROR_VALUE) + * and the error string will be set to NULL. + * + * @see TError_GetLastErrorNum, TError_GetLastErrorStr, TERROR_NOERROR_VALUE + */ +extern TERROR_DECLSPEC TErrorStatus TERROR_CALL TError_GetLastError(TErrorPool* err_pool); + + +#ifdef __cplusplus +} +#endif + + +#endif /* TERRORLIB_H */ +