mirror of
https://github.com/RetroDECK/Supermodel.git
synced 2024-11-30 01:25:49 +00:00
03fa9532eb
- Added object interface CThread for OSD threading and synchronization, together with SDL implementation - Added multi-threading to CModel3 so that separate CPUs (PPC of main board, 68K of sound board and Z80 of drive board) can be run in separate threads if requested to improve performance on multi-core PCs - Added -multi-threaded command line option (default is to run single-threaded still)
507 lines
12 KiB
C++
507 lines
12 KiB
C++
//TODO: clean up M68K interface. pass a bus pointer (SoundBoard should be derived from it), so that M68K handlers have access to CSoundBoard
|
|
//TODO: must store actual value of bank register so we can save it to save states
|
|
/**
|
|
** Supermodel
|
|
** A Sega Model 3 Arcade Emulator.
|
|
** Copyright 2011 Bart Trzynadlowski
|
|
**
|
|
** This file is part of Supermodel.
|
|
**
|
|
** Supermodel 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 3 of the License, or (at your option)
|
|
** any later version.
|
|
**
|
|
** Supermodel 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 Supermodel. If not, see <http://www.gnu.org/licenses/>.
|
|
**/
|
|
|
|
/*
|
|
* SoundBoard.cpp
|
|
*
|
|
* Model 3 sound board. Implementation of the CSoundBoard class. This class can
|
|
* only be instantiated once because it relies on global variables (the non-OOP
|
|
* 68K core).
|
|
*
|
|
* TO-DO List
|
|
* ----------
|
|
* - Optimize memory handlers (jump table).
|
|
*
|
|
* Bank Switching
|
|
* --------------
|
|
* Banking is not fully understood yet. It is presumed that the low 2MB of the
|
|
* sample ROMs are not banked (MAME), but this is not guaranteed. Below are
|
|
* examples of known situations where banking helps (sound names are as they
|
|
* appear in the sound test menu).
|
|
*
|
|
* sound name
|
|
* ROM Offsets -> Address
|
|
*
|
|
* dayto2pe
|
|
* --------
|
|
* let's hope he does better
|
|
* A... -> A... (400001=3E)
|
|
* doing good i'd say you have a ..
|
|
* E... -> E... (400001=3D)
|
|
*
|
|
* From the above, it appears that when (400001)&10, then:
|
|
*
|
|
* ROM A00000-DFFFFF -> A00000-DFFFFF
|
|
* ROM E00000-FFFFFF -> E00000-FFFFFF
|
|
*
|
|
* And when that bit is clear (just use default mapping, upper 6MB of ROM):
|
|
*
|
|
* ROM 200000-5FFFFF -> A00000-DFFFFF
|
|
* ROM 600000-7FFFFF -> E00000-FFFFFF
|
|
*/
|
|
|
|
#include "Supermodel.h"
|
|
|
|
//TEMP: these need to be dynamically allocated in the memory pool
|
|
static INT16 leftBuffer[44100/60],rightBuffer[44100/60];
|
|
static FILE *soundFP;
|
|
|
|
/******************************************************************************
|
|
68K Access Handlers
|
|
******************************************************************************/
|
|
|
|
// Memory regions passed out of CSoundBoard object for global access handlers
|
|
static UINT8 *sbRAM1, *sbRAM2;
|
|
static const UINT8 *sbSoundROM, *sbSampleROM, *sbSampleBankLo, *sbSampleBankHi;
|
|
|
|
static UINT8 Read8(UINT32 a)
|
|
{
|
|
// SCSP RAM 1
|
|
if ((a >= 0x000000) && (a <= 0x0FFFFF))
|
|
return sbRAM1[a^1];
|
|
|
|
// SCSP RAM 2
|
|
else if ((a >= 0x200000) && (a <= 0x2FFFFF))
|
|
return sbRAM2[(a-0x200000)^1];
|
|
|
|
// Program ROM
|
|
else if ((a >= 0x600000) && (a <= 0x67FFFF))
|
|
return sbSoundROM[(a-0x600000)^1];
|
|
|
|
// Sample ROM (low 2MB, fixed)
|
|
else if ((a >= 0x800000) && (a <= 0x9FFFFF))
|
|
return sbSampleROM[(a-0x800000)^1];
|
|
|
|
// Sample ROM (bank)
|
|
else if ((a >= 0xA00000) && (a <= 0xDFFFFF))
|
|
return sbSampleBankLo[(a-0xA00000)^1];
|
|
|
|
// Sample ROM (bank)
|
|
else if ((a >= 0xE00000) && (a <= 0xFFFFFF))
|
|
return sbSampleBankHi[(a-0xE00000)^1];
|
|
|
|
// SCSP (Master)
|
|
else if ((a >= 0x100000) && (a <= 0x10FFFF))
|
|
return SCSP_Master_r8(a);
|
|
|
|
// SCSP (Slave)
|
|
else if ((a >= 0x300000) && (a <= 0x30FFFF))
|
|
return SCSP_Slave_r8(a);
|
|
|
|
// Unknown
|
|
else
|
|
{
|
|
printf("68K: Unknown read8 %06X\n", a);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static UINT16 Read16(UINT32 a)
|
|
{
|
|
// SCSP RAM 1
|
|
if ((a >= 0x000000) && (a <= 0x0FFFFF))
|
|
return *(UINT16 *) &sbRAM1[a];
|
|
|
|
// SCSP RAM 2
|
|
else if ((a >= 0x200000) && (a <= 0x2FFFFF))
|
|
return *(UINT16 *) &sbRAM2[(a-0x200000)];
|
|
|
|
// Program ROM
|
|
else if ((a >= 0x600000) && (a <= 0x67FFFF))
|
|
return *(UINT16 *) &sbSoundROM[(a-0x600000)];
|
|
|
|
// Sample ROM (low 2MB, fixed)
|
|
else if ((a >= 0x800000) && (a <= 0x9FFFFF))
|
|
return *(UINT16 *) &sbSampleROM[(a-0x800000)];
|
|
|
|
// Sample ROM (bank)
|
|
else if ((a >= 0xA00000) && (a <= 0xDFFFFF))
|
|
return *(UINT16 *) &sbSampleBankLo[(a-0xA00000)];
|
|
|
|
// Sample ROM (bank)
|
|
else if ((a >= 0xE00000) && (a <= 0xFFFFFF))
|
|
return *(UINT16 *) &sbSampleBankHi[(a-0xE00000)];
|
|
|
|
// SCSP (Master)
|
|
else if ((a >= 0x100000) && (a <= 0x10FFFF))
|
|
return SCSP_Master_r16(a);
|
|
|
|
// SCSP (Slave)
|
|
else if ((a >= 0x300000) && (a <= 0x30FFFF))
|
|
return SCSP_Slave_r16(a);
|
|
|
|
// Unknown
|
|
else
|
|
{
|
|
printf("68K: Unknown read16 %06X\n", a);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static UINT32 Read32(UINT32 a)
|
|
{
|
|
// SCSP RAM 1
|
|
if ((a >= 0x000000) && (a <= 0x0FFFFF))
|
|
return (Read16(a)<<16)|Read16(a+2);
|
|
|
|
// SCSP RAM 2
|
|
else if ((a >= 0x200000) && (a <= 0x2FFFFF))
|
|
return (Read16(a)<<16)|Read16(a+2);
|
|
|
|
// Program ROM
|
|
else if ((a >= 0x600000) && (a <= 0x67FFFF))
|
|
return (Read16(a)<<16)|Read16(a+2);
|
|
|
|
// Sample ROM (low 2MB, fixed)
|
|
else if ((a >= 0x800000) && (a <= 0x9FFFFF))
|
|
return (Read16(a)<<16)|Read16(a+2);
|
|
|
|
// Sample ROM (bank)
|
|
else if ((a >= 0xA00000) && (a <= 0xDFFFFF))
|
|
return (Read16(a)<<16)|Read16(a+2);
|
|
|
|
// Sample ROM (bank)
|
|
else if ((a >= 0xE00000) && (a <= 0xFFFFFF))
|
|
return (Read16(a)<<16)|Read16(a+2);
|
|
|
|
// SCSP (Master)
|
|
else if ((a >= 0x100000) && (a <= 0x10FFFF))
|
|
return SCSP_Master_r32(a);
|
|
|
|
// SCSP (Slave)
|
|
else if ((a >= 0x300000) && (a <= 0x30FFFF))
|
|
return SCSP_Slave_r32(a);
|
|
|
|
// Unknown
|
|
else
|
|
{
|
|
printf("68K: Unknown read32 %06X\n", a);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static void Write8(unsigned int a,unsigned char d)
|
|
{
|
|
|
|
// SCSP RAM 1
|
|
if ((a >= 0x000000) && (a <= 0x0FFFFF))
|
|
sbRAM1[a^1] = d;
|
|
|
|
// SCSP RAM 2
|
|
else if ((a >= 0x200000) && (a <= 0x2FFFFF))
|
|
sbRAM2[(a-0x200000)^1] = d;
|
|
|
|
// SCSP (Master)
|
|
else if ((a >= 0x100000) && (a <= 0x10FFFF))
|
|
SCSP_Master_w8(a,d);
|
|
|
|
// SCSP (Slave)
|
|
else if ((a >= 0x300000) && (a <= 0x30FFFF))
|
|
SCSP_Slave_w8(a,d);
|
|
|
|
// Bank register
|
|
else if (a == 0x400001)
|
|
{
|
|
if ((d&0x10))
|
|
{
|
|
sbSampleBankLo = &sbSampleROM[0xA00000];
|
|
sbSampleBankHi = &sbSampleROM[0xE00000];
|
|
}
|
|
else
|
|
{
|
|
sbSampleBankLo = &sbSampleROM[0x200000];
|
|
sbSampleBankHi = &sbSampleROM[0x600000];
|
|
}
|
|
}
|
|
|
|
// Unknown
|
|
else
|
|
printf("68K: Unknown write8 %06X=%02X\n", a, d);
|
|
}
|
|
|
|
static void Write16(unsigned int a,unsigned short d)
|
|
{
|
|
|
|
// SCSP RAM 1
|
|
if ((a >= 0x000000) && (a <= 0x0FFFFF))
|
|
*(UINT16 *) &sbRAM1[a] = d;
|
|
|
|
// SCSP RAM 2
|
|
else if ((a >= 0x200000) && (a <= 0x2FFFFF))
|
|
*(UINT16 *) &sbRAM2[(a-0x200000)] = d;
|
|
|
|
// SCSP (Master)
|
|
else if ((a >= 0x100000) && (a <= 0x10FFFF))
|
|
SCSP_Master_w16(a,d);
|
|
|
|
// SCSP (Slave)
|
|
else if ((a >= 0x300000) && (a <= 0x30FFFF))
|
|
SCSP_Slave_w16(a,d);
|
|
|
|
// Unknown
|
|
else
|
|
printf("68K: Unknown write16 %06X=%04X\n", a, d);
|
|
|
|
}
|
|
|
|
static void Write32(unsigned int a,unsigned int d)
|
|
{
|
|
// SCSP RAM 1
|
|
if ((a >= 0x000000) && (a <= 0x0FFFFF))
|
|
{
|
|
Write16(a,d>>16);
|
|
Write16(a+2,d&0xFFFF);
|
|
}
|
|
|
|
// SCSP RAM 2
|
|
else if ((a >= 0x200000) && (a <= 0x2FFFFF))
|
|
{
|
|
Write16(a,d>>16);
|
|
Write16(a+2,d&0xFFFF);
|
|
}
|
|
|
|
// SCSP (Master)
|
|
else if ((a >= 0x100000) && (a <= 0x10FFFF))
|
|
SCSP_Master_w32(a,d);
|
|
|
|
// SCSP (Slave)
|
|
else if ((a >= 0x300000) && (a <= 0x30FFFF))
|
|
SCSP_Slave_w32(a,d);
|
|
|
|
// Unknown
|
|
else
|
|
printf("68K: Unknown write32 %06X=%08X\n", a, d);
|
|
}
|
|
|
|
|
|
/******************************************************************************
|
|
SCSP 68K Callbacks
|
|
|
|
The SCSP emulator drives the 68K via callbacks.
|
|
******************************************************************************/
|
|
|
|
// Status of IRQ pins (IPL2-0) on 68K
|
|
static int irqLine = 0;
|
|
|
|
// Interrupt acknowledge callback (TODO: don't need this, default behavior in M68K.cpp is fine)
|
|
int IRQAck(int irqLevel)
|
|
{
|
|
M68KSetIRQ(0);
|
|
irqLine = 0;
|
|
return M68K_IRQ_AUTOVECTOR;
|
|
}
|
|
|
|
// SCSP callback for generating IRQs
|
|
void SCSP68KIRQCallback(int irqLevel)
|
|
{
|
|
/*
|
|
* IRQ arbitration logic: only allow higher priority IRQs to be asserted or
|
|
* 0 to clear pending IRQ.
|
|
*/
|
|
if ((irqLevel>irqLine) || (0==irqLevel))
|
|
{
|
|
irqLine = irqLevel;
|
|
|
|
}
|
|
M68KSetIRQ(irqLine);
|
|
}
|
|
|
|
// SCSP callback for running the 68K
|
|
int SCSP68KRunCallback(int numCycles)
|
|
{
|
|
return M68KRun(numCycles);
|
|
}
|
|
|
|
|
|
/******************************************************************************
|
|
Sound Board Emulation
|
|
******************************************************************************/
|
|
|
|
void CSoundBoard::WriteMIDIPort(UINT8 data)
|
|
{
|
|
SCSP_MidiIn(data);
|
|
}
|
|
|
|
void CSoundBoard::RunFrame(void)
|
|
{
|
|
#ifdef SUPERMODEL_SOUND
|
|
SCSP_Update();
|
|
|
|
// Output the audio buffers
|
|
OutputAudio(44100/60, leftBuffer, rightBuffer);
|
|
|
|
// Output to binary file
|
|
INT16 s;
|
|
for (int i = 0; i < 44100/60; i++)
|
|
{
|
|
s = ((UINT16)leftBuffer[i]>>8) | ((leftBuffer[i]&0xFF)<<8);
|
|
fwrite(&s, sizeof(INT16), 1, soundFP); // left channel
|
|
s = ((UINT16)rightBuffer[i]>>8) | ((rightBuffer[i]&0xFF)<<8);
|
|
fwrite(&s, sizeof(INT16), 1, soundFP); // right channel
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void CSoundBoard::Reset(void)
|
|
{
|
|
// lets hope he does better... ->
|
|
memcpy(ram1, soundROM, 16); // copy 68K vector table
|
|
sbSampleBankLo = &sampleROM[0x200000]; // default banks
|
|
sbSampleBankHi = &sampleROM[0x600000];
|
|
M68KReset();
|
|
DebugLog("Sound Board Reset\n");
|
|
}
|
|
|
|
|
|
/******************************************************************************
|
|
Configuration, Initialization, and Shutdown
|
|
******************************************************************************/
|
|
|
|
// Offsets of memory regions within sound board's pool
|
|
#define OFFSET_RAM1 0 // 1 MB SCSP1 RAM
|
|
#define OFFSET_RAM2 0x100000 // 1 MB SCSP2 RAM
|
|
#define MEMORY_POOL_SIZE (0x100000+0x100000)
|
|
|
|
BOOL CSoundBoard::Init(const UINT8 *soundROMPtr, const UINT8 *sampleROMPtr, CIRQ *ppcIRQObjectPtr, unsigned soundIRQBit)
|
|
{
|
|
float memSizeMB = (float)MEMORY_POOL_SIZE/(float)0x100000;
|
|
|
|
// Attach IRQ controller
|
|
ppcIRQ = ppcIRQObjectPtr;
|
|
ppcSoundIRQBit = soundIRQBit;
|
|
|
|
// Receive sound ROMs
|
|
soundROM = soundROMPtr;
|
|
sampleROM = sampleROMPtr;
|
|
|
|
// Allocate all memory for RAM
|
|
memoryPool = new(std::nothrow) UINT8[MEMORY_POOL_SIZE];
|
|
if (NULL == memoryPool)
|
|
return ErrorLog("Insufficient memory for sound board (needs %1.1f MB).", memSizeMB);
|
|
memset(memoryPool, 0, MEMORY_POOL_SIZE);
|
|
|
|
// Set up memory pointers
|
|
ram1 = &memoryPool[OFFSET_RAM1];
|
|
ram2 = &memoryPool[OFFSET_RAM2];
|
|
|
|
// Make global copies of memory pointers for 68K access handlers
|
|
sbRAM1 = ram1;
|
|
sbRAM2 = ram2;
|
|
sbSoundROM = soundROM;
|
|
sbSampleROM = sampleROM;
|
|
sbSampleBankLo = &sampleROM[0x200000];
|
|
sbSampleBankHi = &sampleROM[0x600000];
|
|
|
|
// Initialize 68K core
|
|
M68KInit();
|
|
M68KSetIRQCallback(IRQAck);
|
|
M68KSetFetch8Callback(Read8);
|
|
M68KSetFetch16Callback(Read16);
|
|
M68KSetFetch32Callback(Read32);
|
|
M68KSetRead8Callback(Read8);
|
|
M68KSetRead16Callback(Read16);
|
|
M68KSetRead32Callback(Read32);
|
|
M68KSetWrite8Callback(Write8);
|
|
M68KSetWrite16Callback(Write16);
|
|
M68KSetWrite32Callback(Write32);
|
|
|
|
// Initialize SCSPs
|
|
SCSP_SetBuffers(leftBuffer, rightBuffer, 44100/60);
|
|
SCSP_SetCB(SCSP68KRunCallback, SCSP68KIRQCallback, ppcIRQ, ppcSoundIRQBit);
|
|
SCSP_Init(2);
|
|
SCSP_SetRAM(0, ram1);
|
|
SCSP_SetRAM(1, ram2);
|
|
|
|
// Binary logging
|
|
#ifdef SUPERMODEL_SOUND
|
|
soundFP = fopen("sound.bin","wb"); // delete existing file
|
|
fclose(soundFP);
|
|
soundFP = fopen("sound.bin","ab"); // append mode
|
|
#endif
|
|
|
|
return OKAY;
|
|
}
|
|
|
|
CSoundBoard::CSoundBoard(void)
|
|
{
|
|
memoryPool = NULL;
|
|
ram1 = NULL;
|
|
ram2 = NULL;
|
|
|
|
DebugLog("Built Sound Board\n");
|
|
}
|
|
|
|
static void Reverse16(UINT8 *buf, unsigned size)
|
|
{
|
|
unsigned i;
|
|
UINT8 tmp;
|
|
|
|
for (i = 0; i < size; i += 2)
|
|
{
|
|
tmp = buf[i+0];
|
|
buf[i+0] = buf[i+1];
|
|
buf[i+1] = tmp;
|
|
}
|
|
}
|
|
|
|
CSoundBoard::~CSoundBoard(void)
|
|
{
|
|
#ifdef SUPERMODEL_SOUND
|
|
// close binary log file
|
|
fclose(soundFP);
|
|
//#if 0
|
|
FILE *fp;
|
|
|
|
Reverse16(ram1, 0x100000);
|
|
Reverse16(ram2, 0x100000);
|
|
fp = fopen("scspRAM1", "wb");
|
|
if (NULL != fp)
|
|
{
|
|
fwrite(ram1, sizeof(UINT8), 0x100000, fp);
|
|
fclose(fp);
|
|
printf("dumped %s\n", "scspRAM1");
|
|
|
|
}
|
|
fp = fopen("scspRAM2", "wb");
|
|
if (NULL != fp)
|
|
{
|
|
fwrite(ram2, sizeof(UINT8), 0x100000, fp);
|
|
fclose(fp);
|
|
printf("dumped %s\n", "scspRAM2");
|
|
|
|
}
|
|
//#endif
|
|
#endif
|
|
|
|
SCSP_Deinit();
|
|
|
|
if (memoryPool != NULL)
|
|
{
|
|
delete [] memoryPool;
|
|
memoryPool = NULL;
|
|
}
|
|
ram1 = NULL;
|
|
ram2 = NULL;
|
|
DebugLog("Destroyed Sound Board\n");
|
|
}
|