]> 4ch.mooo.com Git - 16.git/blob - src/lib/vgmsnd/vgmSnd.c
fixed up wf3d8086
[16.git] / src / lib / vgmsnd / vgmSnd.c
1 // vgmSndDrv.c - VGM Sound Driver for OPL2\r
2 // Valley Bell, 2015-07-27\r
3 \r
4 // Note: This uses quite a few optimizations that assume that your\r
5 //       machine is Little Endian.\r
6 \r
7 #include <stdio.h>\r
8 #include <stdlib.h>\r
9 #include <string.h>\r
10 \r
11 #include "src/lib/vgmsnd/common.h"\r
12 #include "src/lib/vgmsnd/vgmSnd.h"\r
13 \r
14 \r
15 #define QUICK_READ\r
16 \r
17 \r
18 \r
19 #define FCC_VGM 0x206D6756      // 'Vgm '\r
20 #define FCC_GD3 0x20336447      // 'Gd3 '\r
21 \r
22 typedef struct _vgm_file_header_base\r
23 {\r
24         dword fccVGM;                   // 00\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
36 } VGM_BASE_HDR;\r
37 \r
38 #define PBMODE_MUSIC    0x00\r
39 #define PBMODE_SFX              0x01\r
40 typedef struct _vgm_playback\r
41 {\r
42         UINT8 pbMode;\r
43         UINT8 vgmEnd;   // 00 - running, 01 - finished, FF - not loaded\r
44         UINT16 curLoopCnt;\r
45         dword/*32*/ vgmPos;\r
46         dword/*32*/ vgmSmplPos;\r
47         dword/*32*/ pbSmplPos;\r
48         VGM_FILE* file;\r
49 \r
50         // oplChnMask:\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
53         UINT16 oplChnMask;\r
54         UINT8* oplRegCache;\r
55         UINT8 workRAM[0x04];\r
56 } VGM_PBK;\r
57 \r
58 \r
59 \r
60 INLINE UINT16 ReadLE16(const UINT8* buffer)\r
61 {\r
62 #ifdef QUICK_READ\r
63         return *(UINT16*)buffer;\r
64 #else\r
65         return (buffer[0x00] << 0) | (buffer[0x01] << 8);\r
66 #endif\r
67 }\r
68 \r
69 INLINE dword/*32*/ ReadLE32(const UINT8* buffer)\r
70 {\r
71 #ifdef QUICK_READ\r
72         return *(dword/*32*/*)buffer;\r
73 #else\r
74         return  (buffer[0x00] <<  0) | (buffer[0x01] <<  8) |\r
75                         (buffer[0x02] << 16) | (buffer[0x03] << 24);\r
76 #endif\r
77 }\r
78 \r
79 \r
80 // Function Prototypes\r
81 //UINT8 OpenVGMFile(const char* FileName, VGM_FILE* vgmFile);\r
82 //void FreeVGMFile(VGM_FILE* vgmFile);\r
83 \r
84 static boolean DoVgmLoop(VGM_PBK* vgmPlay);\r
85 static void UpdateVGM(VGM_PBK* vgmPlay, UINT16 Samples);\r
86 \r
87 //void InitEngine(void);\r
88 //void DeinitEngine(void);\r
89 \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
98 \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
103 \r
104 //void UpdateSoundEngine(void);\r
105 \r
106 \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
110 \r
111 \r
112 \r
113 \r
114 #define SFX_CHN_COUNT   6\r
115 \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
118 \r
119 static VGM_PBK vgmPbMusic;\r
120 static VGM_PBK vgmPbSFX[SFX_CHN_COUNT];\r
121 \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
124 \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
138 \r
139 \r
140 UINT8 OpenVGMFile(const char* FileName, VGM_FILE* vgmFile, global_game_variables_t *gvar)\r
141 {\r
142         size_t hdrSize;\r
143         size_t readEl;  // 'elements' read from file\r
144         size_t bytesToRead;\r
145         VGM_BASE_HDR vgmBaseHdr;\r
146         FILE* hFile;\r
147         dword/*32*/ CurPos;\r
148 \r
149         memset(vgmFile, 0x00, sizeof(VGM_FILE));\r
150 \r
151         hFile = fopen(FileName, "rb");\r
152         if (hFile == NULL)\r
153                 return 0xFF;\r
154 \r
155         hdrSize = sizeof(VGM_BASE_HDR);\r
156         readEl = fread(&vgmBaseHdr, hdrSize, 0x01, hFile);\r
157         if (readEl == 0)\r
158         {\r
159                 fclose(hFile);\r
160                 return 0xFE;    // read error\r
161         }\r
162         if (vgmBaseHdr.fccVGM != FCC_VGM)\r
163         {\r
164                 fclose(hFile);\r
165                 return 0x80;    // bad signature\r
166         }\r
167         if (vgmBaseHdr.lngVersion < 0x0150)\r
168         {\r
169                 fclose(hFile);\r
170                 return 0x81;    // We don't support VGM v1.10 and earlier\r
171         }\r
172 \r
173         vgmFile->dataLen = vgmBaseHdr.lngEOFOffset + 0x04;\r
174 #ifndef VGM_USESCAMMPM\r
175         vgmFile->data = (UINT8*)malloc(vgmFile->dataLen);\r
176 #else\r
177         MM_GetPtr(MEMPTRCONV gvar->ca.audiosegs[0], vgmFile->dataLen, gvar);\r
178         vgmFile->data = (UINT8*)gvar->ca.audiosegs[0];\r
179 #endif\r
180         if (vgmFile->data == NULL)\r
181         {\r
182                 fclose(hFile);\r
183                 return 0xF0;    // malloc error\r
184         }\r
185         memcpy(vgmFile->data, &vgmBaseHdr, hdrSize);\r
186         bytesToRead = vgmFile->dataLen - hdrSize;\r
187         readEl = fread(vgmFile->data + hdrSize, 0x01, bytesToRead, hFile);\r
188         if (readEl < bytesToRead)\r
189         {\r
190                 //fclose(hFile);\r
191                 //return 0xFE;  // read error\r
192                 vgmFile->dataLen = hdrSize + readEl;\r
193         }\r
194 \r
195         fclose(hFile);\r
196 \r
197         memcpy(&vgmFile->header, vgmFile->data, sizeof(VGM_HEADER));\r
198 \r
199         // relative -> absolute addresses\r
200         vgmFile->header.lngEOFOffset += 0x04;\r
201         if (vgmFile->header.lngGD3Offset)\r
202                 vgmFile->header.lngGD3Offset += 0x14;\r
203         if (vgmFile->header.lngLoopOffset)\r
204                 vgmFile->header.lngLoopOffset += 0x1C;\r
205         if (! vgmFile->header.lngDataOffset)\r
206                 vgmFile->header.lngDataOffset = 0x0C;\r
207         vgmFile->header.lngDataOffset += 0x34;\r
208 \r
209         CurPos = vgmFile->header.lngDataOffset;\r
210         if (vgmFile->header.lngVersion < 0x0150)\r
211                 CurPos = 0x40;\r
212         hdrSize = sizeof(VGM_HEADER);\r
213         if (hdrSize > CurPos)\r
214                 memset((UINT8*)&vgmFile->header + CurPos, 0x00, hdrSize - CurPos);\r
215 \r
216         fclose(hFile);\r
217         return 0x00;\r
218 }\r
219 \r
220 void FreeVGMFile(VGM_FILE* vgmFile, global_game_variables_t *gvar)\r
221 {\r
222 #ifndef VGM_USESCAMMPM\r
223         if(vgmFile->data){ free(vgmFile->data); vgmFile->data = NULL; }\r
224 #else\r
225         MM_FreePtr(MEMPTRCONV gvar->ca.audiosegs[0], gvar);\r
226 #endif\r
227 //      if(vgmFile->data) free(vgmFile->data);\r
228         vgmFile->dataLen = 0;\r
229 \r
230         return;\r
231 }\r
232 \r
233 \r
234 static boolean DoVgmLoop(VGM_PBK* vgmPlay)\r
235 {\r
236         const VGM_HEADER* vgmHdr = &vgmPlay->file->header;\r
237 \r
238         if (! vgmHdr->lngLoopOffset)\r
239                 return false;\r
240 \r
241         vgmPlay->curLoopCnt ++;\r
242 \r
243         vgmPlay->vgmPos = vgmHdr->lngLoopOffset;\r
244         vgmPlay->vgmSmplPos -= vgmHdr->lngLoopSamples;\r
245         vgmPlay->pbSmplPos -= vgmHdr->lngLoopSamples;\r
246 \r
247         return true;\r
248 }\r
249 \r
250 static void UpdateVGM(VGM_PBK* vgmPlay, UINT16 Samples)\r
251 {\r
252         const dword/*32*/ vgmLen = vgmPlay->file->dataLen;\r
253         const UINT8* vgmData = vgmPlay->file->data;\r
254         const UINT8* VGMPnt;\r
255         dword/*32*/ VGMPos;\r
256         dword/*32*/ VGMSmplPos;\r
257         UINT8 Command;\r
258         UINT8 blockType;\r
259         dword/*32*/ blockLen;\r
260 \r
261         vgmPlay->pbSmplPos += Samples;\r
262         VGMPos = vgmPlay->vgmPos;\r
263         VGMSmplPos = vgmPlay->vgmSmplPos;\r
264         while(VGMSmplPos < vgmPlay->pbSmplPos && ! vgmPlay->vgmEnd)\r
265         {\r
266                 VGMPnt = &vgmData[VGMPos];\r
267                 Command = VGMPnt[0x00];\r
268                 switch(Command & 0xF0)\r
269                 {\r
270                 case 0x70:      // small delay (1-16 samples)\r
271                         VGMSmplPos += (Command & 0x0F) + 0x01;\r
272                         VGMPos += 0x01;\r
273                         break;\r
274                 case 0x80:      // DAC write + small delay (0-15 samples)\r
275                         VGMSmplPos += (Command & 0x0F);\r
276                         VGMPos += 0x01;\r
277                         break;\r
278                 case 0x60:\r
279                         switch(Command)\r
280                         {\r
281                         case 0x66:      // End Of File\r
282                                 vgmPlay->vgmPos = VGMPos;\r
283                                 vgmPlay->vgmSmplPos = VGMSmplPos;\r
284                                 if (! DoVgmLoop(vgmPlay))\r
285                                         vgmPlay->vgmEnd = 0x01;\r
286                                 VGMPos = vgmPlay->vgmPos;\r
287                                 VGMSmplPos = vgmPlay->vgmSmplPos;\r
288                                 break;\r
289                         case 0x62:      // 1/60s delay\r
290                                 VGMSmplPos += 735;\r
291                                 VGMPos += 0x01;\r
292                                 break;\r
293                         case 0x63:      // 1/50s delay\r
294                                 VGMSmplPos += 882;\r
295                                 VGMPos += 0x01;\r
296                                 break;\r
297                         case 0x61:      // xx Sample Delay\r
298                                 VGMSmplPos += ReadLE16(&VGMPnt[0x01]);\r
299                                 VGMPos += 0x03;\r
300                                 break;\r
301                         case 0x67:      // Data Block (PCM Data Stream)\r
302                                 blockType = VGMPnt[0x02];\r
303                                 blockLen = ReadLE32(&VGMPnt[0x03]);\r
304                                 blockLen &= 0x7FFFFFFF;\r
305                                 VGMPos += 0x07 + blockLen;\r
306                                 break;\r
307                         case 0x68:      // PCM RAM write\r
308                                 VGMPos += 0x0C;\r
309                                 break;\r
310                         default:\r
311                                 vgmPlay->vgmEnd = 0x01;\r
312                                 break;\r
313                         }\r
314                         break;\r
315                 case 0x50:\r
316                         if (Command == 0x50)\r
317                         {\r
318                                 VGMPos += 0x02; // SN76496 write\r
319                                 break;\r
320                         }\r
321                         switch(Command)\r
322                         {\r
323                         case 0x51:      // YM2413 write\r
324                                 ym2413_write(vgmPlay, VGMPnt[0x01], VGMPnt[0x02]);\r
325                                 break;\r
326                         case 0x5A:      // YM3812 write\r
327                                 ym3812_write(vgmPlay, VGMPnt[0x01], VGMPnt[0x02]);\r
328                                 break;\r
329                         case 0x5B:      // YM3526 write\r
330                         case 0x5C:      // Y8950 write\r
331                                 ym3512_write(vgmPlay, VGMPnt[0x01], VGMPnt[0x02]);\r
332                                 break;\r
333                         case 0x5E:      // YMF262 write, port 0\r
334                         case 0x5F:      // YMF262 write, port 1\r
335                                 ymf262_write(vgmPlay, Command & 0x01, VGMPnt[0x01], VGMPnt[0x02]);\r
336                                 break;\r
337                         }\r
338                         VGMPos += 0x03;\r
339                         break;\r
340                 case 0x30:\r
341                         VGMPos += 0x02;\r
342                         break;\r
343                 case 0x40:\r
344                 case 0xA0:\r
345                 case 0xB0:\r
346                         VGMPos += 0x03;\r
347                         break;\r
348                 case 0xC0:\r
349                 case 0xD0:\r
350                         VGMPos += 0x04;\r
351                         break;\r
352                 case 0xE0:\r
353                 case 0xF0:\r
354                         VGMPos += 0x05;\r
355                         break;\r
356                 case 0x90:\r
357                         switch(Command)\r
358                         {\r
359                         case 0x90:      // DAC Ctrl: Setup Chip\r
360                                 VGMPos += 0x05;\r
361                                 break;\r
362                         case 0x91:      // DAC Ctrl: Set Data\r
363                                 VGMPos += 0x05;\r
364                                 break;\r
365                         case 0x92:      // DAC Ctrl: Set Freq\r
366                                 VGMPos += 0x06;\r
367                                 break;\r
368                         case 0x93:      // DAC Ctrl: Play from Start Pos\r
369                                 VGMPos += 0x0B;\r
370                                 break;\r
371                         case 0x94:      // DAC Ctrl: Stop immediately\r
372                                 VGMPos += 0x02;\r
373                                 break;\r
374                         case 0x95:      // DAC Ctrl: Play Block (small)\r
375                                 VGMPos += 0x05;\r
376                                 break;\r
377                         default:\r
378                                 vgmPlay->vgmEnd = 0x01;\r
379                                 break;\r
380                         }\r
381                         break;\r
382                 default:\r
383                         vgmPlay->vgmEnd = 0x01;\r
384                         return;\r
385                 }\r
386 \r
387                 if (VGMPos >= vgmLen)\r
388                         vgmPlay->vgmEnd = 0x01;\r
389         }\r
390         vgmPlay->vgmPos = VGMPos;\r
391         vgmPlay->vgmSmplPos = VGMSmplPos;\r
392         if (vgmPlay->vgmEnd)\r
393                 StopPlayback(vgmPlay);\r
394 \r
395         return;\r
396 }\r
397 \r
398 \r
399 \r
400 \r
401 void InitEngine(void)\r
402 {\r
403         UINT8 curSFX;\r
404         UINT8 curReg;\r
405 \r
406         memset(oplRegs_Music, 0x00, 0x100);\r
407         memset(&vgmPbMusic, 0x00, sizeof(VGM_PBK));\r
408         vgmPbMusic.pbMode = PBMODE_MUSIC;\r
409         vgmPbMusic.vgmEnd = 0xFF;\r
410         vgmPbMusic.oplChnMask = 0x0000;\r
411         vgmPbMusic.oplRegCache = oplRegs_Music;\r
412 \r
413         for (curSFX = 0; curSFX < SFX_CHN_COUNT; curSFX ++)\r
414         {\r
415                 memset(&oplRegs_SFX[curSFX], 0x00, sizeof(VGM_PBK));\r
416                 memset(&vgmPbSFX[curSFX], 0x00, sizeof(VGM_PBK));\r
417                 vgmPbSFX[curSFX].pbMode = PBMODE_SFX;\r
418                 vgmPbSFX[curSFX].vgmEnd = 0xFF;\r
419                 vgmPbSFX[curSFX].oplChnMask = curSFX;\r
420                 vgmPbSFX[curSFX].oplRegCache = oplRegs_SFX[curSFX];\r
421         }\r
422 \r
423         // reset OPL2 chip\r
424         curReg = 0x00;\r
425         do\r
426         {\r
427                 curReg --;\r
428                 OPL2_Write(curReg, 0x00);\r
429         } while(curReg > 0x20);\r
430 \r
431         OPL2_Write(0x02, TIMER1_RATE);  // set Timer 1 Period\r
432         OPL2_Write(0x04, 0x01); // Timer 1 on/unmasked, Timer 2 off\r
433         OPL2_Write(0x04, 0x80); // Reset Timer/IRQ Status Flags\r
434 \r
435         OPL2_Write(0x01, 0x20); // Waveform Select: Enable\r
436 \r
437         return;\r
438 }\r
439 \r
440 void DeinitEngine(void)\r
441 {\r
442         UINT8 curSFX;\r
443 \r
444         StopPlayback(&vgmPbMusic);\r
445         for (curSFX = 0; curSFX < SFX_CHN_COUNT; curSFX ++)\r
446                 StopPlayback(&vgmPbSFX[curSFX]);\r
447 \r
448         OPL2_Write(0x04, 0x00); // disable all timers\r
449 \r
450         return;\r
451 }\r
452 \r
453 \r
454 UINT8 PlayMusic(VGM_FILE* vgmFile)\r
455 {\r
456         VGM_PBK* vgmPb = &vgmPbMusic;\r
457 \r
458         if (! vgmPb->vgmEnd)\r
459                 StopPlayback(vgmPb);\r
460 \r
461         vgmPb->file = vgmFile;\r
462 \r
463         StartPlayback(vgmPb);\r
464 \r
465         return 0x00;\r
466 }\r
467 \r
468 UINT8 PlaySFX(VGM_FILE* vgmFile, UINT8 sfxChnID)\r
469 {\r
470         VGM_PBK* vgmPb;\r
471 \r
472         if (sfxChnID >= SFX_CHN_COUNT)\r
473                 return 0xFF;\r
474 \r
475         vgmPb = &vgmPbSFX[sfxChnID];\r
476 \r
477         if (! vgmPb->vgmEnd)\r
478                 StopPlayback(vgmPb);\r
479 \r
480         vgmPb->file = vgmFile;\r
481 \r
482         StartPlayback(vgmPb);\r
483 \r
484         return 0x00;\r
485 }\r
486 \r
487 UINT8 StopMusic(void)\r
488 {\r
489         StopPlayback(&vgmPbMusic);\r
490         return 0x00;\r
491 }\r
492 \r
493 UINT8 StopSFX(UINT8 sfxChnID)\r
494 {\r
495         if (sfxChnID == 0xFF)\r
496         {\r
497                 for (sfxChnID = 0; sfxChnID < SFX_CHN_COUNT; sfxChnID ++)\r
498                         StopPlayback(&vgmPbSFX[sfxChnID]);\r
499                 return 0x00;\r
500         }\r
501 \r
502         if (sfxChnID >= SFX_CHN_COUNT)\r
503                 return 0xFF;\r
504 \r
505         StopPlayback(&vgmPbSFX[sfxChnID]);\r
506         return 0x00;\r
507 }\r
508 \r
509 UINT8 PauseMusic(void)\r
510 {\r
511         if (vgmPbMusic.vgmEnd == 0xFF)\r
512                 return 0xFF;    // not playing\r
513         if (vgmPbMusic.vgmEnd == 0x01)\r
514                 return 0x80;    // finished playing already\r
515         if (vgmPbMusic.vgmEnd == 0x02)\r
516                 return 0x01;    // is already paused\r
517 \r
518         StopPlayback(&vgmPbMusic);\r
519         vgmPbMusic.vgmEnd = 0x02;\r
520 \r
521         return 0x00;\r
522 }\r
523 \r
524 UINT8 ResumeMusic(void)\r
525 {\r
526         if (vgmPbMusic.vgmEnd == 0xFF)\r
527                 return 0xFF;    // not playing\r
528         if (vgmPbMusic.vgmEnd == 0x01)\r
529                 return 0x80;    // finished playing already\r
530         if (! (vgmPbMusic.vgmEnd & 0x02))\r
531                 return 0x01;    // is not paused\r
532 \r
533         vgmPbMusic.vgmEnd &= ~0x02;\r
534 \r
535         return 0x00;\r
536 }\r
537 \r
538 static void StartPlayback(VGM_PBK* vgmPb)\r
539 {\r
540         if (vgmPb->file == NULL || vgmPb->file->data == NULL ||\r
541                 vgmPb->file->header.fccVGM != FCC_VGM\r
542 )\r
543         {\r
544                 vgmPb->vgmEnd = 0xFF;\r
545                 return;\r
546         }\r
547 \r
548         vgmPb->vgmEnd = 0x00;   // set to 'running'\r
549         vgmPb->vgmPos = vgmPb->file->header.lngDataOffset;\r
550         vgmPb->vgmSmplPos = 0;\r
551         vgmPb->pbSmplPos = 0;\r
552         vgmPb->curLoopCnt = 0;\r
553         memset(vgmPb->workRAM, 0x00, 0x04);\r
554 \r
555         if (vgmPb->pbMode == PBMODE_SFX)\r
556         {\r
557                 UINT8 curReg;\r
558 \r
559                 curReg = 0xB0 | vgmPb->oplChnMask;\r
560                 if (oplRegs_Music[curReg] & 0x20)\r
561                         OPL2_Write(curReg, oplRegs_Music[curReg] & ~0x20);      // send Key Off\r
562 \r
563                 vgmPbMusic.oplChnMask |= (1 << vgmPb->oplChnMask);      // mask out music channel\r
564         }\r
565 \r
566         return;\r
567 }\r
568 \r
569 static void StopPlayback(VGM_PBK* vgmPb)\r
570 {\r
571         if (vgmPb->vgmEnd & 0x80)\r
572                 return;\r
573 \r
574         if (vgmPb->pbMode == PBMODE_MUSIC)\r
575         {\r
576                 UINT8 curReg;\r
577                 UINT16 chnMask;\r
578 \r
579                 chnMask = 0x0001;\r
580                 for (curReg = 0xB0; curReg < 0xB9; curReg ++, chnMask <<= 1)\r
581                 {\r
582                         if (vgmPb->oplChnMask & chnMask)\r
583                                 continue;       // keep channels used by SFX untouched\r
584                         if (vgmPb->oplRegCache[curReg] & 0x20)\r
585                         {\r
586                                 vgmPb->oplRegCache[curReg] &= ~0x20;\r
587                                 OPL2_Write(curReg, vgmPb->oplRegCache[curReg]); // send Key Off\r
588                         }\r
589                 }\r
590                 curReg = 0xBD;  // rhythm register\r
591                 if (vgmPb->oplRegCache[curReg] & 0x1F)\r
592                 {\r
593                         vgmPb->oplRegCache[curReg] &= ~0x1F;\r
594                         OPL2_Write(curReg, vgmPb->oplRegCache[curReg]); // send Key Off\r
595                 }\r
596 \r
597                 vgmPb->vgmEnd = 0x01;\r
598         }\r
599         else //if (vgmPb->pbMode == PBMODE_SFX)\r
600         {\r
601                 UINT8 regID;\r
602                 UINT8 curReg;\r
603                 UINT8 opMask;\r
604 \r
605                 curReg = 0xB0 | vgmPb->oplChnMask;\r
606                 if (vgmPb->oplRegCache[0x0C] & 0x20)\r
607                 {\r
608                         vgmPb->oplRegCache[0x0C] &= ~0x20;\r
609                         OPL2_Write(curReg, vgmPb->oplRegCache[0x0C]);   // send Key Off\r
610                 }\r
611 \r
612                 vgmPb->vgmEnd = 0x01;\r
613 \r
614                 if (! vgmPbMusic.vgmEnd)        // if (music is playing)\r
615                 {\r
616                         opMask = CHN_OPMASK[vgmPb->oplChnMask];\r
617                         for (regID = 0x00; regID < 0x0A; regID ++)\r
618                         {\r
619                                 curReg = SFX_REGS[regID] + opMask;\r
620                                 OPL2_Write(curReg, oplRegs_Music[curReg]);      // restore Music register\r
621                         }\r
622                         for (; regID < 0x0D; regID ++)\r
623                         {\r
624                                 curReg = SFX_REGS[regID] | vgmPb->oplChnMask;\r
625                                 OPL2_Write(curReg, oplRegs_Music[curReg]);      // restore Music register\r
626                         }\r
627 \r
628                         vgmPbMusic.oplChnMask &= ~(1 << vgmPb->oplChnMask);\r
629                 }\r
630         }\r
631 \r
632         return;\r
633 }\r
634 \r
635 \r
636 \r
637 static void OPL_CachedWrite(VGM_PBK* vgmPb, UINT8 reg, UINT8 data)\r
638 {\r
639         UINT8 regChn;\r
640         UINT8 ramOfs;\r
641 \r
642         if (vgmPb->pbMode == PBMODE_MUSIC)\r
643         {\r
644                 if (reg == 0x01)\r
645                         data |= 0x20;   // enforce "Waveform Select Enable" bit\r
646                 vgmPb->oplRegCache[reg] = data;\r
647 \r
648                 ramOfs = SFX_REGS_REV[reg >> 4];\r
649                 if (ramOfs < 0x0A)      // Operator 20/40/60/80/E0\r
650                 {\r
651                         regChn = CHN_OPMASK_REV[reg & 0x1F] & 0x7F;\r
652                         if (vgmPb->oplChnMask & (1 << regChn))\r
653                                 return; // channel overridden by SFX - return\r
654                 }\r
655                 else if (ramOfs < 0x0D) // Operator C0/A0/B0\r
656                 {\r
657                         regChn = reg & 0x0F;\r
658                         if (vgmPb->oplChnMask & (1 << regChn))\r
659                                 return; // channel overridden by SFX - return\r
660                 }\r
661         }\r
662         else //if (vgmPb->pbMode == PBMODE_SFX)\r
663         {\r
664                 if (reg == 0xBD)\r
665                         return; // no rhythm register for SFX\r
666 \r
667                 ramOfs = SFX_REGS_REV[reg >> 4];\r
668                 if (ramOfs == 0xFF)\r
669                         return;\r
670 \r
671                 if (ramOfs < 0x0A)      // Operator 20/40/60/80/E0\r
672                 {\r
673                         regChn = CHN_OPMASK_REV[reg & 0x1F];\r
674                         if (regChn == 0xFF)\r
675                                 return; // ignore writes to invalid channels/operators\r
676                         ramOfs += (regChn & 0x80) >> 7;\r
677                         regChn &= 0x7F;\r
678                         vgmPb->oplRegCache[ramOfs] = data;\r
679 \r
680                         if (regChn != vgmPb->oplChnMask)\r
681                         {\r
682                                 // force command to current channel\r
683                                 reg = SFX_REGS[ramOfs] + CHN_OPMASK[vgmPb->oplChnMask];\r
684                         }\r
685                 }\r
686                 else    // Operator C0/A0/B0\r
687                 {\r
688                         regChn = CHN_OPMASK_REV[reg & 0x0F];\r
689                         if (regChn >= 0x09)\r
690                                 return; // ignore writes to invalid channels\r
691                         vgmPb->oplRegCache[ramOfs] = data;\r
692 \r
693                         reg = (reg & 0xF0) | vgmPb->oplChnMask;\r
694                 }\r
695         }\r
696 \r
697         OPL2_Write(reg, data);\r
698         return;\r
699 }\r
700 \r
701 \r
702 static void ym2413_write(VGM_PBK* vgmPb, UINT8 reg, UINT8 data)\r
703 {\r
704         return; // unsupported for now\r
705 }\r
706 \r
707 static void ym3812_write(VGM_PBK* vgmPb, UINT8 reg, UINT8 data)\r
708 {\r
709         if (reg == 0x01)\r
710         {\r
711                 vgmPb->workRAM[0x00] = data & 0x20;     // store "Wave Select Enable" bit\r
712         }\r
713         else if ((reg & 0xE0) == 0xE0)\r
714         {\r
715                 if (! vgmPb->workRAM[0x00])     // "Wave Select Enable" off?\r
716                         data = 0x00;    // disable waveforms\r
717         }\r
718 \r
719         OPL_CachedWrite(vgmPb, reg, data);\r
720         return;\r
721 }\r
722 \r
723 static void ym3512_write(VGM_PBK* vgmPb, UINT8 reg, UINT8 data)\r
724 {\r
725         if ((reg & 0xE0) == 0xE0)\r
726         {\r
727                 data = 0x00;    // OPL1 has no waveforms\r
728         }\r
729         if (reg >= 0x07 && reg < 0x20)\r
730         {\r
731                 if (reg == 0x08)\r
732                         data &= ~0x0F;  // mask out Y8950 DeltaT data\r
733                 else\r
734                         return; // ignore Y8950 DeltaT writes\r
735         }\r
736 \r
737         OPL_CachedWrite(vgmPb, reg, data);\r
738         return;\r
739 }\r
740 \r
741 static void ymf262_write(VGM_PBK* vgmPb, UINT8 port, UINT8 reg, UINT8 data)\r
742 {\r
743         return; // unsupported for now\r
744 }\r
745 \r
746 \r
747 \r
748 void UpdateSoundEngine(void)\r
749 {\r
750         UINT8 tmrMask;\r
751         UINT8 curSFX;\r
752 \r
753         tmrMask = OPL2_ReadStatus();\r
754         if (! (tmrMask & 0x40))\r
755                 return; // wait for overflow\r
756         OPL2_Write(0x04, 0x80); // Reset Timer/IRQ Status Flags\r
757 \r
758         if (! vgmPbMusic.vgmEnd)\r
759                 UpdateVGM(&vgmPbMusic, VGM_UPD_RATE);\r
760         for (curSFX = 0; curSFX < SFX_CHN_COUNT; curSFX ++)\r
761         {\r
762                 if (! vgmPbSFX[curSFX].vgmEnd)\r
763                         UpdateVGM(&vgmPbSFX[curSFX], VGM_UPD_RATE);\r
764         }\r
765 \r
766         return;\r
767 }\r