Mercurial > audlegacy-plugins
comparison src/Input/console/Snes_Spc.cxx @ 0:13389e613d67 trunk
[svn] - initial import of audacious-plugins tree (lots to do)
author | nenolod |
---|---|
date | Mon, 18 Sep 2006 01:11:49 -0700 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:13389e613d67 |
---|---|
1 | |
2 // Game_Music_Emu 0.3.0. http://www.slack.net/~ant/ | |
3 | |
4 #include "Snes_Spc.h" | |
5 | |
6 #include <assert.h> | |
7 #include <string.h> | |
8 | |
9 /* Copyright (C) 2004-2006 Shay Green. This module is free software; you | |
10 can redistribute it and/or modify it under the terms of the GNU Lesser | |
11 General Public License as published by the Free Software Foundation; either | |
12 version 2.1 of the License, or (at your option) any later version. This | |
13 module is distributed in the hope that it will be useful, but WITHOUT ANY | |
14 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS | |
15 FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for | |
16 more details. You should have received a copy of the GNU Lesser General | |
17 Public License along with this module; if not, write to the Free Software | |
18 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ | |
19 | |
20 #include BLARGG_SOURCE_BEGIN | |
21 | |
22 // always in the future (CPU time can go over 0, but not by this much) | |
23 int const timer_disabled_time = 127; | |
24 | |
25 Snes_Spc::Snes_Spc() : dsp( ram ), cpu( this, ram ) | |
26 { | |
27 timer [0].shift = 7; // 8 kHz | |
28 timer [1].shift = 7; // 8 kHz | |
29 timer [2].shift = 4; // 64 kHz | |
30 | |
31 // Put STOP instruction past end of memory to catch PC overflow. | |
32 memset( ram + ram_size, 0xff, (sizeof ram) - ram_size ); | |
33 } | |
34 | |
35 // Load | |
36 | |
37 blargg_err_t Snes_Spc::load_spc( const void* data, long size, bool clear_echo_ ) | |
38 { | |
39 struct spc_file_t { | |
40 char signature [27]; | |
41 char unused [10]; | |
42 uint8_t pc [2]; | |
43 uint8_t a; | |
44 uint8_t x; | |
45 uint8_t y; | |
46 uint8_t status; | |
47 uint8_t sp; | |
48 char unused2 [212]; | |
49 uint8_t ram [0x10000]; | |
50 uint8_t dsp [128]; | |
51 }; | |
52 BOOST_STATIC_ASSERT( sizeof (spc_file_t) == spc_file_size ); | |
53 | |
54 const spc_file_t* spc = (spc_file_t*) data; | |
55 | |
56 if ( size < spc_file_size ) | |
57 return "Not an SPC file"; | |
58 | |
59 if ( strncmp( spc->signature, "SNES-SPC700 Sound File Data", 27 ) != 0 ) | |
60 return "Not an SPC file"; | |
61 | |
62 registers_t regs; | |
63 regs.pc = spc->pc [1] * 0x100 + spc->pc [0]; | |
64 regs.a = spc->a; | |
65 regs.x = spc->x; | |
66 regs.y = spc->y; | |
67 regs.status = spc->status; | |
68 regs.sp = spc->sp; | |
69 | |
70 const char* error = load_state( regs, spc->ram, spc->dsp ); | |
71 | |
72 echo_accessed = false; | |
73 | |
74 if ( clear_echo_ ) | |
75 clear_echo(); | |
76 | |
77 return error; | |
78 } | |
79 | |
80 void Snes_Spc::clear_echo() | |
81 { | |
82 if ( !(dsp.read( 0x6c ) & 0x20) ) | |
83 { | |
84 unsigned addr = 0x100 * dsp.read( 0x6d ); | |
85 unsigned size = 0x800 * dsp.read( 0x7d ); | |
86 unsigned limit = ram_size - addr; | |
87 memset( ram + addr, 0xff, (size < limit) ? size : limit ); | |
88 } | |
89 } | |
90 | |
91 // Handle other file formats (emulator save states) in user code, not here. | |
92 | |
93 blargg_err_t Snes_Spc::load_state( const registers_t& cpu_state, const void* new_ram, | |
94 const void* dsp_state ) | |
95 { | |
96 // cpu | |
97 cpu.r = cpu_state; | |
98 | |
99 // Allow DSP to generate one sample before code starts | |
100 // (Tengai Makyo Zero, Tenjin's Table Toss first notes are lost since it | |
101 // clears KON 31 cycles from starting execution. It works on the SNES | |
102 // since the SPC player adds a few extra cycles delay after restoring | |
103 // KON from the DSP registers at the end of an SPC file). | |
104 extra_cycles = 32; | |
105 | |
106 // ram | |
107 memcpy( ram, new_ram, ram_size ); | |
108 memcpy( extra_ram, ram + rom_addr, sizeof extra_ram ); | |
109 | |
110 // boot rom (have to force enable_rom() to update it) | |
111 rom_enabled = !(ram [0xf1] & 0x80); | |
112 enable_rom( !rom_enabled ); | |
113 | |
114 // dsp | |
115 dsp.reset(); | |
116 int i; | |
117 for ( i = 0; i < Spc_Dsp::register_count; i++ ) | |
118 dsp.write( i, ((uint8_t*) dsp_state) [i] ); | |
119 | |
120 // timers | |
121 for ( i = 0; i < timer_count; i++ ) | |
122 { | |
123 Timer& t = timer [i]; | |
124 | |
125 t.next_tick = 0; | |
126 t.enabled = (ram [0xf1] >> i) & 1; | |
127 if ( !t.enabled ) | |
128 t.next_tick = timer_disabled_time; | |
129 t.count = 0; | |
130 t.counter = ram [0xfd + i] & 15; | |
131 | |
132 int p = ram [0xfa + i]; | |
133 t.period = p ? p : 0x100; | |
134 } | |
135 | |
136 // Handle registers which already give 0 when read by setting RAM and not changing it. | |
137 // Put STOP instruction in registers which can be read, to catch attempted CPU execution. | |
138 ram [0xf0] = 0; | |
139 ram [0xf1] = 0; | |
140 ram [0xf3] = 0xff; | |
141 ram [0xfa] = 0; | |
142 ram [0xfb] = 0; | |
143 ram [0xfc] = 0; | |
144 ram [0xfd] = 0xff; | |
145 ram [0xfe] = 0xff; | |
146 ram [0xff] = 0xff; | |
147 | |
148 return NULL; // success | |
149 } | |
150 | |
151 // Hardware | |
152 | |
153 // Current time starts negative and ends at 0 | |
154 inline spc_time_t Snes_Spc::time() const | |
155 { | |
156 return -cpu.remain(); | |
157 } | |
158 | |
159 // Keep track of next time to run and avoid a function call if it hasn't been reached. | |
160 | |
161 // Timers | |
162 | |
163 void Snes_Spc::Timer::run_until_( spc_time_t time ) | |
164 { | |
165 if ( !enabled ) | |
166 dprintf( "next_tick: %ld, time: %ld", (long) next_tick, (long) time ); | |
167 assert( enabled ); // when disabled, next_tick should always be in the future | |
168 | |
169 int elapsed = ((time - next_tick) >> shift) + 1; | |
170 next_tick += elapsed << shift; | |
171 elapsed += count; | |
172 if ( elapsed >= period ) { // avoid costly divide | |
173 int n = elapsed / period; | |
174 elapsed -= n * period; | |
175 counter = (counter + n) & 15; | |
176 } | |
177 count = elapsed; | |
178 } | |
179 | |
180 // DSP | |
181 | |
182 const int clocks_per_sample = 32; // 1.024 MHz CPU clock / 32000 samples per second | |
183 | |
184 void Snes_Spc::run_dsp_( spc_time_t time ) | |
185 { | |
186 int count = ((time - next_dsp) >> 5) + 1; // divide by clocks_per_sample | |
187 sample_t* buf = sample_buf; | |
188 if ( buf ) { | |
189 sample_buf = buf + count * 2; // stereo | |
190 assert( sample_buf <= buf_end ); | |
191 } | |
192 next_dsp += count * clocks_per_sample; | |
193 dsp.run( count, buf ); | |
194 } | |
195 | |
196 inline void Snes_Spc::run_dsp( spc_time_t time ) | |
197 { | |
198 if ( time >= next_dsp ) | |
199 run_dsp_( time ); | |
200 } | |
201 | |
202 // Debug-only check for read/write within echo buffer, since this might result in | |
203 // inaccurate emulation due to the DSP not being caught up to the present. | |
204 inline void Snes_Spc::check_for_echo_access( spc_addr_t addr ) | |
205 { | |
206 if ( !echo_accessed && !(dsp.read( 0x6c ) & 0x20) ) | |
207 { | |
208 // ** If echo accesses are found that require running the DSP, cache | |
209 // the start and end address on DSP writes to speed up checking. | |
210 | |
211 unsigned start = 0x100 * dsp.read( 0x6d ); | |
212 unsigned end = start + 0x800 * dsp.read( 0x7d ); | |
213 if ( start <= addr && addr < end ) { | |
214 echo_accessed = true; | |
215 dprintf( "Read/write at $%04X within echo buffer\n", (unsigned) addr ); | |
216 } | |
217 } | |
218 } | |
219 | |
220 // Read | |
221 | |
222 int Snes_Spc::read( spc_addr_t addr ) | |
223 { | |
224 // zero page ram is used most often | |
225 if ( addr < 0xf0 ) | |
226 return ram [addr]; | |
227 | |
228 // dsp | |
229 if ( addr == 0xf3 ) { | |
230 run_dsp( time() ); | |
231 if ( ram [0xf2] >= Spc_Dsp::register_count ) | |
232 dprintf( "DSP read from $%02X\n", (int) ram [0xf2] ); | |
233 return dsp.read( ram [0xf2] & 0x7f ); | |
234 } | |
235 | |
236 // counters | |
237 unsigned i = addr - 0xfd; // negative converts to large positive unsigned | |
238 if ( i < timer_count ) { | |
239 Timer& t = timer [i]; | |
240 t.run_until( time() ); | |
241 int result = t.counter; | |
242 t.counter = 0; | |
243 return result; | |
244 } | |
245 | |
246 if ( addr == 0xf0 || addr == 0xf1 || addr == 0xf8 || | |
247 addr == 0xf9 || addr == 0xfa ) | |
248 dprintf( "Read from register $%02X\n", (int) addr ); | |
249 | |
250 // Registers which always read as 0 are handled by setting ram [reg] to 0 | |
251 // at startup then never changing that value. | |
252 | |
253 check(( check_for_echo_access( addr ), true )); | |
254 | |
255 // ram | |
256 return ram [addr]; | |
257 } | |
258 | |
259 | |
260 // Write | |
261 | |
262 const unsigned char Snes_Spc::boot_rom [rom_size] = { // verified | |
263 0xCD, 0xEF, 0xBD, 0xE8, 0x00, 0xC6, 0x1D, 0xD0, | |
264 0xFC, 0x8F, 0xAA, 0xF4, 0x8F, 0xBB, 0xF5, 0x78, | |
265 0xCC, 0xF4, 0xD0, 0xFB, 0x2F, 0x19, 0xEB, 0xF4, | |
266 0xD0, 0xFC, 0x7E, 0xF4, 0xD0, 0x0B, 0xE4, 0xF5, | |
267 0xCB, 0xF4, 0xD7, 0x00, 0xFC, 0xD0, 0xF3, 0xAB, | |
268 0x01, 0x10, 0xEF, 0x7E, 0xF4, 0x10, 0xEB, 0xBA, | |
269 0xF6, 0xDA, 0x00, 0xBA, 0xF4, 0xC4, 0xF4, 0xDD, | |
270 0x5D, 0xD0, 0xDB, 0x1F, 0x00, 0x00, 0xC0, 0xFF | |
271 }; | |
272 | |
273 void Snes_Spc::enable_rom( bool enable ) | |
274 { | |
275 if ( rom_enabled != enable ) | |
276 { | |
277 rom_enabled = enable; | |
278 memcpy( ram + rom_addr, (enable ? boot_rom : extra_ram), rom_size ); | |
279 } | |
280 } | |
281 | |
282 void Snes_Spc::write( spc_addr_t addr, int data ) | |
283 { | |
284 // first page is very common | |
285 if ( addr < 0xf0 ) { | |
286 ram [addr] = data; | |
287 } | |
288 else switch ( addr ) | |
289 { | |
290 // RAM | |
291 default: | |
292 check(( check_for_echo_access( addr ), true )); | |
293 if ( addr < rom_addr ) { | |
294 ram [addr] = data; | |
295 } | |
296 else { | |
297 extra_ram [addr - rom_addr] = data; | |
298 if ( !rom_enabled ) | |
299 ram [addr] = data; | |
300 } | |
301 break; | |
302 | |
303 // DSP | |
304 //case 0xf2: // mapped to RAM | |
305 case 0xf3: { | |
306 run_dsp( time() ); | |
307 int reg = ram [0xf2]; | |
308 if ( next_dsp > 0 ) { | |
309 // skip mode | |
310 | |
311 // key press | |
312 if ( reg == 0x4C ) | |
313 keys_pressed |= data & ~dsp.read( 0x5C ); | |
314 | |
315 // key release | |
316 if ( reg == 0x5C ) { | |
317 keys_released |= data; | |
318 keys_pressed &= ~data; | |
319 } | |
320 } | |
321 if ( reg < Spc_Dsp::register_count ) { | |
322 dsp.write( reg, data ); | |
323 } | |
324 else { | |
325 dprintf( "DSP write to $%02X\n", (int) reg ); | |
326 } | |
327 break; | |
328 } | |
329 | |
330 case 0xf0: // Test register | |
331 dprintf( "Wrote $%02X to $F0\n", (int) data ); | |
332 break; | |
333 | |
334 // Config | |
335 case 0xf1: | |
336 { | |
337 // timers | |
338 for ( int i = 0; i < timer_count; i++ ) | |
339 { | |
340 Timer& t = timer [i]; | |
341 if ( !(data & (1 << i)) ) { | |
342 t.enabled = 0; | |
343 t.next_tick = timer_disabled_time; | |
344 } | |
345 else if ( !t.enabled ) { | |
346 // just enabled | |
347 t.enabled = 1; | |
348 t.counter = 0; | |
349 t.count = 0; | |
350 t.next_tick = time(); | |
351 } | |
352 } | |
353 | |
354 // port clears | |
355 if ( data & 0x10 ) { | |
356 ram [0xf4] = 0; | |
357 ram [0xf5] = 0; | |
358 } | |
359 if ( data & 0x20 ) { | |
360 ram [0xf6] = 0; | |
361 ram [0xf7] = 0; | |
362 } | |
363 | |
364 enable_rom( data & 0x80 ); | |
365 | |
366 break; | |
367 } | |
368 | |
369 // Ports | |
370 case 0xf4: | |
371 case 0xf5: | |
372 case 0xf6: | |
373 case 0xf7: | |
374 // to do: handle output ports | |
375 break; | |
376 | |
377 //case 0xf8: // verified on SNES that these are read/write (RAM) | |
378 //case 0xf9: | |
379 | |
380 // Timers | |
381 case 0xfa: | |
382 case 0xfb: | |
383 case 0xfc: { | |
384 Timer& t = timer [addr - 0xfa]; | |
385 if ( (t.period & 0xff) != data ) { | |
386 t.run_until( time() ); | |
387 t.period = data ? data : 0x100; | |
388 } | |
389 break; | |
390 } | |
391 | |
392 // Counters (cleared on write) | |
393 case 0xfd: | |
394 case 0xfe: | |
395 case 0xff: | |
396 dprintf( "Wrote to counter $%02X\n", (int) addr ); | |
397 timer [addr - 0xfd].counter = 0; | |
398 break; | |
399 } | |
400 } | |
401 | |
402 // Play | |
403 | |
404 blargg_err_t Snes_Spc::skip( long count ) | |
405 { | |
406 if ( count > 4 * 32000L ) | |
407 { | |
408 // don't run DSP for long durations (2-3 times faster) | |
409 | |
410 const long sync_count = 32000L * 2; | |
411 | |
412 // keep track of any keys pressed/released (and not subsequently released) | |
413 keys_pressed = 0; | |
414 keys_released = 0; | |
415 // sentinel tells play to ignore DSP | |
416 BLARGG_RETURN_ERR( play( count - sync_count, skip_sentinel ) ); | |
417 | |
418 // press/release keys now | |
419 dsp.write( 0x5C, keys_released & ~keys_pressed ); | |
420 dsp.write( 0x4C, keys_pressed ); | |
421 | |
422 clear_echo(); | |
423 | |
424 // play the last few seconds normally to help synchronize DSP | |
425 count = sync_count; | |
426 } | |
427 | |
428 return play( count ); | |
429 } | |
430 | |
431 blargg_err_t Snes_Spc::play( long count, sample_t* out ) | |
432 { | |
433 require( count % 2 == 0 ); // output is always in pairs of samples | |
434 | |
435 // CPU time() runs from -duration to 0 | |
436 spc_time_t duration = (count / 2) * clocks_per_sample; | |
437 | |
438 // DSP output is made on-the-fly when the CPU reads/writes DSP registers | |
439 sample_buf = out; | |
440 buf_end = out + (out && out != skip_sentinel ? count : 0); | |
441 next_dsp = (out == skip_sentinel) ? clocks_per_sample : -duration + clocks_per_sample; | |
442 | |
443 // Localize timer next_tick times and run them to the present to prevent a running | |
444 // but ignored timer's next_tick from getting too far behind and overflowing. | |
445 for ( int i = 0; i < timer_count; i++ ) | |
446 { | |
447 Timer& t = timer [i]; | |
448 if ( t.enabled ) | |
449 { | |
450 t.next_tick -= duration; | |
451 t.run_until( -duration ); | |
452 } | |
453 } | |
454 | |
455 // Run CPU for duration, reduced by any extra cycles from previous run | |
456 int elapsed = cpu.run( duration - extra_cycles ); | |
457 if ( elapsed > 0 ) | |
458 { | |
459 dprintf( "Unhandled instruction $%02X, pc = $%04X\n", | |
460 (int) cpu.read( cpu.r.pc ), (unsigned) cpu.r.pc ); | |
461 return "Emulation error"; | |
462 } | |
463 extra_cycles = -elapsed; | |
464 | |
465 // Catch DSP up to present. | |
466 run_dsp( 0 ); | |
467 if ( out ) { | |
468 assert( next_dsp == clocks_per_sample ); | |
469 assert( out == skip_sentinel || sample_buf - out == count ); | |
470 } | |
471 buf_end = NULL; | |
472 | |
473 return blargg_success; | |
474 } | |
475 |