]> 4ch.mooo.com Git - 16.git/blobdiff - src/lib/vgmsnd/vgmSnd.c
refresh wwww
[16.git] / src / lib / vgmsnd / vgmSnd.c
diff --git a/src/lib/vgmsnd/vgmSnd.c b/src/lib/vgmsnd/vgmSnd.c
new file mode 100755 (executable)
index 0000000..1bd719f
--- /dev/null
@@ -0,0 +1,757 @@
+// vgmSndDrv.c - VGM Sound Driver for OPL2\r
+// Valley Bell, 2015-07-27\r
+\r
+// Note: This uses quite a few optimizations that assume that your\r
+//       machine is Little Endian.\r
+\r
+#include <stdio.h>\r
+#include <stdlib.h>\r
+#include <string.h>\r
+\r
+#include "src/lib/vgmsnd/common.h"\r
+#include "src/lib/vgmsnd/vgmSnd.h"\r
+\r
+\r
+#define QUICK_READ\r
+\r
+\r
+\r
+#define FCC_VGM        0x206D6756      // 'Vgm '\r
+#define FCC_GD3        0x20336447      // 'Gd3 '\r
+\r
+typedef struct _vgm_file_header_base\r
+{\r
+       dword fccVGM;                   // 00\r
+       dword/*32*/ lngEOFOffset;       // 04\r
+       dword/*32*/ lngVersion;         // 08\r
+       dword/*32*/ lngSkip1[2];                // 0C\r
+       dword/*32*/ lngGD3Offset;       // 14\r
+       dword/*32*/ lngTotalSamples;    // 18\r
+       dword/*32*/ lngLoopOffset;      // 1C\r
+       dword/*32*/ lngLoopSamples;     // 20\r
+       dword/*32*/ lngRate;                    // 24\r
+       dword/*32*/ lngSkip2[3];                // 28\r
+       dword/*32*/ lngDataOffset;      // 34\r
+       dword/*32*/ lngSkip3[2];                // 38\r
+} VGM_BASE_HDR;\r
+\r
+#define PBMODE_MUSIC   0x00\r
+#define PBMODE_SFX             0x01\r
+typedef struct _vgm_playback\r
+{\r
+       UINT8 pbMode;\r
+       UINT8 vgmEnd;   // 00 - running, 01 - finished, FF - not loaded\r
+       UINT16 curLoopCnt;\r
+       dword/*32*/ vgmPos;\r
+       dword/*32*/ vgmSmplPos;\r
+       dword/*32*/ pbSmplPos;\r
+       VGM_FILE* file;\r
+\r
+       // oplChnMask:\r
+       //      Music: mask of channels used/overridden by SFX\r
+       //      SFX:   ID of channel used by SFX (all commands are forces to it)\r
+       UINT16 oplChnMask;\r
+       UINT8* oplRegCache;\r
+       UINT8 workRAM[0x04];\r
+} VGM_PBK;\r
+\r
+\r
+\r
+INLINE UINT16 ReadLE16(const UINT8* buffer)\r
+{\r
+#ifdef QUICK_READ\r
+       return *(UINT16*)buffer;\r
+#else\r
+       return (buffer[0x00] << 0) | (buffer[0x01] << 8);\r
+#endif\r
+}\r
+\r
+INLINE dword/*32*/ ReadLE32(const UINT8* buffer)\r
+{\r
+#ifdef QUICK_READ\r
+       return *(dword/*32*/*)buffer;\r
+#else\r
+       return  (buffer[0x00] <<  0) | (buffer[0x01] <<  8) |\r
+                       (buffer[0x02] << 16) | (buffer[0x03] << 24);\r
+#endif\r
+}\r
+\r
+\r
+// Function Prototypes\r
+//UINT8 OpenVGMFile(const char* FileName, VGM_FILE* vgmFile);\r
+//void FreeVGMFile(VGM_FILE* vgmFile);\r
+\r
+static boolean DoVgmLoop(VGM_PBK* vgmPlay);\r
+static void UpdateVGM(VGM_PBK* vgmPlay, UINT16 Samples);\r
+\r
+//void InitEngine(void);\r
+//void DeinitEngine(void);\r
+\r
+//UINT8 PlayMusic(VGM_FILE* vgmFile);\r
+//UINT8 PlaySFX(VGM_FILE* vgmFile, UINT8 sfxChnID);\r
+//UINT8 StopMusic(void);\r
+//UINT8 StopSFX(UINT8 sfxChnID);       // Note: sfxChnID == 0xFF -> stop all SFX\r
+//UINT8 PauseMusic(void);\r
+//UINT8 ResumeMusic(void);\r
+static void StartPlayback(VGM_PBK* vgmPb);\r
+static void StopPlayback(VGM_PBK* vgmPb);\r
+\r
+static void ym2413_write(VGM_PBK* vgmPb, UINT8 reg, UINT8 data);\r
+static void ym3812_write(VGM_PBK* vgmPb, UINT8 reg, UINT8 data);\r
+static void ym3512_write(VGM_PBK* vgmPb, UINT8 reg, UINT8 data);\r
+static void ymf262_write(VGM_PBK* vgmPb, UINT8 port, UINT8 reg, UINT8 data);\r
+\r
+//void UpdateSoundEngine(void);\r
+\r
+\r
+// Functions that must be supplied by external library\r
+extern void OPL2_Write(UINT8 reg, UINT8 data);\r
+extern UINT8 OPL2_ReadStatus(void);\r
+\r
+\r
+\r
+\r
+#define SFX_CHN_COUNT  6\r
+\r
+#define TIMER1_RATE            7       // 256-7 = 248; (3579545/72/4) / 248 = 50.12 Hz ~ 50 Hz\r
+#define VGM_UPD_RATE   880     // 880 samples (44.1 KHz) = 50.11 Hz\r
+\r
+static VGM_PBK vgmPbMusic;\r
+static VGM_PBK vgmPbSFX[SFX_CHN_COUNT];\r
+\r
+static UINT8 oplRegs_Music[0x100];\r
+static UINT8 oplRegs_SFX[SFX_CHN_COUNT][0x0D]; // 20 23 40 43 60 63 80 83 E0 E3 C0 A0 B0\r
+\r
+static const UINT8 SFX_REGS[0x0D] =\r
+{      0x20, 0x23, 0x40, 0x43, 0x60, 0x63, 0x80, 0x83,\r
+       0xE0, 0xE3, 0xC0, 0xA0, 0xB0};\r
+static const UINT8 SFX_REGS_REV[0x10] =        // 20/30 -> 0, 40/50 -> 2, ...\r
+{      0xFF, 0xFF, 0x00, 0x00, 0x02, 0x02, 0x04, 0x04,\r
+       0x06, 0x06, 0x0B, 0x0C, 0x0A, 0xFF, 0x08, 0x08};\r
+static const UINT8 CHN_OPMASK[0x09] =\r
+{      0x00, 0x01, 0x02, 0x08, 0x09, 0x0A, 0x10, 0x11, 0x12};\r
+static const UINT8 CHN_OPMASK_REV[0x20] =\r
+{      0x00, 0x01, 0x02, 0x80, 0x81, 0x82, 0xFF, 0xFF,\r
+       0x03, 0x04, 0x05, 0x83, 0x84, 0x85, 0xFF, 0xFF,\r
+       0x06, 0x07, 0x08, 0x86, 0x87, 0x88, 0xFF, 0xFF,\r
+       0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};\r
+\r
+\r
+UINT8 OpenVGMFile(const char* FileName, VGM_FILE* vgmFile)\r
+{\r
+       size_t hdrSize;\r
+       size_t readEl;  // 'elements' read from file\r
+       size_t bytesToRead;\r
+       VGM_BASE_HDR vgmBaseHdr;\r
+       FILE* hFile;\r
+       dword/*32*/ CurPos;\r
+\r
+       memset(vgmFile, 0x00, sizeof(VGM_FILE));\r
+\r
+       hFile = fopen(FileName, "rb");\r
+       if (hFile == NULL)\r
+               return 0xFF;\r
+\r
+       hdrSize = sizeof(VGM_BASE_HDR);\r
+       readEl = fread(&vgmBaseHdr, hdrSize, 0x01, hFile);\r
+       if (readEl == 0)\r
+       {\r
+               fclose(hFile);\r
+               return 0xFE;    // read error\r
+       }\r
+       if (vgmBaseHdr.fccVGM != FCC_VGM)\r
+       {\r
+               fclose(hFile);\r
+               return 0x80;    // bad signature\r
+       }\r
+       if (vgmBaseHdr.lngVersion < 0x0150)\r
+       {\r
+               fclose(hFile);\r
+               return 0x81;    // We don't support VGM v1.10 and earlier\r
+       }\r
+\r
+       vgmFile->dataLen = vgmBaseHdr.lngEOFOffset + 0x04;\r
+       vgmFile->data = (UINT8*)malloc(vgmFile->dataLen);\r
+       if (vgmFile->data == NULL)\r
+       {\r
+               fclose(hFile);\r
+               return 0xF0;    // malloc error\r
+       }\r
+       memcpy(vgmFile->data, &vgmBaseHdr, hdrSize);\r
+       bytesToRead = vgmFile->dataLen - hdrSize;\r
+       readEl = fread(vgmFile->data + hdrSize, 0x01, bytesToRead, hFile);\r
+       if (readEl < bytesToRead)\r
+       {\r
+               //fclose(hFile);\r
+               //return 0xFE;  // read error\r
+               vgmFile->dataLen = hdrSize + readEl;\r
+       }\r
+\r
+       fclose(hFile);\r
+\r
+       memcpy(&vgmFile->header, vgmFile->data, sizeof(VGM_HEADER));\r
+\r
+       // relative -> absolute addresses\r
+       vgmFile->header.lngEOFOffset += 0x04;\r
+       if (vgmFile->header.lngGD3Offset)\r
+               vgmFile->header.lngGD3Offset += 0x14;\r
+       if (vgmFile->header.lngLoopOffset)\r
+               vgmFile->header.lngLoopOffset += 0x1C;\r
+       if (! vgmFile->header.lngDataOffset)\r
+               vgmFile->header.lngDataOffset = 0x0C;\r
+       vgmFile->header.lngDataOffset += 0x34;\r
+\r
+       CurPos = vgmFile->header.lngDataOffset;\r
+       if (vgmFile->header.lngVersion < 0x0150)\r
+               CurPos = 0x40;\r
+       hdrSize = sizeof(VGM_HEADER);\r
+       if (hdrSize > CurPos)\r
+               memset((UINT8*)&vgmFile->header + CurPos, 0x00, hdrSize - CurPos);\r
+\r
+       fclose(hFile);\r
+       return 0x00;\r
+}\r
+\r
+void FreeVGMFile(VGM_FILE* vgmFile)\r
+{\r
+       free(vgmFile->data);    vgmFile->data = NULL;\r
+       vgmFile->dataLen = 0;\r
+\r
+       return;\r
+}\r
+\r
+\r
+static boolean DoVgmLoop(VGM_PBK* vgmPlay)\r
+{\r
+       const VGM_HEADER* vgmHdr = &vgmPlay->file->header;\r
+\r
+       if (! vgmHdr->lngLoopOffset)\r
+               return false;\r
+\r
+       vgmPlay->curLoopCnt ++;\r
+\r
+       vgmPlay->vgmPos = vgmHdr->lngLoopOffset;\r
+       vgmPlay->vgmSmplPos -= vgmHdr->lngLoopSamples;\r
+       vgmPlay->pbSmplPos -= vgmHdr->lngLoopSamples;\r
+\r
+       return true;\r
+}\r
+\r
+static void UpdateVGM(VGM_PBK* vgmPlay, UINT16 Samples)\r
+{\r
+       const dword/*32*/ vgmLen = vgmPlay->file->dataLen;\r
+       const UINT8* vgmData = vgmPlay->file->data;\r
+       const UINT8* VGMPnt;\r
+       dword/*32*/ VGMPos;\r
+       dword/*32*/ VGMSmplPos;\r
+       UINT8 Command;\r
+       UINT8 blockType;\r
+       dword/*32*/ blockLen;\r
+\r
+       vgmPlay->pbSmplPos += Samples;\r
+       VGMPos = vgmPlay->vgmPos;\r
+       VGMSmplPos = vgmPlay->vgmSmplPos;\r
+       while(VGMSmplPos < vgmPlay->pbSmplPos && ! vgmPlay->vgmEnd)\r
+       {\r
+               VGMPnt = &vgmData[VGMPos];\r
+               Command = VGMPnt[0x00];\r
+               switch(Command & 0xF0)\r
+               {\r
+               case 0x70:      // small delay (1-16 samples)\r
+                       VGMSmplPos += (Command & 0x0F) + 0x01;\r
+                       VGMPos += 0x01;\r
+                       break;\r
+               case 0x80:      // DAC write + small delay (0-15 samples)\r
+                       VGMSmplPos += (Command & 0x0F);\r
+                       VGMPos += 0x01;\r
+                       break;\r
+               case 0x60:\r
+                       switch(Command)\r
+                       {\r
+                       case 0x66:      // End Of File\r
+                               vgmPlay->vgmPos = VGMPos;\r
+                               vgmPlay->vgmSmplPos = VGMSmplPos;\r
+                               if (! DoVgmLoop(vgmPlay))\r
+                                       vgmPlay->vgmEnd = 0x01;\r
+                               VGMPos = vgmPlay->vgmPos;\r
+                               VGMSmplPos = vgmPlay->vgmSmplPos;\r
+                               break;\r
+                       case 0x62:      // 1/60s delay\r
+                               VGMSmplPos += 735;\r
+                               VGMPos += 0x01;\r
+                               break;\r
+                       case 0x63:      // 1/50s delay\r
+                               VGMSmplPos += 882;\r
+                               VGMPos += 0x01;\r
+                               break;\r
+                       case 0x61:      // xx Sample Delay\r
+                               VGMSmplPos += ReadLE16(&VGMPnt[0x01]);\r
+                               VGMPos += 0x03;\r
+                               break;\r
+                       case 0x67:      // Data Block (PCM Data Stream)\r
+                               blockType = VGMPnt[0x02];\r
+                               blockLen = ReadLE32(&VGMPnt[0x03]);\r
+                               blockLen &= 0x7FFFFFFF;\r
+                               VGMPos += 0x07 + blockLen;\r
+                               break;\r
+                       case 0x68:      // PCM RAM write\r
+                               VGMPos += 0x0C;\r
+                               break;\r
+                       default:\r
+                               vgmPlay->vgmEnd = 0x01;\r
+                               break;\r
+                       }\r
+                       break;\r
+               case 0x50:\r
+                       if (Command == 0x50)\r
+                       {\r
+                               VGMPos += 0x02; // SN76496 write\r
+                               break;\r
+                       }\r
+                       switch(Command)\r
+                       {\r
+                       case 0x51:      // YM2413 write\r
+                               ym2413_write(vgmPlay, VGMPnt[0x01], VGMPnt[0x02]);\r
+                               break;\r
+                       case 0x5A:      // YM3812 write\r
+                               ym3812_write(vgmPlay, VGMPnt[0x01], VGMPnt[0x02]);\r
+                               break;\r
+                       case 0x5B:      // YM3526 write\r
+                       case 0x5C:      // Y8950 write\r
+                               ym3512_write(vgmPlay, VGMPnt[0x01], VGMPnt[0x02]);\r
+                               break;\r
+                       case 0x5E:      // YMF262 write, port 0\r
+                       case 0x5F:      // YMF262 write, port 1\r
+                               ymf262_write(vgmPlay, Command & 0x01, VGMPnt[0x01], VGMPnt[0x02]);\r
+                               break;\r
+                       }\r
+                       VGMPos += 0x03;\r
+                       break;\r
+               case 0x30:\r
+                       VGMPos += 0x02;\r
+                       break;\r
+               case 0x40:\r
+               case 0xA0:\r
+               case 0xB0:\r
+                       VGMPos += 0x03;\r
+                       break;\r
+               case 0xC0:\r
+               case 0xD0:\r
+                       VGMPos += 0x04;\r
+                       break;\r
+               case 0xE0:\r
+               case 0xF0:\r
+                       VGMPos += 0x05;\r
+                       break;\r
+               case 0x90:\r
+                       switch(Command)\r
+                       {\r
+                       case 0x90:      // DAC Ctrl: Setup Chip\r
+                               VGMPos += 0x05;\r
+                               break;\r
+                       case 0x91:      // DAC Ctrl: Set Data\r
+                               VGMPos += 0x05;\r
+                               break;\r
+                       case 0x92:      // DAC Ctrl: Set Freq\r
+                               VGMPos += 0x06;\r
+                               break;\r
+                       case 0x93:      // DAC Ctrl: Play from Start Pos\r
+                               VGMPos += 0x0B;\r
+                               break;\r
+                       case 0x94:      // DAC Ctrl: Stop immediately\r
+                               VGMPos += 0x02;\r
+                               break;\r
+                       case 0x95:      // DAC Ctrl: Play Block (small)\r
+                               VGMPos += 0x05;\r
+                               break;\r
+                       default:\r
+                               vgmPlay->vgmEnd = 0x01;\r
+                               break;\r
+                       }\r
+                       break;\r
+               default:\r
+                       vgmPlay->vgmEnd = 0x01;\r
+                       return;\r
+               }\r
+\r
+               if (VGMPos >= vgmLen)\r
+                       vgmPlay->vgmEnd = 0x01;\r
+       }\r
+       vgmPlay->vgmPos = VGMPos;\r
+       vgmPlay->vgmSmplPos = VGMSmplPos;\r
+       if (vgmPlay->vgmEnd)\r
+               StopPlayback(vgmPlay);\r
+\r
+       return;\r
+}\r
+\r
+\r
+\r
+\r
+void InitEngine(void)\r
+{\r
+       UINT8 curSFX;\r
+       UINT8 curReg;\r
+\r
+       memset(oplRegs_Music, 0x00, 0x100);\r
+       memset(&vgmPbMusic, 0x00, sizeof(VGM_PBK));\r
+       vgmPbMusic.pbMode = PBMODE_MUSIC;\r
+       vgmPbMusic.vgmEnd = 0xFF;\r
+       vgmPbMusic.oplChnMask = 0x0000;\r
+       vgmPbMusic.oplRegCache = oplRegs_Music;\r
+\r
+       for (curSFX = 0; curSFX < SFX_CHN_COUNT; curSFX ++)\r
+       {\r
+               memset(&oplRegs_SFX[curSFX], 0x00, sizeof(VGM_PBK));\r
+               memset(&vgmPbSFX[curSFX], 0x00, sizeof(VGM_PBK));\r
+               vgmPbSFX[curSFX].pbMode = PBMODE_SFX;\r
+               vgmPbSFX[curSFX].vgmEnd = 0xFF;\r
+               vgmPbSFX[curSFX].oplChnMask = curSFX;\r
+               vgmPbSFX[curSFX].oplRegCache = oplRegs_SFX[curSFX];\r
+       }\r
+\r
+       // reset OPL2 chip\r
+       curReg = 0x00;\r
+       do\r
+       {\r
+               curReg --;\r
+               OPL2_Write(curReg, 0x00);\r
+       } while(curReg > 0x20);\r
+\r
+       OPL2_Write(0x02, TIMER1_RATE);  // set Timer 1 Period\r
+       OPL2_Write(0x04, 0x01); // Timer 1 on/unmasked, Timer 2 off\r
+       OPL2_Write(0x04, 0x80); // Reset Timer/IRQ Status Flags\r
+\r
+       OPL2_Write(0x01, 0x20); // Waveform Select: Enable\r
+\r
+       return;\r
+}\r
+\r
+void DeinitEngine(void)\r
+{\r
+       UINT8 curSFX;\r
+\r
+       StopPlayback(&vgmPbMusic);\r
+       for (curSFX = 0; curSFX < SFX_CHN_COUNT; curSFX ++)\r
+               StopPlayback(&vgmPbSFX[curSFX]);\r
+\r
+       OPL2_Write(0x04, 0x00); // disable all timers\r
+\r
+       return;\r
+}\r
+\r
+\r
+UINT8 PlayMusic(VGM_FILE* vgmFile)\r
+{\r
+       VGM_PBK* vgmPb = &vgmPbMusic;\r
+\r
+       if (! vgmPb->vgmEnd)\r
+               StopPlayback(vgmPb);\r
+\r
+       vgmPb->file = vgmFile;\r
+\r
+       StartPlayback(vgmPb);\r
+\r
+       return 0x00;\r
+}\r
+\r
+UINT8 PlaySFX(VGM_FILE* vgmFile, UINT8 sfxChnID)\r
+{\r
+       VGM_PBK* vgmPb;\r
+\r
+       if (sfxChnID >= SFX_CHN_COUNT)\r
+               return 0xFF;\r
+\r
+       vgmPb = &vgmPbSFX[sfxChnID];\r
+\r
+       if (! vgmPb->vgmEnd)\r
+               StopPlayback(vgmPb);\r
+\r
+       vgmPb->file = vgmFile;\r
+\r
+       StartPlayback(vgmPb);\r
+\r
+       return 0x00;\r
+}\r
+\r
+UINT8 StopMusic(void)\r
+{\r
+       StopPlayback(&vgmPbMusic);\r
+       return 0x00;\r
+}\r
+\r
+UINT8 StopSFX(UINT8 sfxChnID)\r
+{\r
+       if (sfxChnID == 0xFF)\r
+       {\r
+               for (sfxChnID = 0; sfxChnID < SFX_CHN_COUNT; sfxChnID ++)\r
+                       StopPlayback(&vgmPbSFX[sfxChnID]);\r
+               return 0x00;\r
+       }\r
+\r
+       if (sfxChnID >= SFX_CHN_COUNT)\r
+               return 0xFF;\r
+\r
+       StopPlayback(&vgmPbSFX[sfxChnID]);\r
+       return 0x00;\r
+}\r
+\r
+UINT8 PauseMusic(void)\r
+{\r
+       if (vgmPbMusic.vgmEnd == 0xFF)\r
+               return 0xFF;    // not playing\r
+       if (vgmPbMusic.vgmEnd == 0x01)\r
+               return 0x80;    // finished playing already\r
+       if (vgmPbMusic.vgmEnd == 0x02)\r
+               return 0x01;    // is already paused\r
+\r
+       StopPlayback(&vgmPbMusic);\r
+       vgmPbMusic.vgmEnd = 0x02;\r
+\r
+       return 0x00;\r
+}\r
+\r
+UINT8 ResumeMusic(void)\r
+{\r
+       if (vgmPbMusic.vgmEnd == 0xFF)\r
+               return 0xFF;    // not playing\r
+       if (vgmPbMusic.vgmEnd == 0x01)\r
+               return 0x80;    // finished playing already\r
+       if (! (vgmPbMusic.vgmEnd & 0x02))\r
+               return 0x01;    // is not paused\r
+\r
+       vgmPbMusic.vgmEnd &= ~0x02;\r
+\r
+       return 0x00;\r
+}\r
+\r
+static void StartPlayback(VGM_PBK* vgmPb)\r
+{\r
+       if (vgmPb->file == NULL || vgmPb->file->data == NULL ||\r
+               vgmPb->file->header.fccVGM != FCC_VGM\r
+)\r
+       {\r
+               vgmPb->vgmEnd = 0xFF;\r
+               return;\r
+       }\r
+\r
+       vgmPb->vgmEnd = 0x00;   // set to 'running'\r
+       vgmPb->vgmPos = vgmPb->file->header.lngDataOffset;\r
+       vgmPb->vgmSmplPos = 0;\r
+       vgmPb->pbSmplPos = 0;\r
+       vgmPb->curLoopCnt = 0;\r
+       memset(vgmPb->workRAM, 0x00, 0x04);\r
+\r
+       if (vgmPb->pbMode == PBMODE_SFX)\r
+       {\r
+               UINT8 curReg;\r
+\r
+               curReg = 0xB0 | vgmPb->oplChnMask;\r
+               if (oplRegs_Music[curReg] & 0x20)\r
+                       OPL2_Write(curReg, oplRegs_Music[curReg] & ~0x20);      // send Key Off\r
+\r
+               vgmPbMusic.oplChnMask |= (1 << vgmPb->oplChnMask);      // mask out music channel\r
+       }\r
+\r
+       return;\r
+}\r
+\r
+static void StopPlayback(VGM_PBK* vgmPb)\r
+{\r
+       if (vgmPb->vgmEnd & 0x80)\r
+               return;\r
+\r
+       if (vgmPb->pbMode == PBMODE_MUSIC)\r
+       {\r
+               UINT8 curReg;\r
+               UINT16 chnMask;\r
+\r
+               chnMask = 0x0001;\r
+               for (curReg = 0xB0; curReg < 0xB9; curReg ++, chnMask <<= 1)\r
+               {\r
+                       if (vgmPb->oplChnMask & chnMask)\r
+                               continue;       // keep channels used by SFX untouched\r
+                       if (vgmPb->oplRegCache[curReg] & 0x20)\r
+                       {\r
+                               vgmPb->oplRegCache[curReg] &= ~0x20;\r
+                               OPL2_Write(curReg, vgmPb->oplRegCache[curReg]); // send Key Off\r
+                       }\r
+               }\r
+               curReg = 0xBD;  // rhythm register\r
+               if (vgmPb->oplRegCache[curReg] & 0x1F)\r
+               {\r
+                       vgmPb->oplRegCache[curReg] &= ~0x1F;\r
+                       OPL2_Write(curReg, vgmPb->oplRegCache[curReg]); // send Key Off\r
+               }\r
+\r
+               vgmPb->vgmEnd = 0x01;\r
+       }\r
+       else //if (vgmPb->pbMode == PBMODE_SFX)\r
+       {\r
+               UINT8 regID;\r
+               UINT8 curReg;\r
+               UINT8 opMask;\r
+\r
+               curReg = 0xB0 | vgmPb->oplChnMask;\r
+               if (vgmPb->oplRegCache[0x0C] & 0x20)\r
+               {\r
+                       vgmPb->oplRegCache[0x0C] &= ~0x20;\r
+                       OPL2_Write(curReg, vgmPb->oplRegCache[0x0C]);   // send Key Off\r
+               }\r
+\r
+               vgmPb->vgmEnd = 0x01;\r
+\r
+               if (! vgmPbMusic.vgmEnd)        // if (music is playing)\r
+               {\r
+                       opMask = CHN_OPMASK[vgmPb->oplChnMask];\r
+                       for (regID = 0x00; regID < 0x0A; regID ++)\r
+                       {\r
+                               curReg = SFX_REGS[regID] + opMask;\r
+                               OPL2_Write(curReg, oplRegs_Music[curReg]);      // restore Music register\r
+                       }\r
+                       for (; regID < 0x0D; regID ++)\r
+                       {\r
+                               curReg = SFX_REGS[regID] | vgmPb->oplChnMask;\r
+                               OPL2_Write(curReg, oplRegs_Music[curReg]);      // restore Music register\r
+                       }\r
+\r
+                       vgmPbMusic.oplChnMask &= ~(1 << vgmPb->oplChnMask);\r
+               }\r
+       }\r
+\r
+       return;\r
+}\r
+\r
+\r
+\r
+static void OPL_CachedWrite(VGM_PBK* vgmPb, UINT8 reg, UINT8 data)\r
+{\r
+       UINT8 regChn;\r
+       UINT8 ramOfs;\r
+\r
+       if (vgmPb->pbMode == PBMODE_MUSIC)\r
+       {\r
+               if (reg == 0x01)\r
+                       data |= 0x20;   // enforce "Waveform Select Enable" bit\r
+               vgmPb->oplRegCache[reg] = data;\r
+\r
+               ramOfs = SFX_REGS_REV[reg >> 4];\r
+               if (ramOfs < 0x0A)      // Operator 20/40/60/80/E0\r
+               {\r
+                       regChn = CHN_OPMASK_REV[reg & 0x1F] & 0x7F;\r
+                       if (vgmPb->oplChnMask & (1 << regChn))\r
+                               return; // channel overridden by SFX - return\r
+               }\r
+               else if (ramOfs < 0x0D) // Operator C0/A0/B0\r
+               {\r
+                       regChn = reg & 0x0F;\r
+                       if (vgmPb->oplChnMask & (1 << regChn))\r
+                               return; // channel overridden by SFX - return\r
+               }\r
+       }\r
+       else //if (vgmPb->pbMode == PBMODE_SFX)\r
+       {\r
+               if (reg == 0xBD)\r
+                       return; // no rhythm register for SFX\r
+\r
+               ramOfs = SFX_REGS_REV[reg >> 4];\r
+               if (ramOfs == 0xFF)\r
+                       return;\r
+\r
+               if (ramOfs < 0x0A)      // Operator 20/40/60/80/E0\r
+               {\r
+                       regChn = CHN_OPMASK_REV[reg & 0x1F];\r
+                       if (regChn == 0xFF)\r
+                               return; // ignore writes to invalid channels/operators\r
+                       ramOfs += (regChn & 0x80) >> 7;\r
+                       regChn &= 0x7F;\r
+                       vgmPb->oplRegCache[ramOfs] = data;\r
+\r
+                       if (regChn != vgmPb->oplChnMask)\r
+                       {\r
+                               // force command to current channel\r
+                               reg = SFX_REGS[ramOfs] + CHN_OPMASK[vgmPb->oplChnMask];\r
+                       }\r
+               }\r
+               else    // Operator C0/A0/B0\r
+               {\r
+                       regChn = CHN_OPMASK_REV[reg & 0x0F];\r
+                       if (regChn >= 0x09)\r
+                               return; // ignore writes to invalid channels\r
+                       vgmPb->oplRegCache[ramOfs] = data;\r
+\r
+                       reg = (reg & 0xF0) | vgmPb->oplChnMask;\r
+               }\r
+       }\r
+\r
+       OPL2_Write(reg, data);\r
+       return;\r
+}\r
+\r
+\r
+static void ym2413_write(VGM_PBK* vgmPb, UINT8 reg, UINT8 data)\r
+{\r
+       return; // unsupported for now\r
+}\r
+\r
+static void ym3812_write(VGM_PBK* vgmPb, UINT8 reg, UINT8 data)\r
+{\r
+       if (reg == 0x01)\r
+       {\r
+               vgmPb->workRAM[0x00] = data & 0x20;     // store "Wave Select Enable" bit\r
+       }\r
+       else if ((reg & 0xE0) == 0xE0)\r
+       {\r
+               if (! vgmPb->workRAM[0x00])     // "Wave Select Enable" off?\r
+                       data = 0x00;    // disable waveforms\r
+       }\r
+\r
+       OPL_CachedWrite(vgmPb, reg, data);\r
+       return;\r
+}\r
+\r
+static void ym3512_write(VGM_PBK* vgmPb, UINT8 reg, UINT8 data)\r
+{\r
+       if ((reg & 0xE0) == 0xE0)\r
+       {\r
+               data = 0x00;    // OPL1 has no waveforms\r
+       }\r
+       if (reg >= 0x07 && reg < 0x20)\r
+       {\r
+               if (reg == 0x08)\r
+                       data &= ~0x0F;  // mask out Y8950 DeltaT data\r
+               else\r
+                       return; // ignore Y8950 DeltaT writes\r
+       }\r
+\r
+       OPL_CachedWrite(vgmPb, reg, data);\r
+       return;\r
+}\r
+\r
+static void ymf262_write(VGM_PBK* vgmPb, UINT8 port, UINT8 reg, UINT8 data)\r
+{\r
+       return; // unsupported for now\r
+}\r
+\r
+\r
+\r
+void UpdateSoundEngine(void)\r
+{\r
+       UINT8 tmrMask;\r
+       UINT8 curSFX;\r
+\r
+       tmrMask = OPL2_ReadStatus();\r
+       if (! (tmrMask & 0x40))\r
+               return; // wait for overflow\r
+       OPL2_Write(0x04, 0x80); // Reset Timer/IRQ Status Flags\r
+\r
+       if (! vgmPbMusic.vgmEnd)\r
+               UpdateVGM(&vgmPbMusic, VGM_UPD_RATE);\r
+       for (curSFX = 0; curSFX < SFX_CHN_COUNT; curSFX ++)\r
+       {\r
+               if (! vgmPbSFX[curSFX].vgmEnd)\r
+                       UpdateVGM(&vgmPbSFX[curSFX], VGM_UPD_RATE);\r
+       }\r
+\r
+       return;\r
+}\r