1 // Game_Music_Emu 0.6.0. 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
Sap_Emu()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
~Sap_Emu()41 Sap_Emu::~Sap_Emu() { }
42
43 // Track info
44
45 // Returns 16 or greater if not hex
from_hex_char(int h)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
from_hex(byte const * in)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
from_dec(byte const * in,byte const * end)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
parse_string(byte const * in,byte const * end,int len,char * out)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
parse_info(byte const * in,long size,Sap_Emu::info_t * out)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
copy_sap_fields(Sap_Emu::info_t const & in,track_info_t * out)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
track_info_(track_info_t * out,int) const209 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
Sap_FileSap_File219 Sap_File() { set_type( gme_sap_type ); }
220
load_mem_Sap_File221 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
track_info_Sap_File228 blargg_err_t track_info_( track_info_t* out, int ) const
229 {
230 copy_sap_fields( info, out );
231 return 0;
232 }
233 };
234
new_sap_emu()235 static Music_Emu* new_sap_emu () { return BLARGG_NEW Sap_Emu ; }
new_sap_file()236 static Music_Emu* new_sap_file() { return BLARGG_NEW Sap_File; }
237
238 static gme_type_t_ const gme_sap_type_ = { "Atari XL", 0, &new_sap_emu, &new_sap_file, "SAP", 1 };
239 gme_type_t const gme_sap_type = &gme_sap_type_;
240
241
242 // Setup
243
load_mem_(byte const * in,long size)244 blargg_err_t Sap_Emu::load_mem_( byte const* in, long size )
245 {
246 file_end = in + size;
247
248 info.warning = 0;
249 info.type = 'B';
250 info.stereo = false;
251 info.init_addr = -1;
252 info.play_addr = -1;
253 info.music_addr = -1;
254 info.fastplay = 312;
255 RETURN_ERR( parse_info( in, size, &info ) );
256
257 set_warning( info.warning );
258 set_track_count( info.track_count );
259 set_voice_count( Sap_Apu::osc_count << info.stereo );
260 apu_impl.volume( gain() );
261
262 return setup_buffer( 1773447 );
263 }
264
update_eq(blip_eq_t const & eq)265 void Sap_Emu::update_eq( blip_eq_t const& eq )
266 {
267 apu_impl.synth.treble_eq( eq );
268 }
269
set_voice(int i,Blip_Buffer * center,Blip_Buffer * left,Blip_Buffer * right)270 void Sap_Emu::set_voice( int i, Blip_Buffer* center, Blip_Buffer* left, Blip_Buffer* right )
271 {
272 int i2 = i - Sap_Apu::osc_count;
273 if ( i2 >= 0 )
274 apu2.osc_output( i2, right );
275 else
276 apu.osc_output( i, (info.stereo ? left : center) );
277 }
278
279 // Emulation
280
set_tempo_(double t)281 void Sap_Emu::set_tempo_( double t )
282 {
283 scanline_period = sap_time_t (base_scanline_period / t);
284 }
285
play_period() const286 inline sap_time_t Sap_Emu::play_period() const { return info.fastplay * scanline_period; }
287
cpu_jsr(sap_addr_t addr)288 void Sap_Emu::cpu_jsr( sap_addr_t addr )
289 {
290 check( r.sp >= 0xFE ); // catch anything trying to leave data on stack
291 r.pc = addr;
292 int high_byte = (idle_addr - 1) >> 8;
293 if ( r.sp == 0xFE && mem.ram [0x1FF] == high_byte )
294 r.sp = 0xFF; // pop extra byte off
295 mem.ram [0x100 + r.sp--] = high_byte; // some routines use RTI to return
296 mem.ram [0x100 + r.sp--] = high_byte;
297 mem.ram [0x100 + r.sp--] = (idle_addr - 1) & 0xFF;
298 }
299
run_routine(sap_addr_t addr)300 void Sap_Emu::run_routine( sap_addr_t addr )
301 {
302 cpu_jsr( addr );
303 cpu::run( 312 * base_scanline_period * 60 );
304 check( r.pc == idle_addr );
305 }
306
call_init(int track)307 inline void Sap_Emu::call_init( int track )
308 {
309 switch ( info.type )
310 {
311 case 'B':
312 r.a = track;
313 run_routine( info.init_addr );
314 break;
315
316 case 'C':
317 r.a = 0x70;
318 r.x = info.music_addr&0xFF;
319 r.y = info.music_addr >> 8;
320 run_routine( info.play_addr + 3 );
321 r.a = 0;
322 r.x = track;
323 run_routine( info.play_addr + 3 );
324 break;
325 }
326 }
327
start_track_(int track)328 blargg_err_t Sap_Emu::start_track_( int track )
329 {
330 RETURN_ERR( Classic_Emu::start_track_( track ) );
331
332 memset( &mem, 0, sizeof mem );
333
334 byte const* in = info.rom_data;
335 while ( file_end - in >= 5 )
336 {
337 unsigned start = get_le16( in );
338 unsigned end = get_le16( in + 2 );
339 //debug_printf( "Block $%04X-$%04X\n", start, end );
340 in += 4;
341 if ( end < start )
342 {
343 set_warning( "Invalid file data block" );
344 break;
345 }
346 long len = end - start + 1;
347 if ( len > file_end - in )
348 {
349 set_warning( "Invalid file data block" );
350 break;
351 }
352
353 memcpy( mem.ram + start, in, len );
354 in += len;
355 if ( file_end - in >= 2 && in [0] == 0xFF && in [1] == 0xFF )
356 in += 2;
357 }
358
359 apu.reset( &apu_impl );
360 apu2.reset( &apu_impl );
361 cpu::reset( mem.ram );
362 time_mask = 0; // disables sound during init
363 call_init( track );
364 time_mask = -1;
365
366 next_play = play_period();
367
368 return 0;
369 }
370
371 // Emulation
372
373 // see sap_cpu_io.h for read/write functions
374
cpu_write_(sap_addr_t addr,int data)375 void Sap_Emu::cpu_write_( sap_addr_t addr, int data )
376 {
377 if ( (addr ^ Sap_Apu::start_addr) <= (Sap_Apu::end_addr - Sap_Apu::start_addr) )
378 {
379 GME_APU_HOOK( this, addr - Sap_Apu::start_addr, data );
380 apu.write_data( time() & time_mask, addr, data );
381 return;
382 }
383
384 if ( (addr ^ (Sap_Apu::start_addr + 0x10)) <= (Sap_Apu::end_addr - Sap_Apu::start_addr) &&
385 info.stereo )
386 {
387 GME_APU_HOOK( this, addr - 0x10 - Sap_Apu::start_addr + 10, data );
388 apu2.write_data( time() & time_mask, addr ^ 0x10, data );
389 return;
390 }
391
392 if ( (addr & ~0x0010) != 0xD20F || data != 0x03 )
393 debug_printf( "Unmapped write $%04X <- $%02X\n", addr, data );
394 }
395
call_play()396 inline void Sap_Emu::call_play()
397 {
398 switch ( info.type )
399 {
400 case 'B':
401 cpu_jsr( info.play_addr );
402 break;
403
404 case 'C':
405 cpu_jsr( info.play_addr + 6 );
406 break;
407 }
408 }
409
run_clocks(blip_time_t & duration,int)410 blargg_err_t Sap_Emu::run_clocks( blip_time_t& duration, int )
411 {
412 set_time( 0 );
413 while ( time() < duration )
414 {
415 if ( cpu::run( duration ) || r.pc > idle_addr )
416 return "Emulation error (illegal instruction)";
417
418 if ( r.pc == idle_addr )
419 {
420 if ( next_play <= duration )
421 {
422 set_time( next_play );
423 next_play += play_period();
424 call_play();
425 GME_FRAME_HOOK( this );
426 }
427 else
428 {
429 set_time( duration );
430 }
431 }
432 }
433
434 duration = time();
435 next_play -= duration;
436 check( next_play >= 0 );
437 if ( next_play < 0 )
438 next_play = 0;
439 apu.end_frame( duration );
440 if ( info.stereo )
441 apu2.end_frame( duration );
442
443 return 0;
444 }
445