summaryrefslogtreecommitdiff
path: root/lib/rbcodec/codecs/libspc/spc_emu.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/rbcodec/codecs/libspc/spc_emu.c')
-rw-r--r--lib/rbcodec/codecs/libspc/spc_emu.c397
1 files changed, 397 insertions, 0 deletions
diff --git a/lib/rbcodec/codecs/libspc/spc_emu.c b/lib/rbcodec/codecs/libspc/spc_emu.c
new file mode 100644
index 0000000000..5ea5b0cdeb
--- /dev/null
+++ b/lib/rbcodec/codecs/libspc/spc_emu.c
@@ -0,0 +1,397 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2006-2007 Adam Gashlin (hcs)
11 * Copyright (C) 2004-2007 Shay Green (blargg)
12 * Copyright (C) 2002 Brad Martin
13 *
14 * This program is free software; you can redistribute it and/or
15 * modify it under the terms of the GNU General Public License
16 * as published by the Free Software Foundation; either version 2
17 * of the License, or (at your option) any later version.
18 *
19 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
20 * KIND, either express or implied.
21 *
22 ****************************************************************************/
23#include "codeclib.h"
24#include "spc_codec.h"
25#include "spc_profiler.h"
26
27/* lovingly ripped off from Game_Music_Emu 0.5.2. http://www.slack.net/~ant/ */
28/* DSP Based on Brad Martin's OpenSPC DSP emulator */
29/* tag reading from sexyspc by John Brawn (John_Brawn@yahoo.com) and others */
30
31struct cpu_ram_t ram IBSS_ATTR_SPC_LARGE_IRAM CACHEALIGN_ATTR;
32
33/**************** Timers ****************/
34
35static void Timer_run_( struct Timer* t, long time ) ICODE_ATTR_SPC;
36static void Timer_run_( struct Timer* t, long time )
37{
38 /* when disabled, next_tick should always be in the future */
39 assert( t->enabled );
40
41 int elapsed = ((time - t->next_tick) >> t->shift) + 1;
42 t->next_tick += elapsed << t->shift;
43
44 elapsed += t->count;
45 if ( elapsed >= t->period ) /* avoid unnecessary division */
46 {
47 int n = elapsed / t->period;
48 elapsed -= n * t->period;
49 t->counter = (t->counter + n) & 15;
50 }
51 t->count = elapsed;
52}
53
54static inline void Timer_run( struct Timer* t, long time )
55{
56 if ( time >= t->next_tick )
57 Timer_run_( t, time );
58}
59
60/**************** SPC emulator ****************/
61/* 1.024 MHz clock / 32000 samples per second */
62
63static void SPC_enable_rom( THIS, int enable )
64{
65 if ( this->rom_enabled != enable )
66 {
67 this->rom_enabled = enable;
68 ci->memcpy( RAM + ROM_ADDR, (enable ? this->boot_rom : this->extra_ram), ROM_SIZE );
69 /* TODO: ROM can still get overwritten when DSP writes to echo buffer */
70 }
71}
72
73void SPC_Init( THIS )
74{
75 this->timer [0].shift = 4 + 3; /* 8 kHz */
76 this->timer [1].shift = 4 + 3; /* 8 kHz */
77 this->timer [2].shift = 4; /* 8 kHz */
78
79 /* Put STOP instruction around memory to catch PC underflow/overflow. */
80 ci->memset( ram.padding1, 0xFF, sizeof ram.padding1 );
81 ci->memset( ram.padding2, 0xFF, sizeof ram.padding2 );
82
83 /* A few tracks read from the last four bytes of IPL ROM */
84 this->boot_rom [sizeof this->boot_rom - 2] = 0xC0;
85 this->boot_rom [sizeof this->boot_rom - 1] = 0xFF;
86 ci->memset( this->boot_rom, 0, sizeof this->boot_rom - 2 );
87
88 /* Have DSP in a defined state in case EMU is run and hasn't loaded
89 * a program yet */
90 DSP_reset(&this->dsp);
91}
92
93static void SPC_load_state( THIS, struct cpu_regs_t const* cpu_state,
94 const void* new_ram, const void* dsp_state )
95{
96 ci->memcpy(&(this->r),cpu_state,sizeof this->r);
97
98 /* ram */
99 ci->memcpy( RAM, new_ram, sizeof RAM );
100 ci->memcpy( this->extra_ram, RAM + ROM_ADDR, sizeof this->extra_ram );
101
102 /* boot rom (have to force enable_rom() to update it) */
103 this->rom_enabled = !(RAM [0xF1] & 0x80);
104 SPC_enable_rom( this, !this->rom_enabled );
105
106 /* dsp */
107 /* some SPCs rely on DSP immediately generating one sample */
108 this->extra_cycles = 32;
109 DSP_reset( &this->dsp );
110 int i;
111 for ( i = 0; i < REGISTER_COUNT; i++ )
112 DSP_write( &this->dsp, i, ((uint8_t const*) dsp_state) [i] );
113
114 /* timers */
115 for ( i = 0; i < TIMER_COUNT; i++ )
116 {
117 struct Timer* t = &this->timer [i];
118
119 t->next_tick = -EXTRA_CLOCKS;
120 t->enabled = (RAM [0xF1] >> i) & 1;
121 if ( !t->enabled )
122 t->next_tick = TIMER_DISABLED_TIME;
123 t->count = 0;
124 t->counter = RAM [0xFD + i] & 15;
125
126 int p = RAM [0xFA + i];
127 if ( !p )
128 p = 0x100;
129 t->period = p;
130 }
131
132 /* Handle registers which already give 0 when read by
133 setting RAM and not changing it.
134 Put STOP instruction in registers which can be read,
135 to catch attempted execution. */
136 RAM [0xF0] = 0;
137 RAM [0xF1] = 0;
138 RAM [0xF3] = 0xFF;
139 RAM [0xFA] = 0;
140 RAM [0xFB] = 0;
141 RAM [0xFC] = 0;
142 RAM [0xFD] = 0xFF;
143 RAM [0xFE] = 0xFF;
144 RAM [0xFF] = 0xFF;
145}
146
147static void clear_echo( THIS )
148{
149 if ( !(DSP_read( &this->dsp, 0x6C ) & 0x20) )
150 {
151 unsigned addr = 0x100 * DSP_read( &this->dsp, 0x6D );
152 size_t size = 0x800 * DSP_read( &this->dsp, 0x7D );
153 size_t max_size = sizeof RAM - addr;
154 if ( size > max_size )
155 size = sizeof RAM - addr;
156 ci->memset( RAM + addr, 0xFF, size );
157 }
158}
159
160int SPC_load_spc( THIS, const void* data, long size )
161{
162 struct spc_file_t const* spc = (struct spc_file_t const*) data;
163 struct cpu_regs_t regs;
164
165 if ( size < SPC_FILE_SIZE )
166 return -1;
167
168 if ( ci->memcmp( spc->signature, "SNES-SPC700 Sound File Data", 27 ) != 0 )
169 return -1;
170
171 regs.pc = spc->pc [1] * 0x100 + spc->pc [0];
172 regs.a = spc->a;
173 regs.x = spc->x;
174 regs.y = spc->y;
175 regs.status = spc->status;
176 regs.sp = spc->sp;
177
178 if ( (unsigned long) size >= sizeof *spc )
179 ci->memcpy( this->boot_rom, spc->ipl_rom, sizeof this->boot_rom );
180
181 SPC_load_state( this, &regs, spc->ram, spc->dsp );
182
183 clear_echo(this);
184
185 return 0;
186}
187
188/**************** DSP interaction ****************/
189static void SPC_run_dsp_( THIS, long time ) ICODE_ATTR_SPC;
190static void SPC_run_dsp_( THIS, long time )
191{
192 /* divide by CLOCKS_PER_SAMPLE */
193 int count = ((time - this->next_dsp) >> 5) + 1;
194 int32_t* buf = this->sample_buf;
195 this->sample_buf = buf + count;
196 this->next_dsp += count * CLOCKS_PER_SAMPLE;
197 DSP_run( &this->dsp, count, buf );
198}
199
200static inline void SPC_run_dsp( THIS, long time )
201{
202 if ( time >= this->next_dsp )
203 SPC_run_dsp_( this, time );
204}
205
206int SPC_read( THIS, unsigned addr, long const time )
207{
208 int result = RAM [addr];
209
210 if ( ((unsigned) (addr - 0xF0)) < 0x10 )
211 {
212 assert( 0xF0 <= addr && addr <= 0xFF );
213
214 /* counters */
215 int i = addr - 0xFD;
216 if ( i >= 0 )
217 {
218 struct Timer* t = &this->timer [i];
219 Timer_run( t, time );
220 result = t->counter;
221 t->counter = 0;
222 }
223 /* dsp */
224 else if ( addr == 0xF3 )
225 {
226 SPC_run_dsp( this, time );
227 result = DSP_read( &this->dsp, RAM [0xF2] & 0x7F );
228 }
229 }
230 return result;
231}
232
233void SPC_write( THIS, unsigned addr, int data, long const time )
234{
235 /* first page is very common */
236 if ( addr < 0xF0 )
237 {
238 RAM [addr] = (uint8_t) data;
239 }
240 else switch ( addr )
241 {
242 /* RAM */
243 default:
244 if ( addr < ROM_ADDR )
245 {
246 RAM [addr] = (uint8_t) data;
247 }
248 else
249 {
250 this->extra_ram [addr - ROM_ADDR] = (uint8_t) data;
251 if ( !this->rom_enabled )
252 RAM [addr] = (uint8_t) data;
253 }
254 break;
255
256 /* DSP */
257 /*case 0xF2:*/ /* mapped to RAM */
258 case 0xF3: {
259 SPC_run_dsp( this, time );
260 int reg = RAM [0xF2];
261 if ( reg < REGISTER_COUNT ) {
262 DSP_write( &this->dsp, reg, data );
263 }
264 else {
265 /*dprintf( "DSP write to $%02X\n", (int) reg ); */
266 }
267 break;
268 }
269
270 case 0xF0: /* Test register */
271 /*dprintf( "Wrote $%02X to $F0\n", (int) data ); */
272 break;
273
274 /* Config */
275 case 0xF1:
276 {
277 int i;
278 /* timers */
279 for ( i = 0; i < TIMER_COUNT; i++ )
280 {
281 struct Timer * t = this->timer+i;
282 if ( !(data & (1 << i)) )
283 {
284 t->enabled = 0;
285 t->next_tick = TIMER_DISABLED_TIME;
286 }
287 else if ( !t->enabled )
288 {
289 /* just enabled */
290 t->enabled = 1;
291 t->counter = 0;
292 t->count = 0;
293 t->next_tick = time;
294 }
295 }
296
297 /* port clears */
298 if ( data & 0x10 )
299 {
300 RAM [0xF4] = 0;
301 RAM [0xF5] = 0;
302 }
303 if ( data & 0x20 )
304 {
305 RAM [0xF6] = 0;
306 RAM [0xF7] = 0;
307 }
308
309 SPC_enable_rom( this, (data & 0x80) != 0 );
310 break;
311 }
312
313 /* Ports */
314 case 0xF4:
315 case 0xF5:
316 case 0xF6:
317 case 0xF7:
318 /* to do: handle output ports */
319 break;
320
321 /* verified on SNES that these are read/write (RAM) */
322 /*case 0xF8: */
323 /*case 0xF9: */
324
325 /* Timers */
326 case 0xFA:
327 case 0xFB:
328 case 0xFC: {
329 int i = addr - 0xFA;
330 struct Timer* t = &this->timer [i];
331 if ( (t->period & 0xFF) != data )
332 {
333 Timer_run( t, time );
334 this->timer[i].period = data ? data : 0x100;
335 }
336 break;
337 }
338
339 /* Counters (cleared on write) */
340 case 0xFD:
341 case 0xFE:
342 case 0xFF:
343 /*dprintf( "Wrote to counter $%02X\n", (int) addr ); */
344 this->timer [addr - 0xFD].counter = 0;
345 break;
346 }
347}
348
349/**************** Sample generation ****************/
350int SPC_play( THIS, long count, int32_t* out )
351{
352 int i;
353 assert( count % 2 == 0 ); /* output is always in pairs of samples */
354
355 long start_time = -(count >> 1) * CLOCKS_PER_SAMPLE - EXTRA_CLOCKS;
356
357 /* DSP output is made on-the-fly when DSP registers are read or written */
358 this->sample_buf = out;
359 this->next_dsp = start_time + CLOCKS_PER_SAMPLE;
360
361 /* Localize timer next_tick times and run them to the present to prevent
362 a running but ignored timer's next_tick from getting too far behind
363 and overflowing. */
364 for ( i = 0; i < TIMER_COUNT; i++ )
365 {
366 struct Timer* t = &this->timer [i];
367 if ( t->enabled )
368 {
369 t->next_tick += start_time + EXTRA_CLOCKS;
370 Timer_run( t, start_time );
371 }
372 }
373
374 /* Run from start_time to 0, pre-advancing by extra cycles from last run */
375 this->extra_cycles = CPU_run( this, start_time + this->extra_cycles ) +
376 EXTRA_CLOCKS;
377 if ( this->extra_cycles < 0 )
378 {
379 /*dprintf( "Unhandled instruction $%02X, pc = $%04X\n",
380 (int) CPU_read( r.pc ), (unsigned) r.pc ); */
381
382 return -1;
383 }
384
385 /* Catch DSP up to present */
386#if 0
387 ENTER_TIMER(cpu);
388#endif
389 SPC_run_dsp( this, -EXTRA_CLOCKS );
390#if 0
391 EXIT_TIMER(cpu);
392#endif
393 assert( this->next_dsp == CLOCKS_PER_SAMPLE - EXTRA_CLOCKS );
394 assert( this->sample_buf - out == count );
395
396 return 0;
397}