#include <math.h>
#include "toaplan.h"

unsigned char* YMZ280BROM;

unsigned int nYMZ280BRegister;
unsigned int nYMZ280BStatus;

static int* pBuffer = NULL;

static int nYMZ280BFrequency;

//static int YMZ280BDeltaTable[49 * 16];
static int YMZ280BDeltaTable[16];

//static int YMZ280BStepShift[8] = {-1, -1, -1, -1, 2, 4, 6, 8};
static int YMZ280BStepShift[8] = {0x0e6, 0x0e6, 0x0e6, 0x0e6, 0x133, 0x199, 0x200, 0x266};

static struct {
	bool bEnabled;
	bool bLoop;
	unsigned int nMode;
	unsigned int nFrequency;
	int nOutput;
	int nSample;
	unsigned int nSampleSize;
	unsigned int nPosition;
	unsigned int nSampleCount;
	unsigned int nFractionalPosition;
	int nStep;
	unsigned int nSampleStart;
	unsigned int nSampleStop;
	unsigned int nLoopStart;
	unsigned int nLoopStop;
	unsigned int nVolume;
	unsigned int nPan;
} YMZ280BChannelInfo[8];

int YMZ280BInit(int nSamplerate)
{
	// 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;
			}
			YMZ280BDeltaTable[(i << 4) + n] = nDelta;
		}
	}
*/
	
	for (int n = 0; n < 16; n++) {
		int nDelta = (n & 7) * 2 + 1;
		if (n & 8) {
			nDelta = -nDelta;
		}
		YMZ280BDeltaTable[n] = nDelta;
	}

	nYMZ280BFrequency = nSamplerate;
	
	free(pBuffer);
	pBuffer = (int*)malloc(nBurnSoundRate *  2);

	return 0;
}

void YMZ280BExit()
{
	free(pBuffer);
	pBuffer = NULL;
}

int YMZ280BRender(short* pSoundBuf, int nSegmentLength)
{
	int nChannel, nDelta, nSample;

	int i = nSegmentLength;
	int* buf = pBuffer;
	while (i--) {
		*buf++ = 0;
		*buf++ = 0;
	}
	
	for (nChannel = 0; nChannel < 8; nChannel++) {
		i = nSegmentLength;
		buf = pBuffer;
		
		if (YMZ280BChannelInfo[nChannel].bEnabled) {
			while (i--) {
				while (YMZ280BChannelInfo[nChannel].nFractionalPosition >= 0x1000) {

					// Check for end of sample
					if (YMZ280BChannelInfo[nChannel].nSampleCount-- == 0) {
						YMZ280BChannelInfo[nChannel].nOutput = 0;
						YMZ280BChannelInfo[nChannel].bEnabled = false;
						nYMZ280BStatus = 1;
						continue;
					}

					// Get next value & compute delta
					nDelta = YMZ280BROM[YMZ280BChannelInfo[nChannel].nPosition >> 1];
					if (YMZ280BChannelInfo[nChannel].nPosition & 1) {
						nDelta &= 0x0F;
					} else {
						nDelta >>= 4;
					}

//					nSample = YMZ280BChannelInfo[nChannel].nSample + YMZ280BDeltaTable[(YMZ280BChannelInfo[nChannel].nStep << 4) + nDelta];
					nSample = YMZ280BChannelInfo[nChannel].nSample + (YMZ280BChannelInfo[nChannel].nStep * YMZ280BDeltaTable[nDelta]) / 8;
					if (nSample > 32767) {
						nSample = 32767;
					} else {
						if (nSample < -32768) {
							nSample = -32768;
						}
					}
					YMZ280BChannelInfo[nChannel].nSample = nSample;
//					YMZ280BChannelInfo[nChannel].nOutput = nSample;// * 16;//(nSample * YMZ280BChannelInfo[nChannel].nVolume) / 256);
					YMZ280BChannelInfo[nChannel].nOutput = (nSample * 64 /*YMZ280BChannelInfo[nChannel].nVolume*/) / 255;

//					printf("%08X ", YMZ280BChannelInfo[nChannel].nSample);

					YMZ280BChannelInfo[nChannel].nStep = (YMZ280BChannelInfo[nChannel].nStep * YMZ280BStepShift[nDelta & 7]) >> 8;
					if (YMZ280BChannelInfo[nChannel].nStep > 0x6000) {
						YMZ280BChannelInfo[nChannel].nStep = 0x6000;
					} else {
						if (YMZ280BChannelInfo[nChannel].nStep < 127) {
							YMZ280BChannelInfo[nChannel].nStep = 127;
						}
					}

					// Advance sample position
					YMZ280BChannelInfo[nChannel].nPosition++;
					YMZ280BChannelInfo[nChannel].nFractionalPosition -= 0x1000;
				}
				*buf++ += YMZ280BChannelInfo[nChannel].nOutput;
				*buf++ += YMZ280BChannelInfo[nChannel].nOutput;
				
				YMZ280BChannelInfo[nChannel].nFractionalPosition += YMZ280BChannelInfo[nChannel].nSampleSize;
			}
		}
	}

	i = nSegmentLength * 2;
	buf = pBuffer;
	while (i--) {
		nSample = *buf++;
		if (nSample > 32767) {
			nSample = 32767;
		} else {
			if (nSample < -32768) {
				nSample = -32768;
			}
		}

		*pSoundBuf++ = (short)nSample;
	}
	
	return 0;
}

void YMZ280BWriteRegister(unsigned char nValue)
{
	if (nYMZ280BRegister < 0x80) {
		int nChannel = (nYMZ280BRegister >> 2) & 0x07;

		switch (nYMZ280BRegister & 0x63) {
			
			// Miscellaneous
			case 0:															// Frequency
				YMZ280BChannelInfo[nChannel].nFrequency &= 0x0100;
				YMZ280BChannelInfo[nChannel].nFrequency |= nValue;
				break;
			case 1:															// Start/mode/freq
				YMZ280BChannelInfo[nChannel].nFrequency &= 0x00FF;
				YMZ280BChannelInfo[nChannel].nFrequency |= ((nValue & 1) << 8);
				
				YMZ280BChannelInfo[nChannel].bLoop = (nValue & 0x10);
				YMZ280BChannelInfo[nChannel].nMode = ((nValue >> 5) & 0x03);

				if (nValue & 0x80) {
					YMZ280BChannelInfo[nChannel].bEnabled = true;
					YMZ280BChannelInfo[nChannel].nPosition = YMZ280BChannelInfo[nChannel].nSampleStart;
					YMZ280BChannelInfo[nChannel].nFractionalPosition = 0;
					YMZ280BChannelInfo[nChannel].nStep = 0;
					YMZ280BChannelInfo[nChannel].nSample = 0;
					YMZ280BChannelInfo[nChannel].nOutput = 0;
					
					if (YMZ280BChannelInfo[nChannel].bLoop) {
						YMZ280BChannelInfo[nChannel].nSampleCount = YMZ280BChannelInfo[nChannel].nLoopStop - YMZ280BChannelInfo[nChannel].nLoopStart;
					} else {
						YMZ280BChannelInfo[nChannel].nSampleCount = YMZ280BChannelInfo[nChannel].nSampleStop - YMZ280BChannelInfo[nChannel].nSampleStart;
					}

					double ding = (double)(YMZ280BChannelInfo[nChannel].nFrequency + 1) * nYMZ280BFrequency / 32;
					
					YMZ280BChannelInfo[nChannel].nSampleSize = (unsigned int)(ding / nBurnSoundRate);

					printf("Sample Start: %08X - Stop: %08X.\n",YMZ280BChannelInfo[nChannel].nSampleStart, YMZ280BChannelInfo[nChannel].nSampleStop);


				} else {
//					if (YMZ280BChannelInfo[nChannel].bLoop) {
						YMZ280BChannelInfo[nChannel].bEnabled = false;
//					}
				}

			case 2:															// Volume
				YMZ280BChannelInfo[nChannel].nVolume = nValue;
				break;
			case 3:															// Pan
				YMZ280BChannelInfo[nChannel].nPan = nValue & 0x0F;
				break;

			// Sample start address
			case 0x20:
				YMZ280BChannelInfo[nChannel].nSampleStart &= 0x0001FFFE;
				YMZ280BChannelInfo[nChannel].nSampleStart |= (nValue << 17);
				break;
			case 0x40:
				YMZ280BChannelInfo[nChannel].nSampleStart &= 0x01FE01FE;
				YMZ280BChannelInfo[nChannel].nSampleStart |= (nValue << 9);
				break;
			case 0x60:
				YMZ280BChannelInfo[nChannel].nSampleStart &= 0x01FFFE00;
				YMZ280BChannelInfo[nChannel].nSampleStart |= (nValue << 1);
				break;
			
			// Sample stop address
			case 0x23:
				YMZ280BChannelInfo[nChannel].nSampleStop &= 0x0001FFFE;
				YMZ280BChannelInfo[nChannel].nSampleStop |= (nValue << 17);
				break;
			case 0x43:
				YMZ280BChannelInfo[nChannel].nSampleStop &= 0x01FE01FE;
				YMZ280BChannelInfo[nChannel].nSampleStop |= (nValue << 9);
				break;
			case 0x63:
				YMZ280BChannelInfo[nChannel].nSampleStop &= 0x01FFFE00;
				YMZ280BChannelInfo[nChannel].nSampleStop |= (nValue << 1);
				break;
		
			// Loop start address
			case 0x21:
				YMZ280BChannelInfo[nChannel].nLoopStart &= 0x0001FFFE;
				YMZ280BChannelInfo[nChannel].nLoopStart |= (nValue << 17);
				break;
			case 0x41:
				YMZ280BChannelInfo[nChannel].nLoopStart &= 0x01FE01FE;
				YMZ280BChannelInfo[nChannel].nLoopStart |= (nValue << 9);
				break;
			case 0x61:
				YMZ280BChannelInfo[nChannel].nLoopStart &= 0x01FFFE00;
				YMZ280BChannelInfo[nChannel].nLoopStart |= (nValue << 1);
				break;

			// Loop stop address
			case 0x22:
				YMZ280BChannelInfo[nChannel].nLoopStop &= 0x0001FFFE;
				YMZ280BChannelInfo[nChannel].nLoopStop |= (nValue << 17);
				break;
			case 0x42:
				YMZ280BChannelInfo[nChannel].nLoopStop &= 0x01FE01FE;
				YMZ280BChannelInfo[nChannel].nLoopStop |= (nValue << 9);
				break;
			case 0x62:
				YMZ280BChannelInfo[nChannel].nLoopStop &= 0x01FFFE00;
				YMZ280BChannelInfo[nChannel].nLoopStop |= (nValue << 1);
				break;

		}
   	} else {
		if (nYMZ280BRegister == 0xFE) {
			// Set IRQ mask
//			printf("YMZ280B IRQ mask written -> %02x.\n", nValue);
		} else {
			if (nYMZ280BRegister == 0xFF) {
				// Start/stop playing, enable/disable IRQ
//				printf("YMZ280B enable register written -> %02x.\n", nValue);
			}
		}
	}
}

