Mercurial > audlegacy-plugins
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 } |