diff options
author | Sean Bartell <wingedtachikoma@gmail.com> | 2011-06-25 21:32:25 -0400 |
---|---|---|
committer | Nils Wallménius <nils@rockbox.org> | 2012-04-25 22:13:20 +0200 |
commit | f40bfc9267b13b54e6379dfe7539447662879d24 (patch) | |
tree | 9b20069d5e62809ff434061ad730096836f916f2 /lib/rbcodec/codecs/libgme/gbs_emu.c | |
parent | a0009907de7a0107d49040d8a180f140e2eff299 (diff) | |
download | rockbox-f40bfc9267b13b54e6379dfe7539447662879d24.tar.gz rockbox-f40bfc9267b13b54e6379dfe7539447662879d24.zip |
Add codecs to librbcodec.
Change-Id: Id7f4717d51ed02d67cb9f9cb3c0ada4a81843f97
Reviewed-on: http://gerrit.rockbox.org/137
Reviewed-by: Nils Wallménius <nils@rockbox.org>
Tested-by: Nils Wallménius <nils@rockbox.org>
Diffstat (limited to 'lib/rbcodec/codecs/libgme/gbs_emu.c')
-rw-r--r-- | lib/rbcodec/codecs/libgme/gbs_emu.c | 452 |
1 files changed, 452 insertions, 0 deletions
diff --git a/lib/rbcodec/codecs/libgme/gbs_emu.c b/lib/rbcodec/codecs/libgme/gbs_emu.c new file mode 100644 index 0000000000..7a6d484673 --- /dev/null +++ b/lib/rbcodec/codecs/libgme/gbs_emu.c | |||
@@ -0,0 +1,452 @@ | |||
1 | // Game_Music_Emu 0.5.2. http://www.slack.net/~ant/ | ||
2 | |||
3 | #include "gbs_emu.h" | ||
4 | |||
5 | #include "blargg_endian.h" | ||
6 | |||
7 | /* Copyright (C) 2003-2006 Shay Green. this module is free software; you | ||
8 | can redistribute it and/or modify it under the terms of the GNU Lesser | ||
9 | General Public License as published by the Free Software Foundation; either | ||
10 | version 2.1 of the License, or (at your option) any later version. this | ||
11 | module is distributed in the hope that it will be useful, but WITHOUT ANY | ||
12 | WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS | ||
13 | FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more | ||
14 | details. You should have received a copy of the GNU Lesser General Public | ||
15 | License along with this module; if not, write to the Free Software Foundation, | ||
16 | Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ | ||
17 | |||
18 | #include "blargg_source.h" | ||
19 | |||
20 | const char gme_wrong_file_type [] = "Wrong file type for this emulator"; | ||
21 | |||
22 | int const idle_addr = 0xF00D; | ||
23 | int const tempo_unit = 16; | ||
24 | |||
25 | static void clear_track_vars( struct Gbs_Emu* this ) | ||
26 | { | ||
27 | this->current_track_ = -1; | ||
28 | track_stop( &this->track_filter ); | ||
29 | } | ||
30 | |||
31 | void Gbs_init( struct Gbs_Emu* this ) | ||
32 | { | ||
33 | this->sample_rate_ = 0; | ||
34 | this->mute_mask_ = 0; | ||
35 | this->tempo_ = (int)(FP_ONE_TEMPO); | ||
36 | |||
37 | // Unload | ||
38 | this->header.timer_mode = 0; | ||
39 | |||
40 | // defaults | ||
41 | this->tfilter = *track_get_setup( &this->track_filter ); | ||
42 | this->tfilter.max_initial = 21; | ||
43 | this->tfilter.lookahead = 6; | ||
44 | this->track_filter.silence_ignored_ = false; | ||
45 | |||
46 | Sound_set_gain( this, (int)(FP_ONE_GAIN*1.2) ); | ||
47 | |||
48 | Rom_init( &this->rom, 0x4000 ); | ||
49 | |||
50 | Apu_init( &this->apu ); | ||
51 | Cpu_init( &this->cpu ); | ||
52 | |||
53 | this->tempo = tempo_unit; | ||
54 | this->sound_hardware = sound_gbs; | ||
55 | |||
56 | // Reduce apu sound clicks? | ||
57 | Apu_reduce_clicks( &this->apu, true ); | ||
58 | |||
59 | // clears fields | ||
60 | this->voice_count_ = 0; | ||
61 | this->voice_types_ = 0; | ||
62 | clear_track_vars( this ); | ||
63 | } | ||
64 | |||
65 | static blargg_err_t check_gbs_header( void const* header ) | ||
66 | { | ||
67 | if ( memcmp( header, "GBS", 3 ) ) | ||
68 | return gme_wrong_file_type; | ||
69 | return 0; | ||
70 | } | ||
71 | |||
72 | // Setup | ||
73 | |||
74 | blargg_err_t Gbs_load_mem( struct Gbs_Emu* this, void* data, long size ) | ||
75 | { | ||
76 | // Unload | ||
77 | this->header.timer_mode = 0; | ||
78 | this->voice_count_ = 0; | ||
79 | this->track_count = 0; | ||
80 | this->m3u.size = 0; | ||
81 | clear_track_vars( this ); | ||
82 | |||
83 | assert( offsetof (struct header_t,copyright [32]) == header_size ); | ||
84 | RETURN_ERR( Rom_load( &this->rom, data, size, header_size, &this->header, 0 ) ); | ||
85 | |||
86 | RETURN_ERR( check_gbs_header( &this->header ) ); | ||
87 | |||
88 | /* Ignore warnings? */ | ||
89 | /*if ( header_.vers != 1 ) | ||
90 | warning( "Unknown file version" ); | ||
91 | |||
92 | if ( header_.timer_mode & 0x78 ) | ||
93 | warning( "Invalid timer mode" ); */ | ||
94 | |||
95 | /* unsigned load_addr = get_le16( this->header.load_addr ); */ | ||
96 | /* if ( (header_.load_addr [1] | header_.init_addr [1] | header_.play_addr [1]) > 0x7F || | ||
97 | load_addr < 0x400 ) | ||
98 | warning( "Invalid load/init/play address" ); */ | ||
99 | |||
100 | unsigned load_addr = get_le16( this->header.load_addr ); | ||
101 | /* if ( (this->header.load_addr [1] | this->header.init_addr [1] | this->header.play_addr [1]) > 0x7F || | ||
102 | load_addr < 0x400 ) | ||
103 | warning( "Invalid load/init/play address" ); */ | ||
104 | |||
105 | this->cpu.rst_base = load_addr; | ||
106 | Rom_set_addr( &this->rom, load_addr ); | ||
107 | |||
108 | this->voice_count_ = osc_count; | ||
109 | static int const types [osc_count] = { | ||
110 | wave_type+1, wave_type+2, wave_type+3, mixed_type+1 | ||
111 | }; | ||
112 | this->voice_types_ = types; | ||
113 | |||
114 | Apu_volume( &this->apu, this->gain_ ); | ||
115 | |||
116 | // Change clock rate & setup buffer | ||
117 | this->clock_rate_ = 4194304; | ||
118 | Buffer_clock_rate( &this->stereo_buf, 4194304 ); | ||
119 | RETURN_ERR( Buffer_set_channel_count( &this->stereo_buf, this->voice_count_, this->voice_types_ ) ); | ||
120 | this->buf_changed_count = Buffer_channels_changed_count( &this->stereo_buf ); | ||
121 | |||
122 | // Post load | ||
123 | Sound_set_tempo( this, this->tempo_ ); | ||
124 | Sound_mute_voices( this, this->mute_mask_ ); | ||
125 | |||
126 | // Set track count | ||
127 | this->track_count = this->header.track_count; | ||
128 | return 0; | ||
129 | } | ||
130 | |||
131 | // Emulation | ||
132 | |||
133 | // see gb_cpu_io.h for read/write functions | ||
134 | |||
135 | void set_bank( struct Gbs_Emu* this, int n ) | ||
136 | { | ||
137 | addr_t addr = mask_addr( n * this->rom.bank_size, this->rom.mask ); | ||
138 | if ( addr == 0 && this->rom.size > this->rom.bank_size ) | ||
139 | addr = this->rom.bank_size; // MBC1&2 behavior, bank 0 acts like bank 1 | ||
140 | Cpu_map_code( &this->cpu, this->rom.bank_size, this->rom.bank_size, Rom_at_addr( &this->rom, addr ) ); | ||
141 | } | ||
142 | |||
143 | void update_timer( struct Gbs_Emu* this ) | ||
144 | { | ||
145 | this->play_period = 70224 / tempo_unit; /// 59.73 Hz | ||
146 | |||
147 | if ( this->header.timer_mode & 0x04 ) | ||
148 | { | ||
149 | // Using custom rate | ||
150 | static byte const rates [4] = { 6, 0, 2, 4 }; | ||
151 | // TODO: emulate double speed CPU mode rather than halving timer rate | ||
152 | int double_speed = this->header.timer_mode >> 7; | ||
153 | int shift = rates [this->ram [hi_page + 7] & 3] - double_speed; | ||
154 | this->play_period = (256 - this->ram [hi_page + 6]) << shift; | ||
155 | } | ||
156 | |||
157 | this->play_period *= this->tempo; | ||
158 | } | ||
159 | |||
160 | // Jumps to routine, given pointer to address in file header. Pushes idle_addr | ||
161 | // as return address, NOT old PC. | ||
162 | void jsr_then_stop( struct Gbs_Emu* this, byte const addr [] ) | ||
163 | { | ||
164 | check( this->cpu.r.sp == get_le16( this->header.stack_ptr ) ); | ||
165 | this->cpu.r.pc = get_le16( addr ); | ||
166 | write_mem( this, --this->cpu.r.sp, idle_addr >> 8 ); | ||
167 | write_mem( this, --this->cpu.r.sp, idle_addr ); | ||
168 | } | ||
169 | |||
170 | static blargg_err_t run_until( struct Gbs_Emu* this, int end ) | ||
171 | { | ||
172 | this->end_time = end; | ||
173 | Cpu_set_time( &this->cpu, Cpu_time( &this->cpu ) - end ); | ||
174 | while ( true ) | ||
175 | { | ||
176 | run_cpu( this ); | ||
177 | if ( Cpu_time( &this->cpu ) >= 0 ) | ||
178 | break; | ||
179 | |||
180 | if ( this->cpu.r.pc == idle_addr ) | ||
181 | { | ||
182 | if ( this->next_play > this->end_time ) | ||
183 | { | ||
184 | Cpu_set_time( &this->cpu, 0 ); | ||
185 | break; | ||
186 | } | ||
187 | |||
188 | if ( Cpu_time( &this->cpu ) < this->next_play - this->end_time ) | ||
189 | Cpu_set_time( &this->cpu, this->next_play - this->end_time ); | ||
190 | this->next_play += this->play_period; | ||
191 | jsr_then_stop( this, this->header.play_addr ); | ||
192 | } | ||
193 | else if ( this->cpu.r.pc > 0xFFFF ) | ||
194 | { | ||
195 | /* warning( "PC wrapped around\n" ); */ | ||
196 | this->cpu.r.pc &= 0xFFFF; | ||
197 | } | ||
198 | else | ||
199 | { | ||
200 | /* warning( "Emulation error (illegal/unsupported instruction)" ); */ | ||
201 | this->cpu.r.pc = (this->cpu.r.pc + 1) & 0xFFFF; | ||
202 | Cpu_set_time( &this->cpu, Cpu_time( &this->cpu ) + 6 ); | ||
203 | } | ||
204 | } | ||
205 | |||
206 | return 0; | ||
207 | } | ||
208 | |||
209 | static blargg_err_t end_frame( struct Gbs_Emu* this, int end ) | ||
210 | { | ||
211 | RETURN_ERR( run_until( this, end ) ); | ||
212 | |||
213 | this->next_play -= end; | ||
214 | if ( this->next_play < 0 ) // happens when play routine takes too long | ||
215 | { | ||
216 | #if !defined(GBS_IGNORE_STARVED_PLAY) | ||
217 | check( false ); | ||
218 | #endif | ||
219 | this->next_play = 0; | ||
220 | } | ||
221 | |||
222 | Apu_end_frame( &this->apu, end ); | ||
223 | |||
224 | return 0; | ||
225 | } | ||
226 | |||
227 | blargg_err_t run_clocks( struct Gbs_Emu* this, blip_time_t duration ) | ||
228 | { | ||
229 | return end_frame( this, duration ); | ||
230 | } | ||
231 | |||
232 | blargg_err_t play_( void* emu, int count, sample_t* out ) | ||
233 | { | ||
234 | struct Gbs_Emu* this = (struct Gbs_Emu*) emu; | ||
235 | |||
236 | int remain = count; | ||
237 | while ( remain ) | ||
238 | { | ||
239 | Buffer_disable_immediate_removal( &this->stereo_buf ); | ||
240 | remain -= Buffer_read_samples( &this->stereo_buf, &out [count - remain], remain ); | ||
241 | if ( remain ) | ||
242 | { | ||
243 | if ( this->buf_changed_count != Buffer_channels_changed_count( &this->stereo_buf ) ) | ||
244 | { | ||
245 | this->buf_changed_count = Buffer_channels_changed_count( &this->stereo_buf ); | ||
246 | |||
247 | // Remute voices | ||
248 | Sound_mute_voices( this, this->mute_mask_ ); | ||
249 | } | ||
250 | int msec = Buffer_length( &this->stereo_buf ); | ||
251 | blip_time_t clocks_emulated = msec * this->clock_rate_ / 1000 - 100; | ||
252 | RETURN_ERR( run_clocks( this, clocks_emulated ) ); | ||
253 | assert( clocks_emulated ); | ||
254 | Buffer_end_frame( &this->stereo_buf, clocks_emulated ); | ||
255 | } | ||
256 | } | ||
257 | return 0; | ||
258 | } | ||
259 | |||
260 | blargg_err_t Gbs_set_sample_rate( struct Gbs_Emu* this, int rate ) | ||
261 | { | ||
262 | require( !this->sample_rate_ ); // sample rate can't be changed once set | ||
263 | Buffer_init( &this->stereo_buf ); | ||
264 | RETURN_ERR( Buffer_set_sample_rate( &this->stereo_buf, rate, 1000 / 20 ) ); | ||
265 | |||
266 | // Set bass frequency | ||
267 | Buffer_bass_freq( &this->stereo_buf, 300 ); | ||
268 | |||
269 | this->sample_rate_ = rate; | ||
270 | RETURN_ERR( track_init( &this->track_filter, this ) ); | ||
271 | this->tfilter.max_silence = 6 * stereo * this->sample_rate_; | ||
272 | return 0; | ||
273 | } | ||
274 | |||
275 | |||
276 | // Sound | ||
277 | |||
278 | void Sound_mute_voice( struct Gbs_Emu* this, int index, bool mute ) | ||
279 | { | ||
280 | require( (unsigned) index < (unsigned) this->voice_count_ ); | ||
281 | int bit = 1 << index; | ||
282 | int mask = this->mute_mask_ | bit; | ||
283 | if ( !mute ) | ||
284 | mask ^= bit; | ||
285 | Sound_mute_voices( this, mask ); | ||
286 | } | ||
287 | |||
288 | void Sound_mute_voices( struct Gbs_Emu* this, int mask ) | ||
289 | { | ||
290 | require( this->sample_rate_ ); // sample rate must be set first | ||
291 | this->mute_mask_ = mask; | ||
292 | |||
293 | int i; | ||
294 | for ( i = this->voice_count_; i--; ) | ||
295 | { | ||
296 | if ( mask & (1 << i) ) | ||
297 | { | ||
298 | Apu_set_output( &this->apu, i, 0, 0, 0 ); | ||
299 | } | ||
300 | else | ||
301 | { | ||
302 | struct channel_t ch = Buffer_channel( &this->stereo_buf, i ); | ||
303 | assert( (ch.center && ch.left && ch.right) || | ||
304 | (!ch.center && !ch.left && !ch.right) ); // all or nothing | ||
305 | Apu_set_output( &this->apu, i, ch.center, ch.left, ch.right ); | ||
306 | } | ||
307 | } | ||
308 | } | ||
309 | |||
310 | void Sound_set_tempo( struct Gbs_Emu* this, int t ) | ||
311 | { | ||
312 | require( this->sample_rate_ ); // sample rate must be set first | ||
313 | int const min = (int)(FP_ONE_TEMPO*0.02); | ||
314 | int const max = (int)(FP_ONE_TEMPO*4.00); | ||
315 | if ( t < min ) t = min; | ||
316 | if ( t > max ) t = max; | ||
317 | this->tempo_ = t; | ||
318 | |||
319 | this->tempo = (int) ((tempo_unit * FP_ONE_TEMPO) / t); | ||
320 | Apu_set_tempo( &this->apu, t ); | ||
321 | update_timer( this ); | ||
322 | } | ||
323 | |||
324 | blargg_err_t Gbs_start_track( struct Gbs_Emu* this, int track ) | ||
325 | { | ||
326 | clear_track_vars( this ); | ||
327 | |||
328 | // Remap track if playlist available | ||
329 | if ( this->m3u.size > 0 ) { | ||
330 | struct entry_t* e = &this->m3u.entries[track]; | ||
331 | track = e->track; | ||
332 | } | ||
333 | |||
334 | this->current_track_ = track; | ||
335 | Buffer_clear( &this->stereo_buf ); | ||
336 | |||
337 | // Reset APU to state expected by most rips | ||
338 | static byte const sound_data [] = { | ||
339 | 0x80, 0xBF, 0x00, 0x00, 0xB8, // square 1 DAC disabled | ||
340 | 0x00, 0x3F, 0x00, 0x00, 0xB8, // square 2 DAC disabled | ||
341 | 0x7F, 0xFF, 0x9F, 0x00, 0xB8, // wave DAC disabled | ||
342 | 0x00, 0xFF, 0x00, 0x00, 0xB8, // noise DAC disabled | ||
343 | 0x77, 0xFF, 0x80, // max volume, all chans in center, power on | ||
344 | }; | ||
345 | |||
346 | enum sound_t mode = this->sound_hardware; | ||
347 | if ( mode == sound_gbs ) | ||
348 | mode = (this->header.timer_mode & 0x80) ? sound_cgb : sound_dmg; | ||
349 | |||
350 | Apu_reset( &this->apu, (enum gb_mode_t) mode, false ); | ||
351 | Apu_write_register( &this->apu, 0, 0xFF26, 0x80 ); // power on | ||
352 | int i; | ||
353 | for ( i = 0; i < (int) sizeof sound_data; i++ ) | ||
354 | Apu_write_register( &this->apu, 0, i + io_addr, sound_data [i] ); | ||
355 | Apu_end_frame( &this->apu, 1 ); // necessary to get click out of the way */ | ||
356 | |||
357 | memset( this->ram, 0, 0x4000 ); | ||
358 | memset( this->ram + 0x4000, 0xFF, 0x1F80 ); | ||
359 | memset( this->ram + 0x5F80, 0, sizeof this->ram - 0x5F80 ); | ||
360 | this->ram [hi_page] = 0; // joypad reads back as 0 | ||
361 | this->ram [idle_addr - ram_addr] = 0xED; // illegal instruction | ||
362 | this->ram [hi_page + 6] = this->header.timer_modulo; | ||
363 | this->ram [hi_page + 7] = this->header.timer_mode; | ||
364 | |||
365 | Cpu_reset( &this->cpu, this->rom.unmapped ); | ||
366 | Cpu_map_code( &this->cpu, ram_addr, 0x10000 - ram_addr, this->ram ); | ||
367 | Cpu_map_code( &this->cpu, 0, this->rom.bank_size, Rom_at_addr( &this->rom, 0 ) ); | ||
368 | set_bank( this, this->rom.size > this->rom.bank_size ); | ||
369 | |||
370 | update_timer( this ); | ||
371 | this->next_play = this->play_period; | ||
372 | this->cpu.r.rp.fa = track; | ||
373 | this->cpu.r.sp = get_le16( this->header.stack_ptr ); | ||
374 | this->cpu_time = 0; | ||
375 | jsr_then_stop( this, this->header.init_addr ); | ||
376 | |||
377 | // convert filter times to samples | ||
378 | struct setup_t s = this->tfilter; | ||
379 | s.max_initial *= this->sample_rate_ * stereo; | ||
380 | #ifdef GME_DISABLE_SILENCE_LOOKAHEAD | ||
381 | s.lookahead = 1; | ||
382 | #endif | ||
383 | track_setup( &this->track_filter, &s ); | ||
384 | |||
385 | return track_start( &this->track_filter ); | ||
386 | } | ||
387 | |||
388 | |||
389 | // Track | ||
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 Gbs_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 Gbs_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( Gbs_start_track( this, this->current_track_ ) ); | ||
410 | return Track_skip( this, time - track_sample_count( &this->track_filter ) ); | ||
411 | } | ||
412 | |||
413 | blargg_err_t skip_( void* emu, int count ) | ||
414 | { | ||
415 | struct Gbs_Emu* this = (struct Gbs_Emu*) emu; | ||
416 | |||
417 | // for long skip, mute sound | ||
418 | const int threshold = 32768; | ||
419 | if ( count > threshold ) | ||
420 | { | ||
421 | int saved_mute = this->mute_mask_; | ||
422 | Sound_mute_voices( this, ~0 ); | ||
423 | |||
424 | int n = count - threshold/2; | ||
425 | n &= ~(2048-1); // round to multiple of 2048 | ||
426 | count -= n; | ||
427 | RETURN_ERR( skippy_( &this->track_filter, n ) ); | ||
428 | |||
429 | Sound_mute_voices( this, saved_mute ); | ||
430 | } | ||
431 | |||
432 | return skippy_( &this->track_filter, count ); | ||
433 | } | ||
434 | |||
435 | blargg_err_t Track_skip( struct Gbs_Emu* this, int count ) | ||
436 | { | ||
437 | require( this->current_track_ >= 0 ); // start_track() must have been called already | ||
438 | return track_skip( &this->track_filter, count ); | ||
439 | } | ||
440 | |||
441 | void Track_set_fade( struct Gbs_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 Gbs_play( struct Gbs_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 | } | ||