/src/vlc/contrib/contrib-build/game-music-emu/gme/Hes_Emu.cpp
Line | Count | Source |
1 | | // Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/ |
2 | | |
3 | | #include "Hes_Emu.h" |
4 | | |
5 | | #include "blargg_endian.h" |
6 | | #include <string.h> |
7 | | #include <algorithm> |
8 | | |
9 | | /* Copyright (C) 2006 Shay Green. This module is free software; you |
10 | | can redistribute it and/or modify it under the terms of the GNU Lesser |
11 | | General Public License as published by the Free Software Foundation; either |
12 | | version 2.1 of the License, or (at your option) any later version. This |
13 | | module is distributed in the hope that it will be useful, but WITHOUT ANY |
14 | | WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
15 | | FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more |
16 | | details. You should have received a copy of the GNU Lesser General Public |
17 | | License along with this module; if not, write to the Free Software Foundation, |
18 | | Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ |
19 | | |
20 | | #include "blargg_source.h" |
21 | | |
22 | | static int const timer_mask = 0x04; |
23 | | static int const vdp_mask = 0x02; |
24 | | static int const i_flag_mask = 0x04; |
25 | | static int const unmapped = 0xFF; |
26 | | |
27 | | static long const period_60hz = 262 * 455L; // scanlines * clocks per scanline |
28 | | |
29 | | using std::min; |
30 | | using std::max; |
31 | | |
32 | | Hes_Emu::Hes_Emu() |
33 | 2 | { |
34 | 2 | timer.raw_load = 0; |
35 | 2 | set_type( gme_hes_type ); |
36 | | |
37 | 2 | static const char* const names [Hes_Apu::osc_count] = { |
38 | 2 | "Wave 1", "Wave 2", "Wave 3", "Wave 4", "Multi 1", "Multi 2" |
39 | 2 | }; |
40 | 2 | set_voice_names( names ); |
41 | | |
42 | 2 | static int const types [Hes_Apu::osc_count] = { |
43 | 2 | wave_type | 0, wave_type | 1, wave_type | 2, wave_type | 3, |
44 | 2 | mixed_type | 0, mixed_type | 1 |
45 | 2 | }; |
46 | 2 | set_voice_types( types ); |
47 | 2 | set_silence_lookahead( 6 ); |
48 | 2 | set_gain( 1.11 ); |
49 | 2 | } |
50 | | |
51 | 2 | Hes_Emu::~Hes_Emu() { } |
52 | | |
53 | | void Hes_Emu::unload() |
54 | 3 | { |
55 | 3 | rom.clear(); |
56 | 3 | Music_Emu::unload(); |
57 | 3 | } |
58 | | |
59 | | // Track info |
60 | | |
61 | | static byte const* copy_field( byte const* in, char* out ) |
62 | 0 | { |
63 | 0 | if ( in ) |
64 | 0 | { |
65 | 0 | int len = 0x20; |
66 | 0 | if ( in [0x1F] && !in [0x2F] ) |
67 | 0 | len = 0x30; // fields are sometimes 16 bytes longer (ugh) |
68 | | |
69 | | // since text fields are where any data could be, detect non-text |
70 | | // and fields with data after zero byte terminator |
71 | |
|
72 | 0 | int i = 0; |
73 | 0 | for ( i = 0; i < len && in [i]; i++ ) |
74 | 0 | if ( ((in [i] + 1) & 0xFF) < ' ' + 1 ) // also treat 0xFF as non-text |
75 | 0 | return 0; // non-ASCII found |
76 | | |
77 | 0 | for ( ; i < len; i++ ) |
78 | 0 | if ( in [i] ) |
79 | 0 | return 0; // data after terminator |
80 | | |
81 | 0 | Gme_File::copy_field_( out, (char const*) in, len ); |
82 | 0 | in += len; |
83 | 0 | } |
84 | 0 | return in; |
85 | 0 | } |
86 | | |
87 | | static void copy_hes_fields( byte const* in, track_info_t* out ) |
88 | 256 | { |
89 | 256 | if ( *in >= ' ' ) |
90 | 0 | { |
91 | 0 | in = copy_field( in, out->game ); |
92 | 0 | in = copy_field( in, out->author ); |
93 | 0 | in = copy_field( in, out->copyright ); |
94 | 0 | } |
95 | 256 | } |
96 | | |
97 | | blargg_err_t Hes_Emu::track_info_( track_info_t* out, int ) const |
98 | 256 | { |
99 | 256 | copy_hes_fields( rom.begin() + 0x20, out ); |
100 | 256 | return 0; |
101 | 256 | } |
102 | | |
103 | | static blargg_err_t check_hes_header( void const* header ) |
104 | 1 | { |
105 | 1 | if ( memcmp( header, "HESM", 4 ) ) |
106 | 0 | return gme_wrong_file_type; |
107 | 1 | return 0; |
108 | 1 | } |
109 | | |
110 | | struct Hes_File : Gme_Info_ |
111 | | { |
112 | | struct header_t { |
113 | | char header [Hes_Emu::header_size]; |
114 | | char unused [0x20]; |
115 | | byte fields [0x30 * 3]; |
116 | | } h; |
117 | | |
118 | 0 | Hes_File() { set_type( gme_hes_type ); } |
119 | | |
120 | | blargg_err_t load_( Data_Reader& in ) |
121 | 0 | { |
122 | 0 | blaarg_static_assert( offsetof (header_t,fields) == Hes_Emu::header_size + 0x20, "HES header layout is incorrect!" ); |
123 | 0 | blargg_err_t err = in.read( &h, sizeof h ); |
124 | 0 | if ( err ) |
125 | 0 | return (err == in.eof_error ? gme_wrong_file_type : err); |
126 | 0 | return check_hes_header( &h ); |
127 | 0 | } |
128 | | |
129 | | blargg_err_t track_info_( track_info_t* out, int ) const |
130 | 0 | { |
131 | 0 | copy_hes_fields( h.fields, out ); |
132 | 0 | return 0; |
133 | 0 | } |
134 | | }; |
135 | | |
136 | 2 | static Music_Emu* new_hes_emu () { return BLARGG_NEW Hes_Emu ; } |
137 | 0 | static Music_Emu* new_hes_file() { return BLARGG_NEW Hes_File; } |
138 | | |
139 | | static gme_type_t_ const gme_hes_type_ = { "PC Engine", 256, &new_hes_emu, &new_hes_file, "HES", 1 }; |
140 | | extern gme_type_t const gme_hes_type = &gme_hes_type_; |
141 | | |
142 | | |
143 | | // Setup |
144 | | |
145 | | blargg_err_t Hes_Emu::load_( Data_Reader& in ) |
146 | 2 | { |
147 | 2 | blaarg_static_assert( offsetof (header_t,unused [4]) == header_size, "HES header layout is incorrect!" ); |
148 | 2 | RETURN_ERR( rom.load( in, header_size, &header_, unmapped ) ); |
149 | | |
150 | 1 | RETURN_ERR( check_hes_header( header_.tag ) ); |
151 | | |
152 | 1 | if ( header_.vers != 0 ) |
153 | 1 | set_warning( "Unknown file version" ); |
154 | | |
155 | 1 | if ( memcmp( header_.data_tag, "DATA", 4 ) ) |
156 | 1 | set_warning( "Data header missing" ); |
157 | | |
158 | 1 | if ( memcmp( header_.unused, "\0\0\0\0", 4 ) ) |
159 | 1 | set_warning( "Unknown header data" ); |
160 | | |
161 | | // File spec supports multiple blocks, but I haven't found any, and |
162 | | // many files have bad sizes in the only block, so it's simpler to |
163 | | // just try to load the damn data as best as possible. |
164 | | |
165 | 1 | long addr = get_le32( header_.addr ); |
166 | 1 | long size = get_le32( header_.size ); |
167 | 1 | long const rom_max = 0x100000; |
168 | 1 | if ( addr & ~(rom_max - 1) ) |
169 | 1 | { |
170 | 1 | set_warning( "Invalid address" ); |
171 | 1 | addr &= rom_max - 1; |
172 | 1 | } |
173 | 1 | if ( (unsigned long) (addr + size) > (unsigned long) rom_max ) |
174 | 1 | set_warning( "Invalid size" ); |
175 | | |
176 | 1 | if ( size != rom.file_size() ) |
177 | 1 | { |
178 | 1 | if ( size <= rom.file_size() - 4 && !memcmp( rom.begin() + size, "DATA", 4 ) ) |
179 | 0 | set_warning( "Multiple DATA not supported" ); |
180 | 1 | else if ( size < rom.file_size() ) |
181 | 0 | set_warning( "Extra file data" ); |
182 | 1 | else |
183 | 1 | set_warning( "Missing file data" ); |
184 | 1 | } |
185 | | |
186 | 1 | rom.set_addr( addr ); |
187 | | |
188 | 1 | set_voice_count( apu.osc_count ); |
189 | | |
190 | 1 | apu.volume( gain() ); |
191 | | |
192 | 1 | return setup_buffer( 7159091 ); |
193 | 1 | } |
194 | | |
195 | | void Hes_Emu::update_eq( blip_eq_t const& eq ) |
196 | 1 | { |
197 | 1 | apu.treble_eq( eq ); |
198 | 1 | } |
199 | | |
200 | | void Hes_Emu::set_voice( int i, Blip_Buffer* center, Blip_Buffer* left, Blip_Buffer* right ) |
201 | 6 | { |
202 | 6 | apu.osc_output( i, center, left, right ); |
203 | 6 | } |
204 | | |
205 | | // Emulation |
206 | | |
207 | | void Hes_Emu::recalc_timer_load() |
208 | 257 | { |
209 | 257 | timer.load = timer.raw_load * timer_base + 1; |
210 | 257 | } |
211 | | |
212 | | void Hes_Emu::set_tempo_( double t ) |
213 | 1 | { |
214 | 1 | play_period = hes_time_t (period_60hz / t); |
215 | 1 | timer_base = int (1024 / t); |
216 | 1 | recalc_timer_load(); |
217 | 1 | } |
218 | | |
219 | | blargg_err_t Hes_Emu::start_track_( int track ) |
220 | 256 | { |
221 | 256 | RETURN_ERR( Classic_Emu::start_track_( track ) ); |
222 | | |
223 | 256 | memset( ram, 0, sizeof ram ); // some HES music relies on zero fill |
224 | 256 | memset( sgx, 0, sizeof sgx ); |
225 | | |
226 | 256 | apu.reset(); |
227 | 256 | adpcm.reset(); |
228 | 256 | cpu::reset(); |
229 | | |
230 | 2.30k | for ( unsigned i = 0; i < sizeof header_.banks; i++ ) |
231 | 2.04k | set_mmr( i, header_.banks [i] ); |
232 | 256 | set_mmr( page_count, 0xFF ); // unmapped beyond end of address space |
233 | | |
234 | 256 | irq.disables = timer_mask | vdp_mask; |
235 | 256 | irq.timer = future_hes_time; |
236 | 256 | irq.vdp = future_hes_time; |
237 | | |
238 | 256 | timer.enabled = false; |
239 | 256 | timer.raw_load= 0x80; |
240 | 256 | timer.count = timer.load; |
241 | 256 | timer.fired = false; |
242 | 256 | timer.last_time = 0; |
243 | | |
244 | 256 | vdp.latch = 0; |
245 | 256 | vdp.control = 0; |
246 | 256 | vdp.next_vbl = 0; |
247 | | |
248 | 256 | ram [0x1FF] = (idle_addr - 1) >> 8; |
249 | 256 | ram [0x1FE] = (idle_addr - 1) & 0xFF; |
250 | 256 | r.sp = 0xFD; |
251 | 256 | r.pc = get_le16( header_.init_addr ); |
252 | 256 | r.a = track; |
253 | | |
254 | 256 | recalc_timer_load(); |
255 | 256 | last_frame_hook = 0; |
256 | | |
257 | 256 | return 0; |
258 | 256 | } |
259 | | |
260 | | // Hardware |
261 | | |
262 | | void Hes_Emu::cpu_write_vdp( int addr, int data ) |
263 | 0 | { |
264 | 0 | switch ( addr ) |
265 | 0 | { |
266 | 0 | case 0: |
267 | 0 | vdp.latch = data & 0x1F; |
268 | 0 | break; |
269 | | |
270 | 0 | case 2: |
271 | 0 | if ( vdp.latch == 5 ) |
272 | 0 | { |
273 | 0 | if ( data & 0x04 ) |
274 | 0 | set_warning( "Scanline interrupt unsupported" ); |
275 | 0 | run_until( time() ); |
276 | 0 | vdp.control = data; |
277 | 0 | irq_changed(); |
278 | 0 | } |
279 | 0 | else |
280 | 0 | { |
281 | 0 | debug_printf( "VDP not supported: $%02X <- $%02X\n", vdp.latch, data ); |
282 | 0 | } |
283 | 0 | break; |
284 | | |
285 | 0 | case 3: |
286 | 0 | debug_printf( "VDP MSB not supported: $%02X <- $%02X\n", vdp.latch, data ); |
287 | 0 | break; |
288 | 0 | } |
289 | 0 | } |
290 | | |
291 | | void Hes_Emu::cpu_write_( hes_addr_t addr, int data ) |
292 | 0 | { |
293 | 0 | if ( unsigned (addr - apu.start_addr) <= apu.end_addr - apu.start_addr ) |
294 | 0 | { |
295 | 0 | GME_APU_HOOK( this, addr - apu.start_addr, data ); |
296 | | // avoid going way past end when a long block xfer is writing to I/O space |
297 | 0 | hes_time_t t = min( time(), end_time() + 8 ); |
298 | 0 | apu.write_data( t, addr, data ); |
299 | 0 | return; |
300 | 0 | } |
301 | | |
302 | 0 | if ( (unsigned) (addr - adpcm.io_addr) < adpcm.io_size ) |
303 | 0 | { |
304 | 0 | time_t t = min( time(), end_time() + 6 ); |
305 | 0 | adpcm.write_data( t, addr, data ); |
306 | 0 | return; |
307 | 0 | } |
308 | | |
309 | 0 | hes_time_t time = this->time(); |
310 | 0 | switch ( addr ) |
311 | 0 | { |
312 | 0 | case 0x0000: |
313 | 0 | case 0x0002: |
314 | 0 | case 0x0003: |
315 | 0 | cpu_write_vdp( addr, data ); |
316 | 0 | return; |
317 | | |
318 | 0 | case 0x0C00: { |
319 | 0 | run_until( time ); |
320 | 0 | timer.raw_load = (data & 0x7F) + 1; |
321 | 0 | recalc_timer_load(); |
322 | 0 | timer.count = timer.load; |
323 | 0 | break; |
324 | 0 | } |
325 | | |
326 | 0 | case 0x0C01: |
327 | 0 | data &= 1; |
328 | 0 | if ( timer.enabled == data ) |
329 | 0 | return; |
330 | 0 | run_until( time ); |
331 | 0 | timer.enabled = data; |
332 | 0 | if ( data ) |
333 | 0 | timer.count = timer.load; |
334 | 0 | break; |
335 | | |
336 | 0 | case 0x1402: |
337 | 0 | run_until( time ); |
338 | 0 | irq.disables = data; |
339 | 0 | if ( (data & 0xF8) && (data & 0xF8) != 0xF8 ) // flag questionable values |
340 | 0 | debug_printf( "Int mask: $%02X\n", data ); |
341 | 0 | break; |
342 | | |
343 | 0 | case 0x1403: |
344 | 0 | run_until( time ); |
345 | 0 | if ( timer.enabled ) |
346 | 0 | timer.count = timer.load; |
347 | 0 | timer.fired = false; |
348 | 0 | break; |
349 | |
|
350 | | #ifndef NDEBUG |
351 | | case 0x1000: // I/O port |
352 | | case 0x0402: // palette |
353 | | case 0x0403: |
354 | | case 0x0404: |
355 | | case 0x0405: |
356 | | return; |
357 | | |
358 | | default: |
359 | | debug_printf( "unmapped write $%04X <- $%02X\n", addr, data ); |
360 | | return; |
361 | | #endif |
362 | 0 | } |
363 | | |
364 | 0 | irq_changed(); |
365 | 0 | } |
366 | | |
367 | | int Hes_Emu::cpu_read_( hes_addr_t addr ) |
368 | 0 | { |
369 | 0 | hes_time_t time = this->time(); |
370 | 0 | addr &= page_size - 1; |
371 | 0 | switch ( addr ) |
372 | 0 | { |
373 | 0 | case 0x0000: |
374 | 0 | if ( irq.vdp > time ) |
375 | 0 | return 0; |
376 | 0 | irq.vdp = future_hes_time; |
377 | 0 | run_until( time ); |
378 | 0 | irq_changed(); |
379 | 0 | return 0x20; |
380 | | |
381 | 0 | case 0x0002: |
382 | 0 | case 0x0003: |
383 | 0 | debug_printf( "VDP read not supported: %d\n", addr ); |
384 | 0 | return 0; |
385 | | |
386 | 0 | case 0x0C01: |
387 | | //return timer.enabled; // TODO: remove? |
388 | 0 | case 0x0C00: |
389 | 0 | run_until( time ); |
390 | 0 | debug_printf( "Timer count read\n" ); |
391 | 0 | return (unsigned) (timer.count - 1) / timer_base; |
392 | | |
393 | 0 | case 0x1402: |
394 | 0 | return irq.disables; |
395 | | |
396 | 0 | case 0x1403: |
397 | 0 | { |
398 | 0 | int status = 0; |
399 | 0 | if ( irq.timer <= time ) status |= timer_mask; |
400 | 0 | if ( irq.vdp <= time ) status |= vdp_mask; |
401 | 0 | return status; |
402 | 0 | } |
403 | | |
404 | 0 | case 0x180A: |
405 | 0 | case 0x180B: |
406 | 0 | case 0x180C: |
407 | 0 | case 0x180D: |
408 | 0 | return adpcm.read_data( time, addr ); |
409 | |
|
410 | | #ifndef NDEBUG |
411 | | case 0x1000: // I/O port |
412 | | //case 0x180C: // CD-ROM |
413 | | //case 0x180D: |
414 | | break; |
415 | | |
416 | | default: |
417 | | debug_printf( "unmapped read $%04X\n", addr ); |
418 | | #endif |
419 | 0 | } |
420 | | |
421 | 0 | return unmapped; |
422 | 0 | } |
423 | | |
424 | | // see hes_cpu_io.h for core read/write functions |
425 | | |
426 | | // Emulation |
427 | | |
428 | | void Hes_Emu::run_until( hes_time_t present ) |
429 | 41.2k | { |
430 | 165k | while ( vdp.next_vbl < present ) |
431 | 123k | vdp.next_vbl += play_period; |
432 | | |
433 | 41.2k | hes_time_t elapsed = present - timer.last_time; |
434 | 41.2k | if ( elapsed > 0 ) |
435 | 41.2k | { |
436 | 41.2k | if ( timer.enabled ) |
437 | 0 | { |
438 | 0 | timer.count -= elapsed; |
439 | 0 | if ( timer.count <= 0 ) |
440 | 0 | timer.count += timer.load; |
441 | 0 | } |
442 | 41.2k | timer.last_time = present; |
443 | 41.2k | } |
444 | 41.2k | } |
445 | | |
446 | | void Hes_Emu::irq_changed() |
447 | 0 | { |
448 | 0 | hes_time_t present = time(); |
449 | |
|
450 | 0 | if ( irq.timer > present ) |
451 | 0 | { |
452 | 0 | irq.timer = future_hes_time; |
453 | 0 | if ( timer.enabled && !timer.fired ) |
454 | 0 | irq.timer = present + timer.count; |
455 | 0 | } |
456 | |
|
457 | 0 | if ( irq.vdp > present ) |
458 | 0 | { |
459 | 0 | irq.vdp = future_hes_time; |
460 | 0 | if ( vdp.control & 0x08 ) |
461 | 0 | irq.vdp = vdp.next_vbl; |
462 | 0 | } |
463 | |
|
464 | 0 | hes_time_t time = future_hes_time; |
465 | 0 | if ( !(irq.disables & timer_mask) ) time = irq.timer; |
466 | 0 | if ( !(irq.disables & vdp_mask) ) time = min( time, irq.vdp ); |
467 | |
|
468 | 0 | set_irq_time( time ); |
469 | 0 | } |
470 | | |
471 | | int Hes_Emu::cpu_done() |
472 | 41.2k | { |
473 | 41.2k | check( time() >= end_time() || |
474 | 41.2k | (!(r.status & i_flag_mask) && time() >= irq_time()) ); |
475 | | |
476 | 41.2k | if ( !(r.status & i_flag_mask) ) |
477 | 0 | { |
478 | 0 | hes_time_t present = time(); |
479 | |
|
480 | 0 | if ( irq.timer <= present && !(irq.disables & timer_mask) ) |
481 | 0 | { |
482 | 0 | timer.fired = true; |
483 | 0 | irq.timer = future_hes_time; |
484 | 0 | irq_changed(); // overkill, but not worth writing custom code |
485 | | #if GME_FRAME_HOOK_DEFINED |
486 | | { |
487 | | unsigned const threshold = period_60hz / 30; |
488 | | unsigned long elapsed = present - last_frame_hook; |
489 | | if ( elapsed - period_60hz + threshold / 2 < threshold ) |
490 | | { |
491 | | last_frame_hook = present; |
492 | | GME_FRAME_HOOK( this ); |
493 | | } |
494 | | } |
495 | | #endif |
496 | 0 | return 0x0A; |
497 | 0 | } |
498 | | |
499 | 0 | if ( irq.vdp <= present && !(irq.disables & vdp_mask) ) |
500 | 0 | { |
501 | | // work around for bugs with music not acknowledging VDP |
502 | | //run_until( present ); |
503 | | //irq.vdp = future_hes_time; |
504 | | //irq_changed(); |
505 | | #if GME_FRAME_HOOK_DEFINED |
506 | | last_frame_hook = present; |
507 | | GME_FRAME_HOOK( this ); |
508 | | #endif |
509 | 0 | return 0x08; |
510 | 0 | } |
511 | 0 | } |
512 | 41.2k | return 0; |
513 | 41.2k | } |
514 | | |
515 | | static void adjust_time( int32_t& time, hes_time_t delta ) |
516 | 82.4k | { |
517 | 82.4k | if ( time < future_hes_time ) |
518 | 0 | { |
519 | 0 | time -= delta; |
520 | 0 | if ( time < 0 ) |
521 | 0 | time = 0; |
522 | 0 | } |
523 | 82.4k | } |
524 | | |
525 | | blargg_err_t Hes_Emu::run_clocks( blip_time_t& duration_, int ) |
526 | 41.2k | { |
527 | 41.2k | blip_time_t const duration = duration_; // cache |
528 | | |
529 | 41.2k | if ( cpu::run( duration ) ) |
530 | 0 | set_warning( "Emulation error (illegal instruction)" ); |
531 | | |
532 | 41.2k | check( time() >= duration ); |
533 | | //check( time() - duration < 20 ); // Txx instruction could cause going way over |
534 | | |
535 | 41.2k | run_until( duration ); |
536 | | |
537 | | // end time frame |
538 | 41.2k | timer.last_time -= duration; |
539 | 41.2k | vdp.next_vbl -= duration; |
540 | | #if GME_FRAME_HOOK_DEFINED |
541 | | last_frame_hook -= duration; |
542 | | #endif |
543 | 41.2k | cpu::end_frame( duration ); |
544 | 41.2k | ::adjust_time( irq.timer, duration ); |
545 | 41.2k | ::adjust_time( irq.vdp, duration ); |
546 | 41.2k | apu.end_frame( duration ); |
547 | 41.2k | adpcm.end_frame( duration ); |
548 | | |
549 | 41.2k | return 0; |
550 | 41.2k | } |