1 // vgmSndDrv.c - VGM Sound Driver for OPL2
\r
2 // Valley Bell, 2015-07-27
\r
4 // Note: This uses quite a few optimizations that assume that your
\r
5 // machine is Little Endian.
\r
11 #include "src/lib/vgmsnd/common.h"
\r
12 #include "src/lib/vgmsnd/vgmSnd.h"
\r
19 #define FCC_VGM 0x206D6756 // 'Vgm '
\r
20 #define FCC_GD3 0x20336447 // 'Gd3 '
\r
22 typedef struct _vgm_file_header_base
\r
25 dword/*32*/ lngEOFOffset; // 04
\r
26 dword/*32*/ lngVersion; // 08
\r
27 dword/*32*/ lngSkip1[2]; // 0C
\r
28 dword/*32*/ lngGD3Offset; // 14
\r
29 dword/*32*/ lngTotalSamples; // 18
\r
30 dword/*32*/ lngLoopOffset; // 1C
\r
31 dword/*32*/ lngLoopSamples; // 20
\r
32 dword/*32*/ lngRate; // 24
\r
33 dword/*32*/ lngSkip2[3]; // 28
\r
34 dword/*32*/ lngDataOffset; // 34
\r
35 dword/*32*/ lngSkip3[2]; // 38
\r
38 #define PBMODE_MUSIC 0x00
\r
39 #define PBMODE_SFX 0x01
\r
40 typedef struct _vgm_playback
\r
43 UINT8 vgmEnd; // 00 - running, 01 - finished, FF - not loaded
\r
46 dword/*32*/ vgmSmplPos;
\r
47 dword/*32*/ pbSmplPos;
\r
51 // Music: mask of channels used/overridden by SFX
\r
52 // SFX: ID of channel used by SFX (all commands are forces to it)
\r
55 UINT8 workRAM[0x04];
\r
60 INLINE UINT16 ReadLE16(const UINT8* buffer)
\r
63 return *(UINT16*)buffer;
\r
65 return (buffer[0x00] << 0) | (buffer[0x01] << 8);
\r
69 INLINE dword/*32*/ ReadLE32(const UINT8* buffer)
\r
72 return *(dword/*32*/*)buffer;
\r
74 return (buffer[0x00] << 0) | (buffer[0x01] << 8) |
\r
75 (buffer[0x02] << 16) | (buffer[0x03] << 24);
\r
80 // Function Prototypes
\r
81 //UINT8 OpenVGMFile(const char* FileName, VGM_FILE* vgmFile);
\r
82 //void FreeVGMFile(VGM_FILE* vgmFile);
\r
84 static boolean DoVgmLoop(VGM_PBK* vgmPlay);
\r
85 static void UpdateVGM(VGM_PBK* vgmPlay, UINT16 Samples);
\r
87 //void InitEngine(void);
\r
88 //void DeinitEngine(void);
\r
90 //UINT8 PlayMusic(VGM_FILE* vgmFile);
\r
91 //UINT8 PlaySFX(VGM_FILE* vgmFile, UINT8 sfxChnID);
\r
92 //UINT8 StopMusic(void);
\r
93 //UINT8 StopSFX(UINT8 sfxChnID); // Note: sfxChnID == 0xFF -> stop all SFX
\r
94 //UINT8 PauseMusic(void);
\r
95 //UINT8 ResumeMusic(void);
\r
96 static void StartPlayback(VGM_PBK* vgmPb);
\r
97 static void StopPlayback(VGM_PBK* vgmPb);
\r
99 static void ym2413_write(VGM_PBK* vgmPb, UINT8 reg, UINT8 data);
\r
100 static void ym3812_write(VGM_PBK* vgmPb, UINT8 reg, UINT8 data);
\r
101 static void ym3512_write(VGM_PBK* vgmPb, UINT8 reg, UINT8 data);
\r
102 static void ymf262_write(VGM_PBK* vgmPb, UINT8 port, UINT8 reg, UINT8 data);
\r
104 //void UpdateSoundEngine(void);
\r
107 // Functions that must be supplied by external library
\r
108 extern void OPL2_Write(UINT8 reg, UINT8 data);
\r
109 extern UINT8 OPL2_ReadStatus(void);
\r
114 #define SFX_CHN_COUNT 6
\r
116 #define TIMER1_RATE 7 // 256-7 = 248; (3579545/72/4) / 248 = 50.12 Hz ~ 50 Hz
\r
117 #define VGM_UPD_RATE 880 // 880 samples (44.1 KHz) = 50.11 Hz
\r
119 static VGM_PBK vgmPbMusic;
\r
120 static VGM_PBK vgmPbSFX[SFX_CHN_COUNT];
\r
122 static UINT8 oplRegs_Music[0x100];
\r
123 static UINT8 oplRegs_SFX[SFX_CHN_COUNT][0x0D]; // 20 23 40 43 60 63 80 83 E0 E3 C0 A0 B0
\r
125 static const UINT8 SFX_REGS[0x0D] =
\r
126 { 0x20, 0x23, 0x40, 0x43, 0x60, 0x63, 0x80, 0x83,
\r
127 0xE0, 0xE3, 0xC0, 0xA0, 0xB0};
\r
128 static const UINT8 SFX_REGS_REV[0x10] = // 20/30 -> 0, 40/50 -> 2, ...
\r
129 { 0xFF, 0xFF, 0x00, 0x00, 0x02, 0x02, 0x04, 0x04,
\r
130 0x06, 0x06, 0x0B, 0x0C, 0x0A, 0xFF, 0x08, 0x08};
\r
131 static const UINT8 CHN_OPMASK[0x09] =
\r
132 { 0x00, 0x01, 0x02, 0x08, 0x09, 0x0A, 0x10, 0x11, 0x12};
\r
133 static const UINT8 CHN_OPMASK_REV[0x20] =
\r
134 { 0x00, 0x01, 0x02, 0x80, 0x81, 0x82, 0xFF, 0xFF,
\r
135 0x03, 0x04, 0x05, 0x83, 0x84, 0x85, 0xFF, 0xFF,
\r
136 0x06, 0x07, 0x08, 0x86, 0x87, 0x88, 0xFF, 0xFF,
\r
137 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
\r
140 UINT8 OpenVGMFile(const char* FileName, VGM_FILE* vgmFile, global_game_variables_t *gvar)
\r
143 size_t readEl; // 'elements' read from file
\r
144 size_t bytesToRead;
\r
145 VGM_BASE_HDR vgmBaseHdr;
\r
147 dword/*32*/ CurPos;
\r
149 memset(vgmFile, 0x00, sizeof(VGM_FILE));
\r
151 hFile = fopen(FileName, "rb");
\r
155 hdrSize = sizeof(VGM_BASE_HDR);
\r
156 readEl = fread(&vgmBaseHdr, hdrSize, 0x01, hFile);
\r
160 return 0xFE; // read error
\r
162 if (vgmBaseHdr.fccVGM != FCC_VGM)
\r
165 return 0x80; // bad signature
\r
167 if (vgmBaseHdr.lngVersion < 0x0150)
\r
170 return 0x81; // We don't support VGM v1.10 and earlier
\r
173 vgmFile->dataLen = vgmBaseHdr.lngEOFOffset + 0x04;
\r
174 //vgmFile->data = (UINT8*)malloc(vgmFile->dataLen);
\r
175 MM_GetPtr(MEMPTRCONV gvar->ca.audiosegs[0], vgmFile->dataLen, gvar);
\r
176 vgmFile->data = (UINT8*)gvar->ca.audiosegs[0];
\r
177 if (vgmFile->data == NULL)
\r
180 return 0xF0; // malloc error
\r
182 memcpy(vgmFile->data, &vgmBaseHdr, hdrSize);
\r
183 bytesToRead = vgmFile->dataLen - hdrSize;
\r
184 readEl = fread(vgmFile->data + hdrSize, 0x01, bytesToRead, hFile);
\r
185 if (readEl < bytesToRead)
\r
188 //return 0xFE; // read error
\r
189 vgmFile->dataLen = hdrSize + readEl;
\r
194 memcpy(&vgmFile->header, vgmFile->data, sizeof(VGM_HEADER));
\r
196 // relative -> absolute addresses
\r
197 vgmFile->header.lngEOFOffset += 0x04;
\r
198 if (vgmFile->header.lngGD3Offset)
\r
199 vgmFile->header.lngGD3Offset += 0x14;
\r
200 if (vgmFile->header.lngLoopOffset)
\r
201 vgmFile->header.lngLoopOffset += 0x1C;
\r
202 if (! vgmFile->header.lngDataOffset)
\r
203 vgmFile->header.lngDataOffset = 0x0C;
\r
204 vgmFile->header.lngDataOffset += 0x34;
\r
206 CurPos = vgmFile->header.lngDataOffset;
\r
207 if (vgmFile->header.lngVersion < 0x0150)
\r
209 hdrSize = sizeof(VGM_HEADER);
\r
210 if (hdrSize > CurPos)
\r
211 memset((UINT8*)&vgmFile->header + CurPos, 0x00, hdrSize - CurPos);
\r
217 void FreeVGMFile(VGM_FILE* vgmFile, global_game_variables_t *gvar)
\r
219 //if(vgmFile->data){ free(vgmFile->data); vgmFile->data = NULL; }
\r
220 MM_FreePtr(MEMPTRCONV gvar->ca.audiosegs[0], gvar);
\r
221 if(vgmFile->data) free(vgmFile->data);
\r
222 vgmFile->dataLen = 0;
\r
228 static boolean DoVgmLoop(VGM_PBK* vgmPlay)
\r
230 const VGM_HEADER* vgmHdr = &vgmPlay->file->header;
\r
232 if (! vgmHdr->lngLoopOffset)
\r
235 vgmPlay->curLoopCnt ++;
\r
237 vgmPlay->vgmPos = vgmHdr->lngLoopOffset;
\r
238 vgmPlay->vgmSmplPos -= vgmHdr->lngLoopSamples;
\r
239 vgmPlay->pbSmplPos -= vgmHdr->lngLoopSamples;
\r
244 static void UpdateVGM(VGM_PBK* vgmPlay, UINT16 Samples)
\r
246 const dword/*32*/ vgmLen = vgmPlay->file->dataLen;
\r
247 const UINT8* vgmData = vgmPlay->file->data;
\r
248 const UINT8* VGMPnt;
\r
249 dword/*32*/ VGMPos;
\r
250 dword/*32*/ VGMSmplPos;
\r
253 dword/*32*/ blockLen;
\r
255 vgmPlay->pbSmplPos += Samples;
\r
256 VGMPos = vgmPlay->vgmPos;
\r
257 VGMSmplPos = vgmPlay->vgmSmplPos;
\r
258 while(VGMSmplPos < vgmPlay->pbSmplPos && ! vgmPlay->vgmEnd)
\r
260 VGMPnt = &vgmData[VGMPos];
\r
261 Command = VGMPnt[0x00];
\r
262 switch(Command & 0xF0)
\r
264 case 0x70: // small delay (1-16 samples)
\r
265 VGMSmplPos += (Command & 0x0F) + 0x01;
\r
268 case 0x80: // DAC write + small delay (0-15 samples)
\r
269 VGMSmplPos += (Command & 0x0F);
\r
275 case 0x66: // End Of File
\r
276 vgmPlay->vgmPos = VGMPos;
\r
277 vgmPlay->vgmSmplPos = VGMSmplPos;
\r
278 if (! DoVgmLoop(vgmPlay))
\r
279 vgmPlay->vgmEnd = 0x01;
\r
280 VGMPos = vgmPlay->vgmPos;
\r
281 VGMSmplPos = vgmPlay->vgmSmplPos;
\r
283 case 0x62: // 1/60s delay
\r
287 case 0x63: // 1/50s delay
\r
291 case 0x61: // xx Sample Delay
\r
292 VGMSmplPos += ReadLE16(&VGMPnt[0x01]);
\r
295 case 0x67: // Data Block (PCM Data Stream)
\r
296 blockType = VGMPnt[0x02];
\r
297 blockLen = ReadLE32(&VGMPnt[0x03]);
\r
298 blockLen &= 0x7FFFFFFF;
\r
299 VGMPos += 0x07 + blockLen;
\r
301 case 0x68: // PCM RAM write
\r
305 vgmPlay->vgmEnd = 0x01;
\r
310 if (Command == 0x50)
\r
312 VGMPos += 0x02; // SN76496 write
\r
317 case 0x51: // YM2413 write
\r
318 ym2413_write(vgmPlay, VGMPnt[0x01], VGMPnt[0x02]);
\r
320 case 0x5A: // YM3812 write
\r
321 ym3812_write(vgmPlay, VGMPnt[0x01], VGMPnt[0x02]);
\r
323 case 0x5B: // YM3526 write
\r
324 case 0x5C: // Y8950 write
\r
325 ym3512_write(vgmPlay, VGMPnt[0x01], VGMPnt[0x02]);
\r
327 case 0x5E: // YMF262 write, port 0
\r
328 case 0x5F: // YMF262 write, port 1
\r
329 ymf262_write(vgmPlay, Command & 0x01, VGMPnt[0x01], VGMPnt[0x02]);
\r
353 case 0x90: // DAC Ctrl: Setup Chip
\r
356 case 0x91: // DAC Ctrl: Set Data
\r
359 case 0x92: // DAC Ctrl: Set Freq
\r
362 case 0x93: // DAC Ctrl: Play from Start Pos
\r
365 case 0x94: // DAC Ctrl: Stop immediately
\r
368 case 0x95: // DAC Ctrl: Play Block (small)
\r
372 vgmPlay->vgmEnd = 0x01;
\r
377 vgmPlay->vgmEnd = 0x01;
\r
381 if (VGMPos >= vgmLen)
\r
382 vgmPlay->vgmEnd = 0x01;
\r
384 vgmPlay->vgmPos = VGMPos;
\r
385 vgmPlay->vgmSmplPos = VGMSmplPos;
\r
386 if (vgmPlay->vgmEnd)
\r
387 StopPlayback(vgmPlay);
\r
395 void InitEngine(void)
\r
400 memset(oplRegs_Music, 0x00, 0x100);
\r
401 memset(&vgmPbMusic, 0x00, sizeof(VGM_PBK));
\r
402 vgmPbMusic.pbMode = PBMODE_MUSIC;
\r
403 vgmPbMusic.vgmEnd = 0xFF;
\r
404 vgmPbMusic.oplChnMask = 0x0000;
\r
405 vgmPbMusic.oplRegCache = oplRegs_Music;
\r
407 for (curSFX = 0; curSFX < SFX_CHN_COUNT; curSFX ++)
\r
409 memset(&oplRegs_SFX[curSFX], 0x00, sizeof(VGM_PBK));
\r
410 memset(&vgmPbSFX[curSFX], 0x00, sizeof(VGM_PBK));
\r
411 vgmPbSFX[curSFX].pbMode = PBMODE_SFX;
\r
412 vgmPbSFX[curSFX].vgmEnd = 0xFF;
\r
413 vgmPbSFX[curSFX].oplChnMask = curSFX;
\r
414 vgmPbSFX[curSFX].oplRegCache = oplRegs_SFX[curSFX];
\r
422 OPL2_Write(curReg, 0x00);
\r
423 } while(curReg > 0x20);
\r
425 OPL2_Write(0x02, TIMER1_RATE); // set Timer 1 Period
\r
426 OPL2_Write(0x04, 0x01); // Timer 1 on/unmasked, Timer 2 off
\r
427 OPL2_Write(0x04, 0x80); // Reset Timer/IRQ Status Flags
\r
429 OPL2_Write(0x01, 0x20); // Waveform Select: Enable
\r
434 void DeinitEngine(void)
\r
438 StopPlayback(&vgmPbMusic);
\r
439 for (curSFX = 0; curSFX < SFX_CHN_COUNT; curSFX ++)
\r
440 StopPlayback(&vgmPbSFX[curSFX]);
\r
442 OPL2_Write(0x04, 0x00); // disable all timers
\r
448 UINT8 PlayMusic(VGM_FILE* vgmFile)
\r
450 VGM_PBK* vgmPb = &vgmPbMusic;
\r
452 if (! vgmPb->vgmEnd)
\r
453 StopPlayback(vgmPb);
\r
455 vgmPb->file = vgmFile;
\r
457 StartPlayback(vgmPb);
\r
462 UINT8 PlaySFX(VGM_FILE* vgmFile, UINT8 sfxChnID)
\r
466 if (sfxChnID >= SFX_CHN_COUNT)
\r
469 vgmPb = &vgmPbSFX[sfxChnID];
\r
471 if (! vgmPb->vgmEnd)
\r
472 StopPlayback(vgmPb);
\r
474 vgmPb->file = vgmFile;
\r
476 StartPlayback(vgmPb);
\r
481 UINT8 StopMusic(void)
\r
483 StopPlayback(&vgmPbMusic);
\r
487 UINT8 StopSFX(UINT8 sfxChnID)
\r
489 if (sfxChnID == 0xFF)
\r
491 for (sfxChnID = 0; sfxChnID < SFX_CHN_COUNT; sfxChnID ++)
\r
492 StopPlayback(&vgmPbSFX[sfxChnID]);
\r
496 if (sfxChnID >= SFX_CHN_COUNT)
\r
499 StopPlayback(&vgmPbSFX[sfxChnID]);
\r
503 UINT8 PauseMusic(void)
\r
505 if (vgmPbMusic.vgmEnd == 0xFF)
\r
506 return 0xFF; // not playing
\r
507 if (vgmPbMusic.vgmEnd == 0x01)
\r
508 return 0x80; // finished playing already
\r
509 if (vgmPbMusic.vgmEnd == 0x02)
\r
510 return 0x01; // is already paused
\r
512 StopPlayback(&vgmPbMusic);
\r
513 vgmPbMusic.vgmEnd = 0x02;
\r
518 UINT8 ResumeMusic(void)
\r
520 if (vgmPbMusic.vgmEnd == 0xFF)
\r
521 return 0xFF; // not playing
\r
522 if (vgmPbMusic.vgmEnd == 0x01)
\r
523 return 0x80; // finished playing already
\r
524 if (! (vgmPbMusic.vgmEnd & 0x02))
\r
525 return 0x01; // is not paused
\r
527 vgmPbMusic.vgmEnd &= ~0x02;
\r
532 static void StartPlayback(VGM_PBK* vgmPb)
\r
534 if (vgmPb->file == NULL || vgmPb->file->data == NULL ||
\r
535 vgmPb->file->header.fccVGM != FCC_VGM
\r
538 vgmPb->vgmEnd = 0xFF;
\r
542 vgmPb->vgmEnd = 0x00; // set to 'running'
\r
543 vgmPb->vgmPos = vgmPb->file->header.lngDataOffset;
\r
544 vgmPb->vgmSmplPos = 0;
\r
545 vgmPb->pbSmplPos = 0;
\r
546 vgmPb->curLoopCnt = 0;
\r
547 memset(vgmPb->workRAM, 0x00, 0x04);
\r
549 if (vgmPb->pbMode == PBMODE_SFX)
\r
553 curReg = 0xB0 | vgmPb->oplChnMask;
\r
554 if (oplRegs_Music[curReg] & 0x20)
\r
555 OPL2_Write(curReg, oplRegs_Music[curReg] & ~0x20); // send Key Off
\r
557 vgmPbMusic.oplChnMask |= (1 << vgmPb->oplChnMask); // mask out music channel
\r
563 static void StopPlayback(VGM_PBK* vgmPb)
\r
565 if (vgmPb->vgmEnd & 0x80)
\r
568 if (vgmPb->pbMode == PBMODE_MUSIC)
\r
574 for (curReg = 0xB0; curReg < 0xB9; curReg ++, chnMask <<= 1)
\r
576 if (vgmPb->oplChnMask & chnMask)
\r
577 continue; // keep channels used by SFX untouched
\r
578 if (vgmPb->oplRegCache[curReg] & 0x20)
\r
580 vgmPb->oplRegCache[curReg] &= ~0x20;
\r
581 OPL2_Write(curReg, vgmPb->oplRegCache[curReg]); // send Key Off
\r
584 curReg = 0xBD; // rhythm register
\r
585 if (vgmPb->oplRegCache[curReg] & 0x1F)
\r
587 vgmPb->oplRegCache[curReg] &= ~0x1F;
\r
588 OPL2_Write(curReg, vgmPb->oplRegCache[curReg]); // send Key Off
\r
591 vgmPb->vgmEnd = 0x01;
\r
593 else //if (vgmPb->pbMode == PBMODE_SFX)
\r
599 curReg = 0xB0 | vgmPb->oplChnMask;
\r
600 if (vgmPb->oplRegCache[0x0C] & 0x20)
\r
602 vgmPb->oplRegCache[0x0C] &= ~0x20;
\r
603 OPL2_Write(curReg, vgmPb->oplRegCache[0x0C]); // send Key Off
\r
606 vgmPb->vgmEnd = 0x01;
\r
608 if (! vgmPbMusic.vgmEnd) // if (music is playing)
\r
610 opMask = CHN_OPMASK[vgmPb->oplChnMask];
\r
611 for (regID = 0x00; regID < 0x0A; regID ++)
\r
613 curReg = SFX_REGS[regID] + opMask;
\r
614 OPL2_Write(curReg, oplRegs_Music[curReg]); // restore Music register
\r
616 for (; regID < 0x0D; regID ++)
\r
618 curReg = SFX_REGS[regID] | vgmPb->oplChnMask;
\r
619 OPL2_Write(curReg, oplRegs_Music[curReg]); // restore Music register
\r
622 vgmPbMusic.oplChnMask &= ~(1 << vgmPb->oplChnMask);
\r
631 static void OPL_CachedWrite(VGM_PBK* vgmPb, UINT8 reg, UINT8 data)
\r
636 if (vgmPb->pbMode == PBMODE_MUSIC)
\r
639 data |= 0x20; // enforce "Waveform Select Enable" bit
\r
640 vgmPb->oplRegCache[reg] = data;
\r
642 ramOfs = SFX_REGS_REV[reg >> 4];
\r
643 if (ramOfs < 0x0A) // Operator 20/40/60/80/E0
\r
645 regChn = CHN_OPMASK_REV[reg & 0x1F] & 0x7F;
\r
646 if (vgmPb->oplChnMask & (1 << regChn))
\r
647 return; // channel overridden by SFX - return
\r
649 else if (ramOfs < 0x0D) // Operator C0/A0/B0
\r
651 regChn = reg & 0x0F;
\r
652 if (vgmPb->oplChnMask & (1 << regChn))
\r
653 return; // channel overridden by SFX - return
\r
656 else //if (vgmPb->pbMode == PBMODE_SFX)
\r
659 return; // no rhythm register for SFX
\r
661 ramOfs = SFX_REGS_REV[reg >> 4];
\r
662 if (ramOfs == 0xFF)
\r
665 if (ramOfs < 0x0A) // Operator 20/40/60/80/E0
\r
667 regChn = CHN_OPMASK_REV[reg & 0x1F];
\r
668 if (regChn == 0xFF)
\r
669 return; // ignore writes to invalid channels/operators
\r
670 ramOfs += (regChn & 0x80) >> 7;
\r
672 vgmPb->oplRegCache[ramOfs] = data;
\r
674 if (regChn != vgmPb->oplChnMask)
\r
676 // force command to current channel
\r
677 reg = SFX_REGS[ramOfs] + CHN_OPMASK[vgmPb->oplChnMask];
\r
680 else // Operator C0/A0/B0
\r
682 regChn = CHN_OPMASK_REV[reg & 0x0F];
\r
683 if (regChn >= 0x09)
\r
684 return; // ignore writes to invalid channels
\r
685 vgmPb->oplRegCache[ramOfs] = data;
\r
687 reg = (reg & 0xF0) | vgmPb->oplChnMask;
\r
691 OPL2_Write(reg, data);
\r
696 static void ym2413_write(VGM_PBK* vgmPb, UINT8 reg, UINT8 data)
\r
698 return; // unsupported for now
\r
701 static void ym3812_write(VGM_PBK* vgmPb, UINT8 reg, UINT8 data)
\r
705 vgmPb->workRAM[0x00] = data & 0x20; // store "Wave Select Enable" bit
\r
707 else if ((reg & 0xE0) == 0xE0)
\r
709 if (! vgmPb->workRAM[0x00]) // "Wave Select Enable" off?
\r
710 data = 0x00; // disable waveforms
\r
713 OPL_CachedWrite(vgmPb, reg, data);
\r
717 static void ym3512_write(VGM_PBK* vgmPb, UINT8 reg, UINT8 data)
\r
719 if ((reg & 0xE0) == 0xE0)
\r
721 data = 0x00; // OPL1 has no waveforms
\r
723 if (reg >= 0x07 && reg < 0x20)
\r
726 data &= ~0x0F; // mask out Y8950 DeltaT data
\r
728 return; // ignore Y8950 DeltaT writes
\r
731 OPL_CachedWrite(vgmPb, reg, data);
\r
735 static void ymf262_write(VGM_PBK* vgmPb, UINT8 port, UINT8 reg, UINT8 data)
\r
737 return; // unsupported for now
\r
742 void UpdateSoundEngine(void)
\r
747 tmrMask = OPL2_ReadStatus();
\r
748 if (! (tmrMask & 0x40))
\r
749 return; // wait for overflow
\r
750 OPL2_Write(0x04, 0x80); // Reset Timer/IRQ Status Flags
\r
752 if (! vgmPbMusic.vgmEnd)
\r
753 UpdateVGM(&vgmPbMusic, VGM_UPD_RATE);
\r
754 for (curSFX = 0; curSFX < SFX_CHN_COUNT; curSFX ++)
\r
756 if (! vgmPbSFX[curSFX].vgmEnd)
\r
757 UpdateVGM(&vgmPbSFX[curSFX], VGM_UPD_RATE);
\r