Audio SDL plugin. Compile and run on the OpenPandora
[mupen64plus-pandora.git] / source / mupen64plus-audio-sdl / src / main.c
diff --git a/source/mupen64plus-audio-sdl/src/main.c b/source/mupen64plus-audio-sdl/src/main.c
new file mode 100755 (executable)
index 0000000..e80517e
--- /dev/null
@@ -0,0 +1,1008 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ *   Mupen64plus-sdl-audio - main.c                                        *
+ *   Mupen64Plus homepage: http://code.google.com/p/mupen64plus/           *
+ *   Copyright (C) 2007-2009 Richard Goedeken                              *
+ *   Copyright (C) 2007-2008 Ebenblues                                     *
+ *   Copyright (C) 2003 JttL                                               *
+ *   Copyright (C) 2002 Hacktarux                                          *
+ *                                                                         *
+ *   This program is free software; you can redistribute it and/or modify  *
+ *   it under the terms of the GNU General Public License as published by  *
+ *   the Free Software Foundation; either version 2 of the License, or     *
+ *   (at your option) any later version.                                   *
+ *                                                                         *
+ *   This program is distributed in the hope that it will be useful,       *
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
+ *   GNU General Public License for more details.                          *
+ *                                                                         *
+ *   You should have received a copy of the GNU General Public License     *
+ *   along with this program; if not, write to the                         *
+ *   Free Software Foundation, Inc.,                                       *
+ *   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.          *
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <SDL.h>
+#include <SDL_audio.h>
+
+#ifdef USE_SRC
+#include <samplerate.h>
+#endif
+#ifdef USE_SPEEX
+#include <speex/speex_resampler.h>
+#endif
+
+#define M64P_PLUGIN_PROTOTYPES 1
+#include "m64p_types.h"
+#include "m64p_plugin.h"
+#include "m64p_common.h"
+#include "m64p_config.h"
+
+#include "main.h"
+#include "volume.h"
+#include "osal_dynamiclib.h"
+
+/* Default start-time size of primary buffer (in equivalent output samples).
+   This is the buffer where audio is loaded after it's extracted from n64's memory.
+   This value must be larger than PRIMARY_BUFFER_TARGET */
+#define PRIMARY_BUFFER_SIZE 16384
+
+/* this is the buffer fullness level (in equivalent output samples) which is targeted
+   for the primary audio buffer (by inserting delays) each time data is received from
+   the running N64 program.  This value must be larger than the SECONDARY_BUFFER_SIZE.
+   Decreasing this value will reduce audio latency but requires a faster PC to avoid
+   choppiness. Increasing this will increase audio latency but reduce the chance of
+   drop-outs. The default value 10240 gives a 232ms maximum A/V delay at 44.1khz */
+#define PRIMARY_BUFFER_TARGET 10240
+
+/* Size of secondary buffer, in output samples. This is the requested size of SDL's
+   hardware buffer, and the size of the mix buffer for doing SDL volume control. The
+   SDL documentation states that this should be a power of two between 512 and 8192. */
+#define SECONDARY_BUFFER_SIZE 8192
+/*SEB 2048 before*/
+
+/* This sets default frequency what is used if rom doesn't want to change it.
+   Probably only game that needs this is Zelda: Ocarina Of Time Master Quest 
+   *NOTICE* We should try to find out why Demos' frequencies are always wrong
+   They tend to rely on a default frequency, apparently, never the same one ;)*/
+#define DEFAULT_FREQUENCY 33600
+
+/* number of bytes per sample */
+#define N64_SAMPLE_BYTES 4
+#define SDL_SAMPLE_BYTES 4
+
+/* volume mixer types */
+#define VOLUME_TYPE_SDL     1
+#define VOLUME_TYPE_OSS     2
+
+/* local variables */
+static void (*l_DebugCallback)(void *, int, const char *) = NULL;
+static void *l_DebugCallContext = NULL;
+static int l_PluginInit = 0;
+static int l_PausedForSync = 1; /* Audio is started in paused state after SDL initialization */
+static m64p_handle l_ConfigAudio;
+
+enum resampler_type {
+       RESAMPLER_TRIVIAL,
+#ifdef USE_SRC
+       RESAMPLER_SRC,
+#endif
+#ifdef USE_SPEEX
+       RESAMPLER_SPEEX,
+#endif
+};
+
+/* Read header for type definition */
+static AUDIO_INFO AudioInfo;
+/* The hardware specifications we are using */
+static SDL_AudioSpec *hardware_spec;
+/* Pointer to the primary audio buffer */
+static unsigned char *primaryBuffer = NULL;
+static unsigned int primaryBufferBytes = 0;
+/* Pointer to the mixing buffer for voume control*/
+static unsigned char *mixBuffer = NULL;
+/* Position in buffer array where next audio chunk should be placed */
+static unsigned int buffer_pos = 0;
+/* Audio frequency, this is usually obtained from the game, but for compatibility we set default value */
+static int GameFreq = DEFAULT_FREQUENCY;
+/* timestamp for the last time that our audio callback was called */
+static unsigned int last_callback_ticks = 0;
+/* SpeedFactor is used to increase/decrease game playback speed */
+static unsigned int speed_factor = 100;
+// If this is true then left and right channels are swapped */
+static int SwapChannels = 0;
+// Size of Primary audio buffer in equivalent output samples
+static unsigned int PrimaryBufferSize = PRIMARY_BUFFER_SIZE;
+// Fullness level target for Primary audio buffer, in equivalent output samples
+static unsigned int PrimaryBufferTarget = PRIMARY_BUFFER_TARGET;
+// Size of Secondary audio buffer in output samples
+static unsigned int SecondaryBufferSize = SECONDARY_BUFFER_SIZE;
+// Resample type
+static enum resampler_type Resample = RESAMPLER_TRIVIAL;
+// Resampler specific quality
+static int ResampleQuality = 3;
+// volume to scale the audio by, range of 0..100
+// if muted, this holds the volume when not muted
+static int VolPercent = 80;
+// how much percent to increment/decrement volume by
+static int VolDelta = 5;
+// the actual volume passed into SDL, range of 0..SDL_MIX_MAXVOLUME
+static int VolSDL = SDL_MIX_MAXVOLUME;
+// Muted or not
+static int VolIsMuted = 0;
+//which type of volume control to use
+static int VolumeControlType = VOLUME_TYPE_SDL;
+
+static int OutputFreq;
+
+// Prototype of local functions
+static void my_audio_callback(void *userdata, unsigned char *stream, int len);
+static void InitializeAudio(int freq);
+static void ReadConfig(void);
+static void InitializeSDL(void);
+
+static int critical_failure = 0;
+
+/* definitions of pointers to Core config functions */
+ptr_ConfigOpenSection      ConfigOpenSection = NULL;
+ptr_ConfigDeleteSection    ConfigDeleteSection = NULL;
+ptr_ConfigSaveSection      ConfigSaveSection = NULL;
+ptr_ConfigSetParameter     ConfigSetParameter = NULL;
+ptr_ConfigGetParameter     ConfigGetParameter = NULL;
+ptr_ConfigGetParameterHelp ConfigGetParameterHelp = NULL;
+ptr_ConfigSetDefaultInt    ConfigSetDefaultInt = NULL;
+ptr_ConfigSetDefaultFloat  ConfigSetDefaultFloat = NULL;
+ptr_ConfigSetDefaultBool   ConfigSetDefaultBool = NULL;
+ptr_ConfigSetDefaultString ConfigSetDefaultString = NULL;
+ptr_ConfigGetParamInt      ConfigGetParamInt = NULL;
+ptr_ConfigGetParamFloat    ConfigGetParamFloat = NULL;
+ptr_ConfigGetParamBool     ConfigGetParamBool = NULL;
+ptr_ConfigGetParamString   ConfigGetParamString = NULL;
+
+/* Global functions */
+static void DebugMessage(int level, const char *message, ...)
+{
+  char msgbuf[1024];
+  va_list args;
+
+  if (l_DebugCallback == NULL)
+      return;
+
+  va_start(args, message);
+  vsprintf(msgbuf, message, args);
+
+  (*l_DebugCallback)(l_DebugCallContext, level, msgbuf);
+
+  va_end(args);
+}
+
+/* Mupen64Plus plugin functions */
+EXPORT m64p_error CALL PluginStartup(m64p_dynlib_handle CoreLibHandle, void *Context,
+                                   void (*DebugCallback)(void *, int, const char *))
+{
+    ptr_CoreGetAPIVersions CoreAPIVersionFunc;
+    
+    int ConfigAPIVersion, DebugAPIVersion, VidextAPIVersion, bSaveConfig;
+    float fConfigParamsVersion = 0.0f;
+    
+    if (l_PluginInit)
+        return M64ERR_ALREADY_INIT;
+
+    /* first thing is to set the callback function for debug info */
+    l_DebugCallback = DebugCallback;
+    l_DebugCallContext = Context;
+
+    /* attach and call the CoreGetAPIVersions function, check Config API version for compatibility */
+    CoreAPIVersionFunc = (ptr_CoreGetAPIVersions) osal_dynlib_getproc(CoreLibHandle, "CoreGetAPIVersions");
+    if (CoreAPIVersionFunc == NULL)
+    {
+        DebugMessage(M64MSG_ERROR, "Core emulator broken; no CoreAPIVersionFunc() function found.");
+        return M64ERR_INCOMPATIBLE;
+    }
+    
+    (*CoreAPIVersionFunc)(&ConfigAPIVersion, &DebugAPIVersion, &VidextAPIVersion, NULL);
+    if ((ConfigAPIVersion & 0xffff0000) != (CONFIG_API_VERSION & 0xffff0000))
+    {
+        DebugMessage(M64MSG_ERROR, "Emulator core Config API (v%i.%i.%i) incompatible with plugin (v%i.%i.%i)",
+                VERSION_PRINTF_SPLIT(ConfigAPIVersion), VERSION_PRINTF_SPLIT(CONFIG_API_VERSION));
+        return M64ERR_INCOMPATIBLE;
+    }
+
+    /* Get the core config function pointers from the library handle */
+    ConfigOpenSection = (ptr_ConfigOpenSection) osal_dynlib_getproc(CoreLibHandle, "ConfigOpenSection");
+    ConfigDeleteSection = (ptr_ConfigDeleteSection) osal_dynlib_getproc(CoreLibHandle, "ConfigDeleteSection");
+    ConfigSaveSection = (ptr_ConfigSaveSection) osal_dynlib_getproc(CoreLibHandle, "ConfigSaveSection");
+    ConfigSetParameter = (ptr_ConfigSetParameter) osal_dynlib_getproc(CoreLibHandle, "ConfigSetParameter");
+    ConfigGetParameter = (ptr_ConfigGetParameter) osal_dynlib_getproc(CoreLibHandle, "ConfigGetParameter");
+    ConfigSetDefaultInt = (ptr_ConfigSetDefaultInt) osal_dynlib_getproc(CoreLibHandle, "ConfigSetDefaultInt");
+    ConfigSetDefaultFloat = (ptr_ConfigSetDefaultFloat) osal_dynlib_getproc(CoreLibHandle, "ConfigSetDefaultFloat");
+    ConfigSetDefaultBool = (ptr_ConfigSetDefaultBool) osal_dynlib_getproc(CoreLibHandle, "ConfigSetDefaultBool");
+    ConfigSetDefaultString = (ptr_ConfigSetDefaultString) osal_dynlib_getproc(CoreLibHandle, "ConfigSetDefaultString");
+    ConfigGetParamInt = (ptr_ConfigGetParamInt) osal_dynlib_getproc(CoreLibHandle, "ConfigGetParamInt");
+    ConfigGetParamFloat = (ptr_ConfigGetParamFloat) osal_dynlib_getproc(CoreLibHandle, "ConfigGetParamFloat");
+    ConfigGetParamBool = (ptr_ConfigGetParamBool) osal_dynlib_getproc(CoreLibHandle, "ConfigGetParamBool");
+    ConfigGetParamString = (ptr_ConfigGetParamString) osal_dynlib_getproc(CoreLibHandle, "ConfigGetParamString");
+
+    if (!ConfigOpenSection || !ConfigDeleteSection || !ConfigSetParameter || !ConfigGetParameter ||
+        !ConfigSetDefaultInt || !ConfigSetDefaultFloat || !ConfigSetDefaultBool || !ConfigSetDefaultString ||
+        !ConfigGetParamInt   || !ConfigGetParamFloat   || !ConfigGetParamBool   || !ConfigGetParamString)
+        return M64ERR_INCOMPATIBLE;
+
+    /* ConfigSaveSection was added in Config API v2.1.0 */
+    if (ConfigAPIVersion >= 0x020100 && !ConfigSaveSection)
+        return M64ERR_INCOMPATIBLE;
+
+    /* get a configuration section handle */
+    if (ConfigOpenSection("Audio-SDL", &l_ConfigAudio) != M64ERR_SUCCESS)
+    {
+        DebugMessage(M64MSG_ERROR, "Couldn't open config section 'Audio-SDL'");
+        return M64ERR_INPUT_NOT_FOUND;
+    }
+
+    /* check the section version number */
+    bSaveConfig = 0;
+    if (ConfigGetParameter(l_ConfigAudio, "Version", M64TYPE_FLOAT, &fConfigParamsVersion, sizeof(float)) != M64ERR_SUCCESS)
+    {
+        DebugMessage(M64MSG_WARNING, "No version number in 'Audio-SDL' config section. Setting defaults.");
+        ConfigDeleteSection("Audio-SDL");
+        ConfigOpenSection("Audio-SDL", &l_ConfigAudio);
+        bSaveConfig = 1;
+    }
+    else if (((int) fConfigParamsVersion) != ((int) CONFIG_PARAM_VERSION))
+    {
+        DebugMessage(M64MSG_WARNING, "Incompatible version %.2f in 'Audio-SDL' config section: current is %.2f. Setting defaults.", fConfigParamsVersion, (float) CONFIG_PARAM_VERSION);
+        ConfigDeleteSection("Audio-SDL");
+        ConfigOpenSection("Audio-SDL", &l_ConfigAudio);
+        bSaveConfig = 1;
+    }
+    else if ((CONFIG_PARAM_VERSION - fConfigParamsVersion) >= 0.0001f)
+    {
+        /* handle upgrades */
+        float fVersion = CONFIG_PARAM_VERSION;
+        ConfigSetParameter(l_ConfigAudio, "Version", M64TYPE_FLOAT, &fVersion);
+        DebugMessage(M64MSG_INFO, "Updating parameter set version in 'Audio-SDL' config section to %.2f", fVersion);
+        bSaveConfig = 1;
+    }
+
+    /* set the default values for this plugin */
+    ConfigSetDefaultFloat(l_ConfigAudio, "Version",             CONFIG_PARAM_VERSION,  "Mupen64Plus SDL Audio Plugin config parameter version number");
+    ConfigSetDefaultInt(l_ConfigAudio, "DEFAULT_FREQUENCY",     DEFAULT_FREQUENCY,     "Frequency which is used if rom doesn't want to change it");
+    ConfigSetDefaultBool(l_ConfigAudio, "SWAP_CHANNELS",        0,                     "Swaps left and right channels");
+    ConfigSetDefaultInt(l_ConfigAudio, "PRIMARY_BUFFER_SIZE",   PRIMARY_BUFFER_SIZE,   "Size of primary buffer in output samples. This is where audio is loaded after it's extracted from n64's memory.");
+    ConfigSetDefaultInt(l_ConfigAudio, "PRIMARY_BUFFER_TARGET", PRIMARY_BUFFER_TARGET, "Fullness level target for Primary audio buffer, in equivalent output samples");
+    ConfigSetDefaultInt(l_ConfigAudio, "SECONDARY_BUFFER_SIZE", SECONDARY_BUFFER_SIZE, "Size of secondary buffer in output samples. This is SDL's hardware buffer.");
+    ConfigSetDefaultString(l_ConfigAudio, "RESAMPLE",              "trivial",                     "Audio resampling algorithm. src-sinc-best-quality, src-sinc-medium-quality, src-sinc-fastest, src-zero-order-hold, src-linear, speex-fixed-{10-0}, trivial");
+    ConfigSetDefaultInt(l_ConfigAudio, "VOLUME_CONTROL_TYPE",   VOLUME_TYPE_SDL,       "Volume control type: 1 = SDL (only affects Mupen64Plus output)  2 = OSS mixer (adjusts master PC volume)");
+    ConfigSetDefaultInt(l_ConfigAudio, "VOLUME_ADJUST",         5,                     "Percentage change each time the volume is increased or decreased");
+    ConfigSetDefaultInt(l_ConfigAudio, "VOLUME_DEFAULT",        80,                    "Default volume when a game is started.  Only used if VOLUME_CONTROL_TYPE is 1");
+
+    if (bSaveConfig && ConfigAPIVersion >= 0x020100)
+        ConfigSaveSection("Audio-SDL");
+
+    l_PluginInit = 1;
+    return M64ERR_SUCCESS;
+}
+
+EXPORT m64p_error CALL PluginShutdown(void)
+{
+    if (!l_PluginInit)
+        return M64ERR_NOT_INIT;
+
+    /* reset some local variables */
+    l_DebugCallback = NULL;
+    l_DebugCallContext = NULL;
+
+    /* make sure our buffer is freed */
+    if (mixBuffer != NULL)
+    {
+        free(mixBuffer);
+        mixBuffer = NULL;
+    }
+
+    l_PluginInit = 0;
+    return M64ERR_SUCCESS;
+}
+
+EXPORT m64p_error CALL PluginGetVersion(m64p_plugin_type *PluginType, int *PluginVersion, int *APIVersion, const char **PluginNamePtr, int *Capabilities)
+{
+    /* set version info */
+    if (PluginType != NULL)
+        *PluginType = M64PLUGIN_AUDIO;
+
+    if (PluginVersion != NULL)
+        *PluginVersion = SDL_AUDIO_PLUGIN_VERSION;
+
+    if (APIVersion != NULL)
+        *APIVersion = AUDIO_PLUGIN_API_VERSION;
+    
+    if (PluginNamePtr != NULL)
+        *PluginNamePtr = "Mupen64Plus SDL Audio Plugin";
+
+    if (Capabilities != NULL)
+    {
+        *Capabilities = 0;
+    }
+                    
+    return M64ERR_SUCCESS;
+}
+
+/* ----------- Audio Functions ------------- */
+EXPORT void CALL AiDacrateChanged( int SystemType )
+{
+    int f = GameFreq;
+
+    if (!l_PluginInit)
+        return;
+
+    switch (SystemType)
+    {
+        case SYSTEM_NTSC:
+            f = 48681812 / (*AudioInfo.AI_DACRATE_REG + 1);
+            break;
+        case SYSTEM_PAL:
+            f = 49656530 / (*AudioInfo.AI_DACRATE_REG + 1);
+            break;
+        case SYSTEM_MPAL:
+            f = 48628316 / (*AudioInfo.AI_DACRATE_REG + 1);
+            break;
+    }
+    InitializeAudio(f);
+}
+
+
+EXPORT void CALL AiLenChanged( void )
+{
+    unsigned int LenReg;
+    unsigned char *p;
+    unsigned int CurrLevel, CurrTime, ExpectedLevel, ExpectedTime;
+
+    if (critical_failure == 1)
+        return;
+    if (!l_PluginInit)
+        return;
+
+    LenReg = *AudioInfo.AI_LEN_REG;
+    p = AudioInfo.RDRAM + (*AudioInfo.AI_DRAM_ADDR_REG & 0xFFFFFF);
+
+    if (buffer_pos + LenReg < primaryBufferBytes)
+    {
+        unsigned int i;
+
+        SDL_LockAudio();
+        for ( i = 0 ; i < LenReg ; i += 4 )
+        {
+
+            if(SwapChannels == 0)
+            {
+                // Left channel
+                primaryBuffer[ buffer_pos + i ] = p[ i + 2 ];
+                primaryBuffer[ buffer_pos + i + 1 ] = p[ i + 3 ];
+
+                // Right channel
+                primaryBuffer[ buffer_pos + i + 2 ] = p[ i ];
+                primaryBuffer[ buffer_pos + i + 3 ] = p[ i + 1 ];
+            } else {
+                // Left channel
+                primaryBuffer[ buffer_pos + i ] = p[ i ];
+                primaryBuffer[ buffer_pos + i + 1 ] = p[ i + 1 ];
+
+                // Right channel
+                primaryBuffer[ buffer_pos + i + 2 ] = p[ i + 2];
+                primaryBuffer[ buffer_pos + i + 3 ] = p[ i + 3 ];
+            }
+        }
+        buffer_pos += i;
+        SDL_UnlockAudio();
+    }
+    else
+    {
+        DebugMessage(M64MSG_WARNING, "AiLenChanged(): Audio buffer overflow.");
+    }
+
+    /* Now we need to handle synchronization, by inserting time delay to keep the emulator running at the correct speed */
+    /* Start by calculating the current Primary buffer fullness in terms of output samples */
+    CurrLevel = (unsigned int) (((long long) (buffer_pos/N64_SAMPLE_BYTES) * OutputFreq * 100) / (GameFreq * speed_factor));
+    /* Next, extrapolate to the buffer level at the expected time of the next audio callback, assuming that the
+       buffer is filled at the same rate as the output frequency */
+    CurrTime = SDL_GetTicks();
+    ExpectedTime = last_callback_ticks + ((1000 * SecondaryBufferSize) / OutputFreq);
+    ExpectedLevel = CurrLevel;
+    if (CurrTime < ExpectedTime)
+        ExpectedLevel += (ExpectedTime - CurrTime) * OutputFreq / 1000;
+    /* If the expected value of the Primary Buffer Fullness at the time of the next audio callback is more than 10
+       milliseconds ahead of our target buffer fullness level, then insert a delay now */
+    DebugMessage(M64MSG_VERBOSE, "%03i New audio bytes: %i  Time to next callback: %i  Current/Expected buffer level: %i/%i",
+                 CurrTime % 1000, LenReg, (int) (ExpectedTime - CurrTime), CurrLevel, ExpectedLevel);
+    if (ExpectedLevel >= PrimaryBufferTarget + OutputFreq / 100)
+    {
+        unsigned int WaitTime = (ExpectedLevel - PrimaryBufferTarget) * 1000 / OutputFreq;
+        DebugMessage(M64MSG_VERBOSE, "    AiLenChanged(): Waiting %ims", WaitTime);
+        if (l_PausedForSync)
+            SDL_PauseAudio(0);
+        l_PausedForSync = 0;
+        SDL_Delay(WaitTime);
+    }
+    /* Or if the expected level of the primary buffer is less than the secondary buffer size
+       (ie, predicting an underflow), then pause the audio to let the emulator catch up to speed */
+    else if (ExpectedLevel < SecondaryBufferSize)
+    {
+        DebugMessage(M64MSG_VERBOSE, "    AiLenChanged(): Possible underflow at next audio callback; pausing playback");
+        if (!l_PausedForSync)
+            SDL_PauseAudio(1);
+        l_PausedForSync = 1;
+    }
+    /* otherwise the predicted buffer level is within our tolerance, so everything is okay */
+    else
+    {
+        if (l_PausedForSync)
+            SDL_PauseAudio(0);
+        l_PausedForSync = 0;
+    }
+}
+
+EXPORT int CALL InitiateAudio( AUDIO_INFO Audio_Info )
+{
+    if (!l_PluginInit)
+        return 0;
+
+    AudioInfo = Audio_Info;
+    return 1;
+}
+
+static int underrun_count = 0;
+
+#ifdef USE_SRC
+static float *_src = NULL;
+static unsigned int _src_len = 0;
+static float *_dest = NULL;
+static unsigned int _dest_len = 0;
+static int error;
+static SRC_STATE *src_state;
+static SRC_DATA src_data;
+#endif
+#ifdef USE_SPEEX
+SpeexResamplerState* spx_state = NULL;
+static int error;
+#endif
+
+static int resample(unsigned char *input, int input_avail, int oldsamplerate, unsigned char *output, int output_needed, int newsamplerate)
+{
+    int *psrc = (int*)input;
+    int *pdest = (int*)output;
+    int i = 0, j = 0;
+
+#ifdef USE_SPEEX
+    spx_uint32_t in_len, out_len;
+    if(Resample == RESAMPLER_SPEEX)
+    {
+        if(spx_state == NULL)
+        {
+            spx_state = speex_resampler_init(2, oldsamplerate, newsamplerate, ResampleQuality,  &error);
+            if(spx_state == NULL)
+            {
+                memset(output, 0, output_needed);
+                return 0;
+            }
+        }
+        speex_resampler_set_rate(spx_state, oldsamplerate, newsamplerate);
+        in_len = input_avail / 4;
+        out_len = output_needed / 4;
+
+        if ((error = speex_resampler_process_interleaved_int(spx_state, (const spx_int16_t *)input, &in_len, (spx_int16_t *)output, &out_len)))
+        {
+            memset(output, 0, output_needed);
+            return input_avail;  // number of bytes consumed
+        }
+        return in_len * 4;
+    }
+#endif
+#ifdef USE_SRC
+    if(Resample == RESAMPLER_SRC)
+    {
+        // the high quality resampler needs more input than the samplerate ratio would indicate to work properly
+        if (input_avail > output_needed * 3 / 2)
+            input_avail = output_needed * 3 / 2; // just to avoid too much short-float-short conversion time
+        if (_src_len < input_avail*2 && input_avail > 0)
+        {
+            if(_src) free(_src);
+            _src_len = input_avail*2;
+            _src = malloc(_src_len);
+        }
+        if (_dest_len < output_needed*2 && output_needed > 0)
+        {
+            if(_dest) free(_dest);
+            _dest_len = output_needed*2;
+            _dest = malloc(_dest_len);
+        }
+        memset(_src,0,_src_len);
+        memset(_dest,0,_dest_len);
+        if(src_state == NULL)
+        {
+            src_state = src_new (ResampleQuality, 2, &error);
+            if(src_state == NULL)
+            {
+                memset(output, 0, output_needed);
+                return 0;
+            }
+        }
+        src_short_to_float_array ((short *) input, _src, input_avail/2);
+        src_data.end_of_input = 0;
+        src_data.data_in = _src;
+        src_data.input_frames = input_avail/4;
+        src_data.src_ratio = (float) newsamplerate / oldsamplerate;
+        src_data.data_out = _dest;
+        src_data.output_frames = output_needed/4;
+        if ((error = src_process (src_state, &src_data)))
+        {
+            memset(output, 0, output_needed);
+            return input_avail;  // number of bytes consumed
+        }
+        src_float_to_short_array (_dest, (short *) output, output_needed/2);
+        return src_data.input_frames_used * 4;
+    }
+#endif
+    // RESAMPLE == TRIVIAL
+    if (newsamplerate >= oldsamplerate)
+    {
+        int sldf = oldsamplerate;
+        int const2 = 2*sldf;
+        int dldf = newsamplerate;
+        int const1 = const2 - 2*dldf;
+        int criteria = const2 - dldf;
+        for (i = 0; i < output_needed/4; i++)
+        {
+            pdest[i] = psrc[j];
+            if(criteria >= 0)
+            {
+                ++j;
+                criteria += const1;
+            }
+            else criteria += const2;
+        }
+        return j * 4; //number of bytes consumed
+    }
+    // newsamplerate < oldsamplerate, this only happens when speed_factor > 1
+    for (i = 0; i < output_needed/4; i++)
+    {
+        j = i * oldsamplerate / newsamplerate;
+        pdest[i] = psrc[j];
+    }
+    return j * 4; //number of bytes consumed
+}
+
+static void my_audio_callback(void *userdata, unsigned char *stream, int len)
+{
+    int oldsamplerate, newsamplerate;
+
+    if (!l_PluginInit)
+        return;
+
+    /* mark the time, for synchronization on the input side */
+    last_callback_ticks = SDL_GetTicks();
+
+    newsamplerate = OutputFreq * 100 / speed_factor;
+    oldsamplerate = GameFreq;
+
+    if (buffer_pos > (unsigned int) (len * oldsamplerate) / newsamplerate)
+    {
+        int input_used;
+#if defined(HAS_OSS_SUPPORT)
+        if (VolumeControlType == VOLUME_TYPE_OSS)
+        {
+            input_used = resample(primaryBuffer, buffer_pos, oldsamplerate, stream, len, newsamplerate);
+        }
+        else
+#endif
+        {
+            input_used = resample(primaryBuffer, buffer_pos, oldsamplerate, mixBuffer, len, newsamplerate);
+            memset(stream, 0, len);
+            SDL_MixAudio(stream, mixBuffer, len, VolSDL);
+        }
+        memmove(primaryBuffer, &primaryBuffer[input_used], buffer_pos - input_used);
+        buffer_pos -= input_used;
+        DebugMessage(M64MSG_VERBOSE, "%03i my_audio_callback: used %i samples",
+                     last_callback_ticks % 1000, len / SDL_SAMPLE_BYTES);
+    }
+    else
+    {
+        unsigned int SamplesNeeded = (len * oldsamplerate) / (newsamplerate * SDL_SAMPLE_BYTES);
+        unsigned int SamplesPresent = buffer_pos / N64_SAMPLE_BYTES;
+        underrun_count++;
+        DebugMessage(M64MSG_VERBOSE, "%03i Buffer underflow (%i).  %i samples present, %i needed",
+                     last_callback_ticks % 1000, underrun_count, SamplesPresent, SamplesNeeded);
+        memset(stream , 0, len);
+    }
+}
+EXPORT int CALL RomOpen(void)
+{
+    if (!l_PluginInit)
+        return 0;
+
+    ReadConfig();
+    InitializeAudio(GameFreq);
+    return 1;
+}
+
+static void InitializeSDL(void)
+{
+    DebugMessage(M64MSG_INFO, "Initializing SDL audio subsystem...");
+
+    if(SDL_Init(SDL_INIT_AUDIO | SDL_INIT_TIMER) < 0)
+    {
+        DebugMessage(M64MSG_ERROR, "Failed to initialize SDL audio subsystem; forcing exit.\n");
+        critical_failure = 1;
+        return;
+    }
+    critical_failure = 0;
+
+}
+
+static void CreatePrimaryBuffer(void)
+{
+    unsigned int newPrimaryBytes = (unsigned int) ((long long) PrimaryBufferSize * GameFreq * speed_factor /
+                                                   (OutputFreq * 100)) * N64_SAMPLE_BYTES;
+    if (primaryBuffer == NULL)
+    {
+        DebugMessage(M64MSG_VERBOSE, "Allocating memory for audio buffer: %i bytes.", newPrimaryBytes);
+        primaryBuffer = (unsigned char*) malloc(newPrimaryBytes);
+        memset(primaryBuffer, 0, newPrimaryBytes);
+        primaryBufferBytes = newPrimaryBytes;
+    }
+    else if (newPrimaryBytes > primaryBufferBytes) /* primary buffer only grows; there's no point in shrinking it */
+    {
+        unsigned char *newPrimaryBuffer = (unsigned char*) malloc(newPrimaryBytes);
+        unsigned char *oldPrimaryBuffer = primaryBuffer;
+        SDL_LockAudio();
+        memcpy(newPrimaryBuffer, oldPrimaryBuffer, primaryBufferBytes);
+        memset(newPrimaryBuffer + primaryBufferBytes, 0, newPrimaryBytes - primaryBufferBytes);
+        primaryBuffer = newPrimaryBuffer;
+        primaryBufferBytes = newPrimaryBytes;
+        SDL_UnlockAudio();
+        free(oldPrimaryBuffer);
+    }
+}
+
+static void InitializeAudio(int freq)
+{
+    SDL_AudioSpec *desired, *obtained;
+    
+    if(SDL_WasInit(SDL_INIT_AUDIO|SDL_INIT_TIMER) == (SDL_INIT_AUDIO|SDL_INIT_TIMER) ) 
+    {
+        DebugMessage(M64MSG_VERBOSE, "InitializeAudio(): SDL Audio sub-system already initialized.");
+        SDL_PauseAudio(1);
+        SDL_CloseAudio();
+    }
+    else 
+    {
+        DebugMessage(M64MSG_VERBOSE, "InitializeAudio(): Initializing SDL Audio");
+        DebugMessage(M64MSG_VERBOSE, "Primary buffer: %i output samples.", PrimaryBufferSize);
+        DebugMessage(M64MSG_VERBOSE, "Primary target fullness: %i output samples.", PrimaryBufferTarget);
+        DebugMessage(M64MSG_VERBOSE, "Secondary buffer: %i output samples.", SecondaryBufferSize);
+        InitializeSDL();
+    }
+    if (critical_failure == 1)
+        return;
+    GameFreq = freq; // This is important for the sync
+    if(hardware_spec != NULL) free(hardware_spec);
+
+    // Allocate space for SDL_AudioSpec
+    desired = malloc(sizeof(SDL_AudioSpec));
+    obtained = malloc(sizeof(SDL_AudioSpec));
+
+    if(freq < 11025) OutputFreq = 11025;
+    else if(freq < 22050) OutputFreq = 22050;
+    else OutputFreq = 44100;
+    
+    desired->freq = OutputFreq;
+    
+    DebugMessage(M64MSG_VERBOSE, "Requesting frequency: %iHz.", desired->freq);
+    /* 16-bit signed audio */
+    desired->format=AUDIO_S16SYS;
+    DebugMessage(M64MSG_VERBOSE, "Requesting format: %i.", desired->format);
+    /* Stereo */
+    desired->channels=2;
+    /* reload these because they gets re-assigned from SDL data below, and InitializeAudio can be called more than once */
+    PrimaryBufferSize = ConfigGetParamInt(l_ConfigAudio, "PRIMARY_BUFFER_SIZE");
+    PrimaryBufferTarget = ConfigGetParamInt(l_ConfigAudio, "PRIMARY_BUFFER_TARGET");
+    SecondaryBufferSize = ConfigGetParamInt(l_ConfigAudio, "SECONDARY_BUFFER_SIZE");
+    desired->samples = SecondaryBufferSize;
+    /* Our callback function */
+    desired->callback = my_audio_callback;
+    desired->userdata = NULL;
+
+    /* Open the audio device */
+    l_PausedForSync = 1;
+    if (SDL_OpenAudio(desired, obtained) < 0)
+    {
+        DebugMessage(M64MSG_ERROR, "Couldn't open audio: %s", SDL_GetError());
+        critical_failure = 1;
+        return;
+    }
+    if (desired->format != obtained->format)
+    {
+        DebugMessage(M64MSG_WARNING, "Obtained audio format differs from requested.");
+    }
+    if (desired->freq != obtained->freq)
+    {
+        DebugMessage(M64MSG_WARNING, "Obtained frequency differs from requested.");
+    }
+
+    /* desired spec is no longer needed */
+    free(desired);
+    hardware_spec=obtained;
+
+    /* allocate memory for audio buffers */
+    OutputFreq = hardware_spec->freq;
+    SecondaryBufferSize = hardware_spec->samples;
+    if (PrimaryBufferTarget < SecondaryBufferSize)
+        PrimaryBufferTarget = SecondaryBufferSize;
+    if (PrimaryBufferSize < PrimaryBufferTarget)
+        PrimaryBufferSize = PrimaryBufferTarget;
+    if (PrimaryBufferSize < SecondaryBufferSize * 2)
+        PrimaryBufferSize = SecondaryBufferSize * 2;
+    CreatePrimaryBuffer();
+    if (mixBuffer != NULL)
+        free(mixBuffer);
+    mixBuffer = (unsigned char*) malloc(SecondaryBufferSize * SDL_SAMPLE_BYTES);
+
+    /* preset the last callback time */
+    if (last_callback_ticks == 0)
+        last_callback_ticks = SDL_GetTicks();
+
+    DebugMessage(M64MSG_VERBOSE, "Frequency: %i", hardware_spec->freq);
+    DebugMessage(M64MSG_VERBOSE, "Format: %i", hardware_spec->format);
+    DebugMessage(M64MSG_VERBOSE, "Channels: %i", hardware_spec->channels);
+    DebugMessage(M64MSG_VERBOSE, "Silence: %i", hardware_spec->silence);
+    DebugMessage(M64MSG_VERBOSE, "Samples: %i", hardware_spec->samples);
+    DebugMessage(M64MSG_VERBOSE, "Size: %i", hardware_spec->size);
+
+    /* set playback volume */
+#if defined(HAS_OSS_SUPPORT)
+    if (VolumeControlType == VOLUME_TYPE_OSS)
+    {
+        VolPercent = volGet();
+    }
+    else
+#endif
+    {
+        VolSDL = SDL_MIX_MAXVOLUME * VolPercent / 100;
+    }
+
+}
+EXPORT void CALL RomClosed( void )
+{
+    if (!l_PluginInit)
+        return;
+   if (critical_failure == 1)
+       return;
+    DebugMessage(M64MSG_VERBOSE, "Cleaning up SDL sound plugin...");
+    
+    // Shut down SDL Audio output
+    SDL_PauseAudio(1);
+    SDL_CloseAudio();
+
+    // Delete the buffer, as we are done producing sound
+    if (primaryBuffer != NULL)
+    {
+        primaryBufferBytes = 0;
+        free(primaryBuffer);
+        primaryBuffer = NULL;
+    }
+    if (mixBuffer != NULL)
+    {
+        free(mixBuffer);
+        mixBuffer = NULL;
+    }
+
+    // Delete the hardware spec struct
+    if(hardware_spec != NULL) free(hardware_spec);
+    hardware_spec = NULL;
+
+    // Shutdown the respective subsystems
+    if(SDL_WasInit(SDL_INIT_AUDIO) != 0) SDL_QuitSubSystem(SDL_INIT_AUDIO);
+    if(SDL_WasInit(SDL_INIT_TIMER) != 0) SDL_QuitSubSystem(SDL_INIT_TIMER);
+}
+
+EXPORT void CALL ProcessAList(void)
+{
+}
+
+EXPORT void CALL SetSpeedFactor(int percentage)
+{
+    if (!l_PluginInit)
+        return;
+    if (percentage >= 10 && percentage <= 300)
+        speed_factor = percentage;
+    // we need a different size primary buffer to store the N64 samples when the speed changes
+    CreatePrimaryBuffer();
+}
+
+static void ReadConfig(void)
+{
+    const char *resampler_id;
+
+    /* read the configuration values into our static variables */
+    GameFreq = ConfigGetParamInt(l_ConfigAudio, "DEFAULT_FREQUENCY");
+    SwapChannels = ConfigGetParamBool(l_ConfigAudio, "SWAP_CHANNELS");
+    PrimaryBufferSize = ConfigGetParamInt(l_ConfigAudio, "PRIMARY_BUFFER_SIZE");
+    PrimaryBufferTarget = ConfigGetParamInt(l_ConfigAudio, "PRIMARY_BUFFER_TARGET");
+    SecondaryBufferSize = ConfigGetParamInt(l_ConfigAudio, "SECONDARY_BUFFER_SIZE");
+    resampler_id = ConfigGetParamString(l_ConfigAudio, "RESAMPLE");
+    VolumeControlType = ConfigGetParamInt(l_ConfigAudio, "VOLUME_CONTROL_TYPE");
+    VolDelta = ConfigGetParamInt(l_ConfigAudio, "VOLUME_ADJUST");
+    VolPercent = ConfigGetParamInt(l_ConfigAudio, "VOLUME_DEFAULT");
+
+    if (!resampler_id) {
+        Resample = RESAMPLER_TRIVIAL;
+       DebugMessage(M64MSG_WARNING, "Could not find RESAMPLE configuration; use trivial resampler");
+       return;
+    }
+    if (strcmp(resampler_id, "trivial") == 0) {
+        Resample = RESAMPLER_TRIVIAL;
+        return;
+    }
+#ifdef USE_SPEEX
+    if (strncmp(resampler_id, "speex-fixed-", strlen("speex-fixed-")) == 0) {
+        int i;
+        static const char *speex_quality[] = {
+            "speex-fixed-0",
+            "speex-fixed-1",
+            "speex-fixed-2",
+            "speex-fixed-3",
+            "speex-fixed-4",
+            "speex-fixed-5",
+            "speex-fixed-6",
+            "speex-fixed-7",
+            "speex-fixed-8",
+            "speex-fixed-9",
+            "speex-fixed-10",
+        };
+        Resample = RESAMPLER_SPEEX;
+        for (i = 0; i < sizeof(speex_quality) / sizeof(*speex_quality); i++) {
+            if (strcmp(speex_quality[i], resampler_id) == 0) {
+                ResampleQuality = i;
+                return;
+            }
+        }
+        DebugMessage(M64MSG_WARNING, "Unknown RESAMPLE configuration %s; use speex-fixed-4 resampler", resampler_id);
+        ResampleQuality = 4;
+        return;
+    }
+#endif
+#ifdef USE_SRC
+    if (strncmp(resampler_id, "src-", strlen("src-")) == 0) {
+        Resample = RESAMPLER_SRC;
+        if (strcmp(resampler_id, "src-sinc-best-quality") == 0) {
+            ResampleQuality = SRC_SINC_BEST_QUALITY;
+            return;
+        }
+        if (strcmp(resampler_id, "src-sinc-medium-quality") == 0) {
+            ResampleQuality = SRC_SINC_MEDIUM_QUALITY;
+            return;
+        }
+        if (strcmp(resampler_id, "src-sinc-fastest") == 0) {
+            ResampleQuality = SRC_SINC_FASTEST;
+            return;
+        }
+        if (strcmp(resampler_id, "src-zero-order-hold") == 0) {
+            ResampleQuality = SRC_ZERO_ORDER_HOLD;
+            return;
+        }
+        if (strcmp(resampler_id, "src-linear") == 0) {
+            ResampleQuality = SRC_LINEAR;
+            return;
+        }
+        DebugMessage(M64MSG_WARNING, "Unknown RESAMPLE configuration %s; use src-sinc-medium-quality resampler", resampler_id);
+        ResampleQuality = SRC_SINC_MEDIUM_QUALITY;
+        return;
+    }
+#endif
+    DebugMessage(M64MSG_WARNING, "Unknown RESAMPLE configuration %s; use trivial resampler", resampler_id);
+    Resample = RESAMPLER_TRIVIAL;
+}
+
+// Returns the most recent ummuted volume level.
+static int VolumeGetUnmutedLevel(void)
+{
+#if defined(HAS_OSS_SUPPORT)
+    // reload volume if we're using OSS
+    if (!VolIsMuted && VolumeControlType == VOLUME_TYPE_OSS)
+    {
+        return volGet();
+    }
+#endif
+
+    return VolPercent;
+}
+
+// Sets the volume level based on the contents of VolPercent and VolIsMuted
+static void VolumeCommit(void)
+{
+    int levelToCommit = VolIsMuted ? 0 : VolPercent;
+
+#if defined(HAS_OSS_SUPPORT)
+    if (VolumeControlType == VOLUME_TYPE_OSS)
+    {
+        //OSS mixer volume
+        volSet(levelToCommit);
+    }
+    else
+#endif
+    {
+        VolSDL = SDL_MIX_MAXVOLUME * levelToCommit / 100;
+    }
+}
+
+EXPORT void CALL VolumeMute(void)
+{
+    if (!l_PluginInit)
+        return;
+
+    // Store the volume level in order to restore it later
+    if (!VolIsMuted)
+        VolPercent = VolumeGetUnmutedLevel();
+
+    // Toogle mute
+    VolIsMuted = !VolIsMuted;
+    VolumeCommit();
+}
+
+EXPORT void CALL VolumeUp(void)
+{
+    if (!l_PluginInit)
+        return;
+
+    VolumeSetLevel(VolumeGetUnmutedLevel() + VolDelta);
+}
+
+EXPORT void CALL VolumeDown(void)
+{
+    if (!l_PluginInit)
+        return;
+
+    VolumeSetLevel(VolumeGetUnmutedLevel() - VolDelta);
+}
+
+EXPORT int CALL VolumeGetLevel(void)
+{
+    return VolIsMuted ? 0 : VolumeGetUnmutedLevel();
+}
+
+EXPORT void CALL VolumeSetLevel(int level)
+{
+    if (!l_PluginInit)
+        return;
+
+    //if muted, unmute first
+    VolIsMuted = 0;
+
+    // adjust volume 
+    VolPercent = level;
+    if (VolPercent < 0)
+        VolPercent = 0;
+    else if (VolPercent > 100)
+        VolPercent = 100;
+
+    VolumeCommit();
+}
+
+EXPORT const char * CALL VolumeGetString(void)
+{
+    static char VolumeString[32];
+
+    if (VolIsMuted)
+    {
+        strcpy(VolumeString, "Mute");
+    }
+    else
+    {
+        sprintf(VolumeString, "%i%%", VolPercent);
+    }
+
+    return VolumeString;
+}
+