//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");
}