comparison Plugins/Input/console/Snes_Spc.cpp @ 90:252843aac42f trunk

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