// VGMPlay_AddFmts.c: C Source File for playback of additional non-VGM formats

#ifdef ADDITIONAL_FORMATS

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <string.h>
#include "stdbool.h"
#include <math.h>

#ifdef WIN32
//#include <windows.h>
void __stdcall Sleep(unsigned int dwMilliseconds);
#else
#define	Sleep(msec)		usleep(msec * 1000)
#endif

#include <zlib.h>

#include "chips/mamedef.h"

#include "VGMPlay.h"

#include "ChipMapper.h"

// Structures for DRO and CMF files
typedef struct _cmf_file_header
{
	UINT32 fccCMF;
	UINT16 shtVersion;
	UINT16 shtOffsetInsData;
	UINT16 shtOffsetMusData;
	UINT16 shtTickspQuarter;
	UINT16 shtTickspSecond;
	UINT16 shtOffsetTitle;
	UINT16 shtOffsetAuthor;
	UINT16 shtOffsetComments;
	UINT8 bytChnUsed[0x10];
	UINT16 shtInstrumentCount;
	UINT16 shtTempo;
} CMF_HEADER;
typedef struct _cmf_instrument_table
{
	UINT8 Character[0x02];
	UINT8 ScaleLevel[0x02];
	UINT8 AttackDelay[0x02];
	UINT8 SustnRelease[0x02];
	UINT8 WaveSelect[0x02];
	UINT8 FeedbConnect;
	UINT8 Reserved[0x5];
} CMF_INSTRUMENT;

typedef struct _dro_file_header
{
	char cSignature[0x08];
	UINT16 iVersionMajor;
	UINT16 iVersionMinor;
} DRO_HEADER;
typedef struct _dro_version_header_1
{
	UINT32 iLengthMS;
	UINT32 iLengthBytes;
	UINT32 iHardwareType;
} DRO_VER_HEADER_1;
typedef struct _dro_version_header_2
{
	UINT32 iLengthPairs;
	UINT32 iLengthMS;
	UINT8 iHardwareType;
	UINT8 iFormat;
	UINT8 iCompression;
	UINT8 iShortDelayCode;
	UINT8 iLongDelayCode;
	UINT8 iCodemapLength;
} DRO_VER_HEADER_2;

#define FCC_CMF		0x464D5443	// 'CTMF'
#define FCC_DRO1	0x41524244	// 'DBRA'
#define FCC_DRO2	0x4C504F57	// 'WOPL'

extern UINT32 GetGZFileLength(const char* FileName);
//bool OpenOtherFile(const char* FileName)

INLINE UINT16 ReadLE16(const UINT8* Data);
INLINE UINT32 ReadLE32(const UINT8* Data);
INLINE int gzgetLE32(gzFile hFile, UINT32* RetValue);

static UINT32 GetMIDIDelay(UINT32* DelayLen);
static UINT16 MIDINote2FNum(UINT8 Note, INT8 Pitch);
static void SendMIDIVolume(UINT8 ChipID, UINT8 Channel, UINT8 Command,
						   UINT8 ChnIns, UINT8 Volume);
//void InterpretOther(UINT32 SampleCount);

INLINE INT32 SampleVGM2Playback(INT32 SampleVal);
INLINE INT32 SamplePlayback2VGM(INT32 SampleVal);


extern UINT32 SampleRate;	// Note: also used by some sound cores to determinate the chip sample rate

extern UINT8 FileMode;
extern VGM_HEADER VGMHead;
extern UINT32 VGMDataLen;
extern UINT8* VGMData;
extern GD3_TAG VGMTag;

CMF_HEADER CMFHead;
UINT16 CMFInsCount;
CMF_INSTRUMENT* CMFIns;

DRO_HEADER DROHead;
DRO_VER_HEADER_2 DROInf;
UINT8* DROCodemap;


extern UINT32 VGMPos;
extern INT32 VGMSmplPos;
extern INT32 VGMSmplPlayed;
extern INT32 VGMSampleRate;
extern UINT32 BlocksSent;
extern UINT32 BlocksPlayed;
bool VGMEnd;
extern bool EndPlay;
extern bool PausePlay;
extern bool FadePlay;
extern bool ForceVGMExec;

extern UINT32 VGMMaxLoop;
UINT32 CMFMaxLoop;
extern UINT32 VGMMaxLoopM;
extern UINT32 VGMCurLoop;

extern UINT32 FadeTime;
extern UINT32 VGMMaxLoop;
extern bool ErrorHappened;

extern UINT8 CmdList[0x100];

bool OpenOtherFile(const char* FileName)
{
	gzFile hFile;
	UINT32 FileSize;
	UINT32 fccHeader;
	UINT32 CurPos;
	UINT32 TempLng;
	UINT16 FileVer;
	const char* TempStr;
	DRO_VER_HEADER_1 DRO_V1;
	
	FileSize = GetGZFileLength(FileName);
	
	FileMode = 0x00;
	hFile = gzopen(FileName, "rb");
	if (hFile == NULL)
		return false;
	
	gzseek(hFile, 0x00, SEEK_SET);
	gzgetLE32(hFile, &fccHeader);
	switch(fccHeader)
	{
	case FCC_VGM:
		FileMode = 0xFF;	// should already be opened
		break;
	case FCC_CMF:
		FileMode = 0x01;
		break;
	case FCC_DRO1:
		gzgetLE32(hFile, &fccHeader);
		if (fccHeader == FCC_DRO2)
			FileMode = 0x02;
		else
			FileMode = 0xFF;
		break;
	default:
		FileMode = 0xFF;
		break;
	}
	if (FileMode == 0xFF)
		goto OpenErr;
	
	VGMTag.strTrackNameE = L"";
	VGMTag.strTrackNameJ = L"";
	VGMTag.strGameNameE = L"";
	VGMTag.strGameNameJ = L"";
	VGMTag.strSystemNameE = L"";
	VGMTag.strSystemNameJ = L"";
	VGMTag.strAuthorNameE = L"";
	VGMTag.strAuthorNameJ = L"";
	VGMTag.strReleaseDate = L"";
	VGMTag.strCreator = L"";
	VGMTag.strNotes = L"";
	
	switch(FileMode)
	{
	case 0x00:	// VGM File
		break;
	case 0x01:	// CMF File
	case 0x02:	// DosBox RAW OPL
		VGMTag.strGameNameE = (wchar_t*)malloc(0x10 * sizeof(wchar_t*));
		wcscpy(VGMTag.strGameNameE, L"    Player");
		VGMTag.strSystemNameE = L"PC / MS-DOS";
		break;
	}
	
	VGMDataLen = FileSize;
	
	switch(FileMode)
	{
	case 0x00:	// VGM File
		// already done by OpenVGMFile
		break;
	case 0x01:	// CMF File
		// Read Data
		VGMData = (UINT8*)malloc(VGMDataLen);
		if (VGMData == NULL)
			goto OpenErr;
		gzseek(hFile, 0x00, SEEK_SET);
		gzread(hFile, VGMData, VGMDataLen);
		
#ifndef VGM_BIG_ENDIAN
		memcpy(&CMFHead, &VGMData[0x00], sizeof(CMF_HEADER));
#else
		CMFHead.fccCMF = ReadLE32(&VGMData[0x00]);
		CMFHead.shtVersion = ReadLE16(&VGMData[0x04]);
		CMFHead.shtOffsetInsData = ReadLE16(&VGMData[0x06]);
		CMFHead.shtOffsetMusData = ReadLE16(&VGMData[0x08]);
		CMFHead.shtTickspQuarter = ReadLE16(&VGMData[0x0A]);
		CMFHead.shtTickspSecond = ReadLE16(&VGMData[0x0C]);
		CMFHead.shtOffsetTitle = ReadLE16(&VGMData[0x0E]);
		CMFHead.shtOffsetAuthor = ReadLE16(&VGMData[0x10]);
		CMFHead.shtOffsetComments = ReadLE16(&VGMData[0x12]);
		memcpy(CMFHead.bytChnUsed, &VGMData[0x14], 0x10);
		CMFHead.shtInstrumentCount = ReadLE16(&VGMData[0x24]);
		CMFHead.shtTempo = ReadLE16(&VGMData[0x26]);
#endif
		
		if (CMFHead.shtVersion == 0x0100)
		{
			CMFHead.shtInstrumentCount &= 0x00FF;
			CMFHead.shtTempo = (UINT16)(60.0 *
								CMFHead.shtTickspQuarter / CMFHead.shtTickspSecond + 0.5);
		}
		
		if (CMFHead.shtOffsetTitle)
		{
			TempStr = (char*)&VGMData[CMFHead.shtOffsetTitle];
			TempLng = strlen(TempStr) + 0x01;
			VGMTag.strTrackNameE = (wchar_t*)malloc(TempLng * sizeof(wchar_t));
			mbstowcs(VGMTag.strTrackNameE, TempStr, TempLng);
		}
		VGMTag.strGameNameE[0x00] = 'C';
		VGMTag.strGameNameE[0x01] = 'M';
		VGMTag.strGameNameE[0x02] = 'F';
		if (CMFHead.shtOffsetAuthor)
		{
			TempStr = (char*)&VGMData[CMFHead.shtOffsetAuthor];
			TempLng = strlen(TempStr) + 0x01;
			VGMTag.strAuthorNameE = (wchar_t*)malloc(TempLng * sizeof(wchar_t));
			mbstowcs(VGMTag.strAuthorNameE, TempStr, TempLng);
		}
		if (CMFHead.shtOffsetComments)
		{
			TempStr = (char*)&VGMData[CMFHead.shtOffsetComments];
			TempLng = strlen(TempStr) + 0x01;
			VGMTag.strNotes = (wchar_t*)malloc(TempLng * sizeof(wchar_t));
			mbstowcs(VGMTag.strNotes, TempStr, TempLng);
		}
		
		CMFInsCount = CMFHead.shtInstrumentCount;
		TempLng = CMFInsCount * sizeof(CMF_INSTRUMENT);
		CMFIns = (CMF_INSTRUMENT*)malloc(TempLng);
		memcpy(CMFIns, &VGMData[CMFHead.shtOffsetInsData], TempLng);
		
		memset(&VGMHead, 0x00, sizeof(VGM_HEADER));
		VGMHead.lngEOFOffset = VGMDataLen;
		VGMHead.lngVersion = CMFHead.shtVersion;
		VGMHead.lngDataOffset = CMFHead.shtOffsetMusData;
		VGMSampleRate = CMFHead.shtTickspSecond;
		VGMHead.lngTotalSamples = 0;
		VGMHead.lngHzYM3812 = 3579545 | 0x40000000;
		
		break;
	case 0x02:	// DosBox RAW OPL
		// Read Data
		VGMData = (UINT8*)malloc(VGMDataLen);
		if (VGMData == NULL)
			goto OpenErr;
		gzseek(hFile, 0x00, SEEK_SET);
		VGMDataLen = gzread(hFile, VGMData, VGMDataLen);
		
		VGMTag.strGameNameE[0x00] = 'D';
		VGMTag.strGameNameE[0x01] = 'R';
		VGMTag.strGameNameE[0x02] = 'O';
		
		memset(&VGMHead, 0x00, sizeof(VGM_HEADER));
		CurPos = 0x00;
#ifndef VGM_BIG_ENDIAN
		memcpy(&DROHead, &VGMData[CurPos], sizeof(DRO_HEADER));
#else
		memcpy(DROHead.cSignature,			&VGMData[CurPos + 0x00], 0x08);
		DROHead.iVersionMajor = ReadLE16(	&VGMData[CurPos + 0x08]);
		DROHead.iVersionMinor = ReadLE16(	&VGMData[CurPos + 0x0A]);
#endif
		CurPos += sizeof(DRO_HEADER);
		
		memcpy(&TempLng, &VGMData[0x08], sizeof(UINT32));
		if (TempLng & 0xFF00FF00)
		{
			// DosBox Version 0.61
			// this version didn't contain Version Bytes
			CurPos = 0x08;
			DROHead.iVersionMajor = 0x00;
			DROHead.iVersionMinor = 0x00;
		}
		else if (! (TempLng & 0x0000FFFF))
		{
			// DosBox Version 0.63
			// the order of the Version Bytes is swapped in this version
			FileVer = DROHead.iVersionMinor;
			if (FileVer == 0x01)
			{
				DROHead.iVersionMinor = DROHead.iVersionMajor;
				DROHead.iVersionMajor = FileVer;
			}
		}
		VGMHead.lngEOFOffset = VGMDataLen;
		VGMHead.lngVersion = (DROHead.iVersionMajor << 8) |
							((DROHead.iVersionMinor & 0xFF) << 0);
		VGMSampleRate = 1000;
		
		if (DROHead.iVersionMajor > 2)
			DROHead.iVersionMajor = 2;
		switch(DROHead.iVersionMajor)
		{
		case 0:	// Version 0 (DosBox Version 0.61)
		case 1:	// Version 1 (DosBox Version 0.63)
			switch(DROHead.iVersionMajor)
			{
			case 0:	// Version 0
				DRO_V1.iLengthMS = ReadLE32(&VGMData[CurPos + 0x00]);
				DRO_V1.iLengthBytes = ReadLE32(&VGMData[CurPos + 0x04]);
				DRO_V1.iHardwareType = VGMData[CurPos + 0x08];
				CurPos += 0x09;
				break;
			case 1:	// Version 1
				DRO_V1.iLengthMS = ReadLE32(&VGMData[CurPos + 0x00]);
				DRO_V1.iLengthBytes = ReadLE32(&VGMData[CurPos + 0x04]);
				DRO_V1.iHardwareType = ReadLE32(&VGMData[CurPos + 0x08]);
				CurPos += 0x0C;
				break;
			}
			
			DROInf.iLengthPairs = DRO_V1.iLengthBytes >> 1;
			DROInf.iLengthMS = DRO_V1.iLengthMS;
			switch(DRO_V1.iHardwareType)
			{
			case 0x01:	// Single OPL3
				DROInf.iHardwareType = 0x02;
				break;
			case 0x02:	// Dual OPL2
				DROInf.iHardwareType = 0x01;
				break;
			default:
				DROInf.iHardwareType = (UINT8)DRO_V1.iHardwareType;
				break;
			}
			DROInf.iFormat = 0x00;
			DROInf.iCompression = 0x00;
			DROInf.iShortDelayCode = 0x00;
			DROInf.iLongDelayCode = 0x01;
			DROInf.iCodemapLength = 0x00;
			
			break;
		case 2:	// Version 2 (DosBox Version 0.73)
			// sizeof(DRO_VER_HEADER_2) returns 0x10, but the exact size is 0x0E
			//memcpy(&DROInf, &VGMData[CurPos], 0x0E);
			DROInf.iLengthPairs =	ReadLE32(	&VGMData[CurPos + 0x00]);
			DROInf.iLengthMS =		ReadLE32(	&VGMData[CurPos + 0x04]);
			DROInf.iHardwareType =				 VGMData[CurPos + 0x08];
			DROInf.iFormat =					 VGMData[CurPos + 0x09];
			DROInf.iCompression =				 VGMData[CurPos + 0x0A];
			DROInf.iShortDelayCode =			 VGMData[CurPos + 0x0B];
			DROInf.iLongDelayCode =				 VGMData[CurPos + 0x0C];
			DROInf.iCodemapLength =				 VGMData[CurPos + 0x0D];
			CurPos += 0x0E;
			
			break;
		}
		
		if (DROInf.iCodemapLength)
		{
			DROCodemap = (UINT8*)malloc(DROInf.iCodemapLength * sizeof(UINT8));
			memcpy(DROCodemap, &VGMData[CurPos], DROInf.iCodemapLength);
			CurPos += DROInf.iCodemapLength;
		}
		else
		{
			DROCodemap = NULL;
		}
		
		VGMHead.lngDataOffset = CurPos;
		VGMHead.lngTotalSamples = DROInf.iLengthMS;
		switch(DROInf.iHardwareType)
		{
		case 0x00:	// Single OPL2 Chip
			VGMHead.lngHzYM3812 = 3579545;
			break;
		case 0x01:	// Dual OPL2 Chip
			VGMHead.lngHzYM3812 = 3579545 | 0xC0000000;
			break;
		case 0x02:	// Single OPL3 Chip
			VGMHead.lngHzYMF262 = 14318180;
			break;
		default:
			VGMHead.lngHzYM3812 = 3579545 | 0x40000000;
			break;
		}
		
		break;
	}
	
	gzclose(hFile);
	return true;

OpenErr:

	gzclose(hFile);
	return false;
}

INLINE UINT16 ReadLE16(const UINT8* Data)
{
	// read 16-Bit Word (Little Endian/Intel Byte Order)
#ifndef VGM_BIG_ENDIAN
	return *(UINT16*)Data;
#else
	return (Data[0x01] << 8) | (Data[0x00] << 0);
#endif
}

INLINE UINT32 ReadLE32(const UINT8* Data)
{
	// read 32-Bit Word (Little Endian/Intel Byte Order)
#ifndef VGM_BIG_ENDIAN
	return	*(UINT32*)Data;
#else
	return	(Data[0x03] << 24) | (Data[0x02] << 16) |
			(Data[0x01] <<  8) | (Data[0x00] <<  0);
#endif
}

INLINE int gzgetLE32(gzFile hFile, UINT32* RetValue)
{
#ifndef VGM_BIG_ENDIAN
	return gzread(hFile, RetValue, 0x04);
#else
	int RetVal;
	UINT8 Data[0x04];
	
	RetVal = gzread(hFile, Data, 0x04);
	*RetValue =	(Data[0x03] << 24) | (Data[0x02] << 16) |
				(Data[0x01] <<  8) | (Data[0x00] <<  0);
	return RetVal;
#endif
}

static UINT32 GetMIDIDelay(UINT32* DelayLen)
{
	UINT32 CurPos;
	UINT32 DelayVal;
	
	CurPos = VGMPos;
	DelayVal = 0x00;
	do
	{
		DelayVal = (DelayVal << 7) | (VGMData[CurPos] & 0x7F);
	} while(VGMData[CurPos ++] & 0x80);
	
	if (DelayLen != NULL)
		*DelayLen = CurPos - VGMPos;
	return DelayVal;
}

static UINT16 MIDINote2FNum(UINT8 Note, INT8 Pitch)
{
	const double CHIP_RATE = 3579545.0 / 72.0;	// ~49716
	double FreqVal;
	INT8 BlockVal;
	UINT16 KeyVal;
	
	FreqVal = 440.0 * pow(2, (Note - 69 + Pitch / 256.0) / 12.0);
	BlockVal = (Note / 12) - 1;
	if (BlockVal < 0x00)
		BlockVal = 0x00;
	else if (BlockVal > 0x07)
		BlockVal = 0x07;
	KeyVal = (UINT16)(FreqVal * pow(2, 20 - BlockVal) / CHIP_RATE + 0.5);
	
	return (BlockVal << 10) | KeyVal;	// << (8+2)
}

static void SendMIDIVolume(UINT8 ChipID, UINT8 Channel, UINT8 Command,
						   UINT8 ChnIns, UINT8 Volume)
{
	bool RhythmOn;
	UINT8 TempByt;
	//UINT16 TempSht;
	UINT32 TempLng;
	UINT8 OpBase;	// Operator Base
	CMF_INSTRUMENT* TempIns;
	UINT8 OpMask;
	INT8 OpVol;
	INT8 NoteVol;
	
	RhythmOn = (Channel >> 7) & 0x01;
	Channel &= 0x7F;
	
	// Refresh Total Level (Volume)
	TempIns = CMFIns + ChnIns;
	OpBase = (Channel / 0x03) * 0x08 + (Channel % 0x03);
	
	if (! RhythmOn)
	{
		TempLng = 0x01;
		OpMask = 0x03;
	}
	else
	{
		//TempLng = 0x01;
		switch(Command & 0x0F)
		{
		case 0x0B:	// Bass Drum
			OpMask = 0x00;
			break;
		case 0x0F:	// Hi Hat
			OpMask = 0x00;
			break;
		case 0x0C:	// Snare Drum
			OpMask = 0x01;
			break;
		case 0x0D:	// Tom Tom
			OpMask = 0x00;
			break;
		case 0x0E:	// Cymbal
			OpMask = 0x01;
			break;
		default:
			OpMask = 0x00;
			break;
		}
		TempLng = OpMask;
		OpMask *= 0x03;
	}
	
	// Verified with PLAY.EXE
	OpVol = (Volume + 0x04) >> 3;
	OpVol = 0x10 - (OpVol << 1);
	if (OpVol < 0x00)
		OpVol >>= 1;
	NoteVol = (TempIns->ScaleLevel[TempLng] & 0x3F) + OpVol;
	if (NoteVol < 0x00)
		NoteVol = 0x00;
	
	TempByt = NoteVol | TempIns->ScaleLevel[TempLng] & 0xC0;
	chip_reg_write(0x09, ChipID, 0x00, 0x40 | (OpBase + OpMask), TempByt);
	
	return;
}

void InterpretOther(UINT32 SampleCount)
{
	static UINT8 LastCmd = 0x90;
	static UINT8 DrumReg[0x02] = {0x00, 0x00};
	static UINT8 ChnIns[0x10];
	static UINT8 ChnNote[0x20];
	static INT8 ChnPitch[0x10];
	INT32 SmplPlayed;
	UINT8 Command;
	UINT8 Channel;
	UINT8 TempByt;
	UINT16 TempSht;
	UINT32 TempLng;
	UINT32 DataLen;
	static UINT8 CurChip = 0x00;
	UINT8 OpBase;	// Operator Base
	CMF_INSTRUMENT* TempIns;
	bool RhythmOn;
	bool NoteOn;
	UINT8 OpMask;
	
	if (VGMEnd)
		return;
	if (PausePlay && ! ForceVGMExec)
		return;
	
	switch(FileMode)
	{
	case 0x01:	// CMF File Mode
		if (! SampleCount)
		{
			memset(ChnIns, 0xFF, 0x10);
			memset(ChnNote, 0xFF, 0x20);
			memset(ChnPitch, 0x00, 0x10);
			
			TempLng = VGMPos;
			SmplPlayed = VGMSmplPos;
			VGMPos = VGMHead.lngDataOffset;
			RhythmOn = false;
			while(! RhythmOn)
			{
				VGMSmplPos += GetMIDIDelay(&DataLen);
				VGMPos += DataLen;
				
				Command = VGMData[VGMPos];
				if (Command & 0x80)
					VGMPos ++;
				else
					Command = LastCmd;
				Channel = Command & 0x0F;
				
				switch(Command & 0xF0)
				{
				case 0xF0:	// End Of File
					switch(Command)
					{
					case 0xFF:
						switch(VGMData[VGMPos + 0x00])
						{
						case 0x2F:
							VGMHead.lngTotalSamples = VGMSmplPos;
							VGMHead.lngLoopSamples = VGMHead.lngTotalSamples;
							RhythmOn = true;
							break;
						}
						break;
					default:
						VGMPos += 0x01;
					}
					break;
				case 0x80:
				case 0x90:
				case 0xA0:
				case 0xB0:
				case 0xE0:
					VGMPos += 0x02;
					break;
				case 0xC0:
				case 0xD0:
					VGMPos += 0x01;
					break;
				}
				if (Command < 0xF0)
					LastCmd = Command;
			}
			
			VGMPos = TempLng;
			VGMSmplPos = SmplPlayed;
		}
		
		SmplPlayed = SamplePlayback2VGM(VGMSmplPlayed + SampleCount);
		while(true)
		{
			TempLng = GetMIDIDelay(&DataLen);
			if (VGMSmplPos + (INT32)TempLng > SmplPlayed)
				break;
			VGMSmplPos += TempLng;
			VGMPos += DataLen;
			
			Command = VGMData[VGMPos];
			if (Command & 0x80)
				VGMPos ++;
			else
				Command = LastCmd;
			Channel = Command & 0x0F;
			
			if (DrumReg[0x00] & 0x20)
			{
				if (Channel < 0x0B)
				{
					CurChip = Channel / 0x06;
					Channel = Channel % 0x06;
				}
				else
				{
					Channel -= 0x0B;
					CurChip = Channel / 0x03;
					Channel = Channel % 0x03;
					if (CurChip == 0x01 && Channel == 0x00)
						Channel = 0x02;
					Channel += 0x06;
					// Map all drums to one chip
					CurChip = 0x00;
				}
			}
			else
			{
				CurChip = Channel / 0x09;
				Channel = Channel % 0x09;
			}
			CurChip = 0x00;
			
			RhythmOn = (Channel >= 0x06) && (DrumReg[CurChip] & 0x20);
			switch(Command & 0xF0)
			{
			case 0xF0:	// End Of File
				switch(Command)
				{
				case 0xFF:
					switch(VGMData[VGMPos + 0x00])
					{
					case 0x2F:
						if (CMFMaxLoop != 0x01)
						{
							VGMPos = VGMHead.lngDataOffset;
							VGMSmplPos -= VGMHead.lngLoopSamples;
							VGMSmplPlayed -= SampleVGM2Playback(VGMHead.lngLoopSamples);
							SmplPlayed = SamplePlayback2VGM(VGMSmplPlayed + SampleCount);
							VGMCurLoop ++;
							
							if (CMFMaxLoop && VGMCurLoop >= CMFMaxLoop)
								FadePlay = true;
							if (FadePlay && ! FadeTime)
								VGMEnd = true;
						}
						else
						{
							VGMEnd = true;
							break;
						}
						break;
					}
					break;
				default:
					VGMPos += 0x01;
				}
				break;
			case 0x80:
			case 0x90:
				TempSht = MIDINote2FNum(VGMData[VGMPos + 0x00], ChnPitch[Channel]);
				if ((Command & 0xF0) == 0x80)
					NoteOn = false;
				else
					NoteOn = VGMData[VGMPos + 0x01] ? true : false;
				
				if (! RhythmOn)	// Set "Key On"
					TempSht |= (UINT8)NoteOn << 13;	// << (8+5)
				
				if (NoteOn)
				{
					for (CurChip = 0x00; CurChip < 0x02; CurChip ++)
					{
						if (ChnNote[(CurChip << 4) | Channel] == 0xFF)
						{
							ChnNote[(CurChip << 4) | Channel] = VGMData[VGMPos + 0x00];
							break;
						}
					}
					if (CurChip >= 0x02)
					{
						CurChip = 0x00;
						ChnNote[(CurChip << 4) | Channel] = VGMData[VGMPos + 0x00];
					}
				}
				else
				{
					for (CurChip = 0x00; CurChip < 0x02; CurChip ++)
					{
						if (ChnNote[(CurChip << 4) | Channel] != 0xFF)
						{
							ChnNote[(CurChip << 4) | Channel] = 0xFF;
							break;
						}
					}
				}
				if (CurChip >= 0x02)
					CurChip = 0xFF;
				
				if (CurChip != 0xFF)
				{
					if (NoteOn)
					{
						if (! RhythmOn)
							SendMIDIVolume(CurChip, Channel | (RhythmOn << 7), Command,
											ChnIns[Command & 0x0F], VGMData[VGMPos + 0x01]);
					}
					if (NoteOn || ! RhythmOn)
					{
						chip_reg_write(0x09, CurChip, 0x00, 0xA0 | Channel, TempSht & 0xFF);
						chip_reg_write(0x09, CurChip, 0x00, 0xB0 | Channel, TempSht >> 8);
					}
					
					if (RhythmOn)
					{
						TempByt = 0x0F - (Command & 0x0F);
						DrumReg[CurChip] &= ~(0x01 << TempByt);
						if (NoteOn)
							chip_reg_write(0x09, CurChip, 0x00, 0xBD, DrumReg[CurChip]);
						DrumReg[CurChip] |= (UINT8)NoteOn << TempByt;
						chip_reg_write(0x09, CurChip, 0x00, 0xBD, DrumReg[CurChip]);
					}
				}
				VGMPos += 0x02;
				break;
			case 0xB0:
				NoteOn = false;
				switch(VGMData[VGMPos + 0x00])
				{
				case 0x66:	// Marker
					break;
				case 0x67:	// Rhythm Mode
					if (! VGMData[VGMPos + 0x01])
					{	// Set Rhythm Mode off
						DrumReg[0x00] = 0xC0;
						DrumReg[0x01] = 0xC0;
					}
					else
					{	// Set Rhythm Mode on
						DrumReg[0x00] = 0xE0;
						DrumReg[0x01] = 0xE0;
					}
					chip_reg_write(0x09, CurChip, 0x00, 0xBD, DrumReg[0x00]);
					chip_reg_write(0x09, CurChip, 0x00, 0xBD, DrumReg[0x01]);
					break;
				case 0x68:	// Pitch Upward
					ChnPitch[Channel] = +VGMData[VGMPos + 0x01];
					NoteOn = true;
					break;
				case 0x69:	// Pitch Downward
					ChnPitch[Channel] = -VGMData[VGMPos + 0x01];
					NoteOn = true;
					break;
				}
				if (NoteOn)
				{
					for (CurChip = 0x00; CurChip < 0x02; CurChip ++)
					{
						TempByt = ChnNote[(CurChip << 4) | Channel];
						if (! RhythmOn && TempByt != 0xFF)
						{
							TempSht = MIDINote2FNum(TempByt, ChnPitch[Channel]);
							TempSht |= 0x01 << 13;	// << (8+5)
							
							chip_reg_write(0x09, CurChip, 0x00, 0xA0 | Channel, TempSht & 0xFF);
							chip_reg_write(0x09, CurChip, 0x00, 0xB0 | Channel, TempSht >> 8);
						}
					}
				}
				VGMPos += 0x02;
				break;
			case 0xC0:
				TempByt = VGMData[VGMPos + 0x00];
				if (TempByt >= CMFInsCount)
				{
					//VGMPos += 0x01;
					//break;
					TempByt %= CMFInsCount;
				}
				
				TempIns = CMFIns + TempByt;
				ChnIns[Command & 0x0F] = TempByt;
				
				OpBase = (Channel / 0x03) * 0x08 + (Channel % 0x03);
				if (! RhythmOn)
				{
					OpMask = 0x03;
				}
				else
				{
					switch(Command & 0x0F)
					{
					case 0x0B:	// Bass Drum
						OpMask = 0x03;
						break;
					case 0x0F:	// Hi Hat
						OpMask = 0x01;
						//Channel = 0x01;	// PLAY.EXE really does this sometimes
						//OpBase = 0x01;
						break;
					case 0x0C:	// Snare Drum
						OpMask = 0x02;
						break;
					case 0x0D:	// Tom Tom
						OpMask = 0x01;
						break;
					case 0x0E:	// Cymbal
						OpMask = 0x02;
						break;
					default:
						OpMask = 0x03;
						break;
					}
				}
				
				for (CurChip = 0x00; CurChip < 0x02; CurChip ++)
				{
					TempByt = 0x00;
					if (OpMask & 0x01)
					{
						// Write Operator 1
						chip_reg_write(0x09, CurChip, 0x00,
										0x20 | (OpBase + 0x00), TempIns->Character[TempByt]);
						chip_reg_write(0x09, CurChip, 0x00,
										0x40 | (OpBase + 0x00), TempIns->ScaleLevel[TempByt]);
						chip_reg_write(0x09, CurChip, 0x00,
										0x60 | (OpBase + 0x00), TempIns->AttackDelay[TempByt]);
						chip_reg_write(0x09, CurChip, 0x00,
										0x80 | (OpBase + 0x00), TempIns->SustnRelease[TempByt]);
						chip_reg_write(0x09, CurChip, 0x00,
										0xE0 | (OpBase + 0x00), TempIns->WaveSelect[TempByt]);
						TempByt ++;
					}
					if (OpMask & 0x02)
					{
						// Write Operator 2
						chip_reg_write(0x09, CurChip, 0x00,
										0x20 | (OpBase + 0x03), TempIns->Character[TempByt]);
						chip_reg_write(0x09, CurChip, 0x00,
										0x40 | (OpBase + 0x03), TempIns->ScaleLevel[TempByt]);
						chip_reg_write(0x09, CurChip, 0x00,
										0x60 | (OpBase + 0x03), TempIns->AttackDelay[TempByt]);
						chip_reg_write(0x09, CurChip, 0x00,
										0x80 | (OpBase + 0x03), TempIns->SustnRelease[TempByt]);
						chip_reg_write(0x09, CurChip, 0x00,
										0xE0 | (OpBase + 0x03), TempIns->WaveSelect[TempByt]);
						TempByt ++;
					}
					
					chip_reg_write(0x09, CurChip, 0x00, 0xC0 | Channel, TempIns->FeedbConnect);
				}
				
				VGMPos += 0x01;
				break;
			case 0xA0:
			case 0xE0:
				VGMPos += 0x02;
				break;
			case 0xD0:
				VGMPos += 0x01;
				break;
			}
			if (Command < 0xF0)
				LastCmd = Command;
			
			if (VGMEnd)
				break;
		}
		break;
	case 0x02:	// DosBox RAW OPL File Mode
		NoteOn = false;
		if (! SampleCount)
		{
			// was done during Init (Emulation Core / Chip Mapper for real FM),
			// but Chip Mapper's init-routine now works different
			switch(DROInf.iHardwareType)
			{
			case 0x00:	// OPL 2
				for (TempByt = 0xFF; TempByt >= 0x20; TempByt --)
					chip_reg_write(0x09, 0x00, 0x00, TempByt, 0x00);
				chip_reg_write(0x09, 0x00, 0x00, 0x08, 0x00);
				chip_reg_write(0x09, 0x00, 0x00, 0x01, 0x00);
				break;
			case 0x01:	// Dual OPL 2
				for (TempByt = 0xFF; TempByt >= 0x20; TempByt --)
					chip_reg_write(0x09, 0x00, 0x00, TempByt, 0x00);
				chip_reg_write(0x09, 0x00, 0x00, 0x08, 0x00);
				chip_reg_write(0x09, 0x00, 0x00, 0x01, 0x00);
				//Sleep(1);
				for (TempByt = 0xFF; TempByt >= 0x20; TempByt --)
					chip_reg_write(0x09, 0x01, 0x00, TempByt, 0x00);
				chip_reg_write(0x09, 0x01, 0x00, 0x08, 0x00);
				chip_reg_write(0x09, 0x01, 0x00, 0x01, 0x00);
				break;
			case 0x02:	// OPL 3
				for (TempByt = 0xFF; TempByt >= 0x20; TempByt --)
					chip_reg_write(0x0C, 0x00, 0x00, TempByt, 0x00);
				chip_reg_write(0x0C, 0x00, 0x00, 0x08, 0x00);
				chip_reg_write(0x0C, 0x00, 0x00, 0x01, 0x00);
				//Sleep(1);
				for (TempByt = 0xFF; TempByt >= 0x20; TempByt --)
					chip_reg_write(0x0C, 0x00, 0x01, TempByt, 0x00);
				//chip_reg_write(0x0C, 0x00, 0x01, 0x05, 0x00);
				chip_reg_write(0x0C, 0x00, 0x01, 0x04, 0x00);
				break;
			default:
				for (TempByt = 0xFF; TempByt >= 0x20; TempByt --)
					chip_reg_write(0x09, 0x00, 0x00, TempByt, 0x00);
				break;
			}
			Sleep(1);
			NoteOn = true && (DROHead.iVersionMajor < 2);
			OpBase = 0x00;
		}
		
		SmplPlayed = SamplePlayback2VGM(VGMSmplPlayed + SampleCount);
		while(VGMSmplPos <= SmplPlayed)
		{
			Command = VGMData[VGMPos + 0x00];
			
			if (Command == DROInf.iShortDelayCode)
				Command = 0x00;
			else if (Command == DROInf.iLongDelayCode)
				Command = 0x01;
			else
			{
				switch(DROHead.iVersionMajor)
				{
				case 0:
				case 1:
					if (Command <= 0x01)
						Command = 0xFF;
					break;
				case 2:
					Command = 0xFF;
					break;
				}
			}
			
			// DRO v0/v1 only: "Delay-Command" fix
			if (NoteOn)	// "Delay"-Command during init-phase?
			{
				if (Command < OpBase)	// out of operator range?
				{
					NoteOn = false;	// it's a delay
				}
				else
				{
					OpBase = Command;	// it's a command
					Command = 0xFF;
				}
			}
DRO_CommandSwitch:
			switch(Command)
			{
			case 0x00:	// 1-Byte Delay
				VGMSmplPos += 0x01 + VGMData[VGMPos + 0x01];
				VGMPos += 0x02;
				break;
			case 0x01:	// 2-Byte Delay
				switch(DROHead.iVersionMajor)
				{
				case 0:
				case 1:
					memcpy(&TempSht, &VGMData[VGMPos + 0x01], 0x02);
					if ((TempSht & 0xFF00) == 0xBD00)
					{
						Command = 0xFF;
						goto DRO_CommandSwitch;
					}
					VGMSmplPos += 0x01 + TempSht;
					VGMPos += 0x03;
					break;
				case 2:
					VGMSmplPos += (0x01 + VGMData[VGMPos + 0x01]) << 8;
					VGMPos += 0x02;
					break;
				}
				break;
			case 0x02:	// Use 1st OPL Chip
			case 0x03:	// Use 2nd OPL Chip
				CurChip = Command & 0x01;
				if (CurChip > 0x00 && DROInf.iHardwareType == 0x00)
				{
					//CurChip = 0x00;
					if (! CmdList[0x02])
					{
						printf("More chips used than defined in header!\n");
						CmdList[0x02] = true;
					}
				}
				VGMPos += 0x01;
				break;
			case 0x04:	// Escape
				VGMPos += 0x01;
				// No break (execute following Register)
			default:
				Command = VGMData[VGMPos + 0x00];
				if (DROCodemap)
				{
					CurChip = (Command & 0x80) ? 0x01 : 0x00;
					Command &= 0x7F;
					if (Command < DROInf.iCodemapLength)
						Command = DROCodemap[Command];
					else
						Command = Command;
					switch(DROInf.iHardwareType)
					{
					case 0x00:
						if (CurChip)
						{
							if (! CmdList[0x02])
							{
								printf("More chips used than defined in header!\n");
								CmdList[0x02] = true;
							}
							//CurChip = 0x00;
							//Command = 0x00;
						}
						break;
					case 0x01:
					case 0x02:
						break;
					}
				}
				switch(DROInf.iHardwareType)
				{
				case 0x00:	// OPL 2
					if (CurChip > 0x00)
						break;
					chip_reg_write(0x09, 0x00, 0x00, Command, VGMData[VGMPos + 0x01]);
					break;
				case 0x01:
					chip_reg_write(0x09, CurChip, 0x00, Command, VGMData[VGMPos + 0x01]);
					break;
				case 0x02:	// OPL 3
					chip_reg_write(0x0C, 0x00, CurChip, Command, VGMData[VGMPos + 0x01]);
					break;
				default:
					chip_reg_write(0x09, CurChip, 0x00, Command, VGMData[VGMPos + 0x01]);
					break;
				}
				VGMPos += 0x02;
				break;
			}
			
			if (VGMPos >= VGMDataLen)
			{
				if (VGMHead.lngTotalSamples != (UINT32)VGMSmplPos)
				{
					printf("Warning! Header Samples: %u\t Counted Samples: %u\n",
							VGMHead.lngTotalSamples, VGMSmplPos);
					VGMHead.lngTotalSamples = VGMSmplPos;
					ErrorHappened = true;
				}
				VGMEnd = true;
			}
			if (VGMEnd)
				break;
		}
		break;
	}
	
	return;
}

INLINE INT32 SampleVGM2Playback(INT32 SampleVal)
{
	return (INT32)((INT64)SampleVal * SampleRate / VGMSampleRate);
}

INLINE INT32 SamplePlayback2VGM(INT32 SampleVal)
{
	return (INT32)((INT64)SampleVal * VGMSampleRate / SampleRate);
}

#endif
