fbneo/src/burn/snd/burn_ym3812.cpp

605 lines
15 KiB
C++

#include "burnint.h"
#include "burn_sound.h"
#include "burn_ym3812.h"
#include "m68000_intf.h"
#include "z80_intf.h"
#include "m6809_intf.h"
#include "hd6309_intf.h"
#include "m6800_intf.h"
#include "m6502_intf.h"
#include "h6280_intf.h"
// Timer Related
#define MAX_TIMER_VALUE ((1 << 30) - 65536)
static double dTimeYM3812; // Time elapsed since the emulated machine was started
static INT32 nTimerCount[2], nTimerStart[2];
// Callbacks
static INT32 (*pTimerOverCallback)(INT32, INT32);
static double (*pTimerTimeCallback)();
static INT32 nCPUClockspeed = 0;
static INT32 (*pCPUTotalCycles)() = NULL;
static INT32 (*pCPURun)(INT32) = NULL;
static void (*pCPURunEnd)() = NULL;
// ---------------------------------------------------------------------------
// Running time
static double BurnTimerTimeCallbackDummy()
{
return 0.0;
}
extern "C" double BurnTimerGetTimeYM3812()
{
return dTimeYM3812 + pTimerTimeCallback();
}
// ---------------------------------------------------------------------------
// Update timers
static INT32 nTicksTotal, nTicksDone, nTicksExtra;
INT32 BurnTimerUpdateYM3812(INT32 nCycles)
{
INT32 nIRQStatus = 0;
nTicksTotal = MAKE_TIMER_TICKS(nCycles, nCPUClockspeed);
while (nTicksDone < nTicksTotal) {
INT32 nTimer, nCyclesSegment, nTicksSegment;
// Determine which timer fires first
if (nTimerCount[0] <= nTimerCount[1]) {
nTicksSegment = nTimerCount[0];
} else {
nTicksSegment = nTimerCount[1];
}
if (nTicksSegment > nTicksTotal) {
nTicksSegment = nTicksTotal;
}
nCyclesSegment = MAKE_CPU_CYLES(nTicksSegment + nTicksExtra, nCPUClockspeed);
pCPURun(nCyclesSegment - pCPUTotalCycles());
nTicksDone = MAKE_TIMER_TICKS(pCPUTotalCycles() + 1, nCPUClockspeed) - 1;
nTimer = 0;
if (nTicksDone >= nTimerCount[0]) {
if (nTimerStart[0] == MAX_TIMER_VALUE) {
nTimerCount[0] = MAX_TIMER_VALUE;
} else {
nTimerCount[0] += nTimerStart[0];
}
nTimer |= 1;
}
if (nTicksDone >= nTimerCount[1]) {
if (nTimerStart[1] == MAX_TIMER_VALUE) {
nTimerCount[1] = MAX_TIMER_VALUE;
} else {
nTimerCount[1] += nTimerStart[1];
}
nTimer |= 2;
}
if (nTimer & 1) {
nIRQStatus |= pTimerOverCallback(0, 0);
}
if (nTimer & 2) {
nIRQStatus |= pTimerOverCallback(0, 1);
}
}
return nIRQStatus;
}
void BurnTimerEndFrameYM3812(INT32 nCycles)
{
INT32 nTicks = MAKE_TIMER_TICKS(nCycles, nCPUClockspeed);
BurnTimerUpdateYM3812(nCycles);
if (nTimerCount[0] < MAX_TIMER_VALUE) {
nTimerCount[0] -= nTicks;
}
if (nTimerCount[1] < MAX_TIMER_VALUE) {
nTimerCount[1] -= nTicks;
}
nTicksDone -= nTicks;
if (nTicksDone < 0) {
nTicksDone = 0;
}
}
void BurnTimerUpdateEndYM3812()
{
pCPURunEnd();
nTicksTotal = 0;
}
void BurnOPLTimerCallbackYM3812(INT32 c, double period)
{
pCPURunEnd();
if (period == 0.0) {
nTimerCount[c] = MAX_TIMER_VALUE;
return;
}
nTimerCount[c] = (INT32)(period * (double)TIMER_TICKS_PER_SECOND);
nTimerCount[c] += MAKE_TIMER_TICKS(pCPUTotalCycles(), nCPUClockspeed);
}
void BurnTimerScanYM3812(INT32 nAction, INT32* pnMin)
{
if (pnMin && *pnMin < 0x029521) {
*pnMin = 0x029521;
}
if (nAction & ACB_DRIVER_DATA) {
SCAN_VAR(nTimerCount);
SCAN_VAR(nTimerStart);
SCAN_VAR(dTimeYM3812);
SCAN_VAR(nTicksDone);
}
}
void BurnTimerExitYM3812()
{
nCPUClockspeed = 0;
pCPUTotalCycles = NULL;
pCPURun = NULL;
pCPURunEnd = NULL;
return;
}
void BurnTimerResetYM3812()
{
nTimerCount[0] = nTimerCount[1] = MAX_TIMER_VALUE;
nTimerStart[0] = nTimerStart[1] = MAX_TIMER_VALUE;
dTimeYM3812 = 0.0;
nTicksDone = 0;
}
INT32 BurnTimerInitYM3812(INT32 (*pOverCallback)(INT32, INT32), double (*pTimeCallback)())
{
BurnTimerExitYM3812();
pTimerOverCallback = pOverCallback;
pTimerTimeCallback = pTimeCallback ? pTimeCallback : BurnTimerTimeCallbackDummy;
BurnTimerResetYM3812();
return 0;
}
INT32 BurnTimerAttachSekYM3812(INT32 nClockspeed)
{
nCPUClockspeed = nClockspeed;
pCPUTotalCycles = SekTotalCycles;
pCPURun = SekRun;
pCPURunEnd = SekRunEnd;
nTicksExtra = MAKE_TIMER_TICKS(1, nCPUClockspeed) - 1;
return 0;
}
INT32 BurnTimerAttachZetYM3812(INT32 nClockspeed)
{
nCPUClockspeed = nClockspeed;
pCPUTotalCycles = ZetTotalCycles;
pCPURun = ZetRun;
pCPURunEnd = ZetRunEnd;
nTicksExtra = MAKE_TIMER_TICKS(1, nCPUClockspeed) - 1;
return 0;
}
INT32 BurnTimerAttachM6809YM3812(INT32 nClockspeed)
{
nCPUClockspeed = nClockspeed;
pCPUTotalCycles = M6809TotalCycles;
pCPURun = M6809Run;
pCPURunEnd = M6809RunEnd;
nTicksExtra = MAKE_TIMER_TICKS(1, nCPUClockspeed) - 1;
return 0;
}
INT32 BurnTimerAttachHD6309YM3812(INT32 nClockspeed)
{
nCPUClockspeed = nClockspeed;
pCPUTotalCycles = HD6309TotalCycles;
pCPURun = HD6309Run;
pCPURunEnd = HD6309RunEnd;
nTicksExtra = MAKE_TIMER_TICKS(1, nCPUClockspeed) - 1;
return 0;
}
INT32 BurnTimerAttachM6800YM3812(INT32 nClockspeed)
{
nCPUClockspeed = nClockspeed;
pCPUTotalCycles = M6800TotalCycles;
pCPURun = M6800Run;
pCPURunEnd = M6800RunEnd;
nTicksExtra = MAKE_TIMER_TICKS(1, nCPUClockspeed) - 1;
return 0;
}
INT32 BurnTimerAttachHD63701YM3812(INT32 nClockspeed)
{
nCPUClockspeed = nClockspeed;
pCPUTotalCycles = M6800TotalCycles;
pCPURun = HD63701Run;
pCPURunEnd = HD63701RunEnd;
nTicksExtra = MAKE_TIMER_TICKS(1, nCPUClockspeed) - 1;
return 0;
}
INT32 BurnTimerAttachM6803YM3812(INT32 nClockspeed)
{
nCPUClockspeed = nClockspeed;
pCPUTotalCycles = M6800TotalCycles;
pCPURun = M6803Run;
pCPURunEnd = M6803RunEnd;
nTicksExtra = MAKE_TIMER_TICKS(1, nCPUClockspeed) - 1;
return 0;
}
INT32 BurnTimerAttachM6502YM3812(INT32 nClockspeed)
{
nCPUClockspeed = nClockspeed;
pCPUTotalCycles = M6502TotalCycles;
pCPURun = M6502Run;
pCPURunEnd = M6502RunEnd; // doesn't do anything...
nTicksExtra = MAKE_TIMER_TICKS(1, nCPUClockspeed) - 1;
return 0;
}
INT32 BurnTimerAttachH6280YM3812(INT32 nClockspeed)
{
nCPUClockspeed = nClockspeed;
pCPUTotalCycles = h6280TotalCycles;
pCPURun = h6280Run;
pCPURunEnd = h6280RunEnd;
nTicksExtra = MAKE_TIMER_TICKS(1, nCPUClockspeed) - 1;
return 0;
}
// Sound Related
void (*BurnYM3812Update)(INT16* pSoundBuf, INT32 nSegmentEnd);
static INT32 (*BurnYM3812StreamCallback)(INT32 nSoundRate);
static INT32 nBurnYM3812SoundRate;
static INT16* pBuffer;
static INT16* pYM3812Buffer;
static INT32 nYM3812Position;
static UINT32 nSampleSize;
static INT32 nFractionalPosition;
static INT32 bYM3812AddSignal;
static double YM3812Volumes[1];
static INT32 YM3812RouteDirs[1];
// ----------------------------------------------------------------------------
// Dummy functions
static void YM3812UpdateDummy(INT16* , INT32)
{
return;
}
static INT32 YM3812StreamCallbackDummy(INT32)
{
return 0;
}
// ----------------------------------------------------------------------------
// Execute YM3812 for part of a frame
static void YM3812Render(INT32 nSegmentLength)
{
#if defined FBA_DEBUG
if (!DebugSnd_YM3812Initted) bprintf(PRINT_ERROR, _T("YM3812Render called without init\n"));
#endif
if (nYM3812Position >= nSegmentLength) {
return;
}
nSegmentLength -= nYM3812Position;
YM3812UpdateOne(0, pBuffer + 0 * 4096 + 4 + nYM3812Position, nSegmentLength);
nYM3812Position += nSegmentLength;
}
// ----------------------------------------------------------------------------
// Update the sound buffer
static void YM3812UpdateResample(INT16* pSoundBuf, INT32 nSegmentEnd)
{
#if defined FBA_DEBUG
if (!DebugSnd_YM3812Initted) bprintf(PRINT_ERROR, _T("YM3812UpdateResample called without init\n"));
#endif
INT32 nSegmentLength = nSegmentEnd;
INT32 nSamplesNeeded = nSegmentEnd * nBurnYM3812SoundRate / nBurnSoundRate + 1;
if (nSamplesNeeded < nYM3812Position) {
nSamplesNeeded = nYM3812Position;
}
if (nSegmentLength > nBurnSoundLen) {
nSegmentLength = nBurnSoundLen;
}
nSegmentLength <<= 1;
YM3812Render(nSamplesNeeded);
pYM3812Buffer = pBuffer + 0 * 4096 + 4;
for (INT32 i = (nFractionalPosition & 0xFFFF0000) >> 15; i < nSegmentLength; i += 2, nFractionalPosition += nSampleSize) {
INT32 nLeftSample[4] = {0, 0, 0, 0};
INT32 nRightSample[4] = {0, 0, 0, 0};
INT32 nTotalLeftSample, nTotalRightSample;
if ((YM3812RouteDirs[BURN_SND_YM3812_ROUTE] & BURN_SND_ROUTE_LEFT) == BURN_SND_ROUTE_LEFT) {
nLeftSample[0] += (INT32)(pYM3812Buffer[(nFractionalPosition >> 16) - 3] * YM3812Volumes[BURN_SND_YM3812_ROUTE]);
nLeftSample[1] += (INT32)(pYM3812Buffer[(nFractionalPosition >> 16) - 2] * YM3812Volumes[BURN_SND_YM3812_ROUTE]);
nLeftSample[2] += (INT32)(pYM3812Buffer[(nFractionalPosition >> 16) - 1] * YM3812Volumes[BURN_SND_YM3812_ROUTE]);
nLeftSample[3] += (INT32)(pYM3812Buffer[(nFractionalPosition >> 16) - 0] * YM3812Volumes[BURN_SND_YM3812_ROUTE]);
}
if ((YM3812RouteDirs[BURN_SND_YM3812_ROUTE] & BURN_SND_ROUTE_RIGHT) == BURN_SND_ROUTE_RIGHT) {
nRightSample[0] += (INT32)(pYM3812Buffer[(nFractionalPosition >> 16) - 3] * YM3812Volumes[BURN_SND_YM3812_ROUTE]);
nRightSample[1] += (INT32)(pYM3812Buffer[(nFractionalPosition >> 16) - 2] * YM3812Volumes[BURN_SND_YM3812_ROUTE]);
nRightSample[2] += (INT32)(pYM3812Buffer[(nFractionalPosition >> 16) - 1] * YM3812Volumes[BURN_SND_YM3812_ROUTE]);
nRightSample[3] += (INT32)(pYM3812Buffer[(nFractionalPosition >> 16) - 0] * YM3812Volumes[BURN_SND_YM3812_ROUTE]);
}
nTotalLeftSample = INTERPOLATE4PS_16BIT((nFractionalPosition >> 4) & 0x0fff, nLeftSample[0], nLeftSample[1], nLeftSample[2], nLeftSample[3]);
nTotalRightSample = INTERPOLATE4PS_16BIT((nFractionalPosition >> 4) & 0x0fff, nRightSample[0], nRightSample[1], nRightSample[2], nRightSample[3]);
nTotalLeftSample = BURN_SND_CLIP(nTotalLeftSample);
nTotalRightSample = BURN_SND_CLIP(nTotalRightSample);
if (bYM3812AddSignal) {
//pSoundBuf[i + 0] += nTotalLeftSample;
//pSoundBuf[i + 1] += nTotalRightSample;
pSoundBuf[i + 0] = BURN_SND_CLIP(pSoundBuf[i + 0] + nTotalLeftSample);
pSoundBuf[i + 1] = BURN_SND_CLIP(pSoundBuf[i + 1] + nTotalRightSample);
} else {
pSoundBuf[i + 0] = nTotalLeftSample;
pSoundBuf[i + 1] = nTotalRightSample;
}
}
if (nSegmentEnd >= nBurnSoundLen) {
INT32 nExtraSamples = nSamplesNeeded - (nFractionalPosition >> 16);
for (INT32 i = -4; i < nExtraSamples; i++) {
pYM3812Buffer[i] = pYM3812Buffer[(nFractionalPosition >> 16) + i];
}
nFractionalPosition &= 0xFFFF;
nYM3812Position = nExtraSamples;
}
}
static void YM3812UpdateNormal(INT16* pSoundBuf, INT32 nSegmentEnd)
{
#if defined FBA_DEBUG
if (!DebugSnd_YM3812Initted) bprintf(PRINT_ERROR, _T("YM3812UpdateNormal called without init\n"));
#endif
INT32 nSegmentLength = nSegmentEnd;
if (nSegmentEnd < nYM3812Position) {
nSegmentEnd = nYM3812Position;
}
if (nSegmentLength > nBurnSoundLen) {
nSegmentLength = nBurnSoundLen;
}
YM3812Render(nSegmentEnd);
pYM3812Buffer = pBuffer + 4 + 0 * 4096;
for (INT32 n = nFractionalPosition; n < nSegmentLength; n++) {
INT32 nLeftSample = 0, nRightSample = 0;
if ((YM3812RouteDirs[BURN_SND_YM3812_ROUTE] & BURN_SND_ROUTE_LEFT) == BURN_SND_ROUTE_LEFT) {
nLeftSample += (INT32)(pYM3812Buffer[n] * YM3812Volumes[BURN_SND_YM3812_ROUTE]);
}
if ((YM3812RouteDirs[BURN_SND_YM3812_ROUTE] & BURN_SND_ROUTE_RIGHT) == BURN_SND_ROUTE_RIGHT) {
nRightSample += (INT32)(pYM3812Buffer[n] * YM3812Volumes[BURN_SND_YM3812_ROUTE]);
}
nLeftSample = BURN_SND_CLIP(nLeftSample);
nRightSample = BURN_SND_CLIP(nRightSample);
if (bYM3812AddSignal) {
pSoundBuf[(n << 1) + 0] += nLeftSample;
pSoundBuf[(n << 1) + 1] += nRightSample;
} else {
pSoundBuf[(n << 1) + 0] = nLeftSample;
pSoundBuf[(n << 1) + 1] = nRightSample;
}
}
nFractionalPosition = nSegmentLength;
if (nSegmentEnd >= nBurnSoundLen) {
INT32 nExtraSamples = nSegmentEnd - nBurnSoundLen;
for (INT32 i = 0; i < nExtraSamples; i++) {
pYM3812Buffer[i] = pYM3812Buffer[nBurnSoundLen + i];
}
nFractionalPosition = 0;
nYM3812Position = nExtraSamples;
}
}
// ----------------------------------------------------------------------------
// Callbacks for YM3812 core
void BurnYM3812UpdateRequest(INT32, INT32)
{
#if defined FBA_DEBUG
if (!DebugSnd_YM3812Initted) bprintf(PRINT_ERROR, _T("BurnYM3812UpdateRequest called without init\n"));
#endif
YM3812Render(BurnYM3812StreamCallback(nBurnYM3812SoundRate));
}
// ----------------------------------------------------------------------------
// Initialisation, etc.
void BurnYM3812Reset()
{
#if defined FBA_DEBUG
if (!DebugSnd_YM3812Initted) bprintf(PRINT_ERROR, _T("BurnYM3812Reset called without init\n"));
#endif
BurnTimerResetYM3812();
YM3812ResetChip(0);
}
void BurnYM3812Exit()
{
#if defined FBA_DEBUG
if (!DebugSnd_YM3812Initted) bprintf(PRINT_ERROR, _T("BurnYM3812Exit called without init\n"));
#endif
YM3812Shutdown();
BurnTimerExitYM3812();
if (pBuffer) {
free(pBuffer);
pBuffer = NULL;
}
bYM3812AddSignal = 0;
DebugSnd_YM3812Initted = 0;
}
INT32 BurnYM3812Init(INT32 nClockFrequency, OPL_IRQHANDLER IRQCallback, INT32 (*StreamCallback)(INT32), INT32 bAddSignal)
{
DebugSnd_YM3812Initted = 1;
BurnTimerInitYM3812(&YM3812TimerOver, NULL);
if (nBurnSoundRate <= 0) {
BurnYM3812StreamCallback = YM3812StreamCallbackDummy;
BurnYM3812Update = YM3812UpdateDummy;
YM3812Init(1, nClockFrequency, 11025);
return 0;
}
BurnYM3812StreamCallback = StreamCallback;
if (nFMInterpolation == 3) {
// Set YM3812 core samplerate to match the hardware
nBurnYM3812SoundRate = nClockFrequency / 72;
// Bring YM3812 core samplerate within usable range
while (nBurnYM3812SoundRate > nBurnSoundRate * 3) {
nBurnYM3812SoundRate >>= 1;
}
BurnYM3812Update = YM3812UpdateResample;
nSampleSize = (UINT32)nBurnYM3812SoundRate * (1 << 16) / nBurnSoundRate;
nFractionalPosition = 0;
} else {
nBurnYM3812SoundRate = nBurnSoundRate;
BurnYM3812Update = YM3812UpdateNormal;
}
YM3812Init(1, nClockFrequency, nBurnYM3812SoundRate);
YM3812SetIRQHandler(0, IRQCallback, 0);
YM3812SetTimerHandler(0, &BurnOPLTimerCallbackYM3812, 0);
YM3812SetUpdateHandler(0, &BurnYM3812UpdateRequest, 0);
pBuffer = (INT16*)malloc(4096 * sizeof(INT16));
memset(pBuffer, 0, 4096 * sizeof(INT16));
nYM3812Position = 0;
nFractionalPosition = 0;
bYM3812AddSignal = bAddSignal;
// default routes
YM3812Volumes[BURN_SND_YM3812_ROUTE] = 1.00;
YM3812RouteDirs[BURN_SND_YM3812_ROUTE] = BURN_SND_ROUTE_BOTH;
return 0;
}
void BurnYM3812SetRoute(INT32 nIndex, double nVolume, INT32 nRouteDir)
{
#if defined FBA_DEBUG
if (!DebugSnd_YM3812Initted) bprintf(PRINT_ERROR, _T("BurnYM3812SetRoute called without init\n"));
if (nIndex < 0 || nIndex > 1) bprintf(PRINT_ERROR, _T("BurnYM3812SetRoute called with invalid index %i\n"), nIndex);
#endif
YM3812Volumes[nIndex] = nVolume;
YM3812RouteDirs[nIndex] = nRouteDir;
}
void BurnYM3812Scan(INT32 nAction, INT32* pnMin)
{
#if defined FBA_DEBUG
if (!DebugSnd_YM3812Initted) bprintf(PRINT_ERROR, _T("BurnYM3812Scan called without init\n"));
#endif
BurnTimerScanYM3812(nAction, pnMin);
FMOPLScan(FM_OPL_SAVESTATE_YM3812, 0, nAction, pnMin);
if (nAction & ACB_DRIVER_DATA) {
SCAN_VAR(nYM3812Position);
}
}