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