diff options
Diffstat (limited to 'lib/rbcodec/codecs/libgme/hes_emu.c')
-rw-r--r-- | lib/rbcodec/codecs/libgme/hes_emu.c | 644 |
1 files changed, 644 insertions, 0 deletions
diff --git a/lib/rbcodec/codecs/libgme/hes_emu.c b/lib/rbcodec/codecs/libgme/hes_emu.c new file mode 100644 index 0000000000..d6bafea334 --- /dev/null +++ b/lib/rbcodec/codecs/libgme/hes_emu.c | |||
@@ -0,0 +1,644 @@ | |||
1 | // Game_Music_Emu 0.5.2. http://www.slack.net/~ant/ | ||
2 | |||
3 | #include "hes_emu.h" | ||
4 | |||
5 | #include "blargg_endian.h" | ||
6 | #include "blargg_source.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 | int const timer_mask = 0x04; | ||
20 | int const vdp_mask = 0x02; | ||
21 | int const i_flag_mask = 0x04; | ||
22 | int const unmapped = 0xFF; | ||
23 | |||
24 | int const period_60hz = 262 * 455; // scanlines * clocks per scanline | ||
25 | |||
26 | const char gme_wrong_file_type [] = "Wrong file type for this emulator"; | ||
27 | |||
28 | static void clear_track_vars( struct Hes_Emu* this ) | ||
29 | { | ||
30 | this->current_track_ = -1; | ||
31 | track_stop( &this->track_filter ); | ||
32 | } | ||
33 | |||
34 | void Hes_init( struct Hes_Emu* this ) | ||
35 | { | ||
36 | this->sample_rate_ = 0; | ||
37 | this->mute_mask_ = 0; | ||
38 | this->tempo_ = (int)(FP_ONE_TEMPO); | ||
39 | |||
40 | // defaults | ||
41 | this->tfilter = *track_get_setup( &this->track_filter ); | ||
42 | this->tfilter.max_initial = 2; | ||
43 | this->tfilter.lookahead = 6; | ||
44 | this->track_filter.silence_ignored_ = false; | ||
45 | |||
46 | this->timer.raw_load = 0; | ||
47 | Sound_set_gain( this, (int)(FP_ONE_GAIN*1.11) ); | ||
48 | |||
49 | Rom_init( &this->rom, 0x2000 ); | ||
50 | |||
51 | Apu_init( &this->apu ); | ||
52 | Adpcm_init( &this->adpcm ); | ||
53 | Cpu_init( &this->cpu ); | ||
54 | |||
55 | /* Set default track count */ | ||
56 | this->track_count = 255; | ||
57 | |||
58 | // clears fields | ||
59 | this->voice_count_ = 0; | ||
60 | this->voice_types_ = 0; | ||
61 | clear_track_vars( this ); | ||
62 | } | ||
63 | |||
64 | static blargg_err_t check_hes_header( void const* header ) | ||
65 | { | ||
66 | if ( memcmp( header, "HESM", 4 ) ) | ||
67 | return gme_wrong_file_type; | ||
68 | return 0; | ||
69 | } | ||
70 | |||
71 | // Setup | ||
72 | |||
73 | blargg_err_t Hes_load_mem( struct Hes_Emu* this, void* data, long size ) | ||
74 | { | ||
75 | // Unload | ||
76 | this->voice_count_ = 0; | ||
77 | this->track_count = 255; | ||
78 | this->m3u.size = 0; | ||
79 | clear_track_vars( this ); | ||
80 | |||
81 | assert( offsetof (struct header_t,unused [4]) == header_size ); | ||
82 | RETURN_ERR( Rom_load( &this->rom, data, size, header_size, &this->header, unmapped ) ); | ||
83 | |||
84 | RETURN_ERR( check_hes_header( this->header.tag ) ); | ||
85 | |||
86 | /* if ( header_.vers != 0 ) | ||
87 | warning( "Unknown file version" ); | ||
88 | |||
89 | if ( memcmp( header_.data_tag, "DATA", 4 ) ) | ||
90 | warning( "Data header missing" ); | ||
91 | |||
92 | if ( memcmp( header_.unused, "\0\0\0\0", 4 ) ) | ||
93 | warning( "Unknown header data" ); */ | ||
94 | |||
95 | // File spec supports multiple blocks, but I haven't found any, and | ||
96 | // many files have bad sizes in the only block, so it's simpler to | ||
97 | // just try to load the damn data as best as possible. | ||
98 | |||
99 | int addr = get_le32( this->header.addr ); | ||
100 | /* int rom_size = get_le32( this->header.size ); */ | ||
101 | int const rom_max = 0x100000; | ||
102 | if ( (unsigned) addr >= (unsigned) rom_max ) | ||
103 | { | ||
104 | /* warning( "Invalid address" ); */ | ||
105 | addr &= rom_max - 1; | ||
106 | } | ||
107 | /* if ( (unsigned) (addr + size) > (unsigned) rom_max ) | ||
108 | warning( "Invalid size" ); | ||
109 | |||
110 | if ( rom_size != rom.file_size() ) | ||
111 | { | ||
112 | if ( size <= rom.file_size() - 4 && !memcmp( rom.begin() + size, "DATA", 4 ) ) | ||
113 | warning( "Multiple DATA not supported" ); | ||
114 | else if ( size < rom.file_size() ) | ||
115 | warning( "Extra file data" ); | ||
116 | else | ||
117 | warning( "Missing file data" ); | ||
118 | } */ | ||
119 | |||
120 | Rom_set_addr( &this->rom, addr ); | ||
121 | |||
122 | this->voice_count_ = osc_count + adpcm_osc_count; | ||
123 | static int const types [osc_count + adpcm_osc_count] = { | ||
124 | wave_type+0, wave_type+1, wave_type+2, wave_type+3, mixed_type+0, mixed_type+1, mixed_type+2 | ||
125 | }; | ||
126 | this->voice_types_ = types; | ||
127 | |||
128 | Apu_volume( &this->apu, this->gain_ ); | ||
129 | Adpcm_volume( &this->adpcm, this->gain_ ); | ||
130 | |||
131 | // Setup buffer | ||
132 | this->clock_rate_ = 7159091; | ||
133 | Buffer_clock_rate( &this->stereo_buf, 7159091 ); | ||
134 | RETURN_ERR( Buffer_set_channel_count( &this->stereo_buf, this->voice_count_, this->voice_types_ ) ); | ||
135 | this->buf_changed_count = Buffer_channels_changed_count( &this->stereo_buf ); | ||
136 | |||
137 | Sound_set_tempo( this, this->tempo_ ); | ||
138 | Sound_mute_voices( this, this->mute_mask_ ); | ||
139 | |||
140 | return 0; | ||
141 | } | ||
142 | |||
143 | // Emulation | ||
144 | |||
145 | static void recalc_timer_load( struct Hes_Emu* this ) | ||
146 | { | ||
147 | this->timer.load = this->timer.raw_load * this->timer_base + 1; | ||
148 | } | ||
149 | |||
150 | // Hardware | ||
151 | |||
152 | void run_until( struct Hes_Emu* this, hes_time_t present ) | ||
153 | { | ||
154 | while ( this->vdp.next_vbl < present ) | ||
155 | this->vdp.next_vbl += this->play_period; | ||
156 | |||
157 | hes_time_t elapsed = present - this->timer.last_time; | ||
158 | if ( elapsed > 0 ) | ||
159 | { | ||
160 | if ( this->timer.enabled ) | ||
161 | { | ||
162 | this->timer.count -= elapsed; | ||
163 | if ( this->timer.count <= 0 ) | ||
164 | this->timer.count += this->timer.load; | ||
165 | } | ||
166 | this->timer.last_time = present; | ||
167 | } | ||
168 | } | ||
169 | |||
170 | void write_vdp( struct Hes_Emu* this, int addr, int data ) | ||
171 | { | ||
172 | switch ( addr ) | ||
173 | { | ||
174 | case 0: | ||
175 | this->vdp.latch = data & 0x1F; | ||
176 | break; | ||
177 | |||
178 | case 2: | ||
179 | if ( this->vdp.latch == 5 ) | ||
180 | { | ||
181 | /* if ( data & 0x04 ) | ||
182 | warning( "Scanline interrupt unsupported" ); */ | ||
183 | run_until( this, Cpu_time( &this->cpu ) ); | ||
184 | this->vdp.control = data; | ||
185 | irq_changed( this ); | ||
186 | } | ||
187 | /* else | ||
188 | { | ||
189 | dprintf( "VDP not supported: $%02X <- $%02X\n", vdp.latch, data ); | ||
190 | } */ | ||
191 | break; | ||
192 | |||
193 | case 3: | ||
194 | /* dprintf( "VDP MSB not supported: $%02X <- $%02X\n", vdp.latch, data ); */ | ||
195 | break; | ||
196 | } | ||
197 | } | ||
198 | |||
199 | void write_mem_( struct Hes_Emu* this, hes_addr_t addr, int data ) | ||
200 | { | ||
201 | hes_time_t time = Cpu_time( &this->cpu ); | ||
202 | if ( (unsigned) (addr - apu_io_addr) < apu_io_size ) | ||
203 | { | ||
204 | // Avoid going way past end when a long block xfer is writing to I/O space. | ||
205 | // Not a problem for other registers below because they don't write to | ||
206 | // Blip_Buffer. | ||
207 | hes_time_t t = min( time, Cpu_end_time( &this->cpu ) + 8 ); | ||
208 | Apu_write_data( &this->apu, t, addr, data ); | ||
209 | return; | ||
210 | } | ||
211 | if ( (unsigned) (addr - adpcm_io_addr) < adpcm_io_size ) | ||
212 | { | ||
213 | hes_time_t t = min( time, Cpu_end_time( &this->cpu ) + 6 ); | ||
214 | Adpcm_write_data( &this->adpcm, t, addr, data ); | ||
215 | return; | ||
216 | } | ||
217 | |||
218 | switch ( addr ) | ||
219 | { | ||
220 | case 0x0000: | ||
221 | case 0x0002: | ||
222 | case 0x0003: | ||
223 | write_vdp( this, addr, data ); | ||
224 | return; | ||
225 | |||
226 | case 0x0C00: { | ||
227 | run_until( this, time ); | ||
228 | this->timer.raw_load = (data & 0x7F) + 1; | ||
229 | recalc_timer_load( this ); | ||
230 | this->timer.count = this->timer.load; | ||
231 | break; | ||
232 | } | ||
233 | |||
234 | case 0x0C01: | ||
235 | data &= 1; | ||
236 | if ( this->timer.enabled == data ) | ||
237 | return; | ||
238 | run_until( this, time ); | ||
239 | this->timer.enabled = data; | ||
240 | if ( data ) | ||
241 | this->timer.count = this->timer.load; | ||
242 | break; | ||
243 | |||
244 | case 0x1402: | ||
245 | run_until( this, time ); | ||
246 | this->irq.disables = data; | ||
247 | /* if ( (data & 0xF8) && (data & 0xF8) != 0xF8 ) // flag questionable values | ||
248 | dprintf( "Int mask: $%02X\n", data ); */ | ||
249 | break; | ||
250 | |||
251 | case 0x1403: | ||
252 | run_until( this, time ); | ||
253 | if ( this->timer.enabled ) | ||
254 | this->timer.count = this->timer.load; | ||
255 | this->timer.fired = false; | ||
256 | break; | ||
257 | |||
258 | #ifndef NDEBUG | ||
259 | case 0x1000: // I/O port | ||
260 | case 0x0402: // palette | ||
261 | case 0x0403: | ||
262 | case 0x0404: | ||
263 | case 0x0405: | ||
264 | return; | ||
265 | |||
266 | default: | ||
267 | /* dprintf( "unmapped write $%04X <- $%02X\n", addr, data ); */ | ||
268 | return; | ||
269 | #endif | ||
270 | } | ||
271 | |||
272 | irq_changed( this ); | ||
273 | } | ||
274 | |||
275 | int read_mem_( struct Hes_Emu* this, hes_addr_t addr ) | ||
276 | { | ||
277 | hes_time_t time = Cpu_time( &this->cpu ); | ||
278 | addr &= page_size - 1; | ||
279 | switch ( addr ) | ||
280 | { | ||
281 | case 0x0000: | ||
282 | if ( this->irq.vdp > time ) | ||
283 | return 0; | ||
284 | this->irq.vdp = future_time; | ||
285 | run_until( this, time ); | ||
286 | irq_changed( this ); | ||
287 | return 0x20; | ||
288 | |||
289 | /* case 0x0002: | ||
290 | case 0x0003: | ||
291 | dprintf( "VDP read not supported: %d\n", addr ); | ||
292 | return 0; */ | ||
293 | |||
294 | case 0x0C01: | ||
295 | //return timer.enabled; // TODO: remove? | ||
296 | case 0x0C00: | ||
297 | run_until( this, time ); | ||
298 | /* dprintf( "Timer count read\n" ); */ | ||
299 | return (unsigned) (this->timer.count - 1) / this->timer_base; | ||
300 | |||
301 | case 0x1402: | ||
302 | return this->irq.disables; | ||
303 | |||
304 | case 0x1403: | ||
305 | { | ||
306 | int status = 0; | ||
307 | if ( this->irq.timer <= time ) status |= timer_mask; | ||
308 | if ( this->irq.vdp <= time ) status |= vdp_mask; | ||
309 | return status; | ||
310 | } | ||
311 | |||
312 | case 0x180A: | ||
313 | case 0x180B: | ||
314 | case 0x180C: | ||
315 | case 0x180D: | ||
316 | return Adpcm_read_data( &this->adpcm, time, addr ); | ||
317 | |||
318 | #ifndef NDEBUG | ||
319 | case 0x1000: // I/O port | ||
320 | //case 0x180C: // CD-ROM | ||
321 | //case 0x180D: | ||
322 | break; | ||
323 | |||
324 | /* default: | ||
325 | dprintf( "unmapped read $%04X\n", addr ); */ | ||
326 | #endif | ||
327 | } | ||
328 | |||
329 | return unmapped; | ||
330 | } | ||
331 | |||
332 | void irq_changed( struct Hes_Emu* this ) | ||
333 | { | ||
334 | hes_time_t present = Cpu_time( &this->cpu ); | ||
335 | |||
336 | if ( this->irq.timer > present ) | ||
337 | { | ||
338 | this->irq.timer = future_time; | ||
339 | if ( this->timer.enabled && !this->timer.fired ) | ||
340 | this->irq.timer = present + this->timer.count; | ||
341 | } | ||
342 | |||
343 | if ( this->irq.vdp > present ) | ||
344 | { | ||
345 | this->irq.vdp = future_time; | ||
346 | if ( this->vdp.control & 0x08 ) | ||
347 | this->irq.vdp = this->vdp.next_vbl; | ||
348 | } | ||
349 | |||
350 | hes_time_t time = future_time; | ||
351 | if ( !(this->irq.disables & timer_mask) ) time = this->irq.timer; | ||
352 | if ( !(this->irq.disables & vdp_mask) ) time = min( time, this->irq.vdp ); | ||
353 | |||
354 | Cpu_set_irq_time( &this->cpu, time ); | ||
355 | } | ||
356 | |||
357 | int cpu_done( struct Hes_Emu* this ) | ||
358 | { | ||
359 | check( Cpu_time( &this->cpu ) >= Cpu_end_time( &this->cpu ) || | ||
360 | (!(this->cpu.r.flags & i_flag_mask) && Cpu_time( &this->cpu ) >= Cpu_irq_time( &this->cpu )) ); | ||
361 | |||
362 | if ( !(this->cpu.r.flags & i_flag_mask) ) | ||
363 | { | ||
364 | hes_time_t present = Cpu_time( &this->cpu ); | ||
365 | |||
366 | if ( this->irq.timer <= present && !(this->irq.disables & timer_mask) ) | ||
367 | { | ||
368 | this->timer.fired = true; | ||
369 | this->irq.timer = future_time; | ||
370 | irq_changed( this ); // overkill, but not worth writing custom code | ||
371 | return 0x0A; | ||
372 | } | ||
373 | |||
374 | if ( this->irq.vdp <= present && !(this->irq.disables & vdp_mask) ) | ||
375 | { | ||
376 | // work around for bugs with music not acknowledging VDP | ||
377 | //run_until( present ); | ||
378 | //irq.vdp = cpu.future_time; | ||
379 | //irq_changed(); | ||
380 | return 0x08; | ||
381 | } | ||
382 | } | ||
383 | return -1; | ||
384 | } | ||
385 | |||
386 | static void adjust_time( hes_time_t* time, hes_time_t delta ) | ||
387 | { | ||
388 | if ( *time < future_time ) | ||
389 | { | ||
390 | *time -= delta; | ||
391 | if ( *time < 0 ) | ||
392 | *time = 0; | ||
393 | } | ||
394 | } | ||
395 | |||
396 | static blargg_err_t end_frame( struct Hes_Emu* this, hes_time_t duration ) | ||
397 | { | ||
398 | /* if ( run_cpu( this, duration ) ) | ||
399 | warning( "Emulation error (illegal instruction)" ); */ | ||
400 | run_cpu( this, duration ); | ||
401 | |||
402 | check( Cpu_time( &this->cpu ) >= duration ); | ||
403 | //check( time() - duration < 20 ); // Txx instruction could cause going way over | ||
404 | |||
405 | run_until( this, duration ); | ||
406 | |||
407 | // end time frame | ||
408 | this->timer.last_time -= duration; | ||
409 | this->vdp.next_vbl -= duration; | ||
410 | Cpu_end_frame( &this->cpu, duration ); | ||
411 | adjust_time( &this->irq.timer, duration ); | ||
412 | adjust_time( &this->irq.vdp, duration ); | ||
413 | Apu_end_frame( &this->apu, duration ); | ||
414 | Adpcm_end_frame( &this->adpcm, duration ); | ||
415 | |||
416 | return 0; | ||
417 | } | ||
418 | |||
419 | static blargg_err_t run_clocks( struct Hes_Emu* this, blip_time_t* duration_ ) | ||
420 | { | ||
421 | return end_frame( this, *duration_ ); | ||
422 | } | ||
423 | |||
424 | blargg_err_t play_( void *emu, int count, sample_t out [] ) | ||
425 | { | ||
426 | struct Hes_Emu* this = (struct Hes_Emu*) emu; | ||
427 | |||
428 | int remain = count; | ||
429 | while ( remain ) | ||
430 | { | ||
431 | Buffer_disable_immediate_removal( &this->stereo_buf ); | ||
432 | remain -= Buffer_read_samples( &this->stereo_buf, &out [count - remain], remain ); | ||
433 | if ( remain ) | ||
434 | { | ||
435 | if ( this->buf_changed_count != Buffer_channels_changed_count( &this->stereo_buf ) ) | ||
436 | { | ||
437 | this->buf_changed_count = Buffer_channels_changed_count( &this->stereo_buf ); | ||
438 | |||
439 | // Remute voices | ||
440 | Sound_mute_voices( this, this->mute_mask_ ); | ||
441 | } | ||
442 | int msec = Buffer_length( &this->stereo_buf ); | ||
443 | blip_time_t clocks_emulated = msec * this->clock_rate_ / 1000 - 100; | ||
444 | RETURN_ERR( run_clocks( this, &clocks_emulated ) ); | ||
445 | assert( clocks_emulated ); | ||
446 | Buffer_end_frame( &this->stereo_buf, clocks_emulated ); | ||
447 | } | ||
448 | } | ||
449 | return 0; | ||
450 | } | ||
451 | |||
452 | // Music emu | ||
453 | |||
454 | blargg_err_t Hes_set_sample_rate( struct Hes_Emu* this, int rate ) | ||
455 | { | ||
456 | require( !this->sample_rate_ ); // sample rate can't be changed once set | ||
457 | Buffer_init( &this->stereo_buf ); | ||
458 | RETURN_ERR( Buffer_set_sample_rate( &this->stereo_buf, rate, 1000 / 20 ) ); | ||
459 | |||
460 | // Set bass frequency | ||
461 | Buffer_bass_freq( &this->stereo_buf, 60 ); | ||
462 | |||
463 | this->sample_rate_ = rate; | ||
464 | RETURN_ERR( track_init( &this->track_filter, this ) ); | ||
465 | this->tfilter.max_silence = 6 * stereo * this->sample_rate_; | ||
466 | return 0; | ||
467 | } | ||
468 | |||
469 | void Sound_mute_voice( struct Hes_Emu* this, int index, bool mute ) | ||
470 | { | ||
471 | require( (unsigned) index < (unsigned) this->voice_count_ ); | ||
472 | int bit = 1 << index; | ||
473 | int mask = this->mute_mask_ | bit; | ||
474 | if ( !mute ) | ||
475 | mask ^= bit; | ||
476 | Sound_mute_voices( this, mask ); | ||
477 | } | ||
478 | |||
479 | void Sound_mute_voices( struct Hes_Emu* this, int mask ) | ||
480 | { | ||
481 | require( this->sample_rate_ ); // sample rate must be set first | ||
482 | this->mute_mask_ = mask; | ||
483 | |||
484 | // Set adpcm voice | ||
485 | struct channel_t ch = Buffer_channel( &this->stereo_buf, this->voice_count_ ); | ||
486 | if ( mask & (1 << this->voice_count_ ) ) | ||
487 | Adpcm_set_output( &this->adpcm, 0, 0, 0, 0 ); | ||
488 | else | ||
489 | Adpcm_set_output( &this->adpcm, 0, ch.center, ch.left, ch.right ); | ||
490 | |||
491 | // Set apu voices | ||
492 | int i = this->voice_count_ - 1; | ||
493 | for ( ; i--; ) | ||
494 | { | ||
495 | if ( mask & (1 << i) ) | ||
496 | { | ||
497 | Apu_osc_output( &this->apu, i, 0, 0, 0 ); | ||
498 | } | ||
499 | else | ||
500 | { | ||
501 | struct channel_t ch = Buffer_channel( &this->stereo_buf, i ); | ||
502 | assert( (ch.center && ch.left && ch.right) || | ||
503 | (!ch.center && !ch.left && !ch.right) ); // all or nothing | ||
504 | Apu_osc_output( &this->apu, i, ch.center, ch.left, ch.right ); | ||
505 | } | ||
506 | } | ||
507 | } | ||
508 | |||
509 | void Sound_set_tempo( struct Hes_Emu* this, int t ) | ||
510 | { | ||
511 | require( this->sample_rate_ ); // sample rate must be set first | ||
512 | int const min = (int)(FP_ONE_TEMPO*0.02); | ||
513 | int const max = (int)(FP_ONE_TEMPO*4.00); | ||
514 | if ( t < min ) t = min; | ||
515 | if ( t > max ) t = max; | ||
516 | this->play_period = (hes_time_t) ((period_60hz*FP_ONE_TEMPO) / t); | ||
517 | this->timer_base = (int) ((1024*FP_ONE_TEMPO) / t); | ||
518 | recalc_timer_load( this ); | ||
519 | this->tempo_ = t; | ||
520 | } | ||
521 | |||
522 | blargg_err_t Hes_start_track( struct Hes_Emu* this, int track ) | ||
523 | { | ||
524 | clear_track_vars( this ); | ||
525 | |||
526 | // Remap track if playlist available | ||
527 | if ( this->m3u.size > 0 ) { | ||
528 | struct entry_t* e = &this->m3u.entries[track]; | ||
529 | track = e->track; | ||
530 | } | ||
531 | |||
532 | this->current_track_ = track; | ||
533 | |||
534 | Buffer_clear( &this->stereo_buf ); | ||
535 | |||
536 | memset( this->ram, 0, sizeof this->ram ); // some HES music relies on zero fill | ||
537 | memset( this->sgx, 0, sizeof this->sgx ); | ||
538 | |||
539 | Apu_reset( &this->apu ); | ||
540 | Adpcm_reset( &this->adpcm ); | ||
541 | Cpu_reset( &this->cpu ); | ||
542 | |||
543 | unsigned i; | ||
544 | for ( i = 0; i < sizeof this->header.banks; i++ ) | ||
545 | set_mmr( this, i, this->header.banks [i] ); | ||
546 | set_mmr( this, page_count, 0xFF ); // unmapped beyond end of address space | ||
547 | |||
548 | this->irq.disables = timer_mask | vdp_mask; | ||
549 | this->irq.timer = future_time; | ||
550 | this->irq.vdp = future_time; | ||
551 | |||
552 | this->timer.enabled = false; | ||
553 | this->timer.raw_load= 0x80; | ||
554 | this->timer.count = this->timer.load; | ||
555 | this->timer.fired = false; | ||
556 | this->timer.last_time = 0; | ||
557 | |||
558 | this->vdp.latch = 0; | ||
559 | this->vdp.control = 0; | ||
560 | this->vdp.next_vbl = 0; | ||
561 | |||
562 | this->ram [0x1FF] = (idle_addr - 1) >> 8; | ||
563 | this->ram [0x1FE] = (idle_addr - 1) & 0xFF; | ||
564 | this->cpu.r.sp = 0xFD; | ||
565 | this->cpu.r.pc = get_le16( this->header.init_addr ); | ||
566 | this->cpu.r.a = track; | ||
567 | |||
568 | recalc_timer_load( this ); | ||
569 | |||
570 | // convert filter times to samples | ||
571 | struct setup_t s = this->tfilter; | ||
572 | s.max_initial *= this->sample_rate_ * stereo; | ||
573 | #ifdef GME_DISABLE_SILENCE_LOOKAHEAD | ||
574 | s.lookahead = 1; | ||
575 | #endif | ||
576 | track_setup( &this->track_filter, &s ); | ||
577 | |||
578 | return track_start( &this->track_filter ); | ||
579 | } | ||
580 | |||
581 | // Tell/Seek | ||
582 | |||
583 | static int msec_to_samples( int msec, int sample_rate ) | ||
584 | { | ||
585 | int sec = msec / 1000; | ||
586 | msec -= sec * 1000; | ||
587 | return (sec * sample_rate + msec * sample_rate / 1000) * stereo; | ||
588 | } | ||
589 | |||
590 | int Track_tell( struct Hes_Emu* this ) | ||
591 | { | ||
592 | int rate = this->sample_rate_ * stereo; | ||
593 | int sec = track_sample_count( &this->track_filter ) / rate; | ||
594 | return sec * 1000 + (track_sample_count( &this->track_filter ) - sec * rate) * 1000 / rate; | ||
595 | } | ||
596 | |||
597 | blargg_err_t Track_seek( struct Hes_Emu* this, int msec ) | ||
598 | { | ||
599 | int time = msec_to_samples( msec, this->sample_rate_ ); | ||
600 | if ( time < track_sample_count( &this->track_filter ) ) | ||
601 | RETURN_ERR( Hes_start_track( this, this->current_track_ ) ); | ||
602 | return Track_skip( this, time - track_sample_count( &this->track_filter ) ); | ||
603 | } | ||
604 | |||
605 | blargg_err_t skip_( void* emu, int count ) | ||
606 | { | ||
607 | struct Hes_Emu* this = (struct Hes_Emu*) emu; | ||
608 | |||
609 | // for long skip, mute sound | ||
610 | const int threshold = 32768; | ||
611 | if ( count > threshold ) | ||
612 | { | ||
613 | int saved_mute = this->mute_mask_; | ||
614 | Sound_mute_voices( this, ~0 ); | ||
615 | |||
616 | int n = count - threshold/2; | ||
617 | n &= ~(2048-1); // round to multiple of 2048 | ||
618 | count -= n; | ||
619 | RETURN_ERR( skippy_( &this->track_filter, n ) ); | ||
620 | |||
621 | Sound_mute_voices( this, saved_mute ); | ||
622 | } | ||
623 | |||
624 | return skippy_( &this->track_filter, count ); | ||
625 | } | ||
626 | |||
627 | blargg_err_t Track_skip( struct Hes_Emu* this, int count ) | ||
628 | { | ||
629 | require( this->current_track_ >= 0 ); // start_track() must have been called already | ||
630 | return track_skip( &this->track_filter, count ); | ||
631 | } | ||
632 | |||
633 | void Track_set_fade( struct Hes_Emu* this, int start_msec, int length_msec ) | ||
634 | { | ||
635 | track_set_fade( &this->track_filter, msec_to_samples( start_msec, this->sample_rate_ ), | ||
636 | length_msec * this->sample_rate_ / (1000 / stereo) ); | ||
637 | } | ||
638 | |||
639 | blargg_err_t Hes_play( struct Hes_Emu* this, int out_count, sample_t* out ) | ||
640 | { | ||
641 | require( this->current_track_ >= 0 ); | ||
642 | require( out_count % stereo == 0 ); | ||
643 | return track_play( &this->track_filter, out_count, out ); | ||
644 | } | ||