cog/Frameworks/GME/vgmplay/VGMPlay_AddFmts.c

1156 lines
27 KiB
C

// 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