diff options
Diffstat (limited to 'lib/rbcodec/codecs/libgme/sgc_emu.c')
-rw-r--r-- | lib/rbcodec/codecs/libgme/sgc_emu.c | 480 |
1 files changed, 480 insertions, 0 deletions
diff --git a/lib/rbcodec/codecs/libgme/sgc_emu.c b/lib/rbcodec/codecs/libgme/sgc_emu.c new file mode 100644 index 0000000000..267f2c9271 --- /dev/null +++ b/lib/rbcodec/codecs/libgme/sgc_emu.c | |||
@@ -0,0 +1,480 @@ | |||
1 | // Game_Music_Emu 0.6-pre. http://www.slack.net/~ant/ | ||
2 | |||
3 | #include "sgc_emu.h" | ||
4 | |||
5 | /* Copyright (C) 2009 Shay Green. This module is free software; you | ||
6 | can redistribute it and/or modify it under the terms of the GNU Lesser | ||
7 | General Public License as published by the Free Software Foundation; either | ||
8 | version 2.1 of the License, or (at your option) any later version. This | ||
9 | module is distributed in the hope that it will be useful, but WITHOUT ANY | ||
10 | WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS | ||
11 | FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more | ||
12 | details. You should have received a copy of the GNU Lesser General Public | ||
13 | License aint with this module; if not, write to the Free Software Foundation, | ||
14 | Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ | ||
15 | |||
16 | #include "blargg_source.h" | ||
17 | |||
18 | int const osc_count = sms_osc_count + fm_apu_osc_count; | ||
19 | |||
20 | const char gme_wrong_file_type [] = "Wrong file type for this emulator"; | ||
21 | |||
22 | static void clear_track_vars( struct Sgc_Emu* this ) | ||
23 | { | ||
24 | this->current_track = -1; | ||
25 | track_stop( &this->track_filter ); | ||
26 | } | ||
27 | |||
28 | void Sgc_init( struct Sgc_Emu* this ) | ||
29 | { | ||
30 | assert( offsetof (struct header_t,copyright [32]) == header_size ); | ||
31 | |||
32 | this->sample_rate = 0; | ||
33 | this->mute_mask_ = 0; | ||
34 | this->tempo = (int)FP_ONE_TEMPO; | ||
35 | this->gain = (int)FP_ONE_GAIN; | ||
36 | |||
37 | // defaults | ||
38 | this->tfilter = *track_get_setup( &this->track_filter ); | ||
39 | this->tfilter.max_initial = 2; | ||
40 | this->tfilter.lookahead = 6; | ||
41 | this->track_filter.silence_ignored_ = false; | ||
42 | |||
43 | Sms_apu_init( &this->apu ); | ||
44 | Fm_apu_create( &this->fm_apu ); | ||
45 | |||
46 | Rom_init( &this->rom, 0x4000 ); | ||
47 | Z80_init( &this->cpu ); | ||
48 | |||
49 | Sound_set_gain( this, (int)(FP_ONE_GAIN*1.2) ); | ||
50 | |||
51 | // Unload | ||
52 | this->voice_count = 0; | ||
53 | this->voice_types = 0; | ||
54 | clear_track_vars( this ); | ||
55 | } | ||
56 | |||
57 | // Setup | ||
58 | |||
59 | blargg_err_t Sgc_load_mem( struct Sgc_Emu* this, const void* data, long size ) | ||
60 | { | ||
61 | RETURN_ERR( Rom_load( &this->rom, data, size, header_size, &this->header, 0 ) ); | ||
62 | |||
63 | if ( !valid_tag( &this->header ) ) | ||
64 | return gme_wrong_file_type; | ||
65 | |||
66 | /* if ( header.vers != 1 ) | ||
67 | warning( "Unknown file version" ); */ | ||
68 | |||
69 | /* if ( header.system > 2 ) | ||
70 | warning( "Unknown system" ); */ | ||
71 | |||
72 | addr_t load_addr = get_le16( this->header.load_addr ); | ||
73 | /* if ( load_addr < 0x400 ) | ||
74 | set_warning( "Invalid load address" ); */ | ||
75 | |||
76 | Rom_set_addr( &this->rom, load_addr ); | ||
77 | this->play_period = clock_rate( this ) / 60; | ||
78 | |||
79 | if ( sega_mapping( this ) && Fm_apu_supported() ) | ||
80 | RETURN_ERR( Fm_apu_init( &this->fm_apu, clock_rate( this ), clock_rate( this ) / 72 ) ); | ||
81 | |||
82 | this->m3u.size = 0; | ||
83 | this->track_count = this->header.song_count; | ||
84 | this->voice_count = sega_mapping( this ) ? osc_count : sms_osc_count; | ||
85 | static int const types [sms_osc_count + fm_apu_osc_count] = { | ||
86 | wave_type+1, wave_type+2, wave_type+3, mixed_type+1, mixed_type+2 | ||
87 | }; | ||
88 | this->voice_types = types; | ||
89 | |||
90 | Sms_apu_volume( &this->apu, this->gain ); | ||
91 | Fm_apu_volume( &this->fm_apu, this->gain ); | ||
92 | |||
93 | // Setup buffer | ||
94 | this->clock_rate_ = clock_rate( this ); | ||
95 | Buffer_clock_rate( &this->stereo_buf, clock_rate( this ) ); | ||
96 | RETURN_ERR( Buffer_set_channel_count( &this->stereo_buf, this->voice_count, this->voice_types ) ); | ||
97 | this->buf_changed_count = Buffer_channels_changed_count( &this->stereo_buf ); | ||
98 | |||
99 | Sound_set_tempo( this, this->tempo ); | ||
100 | Sound_mute_voices( this, this->mute_mask_ ); | ||
101 | return 0; | ||
102 | } | ||
103 | |||
104 | static void Sound_set_voice( struct Sgc_Emu* this, int i, struct Blip_Buffer* c, struct Blip_Buffer* l, struct Blip_Buffer* r ) | ||
105 | { | ||
106 | if ( i < sms_osc_count ) | ||
107 | Sms_apu_set_output( &this->apu, i, c, l, r ); | ||
108 | else | ||
109 | Fm_apu_set_output( &this->fm_apu, c ); | ||
110 | } | ||
111 | |||
112 | static blargg_err_t run_clocks( struct Sgc_Emu* this, blip_time_t* duration, int msec ) | ||
113 | { | ||
114 | #if defined(ROCKBOX) | ||
115 | (void) msec; | ||
116 | #endif | ||
117 | |||
118 | cpu_time_t t = *duration; | ||
119 | while ( Z80_time( &this->cpu ) < t ) | ||
120 | { | ||
121 | cpu_time_t next = min( t, this->next_play ); | ||
122 | if ( run_cpu( this, next ) ) | ||
123 | { | ||
124 | /* warning( "Unsupported CPU instruction" ); */ | ||
125 | Z80_set_time( &this->cpu, next ); | ||
126 | } | ||
127 | |||
128 | if ( this->cpu.r.pc == this->idle_addr ) | ||
129 | Z80_set_time( &this->cpu, next ); | ||
130 | |||
131 | if ( Z80_time( &this->cpu ) >= this->next_play ) | ||
132 | { | ||
133 | this->next_play += this->play_period; | ||
134 | if ( this->cpu.r.pc == this->idle_addr ) | ||
135 | jsr( this, this->header.play_addr ); | ||
136 | } | ||
137 | } | ||
138 | |||
139 | this->next_play -= t; | ||
140 | check( this->next_play >= 0 ); | ||
141 | Z80_adjust_time( &this->cpu, -t ); | ||
142 | |||
143 | Sms_apu_end_frame( &this->apu, t ); | ||
144 | if ( sega_mapping( this ) && this->fm_accessed ) | ||
145 | { | ||
146 | if ( Fm_apu_supported() ) | ||
147 | Fm_apu_end_frame( &this->fm_apu, t ); | ||
148 | /* else | ||
149 | warning( "FM sound not supported" ); */ | ||
150 | } | ||
151 | |||
152 | return 0; | ||
153 | } | ||
154 | |||
155 | // Emulation | ||
156 | |||
157 | void cpu_out( struct Sgc_Emu* this, cpu_time_t time, addr_t addr, int data ) | ||
158 | { | ||
159 | int port = addr & 0xFF; | ||
160 | |||
161 | if ( sega_mapping( this ) ) | ||
162 | { | ||
163 | switch ( port ) | ||
164 | { | ||
165 | case 0x06: | ||
166 | Sms_apu_write_ggstereo( &this->apu, time, data ); | ||
167 | return; | ||
168 | |||
169 | case 0x7E: | ||
170 | case 0x7F: | ||
171 | Sms_apu_write_data( &this->apu, time, data ); /* dprintf( "$7E<-%02X\n", data ); */ | ||
172 | return; | ||
173 | |||
174 | case 0xF0: | ||
175 | this->fm_accessed = true; | ||
176 | if ( Fm_apu_supported() ) | ||
177 | Fm_apu_write_addr( &this->fm_apu, data );//, dprintf( "$F0<-%02X\n", data ); | ||
178 | return; | ||
179 | |||
180 | case 0xF1: | ||
181 | this->fm_accessed = true; | ||
182 | if ( Fm_apu_supported() ) | ||
183 | Fm_apu_write_data( &this->fm_apu, time, data );//, dprintf( "$F1<-%02X\n", data ); | ||
184 | return; | ||
185 | } | ||
186 | } | ||
187 | else if ( port >= 0xE0 ) | ||
188 | { | ||
189 | Sms_apu_write_data( &this->apu, time, data ); | ||
190 | return; | ||
191 | } | ||
192 | } | ||
193 | |||
194 | void jsr( struct Sgc_Emu* this, byte addr [2] ) | ||
195 | { | ||
196 | *Z80_write( &this->cpu, --this->cpu.r.sp ) = this->idle_addr >> 8; | ||
197 | *Z80_write( &this->cpu, --this->cpu.r.sp ) = this->idle_addr & 0xFF; | ||
198 | this->cpu.r.pc = get_le16( addr ); | ||
199 | } | ||
200 | |||
201 | static void set_bank( struct Sgc_Emu* this, int bank, void const* data ) | ||
202 | { | ||
203 | //dprintf( "map bank %d to %p\n", bank, (byte*) data - rom.at_addr( 0 ) ); | ||
204 | Z80_map_mem( &this->cpu, bank * this->rom.bank_size, this->rom.bank_size, this->unmapped_write, data ); | ||
205 | } | ||
206 | |||
207 | void cpu_write( struct Sgc_Emu* this, addr_t addr, int data ) | ||
208 | { | ||
209 | if ( (addr ^ 0xFFFC) > 3 || !sega_mapping( this ) ) | ||
210 | { | ||
211 | *Z80_write( &this->cpu, addr ) = data; | ||
212 | return; | ||
213 | } | ||
214 | |||
215 | switch ( addr ) | ||
216 | { | ||
217 | case 0xFFFC: | ||
218 | Z80_map_mem_rw( &this->cpu, 2 * this->rom.bank_size, this->rom.bank_size, this->ram2 ); | ||
219 | if ( data & 0x08 ) | ||
220 | break; | ||
221 | |||
222 | this->bank2 = this->ram2; | ||
223 | // FALL THROUGH | ||
224 | |||
225 | case 0xFFFF: { | ||
226 | bool rom_mapped = (Z80_read( &this->cpu, 2 * this->rom.bank_size ) == this->bank2); | ||
227 | this->bank2 = Rom_at_addr( &this->rom, data * this->rom.bank_size ); | ||
228 | if ( rom_mapped ) | ||
229 | set_bank( this, 2, this->bank2 ); | ||
230 | break; | ||
231 | } | ||
232 | |||
233 | case 0xFFFD: | ||
234 | set_bank( this, 0, Rom_at_addr( &this->rom, data * this->rom.bank_size ) ); | ||
235 | break; | ||
236 | |||
237 | case 0xFFFE: | ||
238 | set_bank( this, 1, Rom_at_addr( &this->rom, data * this->rom.bank_size ) ); | ||
239 | break; | ||
240 | } | ||
241 | } | ||
242 | |||
243 | blargg_err_t Sgc_set_sample_rate( struct Sgc_Emu* this, int rate ) | ||
244 | { | ||
245 | require( !this->sample_rate ); // sample rate can't be changed once set | ||
246 | Buffer_init( &this->stereo_buf ); | ||
247 | Buffer_set_sample_rate( &this->stereo_buf, rate, 1000 / 20 ); | ||
248 | |||
249 | // Set buffer bass | ||
250 | Buffer_bass_freq( &this->stereo_buf, 80 ); | ||
251 | |||
252 | this->sample_rate = rate; | ||
253 | RETURN_ERR( track_init( &this->track_filter, this ) ); | ||
254 | this->tfilter.max_silence = 6 * stereo * this->sample_rate; | ||
255 | return 0; | ||
256 | } | ||
257 | |||
258 | void Sound_mute_voice( struct Sgc_Emu* this, int index, bool mute ) | ||
259 | { | ||
260 | require( (unsigned) index < (unsigned) this->voice_count ); | ||
261 | int bit = 1 << index; | ||
262 | int mask = this->mute_mask_ | bit; | ||
263 | if ( !mute ) | ||
264 | mask ^= bit; | ||
265 | Sound_mute_voices( this, mask ); | ||
266 | } | ||
267 | |||
268 | void Sound_mute_voices( struct Sgc_Emu* this, int mask ) | ||
269 | { | ||
270 | require( this->sample_rate ); // sample rate must be set first | ||
271 | this->mute_mask_ = mask; | ||
272 | |||
273 | int i; | ||
274 | for ( i = this->voice_count; i--; ) | ||
275 | { | ||
276 | if ( mask & (1 << i) ) | ||
277 | { | ||
278 | Sound_set_voice( this, i, 0, 0, 0 ); | ||
279 | } | ||
280 | else | ||
281 | { | ||
282 | struct channel_t ch = Buffer_channel( &this->stereo_buf, i ); | ||
283 | assert( (ch.center && ch.left && ch.right) || | ||
284 | (!ch.center && !ch.left && !ch.right) ); // all or nothing | ||
285 | Sound_set_voice( this, i, ch.center, ch.left, ch.right ); | ||
286 | } | ||
287 | } | ||
288 | } | ||
289 | |||
290 | void Sound_set_tempo( struct Sgc_Emu* this, int t ) | ||
291 | { | ||
292 | require( this->sample_rate ); // sample rate must be set first | ||
293 | int const min = (int)(FP_ONE_TEMPO*0.02); | ||
294 | int const max = (int)(FP_ONE_TEMPO*4.00); | ||
295 | if ( t < min ) t = min; | ||
296 | if ( t > max ) t = max; | ||
297 | this->tempo = t; | ||
298 | |||
299 | this->play_period = (int) ((clock_rate( this ) * FP_ONE_TEMPO) / (this->header.rate ? 50 : 60) / t); | ||
300 | } | ||
301 | |||
302 | blargg_err_t Sgc_start_track( struct Sgc_Emu* this, int track ) | ||
303 | { | ||
304 | clear_track_vars( this ); | ||
305 | |||
306 | // Remap track if playlist available | ||
307 | if ( this->m3u.size > 0 ) { | ||
308 | struct entry_t* e = &this->m3u.entries[track]; | ||
309 | track = e->track; | ||
310 | } | ||
311 | |||
312 | this->current_track = track; | ||
313 | |||
314 | if ( sega_mapping( this ) ) | ||
315 | { | ||
316 | Sms_apu_reset( &this->apu, 0, 0 ); | ||
317 | Fm_apu_reset( &this->fm_apu ); | ||
318 | this->fm_accessed = false; | ||
319 | } | ||
320 | else | ||
321 | { | ||
322 | Sms_apu_reset( &this->apu, 0x0003, 15 ); | ||
323 | } | ||
324 | |||
325 | memset( this->ram , 0, sizeof this->ram ); | ||
326 | memset( this->ram2, 0, sizeof this->ram2 ); | ||
327 | memset( this->vectors, 0xFF, sizeof this->vectors ); | ||
328 | Z80_reset( &this->cpu, this->unmapped_write, this->rom.unmapped ); | ||
329 | |||
330 | if ( sega_mapping( this ) ) | ||
331 | { | ||
332 | this->vectors_addr = 0x10000 - page_size; | ||
333 | this->idle_addr = this->vectors_addr; | ||
334 | int i; | ||
335 | for ( i = 1; i < 8; ++i ) | ||
336 | { | ||
337 | this->vectors [i*8 + 0] = 0xC3; // JP addr | ||
338 | this->vectors [i*8 + 1] = this->header.rst_addrs [i - 1] & 0xff; | ||
339 | this->vectors [i*8 + 2] = this->header.rst_addrs [i - 1] >> 8; | ||
340 | } | ||
341 | |||
342 | Z80_map_mem_rw( &this->cpu, 0xC000, 0x2000, this->ram ); | ||
343 | Z80_map_mem( &this->cpu, this->vectors_addr, page_size, this->unmapped_write, this->vectors ); | ||
344 | |||
345 | this->bank2 = NULL; | ||
346 | for ( i = 0; i < 4; ++i ) | ||
347 | cpu_write( this, 0xFFFC + i, this->header.mapping [i] ); | ||
348 | } | ||
349 | else | ||
350 | { | ||
351 | if ( !this->coleco_bios ) | ||
352 | return "Coleco BIOS not set"; /* BLARGG_ERR( BLARGG_ERR_CALLER, "Coleco BIOS not set" ); */ | ||
353 | |||
354 | this->vectors_addr = 0; | ||
355 | Z80_map_mem( &this->cpu, 0, 0x2000, this->unmapped_write, this->coleco_bios ); | ||
356 | int i; | ||
357 | for ( i = 0; i < 8; ++i ) | ||
358 | Z80_map_mem_rw( &this->cpu, 0x6000 + i*0x400, 0x400, this->ram ); | ||
359 | |||
360 | this->idle_addr = 0x2000; | ||
361 | Z80_map_mem( &this->cpu, 0x2000, page_size, this->unmapped_write, this->vectors ); | ||
362 | |||
363 | for ( i = 0; i < 0x8000 / this->rom.bank_size; ++i ) | ||
364 | { | ||
365 | int addr = 0x8000 + i*this->rom.bank_size; | ||
366 | Z80_map_mem( &this->cpu, addr, this->rom.bank_size, this->unmapped_write, Rom_at_addr( &this->rom, addr ) ); | ||
367 | } | ||
368 | } | ||
369 | |||
370 | this->cpu.r.sp = get_le16( this->header.stack_ptr ); | ||
371 | this->cpu.r.b.a = track; | ||
372 | this->next_play = this->play_period; | ||
373 | |||
374 | jsr( this, this->header.init_addr ); | ||
375 | |||
376 | Buffer_clear( &this->stereo_buf ); | ||
377 | |||
378 | // convert filter times to samples | ||
379 | struct setup_t s = this->tfilter; | ||
380 | s.max_initial *= this->sample_rate * stereo; | ||
381 | #ifdef GME_DISABLE_SILENCE_LOOKAHEAD | ||
382 | s.lookahead = 1; | ||
383 | #endif | ||
384 | track_setup( &this->track_filter, &s ); | ||
385 | |||
386 | return track_start( &this->track_filter ); | ||
387 | } | ||
388 | |||
389 | // Tell/Seek | ||
390 | |||
391 | static int msec_to_samples( int msec, int sample_rate ) | ||
392 | { | ||
393 | int sec = msec / 1000; | ||
394 | msec -= sec * 1000; | ||
395 | return (sec * sample_rate + msec * sample_rate / 1000) * stereo; | ||
396 | } | ||
397 | |||
398 | int Track_tell( struct Sgc_Emu* this ) | ||
399 | { | ||
400 | int rate = this->sample_rate * stereo; | ||
401 | int sec = track_sample_count( &this->track_filter ) / rate; | ||
402 | return sec * 1000 + (track_sample_count( &this->track_filter ) - sec * rate) * 1000 / rate; | ||
403 | } | ||
404 | |||
405 | blargg_err_t Track_seek( struct Sgc_Emu* this, int msec ) | ||
406 | { | ||
407 | int time = msec_to_samples( msec, this->sample_rate ); | ||
408 | if ( time < track_sample_count( &this->track_filter ) ) | ||
409 | RETURN_ERR( Sgc_start_track( this, this->current_track ) ); | ||
410 | return Track_skip( this, time - track_sample_count( &this->track_filter ) ); | ||
411 | } | ||
412 | |||
413 | blargg_err_t Track_skip( struct Sgc_Emu* this, int count ) | ||
414 | { | ||
415 | require( this->current_track >= 0 ); // start_track() must have been called already | ||
416 | return track_skip( &this->track_filter, count ); | ||
417 | } | ||
418 | |||
419 | blargg_err_t skip_( void* emu, int count ) | ||
420 | { | ||
421 | struct Sgc_Emu* this = (struct Sgc_Emu*) emu; | ||
422 | |||
423 | // for long skip, mute sound | ||
424 | const int threshold = 32768; | ||
425 | if ( count > threshold ) | ||
426 | { | ||
427 | int saved_mute = this->mute_mask_; | ||
428 | Sound_mute_voices( this, ~0 ); | ||
429 | |||
430 | int n = count - threshold/2; | ||
431 | n &= ~(2048-1); // round to multiple of 2048 | ||
432 | count -= n; | ||
433 | RETURN_ERR( skippy_( &this->track_filter, n ) ); | ||
434 | |||
435 | Sound_mute_voices( this, saved_mute ); | ||
436 | } | ||
437 | |||
438 | return skippy_( &this->track_filter, count ); | ||
439 | } | ||
440 | |||
441 | void Track_set_fade( struct Sgc_Emu* this, int start_msec, int length_msec ) | ||
442 | { | ||
443 | track_set_fade( &this->track_filter, msec_to_samples( start_msec, this->sample_rate ), | ||
444 | length_msec * this->sample_rate / (1000 / stereo) ); | ||
445 | } | ||
446 | |||
447 | blargg_err_t Sgc_play( struct Sgc_Emu* this, int out_count, sample_t* out ) | ||
448 | { | ||
449 | require( this->current_track >= 0 ); | ||
450 | require( out_count % stereo == 0 ); | ||
451 | return track_play( &this->track_filter, out_count, out ); | ||
452 | } | ||
453 | |||
454 | blargg_err_t play_( void* emu, int count, sample_t out [] ) | ||
455 | { | ||
456 | struct Sgc_Emu* this = (struct Sgc_Emu*) emu; | ||
457 | |||
458 | int remain = count; | ||
459 | while ( remain ) | ||
460 | { | ||
461 | Buffer_disable_immediate_removal( &this->stereo_buf ); | ||
462 | remain -= Buffer_read_samples( &this->stereo_buf, &out [count - remain], remain ); | ||
463 | if ( remain ) | ||
464 | { | ||
465 | if ( this->buf_changed_count != Buffer_channels_changed_count( &this->stereo_buf ) ) | ||
466 | { | ||
467 | this->buf_changed_count = Buffer_channels_changed_count( &this->stereo_buf ); | ||
468 | |||
469 | // Remute voices | ||
470 | Sound_mute_voices( this, this->mute_mask_ ); | ||
471 | } | ||
472 | int msec = Buffer_length( &this->stereo_buf ); | ||
473 | blip_time_t clocks_emulated = msec * this->clock_rate_ / 1000 - 100; | ||
474 | RETURN_ERR( run_clocks( this, &clocks_emulated, msec ) ); | ||
475 | assert( clocks_emulated ); | ||
476 | Buffer_end_frame( &this->stereo_buf, clocks_emulated ); | ||
477 | } | ||
478 | } | ||
479 | return 0; | ||
480 | } | ||