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