Mercurial > audlegacy
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 |