view snes_spc/SNES_SPC_misc.cpp @ 4:87e64f302234

added conversion script
author Robert McIntyre <rlm@mit.edu>
date Fri, 21 Oct 2011 06:54:14 -0700
parents e38dacceb958
children
line wrap: on
line source
1 // SPC emulation support: init, sample buffering, reset, SPC loading
3 // snes_spc 0.9.0. http://www.slack.net/~ant/
5 #include "SNES_SPC.h"
7 #include <string.h>
9 /* Copyright (C) 2004-2007 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 more
16 details. You should have received a copy of the GNU Lesser General Public
17 License along with this module; if not, write to the Free Software Foundation,
18 Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
20 #include "blargg_source.h"
22 #define RAM (m.ram.ram)
23 #define REGS (m.smp_regs [0])
24 #define REGS_IN (m.smp_regs [1])
26 // (n ? n : 256)
27 #define IF_0_THEN_256( n ) ((uint8_t) ((n) - 1) + 1)
30 //// Init
32 blargg_err_t SNES_SPC::init()
33 {
34 memset( &m, 0, sizeof m );
35 dsp.init( RAM );
37 m.tempo = tempo_unit;
39 // Most SPC music doesn't need ROM, and almost all the rest only rely
40 // on these two bytes
41 m.rom [0x3E] = 0xFF;
42 m.rom [0x3F] = 0xC0;
44 static unsigned char const cycle_table [128] =
45 {// 01 23 45 67 89 AB CD EF
46 0x28,0x47,0x34,0x36,0x26,0x54,0x54,0x68, // 0
47 0x48,0x47,0x45,0x56,0x55,0x65,0x22,0x46, // 1
48 0x28,0x47,0x34,0x36,0x26,0x54,0x54,0x74, // 2
49 0x48,0x47,0x45,0x56,0x55,0x65,0x22,0x38, // 3
50 0x28,0x47,0x34,0x36,0x26,0x44,0x54,0x66, // 4
51 0x48,0x47,0x45,0x56,0x55,0x45,0x22,0x43, // 5
52 0x28,0x47,0x34,0x36,0x26,0x44,0x54,0x75, // 6
53 0x48,0x47,0x45,0x56,0x55,0x55,0x22,0x36, // 7
54 0x28,0x47,0x34,0x36,0x26,0x54,0x52,0x45, // 8
55 0x48,0x47,0x45,0x56,0x55,0x55,0x22,0xC5, // 9
56 0x38,0x47,0x34,0x36,0x26,0x44,0x52,0x44, // A
57 0x48,0x47,0x45,0x56,0x55,0x55,0x22,0x34, // B
58 0x38,0x47,0x45,0x47,0x25,0x64,0x52,0x49, // C
59 0x48,0x47,0x56,0x67,0x45,0x55,0x22,0x83, // D
60 0x28,0x47,0x34,0x36,0x24,0x53,0x43,0x40, // E
61 0x48,0x47,0x45,0x56,0x34,0x54,0x22,0x60, // F
62 };
64 // unpack cycle table
65 for ( int i = 0; i < 128; i++ )
66 {
67 int n = cycle_table [i];
68 m.cycle_table [i * 2 + 0] = n >> 4;
69 m.cycle_table [i * 2 + 1] = n & 0x0F;
70 }
72 #if SPC_LESS_ACCURATE
73 memcpy( reg_times, reg_times_, sizeof reg_times );
74 #endif
76 reset();
77 return 0;
78 }
80 void SNES_SPC::init_rom( uint8_t const in [rom_size] )
81 {
82 memcpy( m.rom, in, sizeof m.rom );
83 }
85 void SNES_SPC::set_tempo( int t )
86 {
87 m.tempo = t;
88 int const timer2_shift = 4; // 64 kHz
89 int const other_shift = 3; // 8 kHz
91 #if SPC_DISABLE_TEMPO
92 m.timers [2].prescaler = timer2_shift;
93 m.timers [1].prescaler = timer2_shift + other_shift;
94 m.timers [0].prescaler = timer2_shift + other_shift;
95 #else
96 if ( !t )
97 t = 1;
98 int const timer2_rate = 1 << timer2_shift;
99 int rate = (timer2_rate * tempo_unit + (t >> 1)) / t;
100 if ( rate < timer2_rate / 4 )
101 rate = timer2_rate / 4; // max 4x tempo
102 m.timers [2].prescaler = rate;
103 m.timers [1].prescaler = rate << other_shift;
104 m.timers [0].prescaler = rate << other_shift;
105 #endif
106 }
108 // Timer registers have been loaded. Applies these to the timers. Does not
109 // reset timer prescalers or dividers.
110 void SNES_SPC::timers_loaded()
111 {
112 int i;
113 for ( i = 0; i < timer_count; i++ )
114 {
115 Timer* t = &m.timers [i];
116 t->period = IF_0_THEN_256( REGS [r_t0target + i] );
117 t->enabled = REGS [r_control] >> i & 1;
118 t->counter = REGS_IN [r_t0out + i] & 0x0F;
119 }
121 set_tempo( m.tempo );
122 }
124 // Loads registers from unified 16-byte format
125 void SNES_SPC::load_regs( uint8_t const in [reg_count] )
126 {
127 memcpy( REGS, in, reg_count );
128 memcpy( REGS_IN, REGS, reg_count );
130 // These always read back as 0
131 REGS_IN [r_test ] = 0;
132 REGS_IN [r_control ] = 0;
133 REGS_IN [r_t0target] = 0;
134 REGS_IN [r_t1target] = 0;
135 REGS_IN [r_t2target] = 0;
136 }
138 // RAM was just loaded from SPC, with $F0-$FF containing SMP registers
139 // and timer counts. Copies these to proper registers.
140 void SNES_SPC::ram_loaded()
141 {
142 m.rom_enabled = 0;
143 load_regs( &RAM [0xF0] );
145 // Put STOP instruction around memory to catch PC underflow/overflow
146 memset( m.ram.padding1, cpu_pad_fill, sizeof m.ram.padding1 );
147 memset( m.ram.padding2, cpu_pad_fill, sizeof m.ram.padding2 );
148 }
150 // Registers were just loaded. Applies these new values.
151 void SNES_SPC::regs_loaded()
152 {
153 enable_rom( REGS [r_control] & 0x80 );
154 timers_loaded();
155 }
157 void SNES_SPC::reset_time_regs()
158 {
159 m.cpu_error = 0;
160 m.echo_accessed = 0;
161 m.spc_time = 0;
162 m.dsp_time = 0;
163 #if SPC_LESS_ACCURATE
164 m.dsp_time = clocks_per_sample + 1;
165 #endif
167 for ( int i = 0; i < timer_count; i++ )
168 {
169 Timer* t = &m.timers [i];
170 t->next_time = 1;
171 t->divider = 0;
172 }
174 regs_loaded();
176 m.extra_clocks = 0;
177 reset_buf();
178 }
180 void SNES_SPC::reset_common( int timer_counter_init )
181 {
182 int i;
183 for ( i = 0; i < timer_count; i++ )
184 REGS_IN [r_t0out + i] = timer_counter_init;
186 // Run IPL ROM
187 memset( &m.cpu_regs, 0, sizeof m.cpu_regs );
188 m.cpu_regs.pc = rom_addr;
190 REGS [r_test ] = 0x0A;
191 REGS [r_control] = 0xB0; // ROM enabled, clear ports
192 for ( i = 0; i < port_count; i++ )
193 REGS_IN [r_cpuio0 + i] = 0;
195 reset_time_regs();
196 }
198 void SNES_SPC::soft_reset()
199 {
200 reset_common( 0 );
201 dsp.soft_reset();
202 }
204 void SNES_SPC::reset()
205 {
206 memset( RAM, 0xFF, 0x10000 );
207 ram_loaded();
208 reset_common( 0x0F );
209 dsp.reset();
210 }
212 char const SNES_SPC::signature [signature_size + 1] =
213 "SNES-SPC700 Sound File Data v0.30\x1A\x1A";
215 blargg_err_t SNES_SPC::load_spc( void const* data, long size )
216 {
217 spc_file_t const* const spc = (spc_file_t const*) data;
219 // be sure compiler didn't insert any padding into fle_t
220 assert( sizeof (spc_file_t) == spc_min_file_size + 0x80 );
222 // Check signature and file size
223 if ( size < signature_size || memcmp( spc, signature, 27 ) )
224 return "Not an SPC file";
226 if ( size < spc_min_file_size )
227 return "Corrupt SPC file";
229 // CPU registers
230 m.cpu_regs.pc = spc->pch * 0x100 + spc->pcl;
231 m.cpu_regs.a = spc->a;
232 m.cpu_regs.x = spc->x;
233 m.cpu_regs.y = spc->y;
234 m.cpu_regs.psw = spc->psw;
235 m.cpu_regs.sp = spc->sp;
237 // RAM and registers
238 memcpy( RAM, spc->ram, 0x10000 );
239 ram_loaded();
241 // DSP registers
242 dsp.load( spc->dsp );
244 reset_time_regs();
246 return 0;
247 }
249 void SNES_SPC::clear_echo()
250 {
251 if ( !(dsp.read( SPC_DSP::r_flg ) & 0x20) )
252 {
253 int addr = 0x100 * dsp.read( SPC_DSP::r_esa );
254 int end = addr + 0x800 * (dsp.read( SPC_DSP::r_edl ) & 0x0F);
255 if ( end > 0x10000 )
256 end = 0x10000;
257 memset( &RAM [addr], 0xFF, end - addr );
258 }
259 }
262 //// Sample output
264 void SNES_SPC::reset_buf()
265 {
266 // Start with half extra buffer of silence
267 sample_t* out = m.extra_buf;
268 while ( out < &m.extra_buf [extra_size / 2] )
269 *out++ = 0;
271 m.extra_pos = out;
272 m.buf_begin = 0;
274 dsp.set_output( 0, 0 );
275 }
277 void SNES_SPC::set_output( sample_t* out, int size )
278 {
279 require( (size & 1) == 0 ); // size must be even
281 m.extra_clocks &= clocks_per_sample - 1;
282 if ( out )
283 {
284 sample_t const* out_end = out + size;
285 m.buf_begin = out;
286 m.buf_end = out_end;
288 // Copy extra to output
289 sample_t const* in = m.extra_buf;
290 while ( in < m.extra_pos && out < out_end )
291 *out++ = *in++;
293 // Handle output being full already
294 if ( out >= out_end )
295 {
296 // Have DSP write to remaining extra space
297 out = dsp.extra();
298 out_end = &dsp.extra() [extra_size];
300 // Copy any remaining extra samples as if DSP wrote them
301 while ( in < m.extra_pos )
302 *out++ = *in++;
303 assert( out <= out_end );
304 }
306 dsp.set_output( out, out_end - out );
307 }
308 else
309 {
310 reset_buf();
311 }
312 }
314 void SNES_SPC::save_extra()
315 {
316 // Get end pointers
317 sample_t const* main_end = m.buf_end; // end of data written to buf
318 sample_t const* dsp_end = dsp.out_pos(); // end of data written to dsp.extra()
319 if ( m.buf_begin <= dsp_end && dsp_end <= main_end )
320 {
321 main_end = dsp_end;
322 dsp_end = dsp.extra(); // nothing in DSP's extra
323 }
325 // Copy any extra samples at these ends into extra_buf
326 sample_t* out = m.extra_buf;
327 sample_t const* in;
328 for ( in = m.buf_begin + sample_count(); in < main_end; in++ )
329 *out++ = *in;
330 for ( in = dsp.extra(); in < dsp_end ; in++ )
331 *out++ = *in;
333 m.extra_pos = out;
334 assert( out <= &m.extra_buf [extra_size] );
335 }
337 blargg_err_t SNES_SPC::play( int count, sample_t* out )
338 {
339 require( (count & 1) == 0 ); // must be even
340 if ( count )
341 {
342 set_output( out, count );
343 end_frame( count * (clocks_per_sample / 2) );
344 }
346 const char* err = m.cpu_error;
347 m.cpu_error = 0;
348 return err;
349 }
351 blargg_err_t SNES_SPC::skip( int count )
352 {
353 #if SPC_LESS_ACCURATE
354 if ( count > 2 * sample_rate * 2 )
355 {
356 set_output( 0, 0 );
358 // Skip a multiple of 4 samples
359 time_t end = count;
360 count = (count & 3) + 1 * sample_rate * 2;
361 end = (end - count) * (clocks_per_sample / 2);
363 m.skipped_kon = 0;
364 m.skipped_koff = 0;
366 // Preserve DSP and timer synchronization
367 // TODO: verify that this really preserves it
368 int old_dsp_time = m.dsp_time + m.spc_time;
369 m.dsp_time = end - m.spc_time + skipping_time;
370 end_frame( end );
371 m.dsp_time = m.dsp_time - skipping_time + old_dsp_time;
373 dsp.write( SPC_DSP::r_koff, m.skipped_koff & ~m.skipped_kon );
374 dsp.write( SPC_DSP::r_kon , m.skipped_kon );
375 clear_echo();
376 }
377 #endif
379 return play( count, 0 );
380 }