comparison src/console/Sap_Emu.cxx @ 316:fb513e10174e trunk

[svn] - merge libconsole-blargg into mainline libconsole: + obsoletes plugins-ugly:sapplug
author nenolod
date Thu, 30 Nov 2006 19:54:33 -0800
parents
children 230decbfe9be
comparison
equal deleted inserted replaced
315:2294f3a6f136 316:fb513e10174e
1 // Game_Music_Emu 0.5.1. http://www.slack.net/~ant/
2
3 #include "Sap_Emu.h"
4
5 #include "blargg_endian.h"
6 #include <string.h>
7
8 /* Copyright (C) 2006 Shay Green. This module is free software; you
9 can redistribute it and/or modify it under the terms of the GNU Lesser
10 General Public License as published by the Free Software Foundation; either
11 version 2.1 of the License, or (at your option) any later version. This
12 module is distributed in the hope that it will be useful, but WITHOUT ANY
13 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
14 FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
15 details. You should have received a copy of the GNU Lesser General Public
16 License along with this module; if not, write to the Free Software Foundation,
17 Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
18
19 #include "blargg_source.h"
20
21 long const base_scanline_period = 114;
22
23 Sap_Emu::Sap_Emu()
24 {
25 set_type( gme_sap_type );
26
27 static const char* const names [Sap_Apu::osc_count * 2] = {
28 "Wave 1", "Wave 2", "Wave 3", "Wave 4",
29 "Wave 5", "Wave 6", "Wave 7", "Wave 8",
30 };
31 set_voice_names( names );
32
33 static int const types [Sap_Apu::osc_count * 2] = {
34 wave_type | 1, wave_type | 2, wave_type | 3, wave_type | 0,
35 wave_type | 5, wave_type | 6, wave_type | 7, wave_type | 4,
36 };
37 set_voice_types( types );
38 set_silence_lookahead( 6 );
39 }
40
41 Sap_Emu::~Sap_Emu() { }
42
43 // Track info
44
45 // Returns 16 or greater if not hex
46 inline int from_hex_char( int h )
47 {
48 h -= 0x30;
49 if ( (unsigned) h > 9 )
50 h = ((h - 0x11) & 0xDF) + 10;
51 return h;
52 }
53
54 static long from_hex( byte const* in )
55 {
56 unsigned result = 0;
57 for ( int n = 4; n--; )
58 {
59 int h = from_hex_char( *in++ );
60 if ( h > 15 )
61 return -1;
62 result = result * 0x10 + h;
63 }
64 return result;
65 }
66
67 static int from_dec( byte const* in, byte const* end )
68 {
69 if ( in >= end )
70 return -1;
71
72 int n = 0;
73 while ( in < end )
74 {
75 int dig = *in++ - '0';
76 if ( (unsigned) dig > 9 )
77 return -1;
78 n = n * 10 + dig;
79 }
80 return n;
81 }
82
83 static void parse_string( byte const* in, byte const* end, int len, char* out )
84 {
85 byte const* start = in;
86 if ( *in++ == '\"' )
87 {
88 start++;
89 while ( in < end && *in != '\"' )
90 in++;
91 }
92 else
93 {
94 in = end;
95 }
96 len = min( len - 1, int (in - start) );
97 out [len] = 0;
98 memcpy( out, start, len );
99 }
100
101 static blargg_err_t parse_info( byte const* in, long size, Sap_Emu::info_t* out )
102 {
103 out->track_count = 1;
104 out->author [0] = 0;
105 out->name [0] = 0;
106 out->copyright [0] = 0;
107
108 if ( size < 16 || memcmp( in, "SAP\x0D\x0A", 5 ) )
109 return gme_wrong_file_type;
110
111 byte const* file_end = in + size - 5;
112 in += 5;
113 while ( in < file_end && (in [0] != 0xFF || in [1] != 0xFF) )
114 {
115 byte const* line_end = in;
116 while ( line_end < file_end && *line_end != 0x0D )
117 line_end++;
118
119 char const* tag = (char const*) in;
120 while ( in < line_end && *in > ' ' )
121 in++;
122 int tag_len = (char const*) in - tag;
123
124 while ( in < line_end && *in <= ' ' ) in++;
125
126 if ( tag_len <= 0 )
127 {
128 // skip line
129 }
130 else if ( !strncmp( "INIT", tag, tag_len ) )
131 {
132 out->init_addr = from_hex( in );
133 if ( (unsigned long) out->init_addr > 0xFFFF )
134 return "Invalid init address";
135 }
136 else if ( !strncmp( "PLAYER", tag, tag_len ) )
137 {
138 out->play_addr = from_hex( in );
139 if ( (unsigned long) out->play_addr > 0xFFFF )
140 return "Invalid play address";
141 }
142 else if ( !strncmp( "MUSIC", tag, tag_len ) )
143 {
144 out->music_addr = from_hex( in );
145 if ( (unsigned long) out->music_addr > 0xFFFF )
146 return "Invalid music address";
147 }
148 else if ( !strncmp( "SONGS", tag, tag_len ) )
149 {
150 out->track_count = from_dec( in, line_end );
151 if ( out->track_count <= 0 )
152 return "Invalid track count";
153 }
154 else if ( !strncmp( "TYPE", tag, tag_len ) )
155 {
156 switch ( out->type = *in )
157 {
158 case 'C':
159 case 'B':
160 break;
161
162 case 'D':
163 return "Digimusic not supported";
164
165 default:
166 return "Unsupported player type";
167 }
168 }
169 else if ( !strncmp( "STEREO", tag, tag_len ) )
170 {
171 out->stereo = true;
172 }
173 else if ( !strncmp( "FASTPLAY", tag, tag_len ) )
174 {
175 out->fastplay = from_dec( in, line_end );
176 if ( out->fastplay <= 0 )
177 return "Invalid fastplay value";
178 }
179 else if ( !strncmp( "AUTHOR", tag, tag_len ) )
180 {
181 parse_string( in, line_end, sizeof out->author, out->author );
182 }
183 else if ( !strncmp( "NAME", tag, tag_len ) )
184 {
185 parse_string( in, line_end, sizeof out->name, out->name );
186 }
187 else if ( !strncmp( "DATE", tag, tag_len ) )
188 {
189 parse_string( in, line_end, sizeof out->copyright, out->copyright );
190 }
191
192 in = line_end + 2;
193 }
194
195 if ( in [0] != 0xFF || in [1] != 0xFF )
196 return "ROM data missing";
197 out->rom_data = in + 2;
198
199 return 0;
200 }
201
202 static void copy_sap_fields( Sap_Emu::info_t const& in, track_info_t* out )
203 {
204 Gme_File::copy_field_( out->game, in.name );
205 Gme_File::copy_field_( out->author, in.author );
206 Gme_File::copy_field_( out->copyright, in.copyright );
207 }
208
209 blargg_err_t Sap_Emu::track_info_( track_info_t* out, int ) const
210 {
211 copy_sap_fields( info, out );
212 return 0;
213 }
214
215 struct Sap_File : Gme_Info_
216 {
217 Sap_Emu::info_t info;
218
219 Sap_File() { set_type( gme_sap_type ); }
220
221 blargg_err_t load_mem_( byte const* begin, long size )
222 {
223 RETURN_ERR( parse_info( begin, size, &info ) );
224 set_track_count( info.track_count );
225 return 0;
226 }
227
228 blargg_err_t track_info_( track_info_t* out, int ) const
229 {
230 copy_sap_fields( info, out );
231 return 0;
232 }
233 };
234
235 static Music_Emu* new_sap_emu () { return BLARGG_NEW Sap_Emu ; }
236 static Music_Emu* new_sap_file() { return BLARGG_NEW Sap_File; }
237
238 gme_type_t_ const gme_sap_type [1] = { "Atari XL", 0, &new_sap_emu, &new_sap_file, "SAP", 1 };
239
240 // Setup
241
242 blargg_err_t Sap_Emu::load_mem_( byte const* in, long size )
243 {
244 file_end = in + size;
245
246 info.warning = 0;
247 info.type = 'B';
248 info.stereo = false;
249 info.init_addr = -1;
250 info.play_addr = -1;
251 info.music_addr = -1;
252 info.fastplay = 312;
253 RETURN_ERR( parse_info( in, size, &info ) );
254
255 set_warning( info.warning );
256 set_track_count( info.track_count );
257 set_voice_count( Sap_Apu::osc_count << info.stereo );
258 apu_impl.volume( gain() );
259
260 return setup_buffer( 1773447 );
261 }
262
263 void Sap_Emu::update_eq( blip_eq_t const& eq )
264 {
265 apu_impl.synth.treble_eq( eq );
266 }
267
268 void Sap_Emu::set_voice( int i, Blip_Buffer* center, Blip_Buffer* left, Blip_Buffer* right )
269 {
270 int i2 = i - Sap_Apu::osc_count;
271 if ( i2 >= 0 )
272 apu2.osc_output( i2, right );
273 else
274 apu.osc_output( i, (info.stereo ? left : center) );
275 }
276
277 // Emulation
278
279 void Sap_Emu::set_tempo_( double t )
280 {
281 scanline_period = sap_time_t (base_scanline_period / t);
282 }
283
284 inline sap_time_t Sap_Emu::play_period() const { return info.fastplay * scanline_period; }
285
286 void Sap_Emu::cpu_jsr( sap_addr_t addr )
287 {
288 check( r.sp >= 0xFE ); // catch anything trying to leave data on stack
289 r.pc = addr;
290 int high_byte = (idle_addr - 1) >> 8;
291 if ( r.sp == 0xFE && mem [0x1FF] == high_byte )
292 r.sp = 0xFF; // pop extra byte off
293 mem [0x100 + r.sp--] = high_byte; // some routines use RTI to return
294 mem [0x100 + r.sp--] = high_byte;
295 mem [0x100 + r.sp--] = idle_addr - 1;
296 }
297
298 void Sap_Emu::run_routine( sap_addr_t addr )
299 {
300 cpu_jsr( addr );
301 cpu::run( 312 * base_scanline_period * 60 );
302 check( r.pc == idle_addr );
303 }
304
305 inline void Sap_Emu::call_init( int track )
306 {
307 switch ( info.type )
308 {
309 case 'B':
310 r.a = track;
311 run_routine( info.init_addr );
312 break;
313
314 case 'C':
315 r.a = 0x70;
316 r.x = info.music_addr&0xFF;
317 r.y = info.music_addr >> 8;
318 run_routine( info.play_addr + 3 );
319 r.a = 0;
320 r.x = track;
321 run_routine( info.play_addr + 3 );
322 break;
323 }
324 }
325
326 blargg_err_t Sap_Emu::start_track_( int track )
327 {
328 RETURN_ERR( Classic_Emu::start_track_( track ) );
329
330 memset( mem, 0, sizeof mem );
331 byte const* in = info.rom_data;
332 while ( file_end - in >= 5 )
333 {
334 unsigned start = get_le16( in );
335 unsigned end = get_le16( in + 2 );
336 //dprintf( "Block $%04X-$%04X\n", start, end );
337 in += 4;
338 if ( end < start )
339 {
340 set_warning( "Invalid file data block" );
341 break;
342 }
343 long len = end - start + 1;
344 if ( len > file_end - in )
345 {
346 set_warning( "Invalid file data block" );
347 break;
348 }
349
350 memcpy( mem + start, in, len );
351 in += len;
352 if ( file_end - in >= 2 && in [0] == 0xFF && in [1] == 0xFF )
353 in += 2;
354 }
355
356 apu.reset( &apu_impl );
357 apu2.reset( &apu_impl );
358 cpu::reset( mem );
359 time_mask = 0; // disables sound during init
360 call_init( track );
361 time_mask = -1;
362
363 next_play = play_period();
364
365 return 0;
366 }
367
368 // Emulation
369
370 // see sap_cpu_io.h for read/write functions
371
372 void Sap_Emu::cpu_write_( sap_addr_t addr, int data )
373 {
374 if ( (addr ^ Sap_Apu::start_addr) <= (Sap_Apu::end_addr - Sap_Apu::start_addr) )
375 {
376 apu.write_data( time() & time_mask, addr, data );
377 return;
378 }
379
380 if ( (addr ^ (Sap_Apu::start_addr + 0x10)) <= (Sap_Apu::end_addr - Sap_Apu::start_addr) &&
381 info.stereo )
382 {
383 apu2.write_data( time() & time_mask, addr ^ 0x10, data );
384 return;
385 }
386
387 if ( (addr & ~0x0010) != 0xD20F || data != 0x03 )
388 dprintf( "Unmapped write $%04X <- $%02X\n", addr, data );
389 }
390
391 inline void Sap_Emu::call_play()
392 {
393 switch ( info.type )
394 {
395 case 'B':
396 cpu_jsr( info.play_addr );
397 break;
398
399 case 'C':
400 cpu_jsr( info.play_addr + 6 );
401 break;
402 }
403 }
404
405 blargg_err_t Sap_Emu::run_clocks( blip_time_t& duration, int )
406 {
407 set_time( 0 );
408 while ( time() < duration )
409 {
410 if ( cpu::run( duration ) || r.pc > idle_addr )
411 return "Emulation error (illegal instruction)";
412
413 if ( r.pc == idle_addr )
414 {
415 if ( next_play <= duration )
416 {
417 set_time( next_play );
418 next_play += play_period();
419 call_play();
420 }
421 else
422 {
423 set_time( duration );
424 }
425 }
426 }
427
428 duration = time();
429 next_play -= duration;
430 check( next_play >= 0 );
431 if ( next_play < 0 )
432 next_play = 0;
433 apu.end_frame( duration );
434 if ( info.stereo )
435 apu2.end_frame( duration );
436
437 return 0;
438 }