1 // Game_Music_Emu 0.6.0. http://www.slack.net/~ant/
2 
3 #include "Ay_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 spectrum_clock = 3546900;
22 long const cpc_clock      = 2000000;
23 
24 unsigned const ram_start = 0x4000;
25 int const osc_count = Ay_Apu::osc_count + 1;
26 
Ay_Emu()27 Ay_Emu::Ay_Emu()
28 {
29 	beeper_output = 0;
30 	set_type( gme_ay_type );
31 
32 	static const char* const names [osc_count] = {
33 		"Wave 1", "Wave 2", "Wave 3", "Beeper"
34 	};
35 	set_voice_names( names );
36 
37 	static int const types [osc_count] = {
38 		wave_type | 0, wave_type | 1, wave_type | 2, mixed_type | 0
39 	};
40 	set_voice_types( types );
41 	set_silence_lookahead( 6 );
42 }
43 
~Ay_Emu()44 Ay_Emu::~Ay_Emu() { }
45 
46 // Track info
47 
get_data(Ay_Emu::file_t const & file,byte const * ptr,int min_size)48 static byte const* get_data( Ay_Emu::file_t const& file, byte const* ptr, int min_size )
49 {
50 	long pos = long(ptr - (byte const*) file.header);
51 	long file_size = long(file.end - (byte const*) file.header);
52 	assert( (unsigned long) pos <= (unsigned long) file_size - 2 );
53 	int offset = (BOOST::int16_t) get_be16( ptr );
54 	if ( !offset || blargg_ulong (pos + offset) > blargg_ulong (file_size - min_size) )
55 		return 0;
56 	return ptr + offset;
57 }
58 
parse_header(byte const * in,long size,Ay_Emu::file_t * out)59 static blargg_err_t parse_header( byte const* in, long size, Ay_Emu::file_t* out )
60 {
61 	typedef Ay_Emu::header_t header_t;
62 	out->header = (header_t const*) in;
63 	out->end    = in + size;
64 
65 	if ( size < Ay_Emu::header_size )
66 		return gme_wrong_file_type;
67 
68 	header_t const& h = *(header_t const*) in;
69 	if ( memcmp( h.tag, "ZXAYEMUL", 8 ) )
70 		return gme_wrong_file_type;
71 
72 	out->tracks = get_data( *out, h.track_info, (h.max_track + 1) * 4 );
73 	if ( !out->tracks )
74 		return "Missing track data";
75 
76 	return 0;
77 }
78 
copy_ay_fields(Ay_Emu::file_t const & file,track_info_t * out,int track)79 static void copy_ay_fields( Ay_Emu::file_t const& file, track_info_t* out, int track )
80 {
81 	Gme_File::copy_field_( out->song, (char const*) get_data( file, file.tracks + track * 4, 1 ) );
82 	byte const* track_info = get_data( file, file.tracks + track * 4 + 2, 6 );
83 	if ( track_info )
84 		out->length = get_be16( track_info + 4 ) * (1000L / 50); // frames to msec
85 
86 	Gme_File::copy_field_( out->author,  (char const*) get_data( file, file.header->author, 1 ) );
87 	Gme_File::copy_field_( out->comment, (char const*) get_data( file, file.header->comment, 1 ) );
88 }
89 
track_info_(track_info_t * out,int track) const90 blargg_err_t Ay_Emu::track_info_( track_info_t* out, int track ) const
91 {
92 	copy_ay_fields( file, out, track );
93 	return 0;
94 }
95 
96 struct Ay_File : Gme_Info_
97 {
98 	Ay_Emu::file_t file;
99 
Ay_FileAy_File100 	Ay_File() { set_type( gme_ay_type ); }
101 
load_mem_Ay_File102 	blargg_err_t load_mem_( byte const* begin, long size )
103 	{
104 		RETURN_ERR( parse_header( begin, size, &file ) );
105 		set_track_count( file.header->max_track + 1 );
106 		return 0;
107 	}
108 
track_info_Ay_File109 	blargg_err_t track_info_( track_info_t* out, int track ) const
110 	{
111 		copy_ay_fields( file, out, track );
112 		return 0;
113 	}
114 };
115 
new_ay_emu()116 static Music_Emu* new_ay_emu () { return BLARGG_NEW Ay_Emu ; }
new_ay_file()117 static Music_Emu* new_ay_file() { return BLARGG_NEW Ay_File; }
118 
119 static gme_type_t_ const gme_ay_type_ = { "ZX Spectrum", 0, &new_ay_emu, &new_ay_file, "AY", 1 };
120 gme_type_t const gme_ay_type = &gme_ay_type_;
121 
122 // Setup
123 
load_mem_(byte const * in,long size)124 blargg_err_t Ay_Emu::load_mem_( byte const* in, long size )
125 {
126 	assert( offsetof (header_t,track_info [2]) == header_size );
127 
128 	RETURN_ERR( parse_header( in, size, &file ) );
129 	set_track_count( file.header->max_track + 1 );
130 
131 	if ( file.header->vers > 2 )
132 		set_warning( "Unknown file version" );
133 
134 	set_voice_count( osc_count );
135 	apu.volume( gain() );
136 
137 	return setup_buffer( spectrum_clock );
138 }
139 
update_eq(blip_eq_t const & eq)140 void Ay_Emu::update_eq( blip_eq_t const& eq )
141 {
142 	apu.treble_eq( eq );
143 }
144 
set_voice(int i,Blip_Buffer * center,Blip_Buffer *,Blip_Buffer *)145 void Ay_Emu::set_voice( int i, Blip_Buffer* center, Blip_Buffer*, Blip_Buffer* )
146 {
147 	if ( i >= Ay_Apu::osc_count )
148 		beeper_output = center;
149 	else
150 		apu.osc_output( i, center );
151 }
152 
153 // Emulation
154 
set_tempo_(double t)155 void Ay_Emu::set_tempo_( double t )
156 {
157 	play_period = blip_time_t (clock_rate() / 50 / t);
158 }
159 
start_track_(int track)160 blargg_err_t Ay_Emu::start_track_( int track )
161 {
162 	RETURN_ERR( Classic_Emu::start_track_( track ) );
163 
164 	memset( mem.ram + 0x0000, 0xC9, 0x100 ); // fill RST vectors with RET
165 	memset( mem.ram + 0x0100, 0xFF, 0x4000 - 0x100 );
166 	memset( mem.ram + ram_start, 0x00, sizeof mem.ram - ram_start );
167 	memset( mem.padding1, 0xFF, sizeof mem.padding1 );
168 	memset( mem.ram + 0x10000, 0xFF, sizeof mem.ram - 0x10000 );
169 
170 	// locate data blocks
171 	byte const* const data = get_data( file, file.tracks + track * 4 + 2, 14 );
172 	if ( !data ) return "File data missing";
173 
174 	byte const* const more_data = get_data( file, data + 10, 6 );
175 	if ( !more_data ) return "File data missing";
176 
177 	byte const* blocks = get_data( file, data + 12, 8 );
178 	if ( !blocks ) return "File data missing";
179 
180 	// initial addresses
181 	cpu::reset( mem.ram );
182 	r.sp = get_be16( more_data );
183 	r.b.a = r.b.b = r.b.d = r.b.h = data [8];
184 	r.b.flags = r.b.c = r.b.e = r.b.l = data [9];
185 	r.alt.w = r.w;
186 	r.ix = r.iy = r.w.hl;
187 
188 	unsigned addr = get_be16( blocks );
189 	if ( !addr ) return "File data missing";
190 
191 	unsigned init = get_be16( more_data + 2 );
192 	if ( !init )
193 		init = addr;
194 
195 	// copy blocks into memory
196 	do
197 	{
198 		blocks += 2;
199 		unsigned len = get_be16( blocks ); blocks += 2;
200 		if ( addr + len > 0x10000 )
201 		{
202 			set_warning( "Bad data block size" );
203 			len = 0x10000 - addr;
204 		}
205 		check( len );
206 		byte const* in = get_data( file, blocks, 0 ); blocks += 2;
207 		if ( len > blargg_ulong (file.end - in) )
208 		{
209 			set_warning( "Missing file data" );
210 			len = unsigned(file.end - in);
211 		}
212 		//debug_printf( "addr: $%04X, len: $%04X\n", addr, len );
213 		if ( addr < ram_start && addr >= 0x400 ) // several tracks use low data
214 			debug_printf( "Block addr in ROM\n" );
215 		memcpy( mem.ram + addr, in, len );
216 
217 		if ( file.end - blocks < 8 )
218 		{
219 			set_warning( "Missing file data" );
220 			break;
221 		}
222 	}
223 	while ( (addr = get_be16( blocks )) != 0 );
224 
225 	// copy and configure driver
226 	static byte const passive [] = {
227 		0xF3,       // DI
228 		0xCD, 0, 0, // CALL init
229 		0xED, 0x5E, // LOOP: IM 2
230 		0xFB,       // EI
231 		0x76,       // HALT
232 		0x18, 0xFA  // JR LOOP
233 	};
234 	static byte const active [] = {
235 		0xF3,       // DI
236 		0xCD, 0, 0, // CALL init
237 		0xED, 0x56, // LOOP: IM 1
238 		0xFB,       // EI
239 		0x76,       // HALT
240 		0xCD, 0, 0, // CALL play
241 		0x18, 0xF7  // JR LOOP
242 	};
243 	memcpy( mem.ram, passive, sizeof passive );
244 	unsigned play_addr = get_be16( more_data + 4 );
245 	//debug_printf( "Play: $%04X\n", play_addr );
246 	if ( play_addr )
247 	{
248 		memcpy( mem.ram, active, sizeof active );
249 		mem.ram [ 9] = play_addr;
250 		mem.ram [10] = play_addr >> 8;
251 	}
252 	mem.ram [2] = init;
253 	mem.ram [3] = init >> 8;
254 
255 	mem.ram [0x38] = 0xFB; // Put EI at interrupt vector (followed by RET)
256 
257 	memcpy( mem.ram + 0x10000, mem.ram, 0x80 ); // some code wraps around (ugh)
258 
259 	beeper_delta = int (apu.amp_range * 0.65);
260 	last_beeper = 0;
261 	apu.reset();
262 	next_play = play_period;
263 
264 	// start at spectrum speed
265 	change_clock_rate( spectrum_clock );
266 	set_tempo( tempo() );
267 
268 	spectrum_mode = false;
269 	cpc_mode      = false;
270 	cpc_latch     = 0;
271 
272 	return 0;
273 }
274 
275 // Emulation
276 
cpu_out_misc(cpu_time_t time,unsigned addr,int data)277 void Ay_Emu::cpu_out_misc( cpu_time_t time, unsigned addr, int data )
278 {
279 	if ( !cpc_mode )
280 	{
281 		switch ( addr & 0xFEFF )
282 		{
283 		case 0xFEFD:
284 			spectrum_mode = true;
285 			apu_addr = data & 0x0F;
286 			return;
287 
288 		case 0xBEFD:
289 			spectrum_mode = true;
290 			apu.write( time, apu_addr, data );
291 			return;
292 		}
293 	}
294 
295 	if ( !spectrum_mode )
296 	{
297 		switch ( addr >> 8 )
298 		{
299 		case 0xF6:
300 			switch ( data & 0xC0 )
301 			{
302 			case 0xC0:
303 				apu_addr = cpc_latch & 0x0F;
304 				goto enable_cpc;
305 
306 			case 0x80:
307 				apu.write( time, apu_addr, cpc_latch );
308 				goto enable_cpc;
309 			}
310 			break;
311 
312 		case 0xF4:
313 			cpc_latch = data;
314 			goto enable_cpc;
315 		}
316 	}
317 
318 	debug_printf( "Unmapped OUT: $%04X <- $%02X\n", addr, data );
319 	return;
320 
321 enable_cpc:
322 	if ( !cpc_mode )
323 	{
324 		cpc_mode = true;
325 		change_clock_rate( cpc_clock );
326 		set_tempo( tempo() );
327 	}
328 }
329 
ay_cpu_out(Ay_Cpu * cpu,cpu_time_t time,unsigned addr,int data)330 void ay_cpu_out( Ay_Cpu* cpu, cpu_time_t time, unsigned addr, int data )
331 {
332 	Ay_Emu& emu = STATIC_CAST(Ay_Emu&,*cpu);
333 
334 	if ( (addr & 0xFF) == 0xFE && !emu.cpc_mode )
335 	{
336 		int delta = emu.beeper_delta;
337 		data &= 0x10;
338 		if ( emu.last_beeper != data )
339 		{
340 			emu.last_beeper = data;
341 			emu.beeper_delta = -delta;
342 			emu.spectrum_mode = true;
343 			if ( emu.beeper_output )
344 				emu.apu.synth_.offset( time, delta, emu.beeper_output );
345 		}
346 	}
347 	else
348 	{
349 		emu.cpu_out_misc( time, addr, data );
350 	}
351 }
352 
ay_cpu_in(Ay_Cpu *,unsigned addr)353 int ay_cpu_in( Ay_Cpu*, unsigned addr )
354 {
355 	// keyboard read and other things
356 	if ( (addr & 0xFF) == 0xFE )
357 		return 0xFF; // other values break some beeper tunes
358 
359 	debug_printf( "Unmapped IN : $%04X\n", addr );
360 	return 0xFF;
361 }
362 
run_clocks(blip_time_t & duration,int)363 blargg_err_t Ay_Emu::run_clocks( blip_time_t& duration, int )
364 {
365 	set_time( 0 );
366 	if ( !(spectrum_mode | cpc_mode) )
367 		duration /= 2; // until mode is set, leave room for halved clock rate
368 
369 	while ( time() < duration )
370 	{
371 		cpu::run( min( duration, (blip_time_t) next_play ) );
372 
373 		if ( time() >= next_play )
374 		{
375 			next_play += play_period;
376 
377 			if ( r.iff1 )
378 			{
379 				if ( mem.ram [r.pc] == 0x76 )
380 					r.pc++;
381 
382 				r.iff1 = r.iff2 = 0;
383 
384 				mem.ram [--r.sp] = uint8_t (r.pc >> 8);
385 				mem.ram [--r.sp] = uint8_t (r.pc);
386 				r.pc = 0x38;
387 				cpu::adjust_time( 12 );
388 				if ( r.im == 2 )
389 				{
390 					cpu::adjust_time( 6 );
391 					unsigned addr = r.i * 0x100u + 0xFF;
392 					r.pc = mem.ram [(addr + 1) & 0xFFFF] * 0x100u + mem.ram [addr];
393 				}
394 			}
395 		}
396 	}
397 	duration = time();
398 	next_play -= duration;
399 	check( next_play >= 0 );
400 	adjust_time( -duration );
401 
402 	apu.end_frame( duration );
403 
404 	return 0;
405 }
406