diff --git a/BizHawk.Emulation/Consoles/Nintendo/N64/N64.cs b/BizHawk.Emulation/Consoles/Nintendo/N64/N64.cs index 0d0d560199..8016d51124 100644 --- a/BizHawk.Emulation/Consoles/Nintendo/N64/N64.cs +++ b/BizHawk.Emulation/Consoles/Nintendo/N64/N64.cs @@ -36,8 +36,19 @@ namespace BizHawk.Emulation.Consoles.Nintendo.N64 public int BufferHeight { get { return 480; } } public int BackgroundColor { get { return 0; } } + short[] m64pAudioBuffer; public ISoundProvider SoundProvider { get { return this; } } - public void GetSamples(short[] samples) { } + public void GetSamples(short[] samples) + { + if (m64pAudioBuffer.Length > 0) + { + for (int i = 0; i < samples.Length / 2; i++) + { + samples[i * 2] = m64pAudioBuffer[(int)((((double)m64pAudioBuffer.Length / 2) / (double)(samples.Length / 2)) * i) * 2]; + samples[i * 2 + 1] = m64pAudioBuffer[(int)((((double)m64pAudioBuffer.Length / 2) / (double)(samples.Length / 2)) * i) * 2 + 1]; ; + } + } + } public void DiscardSamples() { } public int MaxVolume { get; set; } public ISyncSoundProvider SyncSoundProvider { get { return null; } } @@ -202,12 +213,22 @@ namespace BizHawk.Emulation.Consoles.Nintendo.N64 private delegate void ReadScreen2(byte[] framebuffer, ref int width, ref int height, int buffer); ReadScreen2 GFXReadScreen2; + // Audio plugin specific + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + private delegate int GetBufferSize(); + GetBufferSize AudGetBufferSize; + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + private delegate void ReadAudioBuffer(short[] dest); + ReadAudioBuffer AudReadAudioBuffer; + // This has the same calling pattern for all the plugins [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate m64p_error PluginStartup(IntPtr CoreHandle, string Context, DebugCallback DebugCallback); PluginStartup GfxPluginStartup; PluginStartup RspPluginStartup; + PluginStartup AudPluginStartup; public delegate void DebugCallback(IntPtr Context, int level, string Message); @@ -227,12 +248,19 @@ namespace BizHawk.Emulation.Consoles.Nintendo.N64 VICallback m64pVICallback; public void FrameComplete() { + int m64pAudioBufferSize = AudGetBufferSize(); + m64pAudioBuffer = new short[m64pAudioBufferSize]; + if (m64pAudioBufferSize > 0) + { + AudReadAudioBuffer(m64pAudioBuffer); + } m64pFrameComplete = true; } IntPtr CoreDll; IntPtr GfxDll; IntPtr RspDll; + IntPtr AudDll; Thread m64pEmulator; @@ -248,6 +276,7 @@ namespace BizHawk.Emulation.Consoles.Nintendo.N64 CoreDll = LoadLibrary("mupen64plus.dll"); GfxDll = LoadLibrary("mupen64plus-video-rice.dll"); RspDll = LoadLibrary("mupen64plus-rsp-hle.dll"); + AudDll = LoadLibrary("mupen64plus-audio-bkm.dll"); m64pCoreStartup = (CoreStartup)Marshal.GetDelegateForFunctionPointer(GetProcAddress(CoreDll, "CoreStartup"), typeof(CoreStartup)); m64pCoreDoCommandByteArray = (CoreDoCommandByteArray)Marshal.GetDelegateForFunctionPointer(GetProcAddress(CoreDll, "CoreDoCommand"), typeof(CoreDoCommandByteArray)); @@ -260,6 +289,10 @@ namespace BizHawk.Emulation.Consoles.Nintendo.N64 GfxPluginStartup = (PluginStartup)Marshal.GetDelegateForFunctionPointer(GetProcAddress(GfxDll, "PluginStartup"), typeof(PluginStartup)); GFXReadScreen2 = (ReadScreen2)Marshal.GetDelegateForFunctionPointer(GetProcAddress(GfxDll, "ReadScreen2"), typeof(ReadScreen2)); + AudPluginStartup = (PluginStartup)Marshal.GetDelegateForFunctionPointer(GetProcAddress(AudDll, "PluginStartup"), typeof(PluginStartup)); + AudGetBufferSize = (GetBufferSize)Marshal.GetDelegateForFunctionPointer(GetProcAddress(AudDll, "GetBufferSize"), typeof(GetBufferSize)); + AudReadAudioBuffer = (ReadAudioBuffer)Marshal.GetDelegateForFunctionPointer(GetProcAddress(AudDll, "ReadAudioBuffer"), typeof(ReadAudioBuffer)); + RspPluginStartup = (PluginStartup)Marshal.GetDelegateForFunctionPointer(GetProcAddress(RspDll, "PluginStartup"), typeof(PluginStartup)); // Set up the core @@ -271,7 +304,8 @@ namespace BizHawk.Emulation.Consoles.Nintendo.N64 result = m64pCoreAttachPlugin(m64p_plugin_type.M64PLUGIN_GFX, GfxDll); // Set up a null audio plugin - result = m64pCoreAttachPlugin(m64p_plugin_type.M64PLUGIN_AUDIO, IntPtr.Zero); + result = AudPluginStartup(CoreDll, "Audio", (IntPtr foo, int level, string Message) => { Console.WriteLine(Message); }); + result = m64pCoreAttachPlugin(m64p_plugin_type.M64PLUGIN_AUDIO, AudDll); // Set up a null input plugin result = m64pCoreAttachPlugin(m64p_plugin_type.M64PLUGIN_INPUT, IntPtr.Zero); diff --git a/BizHawk.MultiClient/output/dll/mupen64plus-audio-bkm.dll b/BizHawk.MultiClient/output/dll/mupen64plus-audio-bkm.dll new file mode 100644 index 0000000000..da023ba69f Binary files /dev/null and b/BizHawk.MultiClient/output/dll/mupen64plus-audio-bkm.dll differ diff --git a/libmupen64plus/mupen64plus-audio-bkm/main.c b/libmupen64plus/mupen64plus-audio-bkm/main.c new file mode 100644 index 0000000000..43f681b9d2 --- /dev/null +++ b/libmupen64plus/mupen64plus-audio-bkm/main.c @@ -0,0 +1,748 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * 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 +#include +#include + +#ifdef USE_SRC +#include +#endif +#ifdef USE_SPEEX +#include +#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 "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 2048 + +/* 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 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; +/* Pointer to the primary audio buffer */ +static unsigned char *primaryBuffer = NULL; +static unsigned char *mixBuffer = NULL; +static unsigned int primaryBufferBytes = 0; +/* 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; +// 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 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; + + 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; + + 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; + + 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; + } + else + { + DebugMessage(M64MSG_WARNING, "AiLenChanged(): Audio buffer overflow."); + } +} + +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 + +EXPORT int CALL RomOpen(void) +{ + if (!l_PluginInit) + return 0; + + ReadConfig(); + InitializeAudio(GameFreq); + return 1; +} + +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; + memcpy(newPrimaryBuffer, oldPrimaryBuffer, primaryBufferBytes); + memset(newPrimaryBuffer + primaryBufferBytes, 0, newPrimaryBytes - primaryBufferBytes); + primaryBuffer = newPrimaryBuffer; + primaryBufferBytes = newPrimaryBytes; + free(oldPrimaryBuffer); + } +} + +static void InitializeAudio(int freq) +{ + GameFreq = freq; // This is important for the sync + + if(freq < 11025) OutputFreq = 11025; + else if(freq < 22050) OutputFreq = 22050; + else OutputFreq = 44100; + + OutputFreq = 11025; + + /* 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"); + + 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); +} +EXPORT void CALL RomClosed( void ) +{ + if (!l_PluginInit) + return; + if (critical_failure == 1) + return; + + // Delete the buffer, as we are done producing sound + if (primaryBuffer != NULL) + { + primaryBufferBytes = 0; + free(primaryBuffer); + primaryBuffer = NULL; + } + +} + +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; +} + +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; +} + +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; + +} + +EXPORT const char * CALL VolumeGetString(void) +{ + static char VolumeString[32]; + + if (VolIsMuted) + { + strcpy(VolumeString, "Mute"); + } + else + { + sprintf(VolumeString, "%i%%", VolPercent); + } + + return VolumeString; +} + +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; + + // 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 +} + +EXPORT void CALL my_audio_callback(unsigned char *stream, int len) +{ + int oldsamplerate, newsamplerate; + + if (!l_PluginInit) + return; + + newsamplerate = OutputFreq * 100 / speed_factor; + oldsamplerate = GameFreq; + + if (buffer_pos > (unsigned int) (len * oldsamplerate) / newsamplerate) + { + int input_used; + { + input_used = resample(primaryBuffer, buffer_pos, oldsamplerate, stream, len, newsamplerate); + } + memmove(primaryBuffer, &primaryBuffer[input_used], buffer_pos - input_used); + buffer_pos -= input_used; + DebugMessage(M64MSG_VERBOSE, "my_audio_callback: used %i samples", + len / 4); + } + else + { + unsigned int SamplesNeeded = (len * oldsamplerate) / (newsamplerate * 4); + unsigned int SamplesPresent = buffer_pos / N64_SAMPLE_BYTES; + underrun_count++; + DebugMessage(M64MSG_VERBOSE, "Buffer underflow (%i). %i samples present, %i needed", + underrun_count, SamplesPresent, SamplesNeeded); + memset(stream , 0, len); + } +} + +EXPORT void CALL ReadAudioBuffer(short* dest) +{ + int i; + short * src = (short*)primaryBuffer; + for (i = 0; i < buffer_pos/2; i++) + { + dest[i] = src[i]; + } + + buffer_pos = 0; +} + +EXPORT int CALL GetBufferSize() +{ + return buffer_pos/2; +} \ No newline at end of file diff --git a/libmupen64plus/mupen64plus-audio-bkm/main.h b/libmupen64plus/mupen64plus-audio-bkm/main.h new file mode 100644 index 0000000000..c46bdc3c39 --- /dev/null +++ b/libmupen64plus/mupen64plus-audio-bkm/main.h @@ -0,0 +1,48 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Mupen64plus - main.h * + * Mupen64Plus homepage: http://code.google.com/p/mupen64plus/ * + * Copyright (C) 2008-2012 Tillin9, Richard42 * + * * + * 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. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +/* version info */ +#define SDL_AUDIO_PLUGIN_VERSION 0x016305 +#define AUDIO_PLUGIN_API_VERSION 0x020000 +#define CONFIG_API_VERSION 0x020100 +#define CONFIG_PARAM_VERSION 1.00 + +#define VERSION_PRINTF_SPLIT(x) (((x) >> 16) & 0xffff), (((x) >> 8) & 0xff), ((x) & 0xff) + +/* declarations of pointers to Core config functions */ +extern ptr_ConfigListSections ConfigListSections; +extern ptr_ConfigOpenSection ConfigOpenSection; +extern ptr_ConfigDeleteSection ConfigDeleteSection; +extern ptr_ConfigSaveSection ConfigSaveSection; +extern ptr_ConfigListParameters ConfigListParameters; +extern ptr_ConfigSaveFile ConfigSaveFile; +extern ptr_ConfigSetParameter ConfigSetParameter; +extern ptr_ConfigGetParameter ConfigGetParameter; +extern ptr_ConfigGetParameterHelp ConfigGetParameterHelp; +extern ptr_ConfigSetDefaultInt ConfigSetDefaultInt; +extern ptr_ConfigSetDefaultFloat ConfigSetDefaultFloat; +extern ptr_ConfigSetDefaultBool ConfigSetDefaultBool; +extern ptr_ConfigSetDefaultString ConfigSetDefaultString; +extern ptr_ConfigGetParamInt ConfigGetParamInt; +extern ptr_ConfigGetParamFloat ConfigGetParamFloat; +extern ptr_ConfigGetParamBool ConfigGetParamBool; +extern ptr_ConfigGetParamString ConfigGetParamString; + diff --git a/libmupen64plus/mupen64plus-audio-bkm/mupen64plus-audio-bkm/mupen64plus-audio-bkm.vcxproj b/libmupen64plus/mupen64plus-audio-bkm/mupen64plus-audio-bkm/mupen64plus-audio-bkm.vcxproj new file mode 100644 index 0000000000..21bb859406 --- /dev/null +++ b/libmupen64plus/mupen64plus-audio-bkm/mupen64plus-audio-bkm/mupen64plus-audio-bkm.vcxproj @@ -0,0 +1,83 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + + {B6F20769-1385-4C0F-91A9-26F47AD1E78F} + mupen64plusaudiobkm + + + + DynamicLibrary + true + v110 + MultiByte + + + DynamicLibrary + false + v100 + true + MultiByte + + + + + + + + + + + + + + + Level3 + Disabled + ..\..\mupen64plus-core\src\api;%(AdditionalIncludeDirectories) + + + true + + + + + Level3 + MaxSpeed + + + false + ..\..\mupen64plus-core\src\api;%(AdditionalIncludeDirectories) + WIN32;NDEBUG;_WINDOWS;_USRDLL;_CRT_SECURE_NO_DEPRECATE;%(PreprocessorDefinitions) + + + + true + true + true + + + xcopy /y $(OutDir)$(TargetName)$(TargetExt) $(TargetDir)..\..\..\..\..\BizHawk.MultiClient\output\dll\ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/libmupen64plus/mupen64plus-audio-bkm/osal_dynamiclib.h b/libmupen64plus/mupen64plus-audio-bkm/osal_dynamiclib.h new file mode 100644 index 0000000000..daef1541c0 --- /dev/null +++ b/libmupen64plus/mupen64plus-audio-bkm/osal_dynamiclib.h @@ -0,0 +1,30 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Mupen64plus-core - osal/dynamiclib.h * + * Mupen64Plus homepage: http://code.google.com/p/mupen64plus/ * + * Copyright (C) 2009 Richard Goedeken * + * * + * 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. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#if !defined(OSAL_DYNAMICLIB_H) +#define OSAL_DYNAMICLIB_H + +#include "m64p_types.h" + +void * osal_dynlib_getproc(m64p_dynlib_handle LibHandle, const char *pccProcedureName); + +#endif /* #define OSAL_DYNAMICLIB_H */ + diff --git a/libmupen64plus/mupen64plus-audio-bkm/osal_dynamiclib_unix.c b/libmupen64plus/mupen64plus-audio-bkm/osal_dynamiclib_unix.c new file mode 100644 index 0000000000..b3b7ba52dc --- /dev/null +++ b/libmupen64plus/mupen64plus-audio-bkm/osal_dynamiclib_unix.c @@ -0,0 +1,37 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Mupen64plus-core - osal/dynamiclib_unix.c * + * Mupen64Plus homepage: http://code.google.com/p/mupen64plus/ * + * Copyright (C) 2009 Richard Goedeken * + * * + * 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 +#include +#include + +#include "m64p_types.h" +#include "osal_dynamiclib.h" + +void * osal_dynlib_getproc(m64p_dynlib_handle LibHandle, const char *pccProcedureName) +{ + if (pccProcedureName == NULL) + return NULL; + + return dlsym(LibHandle, pccProcedureName); +} + + diff --git a/libmupen64plus/mupen64plus-audio-bkm/osal_dynamiclib_win32.c b/libmupen64plus/mupen64plus-audio-bkm/osal_dynamiclib_win32.c new file mode 100644 index 0000000000..4455cee52b --- /dev/null +++ b/libmupen64plus/mupen64plus-audio-bkm/osal_dynamiclib_win32.c @@ -0,0 +1,74 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Mupen64plus-ui-console - osal_dynamiclib_win32.c * + * Mupen64Plus homepage: http://code.google.com/p/mupen64plus/ * + * Copyright (C) 2009 Richard Goedeken * + * * + * 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 +#include +#include + +#include "m64p_types.h" +#include "osal_dynamiclib.h" + +m64p_error osal_dynlib_open(m64p_dynlib_handle *pLibHandle, const char *pccLibraryPath) +{ + if (pLibHandle == NULL || pccLibraryPath == NULL) + return M64ERR_INPUT_ASSERT; + + *pLibHandle = LoadLibrary(pccLibraryPath); + + if (*pLibHandle == NULL) + { + char *pchErrMsg; + DWORD dwErr = GetLastError(); + FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, NULL, dwErr, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR) &pchErrMsg, 0, NULL); + fprintf(stderr, "LoadLibrary('%s') error: %s\n", pccLibraryPath, pchErrMsg); + LocalFree(pchErrMsg); + return M64ERR_INPUT_NOT_FOUND; + } + + return M64ERR_SUCCESS; +} + +void * osal_dynlib_getproc(m64p_dynlib_handle LibHandle, const char *pccProcedureName) +{ + if (pccProcedureName == NULL) + return NULL; + + return GetProcAddress(LibHandle, pccProcedureName); +} + +m64p_error osal_dynlib_close(m64p_dynlib_handle LibHandle) +{ + int rval = FreeLibrary(LibHandle); + + if (rval == 0) + { + char *pchErrMsg; + DWORD dwErr = GetLastError(); + FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, NULL, dwErr, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR) &pchErrMsg, 0, NULL); + fprintf(stderr, "FreeLibrary() error: %s\n", pchErrMsg); + LocalFree(pchErrMsg); + return M64ERR_INTERNAL; + } + + return M64ERR_SUCCESS; +} diff --git a/libmupen64plus/mupen64plus-ui-console/projects/msvc11/mupen64plus-ui-console.sln b/libmupen64plus/mupen64plus-ui-console/projects/msvc11/mupen64plus-ui-console.sln index 69a5354f19..305bedd73f 100644 --- a/libmupen64plus/mupen64plus-ui-console/projects/msvc11/mupen64plus-ui-console.sln +++ b/libmupen64plus/mupen64plus-ui-console/projects/msvc11/mupen64plus-ui-console.sln @@ -14,6 +14,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "mupen64plus-audio-sdl", ".. EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "mupen64plus-video-glide64mk2", "..\..\..\mupen64plus-video-glide64mk2\projects\msvc10\mupen64plus-video-glide64mk2.vcxproj", "{A4D13408-A794-4199-8FC7-4A9A32505005}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "mupen64plus-audio-bkm", "..\..\..\mupen64plus-audio-bkm\mupen64plus-audio-bkm\mupen64plus-audio-bkm.vcxproj", "{B6F20769-1385-4C0F-91A9-26F47AD1E78F}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Win32 = Debug|Win32 @@ -48,6 +50,10 @@ Global {A4D13408-A794-4199-8FC7-4A9A32505005}.Debug|Win32.Build.0 = Debug|Win32 {A4D13408-A794-4199-8FC7-4A9A32505005}.Release|Win32.ActiveCfg = Release|Win32 {A4D13408-A794-4199-8FC7-4A9A32505005}.Release|Win32.Build.0 = Release|Win32 + {B6F20769-1385-4C0F-91A9-26F47AD1E78F}.Debug|Win32.ActiveCfg = Debug|Win32 + {B6F20769-1385-4C0F-91A9-26F47AD1E78F}.Debug|Win32.Build.0 = Debug|Win32 + {B6F20769-1385-4C0F-91A9-26F47AD1E78F}.Release|Win32.ActiveCfg = Release|Win32 + {B6F20769-1385-4C0F-91A9-26F47AD1E78F}.Release|Win32.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE