#include "burnint.h"
#include "okim6295.h"
#include <math.h>

unsigned char* OKIM6295ROM;
unsigned char* OKIM6295SampleInfo[MAX_OKIM6295][4];
unsigned char* OKIM6295SampleData[MAX_OKIM6295][4];

unsigned int nOKIM6295Status[MAX_OKIM6295];

struct OKIM6295ChannelInfo {
	int nOutput;
	int nVolume;
	unsigned int nPosition;
	unsigned int nSampleCount;
	int nSample;
	int nStep;
	int nDelta;
};

static struct {
	int nVolume;
	int nSampleRate;
	int nSampleSize;
	int nFractionalPosition;
	
	// All current settings for each channel
	OKIM6295ChannelInfo ChannelInfo[4];

	// Used for sending commands
	bool bIsCommand;
	int nSampleInfo;

} OKIM6295[MAX_OKIM6295];

static unsigned int OKIM6295VolumeTable[16];
static int OKIM6295DeltaTable[49 * 16];
static int OKIM6295StepShift[8] = {-1, -1, -1, -1, 2, 4, 6, 8};

int OKIM6295Init(int nChip, int nSamplerate, float fMaxVolume)
{
	// Convert volume from percentage
	OKIM6295[nChip].nVolume = int(fMaxVolume * 1024.0 / 100.0 + 0.5f);
	
	OKIM6295[nChip].nSampleRate = nSamplerate;
	OKIM6295[nChip].nSampleSize = (nSamplerate << 12) / nBurnSoundRate;

	OKIM6295[nChip].nFractionalPosition = 0;

	nOKIM6295Status[nChip] = 0;
	OKIM6295[nChip].bIsCommand = false;

	// Compute sample deltas
	for (int i = 0; i < 49; i++) {
		int nStep = (int)(pow(1.1f, (double)i) * 16.0);
		for (int n = 0; n < 16; n++) {
			int nDelta = nStep >> 3;
			if (n & 1) {
				nDelta += nStep >> 2;
			}
			if (n & 2) {
				nDelta += nStep >> 1;
			}
			if (n & 4) {
				nDelta += nStep;
			}
			if (n & 8) {
				nDelta = -nDelta;
			}
			OKIM6295DeltaTable[(i << 4) + n] = nDelta;
		}
	}

	// Compute volume levels
	for (int i = 0; i < 16; i++) {
		double nVolume = 256.0;
		for (int n = i; n > 0; n--) {
			nVolume /= 1.412537545f;
		}
		OKIM6295VolumeTable[i] = (unsigned int)(nVolume + 0.5f);
	}

	for (int nChannel = 0; nChannel < 4; nChannel++) {
		// Set initial bank information
		OKIM6295SampleInfo[nChip][nChannel] = OKIM6295ROM + (nChip * 0x0100000) + (nChannel << 8);
		OKIM6295SampleData[nChip][nChannel] = OKIM6295ROM + (nChip * 0x0100000) + (nChannel << 16);
	}

	return 0;
}

void OKIM6295Exit(int)			// int nChip
{
}

int OKIM6295Scan(int nChip)
{
	SCAN_VAR(OKIM6295[nChip]);
	SCAN_VAR(nOKIM6295Status[nChip]);
	for (int i = 0; i < 4; i++) {
		OKIM6295SampleInfo[nChip][i] -= (unsigned int)OKIM6295ROM;
		SCAN_VAR(OKIM6295SampleInfo[nChip][i]);
		OKIM6295SampleInfo[nChip][i] += (unsigned int)OKIM6295ROM;
		
		OKIM6295SampleData[nChip][i] -= (unsigned int)OKIM6295ROM;
		SCAN_VAR(OKIM6295SampleData[nChip][i]);
		OKIM6295SampleData[nChip][i] += (unsigned int)OKIM6295ROM;
	}
	
	return 0;
}

int OKIM6295Render(int nChip, short* pSoundBuf, int nSegmentLength)
{
	static int nPreviousSample[MAX_OKIM6295], nCurrentSample[MAX_OKIM6295];
	int nVolume = OKIM6295[nChip].nVolume;
	int nFractionalPosition = OKIM6295[nChip].nFractionalPosition;
	
	int nChannel, nDelta, nSample;
	OKIM6295ChannelInfo* pChannelInfo;

	while (nSegmentLength--) {
		while (nFractionalPosition >= 0x1000) {			
			nPreviousSample[nChip] = nCurrentSample[nChip];
			nCurrentSample[nChip] = 0;

			for (nChannel = 0; nChannel < 4; nChannel++) {
				if (nOKIM6295Status[nChip] & (1 << nChannel)) {
					pChannelInfo = &OKIM6295[nChip].ChannelInfo[nChannel];

					// Check for end of sample
					if (pChannelInfo->nSampleCount-- == 0) {
						nOKIM6295Status[nChip] &= ~(1 << nChannel);
						continue;
					}

					// Get new delta from ROM
					if (pChannelInfo->nPosition & 1) {
						nDelta = pChannelInfo->nDelta & 0x0F;
					} else {
						pChannelInfo->nDelta = OKIM6295SampleData[nChip][pChannelInfo->nPosition >> 17][(pChannelInfo->nPosition >> 1) & 0xFFFF];
						nDelta = pChannelInfo->nDelta >> 4;
					}

					// Compute new sample
					nSample = pChannelInfo->nSample + OKIM6295DeltaTable[(pChannelInfo->nStep << 4) + nDelta];
					if (nSample > 2047) {
						nSample = 2047;
					} else {
						if (nSample < -2048) {
							nSample = -2048;
						}
					}
					pChannelInfo->nSample = nSample;
					pChannelInfo->nOutput = (nSample * pChannelInfo->nVolume);

					// Update step value
					pChannelInfo->nStep = pChannelInfo->nStep + OKIM6295StepShift[nDelta & 7];
					if (pChannelInfo->nStep > 48) {
						pChannelInfo->nStep = 48;
					} else {
						if (pChannelInfo->nStep < 0) {
							pChannelInfo->nStep = 0;
						}
					}

					nCurrentSample[nChip] += pChannelInfo->nOutput;

					// Advance sample position
					pChannelInfo->nPosition++;
				}
			}
			
			nFractionalPosition -= 0x1000;

			// Scale all 4 channels
			nCurrentSample[nChip] *= nVolume;
			nCurrentSample[nChip] /= 0x4000;
		}

		// Compute linearly interpolated sample
		nSample = ((nPreviousSample[nChip] * (0x1000 - nFractionalPosition)) + (nCurrentSample[nChip] * nFractionalPosition)) >> 12;

		*pSoundBuf++ += (short)nSample;
		*pSoundBuf++ += (short)nSample;
		
		nFractionalPosition += OKIM6295[nChip].nSampleSize;
	}

	OKIM6295[nChip].nFractionalPosition = nFractionalPosition;

	return 0;
}

void OKIM6295Command(int nChip, unsigned char nCommand)
{
	if (OKIM6295[nChip].bIsCommand) {
		// Process second half of command
		int nChannel, nSampleStart, nSampleCount;
		int nVolume = nCommand & 0x0F;
		nCommand >>= 4;

		OKIM6295[nChip].bIsCommand = false;

		for (nChannel = 0; nChannel < 4; nChannel++) {
			if (nCommand & (0x01 << nChannel)) {
				int nBank = (OKIM6295[nChip].nSampleInfo & 0x0300) >> 8;
				OKIM6295[nChip].nSampleInfo &= 0xFF;

				nSampleStart = OKIM6295SampleInfo[nChip][nBank][OKIM6295[nChip].nSampleInfo + 0];
				nSampleStart <<= 8;
				nSampleStart |= OKIM6295SampleInfo[nChip][nBank][OKIM6295[nChip].nSampleInfo + 1];
				nSampleStart <<= 8;
				nSampleStart |= OKIM6295SampleInfo[nChip][nBank][OKIM6295[nChip].nSampleInfo + 2];
				nSampleStart <<= 1;

				nSampleCount = OKIM6295SampleInfo[nChip][nBank][OKIM6295[nChip].nSampleInfo + 3];
				nSampleCount <<= 8;
				nSampleCount |= OKIM6295SampleInfo[nChip][nBank][OKIM6295[nChip].nSampleInfo + 4];
				nSampleCount <<= 8;
				nSampleCount |= OKIM6295SampleInfo[nChip][nBank][OKIM6295[nChip].nSampleInfo + 5];
				nSampleCount <<= 1;

				if (nSampleCount < 0x80000) {
					nSampleCount -= nSampleStart;
					
					// Start playing channel
					OKIM6295[nChip].ChannelInfo[nChannel].nVolume = OKIM6295VolumeTable[nVolume];
					OKIM6295[nChip].ChannelInfo[nChannel].nPosition = nSampleStart;
					OKIM6295[nChip].ChannelInfo[nChannel].nSampleCount = nSampleCount;
					OKIM6295[nChip].ChannelInfo[nChannel].nSample = 0;
					OKIM6295[nChip].ChannelInfo[nChannel].nStep = 0;

					nOKIM6295Status[nChip] |= nCommand;
				}
			}
		}

	} else {
		// Process command
		if (nCommand & 0x80) {
			OKIM6295[nChip].nSampleInfo = (nCommand & 0x7F) << 3;
			OKIM6295[nChip].bIsCommand = true;
		} else {
			// Stop playing samples
			nCommand >>= 3;
			nOKIM6295Status[nChip] &= ~nCommand;
		}
	}
}
