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)
\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 if (vgmFile->data == NULL)
\r
178 return 0xF0; // malloc error
\r
180 memcpy(vgmFile->data, &vgmBaseHdr, hdrSize);
\r
181 bytesToRead = vgmFile->dataLen - hdrSize;
\r
182 readEl = fread(vgmFile->data + hdrSize, 0x01, bytesToRead, hFile);
\r
183 if (readEl < bytesToRead)
\r
186 //return 0xFE; // read error
\r
187 vgmFile->dataLen = hdrSize + readEl;
\r
192 memcpy(&vgmFile->header, vgmFile->data, sizeof(VGM_HEADER));
\r
194 // relative -> absolute addresses
\r
195 vgmFile->header.lngEOFOffset += 0x04;
\r
196 if (vgmFile->header.lngGD3Offset)
\r
197 vgmFile->header.lngGD3Offset += 0x14;
\r
198 if (vgmFile->header.lngLoopOffset)
\r
199 vgmFile->header.lngLoopOffset += 0x1C;
\r
200 if (! vgmFile->header.lngDataOffset)
\r
201 vgmFile->header.lngDataOffset = 0x0C;
\r
202 vgmFile->header.lngDataOffset += 0x34;
\r
204 CurPos = vgmFile->header.lngDataOffset;
\r
205 if (vgmFile->header.lngVersion < 0x0150)
\r
207 hdrSize = sizeof(VGM_HEADER);
\r
208 if (hdrSize > CurPos)
\r
209 memset((UINT8*)&vgmFile->header + CurPos, 0x00, hdrSize - CurPos);
\r
215 void FreeVGMFile(VGM_FILE* vgmFile)
\r
217 free(vgmFile->data); vgmFile->data = NULL;
\r
218 vgmFile->dataLen = 0;
\r
224 static boolean DoVgmLoop(VGM_PBK* vgmPlay)
\r
226 const VGM_HEADER* vgmHdr = &vgmPlay->file->header;
\r
228 if (! vgmHdr->lngLoopOffset)
\r
231 vgmPlay->curLoopCnt ++;
\r
233 vgmPlay->vgmPos = vgmHdr->lngLoopOffset;
\r
234 vgmPlay->vgmSmplPos -= vgmHdr->lngLoopSamples;
\r
235 vgmPlay->pbSmplPos -= vgmHdr->lngLoopSamples;
\r
240 static void UpdateVGM(VGM_PBK* vgmPlay, UINT16 Samples)
\r
242 const dword/*32*/ vgmLen = vgmPlay->file->dataLen;
\r
243 const UINT8* vgmData = vgmPlay->file->data;
\r
244 const UINT8* VGMPnt;
\r
245 dword/*32*/ VGMPos;
\r
246 dword/*32*/ VGMSmplPos;
\r
249 dword/*32*/ blockLen;
\r
251 vgmPlay->pbSmplPos += Samples;
\r
252 VGMPos = vgmPlay->vgmPos;
\r
253 VGMSmplPos = vgmPlay->vgmSmplPos;
\r
254 while(VGMSmplPos < vgmPlay->pbSmplPos && ! vgmPlay->vgmEnd)
\r
256 VGMPnt = &vgmData[VGMPos];
\r
257 Command = VGMPnt[0x00];
\r
258 switch(Command & 0xF0)
\r
260 case 0x70: // small delay (1-16 samples)
\r
261 VGMSmplPos += (Command & 0x0F) + 0x01;
\r
264 case 0x80: // DAC write + small delay (0-15 samples)
\r
265 VGMSmplPos += (Command & 0x0F);
\r
271 case 0x66: // End Of File
\r
272 vgmPlay->vgmPos = VGMPos;
\r
273 vgmPlay->vgmSmplPos = VGMSmplPos;
\r
274 if (! DoVgmLoop(vgmPlay))
\r
275 vgmPlay->vgmEnd = 0x01;
\r
276 VGMPos = vgmPlay->vgmPos;
\r
277 VGMSmplPos = vgmPlay->vgmSmplPos;
\r
279 case 0x62: // 1/60s delay
\r
283 case 0x63: // 1/50s delay
\r
287 case 0x61: // xx Sample Delay
\r
288 VGMSmplPos += ReadLE16(&VGMPnt[0x01]);
\r
291 case 0x67: // Data Block (PCM Data Stream)
\r
292 blockType = VGMPnt[0x02];
\r
293 blockLen = ReadLE32(&VGMPnt[0x03]);
\r
294 blockLen &= 0x7FFFFFFF;
\r
295 VGMPos += 0x07 + blockLen;
\r
297 case 0x68: // PCM RAM write
\r
301 vgmPlay->vgmEnd = 0x01;
\r
306 if (Command == 0x50)
\r
308 VGMPos += 0x02; // SN76496 write
\r
313 case 0x51: // YM2413 write
\r
314 ym2413_write(vgmPlay, VGMPnt[0x01], VGMPnt[0x02]);
\r
316 case 0x5A: // YM3812 write
\r
317 ym3812_write(vgmPlay, VGMPnt[0x01], VGMPnt[0x02]);
\r
319 case 0x5B: // YM3526 write
\r
320 case 0x5C: // Y8950 write
\r
321 ym3512_write(vgmPlay, VGMPnt[0x01], VGMPnt[0x02]);
\r
323 case 0x5E: // YMF262 write, port 0
\r
324 case 0x5F: // YMF262 write, port 1
\r
325 ymf262_write(vgmPlay, Command & 0x01, VGMPnt[0x01], VGMPnt[0x02]);
\r
349 case 0x90: // DAC Ctrl: Setup Chip
\r
352 case 0x91: // DAC Ctrl: Set Data
\r
355 case 0x92: // DAC Ctrl: Set Freq
\r
358 case 0x93: // DAC Ctrl: Play from Start Pos
\r
361 case 0x94: // DAC Ctrl: Stop immediately
\r
364 case 0x95: // DAC Ctrl: Play Block (small)
\r
368 vgmPlay->vgmEnd = 0x01;
\r
373 vgmPlay->vgmEnd = 0x01;
\r
377 if (VGMPos >= vgmLen)
\r
378 vgmPlay->vgmEnd = 0x01;
\r
380 vgmPlay->vgmPos = VGMPos;
\r
381 vgmPlay->vgmSmplPos = VGMSmplPos;
\r
382 if (vgmPlay->vgmEnd)
\r
383 StopPlayback(vgmPlay);
\r
391 void InitEngine(void)
\r
396 memset(oplRegs_Music, 0x00, 0x100);
\r
397 memset(&vgmPbMusic, 0x00, sizeof(VGM_PBK));
\r
398 vgmPbMusic.pbMode = PBMODE_MUSIC;
\r
399 vgmPbMusic.vgmEnd = 0xFF;
\r
400 vgmPbMusic.oplChnMask = 0x0000;
\r
401 vgmPbMusic.oplRegCache = oplRegs_Music;
\r
403 for (curSFX = 0; curSFX < SFX_CHN_COUNT; curSFX ++)
\r
405 memset(&oplRegs_SFX[curSFX], 0x00, sizeof(VGM_PBK));
\r
406 memset(&vgmPbSFX[curSFX], 0x00, sizeof(VGM_PBK));
\r
407 vgmPbSFX[curSFX].pbMode = PBMODE_SFX;
\r
408 vgmPbSFX[curSFX].vgmEnd = 0xFF;
\r
409 vgmPbSFX[curSFX].oplChnMask = curSFX;
\r
410 vgmPbSFX[curSFX].oplRegCache = oplRegs_SFX[curSFX];
\r
418 OPL2_Write(curReg, 0x00);
\r
419 } while(curReg > 0x20);
\r
421 OPL2_Write(0x02, TIMER1_RATE); // set Timer 1 Period
\r
422 OPL2_Write(0x04, 0x01); // Timer 1 on/unmasked, Timer 2 off
\r
423 OPL2_Write(0x04, 0x80); // Reset Timer/IRQ Status Flags
\r
425 OPL2_Write(0x01, 0x20); // Waveform Select: Enable
\r
430 void DeinitEngine(void)
\r
434 StopPlayback(&vgmPbMusic);
\r
435 for (curSFX = 0; curSFX < SFX_CHN_COUNT; curSFX ++)
\r
436 StopPlayback(&vgmPbSFX[curSFX]);
\r
438 OPL2_Write(0x04, 0x00); // disable all timers
\r
444 UINT8 PlayMusic(VGM_FILE* vgmFile)
\r
446 VGM_PBK* vgmPb = &vgmPbMusic;
\r
448 if (! vgmPb->vgmEnd)
\r
449 StopPlayback(vgmPb);
\r
451 vgmPb->file = vgmFile;
\r
453 StartPlayback(vgmPb);
\r
458 UINT8 PlaySFX(VGM_FILE* vgmFile, UINT8 sfxChnID)
\r
462 if (sfxChnID >= SFX_CHN_COUNT)
\r
465 vgmPb = &vgmPbSFX[sfxChnID];
\r
467 if (! vgmPb->vgmEnd)
\r
468 StopPlayback(vgmPb);
\r
470 vgmPb->file = vgmFile;
\r
472 StartPlayback(vgmPb);
\r
477 UINT8 StopMusic(void)
\r
479 StopPlayback(&vgmPbMusic);
\r
483 UINT8 StopSFX(UINT8 sfxChnID)
\r
485 if (sfxChnID == 0xFF)
\r
487 for (sfxChnID = 0; sfxChnID < SFX_CHN_COUNT; sfxChnID ++)
\r
488 StopPlayback(&vgmPbSFX[sfxChnID]);
\r
492 if (sfxChnID >= SFX_CHN_COUNT)
\r
495 StopPlayback(&vgmPbSFX[sfxChnID]);
\r
499 UINT8 PauseMusic(void)
\r
501 if (vgmPbMusic.vgmEnd == 0xFF)
\r
502 return 0xFF; // not playing
\r
503 if (vgmPbMusic.vgmEnd == 0x01)
\r
504 return 0x80; // finished playing already
\r
505 if (vgmPbMusic.vgmEnd == 0x02)
\r
506 return 0x01; // is already paused
\r
508 StopPlayback(&vgmPbMusic);
\r
509 vgmPbMusic.vgmEnd = 0x02;
\r
514 UINT8 ResumeMusic(void)
\r
516 if (vgmPbMusic.vgmEnd == 0xFF)
\r
517 return 0xFF; // not playing
\r
518 if (vgmPbMusic.vgmEnd == 0x01)
\r
519 return 0x80; // finished playing already
\r
520 if (! (vgmPbMusic.vgmEnd & 0x02))
\r
521 return 0x01; // is not paused
\r
523 vgmPbMusic.vgmEnd &= ~0x02;
\r
528 static void StartPlayback(VGM_PBK* vgmPb)
\r
530 if (vgmPb->file == NULL || vgmPb->file->data == NULL ||
\r
531 vgmPb->file->header.fccVGM != FCC_VGM
\r
534 vgmPb->vgmEnd = 0xFF;
\r
538 vgmPb->vgmEnd = 0x00; // set to 'running'
\r
539 vgmPb->vgmPos = vgmPb->file->header.lngDataOffset;
\r
540 vgmPb->vgmSmplPos = 0;
\r
541 vgmPb->pbSmplPos = 0;
\r
542 vgmPb->curLoopCnt = 0;
\r
543 memset(vgmPb->workRAM, 0x00, 0x04);
\r
545 if (vgmPb->pbMode == PBMODE_SFX)
\r
549 curReg = 0xB0 | vgmPb->oplChnMask;
\r
550 if (oplRegs_Music[curReg] & 0x20)
\r
551 OPL2_Write(curReg, oplRegs_Music[curReg] & ~0x20); // send Key Off
\r
553 vgmPbMusic.oplChnMask |= (1 << vgmPb->oplChnMask); // mask out music channel
\r
559 static void StopPlayback(VGM_PBK* vgmPb)
\r
561 if (vgmPb->vgmEnd & 0x80)
\r
564 if (vgmPb->pbMode == PBMODE_MUSIC)
\r
570 for (curReg = 0xB0; curReg < 0xB9; curReg ++, chnMask <<= 1)
\r
572 if (vgmPb->oplChnMask & chnMask)
\r
573 continue; // keep channels used by SFX untouched
\r
574 if (vgmPb->oplRegCache[curReg] & 0x20)
\r
576 vgmPb->oplRegCache[curReg] &= ~0x20;
\r
577 OPL2_Write(curReg, vgmPb->oplRegCache[curReg]); // send Key Off
\r
580 curReg = 0xBD; // rhythm register
\r
581 if (vgmPb->oplRegCache[curReg] & 0x1F)
\r
583 vgmPb->oplRegCache[curReg] &= ~0x1F;
\r
584 OPL2_Write(curReg, vgmPb->oplRegCache[curReg]); // send Key Off
\r
587 vgmPb->vgmEnd = 0x01;
\r
589 else //if (vgmPb->pbMode == PBMODE_SFX)
\r
595 curReg = 0xB0 | vgmPb->oplChnMask;
\r
596 if (vgmPb->oplRegCache[0x0C] & 0x20)
\r
598 vgmPb->oplRegCache[0x0C] &= ~0x20;
\r
599 OPL2_Write(curReg, vgmPb->oplRegCache[0x0C]); // send Key Off
\r
602 vgmPb->vgmEnd = 0x01;
\r
604 if (! vgmPbMusic.vgmEnd) // if (music is playing)
\r
606 opMask = CHN_OPMASK[vgmPb->oplChnMask];
\r
607 for (regID = 0x00; regID < 0x0A; regID ++)
\r
609 curReg = SFX_REGS[regID] + opMask;
\r
610 OPL2_Write(curReg, oplRegs_Music[curReg]); // restore Music register
\r
612 for (; regID < 0x0D; regID ++)
\r
614 curReg = SFX_REGS[regID] | vgmPb->oplChnMask;
\r
615 OPL2_Write(curReg, oplRegs_Music[curReg]); // restore Music register
\r
618 vgmPbMusic.oplChnMask &= ~(1 << vgmPb->oplChnMask);
\r
627 static void OPL_CachedWrite(VGM_PBK* vgmPb, UINT8 reg, UINT8 data)
\r
632 if (vgmPb->pbMode == PBMODE_MUSIC)
\r
635 data |= 0x20; // enforce "Waveform Select Enable" bit
\r
636 vgmPb->oplRegCache[reg] = data;
\r
638 ramOfs = SFX_REGS_REV[reg >> 4];
\r
639 if (ramOfs < 0x0A) // Operator 20/40/60/80/E0
\r
641 regChn = CHN_OPMASK_REV[reg & 0x1F] & 0x7F;
\r
642 if (vgmPb->oplChnMask & (1 << regChn))
\r
643 return; // channel overridden by SFX - return
\r
645 else if (ramOfs < 0x0D) // Operator C0/A0/B0
\r
647 regChn = reg & 0x0F;
\r
648 if (vgmPb->oplChnMask & (1 << regChn))
\r
649 return; // channel overridden by SFX - return
\r
652 else //if (vgmPb->pbMode == PBMODE_SFX)
\r
655 return; // no rhythm register for SFX
\r
657 ramOfs = SFX_REGS_REV[reg >> 4];
\r
658 if (ramOfs == 0xFF)
\r
661 if (ramOfs < 0x0A) // Operator 20/40/60/80/E0
\r
663 regChn = CHN_OPMASK_REV[reg & 0x1F];
\r
664 if (regChn == 0xFF)
\r
665 return; // ignore writes to invalid channels/operators
\r
666 ramOfs += (regChn & 0x80) >> 7;
\r
668 vgmPb->oplRegCache[ramOfs] = data;
\r
670 if (regChn != vgmPb->oplChnMask)
\r
672 // force command to current channel
\r
673 reg = SFX_REGS[ramOfs] + CHN_OPMASK[vgmPb->oplChnMask];
\r
676 else // Operator C0/A0/B0
\r
678 regChn = CHN_OPMASK_REV[reg & 0x0F];
\r
679 if (regChn >= 0x09)
\r
680 return; // ignore writes to invalid channels
\r
681 vgmPb->oplRegCache[ramOfs] = data;
\r
683 reg = (reg & 0xF0) | vgmPb->oplChnMask;
\r
687 OPL2_Write(reg, data);
\r
692 static void ym2413_write(VGM_PBK* vgmPb, UINT8 reg, UINT8 data)
\r
694 return; // unsupported for now
\r
697 static void ym3812_write(VGM_PBK* vgmPb, UINT8 reg, UINT8 data)
\r
701 vgmPb->workRAM[0x00] = data & 0x20; // store "Wave Select Enable" bit
\r
703 else if ((reg & 0xE0) == 0xE0)
\r
705 if (! vgmPb->workRAM[0x00]) // "Wave Select Enable" off?
\r
706 data = 0x00; // disable waveforms
\r
709 OPL_CachedWrite(vgmPb, reg, data);
\r
713 static void ym3512_write(VGM_PBK* vgmPb, UINT8 reg, UINT8 data)
\r
715 if ((reg & 0xE0) == 0xE0)
\r
717 data = 0x00; // OPL1 has no waveforms
\r
719 if (reg >= 0x07 && reg < 0x20)
\r
722 data &= ~0x0F; // mask out Y8950 DeltaT data
\r
724 return; // ignore Y8950 DeltaT writes
\r
727 OPL_CachedWrite(vgmPb, reg, data);
\r
731 static void ymf262_write(VGM_PBK* vgmPb, UINT8 port, UINT8 reg, UINT8 data)
\r
733 return; // unsupported for now
\r
738 void UpdateSoundEngine(void)
\r
743 tmrMask = OPL2_ReadStatus();
\r
744 if (! (tmrMask & 0x40))
\r
745 return; // wait for overflow
\r
746 OPL2_Write(0x04, 0x80); // Reset Timer/IRQ Status Flags
\r
748 if (! vgmPbMusic.vgmEnd)
\r
749 UpdateVGM(&vgmPbMusic, VGM_UPD_RATE);
\r
750 for (curSFX = 0; curSFX < SFX_CHN_COUNT; curSFX ++)
\r
752 if (! vgmPbSFX[curSFX].vgmEnd)
\r
753 UpdateVGM(&vgmPbSFX[curSFX], VGM_UPD_RATE);
\r