352 lines
9.6 KiB
C
352 lines
9.6 KiB
C
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
|
* Mupen64plus-bkm-audio - main.c *
|
|
* Mupen64Plus homepage: http://code.google.com/p/mupen64plus/ *
|
|
* Editet 2013 null_ptr Completely rewritten to suit custom needs *
|
|
* 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>
|
|
|
|
#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"
|
|
|
|
/* 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
|
|
#define DEFAULT_BUFFER_SIZE 12480
|
|
|
|
/* number of bytes per sample */
|
|
#define N64_SAMPLE_BYTES 4
|
|
|
|
/* local variables */
|
|
static void (*l_DebugCallback)(void *, int, const char *) = NULL;
|
|
static void *l_DebugCallContext = NULL;
|
|
static int l_PluginInit = 0;
|
|
|
|
/* Read header for type definition */
|
|
static AUDIO_INFO AudioInfo;
|
|
/* Audio frequency, this is usually obtained from the game, but for compatibility we set default value */
|
|
static int GameFreq = DEFAULT_FREQUENCY;
|
|
|
|
/* Audio buffer */
|
|
static char* audioBuffer = NULL;
|
|
static size_t bufferBack = 0;
|
|
static size_t bufferSize = 0;
|
|
|
|
// Prototype of local functions
|
|
static void SetSamplingRate(int freq);
|
|
static void SetBufferSize(size_t size);
|
|
|
|
static int critical_failure = 0;
|
|
|
|
/* 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);
|
|
}
|
|
|
|
#pragma region (De-)Initialization
|
|
/* 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;
|
|
|
|
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;
|
|
}
|
|
|
|
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 int CALL RomOpen(void)
|
|
{
|
|
if (!l_PluginInit)
|
|
return 0;
|
|
|
|
SetSamplingRate(GameFreq);
|
|
SetBufferSize(DEFAULT_BUFFER_SIZE);
|
|
return 1;
|
|
}
|
|
|
|
EXPORT void CALL RomClosed( void )
|
|
{
|
|
if (!l_PluginInit)
|
|
return;
|
|
if (critical_failure == 1)
|
|
return;
|
|
|
|
// Delete the buffer, as we are done producing sound
|
|
if (audioBuffer != NULL)
|
|
{
|
|
bufferSize = 0;
|
|
bufferBack = 0;
|
|
free(audioBuffer);
|
|
audioBuffer = NULL;
|
|
}
|
|
}
|
|
|
|
EXPORT int CALL InitiateAudio( AUDIO_INFO Audio_Info )
|
|
{
|
|
if (!l_PluginInit)
|
|
return 0;
|
|
|
|
AudioInfo = Audio_Info;
|
|
return 1;
|
|
}
|
|
|
|
static void SetBufferSize(size_t size)
|
|
{
|
|
char* oldBuffer = audioBuffer;
|
|
audioBuffer = (char*)malloc(size);
|
|
if(audioBuffer != NULL)
|
|
{
|
|
memcpy(audioBuffer, oldBuffer, min(size, bufferBack));
|
|
free(oldBuffer);
|
|
}
|
|
bufferSize = size;
|
|
bufferBack = min(size, bufferBack);
|
|
}
|
|
#pragma endregion
|
|
|
|
#pragma region Pluginversion
|
|
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 = BKM_AUDIO_PLUGIN_VERSION;
|
|
|
|
if (APIVersion != NULL)
|
|
*APIVersion = AUDIO_PLUGIN_API_VERSION;
|
|
|
|
if (PluginNamePtr != NULL)
|
|
*PluginNamePtr = "Mupen64Plus BKM Audio Plugin for Bizhawk";
|
|
|
|
if (Capabilities != NULL)
|
|
{
|
|
*Capabilities = 0;
|
|
}
|
|
|
|
return M64ERR_SUCCESS;
|
|
}
|
|
#pragma endregion
|
|
|
|
#pragma region Handle audio
|
|
/* --- Called when sampling rate changes --- */
|
|
EXPORT void CALL AiDacrateChanged( int SystemType )
|
|
{
|
|
int f = GameFreq;
|
|
|
|
if (!l_PluginInit)
|
|
return;
|
|
|
|
if (*AudioInfo.AI_DACRATE_REG == 0)
|
|
{
|
|
f = DEFAULT_FREQUENCY;
|
|
}
|
|
else
|
|
{
|
|
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;
|
|
}
|
|
}
|
|
SetSamplingRate(f);
|
|
}
|
|
|
|
/* --- Called when length of n64 audio buffer changes --- */
|
|
/* --- i.e. new audio data in buffer --- */
|
|
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 (bufferBack + LenReg < bufferSize)
|
|
{
|
|
unsigned int i;
|
|
|
|
for ( i = 0 ; i < LenReg ; i += 4 )
|
|
{
|
|
// Left channel
|
|
audioBuffer[ bufferBack + i ] = p[ i + 2 ];
|
|
audioBuffer[ bufferBack + i + 1 ] = p[ i + 3 ];
|
|
|
|
// Right channel
|
|
audioBuffer[ bufferBack + i + 2 ] = p[ i ];
|
|
audioBuffer[ bufferBack + i + 3 ] = p[ i + 1 ];
|
|
}
|
|
bufferBack += i;
|
|
|
|
}
|
|
else
|
|
{
|
|
DebugMessage(M64MSG_WARNING, "AiLenChanged(): Audio buffer overflow.");
|
|
}
|
|
}
|
|
|
|
static void SetSamplingRate(int freq)
|
|
{
|
|
GameFreq = freq; // This is important for the sync
|
|
}
|
|
#pragma endregion
|
|
|
|
#pragma region Unused methods
|
|
/* ----------------------------------------------------------------------
|
|
* ------------ STUBS. WE DO NOT USE THESE API FUNCTIONS ---------------
|
|
* -------- MUPEN EXPECTS THEM AND FAILS IF THEY DON'T EXIST ------------
|
|
* ---------------------------------------------------------------------- */
|
|
EXPORT void CALL ProcessAList(void)
|
|
{
|
|
}
|
|
|
|
EXPORT void CALL SetSpeedFactor(int percentage)
|
|
{
|
|
}
|
|
|
|
static int VolumeGetUnmutedLevel(void)
|
|
{
|
|
return 100;
|
|
}
|
|
|
|
EXPORT void CALL VolumeMute(void)
|
|
{
|
|
}
|
|
|
|
EXPORT void CALL VolumeUp(void)
|
|
{
|
|
}
|
|
|
|
EXPORT void CALL VolumeDown(void)
|
|
{
|
|
}
|
|
|
|
EXPORT int CALL VolumeGetLevel(void)
|
|
{
|
|
return 100;
|
|
}
|
|
|
|
EXPORT void CALL VolumeSetLevel(int level)
|
|
{
|
|
}
|
|
|
|
EXPORT const char * CALL VolumeGetString(void)
|
|
{
|
|
return "100%";
|
|
}
|
|
/* ----------------------------------------------------------------------
|
|
* --------------------------- STUBS END --------------------------------
|
|
* ---------------------------------------------------------------------- */
|
|
#pragma endregion
|
|
|
|
#pragma region Buffer export
|
|
/* --- Moves content of audio buffer to destination --- */
|
|
EXPORT void CALL ReadAudioBuffer(short* dest)
|
|
{
|
|
memcpy(dest, audioBuffer, bufferBack);
|
|
bufferBack = 0;
|
|
}
|
|
/* --- Returns number of shorts of internal data --- */
|
|
EXPORT int CALL GetBufferSize()
|
|
{
|
|
return max(bufferBack/2, 0);
|
|
}
|
|
|
|
/* --- Returns current sampling rate --- */
|
|
EXPORT int CALL GetAudioRate()
|
|
{
|
|
return GameFreq;
|
|
}
|
|
#pragma endregion |