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 = ptr - (byte const*) file.header;
51 long file_size = 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 = 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