1/*****************************************************************************\
2 Snes9x - Portable Super Nintendo Entertainment System (TM) emulator.
3 This file is licensed under the Snes9x License.
4 For further information, consult the LICENSE file in the root directory.
5\*****************************************************************************/
6
7#include "snes9x.h"
8#include "memmap.h"
9#include "display.h"
10#include "msu1.h"
11#include "apu/resampler.h"
12#include "apu/bapu/dsp/blargg_endian.h"
13#include <fstream>
14#include <sys/stat.h>
15
16STREAM dataStream = NULL;
17STREAM audioStream = NULL;
18uint32 audioLoopPos;
19size_t partial_frames;
20
21// Sample buffer
22static Resampler *msu_resampler = NULL;
23
24#ifdef UNZIP_SUPPORT
25static int unzFindExtension(unzFile &file, const char *ext, bool restart = TRUE, bool print = TRUE, bool allowExact = FALSE)
26{
27 unz_file_info info;
28 int port, l = strlen(ext), e = allowExact ? 0 : 1;
29
30 if (restart)
31 port = unzGoToFirstFile(file);
32 else
33 port = unzGoToNextFile(file);
34
35 while (port == UNZ_OK)
36 {
37 int len;
38 char name[132];
39
40 unzGetCurrentFileInfo(file, &info, name, 128, NULL, 0, NULL, 0);
41 len = strlen(name);
42
43 if (len >= l + e && strcasecmp(name + len - l, ext) == 0 && unzOpenCurrentFile(file) == UNZ_OK)
44 {
45 if (print)
46 printf("Using msu file %s", name);
47
48 return (port);
49 }
50
51 port = unzGoToNextFile(file);
52 }
53
54 return (port);
55}
56#endif
57
58STREAM S9xMSU1OpenFile(const char *msu_ext, bool skip_unpacked)
59{
60 const char *filename = S9xGetFilename(msu_ext, ROMFILENAME_DIR);
61 STREAM file = 0;
62
63 if (!skip_unpacked)
64 {
65 file = OPEN_STREAM(filename, "rb");
66 if (file)
67 printf("Using msu file %s.\n", filename);
68 }
69
70#ifdef UNZIP_SUPPORT
71 // look for msu1 pack file in the rom or patch dir if msu data file not found in rom dir
72 if (!file)
73 {
74 const char *zip_filename = S9xGetFilename(".msu1", ROMFILENAME_DIR);
75 unzFile unzFile = unzOpen(zip_filename);
76
77 if (!unzFile)
78 {
79 zip_filename = S9xGetFilename(".msu1", PATCH_DIR);
80 unzFile = unzOpen(zip_filename);
81 }
82
83 if (unzFile)
84 {
85 int port = unzFindExtension(unzFile, msu_ext, true, true, true);
86 if (port == UNZ_OK)
87 {
88 printf(" in %s.\n", zip_filename);
89 file = new unzStream(unzFile);
90 }
91 else
92 unzClose(unzFile);
93 }
94 }
95#endif
96
97 return file;
98}
99
100static void AudioClose()
101{
102 if (audioStream)
103 {
104 CLOSE_STREAM(audioStream);
105 audioStream = NULL;
106 }
107}
108
109static bool AudioOpen()
110{
111 MSU1.MSU1_STATUS |= AudioError;
112
113 AudioClose();
114
115 char ext[_MAX_EXT];
116 snprintf(ext, _MAX_EXT, "-%d.pcm", MSU1.MSU1_CURRENT_TRACK);
117
118 audioStream = S9xMSU1OpenFile(ext);
119 if (audioStream)
120 {
121 if (GETC_STREAM(audioStream) != 'M')
122 return false;
123 if (GETC_STREAM(audioStream) != 'S')
124 return false;
125 if (GETC_STREAM(audioStream) != 'U')
126 return false;
127 if (GETC_STREAM(audioStream) != '1')
128 return false;
129
130 READ_STREAM((char *)&audioLoopPos, 4, audioStream);
131 audioLoopPos = GET_LE32(&audioLoopPos);
132 audioLoopPos <<= 2;
133 audioLoopPos += 8;
134
135 MSU1.MSU1_AUDIO_POS = 8;
136
137 MSU1.MSU1_STATUS &= ~AudioError;
138 return true;
139 }
140
141 return false;
142}
143
144static void DataClose()
145{
146 if (dataStream)
147 {
148 CLOSE_STREAM(dataStream);
149 dataStream = NULL;
150 }
151}
152
153static bool DataOpen()
154{
155 DataClose();
156
157 dataStream = S9xMSU1OpenFile(".msu");
158
159 if(!dataStream)
160 dataStream = S9xMSU1OpenFile("msu1.rom");
161
162 return dataStream != NULL;
163}
164
165void S9xResetMSU(void)
166{
167 MSU1.MSU1_STATUS = 0;
168 MSU1.MSU1_DATA_SEEK = 0;
169 MSU1.MSU1_DATA_POS = 0;
170 MSU1.MSU1_TRACK_SEEK = 0;
171 MSU1.MSU1_CURRENT_TRACK = 0;
172 MSU1.MSU1_RESUME_TRACK = 0;
173 MSU1.MSU1_VOLUME = 0;
174 MSU1.MSU1_CONTROL = 0;
175 MSU1.MSU1_AUDIO_POS = 0;
176 MSU1.MSU1_RESUME_POS = 0;
177
178 if (msu_resampler)
179 msu_resampler->clear();
180
181 partial_frames = 0;
182
183 DataClose();
184
185 AudioClose();
186
187 Settings.MSU1 = S9xMSU1ROMExists();
188}
189
190void S9xMSU1Init(void)
191{
192 DataOpen();
193}
194
195void S9xMSU1DeInit(void)
196{
197 DataClose();
198 AudioClose();
199}
200
201bool S9xMSU1ROMExists(void)
202{
203 STREAM s = S9xMSU1OpenFile(".msu");
204 if (s)
205 {
206 CLOSE_STREAM(s);
207 return true;
208 }
209#ifdef UNZIP_SUPPORT
210 char drive[_MAX_DRIVE + 1], dir[_MAX_DIR + 1], def[_MAX_FNAME + 1], ext[_MAX_EXT + 1];
211 _splitpath(Memory.ROMFilename, drive, dir, def, ext);
212 if (!strcasecmp(ext, ".msu1"))
213 return true;
214
215 unzFile unzFile = unzOpen(S9xGetFilename(".msu1", ROMFILENAME_DIR));
216
217 if(!unzFile)
218 unzFile = unzOpen(S9xGetFilename(".msu1", PATCH_DIR));
219
220 if (unzFile)
221 {
222 unzClose(unzFile);
223 return true;
224 }
225#endif
226 return false;
227}
228
229void S9xMSU1Generate(size_t sample_count)
230{
231 partial_frames += 4410 * (sample_count / 2);
232
233 while (partial_frames >= 3204)
234 {
235 if (MSU1.MSU1_STATUS & AudioPlaying && audioStream)
236 {
237 int32 sample;
238 int16* left = (int16*)&sample;
239 int16* right = left + 1;
240
241 int bytes_read = READ_STREAM((char *)&sample, 4, audioStream);
242 if (bytes_read == 4)
243 {
244 *left = ((int32)(int16)GET_LE16(left) * MSU1.MSU1_VOLUME / 255);
245 *right = ((int32)(int16)GET_LE16(right) * MSU1.MSU1_VOLUME / 255);
246
247 msu_resampler->push_sample(*left, *right);
248 MSU1.MSU1_AUDIO_POS += 4;
249 partial_frames -= 3204;
250 }
251 else
252 if (bytes_read >= 0)
253 {
254 if (MSU1.MSU1_STATUS & AudioRepeating)
255 {
256 MSU1.MSU1_AUDIO_POS = audioLoopPos;
257 REVERT_STREAM(audioStream, MSU1.MSU1_AUDIO_POS, 0);
258 }
259 else
260 {
261 MSU1.MSU1_STATUS &= ~(AudioPlaying | AudioRepeating);
262 REVERT_STREAM(audioStream, 8, 0);
263 }
264 }
265 else
266 {
267 MSU1.MSU1_STATUS &= ~(AudioPlaying | AudioRepeating);
268 }
269 }
270 else
271 {
272 MSU1.MSU1_STATUS &= ~(AudioPlaying | AudioRepeating);
273 partial_frames -= 3204;
274 msu_resampler->push_sample(0, 0);
275 }
276 }
277}
278
279
280uint8 S9xMSU1ReadPort(uint8 port)
281{
282 switch (port)
283 {
284 case 0:
285 return MSU1.MSU1_STATUS | MSU1_REVISION;
286 case 1:
287 {
288 if (MSU1.MSU1_STATUS & DataBusy)
289 return 0;
290 if (!dataStream)
291 return 0;
292 int data = GETC_STREAM(dataStream);
293 if (data >= 0)
294 {
295 MSU1.MSU1_DATA_POS++;
296 return data;
297 }
298 return 0;
299 }
300 case 2:
301 return 'S';
302 case 3:
303 return '-';
304 case 4:
305 return 'M';
306 case 5:
307 return 'S';
308 case 6:
309 return 'U';
310 case 7:
311 return '1';
312 }
313
314 return 0;
315}
316
317
318void S9xMSU1WritePort(uint8 port, uint8 byte)
319{
320 switch (port)
321 {
322 case 0:
323 MSU1.MSU1_DATA_SEEK &= 0xFFFFFF00;
324 MSU1.MSU1_DATA_SEEK |= byte << 0;
325 break;
326 case 1:
327 MSU1.MSU1_DATA_SEEK &= 0xFFFF00FF;
328 MSU1.MSU1_DATA_SEEK |= byte << 8;
329 break;
330 case 2:
331 MSU1.MSU1_DATA_SEEK &= 0xFF00FFFF;
332 MSU1.MSU1_DATA_SEEK |= byte << 16;
333 break;
334 case 3:
335 MSU1.MSU1_DATA_SEEK &= 0x00FFFFFF;
336 MSU1.MSU1_DATA_SEEK |= byte << 24;
337 MSU1.MSU1_DATA_POS = MSU1.MSU1_DATA_SEEK;
338 if (dataStream)
339 {
340 REVERT_STREAM(dataStream, MSU1.MSU1_DATA_POS, 0);
341 }
342 break;
343 case 4:
344 MSU1.MSU1_TRACK_SEEK &= 0xFF00;
345 MSU1.MSU1_TRACK_SEEK |= byte;
346 break;
347 case 5:
348 MSU1.MSU1_TRACK_SEEK &= 0x00FF;
349 MSU1.MSU1_TRACK_SEEK |= (byte << 8);
350 MSU1.MSU1_CURRENT_TRACK = MSU1.MSU1_TRACK_SEEK;
351
352 MSU1.MSU1_STATUS &= ~AudioPlaying;
353 MSU1.MSU1_STATUS &= ~AudioRepeating;
354
355 if (AudioOpen())
356 {
357 if (MSU1.MSU1_CURRENT_TRACK == MSU1.MSU1_RESUME_TRACK)
358 {
359 MSU1.MSU1_AUDIO_POS = MSU1.MSU1_RESUME_POS;
360 MSU1.MSU1_RESUME_POS = 0;
361 MSU1.MSU1_RESUME_TRACK = ~0;
362 }
363 else
364 {
365 MSU1.MSU1_AUDIO_POS = 8;
366 }
367
368 REVERT_STREAM(audioStream, MSU1.MSU1_AUDIO_POS, 0);
369 }
370 break;
371 case 6:
372 MSU1.MSU1_VOLUME = byte;
373 break;
374 case 7:
375 if (MSU1.MSU1_STATUS & (AudioBusy | AudioError))
376 break;
377
378 MSU1.MSU1_STATUS = (MSU1.MSU1_STATUS & ~0x30) | ((byte & 0x03) << 4);
379
380 if ((byte & (Play | Resume)) == Resume)
381 {
382 MSU1.MSU1_RESUME_TRACK = MSU1.MSU1_CURRENT_TRACK;
383 MSU1.MSU1_RESUME_POS = MSU1.MSU1_AUDIO_POS;
384 }
385 break;
386 }
387}
388
389size_t S9xMSU1Samples(void)
390{
391 return msu_resampler->space_filled();
392}
393
394void S9xMSU1SetOutput(Resampler *resampler)
395{
396 msu_resampler = resampler;
397}
398
399void S9xMSU1PostLoadState(void)
400{
401 if (DataOpen())
402 {
403 REVERT_STREAM(dataStream, MSU1.MSU1_DATA_POS, 0);
404 }
405
406 if (MSU1.MSU1_STATUS & AudioPlaying)
407 {
408 uint32 savedPosition = MSU1.MSU1_AUDIO_POS;
409
410 if (AudioOpen())
411 {
412 REVERT_STREAM(audioStream, 4, 0);
413 READ_STREAM((char *)&audioLoopPos, 4, audioStream);
414 audioLoopPos = GET_LE32(&audioLoopPos);
415 audioLoopPos <<= 2;
416 audioLoopPos += 8;
417
418 MSU1.MSU1_AUDIO_POS = savedPosition;
419 REVERT_STREAM(audioStream, MSU1.MSU1_AUDIO_POS, 0);
420 }
421 else
422 {
423 MSU1.MSU1_STATUS &= ~(AudioPlaying | AudioRepeating);
424 MSU1.MSU1_STATUS |= AudioError;
425 }
426 }
427
428 if (msu_resampler)
429 msu_resampler->clear();
430
431 partial_frames = 0;
432}
433