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 | |
16 | STREAM dataStream = NULL; |
17 | STREAM audioStream = NULL; |
18 | uint32 audioLoopPos; |
19 | size_t partial_frames; |
20 | |
21 | // Sample buffer |
22 | static Resampler *msu_resampler = NULL; |
23 | |
24 | #ifdef UNZIP_SUPPORT |
25 | static 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 | |
58 | STREAM 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 | |
100 | static void AudioClose() |
101 | { |
102 | if (audioStream) |
103 | { |
104 | CLOSE_STREAM(audioStream); |
105 | audioStream = NULL; |
106 | } |
107 | } |
108 | |
109 | static 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 | |
144 | static void DataClose() |
145 | { |
146 | if (dataStream) |
147 | { |
148 | CLOSE_STREAM(dataStream); |
149 | dataStream = NULL; |
150 | } |
151 | } |
152 | |
153 | static 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 | |
165 | void 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 | |
190 | void S9xMSU1Init(void) |
191 | { |
192 | DataOpen(); |
193 | } |
194 | |
195 | void S9xMSU1DeInit(void) |
196 | { |
197 | DataClose(); |
198 | AudioClose(); |
199 | } |
200 | |
201 | bool 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 | |
229 | void 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 | |
280 | uint8 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 | |
318 | void 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 | |
389 | size_t S9xMSU1Samples(void) |
390 | { |
391 | return msu_resampler->space_filled(); |
392 | } |
393 | |
394 | void S9xMSU1SetOutput(Resampler *resampler) |
395 | { |
396 | msu_resampler = resampler; |
397 | } |
398 | |
399 | void 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 | |