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