From 13cbade08a07296d92e7a7d3e20475de0032cba1 Mon Sep 17 00:00:00 2001 From: Andree Buschmann Date: Wed, 31 Aug 2011 19:19:49 +0000 Subject: Update libgme to Blargg's Game_Music_Emu 0.6-pre. git-svn-id: svn://svn.rockbox.org/rockbox/trunk@30397 a1c6a512-1295-4272-9138-f99709370657 --- apps/codecs/ay.c | 4 +- apps/codecs/gbs.c | 6 +- apps/codecs/hes.c | 6 +- apps/codecs/kss.c | 2 +- apps/codecs/libgme/AYSOURCES | 1 + apps/codecs/libgme/GBSSOURCES | 1 + apps/codecs/libgme/HESSOURCES | 1 + apps/codecs/libgme/KSSSOURCES | 1 + apps/codecs/libgme/NSFSOURCES | 1 + apps/codecs/libgme/SGCSOURCES | 1 + apps/codecs/libgme/VGMSOURCES | 1 + apps/codecs/libgme/ay_emu.c | 356 +++------- apps/codecs/libgme/ay_emu.h | 71 +- apps/codecs/libgme/blargg_common.h | 22 +- apps/codecs/libgme/blargg_endian.h | 77 +- apps/codecs/libgme/blargg_source.h | 39 +- apps/codecs/libgme/blip_buffer.c | 237 +++---- apps/codecs/libgme/blip_buffer.h | 342 +++++---- apps/codecs/libgme/gbs_cpu.c | 32 +- apps/codecs/libgme/gbs_emu.c | 355 +++------- apps/codecs/libgme/gbs_emu.h | 82 +-- apps/codecs/libgme/gme_types.h | 21 - apps/codecs/libgme/hes_apu.c | 306 ++++---- apps/codecs/libgme/hes_apu.h | 43 +- apps/codecs/libgme/hes_apu_adpcm.h | 4 +- apps/codecs/libgme/hes_cpu.c | 1356 +++--------------------------------- apps/codecs/libgme/hes_cpu.h | 119 ++-- apps/codecs/libgme/hes_cpu_io.h | 72 -- apps/codecs/libgme/hes_cpu_run.h | 1344 +++++++++++++++++++++++++++++++++++ apps/codecs/libgme/hes_emu.c | 581 +++++---------- apps/codecs/libgme/hes_emu.h | 156 ++--- apps/codecs/libgme/kss_emu.c | 373 +++------- apps/codecs/libgme/kss_emu.h | 69 +- apps/codecs/libgme/kss_scc_apu.h | 2 +- apps/codecs/libgme/multi_buffer.c | 390 ++++++----- apps/codecs/libgme/multi_buffer.h | 108 ++- apps/codecs/libgme/nes_apu.c | 24 +- apps/codecs/libgme/nes_apu.h | 43 +- apps/codecs/libgme/nes_cpu_io.h | 94 --- apps/codecs/libgme/nes_fme7_apu.c | 7 +- apps/codecs/libgme/nes_fme7_apu.h | 2 +- apps/codecs/libgme/nes_namco_apu.c | 15 +- apps/codecs/libgme/nes_namco_apu.h | 4 +- apps/codecs/libgme/nes_oscs.c | 73 +- apps/codecs/libgme/nes_oscs.h | 18 +- apps/codecs/libgme/nes_vrc6_apu.c | 2 +- apps/codecs/libgme/nes_vrc6_apu.h | 4 +- apps/codecs/libgme/nsf_emu.c | 496 ++++--------- apps/codecs/libgme/nsf_emu.h | 104 +-- apps/codecs/libgme/nsfe_info.c | 10 +- apps/codecs/libgme/opl_apu.h | 4 +- apps/codecs/libgme/resampler.c | 22 +- apps/codecs/libgme/resampler.h | 13 +- apps/codecs/libgme/rom_data.c | 8 +- apps/codecs/libgme/rom_data.h | 28 +- apps/codecs/libgme/sgc_emu.c | 319 ++------- apps/codecs/libgme/sgc_emu.h | 76 +- apps/codecs/libgme/sms_apu.c | 4 - apps/codecs/libgme/sms_apu.h | 4 +- apps/codecs/libgme/sms_fm_apu.h | 8 +- apps/codecs/libgme/track_filter.c | 294 ++++++++ apps/codecs/libgme/track_filter.h | 90 +++ apps/codecs/libgme/vgm_emu.c | 388 +++-------- apps/codecs/libgme/vgm_emu.h | 103 ++- apps/codecs/libgme/ym2612_emu.c | 1 - apps/codecs/nsf.c | 6 +- apps/codecs/sgc.c | 4 +- apps/codecs/vgm.c | 2 +- 68 files changed, 4036 insertions(+), 4816 deletions(-) delete mode 100644 apps/codecs/libgme/gme_types.h delete mode 100644 apps/codecs/libgme/hes_cpu_io.h create mode 100644 apps/codecs/libgme/hes_cpu_run.h delete mode 100644 apps/codecs/libgme/nes_cpu_io.h create mode 100644 apps/codecs/libgme/track_filter.c create mode 100644 apps/codecs/libgme/track_filter.h diff --git a/apps/codecs/ay.c b/apps/codecs/ay.c index 914ffa9803..b11ad84294 100644 --- a/apps/codecs/ay.c +++ b/apps/codecs/ay.c @@ -74,7 +74,7 @@ enum codec_status codec_run(void) } if ((err = Ay_load_mem(&ay_emu, buf, ci->filesize))) { - DEBUGF("AY: Ay_load failed (%s)\n", err); + DEBUGF("AY: Ay_load_mem failed (%s)\n", err); return CODEC_ERROR; } @@ -118,7 +118,7 @@ next_track: /* Generate audio buffer */ err = Ay_play(&ay_emu, CHUNK_SIZE, samples); - if (err || ay_emu.track_ended) { + if (err || Track_ended(&ay_emu)) { track++; if (track >= ay_emu.track_count) break; goto next_track; diff --git a/apps/codecs/gbs.c b/apps/codecs/gbs.c index c9c3420683..def05ed351 100644 --- a/apps/codecs/gbs.c +++ b/apps/codecs/gbs.c @@ -67,8 +67,8 @@ enum codec_status codec_run(void) return CODEC_ERROR; } - if ((err = Gbs_load(&gbs_emu, buf, ci->filesize))) { - DEBUGF("GBS: Gbs_load failed (%s)\n", err); + if ((err = Gbs_load_mem(&gbs_emu, buf, ci->filesize))) { + DEBUGF("GBS: Gbs_load_mem failed (%s)\n", err); return CODEC_ERROR; } @@ -95,7 +95,7 @@ next_track: /* Generate audio buffer */ err = Gbs_play(&gbs_emu, CHUNK_SIZE, samples); - if (err || gbs_emu.track_ended) { + if (err || Track_ended(&gbs_emu)) { track++; if (track >= gbs_emu.track_count) break; goto next_track; diff --git a/apps/codecs/hes.c b/apps/codecs/hes.c index c84134c01b..849fd88f12 100644 --- a/apps/codecs/hes.c +++ b/apps/codecs/hes.c @@ -67,8 +67,8 @@ enum codec_status codec_run(void) return CODEC_ERROR; } - if ((err = Hes_load(&hes_emu, buf, ci->filesize))) { - DEBUGF("HES: Hes_load failed (%s)\n", err); + if ((err = Hes_load_mem(&hes_emu, buf, ci->filesize))) { + DEBUGF("HES: Hes_load_mem failed (%s)\n", err); return CODEC_ERROR; } @@ -95,7 +95,7 @@ next_track: /* Generate audio buffer */ err = Hes_play(&hes_emu, CHUNK_SIZE, samples); - if (err || hes_emu.track_ended) { + if (err || Track_ended(&hes_emu)) { track++; if (track >= hes_emu.track_count) break; goto next_track; diff --git a/apps/codecs/kss.c b/apps/codecs/kss.c index f519b3c706..92efcd4e5f 100644 --- a/apps/codecs/kss.c +++ b/apps/codecs/kss.c @@ -98,7 +98,7 @@ next_track: /* Generate audio buffer */ err = Kss_play(&kss_emu, CHUNK_SIZE, samples); - if (err || kss_emu.track_ended) { + if (err || Track_ended(&kss_emu)) { track++; if (track >= kss_emu.track_count) break; goto next_track; diff --git a/apps/codecs/libgme/AYSOURCES b/apps/codecs/libgme/AYSOURCES index 51253fe2f1..f2b52a51d9 100644 --- a/apps/codecs/libgme/AYSOURCES +++ b/apps/codecs/libgme/AYSOURCES @@ -3,4 +3,5 @@ ay_cpu.c ay_emu.c blip_buffer.c multi_buffer.c +track_filter.c z80_cpu.c diff --git a/apps/codecs/libgme/GBSSOURCES b/apps/codecs/libgme/GBSSOURCES index 5548fd85eb..a839a2156e 100644 --- a/apps/codecs/libgme/GBSSOURCES +++ b/apps/codecs/libgme/GBSSOURCES @@ -6,3 +6,4 @@ gbs_emu.c blip_buffer.c multi_buffer.c rom_data.c +track_filter.c diff --git a/apps/codecs/libgme/HESSOURCES b/apps/codecs/libgme/HESSOURCES index 58a38f2f5a..d529388978 100644 --- a/apps/codecs/libgme/HESSOURCES +++ b/apps/codecs/libgme/HESSOURCES @@ -5,3 +5,4 @@ hes_emu.c blip_buffer.c multi_buffer.c rom_data.c +track_filter.c diff --git a/apps/codecs/libgme/KSSSOURCES b/apps/codecs/libgme/KSSSOURCES index 2607c7364c..bde213364b 100644 --- a/apps/codecs/libgme/KSSSOURCES +++ b/apps/codecs/libgme/KSSSOURCES @@ -9,3 +9,4 @@ multi_buffer.c rom_data.c emu8950.c emuadpcm.c +track_filter.c diff --git a/apps/codecs/libgme/NSFSOURCES b/apps/codecs/libgme/NSFSOURCES index 54b4f82f60..60537ff8e4 100644 --- a/apps/codecs/libgme/NSFSOURCES +++ b/apps/codecs/libgme/NSFSOURCES @@ -12,3 +12,4 @@ nsfe_info.c blip_buffer.c multi_buffer.c rom_data.c +track_filter.c diff --git a/apps/codecs/libgme/SGCSOURCES b/apps/codecs/libgme/SGCSOURCES index d91c0e1731..d0e8abc2b7 100644 --- a/apps/codecs/libgme/SGCSOURCES +++ b/apps/codecs/libgme/SGCSOURCES @@ -4,3 +4,4 @@ z80_cpu.c blip_buffer.c multi_buffer.c rom_data.c +track_filter.c diff --git a/apps/codecs/libgme/VGMSOURCES b/apps/codecs/libgme/VGMSOURCES index 637f87e8b0..bb57e16e82 100644 --- a/apps/codecs/libgme/VGMSOURCES +++ b/apps/codecs/libgme/VGMSOURCES @@ -3,6 +3,7 @@ multi_buffer.c resampler.c vgm_emu.c ym2612_emu.c +track_filter.c inflate/bbfuncs.c inflate/inflate.c inflate/mallocer.c diff --git a/apps/codecs/libgme/ay_emu.c b/apps/codecs/libgme/ay_emu.c index e961797dd3..42e739f2df 100644 --- a/apps/codecs/libgme/ay_emu.c +++ b/apps/codecs/libgme/ay_emu.c @@ -17,12 +17,6 @@ Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "blargg_source.h" -int const stereo = 2; // number of channels for stereo -int const silence_max = 6; // seconds -int const silence_threshold = 0x10; -long const fade_block_size = 512; -int const fade_shift = 8; // fade ends with gain at 1.0 / (1 << fade_shift) - const char* const gme_wrong_file_type = "Wrong file type for this emulator"; // TODO: probably don't need detailed errors as to why file is corrupt @@ -38,16 +32,7 @@ int const cpc_clock = 2000000; static void clear_track_vars( struct Ay_Emu *this ) { this->current_track = -1; - this->out_time = 0; - this->emu_time = 0; - this->emu_track_ended_ = true; - this->track_ended = true; - this->fade_start = INT_MAX / 2 + 1; - this->fade_step = 1; - this->silence_time = 0; - this->silence_count = 0; - this->buf_remain = 0; - /* warning(); // clear warning */ + track_stop( &this->track_filter ); } void Ay_init( struct Ay_Emu *this ) @@ -59,18 +44,21 @@ void Ay_init( struct Ay_Emu *this ) this->track_count = 0; // defaults - this->max_initial_silence = 2; - this->ignore_silence = false; + this->tfilter = *track_get_setup( &this->track_filter ); + this->tfilter.max_initial = 2; + this->tfilter.lookahead = 6; + this->track_filter.silence_ignored_ = false; - this->voice_count = 0; - clear_track_vars( this ); this->beeper_output = NULL; disable_beeper( this ); Ay_apu_init( &this->apu ); Z80_init( &this->cpu ); - - this->silence_lookahead = 6 ; + + // clears fields + this->voice_count = 0; + this->voice_types = 0; + clear_track_vars( this ); } // Track info @@ -107,35 +95,22 @@ static blargg_err_t parse_header( byte const in [], int size, struct file_t* out return 0; } -long Track_get_length( struct Ay_Emu* this, int n ) -{ - long length = 0; - - byte const* track_info = get_data( &this->file, this->file.tracks + n * 4 + 2, 6 ); - if ( track_info ) - length = get_be16( track_info + 4 ) * (1000 / 50); // frames to msec - - if ( (this->m3u.size > 0) && (n < this->m3u.size) ) { - struct entry_t* entry = &this->m3u.entries [n]; - length = entry->length; - } - - if ( length <= 0 ) - length = 120 * 1000; /* 2 minutes */ - - return length; -} - // Setup -static void change_clock_rate( struct Ay_Emu *this, long rate ) +static void change_clock_rate( struct Ay_Emu *this, int rate ) { this->clock_rate_ = rate; Buffer_clock_rate( &this->stereo_buf, rate ); } -blargg_err_t Ay_load_mem( struct Ay_Emu *this, byte const in [], int size ) +blargg_err_t Ay_load_mem( struct Ay_Emu *this, byte const in [], long size ) { + // Unload + this->voice_count = 0; + this->track_count = 0; + this->m3u.size = 0; + clear_track_vars( this ); + assert( offsetof (struct header_t,track_info [2]) == header_size ); RETURN_ERR( parse_header( in, size, &this->file ) ); @@ -144,19 +119,22 @@ blargg_err_t Ay_load_mem( struct Ay_Emu *this, byte const in [], int size ) warning( "Unknown file version" ); */ this->voice_count = ay_osc_count + 1; // +1 for beeper + static int const types [ay_osc_count + 1] = { + wave_type+0, wave_type+1, wave_type+2, mixed_type+1 + }; + this->voice_types = types; + Ay_apu_volume( &this->apu, this->gain); // Setup buffer change_clock_rate( this, spectrum_clock ); + RETURN_ERR( Buffer_set_channel_count( &this->stereo_buf, this->voice_count, this->voice_types ) ); this->buf_changed_count = Buffer_channels_changed_count( &this->stereo_buf ); Sound_set_tempo( this, this->tempo ); - - // Remute voices Sound_mute_voices( this, this->mute_mask_ ); this->track_count = this->file.header->max_track + 1; - this->m3u.size = 0; return 0; } @@ -298,7 +276,7 @@ enable_cpc: } } -blargg_err_t Ay_set_sample_rate( struct Ay_Emu *this, long rate ) +blargg_err_t Ay_set_sample_rate( struct Ay_Emu *this, int rate ) { require( !this->sample_rate ); // sample rate can't be changed once set Buffer_init( &this->stereo_buf ); @@ -308,6 +286,8 @@ blargg_err_t Ay_set_sample_rate( struct Ay_Emu *this, long rate ) Buffer_bass_freq( &this->stereo_buf, 160 ); this->sample_rate = rate; + RETURN_ERR( track_init( &this->track_filter, this ) ); + this->tfilter.max_silence = 6 * stereo * this->sample_rate; return 0; } @@ -335,7 +315,7 @@ void Sound_mute_voices( struct Ay_Emu *this, int mask ) } else { - struct channel_t ch = Buffer_channel( &this->stereo_buf ); + struct channel_t ch = Buffer_channel( &this->stereo_buf, i ); assert( (ch.center && ch.left && ch.right) || (!ch.center && !ch.left && !ch.right) ); // all or nothing set_voice( this, i, ch.center ); @@ -359,7 +339,6 @@ void Sound_set_tempo( struct Ay_Emu *this, int t ) this->play_period = (blip_time_t) ((p * FP_ONE_TEMPO) / t); } -void fill_buf( struct Ay_Emu *this );; blargg_err_t Ay_start_track( struct Ay_Emu *this, int track ) { clear_track_vars( this ); @@ -496,272 +475,109 @@ blargg_err_t Ay_start_track( struct Ay_Emu *this, int track ) Ay_apu_write_addr( &this->apu, 7 ); Ay_apu_write_data( &this->apu, 0, 0x38 ); - this->emu_track_ended_ = false; - this->track_ended = false; + // convert filter times to samples + struct setup_t s = this->tfilter; + s.max_initial *= this->sample_rate * stereo; + #ifdef GME_DISABLE_SILENCE_LOOKAHEAD + s.lookahead = 1; + #endif + track_setup( &this->track_filter, &s ); - if ( !this->ignore_silence ) - { - // play until non-silence or end of track - long end; - for ( end = this->max_initial_silence * stereo * this->sample_rate; this->emu_time < end; ) - { - fill_buf( this ); - if ( this->buf_remain | (int) this->emu_track_ended_ ) - break; - } - - this->emu_time = this->buf_remain; - this->out_time = 0; - this->silence_time = 0; - this->silence_count = 0; - } - /* return track_ended() ? warning() : 0; */ - return 0; + return track_start( &this->track_filter ); } // Tell/Seek -static blargg_long msec_to_samples( blargg_long msec, long sample_rate ) +static int msec_to_samples( int msec, int sample_rate ) { - blargg_long sec = msec / 1000; + int sec = msec / 1000; msec -= sec * 1000; return (sec * sample_rate + msec * sample_rate / 1000) * stereo; } -long Track_tell( struct Ay_Emu *this ) +int Track_tell( struct Ay_Emu *this ) { - blargg_long rate = this->sample_rate * stereo; - blargg_long sec = this->out_time / rate; - return sec * 1000 + (this->out_time - sec * rate) * 1000 / rate; + int rate = this->sample_rate * stereo; + int sec = track_sample_count( &this->track_filter ) / rate; + return sec * 1000 + (track_sample_count( &this->track_filter ) - sec * rate) * 1000 / rate; } -blargg_err_t Track_seek( struct Ay_Emu *this, long msec ) +blargg_err_t Track_seek( struct Ay_Emu *this, int msec ) { - blargg_long time = msec_to_samples( msec, this->sample_rate ); - if ( time < this->out_time ) - RETURN_ERR( Ay_start_track( this, this->current_track ) ); - return Track_skip( this, time - this->out_time ); + int time = msec_to_samples( msec, this->sample_rate ); + if ( time < track_sample_count( &this->track_filter ) ) + RETURN_ERR( Ay_start_track( this, this->current_track ) ); + return Track_skip( this, time - track_sample_count( &this->track_filter ) ); } -blargg_err_t play_( struct Ay_Emu *this, long count, sample_t* out ); -static blargg_err_t skip_( struct Ay_Emu *this, long count ) +blargg_err_t skip_( void *emu, int count ) { + struct Ay_Emu* this = (struct Ay_Emu*) emu; + // for long skip, mute sound - const long threshold = 30000; + const int threshold = 32768; if ( count > threshold ) { int saved_mute = this->mute_mask_; Sound_mute_voices( this, ~0 ); - - while ( count > threshold / 2 && !this->emu_track_ended_ ) - { - RETURN_ERR( play_( this, buf_size, this->buf ) ); - count -= buf_size; - } - - Sound_mute_voices( this, saved_mute ); - } - - while ( count && !this->emu_track_ended_ ) - { - long n = buf_size; - if ( n > count ) - n = count; - count -= n; - RETURN_ERR( play_( this, n, this->buf ) ); - } - return 0; -} -blargg_err_t Track_skip( struct Ay_Emu *this, long count ) -{ - require( this->current_track >= 0 ); // start_track() must have been called already - this->out_time += count; - - // remove from silence and buf first - { - long n = min( count, this->silence_count ); - this->silence_count -= n; + int n = count - threshold/2; + n &= ~(2048-1); // round to multiple of 2048 count -= n; - - n = min( count, this->buf_remain ); - this->buf_remain -= n; - count -= n; - } - - if ( count && !this->emu_track_ended_ ) - { - this->emu_time += count; - - // End track if error - if ( skip_( this, count ) ) - this->emu_track_ended_ = true; - } - - if ( !(this->silence_count | this->buf_remain) ) // caught up to emulator, so update track ended - this->track_ended |= this->emu_track_ended_; - - return 0; -} + RETURN_ERR( skippy_( &this->track_filter, n ) ); -// Fading + Sound_mute_voices( this, saved_mute ); + } -void Track_set_fade( struct Ay_Emu *this, long start_msec, long length_msec ) -{ - this->fade_step = this->sample_rate * length_msec / (fade_block_size * fade_shift * 1000 / stereo); - this->fade_start = msec_to_samples( start_msec, this->sample_rate ); + return skippy_( &this->track_filter, count ); } -// unit / pow( 2.0, (double) x / step ) -static int int_log( blargg_long x, int step, int unit ) +blargg_err_t Track_skip( struct Ay_Emu *this, int count ) { - int shift = x / step; - int fraction = (x - shift * step) * unit / step; - return ((unit - fraction) + (fraction >> 1)) >> shift; + require( this->current_track >= 0 ); // start_track() must have been called already + return track_skip( &this->track_filter, count ); } -static void handle_fade( struct Ay_Emu *this, long out_count, sample_t* out ) +int Track_get_length( struct Ay_Emu* this, int n ) { - int i; - for ( i = 0; i < out_count; i += fade_block_size ) - { - int const shift = 14; - int const unit = 1 << shift; - int gain = int_log( (this->out_time + i - this->fade_start) / fade_block_size, - this->fade_step, unit ); - if ( gain < (unit >> fade_shift) ) - this->track_ended = this->emu_track_ended_ = true; - - sample_t* io = &out [i]; - int count; - for ( count = min( fade_block_size, out_count - i ); count; --count ) - { - *io = (sample_t) ((*io * gain) >> shift); - ++io; - } - } -} + int length = 0; -// Silence detection + byte const* track_info = get_data( &this->file, this->file.tracks + n * 4 + 2, 6 ); + if ( track_info ) + length = get_be16( track_info + 4 ) * (1000 / 50); // frames to msec -static void emu_play( struct Ay_Emu *this, long count, sample_t* out ) -{ - check( current_track_ >= 0 ); - this->emu_time += count; - if ( this->current_track >= 0 && !this->emu_track_ended_ ) { - if ( play_( this, count, out ) ) - this->emu_track_ended_ = true; - } - else - memset( out, 0, count * sizeof *out ); -} + if ( (this->m3u.size > 0) && (n < this->m3u.size) ) { + struct entry_t* entry = &this->m3u.entries [n]; + length = entry->length; + } + + if ( length <= 0 ) + length = 120 * 1000; /* 2 minutes */ -// number of consecutive silent samples at end -static long count_silence( sample_t* begin, long size ) -{ - sample_t first = *begin; - *begin = silence_threshold; // sentinel - sample_t* p = begin + size; - while ( (unsigned) (*--p + silence_threshold / 2) <= (unsigned) silence_threshold ) { } - *begin = first; - return size - (p - begin); + return length; } -// fill internal buffer and check it for silence -void fill_buf( struct Ay_Emu *this ) +void Track_set_fade( struct Ay_Emu *this, int start_msec, int length_msec ) { - assert( !this->buf_remain ); - if ( !this->emu_track_ended_ ) - { - emu_play( this, buf_size, this->buf ); - long silence = count_silence( this->buf, buf_size ); - if ( silence < buf_size ) - { - this->silence_time = this->emu_time - silence; - this->buf_remain = buf_size; - return; - } - } - this->silence_count += buf_size; + track_set_fade( &this->track_filter, msec_to_samples( start_msec, this->sample_rate ), + length_msec * this->sample_rate / (1000 / stereo) ); } -blargg_err_t Ay_play( struct Ay_Emu *this, long out_count, sample_t* out ) +blargg_err_t Ay_play( struct Ay_Emu *this, int out_count, sample_t* out ) { - if ( this->track_ended ) - { - memset( out, 0, out_count * sizeof *out ); - } - else - { - require( this->current_track >= 0 ); - require( out_count % stereo == 0 ); - - assert( this->emu_time >= this->out_time ); - - // prints nifty graph of how far ahead we are when searching for silence - //debug_printf( "%*s \n", int ((emu_time - out_time) * 7 / sample_rate()), "*" ); - - long pos = 0; - if ( this->silence_count ) - { - // during a run of silence, run emulator at >=2x speed so it gets ahead - long ahead_time = this->silence_lookahead * (this->out_time + out_count - this->silence_time) + this->silence_time; - while ( this->emu_time < ahead_time && !(this->buf_remain | this->emu_track_ended_) ) - fill_buf( this ); - - // fill with silence - pos = min( this->silence_count, out_count ); - memset( out, 0, pos * sizeof *out ); - this->silence_count -= pos; - - if ( this->emu_time - this->silence_time > silence_max * stereo * this->sample_rate ) - { - this->track_ended = this->emu_track_ended_ = true; - this->silence_count = 0; - this->buf_remain = 0; - } - } - - if ( this->buf_remain ) - { - // empty silence buf - long n = min( this->buf_remain, out_count - pos ); - memcpy( &out [pos], this->buf + (buf_size - this->buf_remain), n * sizeof *out ); - this->buf_remain -= n; - pos += n; - } - - // generate remaining samples normally - long remain = out_count - pos; - if ( remain ) - { - emu_play( this, remain, out + pos ); - this->track_ended |= this->emu_track_ended_; - - if ( !this->ignore_silence || this->out_time > this->fade_start ) - { - // check end for a new run of silence - long silence = count_silence( out + pos, remain ); - if ( silence < remain ) - this->silence_time = this->emu_time - silence; - - if ( this->emu_time - this->silence_time >= buf_size ) - fill_buf( this ); // cause silence detection on next play() - } - } - - if ( this->out_time > this->fade_start ) - handle_fade( this, out_count, out ); - } - this->out_time += out_count; - return 0; + require( this->current_track >= 0 ); + require( out_count % stereo == 0 ); + return track_play( &this->track_filter, out_count, out ); } -blargg_err_t play_( struct Ay_Emu *this, long count, sample_t* out ) +blargg_err_t play_( void *emu, int count, sample_t* out ) { - long remain = count; + struct Ay_Emu* this = (struct Ay_Emu*) emu; + + int remain = count; while ( remain ) { + Buffer_disable_immediate_removal( &this->stereo_buf ); remain -= Buffer_read_samples( &this->stereo_buf, &out [count - remain], remain ); if ( remain ) { @@ -773,7 +589,7 @@ blargg_err_t play_( struct Ay_Emu *this, long count, sample_t* out ) Sound_mute_voices( this, this->mute_mask_ ); } int msec = Buffer_length( &this->stereo_buf ); - blip_time_t clocks_emulated = (blargg_long) msec * this->clock_rate_ / 1000 - 100; + blip_time_t clocks_emulated = msec * this->clock_rate_ / 1000 - 100; RETURN_ERR( run_clocks( this, &clocks_emulated, msec ) ); assert( clocks_emulated ); Buffer_end_frame( &this->stereo_buf, clocks_emulated ); diff --git a/apps/codecs/libgme/ay_emu.h b/apps/codecs/libgme/ay_emu.h index b320e69653..7334167876 100644 --- a/apps/codecs/libgme/ay_emu.h +++ b/apps/codecs/libgme/ay_emu.h @@ -10,14 +10,12 @@ #include "z80_cpu.h" #include "ay_apu.h" #include "m3u_playlist.h" - -typedef short sample_t; +#include "track_filter.h" // 64K memory to load code and data into before starting track. Caller // must parse the AY file. enum { mem_size = 0x10000 }; enum { ram_addr = 0x4000 }; // where official RAM starts -enum { buf_size = 2048 }; // AY file header enum { header_size = 0x14 }; @@ -62,43 +60,30 @@ struct Ay_Emu { bool cpc_mode; // general - int max_initial_silence; int voice_count; + int const* voice_types; int mute_mask_; int tempo; int gain; - long sample_rate; + int sample_rate; // track-specific int current_track; int track_count; - blargg_long out_time; // number of samples played since start of track - blargg_long emu_time; // number of samples emulator has generated since start of track - volatile bool track_ended; - bool emu_track_ended_; // emulator has reached end of track - - // fading - blargg_long fade_start; - int fade_step; - - // silence detection - bool ignore_silence; - int silence_lookahead; // speed to run emulator when looking ahead for silence - long silence_time; // number of samples where most recent silence began - long silence_count; // number of samples of silence to play before using buf - long buf_remain; // number of samples left in silence buffer - - long clock_rate_; + + int clock_rate_; unsigned buf_changed_count; // M3u Playlist struct M3u_Playlist m3u; // large items + struct setup_t tfilter; + struct Track_Filter track_filter; + struct Ay_Apu apu; - sample_t buf [buf_size]; - struct Stereo_Buffer stereo_buf; // NULL if using custom buffer + struct Multi_Buffer stereo_buf; // NULL if using custom buffer struct Z80_Cpu cpu; struct mem_t mem; }; @@ -106,46 +91,58 @@ struct Ay_Emu { // Basic functionality (see Gme_File.h for file loading/track info functions) void Ay_init( struct Ay_Emu* this ); -blargg_err_t Ay_load_mem( struct Ay_Emu* this, byte const in [], int size ); +blargg_err_t Ay_load_mem( struct Ay_Emu* this, byte const in [], long size ); // Set output sample rate. Must be called only once before loading file. -blargg_err_t Ay_set_sample_rate( struct Ay_Emu* this, long sample_rate ); +blargg_err_t Ay_set_sample_rate( struct Ay_Emu* this, int sample_rate ); // Start a track, where 0 is the first track. Also clears warning string. blargg_err_t Ay_start_track( struct Ay_Emu* this, int track ); // Generate 'count' samples info 'buf'. Output is in stereo. Any emulation // errors set warning string, and major errors also end track. -blargg_err_t Ay_play( struct Ay_Emu* this, long count, sample_t* buf ); +blargg_err_t Ay_play( struct Ay_Emu* this, int count, sample_t* buf ); // Track status/control // Number of milliseconds (1000 msec = 1 second) played since beginning of track -long Track_tell( struct Ay_Emu* this ); +int Track_tell( struct Ay_Emu* this ); // Seek to new time in track. Seeking backwards or far forward can take a while. -blargg_err_t Track_seek( struct Ay_Emu* this, long msec ); - +blargg_err_t Track_seek( struct Ay_Emu* this, int msec ); + // Skip n samples -blargg_err_t Track_skip( struct Ay_Emu* this, long n ); - +blargg_err_t Track_skip( struct Ay_Emu* this, int n ); + // Set start time and length of track fade out. Once fade ends track_ended() returns // true. Fade time can be changed while track is playing. -void Track_set_fade( struct Ay_Emu* this, long start_msec, long length_msec ); +void Track_set_fade( struct Ay_Emu* this, int start_msec, int length_msec ); + +// True if a track has reached its end +static inline bool Track_ended( struct Ay_Emu* this ) +{ + return track_ended( &this->track_filter ); +} + +// Disables automatic end-of-track detection and skipping of silence at beginning +static inline void Track_ignore_silence( struct Ay_Emu* this, bool disable ) +{ + this->track_filter.silence_ignored_ = disable; +} // Get track length in milliseconds -long Track_get_length( struct Ay_Emu* this, int n ); +int Track_get_length( struct Ay_Emu* this, int n ); // Sound customization // Adjust song tempo, where 1.0 = normal, 0.5 = half speed, 2.0 = double speed. // Track length as returned by track_info() assumes a tempo of 1.0. void Sound_set_tempo( struct Ay_Emu* this, int t ); - + // Mute/unmute voice i, where voice 0 is first voice void Sound_mute_voice( struct Ay_Emu* this, int index, bool mute ); - + // Set muting state of all voices at once using a bit mask, where -1 mutes them all, // 0 unmutes them all, 0x01 mutes just the first voice, etc. void Sound_mute_voices( struct Ay_Emu* this, int mask ); @@ -168,5 +165,5 @@ static inline void disable_beeper( struct Ay_Emu *this ) this->beeper_mask = 0; this->last_beeper = 0; } - + #endif diff --git a/apps/codecs/libgme/blargg_common.h b/apps/codecs/libgme/blargg_common.h index 65ae76ae99..a023d6b842 100644 --- a/apps/codecs/libgme/blargg_common.h +++ b/apps/codecs/libgme/blargg_common.h @@ -7,12 +7,12 @@ #include #include #include -#include #include #undef BLARGG_COMMON_H // allow blargg_config.h to #include blargg_common.h #include "blargg_config.h" +#include "blargg_source.h" #ifndef BLARGG_COMMON_H #define BLARGG_COMMON_H @@ -98,19 +98,13 @@ static bool false = 0; #endif -// blargg_long/blargg_ulong = at least 32 bits, int if it's big enough -#include - -#if INT_MAX >= 0x7FFFFFFF - typedef int blargg_long; -#else - typedef long blargg_long; -#endif - -#if UINT_MAX >= 0xFFFFFFFF - typedef unsigned blargg_ulong; -#else - typedef unsigned long blargg_ulong; +/* My code depends on int being at least 32 bits. Almost everything these days +uses at least 32-bit ints, so it's hard to even find a system with 16-bit ints +to test with. The issue can't be gotten around by using a suitable blargg_int +everywhere either, because int is often converted to implicitly when doing +arithmetic on smaller types. */ +#if UINT_MAX < 0xFFFFFFFF + #error "int must be at least 32 bits" #endif // int8_t etc. diff --git a/apps/codecs/libgme/blargg_endian.h b/apps/codecs/libgme/blargg_endian.h index ae55d7fd3b..dce5cb2048 100644 --- a/apps/codecs/libgme/blargg_endian.h +++ b/apps/codecs/libgme/blargg_endian.h @@ -64,45 +64,60 @@ static inline void blargg_verify_byte_order( void ) #endif } -static inline unsigned get_le16( void const* p ) { - return ((unsigned char const*) p) [1] * 0x100u + - ((unsigned char const*) p) [0]; +static inline unsigned get_le16( void const* p ) +{ + return (unsigned) ((unsigned char const*) p) [1] << 8 | + (unsigned) ((unsigned char const*) p) [0]; } -static inline unsigned get_be16( void const* p ) { - return ((unsigned char const*) p) [0] * 0x100u + - ((unsigned char const*) p) [1]; + +static inline unsigned get_be16( void const* p ) +{ + return (unsigned) ((unsigned char const*) p) [0] << 8 | + (unsigned) ((unsigned char const*) p) [1]; } -static inline blargg_ulong get_le32( void const* p ) { - return ((unsigned char const*) p) [3] * 0x01000000u + - ((unsigned char const*) p) [2] * 0x00010000u + - ((unsigned char const*) p) [1] * 0x00000100u + - ((unsigned char const*) p) [0]; + +static inline unsigned get_le32( void const* p ) +{ + return (unsigned) ((unsigned char const*) p) [3] << 24 | + (unsigned) ((unsigned char const*) p) [2] << 16 | + (unsigned) ((unsigned char const*) p) [1] << 8 | + (unsigned) ((unsigned char const*) p) [0]; } -static inline blargg_ulong get_be32( void const* p ) { - return ((unsigned char const*) p) [0] * 0x01000000u + - ((unsigned char const*) p) [1] * 0x00010000u + - ((unsigned char const*) p) [2] * 0x00000100u + - ((unsigned char const*) p) [3]; + +static inline unsigned get_be32( void const* p ) +{ + return (unsigned) ((unsigned char const*) p) [0] << 24 | + (unsigned) ((unsigned char const*) p) [1] << 16 | + (unsigned) ((unsigned char const*) p) [2] << 8 | + (unsigned) ((unsigned char const*) p) [3]; } -static inline void set_le16( void* p, unsigned n ) { + +static inline void set_le16( void* p, unsigned n ) +{ ((unsigned char*) p) [1] = (unsigned char) (n >> 8); ((unsigned char*) p) [0] = (unsigned char) n; } -static inline void set_be16( void* p, unsigned n ) { + +static inline void set_be16( void* p, unsigned n ) +{ ((unsigned char*) p) [0] = (unsigned char) (n >> 8); ((unsigned char*) p) [1] = (unsigned char) n; } -static inline void set_le32( void* p, blargg_ulong n ) { - ((unsigned char*) p) [3] = (unsigned char) (n >> 24); - ((unsigned char*) p) [2] = (unsigned char) (n >> 16); - ((unsigned char*) p) [1] = (unsigned char) (n >> 8); + +static inline void set_le32( void* p, unsigned n ) +{ ((unsigned char*) p) [0] = (unsigned char) n; + ((unsigned char*) p) [1] = (unsigned char) (n >> 8); + ((unsigned char*) p) [2] = (unsigned char) (n >> 16); + ((unsigned char*) p) [3] = (unsigned char) (n >> 24); } -static inline void set_be32( void* p, blargg_ulong n ) { - ((unsigned char*) p) [0] = (unsigned char) (n >> 24); - ((unsigned char*) p) [1] = (unsigned char) (n >> 16); - ((unsigned char*) p) [2] = (unsigned char) (n >> 8); + +static inline void set_be32( void* p, unsigned n ) +{ ((unsigned char*) p) [3] = (unsigned char) n; + ((unsigned char*) p) [2] = (unsigned char) (n >> 8); + ((unsigned char*) p) [1] = (unsigned char) (n >> 16); + ((unsigned char*) p) [0] = (unsigned char) (n >> 24); } #if defined(BLARGG_NONPORTABLE) @@ -132,15 +147,21 @@ static inline void set_be32( void* p, blargg_ulong n ) { #ifndef GET_LE16 #define GET_LE16( addr ) get_le16( addr ) - #define GET_LE32( addr ) get_le32( addr ) #define SET_LE16( addr, data ) set_le16( addr, data ) +#endif + +#ifndef GET_LE32 + #define GET_LE32( addr ) get_le32( addr ) #define SET_LE32( addr, data ) set_le32( addr, data ) #endif #ifndef GET_BE16 #define GET_BE16( addr ) get_be16( addr ) - #define GET_BE32( addr ) get_be32( addr ) #define SET_BE16( addr, data ) set_be16( addr, data ) +#endif + +#ifndef GET_BE32 + #define GET_BE32( addr ) get_be32( addr ) #define SET_BE32( addr, data ) set_be32( addr, data ) #endif diff --git a/apps/codecs/libgme/blargg_source.h b/apps/codecs/libgme/blargg_source.h index 4bea02a48b..ab8e1b072b 100644 --- a/apps/codecs/libgme/blargg_source.h +++ b/apps/codecs/libgme/blargg_source.h @@ -2,16 +2,13 @@ #ifndef BLARGG_SOURCE_H #define BLARGG_SOURCE_H -// If debugging is enabled, abort program if expr is false. Meant for checking -// internal state and consistency. A failed assertion indicates a bug in the module. -// void assert( bool expr ); -#include - // If debugging is enabled and expr is false, abort program. Meant for checking // caller-supplied parameters and operations that are outside the control of the // module. A failed requirement indicates a bug outside the module. // void require( bool expr ); #if defined(ROCKBOX) +#undef assert +#define assert( expr ) #undef require #define require( expr ) #else @@ -36,28 +33,36 @@ static inline void blargg_dprintf_( const char* fmt, ... ) { } #undef check #define check( expr ) ((void) 0) -// If expr yields error string, return it from current function, otherwise continue. -#undef RETURN_ERR -#define RETURN_ERR( expr ) do { \ - blargg_err_t blargg_return_err_ = (expr); \ - if ( blargg_return_err_ ) return blargg_return_err_; \ +/* If expr yields non-NULL error string, returns it from current function, +otherwise continues normally. */ +#undef RETURN_ERR +#define RETURN_ERR( expr ) \ + do {\ + blargg_err_t blargg_return_err_ = (expr);\ + if ( blargg_return_err_ )\ + return blargg_return_err_;\ } while ( 0 ) -// If ptr is 0, return out of memory error string. -#undef CHECK_ALLOC -#define CHECK_ALLOC( ptr ) do { if ( (ptr) == 0 ) return "Out of memory"; } while ( 0 ) +/* If ptr is NULL, returns out-of-memory error, otherwise continues normally. */ +#undef CHECK_ALLOC +#define CHECK_ALLOC( ptr ) \ + do {\ + if ( !(ptr) )\ + return "Out of memory";\ + } while ( 0 ) #ifndef max #define max(a,b) (((a) > (b)) ? (a) : (b)) #endif + #ifndef min #define min(a,b) (((a) < (b)) ? (a) : (b)) #endif -// TODO: good idea? bad idea? -#undef byte -#define byte byte_ -typedef unsigned char byte; +// typedef unsigned char byte; +typedef unsigned char blargg_byte; +#undef byte +#define byte blargg_byte // deprecated #define BLARGG_CHECK_ALLOC CHECK_ALLOC diff --git a/apps/codecs/libgme/blip_buffer.c b/apps/codecs/libgme/blip_buffer.c index d8ecbb8bb2..ba0a6558d2 100644 --- a/apps/codecs/libgme/blip_buffer.c +++ b/apps/codecs/libgme/blip_buffer.c @@ -2,7 +2,6 @@ #include "blip_buffer.h" -#include #include #include #include @@ -19,96 +18,78 @@ details. You should have received a copy of the GNU Lesser General Public License along with this module; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -#ifdef BLARGG_ENABLE_OPTIMIZER - #include BLARGG_ENABLE_OPTIMIZER -#endif - -int const silent_buf_size = 1; // size used for Silent_Blip_Buffer +#include "blargg_source.h" void Blip_init( struct Blip_Buffer* this ) { - this->factor_ = (blip_ulong)LONG_MAX; - this->offset_ = 0; - this->buffer_size_ = 0; - this->sample_rate_ = 0; - this->reader_accum_ = 0; - this->bass_shift_ = 0; - this->clock_rate_ = 0; - this->bass_freq_ = 16; - this->length_ = 0; - + this->factor_ = UINT_MAX/2 + 1;; + this->buffer_center_ = NULL; + this->buffer_size_ = 0; + this->sample_rate_ = 0; + this->bass_shift_ = 0; + this->clock_rate_ = 0; + this->bass_freq_ = 16; + this->length_ = 0; + // assumptions code makes about implementation-defined features #ifndef NDEBUG // right shift of negative value preserves sign buf_t_ i = -0x7FFFFFFE; assert( (i >> 1) == -0x3FFFFFFF ); - + // casting to short truncates to 16 bits and sign-extends i = 0x18000; assert( (short) i == -0x8000 ); #endif -} -void Blip_stop( struct Blip_Buffer* this ) -{ - if ( this->buffer_size_ != silent_buf_size ) - free( this->buffer_ ); + Blip_clear( this ); } -void Blip_clear( struct Blip_Buffer* this, int entire_buffer ) +void Blip_clear( struct Blip_Buffer* this ) { - this->offset_ = 0; + bool const entire_buffer = true; + + this->offset_ = 0; this->reader_accum_ = 0; - this->modified_ = 0; + this->modified = false; + if ( this->buffer_ ) { - long count = (entire_buffer ? this->buffer_size_ : Blip_samples_avail( this )); - memset( this->buffer_, 0, (count + blip_buffer_extra_) * sizeof (buf_t_) ); + int count = (entire_buffer ? this->buffer_size_ : Blip_samples_avail( this )); + memset( this->buffer_, 0, (count + blip_buffer_extra_) * sizeof (delta_t) ); } } -blargg_err_t Blip_set_sample_rate( struct Blip_Buffer* this, long new_rate, int msec ) +blargg_err_t Blip_set_sample_rate( struct Blip_Buffer* this, int new_rate, int msec ) { - if ( this->buffer_size_ == silent_buf_size ) - { - assert( 0 ); - return "Internal (tried to resize Silent_Blip_Buffer)"; - } - - // start with maximum length that resampled time can represent - long new_size = (ULONG_MAX >> BLIP_BUFFER_ACCURACY) - blip_buffer_extra_ - 64; - if ( msec != blip_max_length ) - { - long s = (new_rate * (msec + 1) + 999) / 1000; - if ( s < new_size ) - new_size = s; - else - assert( 0 ); // fails if requested buffer length exceeds limit - } - - if ( new_size > blip_buffer_max ) - return "Out of memory"; - - this->buffer_size_ = new_size; - assert( this->buffer_size_ != silent_buf_size ); - + // Limit to maximum size that resampled time can represent + int max_size = (((blip_resampled_time_t) -1) >> BLIP_BUFFER_ACCURACY) - + blip_buffer_extra_ - 64; // TODO: -64 isn't needed + int new_size = (new_rate * (msec + 1) + 999) / 1000; + if ( new_size > max_size ) + new_size = max_size; + + // Resize buffer + if ( this->buffer_size_ != new_size ) { + this->buffer_center_ = this->buffer_ + BLIP_MAX_QUALITY/2; + this->buffer_size_ = new_size; + } + // update things based on the sample rate this->sample_rate_ = new_rate; - this->length_ = new_size * 1000 / new_rate - 1; - if ( msec ) - assert( this->length_ == msec ); // ensure length is same as that passed in + this->length_ = new_size * 1000 / new_rate - 1; if ( this->clock_rate_ ) Blip_set_clock_rate( this, this->clock_rate_ ); Blip_bass_freq( this, this->bass_freq_ ); - - Blip_clear( this, 1 ); - + + Blip_clear( this ); + return 0; // success } -blip_resampled_time_t Blip_clock_rate_factor( struct Blip_Buffer* this, long rate ) +blip_resampled_time_t Blip_clock_rate_factor( struct Blip_Buffer* this, int rate ) { - blip_long factor = (blip_long) ( this->sample_rate_ * (1LL << BLIP_BUFFER_ACCURACY) / rate); + int factor = (int) ( this->sample_rate_ * (1LL << BLIP_BUFFER_ACCURACY) / rate); assert( factor > 0 || !this->sample_rate_ ); // fails if clock/output ratio is too large return (blip_resampled_time_t) factor; } @@ -117,119 +98,111 @@ void Blip_bass_freq( struct Blip_Buffer* this, int freq ) { this->bass_freq_ = freq; int shift = 31; - if ( freq > 0 ) + if ( freq > 0 && this->sample_rate_ ) { shift = 13; - long f = (freq << 16) / this->sample_rate_; + int f = (freq << 16) / this->sample_rate_; while ( (f >>= 1) && --shift ) { } } - this->bass_shift_ = shift; + this->bass_shift_ = shift; } void Blip_end_frame( struct Blip_Buffer* this, blip_time_t t ) { this->offset_ += t * this->factor_; - assert( Blip_samples_avail( this ) <= (long) this->buffer_size_ ); // time outside buffer length + assert( Blip_samples_avail( this ) <= (int) this->buffer_size_ ); // time outside buffer length } -void Blip_remove_silence( struct Blip_Buffer* this, long count ) +int Blip_count_samples( struct Blip_Buffer* this, blip_time_t t ) { - assert( count <= Blip_samples_avail( this ) ); // tried to remove more samples than available - this->offset_ -= (blip_resampled_time_t) count << BLIP_BUFFER_ACCURACY; + blip_resampled_time_t last_sample = Blip_resampled_time( this, t ) >> BLIP_BUFFER_ACCURACY; + blip_resampled_time_t first_sample = this->offset_ >> BLIP_BUFFER_ACCURACY; + return (int) (last_sample - first_sample); } -long Blip_count_samples( struct Blip_Buffer* this, blip_time_t t ) +blip_time_t Blip_count_clocks( struct Blip_Buffer* this, int count ) { - unsigned long last_sample = Blip_resampled_time( this, t ) >> BLIP_BUFFER_ACCURACY; - unsigned long first_sample = this->offset_ >> BLIP_BUFFER_ACCURACY; - return (long) (last_sample - first_sample); -} - -blip_time_t Blip_count_clocks( struct Blip_Buffer* this, long count ) -{ - if ( !this->factor_ ) - { - assert( 0 ); // sample rate and clock rates must be set first - return 0; - } - if ( count > this->buffer_size_ ) count = this->buffer_size_; blip_resampled_time_t time = (blip_resampled_time_t) count << BLIP_BUFFER_ACCURACY; return (blip_time_t) ((time - this->offset_ + this->factor_ - 1) / this->factor_); } -void Blip_remove_samples( struct Blip_Buffer* this, long count ) +void Blip_remove_samples( struct Blip_Buffer* this, int count ) { if ( count ) { Blip_remove_silence( this, count ); - + // copy remaining samples to beginning and clear old samples - long remain = Blip_samples_avail( this ) + blip_buffer_extra_; + int remain = Blip_samples_avail( this ) + blip_buffer_extra_; memmove( this->buffer_, this->buffer_ + count, remain * sizeof *this->buffer_ ); memset( this->buffer_ + remain, 0, count * sizeof *this->buffer_ ); } } -long Blip_read_samples( struct Blip_Buffer* this, blip_sample_t* BLIP_RESTRICT out, long max_samples, int stereo ) +int Blip_read_samples( struct Blip_Buffer* this, blip_sample_t out_ [], int max_samples, bool stereo ) { - long count = Blip_samples_avail( this ); + int count = Blip_samples_avail( this ); if ( count > max_samples ) count = max_samples; - + if ( count ) { - int const bass = BLIP_READER_BASS( *this ); - BLIP_READER_BEGIN( reader, *this ); - + int const bass = this->bass_shift_; + delta_t const* reader = this->buffer_ + count; + int reader_sum = this->reader_accum_; + + blip_sample_t* BLARGG_RESTRICT out = out_ + count; + if ( stereo ) + out += count; + int offset = -count; + if ( !stereo ) { - blip_long n; - for ( n = count; n; --n ) + do { - blip_long s = BLIP_READER_READ( reader ); - if ( (blip_sample_t) s != s ) - s = 0x7FFF - (s >> 24); - *out++ = (blip_sample_t) s; - BLIP_READER_NEXT( reader, bass ); + int s = reader_sum >> delta_bits; + + reader_sum -= reader_sum >> bass; + reader_sum += reader [offset]; + + BLIP_CLAMP( s, s ); + out [offset] = (blip_sample_t) s; } + while ( ++offset ); } else { - blip_long n; - for ( n = count; n; --n ) + do { - blip_long s = BLIP_READER_READ( reader ); - if ( (blip_sample_t) s != s ) - s = 0x7FFF - (s >> 24); - *out = (blip_sample_t) s; - out += 2; - BLIP_READER_NEXT( reader, bass ); + int s = reader_sum >> delta_bits; + + reader_sum -= reader_sum >> bass; + reader_sum += reader [offset]; + + BLIP_CLAMP( s, s ); + out [offset * 2] = (blip_sample_t) s; } + while ( ++offset ); } - BLIP_READER_END( reader, *this ); - + + this->reader_accum_ = reader_sum; + Blip_remove_samples( this, count ); } return count; } -void Blip_mix_samples( struct Blip_Buffer* this, blip_sample_t const* in, long count ) +void Blip_mix_samples( struct Blip_Buffer* this, blip_sample_t const in [], int count ) { - if ( this->buffer_size_ == silent_buf_size ) - { - assert( 0 ); - return; - } - - buf_t_* out = this->buffer_ + (this->offset_ >> BLIP_BUFFER_ACCURACY) + blip_widest_impulse_ / 2; - + delta_t* out = this->buffer_center_ + (this->offset_ >> BLIP_BUFFER_ACCURACY); + int const sample_shift = blip_sample_bits - 16; int prev = 0; - while ( count-- ) + while ( --count >= 0 ) { - blip_long s = (blip_long) *in++ << sample_shift; + int s = *in++ << sample_shift; *out += s - prev; prev = s; ++out; @@ -237,40 +210,16 @@ void Blip_mix_samples( struct Blip_Buffer* this, blip_sample_t const* in, long *out -= prev; } -void Blip_set_modified( struct Blip_Buffer* this ) -{ - this->modified_ = 1; -} - -int Blip_clear_modified( struct Blip_Buffer* this ) -{ - int b = this->modified_; - this->modified_ = 0; - return b; -} - -blip_resampled_time_t Blip_resampled_duration( struct Blip_Buffer* this, int t ) -{ - return t * this->factor_; -} +// Blip_Synth -blip_resampled_time_t Blip_resampled_time( struct Blip_Buffer* this, blip_time_t t ) +void volume_unit( struct Blip_Synth* this, int new_unit ) { - return t * this->factor_ + this->offset_; + this->delta_factor = (int) (new_unit * (1LL << blip_sample_bits) / FP_ONE_VOLUME); } - -// Blip_Synth - void Synth_init( struct Blip_Synth* this ) { this->buf = 0; this->last_amp = 0; this->delta_factor = 0; } - -// Set overall volume of waveform -void Synth_volume( struct Blip_Synth* this, int v ) -{ - this->delta_factor = (int) (v * (1LL << blip_sample_bits) / FP_ONE_VOLUME); -} diff --git a/apps/codecs/libgme/blip_buffer.h b/apps/codecs/libgme/blip_buffer.h index f9f1f6e969..d73a14b3eb 100644 --- a/apps/codecs/libgme/blip_buffer.h +++ b/apps/codecs/libgme/blip_buffer.h @@ -4,202 +4,238 @@ #ifndef BLIP_BUFFER_H #define BLIP_BUFFER_H -#include +#include "blargg_common.h" - // internal - #include "blargg_common.h" - #if INT_MAX >= 0x7FFFFFFF - typedef int blip_long; - typedef unsigned blip_ulong; - #else - typedef long blip_long; - typedef unsigned long blip_ulong; - #endif +typedef unsigned blip_resampled_time_t; +typedef int blip_time_t; +typedef int clocks_t; + +// Output samples are 16-bit signed, with a range of -32768 to 32767 +typedef short blip_sample_t; -// Time unit at source clock rate -typedef blip_long blip_time_t; +static int const blip_default_length = 1000 / 4; // Default Blip_Buffer length (1/4 second) + +#ifndef BLIP_MAX_QUALITY + #define BLIP_MAX_QUALITY 2 +#endif -// Number of bits in resample ratio fraction. Higher values give a more accurate ratio -// but reduce maximum buffer size. #ifndef BLIP_BUFFER_ACCURACY #define BLIP_BUFFER_ACCURACY 16 #endif -// Number bits in phase offset. Fewer than 6 bits (64 phase offsets) results in -// noticeable broadband noise when synthesizing high frequency square waves. -// Affects size of Blip_Synth objects since they store the waveform directly. +// linear interpolation needs 8 bits #ifndef BLIP_PHASE_BITS #define BLIP_PHASE_BITS 8 #endif -// Output samples are 16-bit signed, with a range of -32768 to 32767 -typedef short blip_sample_t; -enum { blip_sample_max = 32767 }; -enum { blip_widest_impulse_ = 16 }; -enum { blip_buffer_extra_ = blip_widest_impulse_ + 2 }; -enum { blip_res = 1 << BLIP_PHASE_BITS }; -enum { blip_max_length = 0 }; -enum { blip_default_length = 250 }; +static int const blip_res = 1 << BLIP_PHASE_BITS; +static int const blip_buffer_extra_ = BLIP_MAX_QUALITY + 2; + +// Properties of fixed-point sample position +typedef unsigned ufixed_t; // unsigned for more range, optimized shifts +enum { fixed_bits = BLIP_BUFFER_ACCURACY }; // bits in fraction +enum { fixed_unit = 1 << fixed_bits }; // 1.0 samples + +// Deltas in buffer are fixed-point with this many fraction bits. +// Less than 16 for extra range. +enum { delta_bits = 14 }; + +// Pointer to first committed delta sample +typedef int delta_t; // Maximun buffer size (48Khz, 50 ms) enum { blip_buffer_max = 2466 }; -enum { blip_sample_bits = 30 }; - -typedef blip_time_t buf_t_; -/* typedef const char* blargg_err_t; */ -typedef blip_ulong blip_resampled_time_t; struct Blip_Buffer { - blip_ulong factor_; - blip_resampled_time_t offset_; - buf_t_ buffer_ [blip_buffer_max]; - blip_long buffer_size_; - blip_long reader_accum_; - int bass_shift_; - - long sample_rate_; - long clock_rate_; - int bass_freq_; - int length_; - int modified_; + unsigned factor_; + ufixed_t offset_; + delta_t* buffer_center_; + int buffer_size_; + int reader_accum_; + int bass_shift_; + int bass_freq_; + int sample_rate_; + int clock_rate_; + int length_; + bool modified; + + delta_t buffer_ [blip_buffer_max]; }; -// not documented yet -void Blip_set_modified( struct Blip_Buffer* this ); -int Blip_clear_modified( struct Blip_Buffer* this ); -void Blip_remove_silence( struct Blip_Buffer* this, long count ); -blip_resampled_time_t Blip_resampled_duration( struct Blip_Buffer* this, int t ); -blip_resampled_time_t Blip_resampled_time( struct Blip_Buffer* this, blip_time_t t ); -blip_resampled_time_t Blip_clock_rate_factor( struct Blip_Buffer* this, long clock_rate ); - -// Initializes Blip_Buffer structure -void Blip_init( struct Blip_Buffer* this ); - -// Stops (clear) Blip_Buffer structure -void Blip_stop( struct Blip_Buffer* this ); +// Blip_Buffer_ implementation +static inline ufixed_t to_fixed( struct Blip_Buffer *this, clocks_t t ) +{ + return t * this->factor_ + this->offset_; +} -// Set output sample rate and buffer length in milliseconds (1/1000 sec, defaults -// to 1/4 second), then clear buffer. Returns NULL on success, otherwise if there -// isn't enough memory, returns error without affecting current buffer setup. -blargg_err_t Blip_set_sample_rate( struct Blip_Buffer* this, long samples_per_sec, int msec_length ); +static inline delta_t* delta_at( struct Blip_Buffer *this, ufixed_t f ) +{ + assert( (f >> fixed_bits) < (unsigned) this->buffer_size_ ); + return this->buffer_center_ + (f >> fixed_bits); +} -// Set number of source time units per second -static inline void Blip_set_clock_rate( struct Blip_Buffer* this, long cps ) +// Number of samples available for reading with read_samples() +static inline int Blip_samples_avail( struct Blip_Buffer* this ) { - this->factor_ = Blip_clock_rate_factor( this, this->clock_rate_ = cps ); + return (int) (this->offset_ >> BLIP_BUFFER_ACCURACY); } -// End current time frame of specified duration and make its samples available -// (along with any still-unread samples) for reading with read_samples(). Begins -// a new time frame at the end of the current frame. -void Blip_end_frame( struct Blip_Buffer* this, blip_time_t time ); +static inline void Blip_remove_silence( struct Blip_Buffer* this, int count ) +{ + assert( count <= Blip_samples_avail( this ) ); // tried to remove more samples than available + this->offset_ -= (blip_resampled_time_t) count << BLIP_BUFFER_ACCURACY; +} -// Read at most 'max_samples' out of buffer into 'dest', removing them from from -// the buffer. Returns number of samples actually read and removed. If stereo is -// true, increments 'dest' one extra time after writing each sample, to allow -// easy interleving of two channels into a stereo output buffer. -long Blip_read_samples( struct Blip_Buffer* this, blip_sample_t* dest, long max_samples, int stereo ); +// Initializes Blip_Buffer structure +void Blip_init( struct Blip_Buffer* this ); -// Additional optional features +// Sets output sample rate and resizes and clears sample buffer +blargg_err_t Blip_set_sample_rate( struct Blip_Buffer* this, int samples_per_sec, int msec_length ); // Current output sample rate -static inline long Blip_sample_rate( struct Blip_Buffer* this ) +static inline int Blip_sample_rate( struct Blip_Buffer* this ) { return this->sample_rate_; } -// Length of buffer, in milliseconds -static inline int Blip_length( struct Blip_Buffer* this ) +// Sets number of source time units per second +blip_resampled_time_t Blip_clock_rate_factor( struct Blip_Buffer* this, int clock_rate ); +static inline void Blip_set_clock_rate( struct Blip_Buffer* this, int clocks_per_sec ) { - return this->length_; + this->factor_ = Blip_clock_rate_factor( this, this->clock_rate_ = clocks_per_sec ); } // Number of source time units per second -static inline long Blip_clock_rate( struct Blip_Buffer* this ) +static inline int Blip_clock_rate( struct Blip_Buffer* this ) { return this->clock_rate_; } +static inline int Blip_length( struct Blip_Buffer* this ) +{ + return this->length_; +} + +// Clears buffer and removes all samples +void Blip_clear( struct Blip_Buffer* this ); + +// Use Blip_Synth to add waveform to buffer + +// Resamples to time t, then subtracts t from current time. Appends result of resampling +// to buffer for reading. +void Blip_end_frame( struct Blip_Buffer* this, blip_time_t time ) ICODE_ATTR; + + +// Reads at most n samples to out [0 to n-1] and returns number actually read. If stereo +// is true, writes to out [0], out [2], out [4] etc. instead. +int Blip_read_samples( struct Blip_Buffer* this, blip_sample_t out [], int n, bool stereo ) ICODE_ATTR; + + +// More features + +// Sets flag that tells some Multi_Buffer types that sound was added to buffer, +// so they know that it needs to be mixed in. Only needs to be called once +// per time frame that sound was added. Not needed if not using Multi_Buffer. +static inline void Blip_set_modified( struct Blip_Buffer* this ) { this->modified = true; } // Set frequency high-pass filter frequency, where higher values reduce bass more void Blip_bass_freq( struct Blip_Buffer* this, int frequency ); -// Number of samples delay from synthesis to samples read out -static inline int Blip_output_latency( void ) -{ - return blip_widest_impulse_ / 2; -} -// Remove all available samples and clear buffer to silence. If 'entire_buffer' is -// false, just clears out any samples waiting rather than the entire buffer. -void Blip_clear( struct Blip_Buffer* this, int entire_buffer ); +// Low-level features -// Number of samples available for reading with read_samples() -static inline long Blip_samples_avail( struct Blip_Buffer* this ) -{ - return (long) (this->offset_ >> BLIP_BUFFER_ACCURACY); -} +// Removes the first n samples +void Blip_remove_samples( struct Blip_Buffer* this, int n ) ICODE_ATTR; + +// Returns number of clocks needed until n samples will be available. +// If buffer cannot even hold n samples, returns number of clocks +// until buffer becomes full. +blip_time_t Blip_count_clocks( struct Blip_Buffer* this, int count ) ICODE_ATTR; + +// Number of samples that should be mixed before calling Blip_end_frame( t ) +int Blip_count_samples( struct Blip_Buffer* this, blip_time_t t ) ICODE_ATTR; + +// Mixes n samples into buffer +void Blip_mix_samples( struct Blip_Buffer* this, blip_sample_t const in [], int n ) ICODE_ATTR; -// Remove 'count' samples from those waiting to be read -void Blip_remove_samples( struct Blip_Buffer* this, long count ); -// Experimental features +// Resampled time (sorry, poor documentation right now) -// Count number of clocks needed until 'count' samples will be available. -// If buffer can't even hold 'count' samples, returns number of clocks until -// buffer becomes full. -blip_time_t Blip_count_clocks( struct Blip_Buffer* this, long count ); +// Resampled time is fixed-point, in terms of output samples. -// Number of raw samples that can be mixed within frame of specified duration. -long Blip_count_samples( struct Blip_Buffer* this, blip_time_t duration ); +// Converts clock count to resampled time +static inline blip_resampled_time_t Blip_resampled_duration( struct Blip_Buffer* this, int t ) +{ + return t * this->factor_; +} + +// Converts clock time since beginning of current time frame to resampled time +static inline blip_resampled_time_t Blip_resampled_time( struct Blip_Buffer* this, blip_time_t t ) +{ + return t * this->factor_ + this->offset_; +} -// Mix 'count' samples from 'buf' into buffer. -void Blip_mix_samples( struct Blip_Buffer* this, blip_sample_t const* buf, long count ); // Range specifies the greatest expected change in amplitude. Calculate it // by finding the difference between the maximum and minimum expected // amplitudes (max - min). +typedef char coeff_t; + struct Blip_Synth { - struct Blip_Buffer* buf; - int last_amp; int delta_factor; + int last_amp; + struct Blip_Buffer* buf; }; +// Blip_Synth_ +void volume_unit( struct Blip_Synth* this, int new_unit ); + // Initializes Blip_Synth structure void Synth_init( struct Blip_Synth* this ); -// Set overall volume of waveform -void Synth_volume( struct Blip_Synth* this, int v ); +// Sets volume of amplitude delta unit +static inline void Synth_volume( struct Blip_Synth* this, int v ) +{ + volume_unit( this, v ); // new_unit = 1 / range * v +} -// Get/set Blip_Buffer used for output -const struct Blip_Buffer* Synth_output( struct Blip_Synth* this ); // Low-level interface - #if defined (__GNUC__) || _MSC_VER >= 1100 - #define BLIP_RESTRICT __restrict - #else - #define BLIP_RESTRICT - #endif +// (in >> sh & mask) * mul +#define BLIP_SH_AND_MUL( in, sh, mask, mul ) \ +((int) (in) / ((1U << (sh)) / (mul)) & (unsigned) ((mask) * (mul))) + +// (T*) ptr + (off >> sh) +#define BLIP_PTR_OFF_SH( T, ptr, off, sh ) \ + ((T*) (BLIP_SH_AND_MUL( off, sh, -1, sizeof (T) ) + (char*) (ptr))) -// Works directly in terms of fractional output samples. Contact author for more info. +// Works directly in terms of fractional output samples. Use resampled time functions in Blip_Buffer +// to convert clock counts to resampled time. static inline void Synth_offset_resampled( struct Blip_Synth* this, blip_resampled_time_t time, int delta, struct Blip_Buffer* blip_buf ) { - // Fails if time is beyond end of Blip_Buffer, due to a bug in caller code or the - // need for a longer buffer as set by set_sample_rate(). - assert( (blip_long) (time >> BLIP_BUFFER_ACCURACY) < blip_buf->buffer_size_ ); + int const half_width = 1; + + delta_t* BLARGG_RESTRICT buf = delta_at( blip_buf, time ); delta *= this->delta_factor; - blip_long* BLIP_RESTRICT buf = blip_buf->buffer_ + (time >> BLIP_BUFFER_ACCURACY); - int phase = (int) (time >> (BLIP_BUFFER_ACCURACY - BLIP_PHASE_BITS) & (blip_res - 1)); - blip_long left = buf [0] + delta; + int const phase_shift = BLIP_BUFFER_ACCURACY - BLIP_PHASE_BITS; + int const phase = (half_width & (half_width - 1)) ? + (int) BLIP_SH_AND_MUL( time, phase_shift, blip_res - 1, sizeof (coeff_t) ) * half_width : + (int) BLIP_SH_AND_MUL( time, phase_shift, blip_res - 1, sizeof (coeff_t) * half_width ); + + int left = buf [0] + delta; // Kind of crappy, but doing shift after multiply results in overflow. // Alternate way of delaying multiply by delta_factor results in worse // sub-sample resolution. - blip_long right = (delta >> BLIP_PHASE_BITS) * phase; + int right = (delta >> BLIP_PHASE_BITS) * phase; + #ifdef BLIP_BUFFER_NOINTERP + // TODO: remove? (just a hack to see how it sounds) + right = 0; + #endif left -= right; right += buf [1]; @@ -213,33 +249,38 @@ static inline void Synth_update( struct Blip_Synth* this, blip_time_t t, int amp { int delta = amp - this->last_amp; this->last_amp = amp; - Synth_offset_resampled( this, t * this->buf->factor_ + this->buf->offset_, delta, this->buf ); + Synth_offset_resampled( this, to_fixed(this->buf, t), delta, this->buf ); } -// Add an amplitude transition of specified delta, optionally into specified buffer -// rather than the one set with output(). Delta can be positive or negative. -// The actual change in amplitude is delta * (volume / range) -static inline void Synth_offset( struct Blip_Synth* this, blip_time_t t, int delta, struct Blip_Buffer* buf ) -{ - Synth_offset_resampled( this, t * buf->factor_ + buf->offset_, delta, buf ); -} - -// Same as offset(), except code is inlined for higher performance +// Adds amplitude transition at time t. Delta can be positive or negative. +// The actual change in amplitude is delta * volume. static inline void Synth_offset_inline( struct Blip_Synth* this, blip_time_t t, int delta, struct Blip_Buffer* buf ) { - Synth_offset_resampled( this, t * buf->factor_ + buf->offset_, delta, buf ); + Synth_offset_resampled( this, to_fixed(buf, t), delta, buf ); } +#define Synth_offset( synth, time, delta, buf ) Synth_offset_inline( synth, time, delta, buf ) + +// Number of bits in raw sample that covers normal output range. Less than 32 bits to give +// extra amplitude range. That is, +// +1 << (blip_sample_bits-1) = +1.0 +// -1 << (blip_sample_bits-1) = -1.0 +static int const blip_sample_bits = 30; + // Optimized reading from Blip_Buffer, for use in custom sample output // Begin reading from buffer. Name should be unique to the current block. #define BLIP_READER_BEGIN( name, blip_buffer ) \ - buf_t_* BLIP_RESTRICT name##_reader_buf = (blip_buffer).buffer_;\ - blip_long name##_reader_accum = (blip_buffer).reader_accum_ + const delta_t* BLARGG_RESTRICT name##_reader_buf = (blip_buffer).buffer_;\ + int name##_reader_accum = (blip_buffer).reader_accum_ // Get value to pass to BLIP_READER_NEXT() #define BLIP_READER_BASS( blip_buffer ) ((blip_buffer).bass_shift_) +// Constant value to use instead of BLIP_READER_BASS(), for slightly more optimal +// code at the cost of having no bass_freq() functionality +static int const blip_reader_default_bass = 9; + // Current sample #define BLIP_READER_READ( name ) (name##_reader_accum >> (blip_sample_bits - 16)) @@ -254,7 +295,7 @@ static inline void Synth_offset_inline( struct Blip_Synth* this, blip_time_t t, // using Blip_remove_samples(). #define BLIP_READER_END( name, blip_buffer ) \ (void) ((blip_buffer).reader_accum_ = name##_reader_accum) - + #define BLIP_READER_ADJ_( name, offset ) (name##_reader_buf += offset) #define BLIP_READER_NEXT_IDX_( name, bass, idx ) {\ @@ -262,18 +303,33 @@ static inline void Synth_offset_inline( struct Blip_Synth* this, blip_time_t t, name##_reader_accum += name##_reader_buf [(idx)];\ } +#define BLIP_READER_NEXT_RAW_IDX_( name, bass, idx ) {\ + name##_reader_accum -= name##_reader_accum >> (bass);\ + name##_reader_accum +=\ + *(delta_t const*) ((char const*) name##_reader_buf + (idx));\ +} + //// BLIP_CLAMP -#if defined (_M_IX86) || defined (_M_IA64) || defined (__i486__) || \ - defined (__x86_64__) || defined (__ia64__) || defined (__i386__) - #define BLIP_X86 1 - #define BLIP_CLAMP_( in ) in < -0x8000 || 0x7FFF < in +#if ARM_ARCH >= 6 + #define BLIP_CLAMP( sample, out ) \ + ({ \ + asm ("ssat %0, #16, %1" \ + : "=r" ( out ) : "r"( sample ) ); \ + out; \ + }) #else - #define BLIP_CLAMP_( in ) (blip_sample_t) in != in -#endif + #if defined (_M_IX86) || defined (_M_IA64) || defined (__i486__) || \ + defined (__x86_64__) || defined (__ia64__) || defined (__i386__) + #define BLIP_X86 1 + #define BLIP_CLAMP_( in ) in < -0x8000 || 0x7FFF < in + #else + #define BLIP_CLAMP_( in ) (blip_sample_t) in != in + #endif -// Clamp sample to blip_sample_t range -#define BLIP_CLAMP( sample, out )\ - { if ( BLIP_CLAMP_( (sample) ) ) (out) = ((sample) >> 31) ^ 0x7FFF; } + // Clamp sample to blip_sample_t range + #define BLIP_CLAMP( sample, out )\ + { if ( BLIP_CLAMP_( (sample) ) ) (out) = ((sample) >> 31) ^ 0x7FFF; } +#endif #endif diff --git a/apps/codecs/libgme/gbs_cpu.c b/apps/codecs/libgme/gbs_cpu.c index 998888fe72..1015dd5358 100644 --- a/apps/codecs/libgme/gbs_cpu.c +++ b/apps/codecs/libgme/gbs_cpu.c @@ -20,7 +20,7 @@ Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #define LOG_MEM( addr, str, data ) data #endif -int Read_mem( struct Gbs_Emu* this, addr_t addr ) +int read_mem( struct Gbs_Emu* this, addr_t addr ) { int result = *Cpu_get_code( &this->cpu, addr ); if ( (unsigned) (addr - io_addr) < io_size ) @@ -29,19 +29,19 @@ int Read_mem( struct Gbs_Emu* this, addr_t addr ) return LOG_MEM( addr, ">", result ); } -static inline void Write_io_inline( struct Gbs_Emu* this, int offset, int data, int base ) +static inline void write_io_inline( struct Gbs_Emu* this, int offset, int data, int base ) { if ( (unsigned) (offset - (io_addr - base)) < io_size ) Apu_write_register( &this->apu, Time( this ), offset + base, data & 0xFF ); else if ( (unsigned) (offset - (0xFF06 - base)) < 2 ) - Update_timer( this ); + update_timer( this ); else if ( offset == io_base - base ) this->ram [base - ram_addr + offset] = 0; // keep joypad return value 0 else this->ram [base - ram_addr + offset] = 0xFF; } -void Write_mem( struct Gbs_Emu* this, addr_t addr, int data ) +void write_mem( struct Gbs_Emu* this, addr_t addr, int data ) { (void) LOG_MEM( addr, "<", data ); @@ -52,11 +52,11 @@ void Write_mem( struct Gbs_Emu* this, addr_t addr, int data ) offset -= 0xE000 - ram_addr; if ( (unsigned) offset < 0x1F80 ) - Write_io_inline( this, offset, data, 0xE000 ); + write_io_inline( this, offset, data, 0xE000 ); } else if ( (unsigned) (offset - (0x2000 - ram_addr)) < 0x2000 ) { - Set_bank( this, data & 0xFF ); + set_bank( this, data & 0xFF ); } #ifndef NDEBUG else if ( unsigned (addr - 0x8000) < 0x2000 || unsigned (addr - 0xE000) < 0x1F00 ) @@ -66,21 +66,21 @@ void Write_mem( struct Gbs_Emu* this, addr_t addr, int data ) #endif } -static void Write_io_( struct Gbs_Emu* this, int offset, int data ) +static void write_io_( struct Gbs_Emu* this, int offset, int data ) { - Write_io_inline( this, offset, data, io_base ); + write_io_inline( this, offset, data, io_base ); } -static inline void Write_io( struct Gbs_Emu* this, int offset, int data ) +static inline void write_io( struct Gbs_Emu* this, int offset, int data ) { (void) LOG_MEM( offset + io_base, "<", data ); this->ram [io_base - ram_addr + offset] = data; if ( (unsigned) offset < 0x80 ) - Write_io_( this, offset, data ); + write_io_( this, offset, data ); } -static int Read_io( struct Gbs_Emu* this, int offset ) +static int read_io( struct Gbs_Emu* this, int offset ) { int const io_base = 0xFF00; int result = this->ram [io_base - ram_addr + offset]; @@ -106,14 +106,14 @@ static int Read_io( struct Gbs_Emu* this, int offset ) check( out == Read_mem( emu, addr ) );\ } -#define READ_MEM( emu, addr ) Read_mem( emu, addr ) -#define WRITE_MEM( emu, addr, data ) Write_mem( emu, addr, data ) +#define READ_MEM( emu, addr ) read_mem( emu, addr ) +#define WRITE_MEM( emu, addr, data ) write_mem( emu, addr, data ) -#define WRITE_IO( emu, addr, data ) Write_io( emu, addr, data ) -#define READ_IO( emu, addr, out ) out = Read_io( emu, addr ) +#define WRITE_IO( emu, addr, data ) write_io( emu, addr, data ) +#define READ_IO( emu, addr, out ) out = read_io( emu, addr ) #define CPU_BEGIN \ -void Run_cpu( struct Gbs_Emu* this )\ +void run_cpu( struct Gbs_Emu* this )\ { \ struct Gb_Cpu* cpu = &this->cpu; #include "gb_cpu_run.h" diff --git a/apps/codecs/libgme/gbs_emu.c b/apps/codecs/libgme/gbs_emu.c index 640ea43a70..7a6d484673 100644 --- a/apps/codecs/libgme/gbs_emu.c +++ b/apps/codecs/libgme/gbs_emu.c @@ -3,7 +3,6 @@ #include "gbs_emu.h" #include "blargg_endian.h" -#include "blargg_source.h" /* Copyright (C) 2003-2006 Shay Green. this module is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser @@ -16,30 +15,17 @@ details. You should have received a copy of the GNU Lesser General Public License along with this module; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ +#include "blargg_source.h" const char gme_wrong_file_type [] = "Wrong file type for this emulator"; int const idle_addr = 0xF00D; int const tempo_unit = 16; -int const stereo = 2; // number of channels for stereo -int const silence_max = 6; // seconds -int const silence_threshold = 0x10; -long const fade_block_size = 512; -int const fade_shift = 8; // fade ends with gain at 1.0 / (1 << fade_shift) - static void clear_track_vars( struct Gbs_Emu* this ) { - this->current_track_ = -1; - this->out_time = 0; - this->emu_time = 0; - this->emu_track_ended_ = true; - this->track_ended = true; - this->fade_start = (blargg_long)(LONG_MAX / 2 + 1); - this->fade_step = 1; - this->silence_time = 0; - this->silence_count = 0; - this->buf_remain = 0; + this->current_track_ = -1; + track_stop( &this->track_filter ); } void Gbs_init( struct Gbs_Emu* this ) @@ -50,11 +36,13 @@ void Gbs_init( struct Gbs_Emu* this ) // Unload this->header.timer_mode = 0; - clear_track_vars( this ); - this->ignore_silence = false; - this->silence_lookahead = 6; - this->max_initial_silence = 21; + // defaults + this->tfilter = *track_get_setup( &this->track_filter ); + this->tfilter.max_initial = 21; + this->tfilter.lookahead = 6; + this->track_filter.silence_ignored_ = false; + Sound_set_gain( this, (int)(FP_ONE_GAIN*1.2) ); Rom_init( &this->rom, 0x4000 ); @@ -67,6 +55,11 @@ void Gbs_init( struct Gbs_Emu* this ) // Reduce apu sound clicks? Apu_reduce_clicks( &this->apu, true ); + + // clears fields + this->voice_count_ = 0; + this->voice_types_ = 0; + clear_track_vars( this ); } static blargg_err_t check_gbs_header( void const* header ) @@ -78,11 +71,12 @@ static blargg_err_t check_gbs_header( void const* header ) // Setup -blargg_err_t Gbs_load( struct Gbs_Emu* this, void* data, long size ) +blargg_err_t Gbs_load_mem( struct Gbs_Emu* this, void* data, long size ) { // Unload this->header.timer_mode = 0; this->voice_count_ = 0; + this->track_count = 0; this->m3u.size = 0; clear_track_vars( this ); @@ -112,20 +106,24 @@ blargg_err_t Gbs_load( struct Gbs_Emu* this, void* data, long size ) Rom_set_addr( &this->rom, load_addr ); this->voice_count_ = osc_count; + static int const types [osc_count] = { + wave_type+1, wave_type+2, wave_type+3, mixed_type+1 + }; + this->voice_types_ = types; + Apu_volume( &this->apu, this->gain_ ); // Change clock rate & setup buffer this->clock_rate_ = 4194304; Buffer_clock_rate( &this->stereo_buf, 4194304 ); + RETURN_ERR( Buffer_set_channel_count( &this->stereo_buf, this->voice_count_, this->voice_types_ ) ); this->buf_changed_count = Buffer_channels_changed_count( &this->stereo_buf ); // Post load Sound_set_tempo( this, this->tempo_ ); - - // Remute voices Sound_mute_voices( this, this->mute_mask_ ); - // Reset track count + // Set track count this->track_count = this->header.track_count; return 0; } @@ -134,7 +132,7 @@ blargg_err_t Gbs_load( struct Gbs_Emu* this, void* data, long size ) // see gb_cpu_io.h for read/write functions -void Set_bank( struct Gbs_Emu* this, int n ) +void set_bank( struct Gbs_Emu* this, int n ) { addr_t addr = mask_addr( n * this->rom.bank_size, this->rom.mask ); if ( addr == 0 && this->rom.size > this->rom.bank_size ) @@ -142,7 +140,7 @@ void Set_bank( struct Gbs_Emu* this, int n ) Cpu_map_code( &this->cpu, this->rom.bank_size, this->rom.bank_size, Rom_at_addr( &this->rom, addr ) ); } -void Update_timer( struct Gbs_Emu* this ) +void update_timer( struct Gbs_Emu* this ) { this->play_period = 70224 / tempo_unit; /// 59.73 Hz @@ -161,21 +159,21 @@ void Update_timer( struct Gbs_Emu* this ) // Jumps to routine, given pointer to address in file header. Pushes idle_addr // as return address, NOT old PC. -void Jsr_then_stop( struct Gbs_Emu* this, byte const addr [] ) +void jsr_then_stop( struct Gbs_Emu* this, byte const addr [] ) { check( this->cpu.r.sp == get_le16( this->header.stack_ptr ) ); this->cpu.r.pc = get_le16( addr ); - Write_mem( this, --this->cpu.r.sp, idle_addr >> 8 ); - Write_mem( this, --this->cpu.r.sp, idle_addr ); + write_mem( this, --this->cpu.r.sp, idle_addr >> 8 ); + write_mem( this, --this->cpu.r.sp, idle_addr ); } -static blargg_err_t Run_until( struct Gbs_Emu* this, int end ) +static blargg_err_t run_until( struct Gbs_Emu* this, int end ) { this->end_time = end; Cpu_set_time( &this->cpu, Cpu_time( &this->cpu ) - end ); while ( true ) { - Run_cpu( this ); + run_cpu( this ); if ( Cpu_time( &this->cpu ) >= 0 ) break; @@ -190,7 +188,7 @@ static blargg_err_t Run_until( struct Gbs_Emu* this, int end ) if ( Cpu_time( &this->cpu ) < this->next_play - this->end_time ) Cpu_set_time( &this->cpu, this->next_play - this->end_time ); this->next_play += this->play_period; - Jsr_then_stop( this, this->header.play_addr ); + jsr_then_stop( this, this->header.play_addr ); } else if ( this->cpu.r.pc > 0xFFFF ) { @@ -208,9 +206,9 @@ static blargg_err_t Run_until( struct Gbs_Emu* this, int end ) return 0; } -static blargg_err_t End_frame( struct Gbs_Emu* this, int end ) +static blargg_err_t end_frame( struct Gbs_Emu* this, int end ) { - RETURN_ERR( Run_until( this, end ) ); + RETURN_ERR( run_until( this, end ) ); this->next_play -= end; if ( this->next_play < 0 ) // happens when play routine takes too long @@ -226,16 +224,19 @@ static blargg_err_t End_frame( struct Gbs_Emu* this, int end ) return 0; } -blargg_err_t Run_clocks( struct Gbs_Emu* this, blip_time_t duration ) +blargg_err_t run_clocks( struct Gbs_Emu* this, blip_time_t duration ) { - return End_frame( this, duration ); + return end_frame( this, duration ); } -static blargg_err_t play_( struct Gbs_Emu* this, long count, sample_t* out ) +blargg_err_t play_( void* emu, int count, sample_t* out ) { - long remain = count; + struct Gbs_Emu* this = (struct Gbs_Emu*) emu; + + int remain = count; while ( remain ) { + Buffer_disable_immediate_removal( &this->stereo_buf ); remain -= Buffer_read_samples( &this->stereo_buf, &out [count - remain], remain ); if ( remain ) { @@ -247,8 +248,8 @@ static blargg_err_t play_( struct Gbs_Emu* this, long count, sample_t* out ) Sound_mute_voices( this, this->mute_mask_ ); } int msec = Buffer_length( &this->stereo_buf ); - blip_time_t clocks_emulated = (blargg_long) msec * this->clock_rate_ / 1000; - RETURN_ERR( Run_clocks( this, clocks_emulated ) ); + blip_time_t clocks_emulated = msec * this->clock_rate_ / 1000 - 100; + RETURN_ERR( run_clocks( this, clocks_emulated ) ); assert( clocks_emulated ); Buffer_end_frame( &this->stereo_buf, clocks_emulated ); } @@ -256,7 +257,7 @@ static blargg_err_t play_( struct Gbs_Emu* this, long count, sample_t* out ) return 0; } -blargg_err_t Gbs_set_sample_rate( struct Gbs_Emu* this, long rate ) +blargg_err_t Gbs_set_sample_rate( struct Gbs_Emu* this, int rate ) { require( !this->sample_rate_ ); // sample rate can't be changed once set Buffer_init( &this->stereo_buf ); @@ -266,6 +267,8 @@ blargg_err_t Gbs_set_sample_rate( struct Gbs_Emu* this, long rate ) Buffer_bass_freq( &this->stereo_buf, 300 ); this->sample_rate_ = rate; + RETURN_ERR( track_init( &this->track_filter, this ) ); + this->tfilter.max_silence = 6 * stereo * this->sample_rate_; return 0; } @@ -296,7 +299,7 @@ void Sound_mute_voices( struct Gbs_Emu* this, int mask ) } else { - struct channel_t ch = Buffer_channel( &this->stereo_buf ); + struct channel_t ch = Buffer_channel( &this->stereo_buf, i ); assert( (ch.center && ch.left && ch.right) || (!ch.center && !ch.left && !ch.right) ); // all or nothing Apu_set_output( &this->apu, i, ch.center, ch.left, ch.right ); @@ -315,10 +318,9 @@ void Sound_set_tempo( struct Gbs_Emu* this, int t ) this->tempo = (int) ((tempo_unit * FP_ONE_TEMPO) / t); Apu_set_tempo( &this->apu, t ); - Update_timer( this ); + update_timer( this ); } -void fill_buf( struct Gbs_Emu* this ); blargg_err_t Gbs_start_track( struct Gbs_Emu* this, int track ) { clear_track_vars( this ); @@ -330,7 +332,6 @@ blargg_err_t Gbs_start_track( struct Gbs_Emu* this, int track ) } this->current_track_ = track; - Buffer_clear( &this->stereo_buf ); // Reset APU to state expected by most rips @@ -364,268 +365,88 @@ blargg_err_t Gbs_start_track( struct Gbs_Emu* this, int track ) Cpu_reset( &this->cpu, this->rom.unmapped ); Cpu_map_code( &this->cpu, ram_addr, 0x10000 - ram_addr, this->ram ); Cpu_map_code( &this->cpu, 0, this->rom.bank_size, Rom_at_addr( &this->rom, 0 ) ); - Set_bank( this, this->rom.size > this->rom.bank_size ); + set_bank( this, this->rom.size > this->rom.bank_size ); - Update_timer( this ); + update_timer( this ); this->next_play = this->play_period; this->cpu.r.rp.fa = track; this->cpu.r.sp = get_le16( this->header.stack_ptr ); this->cpu_time = 0; - Jsr_then_stop( this, this->header.init_addr ); + jsr_then_stop( this, this->header.init_addr ); - this->emu_track_ended_ = false; - this->track_ended = false; + // convert filter times to samples + struct setup_t s = this->tfilter; + s.max_initial *= this->sample_rate_ * stereo; + #ifdef GME_DISABLE_SILENCE_LOOKAHEAD + s.lookahead = 1; + #endif + track_setup( &this->track_filter, &s ); - if ( !this->ignore_silence ) - { - // play until non-silence or end of track - long end; - for ( end = this->max_initial_silence * stereo * this->sample_rate_; this->emu_time < end; ) - { - fill_buf( this ); - if ( this->buf_remain | (int) this->emu_track_ended_ ) - break; - } - - this->emu_time = this->buf_remain; - this->out_time = 0; - this->silence_time = 0; - this->silence_count = 0; - } - /* return track_ended() ? warning() : 0; */ - return 0; + return track_start( &this->track_filter ); } // Track -static blargg_long msec_to_samples( blargg_long msec, long sample_rate ) +static int msec_to_samples( int msec, int sample_rate ) { - blargg_long sec = msec / 1000; + int sec = msec / 1000; msec -= sec * 1000; return (sec * sample_rate + msec * sample_rate / 1000) * stereo; } -long Track_tell( struct Gbs_Emu* this ) +int Track_tell( struct Gbs_Emu* this ) { - blargg_long rate = this->sample_rate_ * stereo; - blargg_long sec = this->out_time / rate; - return sec * 1000 + (this->out_time - sec * rate) * 1000 / rate; + int rate = this->sample_rate_ * stereo; + int sec = track_sample_count( &this->track_filter ) / rate; + return sec * 1000 + (track_sample_count( &this->track_filter ) - sec * rate) * 1000 / rate; } -blargg_err_t Track_seek( struct Gbs_Emu* this, long msec ) +blargg_err_t Track_seek( struct Gbs_Emu* this, int msec ) { - blargg_long time = msec_to_samples( msec, this->sample_rate_ ); - if ( time < this->out_time ) - RETURN_ERR( Gbs_start_track( this, this->current_track_ ) ); - return Track_skip( this, time - this->out_time ); + int time = msec_to_samples( msec, this->sample_rate_ ); + if ( time < track_sample_count( &this->track_filter ) ) + RETURN_ERR( Gbs_start_track( this, this->current_track_ ) ); + return Track_skip( this, time - track_sample_count( &this->track_filter ) ); } -static blargg_err_t skip_( struct Gbs_Emu* this, long count ) +blargg_err_t skip_( void* emu, int count ) { + struct Gbs_Emu* this = (struct Gbs_Emu*) emu; + // for long skip, mute sound - const long threshold = 30000; + const int threshold = 32768; if ( count > threshold ) { int saved_mute = this->mute_mask_; Sound_mute_voices( this, ~0 ); - - while ( count > threshold / 2 && !this->emu_track_ended_ ) - { - RETURN_ERR( play_( this, buf_size, this->buf ) ); - count -= buf_size; - } - - Sound_mute_voices( this, saved_mute ); - } - - while ( count && !this->emu_track_ended_ ) - { - long n = buf_size; - if ( n > count ) - n = count; - count -= n; - RETURN_ERR( play_( this, n, this->buf ) ); - } - return 0; -} -blargg_err_t Track_skip( struct Gbs_Emu* this, long count ) -{ - require( this->current_track_ >= 0 ); // start_track() must have been called already - this->out_time += count; - - // remove from silence and buf first - { - long n = min( count, this->silence_count ); - this->silence_count -= n; - count -= n; - - n = min( count, this->buf_remain ); - this->buf_remain -= n; + int n = count - threshold/2; + n &= ~(2048-1); // round to multiple of 2048 count -= n; - } - - if ( count && !this->emu_track_ended_ ) - { - this->emu_time += count; - // End track if error - if ( skip_( this, count ) ) - this->emu_track_ended_ = true; - } - - if ( !(this->silence_count | this->buf_remain) ) // caught up to emulator, so update track ended - this->track_ended |= this->emu_track_ended_; - - return 0; -} - -// Fading + RETURN_ERR( skippy_( &this->track_filter, n ) ); -void Track_set_fade( struct Gbs_Emu* this, long start_msec, long length_msec ) -{ - this->fade_step = this->sample_rate_ * length_msec / (fade_block_size * fade_shift * 1000 / stereo); - this->fade_start = msec_to_samples( start_msec, this->sample_rate_ ); -} - -// unit / pow( 2.0, (double) x / step ) -static int int_log( blargg_long x, int step, int unit ) -{ - int shift = x / step; - int fraction = (x - shift * step) * unit / step; - return ((unit - fraction) + (fraction >> 1)) >> shift; -} - -static void handle_fade( struct Gbs_Emu* this, long out_count, sample_t* out ) -{ - int i; - for ( i = 0; i < out_count; i += fade_block_size ) - { - int const shift = 14; - int const unit = 1 << shift; - int gain = int_log( (this->out_time + i - this->fade_start) / fade_block_size, - this->fade_step, unit ); - if ( gain < (unit >> fade_shift) ) - this->track_ended = this->emu_track_ended_ = true; - - sample_t* io = &out [i]; - int count; - for ( count = min( fade_block_size, out_count - i ); count; --count ) - { - *io = (sample_t) ((*io * gain) >> shift); - ++io; - } + Sound_mute_voices( this, saved_mute ); } -} - -// Silence detection -static void emu_play( struct Gbs_Emu* this, long count, sample_t* out ) -{ - check( current_track_ >= 0 ); - this->emu_time += count; - if ( this->current_track_ >= 0 && !this->emu_track_ended_ ) { - // End track if error - if ( play_( this, count, out ) ) this->emu_track_ended_ = true; - } - else - memset( out, 0, count * sizeof *out ); + return skippy_( &this->track_filter, count ); } -// number of consecutive silent samples at end -static long count_silence( sample_t* begin, long size ) +blargg_err_t Track_skip( struct Gbs_Emu* this, int count ) { - sample_t first = *begin; - *begin = silence_threshold; // sentinel - sample_t* p = begin + size; - while ( (unsigned) (*--p + silence_threshold / 2) <= (unsigned) silence_threshold ) { } - *begin = first; - return size - (p - begin); + require( this->current_track_ >= 0 ); // start_track() must have been called already + return track_skip( &this->track_filter, count ); } -// fill internal buffer and check it for silence -void fill_buf( struct Gbs_Emu* this ) +void Track_set_fade( struct Gbs_Emu* this, int start_msec, int length_msec ) { - assert( !this->buf_remain ); - if ( !this->emu_track_ended_ ) - { - emu_play( this, buf_size, this->buf ); - long silence = count_silence( this->buf, buf_size ); - if ( silence < buf_size ) - { - this->silence_time = this->emu_time - silence; - this->buf_remain = buf_size; - return; - } - } - this->silence_count += buf_size; + track_set_fade( &this->track_filter, msec_to_samples( start_msec, this->sample_rate_ ), + length_msec * this->sample_rate_ / (1000 / stereo) ); } -blargg_err_t Gbs_play( struct Gbs_Emu* this, long out_count, sample_t* out ) +blargg_err_t Gbs_play( struct Gbs_Emu* this, int out_count, sample_t* out ) { - if ( this->track_ended ) - { - memset( out, 0, out_count * sizeof *out ); - } - else - { - require( this->current_track_ >= 0 ); - require( out_count % stereo == 0 ); - - assert( this->emu_time >= this->out_time ); - - long pos = 0; - if ( this->silence_count ) - { - // during a run of silence, run emulator at >=2x speed so it gets ahead - long ahead_time = this->silence_lookahead * (this->out_time + out_count - this->silence_time) + this->silence_time; - while ( this->emu_time < ahead_time && !(this->buf_remain | this->emu_track_ended_) ) - fill_buf( this ); - - // fill with silence - pos = min( this->silence_count, out_count ); - memset( out, 0, pos * sizeof *out ); - this->silence_count -= pos; - - if ( this->emu_time - this->silence_time > silence_max * stereo * this->sample_rate_ ) - { - this->track_ended = this->emu_track_ended_ = true; - this->silence_count = 0; - this->buf_remain = 0; - } - } - - if ( this->buf_remain ) - { - // empty silence buf - long n = min( this->buf_remain, out_count - pos ); - memcpy( &out [pos], this->buf + (buf_size - this->buf_remain), n * sizeof *out ); - this->buf_remain -= n; - pos += n; - } - - // generate remaining samples normally - long remain = out_count - pos; - if ( remain ) - { - emu_play( this, remain, out + pos ); - this->track_ended |= this->emu_track_ended_; - - if ( !this->ignore_silence || this->out_time > this->fade_start ) - { - // check end for a new run of silence - long silence = count_silence( out + pos, remain ); - if ( silence < remain ) - this->silence_time = this->emu_time - silence; - - if ( this->emu_time - this->silence_time >= buf_size ) - fill_buf( this ); // cause silence detection on next play() - } - } - - if ( this->out_time > this->fade_start ) - handle_fade( this, out_count, out ); - } - this->out_time += out_count; - return 0; + require( this->current_track_ >= 0 ); + require( out_count % stereo == 0 ); + return track_play( &this->track_filter, out_count, out ); } diff --git a/apps/codecs/libgme/gbs_emu.h b/apps/codecs/libgme/gbs_emu.h index 409cf2d6c2..72671b4658 100644 --- a/apps/codecs/libgme/gbs_emu.h +++ b/apps/codecs/libgme/gbs_emu.h @@ -9,15 +9,12 @@ #include "gb_apu.h" #include "gb_cpu.h" #include "m3u_playlist.h" - -/* typedef uint8_t byte; */ -typedef short sample_t; +#include "track_filter.h" enum { joypad_addr = 0xFF00 }; enum { ram_addr = 0xA000 }; enum { hi_page = 0xFF00 - ram_addr }; enum { io_base = 0xFF00 }; -enum { buf_size = 2048 }; // Selects which sound hardware to use. AGB hardware is cleaner than the // others. Doesn't take effect until next start_track(). @@ -59,36 +56,19 @@ struct Gbs_Emu { blip_time_t next_play; // Sound - long clock_rate_; - long sample_rate_; + int clock_rate_; + int sample_rate_; unsigned buf_changed_count; int voice_count_; + int const* voice_types_; + int mute_mask_; int gain_; int tempo_; // track-specific byte track_count; - volatile bool track_ended; int current_track_; - blargg_long out_time; // number of samples played since start of track - blargg_long emu_time; // number of samples emulator has generated since start of track - bool emu_track_ended_; // emulator has reached end of track - - // fading - blargg_long fade_start; - int fade_step; - - // silence detection - // Disable automatic end-of-track detection and skipping of silence at beginning - bool ignore_silence; - int max_initial_silence; - int mute_mask_; - int silence_lookahead; // speed to run emulator when looking ahead for silence - long silence_time; // number of samples where most recent silence began - long silence_count; // number of samples of silence to play before using buf - long buf_remain; // number of samples left in silence buffer - // Larger items at the end // Header for currently loaded file struct header_t header; @@ -96,11 +76,12 @@ struct Gbs_Emu { // M3u Playlist struct M3u_Playlist m3u; + struct setup_t tfilter; + struct Track_Filter track_filter; + struct Gb_Apu apu; struct Gb_Cpu cpu; - struct Stereo_Buffer stereo_buf; - - sample_t buf [buf_size]; + struct Multi_Buffer stereo_buf; // rom & ram struct Rom_Data rom; @@ -116,36 +97,48 @@ void Gbs_init( struct Gbs_Emu* this ); void Gbs_stop( struct Gbs_Emu* this ); // Loads a file from memory -blargg_err_t Gbs_load( struct Gbs_Emu* this, void* data, long size ); +blargg_err_t Gbs_load_mem( struct Gbs_Emu* this, void* data, long size ); // Set output sample rate. Must be called only once before loading file. -blargg_err_t Gbs_set_sample_rate( struct Gbs_Emu* this, long sample_rate ); +blargg_err_t Gbs_set_sample_rate( struct Gbs_Emu* this, int sample_rate ); // Start a track, where 0 is the first track. Also clears warning string. blargg_err_t Gbs_start_track( struct Gbs_Emu* this, int ); // Generate 'count' samples info 'buf'. Output is in stereo. Any emulation // errors set warning string, and major errors also end track. -blargg_err_t Gbs_play( struct Gbs_Emu* this, long count, sample_t* buf ); +blargg_err_t Gbs_play( struct Gbs_Emu* this, int count, sample_t* buf ); // Track status/control // Number of milliseconds (1000 msec = 1 second) played since beginning of track -long Track_tell( struct Gbs_Emu* this ); +int Track_tell( struct Gbs_Emu* this ); // Seek to new time in track. Seeking backwards or far forward can take a while. -blargg_err_t Track_seek( struct Gbs_Emu* this, long msec ); +blargg_err_t Track_seek( struct Gbs_Emu* this, int msec ); // Skip n samples -blargg_err_t Track_skip( struct Gbs_Emu* this, long n ); +blargg_err_t Track_skip( struct Gbs_Emu* this, int n ); // Set start time and length of track fade out. Once fade ends track_ended() returns // true. Fade time can be changed while track is playing. -void Track_set_fade( struct Gbs_Emu* this, long start_msec, long length_msec ); +void Track_set_fade( struct Gbs_Emu* this, int start_msec, int length_msec ); + +// True if a track has reached its end +static inline bool Track_ended( struct Gbs_Emu* this ) +{ + return track_ended( &this->track_filter ); +} + +// Disables automatic end-of-track detection and skipping of silence at beginning +static inline void Track_ignore_silence( struct Gbs_Emu* this, bool disable ) +{ + this->track_filter.silence_ignored_ = disable; +} // Get track length in milliseconds -static inline long Track_get_length( struct Gbs_Emu* this, int n ) +static inline int Track_get_length( struct Gbs_Emu* this, int n ) { - long length = 120 * 1000; /* 2 minutes */ + int length = 120 * 1000; /* 2 minutes */ if ( (this->m3u.size > 0) && (n < this->m3u.size) ) { struct entry_t* entry = &this->m3u.entries [n]; length = entry->length; @@ -175,19 +168,18 @@ static inline void Sound_set_gain( struct Gbs_Emu* this, int g ) this->gain_ = g; } - // Emulation (You shouldn't touch these) -blargg_err_t Run_clocks( struct Gbs_Emu* this, blip_time_t duration ); -void Set_bank( struct Gbs_Emu* this, int ); -void Update_timer( struct Gbs_Emu* this ); +blargg_err_t run_clocks( struct Gbs_Emu* this, blip_time_t duration ); +void set_bank( struct Gbs_Emu* this, int ); +void update_timer( struct Gbs_Emu* this ); // Runs CPU until time becomes >= 0 -void Run_cpu( struct Gbs_Emu* this ); +void run_cpu( struct Gbs_Emu* this ); // Reads/writes memory and I/O -int Read_mem( struct Gbs_Emu* this, addr_t addr ); -void Write_mem( struct Gbs_Emu* this, addr_t addr, int data ); +int read_mem( struct Gbs_Emu* this, addr_t addr ); +void write_mem( struct Gbs_Emu* this, addr_t addr, int data ); // Current time static inline blip_time_t Time( struct Gbs_Emu* this ) @@ -195,6 +187,6 @@ static inline blip_time_t Time( struct Gbs_Emu* this ) return Cpu_time( &this->cpu ) + this->end_time; } -void Jsr_then_stop( struct Gbs_Emu* this, byte const [] ); +void jsr_then_stop( struct Gbs_Emu* this, byte const [] ); #endif diff --git a/apps/codecs/libgme/gme_types.h b/apps/codecs/libgme/gme_types.h deleted file mode 100644 index 06226f4aa1..0000000000 --- a/apps/codecs/libgme/gme_types.h +++ /dev/null @@ -1,21 +0,0 @@ -#ifndef GME_TYPES_H -#define GME_TYPES_H - -/* - * This is a default gme_types.h for use when *not* using - * CMake. If CMake is in use gme_types.h.in will be - * processed instead. - */ -#define USE_GME_AY -#define USE_GME_GBS -#define USE_GME_GYM -#define USE_GME_HES -#define USE_GME_KSS -#define USE_GME_NSF -#define USE_GME_NSFE -#define USE_GME_SAP -#define USE_GME_SPC -/* VGM and VGZ are a package deal */ -#define USE_GME_VGM - -#endif /* GME_TYPES_H */ diff --git a/apps/codecs/libgme/hes_apu.c b/apps/codecs/libgme/hes_apu.c index 2a831426f5..054b164a9a 100644 --- a/apps/codecs/libgme/hes_apu.c +++ b/apps/codecs/libgme/hes_apu.c @@ -18,8 +18,7 @@ Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ enum { center_waves = 1 }; // reduces asymmetry and clamping when starting notes -static void Apu_balance_changed( struct Hes_Apu* this, struct Hes_Osc* osc ); -static void Apu_balance_changed( struct Hes_Apu* this, struct Hes_Osc* osc ) +static void balance_changed( struct Hes_Apu* this, struct Hes_Osc* osc ) { static short const log_table [32] = { // ~1.5 db per step #define ENTRY( factor ) (short) (factor * amp_range / 31.0 + 0.5) @@ -42,27 +41,40 @@ static void Apu_balance_changed( struct Hes_Apu* this, struct Hes_Osc* osc ) int right = vol + (osc->balance << 1 & 0x1E) + (this->balance << 1 & 0x1E); if ( right < 0 ) right = 0; - left = log_table [left ]; - right = log_table [right]; - // optimizing for the common case of being centered also allows easy // panning using Effects_Buffer - osc->outputs [0] = osc->chans [0]; // center - osc->outputs [1] = 0; - if ( left != right ) + + // Separate balance into center volume and additional on either left or right + osc->output [0] = osc->outputs [0]; // center + osc->output [1] = osc->outputs [2]; // right + int base = log_table [left ]; + int side = log_table [right] - base; + if ( side < 0 ) + { + base += side; + side = -side; + osc->output [1] = osc->outputs [1]; // left + } + + // Optimize when output is far left, center, or far right + if ( !base || osc->output [0] == osc->output [1] ) { - osc->outputs [0] = osc->chans [1]; // left - osc->outputs [1] = osc->chans [2]; // right + base += side; + side = 0; + osc->output [0] = osc->output [1]; + osc->output [1] = NULL; + osc->last_amp [1] = 0; } if ( center_waves ) { - osc->last_amp [0] += (left - osc->volume [0]) * 16; - osc->last_amp [1] += (right - osc->volume [1]) * 16; + // TODO: this can leave a non-zero level in a buffer (minor) + osc->last_amp [0] += (base - osc->volume [0]) * 16; + osc->last_amp [1] += (side - osc->volume [1]) * 16; } - osc->volume [0] = left; - osc->volume [1] = right; + osc->volume [0] = base; + osc->volume [1] = side; } void Apu_init( struct Hes_Apu* this ) @@ -71,11 +83,11 @@ void Apu_init( struct Hes_Apu* this ) do { osc--; - osc->outputs [0] = 0; - osc->outputs [1] = 0; - osc->chans [0] = 0; - osc->chans [1] = 0; - osc->chans [2] = 0; + osc->output [0] = NULL; + osc->output [1] = NULL; + osc->outputs [0] = NULL; + osc->outputs [1] = NULL; + osc->outputs [2] = NULL; } while ( osc != this->oscs ); @@ -92,139 +104,183 @@ void Apu_reset( struct Hes_Apu* this ) { osc--; memset( osc, 0, offsetof (struct Hes_Osc,outputs) ); - osc->noise_lfsr = 1; + osc->lfsr = 1; osc->control = 0x40; osc->balance = 0xFF; } while ( osc != this->oscs ); + + // Only last two oscs support noise + this->oscs [osc_count - 2].lfsr = 0x200C3; // equivalent to 1 in Fibonacci LFSR + this->oscs [osc_count - 1].lfsr = 0x200C3; } -void Apu_osc_output( struct Hes_Apu* this, int index, struct Blip_Buffer* center, struct Blip_Buffer* left, struct Blip_Buffer* right ) +void Apu_osc_output( struct Hes_Apu* this, int i, struct Blip_Buffer* center, struct Blip_Buffer* left, struct Blip_Buffer* right ) { - require( (unsigned) index < osc_count ); - this->oscs [index].chans [0] = center; - this->oscs [index].chans [1] = left; - this->oscs [index].chans [2] = right; + // Must be silent (all NULL), mono (left and right NULL), or stereo (none NULL) + require( !center || (center && !left && !right) || (center && left && right) ); + require( (unsigned) i < osc_count ); // fails if you pass invalid osc index - struct Hes_Osc* osc = &this->oscs [osc_count]; - do + if ( !center || !left || !right ) { - osc--; - Apu_balance_changed( this, osc ); + left = center; + right = center; } - while ( osc != this->oscs ); + + struct Hes_Osc* o = &this->oscs [i]; + o->outputs [0] = center; + o->outputs [1] = right; + o->outputs [2] = left; + balance_changed( this, o ); } -void Osc_run_until( struct Hes_Osc* this, struct Blip_Synth* synth_, blip_time_t end_time ) +void run_osc( struct Hes_Osc* o, struct Blip_Synth* syn, blip_time_t end_time ) { - struct Blip_Buffer* const osc_outputs_0 = this->outputs [0]; // cache often-used values - if ( osc_outputs_0 && this->control & 0x80 ) + int vol0 = o->volume [0]; + int vol1 = o->volume [1]; + int dac = o->dac; + + struct Blip_Buffer* out0 = o->output [0]; // cache often-used values + struct Blip_Buffer* out1 = o->output [1]; + if ( !(o->control & 0x80) ) + out0 = NULL; + + if ( out0 ) { - int dac = this->dac; - - int const volume_0 = this->volume [0]; + // Update amplitudes + if ( out1 ) { - int delta = dac * volume_0 - this->last_amp [0]; + int delta = dac * vol1 - o->last_amp [1]; if ( delta ) - Synth_offset( synth_, this->last_time, delta, osc_outputs_0 ); - Blip_set_modified( osc_outputs_0 ); + { + Synth_offset( syn, o->last_time, delta, out1 ); + Blip_set_modified( out1 ); + } } - - struct Blip_Buffer* const osc_outputs_1 = this->outputs [1]; - int const volume_1 = this->volume [1]; - if ( osc_outputs_1 ) + int delta = dac * vol0 - o->last_amp [0]; + if ( delta ) { - int delta = dac * volume_1 - this->last_amp [1]; - if ( delta ) - Synth_offset( synth_, this->last_time, delta, osc_outputs_1 ); - Blip_set_modified( osc_outputs_1 ); + Synth_offset( syn, o->last_time, delta, out0 ); + Blip_set_modified( out0 ); } - blip_time_t time = this->last_time + this->delay; + // Don't generate if silent + if ( !(vol0 | vol1) ) + out0 = NULL; + } + + // Generate noise + int noise = 0; + if ( o->lfsr ) + { + noise = o->noise & 0x80; + + blip_time_t time = o->last_time + o->noise_delay; if ( time < end_time ) { - if ( this->noise & 0x80 ) + int period = (~o->noise & 0x1F) * 128; + if ( !period ) + period = 64; + + if ( noise && out0 ) { - if ( volume_0 | volume_1 ) + unsigned lfsr = o->lfsr; + do { - // noise - int const period = (32 - (this->noise & 0x1F)) * 64; // TODO: correct? - unsigned noise_lfsr = this->noise_lfsr; - do + int new_dac = -(lfsr & 1); + lfsr = (lfsr >> 1) ^ (0x30061 & new_dac); + + int delta = (new_dac &= 0x1F) - dac; + if ( delta ) { - int new_dac = 0x1F & -(noise_lfsr >> 1 & 1); - // Implemented using "Galios configuration" - // TODO: find correct LFSR algorithm - noise_lfsr = (noise_lfsr >> 1) ^ (0xE008 & -(noise_lfsr & 1)); - //noise_lfsr = (noise_lfsr >> 1) ^ (0x6000 & -(noise_lfsr & 1)); - int delta = new_dac - dac; - if ( delta ) - { - dac = new_dac; - Synth_offset( synth_, time, delta * volume_0, osc_outputs_0 ); - if ( osc_outputs_1 ) - Synth_offset( synth_, time, delta * volume_1, osc_outputs_1 ); - } - time += period; + dac = new_dac; + Synth_offset( syn, time, delta * vol0, out0 ); + if ( out1 ) + Synth_offset( syn, time, delta * vol1, out1 ); } - while ( time < end_time ); - - this->noise_lfsr = noise_lfsr; - assert( noise_lfsr ); + time += period; } - } - else if ( !(this->control & 0x40) ) - { - // wave - int phase = (this->phase + 1) & 0x1F; // pre-advance for optimal inner loop - int period = this->period * 2; - if ( period >= 14 && (volume_0 | volume_1) ) + while ( time < end_time ); + + if ( !lfsr ) { - do - { - int new_dac = this->wave [phase]; - phase = (phase + 1) & 0x1F; - int delta = new_dac - dac; - if ( delta ) - { - dac = new_dac; - Synth_offset( synth_, time, delta * volume_0, osc_outputs_0 ); - if ( osc_outputs_1 ) - Synth_offset( synth_, time, delta * volume_1, osc_outputs_1 ); - } - time += period; - } - while ( time < end_time ); + lfsr = 1; + check( false ); } - else + o->lfsr = lfsr; + + Blip_set_modified( out0 ); + if ( out1 ) + Blip_set_modified( out1 ); + } + else + { + // Maintain phase when silent + int count = (end_time - time + period - 1) / period; + time += count * period; + + // not worth it + //while ( count-- ) + // o->lfsr = (o->lfsr >> 1) ^ (0x30061 * (o->lfsr & 1)); + } + } + o->noise_delay = time - end_time; + } + + // Generate wave + blip_time_t time = o->last_time + o->delay; + if ( time < end_time ) + { + int phase = (o->phase + 1) & 0x1F; // pre-advance for optimal inner loop + int period = o->period * 2; + + if ( period >= 14 && out0 && !((o->control & 0x40) | noise) ) + { + do + { + int new_dac = o->wave [phase]; + phase = (phase + 1) & 0x1F; + int delta = new_dac - dac; + if ( delta ) { - if ( !period ) - { - // TODO: Gekisha Boy assumes that period = 0 silences wave - //period = 0x1000 * 2; - period = 1; - //if ( !(volume_0 | volume_1) ) - // dprintf( "Used period 0\n" ); - } - - // maintain phase when silent - blargg_long count = (end_time - time + period - 1) / period; - phase += count; // phase will be masked below - time += count * period; + dac = new_dac; + Synth_offset( syn, time, delta * vol0, out0 ); + if ( out1 ) + Synth_offset( syn, time, delta * vol1, out1 ); } - this->phase = (phase - 1) & 0x1F; // undo pre-advance + time += period; } + while ( time < end_time ); + Blip_set_modified( out0 ); + if ( out1 ) + Blip_set_modified( out1 ); + } + else + { + // Maintain phase when silent + int count = end_time - time; + if ( !period ) + period = 1; + count = (count + period - 1) / period; + + phase += count; // phase will be masked below + time += count * period; } - time -= end_time; - if ( time < 0 ) - time = 0; - this->delay = time; - this->dac = dac; - this->last_amp [0] = dac * volume_0; - this->last_amp [1] = dac * volume_1; + // TODO: Find whether phase increments even when both volumes are zero. + // CAN'T simply check for out0 being non-NULL, since it could be NULL + // if channel is muted in player, but still has non-zero volume. + // City Hunter breaks when this check is removed. + if ( !(o->control & 0x40) && (vol0 | vol1) ) + o->phase = (phase - 1) & 0x1F; // undo pre-advance } - this->last_time = end_time; + o->delay = time - end_time; + check( o->delay >= 0 ); + + o->last_time = end_time; + o->dac = dac; + o->last_amp [0] = dac * vol0; + o->last_amp [1] = dac * vol1; } void Apu_write_data( struct Hes_Apu* this, blip_time_t time, int addr, int data ) @@ -243,8 +299,8 @@ void Apu_write_data( struct Hes_Apu* this, blip_time_t time, int addr, int data do { osc--; - Osc_run_until( osc, &this->synth, time ); - Apu_balance_changed( this, this->oscs ); + run_osc( osc, &this->synth, time ); + balance_changed( this, this->oscs ); } while ( osc != this->oscs ); } @@ -252,7 +308,7 @@ void Apu_write_data( struct Hes_Apu* this, blip_time_t time, int addr, int data else if ( this->latch < osc_count ) { struct Hes_Osc* osc = &this->oscs [this->latch]; - Osc_run_until( osc, &this->synth, time ); + run_osc( osc, &this->synth, time ); switch ( addr ) { case 0x802: @@ -267,12 +323,12 @@ void Apu_write_data( struct Hes_Apu* this, blip_time_t time, int addr, int data if ( osc->control & 0x40 & ~data ) osc->phase = 0; osc->control = data; - Apu_balance_changed( this, osc ); + balance_changed( this, osc ); break; case 0x805: osc->balance = data; - Apu_balance_changed( this, osc ); + balance_changed( this, osc ); break; case 0x806: @@ -289,9 +345,9 @@ void Apu_write_data( struct Hes_Apu* this, blip_time_t time, int addr, int data break; case 0x807: - if ( osc >= &this->oscs [4] ) - osc->noise = data; + osc->noise = data; break; + case 0x809: if ( !(data & 0x80) && (data & 0x03) != 0 ) { dprintf( "HES LFO not supported\n" ); @@ -307,7 +363,7 @@ void Apu_end_frame( struct Hes_Apu* this, blip_time_t end_time ) { osc--; if ( end_time > osc->last_time ) - Osc_run_until( osc, &this->synth, end_time ); + run_osc( osc, &this->synth, end_time ); assert( osc->last_time >= end_time ); osc->last_time -= end_time; } diff --git a/apps/codecs/libgme/hes_apu.h b/apps/codecs/libgme/hes_apu.h index 8f8a525108..0265e6a3ad 100644 --- a/apps/codecs/libgme/hes_apu.h +++ b/apps/codecs/libgme/hes_apu.h @@ -5,40 +5,46 @@ #define HES_APU_H #include "blargg_common.h" +#include "blargg_source.h" #include "blip_buffer.h" enum { amp_range = 0x8000 }; -enum { osc_count = 6 }; -enum { start_addr = 0x0800 }; -enum { end_addr = 0x0809 }; +enum { osc_count = 6 }; // 0 <= chan < osc_count + +// Registers are at io_addr to io_addr+io_size-1 +enum { apu_io_addr = 0x0800 }; +enum { apu_io_size = 10 }; struct Hes_Osc { - unsigned char wave [32]; + byte wave [32]; + int delay; + int period; + int phase; + + int noise_delay; + byte noise; + unsigned lfsr; + + byte control; + byte balance; + byte dac; short volume [2]; int last_amp [2]; - int delay; - int period; - unsigned char noise; - unsigned char phase; - unsigned char balance; - unsigned char dac; - blip_time_t last_time; - struct Blip_Buffer* outputs [2]; - struct Blip_Buffer* chans [3]; - unsigned noise_lfsr; - unsigned char control; + blip_time_t last_time; + struct Blip_Buffer* output [2]; + struct Blip_Buffer* outputs [3]; }; void Osc_run_until( struct Hes_Osc* this, struct Blip_Synth* synth, blip_time_t ); struct Hes_Apu { - struct Hes_Osc oscs [osc_count]; int latch; int balance; struct Blip_Synth synth; + struct Hes_Osc oscs [osc_count]; }; // Init HES apu sound chip @@ -48,7 +54,12 @@ void Apu_init( struct Hes_Apu* this ); void Apu_reset( struct Hes_Apu* this ); void Apu_osc_output( struct Hes_Apu* this, int index, struct Blip_Buffer* center, struct Blip_Buffer* left, struct Blip_Buffer* right ); + +// Emulates to time t, then writes data to addr void Apu_write_data( struct Hes_Apu* this, blip_time_t, int addr, int data ); + +// Emulates to time t, then subtracts t from the current time. +// OK if previous write call had time slightly after t. void Apu_end_frame( struct Hes_Apu* this, blip_time_t ); static inline void Apu_volume( struct Hes_Apu* this, int v ) { Synth_volume( &this->synth, (v*9)/5 / osc_count / amp_range ); } diff --git a/apps/codecs/libgme/hes_apu_adpcm.h b/apps/codecs/libgme/hes_apu_adpcm.h index 4a2afb3e2a..afe160bb9c 100644 --- a/apps/codecs/libgme/hes_apu_adpcm.h +++ b/apps/codecs/libgme/hes_apu_adpcm.h @@ -12,8 +12,8 @@ enum { adpcm_amp_range = 2048 }; enum { adpcm_osc_count = 1 }; // 0 <= chan < osc_count // Registers are at io_addr to io_addr+io_size-1 -enum { io_addr = 0x1800 }; -enum { io_size = 0x400 }; +enum { adpcm_io_addr = 0x1800 }; +enum { adpcm_io_size = 0x400 }; struct State { diff --git a/apps/codecs/libgme/hes_cpu.c b/apps/codecs/libgme/hes_cpu.c index 74b90593f2..6b833b3b98 100644 --- a/apps/codecs/libgme/hes_cpu.c +++ b/apps/codecs/libgme/hes_cpu.c @@ -1,6 +1,6 @@ // Game_Music_Emu 0.5.2. http://www.slack.net/~ant/ -#include "hes_cpu.h" +#include "hes_emu.h" #include "blargg_endian.h" @@ -17,1305 +17,105 @@ details. You should have received a copy of the GNU Lesser General Public License along with this module; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -// TODO: support T flag, including clearing it at appropriate times? - -// all zero-page should really use whatever is at page 1, but that would -// reduce efficiency quite a bit -int const ram_addr = 0x2000; - -#define FLUSH_TIME() (void) (s.time = s_time) -#define CACHE_TIME() (void) (s_time = s.time) - -#include "hes_cpu_io.h" - #include "blargg_source.h" +#define PAGE HES_CPU_PAGE -#ifdef BLARGG_NONPORTABLE - #define PAGE_OFFSET( addr ) (addr) -#else - #define PAGE_OFFSET( addr ) ((addr) & (page_size - 1)) -#endif - -// status flags -int const st_n = 0x80; -int const st_v = 0x40; -int const st_t = 0x20; -int const st_b = 0x10; -int const st_d = 0x08; -int const st_i = 0x04; -int const st_z = 0x02; -int const st_c = 0x01; - -void Cpu_init( struct Hes_Cpu* this ) +int read_mem( struct Hes_Emu* this, hes_addr_t addr ) { - this->state = &this->state_; + check( addr < 0x10000 ); + int result = *Cpu_get_code( &this->cpu, addr ); + if ( this->cpu.mmr [PAGE( addr )] == 0xFF ) + result = read_mem_( this, addr ); + return result; } -void Cpu_reset( struct Hes_Cpu* this ) +void write_mem( struct Hes_Emu* this, hes_addr_t addr, int data ) { - check( this->state == &state_ ); - this->state = &this->state_; - - this->state_.time = 0; - this->state_.base = 0; - this->irq_time = (hes_time_t)future_hes_time; - this->end_time = (hes_time_t)future_hes_time; - - this->r.status = st_i; - this->r.sp = 0; - this->r.pc = 0; - this->r.a = 0; - this->r.x = 0; - this->r.y = 0; - - blargg_verify_byte_order(); + check( addr < 0x10000 ); + byte* out = this->write_pages [PAGE( addr )]; + if ( out ) + out [addr & (page_size - 1)] = data; + else if ( this->cpu.mmr [PAGE( addr )] == 0xFF ) + write_mem_( this, addr, data ); } -void Cpu_set_mmr( struct Hes_Emu* this, int reg, int bank ) +void set_mmr( struct Hes_Emu* this, int page, int bank ) { - assert( (unsigned) reg <= page_count ); // allow page past end to be set - assert( (unsigned) bank < 0x100 ); - this->cpu.mmr [reg] = bank; - uint8_t const* code = CPU_SET_MMR( this, reg, bank ); - this->cpu.state->code_map [reg] = code - PAGE_OFFSET( reg << page_shift ); -} - -#define TIME (s_time + s.base) - -#define READ( addr ) CPU_READ( this, (addr), TIME ) -#define WRITE( addr, data ) {CPU_WRITE( this, (addr), (data), TIME );} -#define READ_LOW( addr ) (cpu->ram [(int) (addr)]) -#define WRITE_LOW( addr, data ) (void) (READ_LOW( addr ) = (data)) -#define READ_PROG( addr ) (s.code_map [(addr) >> page_shift] [PAGE_OFFSET( addr )]) - -#define SET_SP( v ) (sp = ((v) + 1) | 0x100) -#define GET_SP() ((sp - 1) & 0xFF) -#define PUSH( v ) ((sp = (sp - 1) | 0x100), WRITE_LOW( sp, v )) - -// even on x86, using short and unsigned char was slower -typedef int fint16; -typedef unsigned fuint16; -typedef unsigned fuint8; -typedef blargg_long fint32; - -bool Cpu_run( struct Hes_Emu* this, hes_time_t end_time ) -{ - bool illegal_encountered = false; - - // Set cpu end time - struct Hes_Cpu* cpu = &this->cpu; - cpu->state->time += Cpu_update_end_time( cpu, cpu->r.status, (cpu->end_time = end_time), cpu->irq_time ); - - struct state_t s = cpu->state_; - cpu->state = &s; - - // even on x86, using s.time in place of s_time was slower - fint16 s_time = s.time; - - struct registers_t* r = &cpu->r; - - // registers - fuint16 pc = r->pc; - fuint8 a = r->a; - fuint8 x = r->x; - fuint8 y = r->y; - fuint16 sp; - SET_SP( r->sp ); - - #define IS_NEG (nz & 0x8080) - - #define CALC_STATUS( out ) do {\ - out = status & (st_v | st_d | st_i);\ - out |= ((nz >> 8) | nz) & st_n;\ - out |= c >> 8 & st_c;\ - if ( !(nz & 0xFF) ) out |= st_z;\ - } while ( 0 ) - - #define SET_STATUS( in ) do {\ - status = in & (st_v | st_d | st_i);\ - nz = in << 8;\ - c = nz;\ - nz |= ~in & st_z;\ - } while ( 0 ) - - fuint8 status; - fuint16 c; // carry set if (c & 0x100) != 0 - fuint16 nz; // Z set if (nz & 0xFF) == 0, N set if (nz & 0x8080) != 0 - { - fuint8 temp = r->status; - SET_STATUS( temp ); - } - - goto loop; -branch_not_taken: - s_time -= 2; -loop: - - #ifndef NDEBUG - { - hes_time_t correct = end_time_; - if ( !(status & st_i) && correct > irq_time_ ) - correct = irq_time_; - check( s.base == correct ); - /* - static long count; - if ( count == 1844 ) Debugger(); - if ( s.base != correct ) dprintf( "%ld\n", count ); - count++; - */ - } - #endif - - check( (unsigned) GET_SP() < 0x100 ); - check( (unsigned) a < 0x100 ); - check( (unsigned) x < 0x100 ); - - uint8_t const* instr = s.code_map [pc >> page_shift]; - fuint8 opcode; - - // TODO: eliminate this special case - #ifdef BLARGG_NONPORTABLE - opcode = instr [pc]; - pc++; - instr += pc; - #else - instr += PAGE_OFFSET( pc ); - opcode = *instr++; - pc++; - #endif - - // TODO: each reference lists slightly different timing values, ugh - static uint8_t const clock_table [256] = - {// 0 1 2 3 4 5 6 7 8 9 A B C D E F - 1,7,3, 4,6,4,6,7,3,2,2,2,7,5,7,6,// 0 - 4,7,7, 4,6,4,6,7,2,5,2,2,7,5,7,6,// 1 - 7,7,3, 4,4,4,6,7,4,2,2,2,5,5,7,6,// 2 - 4,7,7, 2,4,4,6,7,2,5,2,2,5,5,7,6,// 3 - 7,7,3, 4,8,4,6,7,3,2,2,2,4,5,7,6,// 4 - 4,7,7, 5,2,4,6,7,2,5,3,2,2,5,7,6,// 5 - 7,7,2, 2,4,4,6,7,4,2,2,2,7,5,7,6,// 6 - 4,7,7,17,4,4,6,7,2,5,4,2,7,5,7,6,// 7 - 4,7,2, 7,4,4,4,7,2,2,2,2,5,5,5,6,// 8 - 4,7,7, 8,4,4,4,7,2,5,2,2,5,5,5,6,// 9 - 2,7,2, 7,4,4,4,7,2,2,2,2,5,5,5,6,// A - 4,7,7, 8,4,4,4,7,2,5,2,2,5,5,5,6,// B - 2,7,2,17,4,4,6,7,2,2,2,2,5,5,7,6,// C - 4,7,7,17,2,4,6,7,2,5,3,2,2,5,7,6,// D - 2,7,2,17,4,4,6,7,2,2,2,2,5,5,7,6,// E - 4,7,7,17,2,4,6,7,2,5,4,2,2,5,7,6 // F - }; // 0x00 was 8 - - fuint16 data; - data = clock_table [opcode]; - if ( (s_time += data) >= 0 ) - goto possibly_out_of_time; -almost_out_of_time: - - data = *instr; - - #ifdef HES_CPU_LOG_H - log_cpu( "new", pc - 1, opcode, instr [0], instr [1], instr [2], - instr [3], instr [4], instr [5] ); - //log_opcode( opcode ); - #endif - - switch ( opcode ) - { -possibly_out_of_time: - if ( s_time < (int) data ) - goto almost_out_of_time; - s_time -= data; - goto out_of_time; - -// Macros - -#define GET_MSB() (instr [1]) -#define ADD_PAGE( out ) (pc++, out = data + 0x100 * GET_MSB()); -#define GET_ADDR() GET_LE16( instr ) - -// TODO: is the penalty really always added? the original 6502 was much better -//#define PAGE_CROSS_PENALTY( lsb ) (void) (s_time += (lsb) >> 8) -#define PAGE_CROSS_PENALTY( lsb ) - -// Branch - -// TODO: more efficient way to handle negative branch that wraps PC around -#define BRANCH( cond )\ -{\ - fint16 offset = (int8_t) data;\ - pc++;\ - if ( !(cond) ) goto branch_not_taken;\ - pc = (uint16_t) (pc + offset);\ - goto loop;\ -} - - case 0xF0: // BEQ - BRANCH( !((uint8_t) nz) ); - - case 0xD0: // BNE - BRANCH( (uint8_t) nz ); - - case 0x10: // BPL - BRANCH( !IS_NEG ); - - case 0x90: // BCC - BRANCH( !(c & 0x100) ) - - case 0x30: // BMI - BRANCH( IS_NEG ) - - case 0x50: // BVC - BRANCH( !(status & st_v) ) - - case 0x70: // BVS - BRANCH( status & st_v ) - - case 0xB0: // BCS - BRANCH( c & 0x100 ) - - case 0x80: // BRA - branch_taken: - BRANCH( true ); - - case 0xFF: - if ( pc == idle_addr + 1 ) - goto idle_done; - case 0x0F: // BBRn - case 0x1F: - case 0x2F: - case 0x3F: - case 0x4F: - case 0x5F: - case 0x6F: - case 0x7F: - case 0x8F: // BBSn - case 0x9F: - case 0xAF: - case 0xBF: - case 0xCF: - case 0xDF: - case 0xEF: { - fuint16 t = 0x101 * READ_LOW( data ); - t ^= 0xFF; - pc++; - data = GET_MSB(); - BRANCH( t & (1 << (opcode >> 4)) ) - } - - case 0x4C: // JMP abs - pc = GET_ADDR(); - goto loop; - - case 0x7C: // JMP (ind+X) - data += x; - case 0x6C:{// JMP (ind) - data += 0x100 * GET_MSB(); - pc = GET_LE16( &READ_PROG( data ) ); - goto loop; - } - -// Subroutine - - case 0x44: // BSR - WRITE_LOW( 0x100 | (sp - 1), pc >> 8 ); - sp = (sp - 2) | 0x100; - WRITE_LOW( sp, pc ); - goto branch_taken; - - case 0x20: { // JSR - fuint16 temp = pc + 1; - pc = GET_ADDR(); - WRITE_LOW( 0x100 | (sp - 1), temp >> 8 ); - sp = (sp - 2) | 0x100; - WRITE_LOW( sp, temp ); - goto loop; - } - - case 0x60: // RTS - pc = 0x100 * READ_LOW( 0x100 | (sp - 0xFF) ); - pc += 1 + READ_LOW( sp ); - sp = (sp - 0xFE) | 0x100; - goto loop; - - case 0x00: // BRK - goto handle_brk; - -// Common - - case 0xBD:{// LDA abs,X - PAGE_CROSS_PENALTY( data + x ); - fuint16 addr = GET_ADDR() + x; - pc += 2; - CPU_READ_FAST( this, addr, TIME, nz ); - a = nz; - goto loop; - } - - case 0x9D:{// STA abs,X - fuint16 addr = GET_ADDR() + x; - pc += 2; - CPU_WRITE_FAST( this, addr, a, TIME ); - goto loop; - } - - case 0x95: // STA zp,x - data = (uint8_t) (data + x); - case 0x85: // STA zp - pc++; - WRITE_LOW( data, a ); - goto loop; - - case 0xAE:{// LDX abs - fuint16 addr = GET_ADDR(); - pc += 2; - CPU_READ_FAST( this, addr, TIME, nz ); - x = nz; - goto loop; - } - - case 0xA5: // LDA zp - a = nz = READ_LOW( data ); - pc++; - goto loop; - -// Load/store - + this->write_pages [page] = 0; + byte* data = Rom_at_addr( &this->rom, bank * page_size ); + if ( bank >= 0x80 ) { - fuint16 addr; - case 0x91: // STA (ind),Y - addr = 0x100 * READ_LOW( (uint8_t) (data + 1) ); - addr += READ_LOW( data ) + y; - pc++; - goto sta_ptr; - - case 0x81: // STA (ind,X) - data = (uint8_t) (data + x); - case 0x92: // STA (ind) - addr = 0x100 * READ_LOW( (uint8_t) (data + 1) ); - addr += READ_LOW( data ); - pc++; - goto sta_ptr; - - case 0x99: // STA abs,Y - data += y; - case 0x8D: // STA abs - addr = data + 0x100 * GET_MSB(); - pc += 2; - sta_ptr: - CPU_WRITE_FAST( this, addr, a, TIME ); - goto loop; - } - - { - fuint16 addr; - case 0xA1: // LDA (ind,X) - data = (uint8_t) (data + x); - case 0xB2: // LDA (ind) - addr = 0x100 * READ_LOW( (uint8_t) (data + 1) ); - addr += READ_LOW( data ); - pc++; - goto a_nz_read_addr; - - case 0xB1:// LDA (ind),Y - addr = READ_LOW( data ) + y; - PAGE_CROSS_PENALTY( addr ); - addr += 0x100 * READ_LOW( (uint8_t) (data + 1) ); - pc++; - goto a_nz_read_addr; - - case 0xB9: // LDA abs,Y - data += y; - PAGE_CROSS_PENALTY( data ); - case 0xAD: // LDA abs - addr = data + 0x100 * GET_MSB(); - pc += 2; - a_nz_read_addr: - CPU_READ_FAST( this, addr, TIME, nz ); - a = nz; - goto loop; - } - - case 0xBE:{// LDX abs,y - PAGE_CROSS_PENALTY( data + y ); - fuint16 addr = GET_ADDR() + y; - pc += 2; - FLUSH_TIME(); - x = nz = READ( addr ); - CACHE_TIME(); - goto loop; - } - - case 0xB5: // LDA zp,x - a = nz = READ_LOW( (uint8_t) (data + x) ); - pc++; - goto loop; - - case 0xA9: // LDA #imm - pc++; - a = data; - nz = data; - goto loop; - -// Bit operations - - case 0x3C: // BIT abs,x - data += x; - case 0x2C:{// BIT abs - fuint16 addr; - ADD_PAGE( addr ); - FLUSH_TIME(); - nz = READ( addr ); - CACHE_TIME(); - goto bit_common; - } - case 0x34: // BIT zp,x - data = (uint8_t) (data + x); - case 0x24: // BIT zp - data = READ_LOW( data ); - case 0x89: // BIT imm - nz = data; - bit_common: - pc++; - status &= ~st_v; - status |= nz & st_v; - if ( nz & a ) - goto loop; // Z should be clear, and nz must be non-zero if nz & a is - nz <<= 8; // set Z flag without affecting N flag - goto loop; + data = 0; + switch ( bank ) + { + case 0xF8: + data = this->ram; + break; - { - fuint16 addr; + case 0xF9: + case 0xFA: + case 0xFB: + data = &this->sgx [(bank - 0xF9) * page_size]; + break; - case 0xB3: // TST abs,x - addr = GET_MSB() + x; - goto tst_abs; - - case 0x93: // TST abs - addr = GET_MSB(); - tst_abs: - addr += 0x100 * instr [2]; - pc++; - FLUSH_TIME(); - nz = READ( addr ); - CACHE_TIME(); - goto tst_common; - } - - case 0xA3: // TST zp,x - nz = READ_LOW( (uint8_t) (GET_MSB() + x) ); - goto tst_common; - - case 0x83: // TST zp - nz = READ_LOW( GET_MSB() ); - tst_common: - pc += 2; - status &= ~st_v; - status |= nz & st_v; - if ( nz & data ) - goto loop; // Z should be clear, and nz must be non-zero if nz & data is - nz <<= 8; // set Z flag without affecting N flag - goto loop; - - { - fuint16 addr; - case 0x0C: // TSB abs - case 0x1C: // TRB abs - addr = GET_ADDR(); - pc++; - goto txb_addr; - - // TODO: everyone lists different behaviors for the status flags, ugh - case 0x04: // TSB zp - case 0x14: // TRB zp - addr = data + ram_addr; - txb_addr: - FLUSH_TIME(); - nz = a | READ( addr ); - if ( opcode & 0x10 ) - nz ^= a; // bits from a will already be set, so this clears them - status &= ~st_v; - status |= nz & st_v; - pc++; - WRITE( addr, nz ); - CACHE_TIME(); - goto loop; - } - - case 0x07: // RMBn - case 0x17: - case 0x27: - case 0x37: - case 0x47: - case 0x57: - case 0x67: - case 0x77: - pc++; - READ_LOW( data ) &= ~(1 << (opcode >> 4)); - goto loop; - - case 0x87: // SMBn - case 0x97: - case 0xA7: - case 0xB7: - case 0xC7: - case 0xD7: - case 0xE7: - case 0xF7: - pc++; - READ_LOW( data ) |= 1 << ((opcode >> 4) - 8); - goto loop; - -// Load/store - - case 0x9E: // STZ abs,x - data += x; - case 0x9C: // STZ abs - ADD_PAGE( data ); - pc++; - FLUSH_TIME(); - WRITE( data, 0 ); - CACHE_TIME(); - goto loop; - - case 0x74: // STZ zp,x - data = (uint8_t) (data + x); - case 0x64: // STZ zp - pc++; - WRITE_LOW( data, 0 ); - goto loop; - - case 0x94: // STY zp,x - data = (uint8_t) (data + x); - case 0x84: // STY zp - pc++; - WRITE_LOW( data, y ); - goto loop; - - case 0x96: // STX zp,y - data = (uint8_t) (data + y); - case 0x86: // STX zp - pc++; - WRITE_LOW( data, x ); - goto loop; - - case 0xB6: // LDX zp,y - data = (uint8_t) (data + y); - case 0xA6: // LDX zp - data = READ_LOW( data ); - case 0xA2: // LDX #imm - pc++; - x = data; - nz = data; - goto loop; - - case 0xB4: // LDY zp,x - data = (uint8_t) (data + x); - case 0xA4: // LDY zp - data = READ_LOW( data ); - case 0xA0: // LDY #imm - pc++; - y = data; - nz = data; - goto loop; - - case 0xBC: // LDY abs,X - data += x; - PAGE_CROSS_PENALTY( data ); - case 0xAC:{// LDY abs - fuint16 addr = data + 0x100 * GET_MSB(); - pc += 2; - FLUSH_TIME(); - y = nz = READ( addr ); - CACHE_TIME(); - goto loop; - } - - { - fuint8 temp; - case 0x8C: // STY abs - temp = y; - goto store_abs; - - case 0x8E: // STX abs - temp = x; - store_abs: - { - fuint16 addr = GET_ADDR(); - pc += 2; - FLUSH_TIME(); - WRITE( addr, temp ); - CACHE_TIME(); - goto loop; - } + default: + /* if ( bank != 0xFF ) + dprintf( "Unmapped bank $%02X\n", bank ); */ + data = this->rom.unmapped; + goto end; + } + + this->write_pages [page] = data; } +end: + Cpu_set_mmr( &this->cpu, page, bank, data ); +} -// Compare - - case 0xEC:{// CPX abs - fuint16 addr = GET_ADDR(); - pc++; - FLUSH_TIME(); - data = READ( addr ); - CACHE_TIME(); - goto cpx_data; - } - - case 0xE4: // CPX zp - data = READ_LOW( data ); - case 0xE0: // CPX #imm - cpx_data: - nz = x - data; - pc++; - c = ~nz; - nz &= 0xFF; - goto loop; - - case 0xCC:{// CPY abs - fuint16 addr = GET_ADDR(); - pc++; - FLUSH_TIME(); - data = READ( addr ); - CACHE_TIME(); - goto cpy_data; - } - - case 0xC4: // CPY zp - data = READ_LOW( data ); - case 0xC0: // CPY #imm - cpy_data: - nz = y - data; - pc++; - c = ~nz; - nz &= 0xFF; - goto loop; - -// Logical +#define READ_FAST( addr, out ) \ +{\ + out = READ_CODE( addr );\ + if ( cpu->mmr [PAGE( addr )] == 0xFF )\ + {\ + FLUSH_TIME();\ + out = read_mem_( this, addr );\ + CACHE_TIME();\ + }\ +} -#define ARITH_ADDR_MODES( op )\ - case op - 0x04: /* (ind,x) */\ - data = (uint8_t) (data + x);\ - case op + 0x0D: /* (ind) */\ - data = 0x100 * READ_LOW( (uint8_t) (data + 1) ) + READ_LOW( data );\ - goto ptr##op;\ - case op + 0x0C:{/* (ind),y */\ - fuint16 temp = READ_LOW( data ) + y;\ - PAGE_CROSS_PENALTY( temp );\ - data = temp + 0x100 * READ_LOW( (uint8_t) (data + 1) );\ - goto ptr##op;\ +#define WRITE_FAST( addr, data ) \ +{\ + int page = PAGE( addr );\ + byte* out = this->write_pages [page];\ + addr &= page_size - 1;\ + if ( out )\ + {\ + out [addr] = data;\ }\ - case op + 0x10: /* zp,X */\ - data = (uint8_t) (data + x);\ - case op + 0x00: /* zp */\ - data = READ_LOW( data );\ - goto imm##op;\ - case op + 0x14: /* abs,Y */\ - data += y;\ - goto ind##op;\ - case op + 0x18: /* abs,X */\ - data += x;\ - ind##op:\ - PAGE_CROSS_PENALTY( data );\ - case op + 0x08: /* abs */\ - ADD_PAGE( data );\ - ptr##op:\ + else if ( cpu->mmr [page] == 0xFF )\ + {\ FLUSH_TIME();\ - data = READ( data );\ + write_mem_( this, addr, data );\ CACHE_TIME();\ - case op + 0x04: /* imm */\ - imm##op: - - ARITH_ADDR_MODES( 0xC5 ) // CMP - nz = a - data; - pc++; - c = ~nz; - nz &= 0xFF; - goto loop; - - ARITH_ADDR_MODES( 0x25 ) // AND - nz = (a &= data); - pc++; - goto loop; - - ARITH_ADDR_MODES( 0x45 ) // EOR - nz = (a ^= data); - pc++; - goto loop; - - ARITH_ADDR_MODES( 0x05 ) // ORA - nz = (a |= data); - pc++; - goto loop; - -// Add/subtract - - ARITH_ADDR_MODES( 0xE5 ) // SBC - data ^= 0xFF; - goto adc_imm; - - ARITH_ADDR_MODES( 0x65 ) // ADC - adc_imm: { - if ( status & st_d ) { - dprintf( "Decimal mode not supported\n" ); - } - fint16 carry = c >> 8 & 1; - fint16 ov = (a ^ 0x80) + carry + (int8_t) data; // sign-extend - status &= ~st_v; - status |= ov >> 2 & 0x40; - c = nz = a + data + carry; - pc++; - a = (uint8_t) nz; - goto loop; - } - -// Shift/rotate - - case 0x4A: // LSR A - c = 0; - case 0x6A: // ROR A - nz = c >> 1 & 0x80; - c = a << 8; - nz |= a >> 1; - a = nz; - goto loop; - - case 0x0A: // ASL A - nz = a << 1; - c = nz; - a = (uint8_t) nz; - goto loop; - - case 0x2A: { // ROL A - nz = a << 1; - fint16 temp = c >> 8 & 1; - c = nz; - nz |= temp; - a = (uint8_t) nz; - goto loop; - } - - case 0x5E: // LSR abs,X - data += x; - case 0x4E: // LSR abs - c = 0; - case 0x6E: // ROR abs - ror_abs: { - ADD_PAGE( data ); - FLUSH_TIME(); - int temp = READ( data ); - nz = (c >> 1 & 0x80) | (temp >> 1); - c = temp << 8; - goto rotate_common; - } - - case 0x3E: // ROL abs,X - data += x; - goto rol_abs; - - case 0x1E: // ASL abs,X - data += x; - case 0x0E: // ASL abs - c = 0; - case 0x2E: // ROL abs - rol_abs: - ADD_PAGE( data ); - nz = c >> 8 & 1; - FLUSH_TIME(); - nz |= (c = READ( data ) << 1); - rotate_common: - pc++; - WRITE( data, (uint8_t) nz ); - CACHE_TIME(); - goto loop; - - case 0x7E: // ROR abs,X - data += x; - goto ror_abs; - - case 0x76: // ROR zp,x - data = (uint8_t) (data + x); - goto ror_zp; - - case 0x56: // LSR zp,x - data = (uint8_t) (data + x); - case 0x46: // LSR zp - c = 0; - case 0x66: // ROR zp - ror_zp: { - int temp = READ_LOW( data ); - nz = (c >> 1 & 0x80) | (temp >> 1); - c = temp << 8; - goto write_nz_zp; - } - - case 0x36: // ROL zp,x - data = (uint8_t) (data + x); - goto rol_zp; - - case 0x16: // ASL zp,x - data = (uint8_t) (data + x); - case 0x06: // ASL zp - c = 0; - case 0x26: // ROL zp - rol_zp: - nz = c >> 8 & 1; - nz |= (c = READ_LOW( data ) << 1); - goto write_nz_zp; - -// Increment/decrement - -#define INC_DEC_AXY( reg, n ) reg = (uint8_t) (nz = reg + n); goto loop; - - case 0x1A: // INA - INC_DEC_AXY( a, +1 ) - - case 0xE8: // INX - INC_DEC_AXY( x, +1 ) - - case 0xC8: // INY - INC_DEC_AXY( y, +1 ) - - case 0x3A: // DEA - INC_DEC_AXY( a, -1 ) - - case 0xCA: // DEX - INC_DEC_AXY( x, -1 ) - - case 0x88: // DEY - INC_DEC_AXY( y, -1 ) - - case 0xF6: // INC zp,x - data = (uint8_t) (data + x); - case 0xE6: // INC zp - nz = 1; - goto add_nz_zp; - - case 0xD6: // DEC zp,x - data = (uint8_t) (data + x); - case 0xC6: // DEC zp - nz = (unsigned) -1; - add_nz_zp: - nz += READ_LOW( data ); - write_nz_zp: - pc++; - WRITE_LOW( data, nz ); - goto loop; - - case 0xFE: // INC abs,x - data = x + GET_ADDR(); - goto inc_ptr; - - case 0xEE: // INC abs - data = GET_ADDR(); - inc_ptr: - nz = 1; - goto inc_common; - - case 0xDE: // DEC abs,x - data = x + GET_ADDR(); - goto dec_ptr; - - case 0xCE: // DEC abs - data = GET_ADDR(); - dec_ptr: - nz = (unsigned) -1; - inc_common: - FLUSH_TIME(); - nz += READ( data ); - pc += 2; - WRITE( data, (uint8_t) nz ); - CACHE_TIME(); - goto loop; - -// Transfer - - case 0xA8: // TAY - y = a; - nz = a; - goto loop; - - case 0x98: // TYA - a = y; - nz = y; - goto loop; - - case 0xAA: // TAX - x = a; - nz = a; - goto loop; - - case 0x8A: // TXA - a = x; - nz = x; - goto loop; - - case 0x9A: // TXS - SET_SP( x ); // verified (no flag change) - goto loop; - - case 0xBA: // TSX - x = nz = GET_SP(); - goto loop; - - #define SWAP_REGS( r1, r2 ) {\ - fuint8 t = r1;\ - r1 = r2;\ - r2 = t;\ - goto loop;\ - } - - case 0x02: // SXY - SWAP_REGS( x, y ); - - case 0x22: // SAX - SWAP_REGS( a, x ); - - case 0x42: // SAY - SWAP_REGS( a, y ); - - case 0x62: // CLA - a = 0; - goto loop; - - case 0x82: // CLX - x = 0; - goto loop; - - case 0xC2: // CLY - y = 0; - goto loop; - -// Stack - - case 0x48: // PHA - PUSH( a ); - goto loop; - - case 0xDA: // PHX - PUSH( x ); - goto loop; - - case 0x5A: // PHY - PUSH( y ); - goto loop; - - case 0x40:{// RTI - fuint8 temp = READ_LOW( sp ); - pc = READ_LOW( 0x100 | (sp - 0xFF) ); - pc |= READ_LOW( 0x100 | (sp - 0xFE) ) * 0x100; - sp = (sp - 0xFD) | 0x100; - data = status; - SET_STATUS( temp ); - r->status = status; // update externally-visible I flag - if ( (data ^ status) & st_i ) - { - hes_time_t new_time = cpu->end_time; - if ( !(status & st_i) && new_time > cpu->irq_time ) - new_time = cpu->irq_time; - blargg_long delta = s.base - new_time; - s.base = new_time; - s_time += delta; - } - goto loop; - } - - #define POP() READ_LOW( sp ); sp = (sp - 0xFF) | 0x100 - - case 0x68: // PLA - a = nz = POP(); - goto loop; - - case 0xFA: // PLX - x = nz = POP(); - goto loop; - - case 0x7A: // PLY - y = nz = POP(); - goto loop; - - case 0x28:{// PLP - fuint8 temp = POP(); - fuint8 changed = status ^ temp; - SET_STATUS( temp ); - if ( !(changed & st_i) ) - goto loop; // I flag didn't change - if ( status & st_i ) - goto handle_sei; - goto handle_cli; - } - #undef POP - - case 0x08: { // PHP - fuint8 temp; - CALC_STATUS( temp ); - PUSH( temp | st_b ); - goto loop; - } - -// Flags - - case 0x38: // SEC - c = (unsigned) ~0; - goto loop; - - case 0x18: // CLC - c = 0; - goto loop; - - case 0xB8: // CLV - status &= ~st_v; - goto loop; - - case 0xD8: // CLD - status &= ~st_d; - goto loop; - - case 0xF8: // SED - status |= st_d; - goto loop; - - case 0x58: // CLI - if ( !(status & st_i) ) - goto loop; - status &= ~st_i; - handle_cli: { - r->status = status; // update externally-visible I flag - blargg_long delta = s.base - cpu->irq_time; - if ( delta <= 0 ) - { - if ( TIME < cpu->irq_time ) - goto loop; - goto delayed_cli; - } - s.base = cpu->irq_time; - s_time += delta; - if ( s_time < 0 ) - goto loop; - - if ( delta >= s_time + 1 ) - { - // delayed irq until after next instruction - s.base += s_time + 1; - s_time = -1; - cpu->irq_time = s.base; // TODO: remove, as only to satisfy debug check in loop - goto loop; - } - delayed_cli: - dprintf( "Delayed CLI not supported\n" ); // TODO: implement - goto loop; - } - - case 0x78: // SEI - if ( status & st_i ) - goto loop; - status |= st_i; - handle_sei: { - r->status = status; // update externally-visible I flag - blargg_long delta = s.base - cpu->end_time; - s.base = cpu->end_time; - s_time += delta; - if ( s_time < 0 ) - goto loop; - dprintf( "Delayed SEI not supported\n" ); // TODO: implement - goto loop; - } - -// Special - - case 0x53:{// TAM - fuint8 const bits = data; // avoid using data across function call - pc++; - int i; - for ( i = 0; i < 8; i++ ) - if ( bits & (1 << i) ) - /* this->cpu.set_mmr( i, a ); */ - Cpu_set_mmr( this, i, a ); - goto loop; - } - - case 0x43:{// TMA - pc++; - byte const* in = cpu->mmr; - do - { - if ( data & 1 ) - a = *in; - in++; - } - while ( (data >>= 1) != 0 ); - goto loop; - } - - case 0x03: // ST0 - case 0x13: // ST1 - case 0x23:{// ST2 - fuint16 addr = opcode >> 4; - if ( addr ) - addr++; - pc++; - FLUSH_TIME(); - CPU_WRITE_VDP( this, addr, data, TIME ); - CACHE_TIME(); - goto loop; - } - - case 0xEA: // NOP - goto loop; - - case 0x54: // CSL - dprintf( "CSL not supported\n" ); - illegal_encountered = true; - goto loop; - - case 0xD4: // CSH - goto loop; - - case 0xF4: { // SET - //fuint16 operand = GET_MSB(); - dprintf( "SET not handled\n" ); - //switch ( data ) - //{ - //} - illegal_encountered = true; - goto loop; - } - -// Block transfer + }\ +} - { - fuint16 in_alt; - fint16 in_inc; - fuint16 out_alt; - fint16 out_inc; - - case 0xE3: // TIA - in_alt = 0; - goto bxfer_alt; - - case 0xF3: // TAI - in_alt = 1; - bxfer_alt: - in_inc = in_alt ^ 1; - out_alt = in_inc; - out_inc = in_alt; - goto bxfer; - - case 0xD3: // TIN - in_inc = 1; - out_inc = 0; - goto bxfer_no_alt; - - case 0xC3: // TDD - in_inc = -1; - out_inc = -1; - goto bxfer_no_alt; - - case 0x73: // TII - in_inc = 1; - out_inc = 1; - bxfer_no_alt: - in_alt = 0; - out_alt = 0; - bxfer: { - fuint16 in = GET_LE16( instr + 0 ); - fuint16 out = GET_LE16( instr + 2 ); - int count = GET_LE16( instr + 4 ); - if ( !count ) - count = 0x10000; - pc += 6; - WRITE_LOW( 0x100 | (sp - 1), y ); - WRITE_LOW( 0x100 | (sp - 2), a ); - WRITE_LOW( 0x100 | (sp - 3), x ); - FLUSH_TIME(); - do - { - // TODO: reads from $0800-$1400 in I/O page return 0 and don't access I/O - fuint8 t = READ( in ); - in += in_inc; - in &= 0xFFFF; - s.time += 6; - if ( in_alt ) - in_inc = -in_inc; - WRITE( out, t ); - out += out_inc; - out &= 0xFFFF; - if ( out_alt ) - out_inc = -out_inc; - } - while ( --count ); - CACHE_TIME(); - goto loop; - } - } +#define READ_LOW( addr ) (this->ram [addr]) +#define WRITE_LOW( addr, data ) (this->ram [addr] = data) +#define READ_MEM( addr ) read_mem( this, addr ) +#define WRITE_MEM( addr, data ) write_mem( this, addr, data ) +#define WRITE_VDP( addr, data ) write_vdp( this, addr, data ) +#define CPU_DONE( result_out ) { FLUSH_TIME(); result_out = cpu_done( this ); CACHE_TIME(); } +#define SET_MMR( reg, bank ) set_mmr( this, reg, bank ) -// Illegal +#define IDLE_ADDR idle_addr - default: - assert( (unsigned) opcode <= 0xFF ); - dprintf( "Illegal opcode $%02X at $%04X\n", (int) opcode, (int) pc - 1 ); - illegal_encountered = true; - goto loop; - } - assert( false ); - - int result_; -handle_brk: - pc++; - result_ = 6; - -interrupt: - { - s_time += 7; - - WRITE_LOW( 0x100 | (sp - 1), pc >> 8 ); - WRITE_LOW( 0x100 | (sp - 2), pc ); - pc = GET_LE16( &READ_PROG( 0xFFF0 ) + result_ ); - - sp = (sp - 3) | 0x100; - fuint8 temp; - CALC_STATUS( temp ); - if ( result_ == 6 ) - temp |= st_b; - WRITE_LOW( sp, temp ); - - status &= ~st_d; - status |= st_i; - r->status = status; // update externally-visible I flag - - blargg_long delta = s.base - cpu->end_time; - s.base = cpu->end_time; - s_time += delta; - goto loop; - } - -idle_done: - s_time = 0; -out_of_time: - pc--; - FLUSH_TIME(); - CPU_DONE( this, TIME, result_ ); - CACHE_TIME(); - if ( result_ > 0 ) - goto interrupt; - if ( s_time < 0 ) - goto loop; - - s.time = s_time; - - r->pc = pc; - r->sp = GET_SP(); - r->a = a; - r->x = x; - r->y = y; - - { - fuint8 temp; - CALC_STATUS( temp ); - r->status = temp; - } +#define CPU_BEGIN \ +bool run_cpu( struct Hes_Emu* this, hes_time_t end_time )\ +{\ + struct Hes_Cpu* cpu = &this->cpu;\ + Cpu_set_end_time( cpu, end_time ); - cpu->state_ = s; - cpu->state = &cpu->state_; + #include "hes_cpu_run.h" return illegal_encountered; } - diff --git a/apps/codecs/libgme/hes_cpu.h b/apps/codecs/libgme/hes_cpu.h index 4d76c83a3b..0429eeaba0 100644 --- a/apps/codecs/libgme/hes_cpu.h +++ b/apps/codecs/libgme/hes_cpu.h @@ -1,56 +1,52 @@ // PC Engine CPU emulator for use with HES music files -// Game_Music_Emu 0.5.2 +// Game_Music_Emu 0.6-pre #ifndef HES_CPU_H #define HES_CPU_H #include "blargg_common.h" +#include "blargg_source.h" -typedef blargg_long hes_time_t; // clock cycle count -typedef unsigned hes_addr_t; // 16-bit address +typedef int hes_time_t; // clock cycle count +typedef int hes_addr_t; // 16-bit address struct Hes_Emu; -enum { future_hes_time = LONG_MAX / 2 + 1 }; -enum { page_size = 0x2000 }; -enum { page_shift = 13 }; -enum { page_count = 8 }; +enum { future_time = INT_MAX/2 + 1 }; +enum { page_bits = 13 }; +enum { page_size = 1 << page_bits }; +enum { page_count = 0x10000 / page_size }; -// Attempt to execute instruction here results in CPU advancing time to -// lesser of irq_time() and end_time() (or end_time() if IRQs are -// disabled) -enum { idle_addr = 0x1FFF }; - // Can read this many bytes past end of a page enum { cpu_padding = 8 }; -enum { irq_inhibit = 0x04 }; - +enum { irq_inhibit_mask = 0x04 }; +enum { idle_addr = 0x1FFF }; // Cpu state -struct state_t { - uint8_t const* code_map [page_count + 1]; +struct cpu_state_t { + byte const* code_map [page_count + 1]; hes_time_t base; - blargg_long time; + int time; }; -// Cpu registers +// NOT kept updated during emulation. struct registers_t { uint16_t pc; - uint8_t a; - uint8_t x; - uint8_t y; - uint8_t status; - uint8_t sp; + byte a; + byte x; + byte y; + byte flags; + byte sp; }; struct Hes_Cpu { struct registers_t r; - hes_time_t irq_time; - hes_time_t end_time; + hes_time_t irq_time_; + hes_time_t end_time_; - struct state_t* state; // points to state_ or a local copy within run() - struct state_t state_; + struct cpu_state_t* cpu_state; // points to state_ or a local copy within run() + struct cpu_state_t cpu_state_; // page mapping registers uint8_t mmr [page_count + 1]; @@ -58,7 +54,10 @@ struct Hes_Cpu { }; // Init cpu state -void Cpu_init( struct Hes_Cpu* this ); +static inline void Cpu_init( struct Hes_Cpu* this ) +{ + this->cpu_state = &this->cpu_state_; +} // Reset hes cpu void Cpu_reset( struct Hes_Cpu* this ); @@ -67,29 +66,67 @@ void Cpu_reset( struct Hes_Cpu* this ); // instructions were encountered. bool Cpu_run( struct Hes_Emu* this, hes_time_t end_time ); -void Cpu_set_mmr( struct Hes_Emu* this, int reg, int bank ); - // Time of ning of next instruction to be executed static inline hes_time_t Cpu_time( struct Hes_Cpu* this ) { - return this->state->time + this->state->base; + return this->cpu_state->time + this->cpu_state->base; } +static inline void Cpu_set_time( struct Hes_Cpu* this, hes_time_t t ) { this->cpu_state->time = t - this->cpu_state->base; } +static inline void Cpu_adjust_time( struct Hes_Cpu* this, int delta ) { this->cpu_state->time += delta; } + +#define HES_CPU_PAGE( addr ) ((unsigned) (addr) >> page_bits) + +#ifdef BLARGG_NONPORTABLE + #define HES_CPU_OFFSET( addr ) (addr) +#else + #define HES_CPU_OFFSET( addr ) ((addr) & (page_size - 1)) +#endif + static inline uint8_t const* Cpu_get_code( struct Hes_Cpu* this, hes_addr_t addr ) { - return this->state->code_map [addr >> page_shift] + addr - #if !defined (BLARGG_NONPORTABLE) - % (unsigned) page_size - #endif - ; + return this->cpu_state_.code_map [HES_CPU_PAGE( addr )] + HES_CPU_OFFSET( addr ); +} + +static inline void update_end_time( struct Hes_Cpu* this, hes_time_t end, hes_time_t irq ) +{ + if ( end > irq && !(this->r.flags & irq_inhibit_mask) ) + end = irq; + + this->cpu_state->time += this->cpu_state->base - end; + this->cpu_state->base = end; +} + +static inline hes_time_t Cpu_end_time( struct Hes_Cpu* this ) { return this->end_time_; } + +static inline void Cpu_set_irq_time( struct Hes_Cpu* this, hes_time_t t ) +{ + this->irq_time_ = t; + update_end_time( this, this->end_time_, t ); +} + +static inline void Cpu_set_end_time( struct Hes_Cpu* this, hes_time_t t ) +{ + this->end_time_ = t; + update_end_time( this, t, this->irq_time_ ); +} + +static inline void Cpu_end_frame( struct Hes_Cpu* this, hes_time_t t ) +{ + assert( this->cpu_state == &this->cpu_state_ ); + this->cpu_state_.base -= t; + if ( this->irq_time_ < future_time ) this->irq_time_ -= t; + if ( this->end_time_ < future_time ) this->end_time_ -= t; } -static inline int Cpu_update_end_time( struct Hes_Cpu* this, uint8_t reg_status, hes_time_t t, hes_time_t irq ) +static inline void Cpu_set_mmr( struct Hes_Cpu* this, int reg, int bank, void const* code ) { - if ( irq < t && !(reg_status & irq_inhibit) ) t = irq; - int delta = this->state->base - t; - this->state->base = t; - return delta; + assert( (unsigned) reg <= page_count ); // allow page past end to be set + assert( (unsigned) bank < 0x100 ); + this->mmr [reg] = bank; + byte const* p = STATIC_CAST(byte const*,code) - HES_CPU_OFFSET( reg << page_bits ); + this->cpu_state->code_map [reg] = p; + this->cpu_state_.code_map [reg] = p; } #endif diff --git a/apps/codecs/libgme/hes_cpu_io.h b/apps/codecs/libgme/hes_cpu_io.h deleted file mode 100644 index 6b49c69e22..0000000000 --- a/apps/codecs/libgme/hes_cpu_io.h +++ /dev/null @@ -1,72 +0,0 @@ - -#include "hes_emu.h" - -#include "blargg_source.h" - -int Cpu_read( struct Hes_Emu* this, hes_addr_t addr ) -{ - check( addr <= 0xFFFF ); - int result = *Cpu_get_code( &this->cpu, addr ); - if ( this->cpu.mmr [addr >> page_shift] == 0xFF ) - result = Emu_cpu_read( this, addr ); - return result; -} - -void Cpu_write( struct Hes_Emu* this, hes_addr_t addr, int data ) -{ - check( addr <= 0xFFFF ); - byte* out = this->write_pages [addr >> page_shift]; - addr &= page_size - 1; - if ( out ) - out [addr] = data; - else if ( this->cpu.mmr [addr >> page_shift] == 0xFF ) - Emu_cpu_write( this, addr, data ); -} - -#define CPU_READ_FAST( emu, addr, time, out ) \ - CPU_READ_FAST_( emu, addr, time, out ) - -#define CPU_READ_FAST_( emu, addr, time, out ) \ -{\ - out = READ_PROG( addr );\ - if ( emu->cpu.mmr [addr >> page_shift] == 0xFF )\ - {\ - FLUSH_TIME();\ - out = Emu_cpu_read( emu, addr );\ - CACHE_TIME();\ - }\ -} - -#define CPU_WRITE_FAST( emu, addr, data, time ) \ - CPU_WRITE_FAST_( emu, addr, data, time ) - -#define CPU_WRITE_FAST_( emu, addr, data, time ) \ -{\ - byte* out = emu->write_pages [addr >> page_shift];\ - addr &= page_size - 1;\ - if ( out )\ - {\ - out [addr] = data;\ - }\ - else if ( emu->cpu.mmr [addr >> page_shift] == 0xFF )\ - {\ - FLUSH_TIME();\ - Emu_cpu_write( emu, addr, data );\ - CACHE_TIME();\ - }\ -} - -#define CPU_READ( emu, addr, time ) \ - Cpu_read( emu, addr ) - -#define CPU_WRITE( emu, addr, data, time ) \ - Cpu_write( emu, addr, data ) - -#define CPU_WRITE_VDP( emu, addr, data, time ) \ - Cpu_write_vdp( emu, addr, data ) - -#define CPU_SET_MMR( emu, page, bank ) \ - Emu_cpu_set_mmr( emu, page, bank ) - -#define CPU_DONE( emu, time, result_out ) \ - result_out = Cpu_done( emu ) diff --git a/apps/codecs/libgme/hes_cpu_run.h b/apps/codecs/libgme/hes_cpu_run.h new file mode 100644 index 0000000000..bfba2b6109 --- /dev/null +++ b/apps/codecs/libgme/hes_cpu_run.h @@ -0,0 +1,1344 @@ +// Game_Music_Emu 0.6-pre. http://www.slack.net/~ant/ + +#if 0 +/* Define these macros in the source file before #including this file. +- Parameters might be expressions, so they are best evaluated only once, +though they NEVER have side-effects, so multiple evaluation is OK. +- Output parameters might be a multiple-assignment expression like "a=x", +so they must NOT be parenthesized. +- Except where noted, time() and related functions will NOT work +correctly inside a macro. TIME() is always correct, and FLUSH_TIME() and +CACHE_TIME() allow the time changing functions to work. +- Macros "returning" void may use a {} statement block. */ + + // 0 <= addr <= 0xFFFF + page_size + // time functions can be used + int READ_MEM( addr_t ); + void WRITE_MEM( addr_t, int data ); + + // 0 <= addr <= 0x1FF + int READ_LOW( addr_t ); + void WRITE_LOW( addr_t, int data ); + + // 0 <= addr <= 0xFFFF + page_size + // Used by common instructions. + int READ_FAST( addr_t, int& out ); + void WRITE_FAST( addr_t, int data ); + + // 0 <= addr <= 2 + // ST0, ST1, ST2 instructions + void WRITE_VDP( int addr, int data ); + +// The following can be used within macros: + + // Current time + hes_time_t TIME(); + + // Allows use of time functions + void FLUSH_TIME(); + + // Must be used before end of macro if FLUSH_TIME() was used earlier + void CACHE_TIME(); + +// Configuration (optional; commented behavior if defined) + + // Expanded just before beginning of code, to help debugger + #define CPU_BEGIN void my_run_cpu() { +#endif + +/* Copyright (C) 2003-2008 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +// TODO: support T flag, including clearing it at appropriate times? + +// all zero-page should really use whatever is at page 1, but that would +// reduce efficiency quite a bit +int const ram_addr = 0x2000; + +void Cpu_reset( struct Hes_Cpu* this ) +{ + check( this->cpu_state == &this->cpu_state_ ); + this->cpu_state = &this->cpu_state_; + + this->cpu_state_.time = 0; + this->cpu_state_.base = 0; + this->irq_time_ = future_time; + this->end_time_ = future_time; + + this->r.flags = 0x04; + this->r.sp = 0; + this->r.pc = 0; + this->r.a = 0; + this->r.x = 0; + this->r.y = 0; + + // Be sure "blargg_endian.h" has been #included + blargg_verify_byte_order(); +} + +// Allows MWCW debugger to step through code properly +#ifdef CPU_BEGIN + CPU_BEGIN +#endif + +// Time +#define TIME() (s_time + s.base) +#define FLUSH_TIME() {s.time = s_time;} +#define CACHE_TIME() {s_time = s.time;} + +// Memory +#define READ_STACK READ_LOW +#define WRITE_STACK WRITE_LOW + +#define CODE_PAGE( addr ) s.code_map [HES_CPU_PAGE( addr )] +#define CODE_OFFSET( addr ) HES_CPU_OFFSET( addr ) +#define READ_CODE( addr ) CODE_PAGE( addr ) [CODE_OFFSET( addr )] + +// Stack +#define SET_SP( v ) (sp = ((v) + 1) | 0x100) +#define GET_SP() ((sp - 1) & 0xFF) +#define SP( o ) ((sp + (o - (o>0)*0x100)) | 0x100) + +// Truncation +#define BYTE( n ) ((uint8_t ) (n)) /* (unsigned) n & 0xFF */ +#define SBYTE( n ) ((int8_t ) (n)) /* (BYTE( n ) ^ 0x80) - 0x80 */ +#define WORD( n ) ((uint16_t) (n)) /* (unsigned) n & 0xFFFF */ + +// Flags with hex value for clarity when used as mask. +// Stored in indicated variable during emulation. +int const n80 = 0x80; // nz +int const v40 = 0x40; // flags +//int const t20 = 0x20; +int const b10 = 0x10; +int const d08 = 0x08; // flags +int const i04 = 0x04; // flags +int const z02 = 0x02; // nz +int const c01 = 0x01; // c + +#define IS_NEG (nz & 0x8080) + +#define GET_FLAGS( out ) \ +{\ + out = flags & (v40 | d08 | i04);\ + out += ((nz >> 8) | nz) & n80;\ + out += c >> 8 & c01;\ + if ( !BYTE( nz ) )\ + out += z02;\ +} + +#define SET_FLAGS( in ) \ +{\ + flags = in & (v40 | d08 | i04);\ + c = nz = in << 8;\ + nz += ~in & z02;\ +} + +bool illegal_encountered = false; +{ + struct cpu_state_t s = cpu->cpu_state_; + cpu->cpu_state = &s; + // even on x86, using s.time in place of s_time was slower + int s_time = s.time; + + // registers + int pc = cpu->r.pc; + int a = cpu->r.a; + int x = cpu->r.x; + int y = cpu->r.y; + int sp; + SET_SP( cpu->r.sp ); + + // Flags + int flags; + int c; // carry set if (c & 0x100) != 0 + int nz; // Z set if (nz & 0xFF) == 0, N set if (nz & 0x8080) != 0 + { + int temp = cpu->r.flags; + SET_FLAGS( temp ); + } + +loop: + + #ifndef NDEBUG + { + hes_time_t correct = cpu->end_time_; + if ( !(flags & i04) && correct > cpu->irq_time_ ) + correct = cpu->irq_time_; + check( s.base == correct ); + /* + static int count; + if ( count == 1844 ) Debugger(); + if ( s.base != correct ) dprintf( "%ld\n", count ); + count++; + */ + } + #endif + + // Check all values + check( (unsigned) sp - 0x100 < 0x100 ); + check( (unsigned) pc < 0x10000 + 0x100 ); // +0x100 so emulator can catch wrap-around + check( (unsigned) a < 0x100 ); + check( (unsigned) x < 0x100 ); + check( (unsigned) y < 0x100 ); + + // Read instruction + byte const* instr = CODE_PAGE( pc ); + int opcode; + + if ( CODE_OFFSET(~0) == ~0 ) + { + opcode = instr [pc]; + pc++; + instr += pc; + } + else + { + instr += CODE_OFFSET( pc ); + opcode = *instr++; + pc++; + } + + // TODO: each reference lists slightly different timing values, ugh + static byte const clock_table [256] = + {// 0 1 2 3 4 5 6 7 8 9 A B C D E F + 1,7,3, 4,6,4,6,7,3,2,2,2,7,5,7,4,// 0 + 2,7,7, 4,6,4,6,7,2,5,2,2,7,5,7,4,// 1 + 7,7,3, 4,4,4,6,7,4,2,2,2,5,5,7,4,// 2 + 2,7,7, 2,4,4,6,7,2,5,2,2,5,5,7,4,// 3 + 7,7,3, 4,8,4,6,7,3,2,2,2,4,5,7,4,// 4 + 2,7,7, 5,2,4,6,7,2,5,3,2,2,5,7,4,// 5 + 7,7,2, 2,4,4,6,7,4,2,2,2,7,5,7,4,// 6 + 2,7,7,17,4,4,6,7,2,5,4,2,7,5,7,4,// 7 + 4,7,2, 7,4,4,4,7,2,2,2,2,5,5,5,4,// 8 + 2,7,7, 8,4,4,4,7,2,5,2,2,5,5,5,4,// 9 + 2,7,2, 7,4,4,4,7,2,2,2,2,5,5,5,4,// A + 2,7,7, 8,4,4,4,7,2,5,2,2,5,5,5,4,// B + 2,7,2,17,4,4,6,7,2,2,2,2,5,5,7,4,// C + 2,7,7,17,2,4,6,7,2,5,3,2,2,5,7,4,// D + 2,7,2,17,4,4,6,7,2,2,2,2,5,5,7,4,// E + 2,7,7,17,2,4,6,7,2,5,4,2,2,5,7,4 // F + }; // 0x00 was 8 + + // Update time + if ( s_time >= 0 ) + goto out_of_time; + + #ifdef HES_CPU_LOG_H + log_cpu( "new", pc - 1, opcode, instr [0], instr [1], instr [2], + instr [3], instr [4], instr [5], a, x, y ); + //log_opcode( opcode ); + #endif + + s_time += clock_table [opcode]; + + int data; + data = *instr; + + switch ( opcode ) + { +// Macros + +#define GET_MSB() (instr [1]) +#define ADD_PAGE( out ) (pc++, out = data + 0x100 * GET_MSB()); +#define GET_ADDR() GET_LE16( instr ) + +// TODO: is the penalty really always added? the original 6502 was much better +//#define PAGE_PENALTY( lsb ) (void) (s_time += (lsb) >> 8) +#define PAGE_PENALTY( lsb ) + +// Branch + +// TODO: more efficient way to handle negative branch that wraps PC around +#define BRANCH_( cond, adj )\ +{\ + pc++;\ + if ( !(cond) ) goto loop;\ + pc = (uint16_t) (pc + SBYTE( data ));\ + s_time += adj;\ + goto loop;\ +} + +#define BRANCH( cond ) BRANCH_( cond, 2 ) + + case 0xF0: // BEQ + BRANCH( !BYTE( nz ) ); + + case 0xD0: // BNE + BRANCH( BYTE( nz ) ); + + case 0x10: // BPL + BRANCH( !IS_NEG ); + + case 0x90: // BCC + BRANCH( !(c & 0x100) ) + + case 0x30: // BMI + BRANCH( IS_NEG ) + + case 0x50: // BVC + BRANCH( !(flags & v40) ) + + case 0x70: // BVS + BRANCH( flags & v40 ) + + case 0xB0: // BCS + BRANCH( c & 0x100 ) + + case 0x80: // BRA + branch_taken: + BRANCH_( true, 0 ); + + case 0xFF: + #ifdef IDLE_ADDR + if ( pc == IDLE_ADDR + 1 ) + goto idle_done; + #endif + + pc = (uint16_t) pc; + + case 0x0F: // BBRn + case 0x1F: + case 0x2F: + case 0x3F: + case 0x4F: + case 0x5F: + case 0x6F: + case 0x7F: + case 0x8F: // BBSn + case 0x9F: + case 0xAF: + case 0xBF: + case 0xCF: + case 0xDF: + case 0xEF: { + // Make two copies of bits, one negated + int t = 0x101 * READ_LOW( data ); + t ^= 0xFF; + pc++; + data = GET_MSB(); + BRANCH( t & (1 << (opcode >> 4)) ) + } + + case 0x4C: // JMP abs + pc = GET_ADDR(); + goto loop; + + case 0x7C: // JMP (ind+X) + data += x; + case 0x6C:{// JMP (ind) + data += 0x100 * GET_MSB(); + pc = GET_LE16( &READ_CODE( data ) ); + goto loop; + } + +// Subroutine + + case 0x44: // BSR + WRITE_STACK( SP( -1 ), pc >> 8 ); + sp = SP( -2 ); + WRITE_STACK( sp, pc ); + goto branch_taken; + + case 0x20: { // JSR + int temp = pc + 1; + pc = GET_ADDR(); + WRITE_STACK( SP( -1 ), temp >> 8 ); + sp = SP( -2 ); + WRITE_STACK( sp, temp ); + goto loop; + } + + case 0x60: // RTS + pc = 1 + READ_STACK( sp ); + pc += 0x100 * READ_STACK( SP( 1 ) ); + sp = SP( 2 ); + goto loop; + + case 0x00: // BRK + goto handle_brk; + +// Common + + case 0xBD:{// LDA abs,X + PAGE_PENALTY( data + x ); + int addr = GET_ADDR() + x; + pc += 2; + READ_FAST( addr, nz ); + a = nz; + goto loop; + } + + case 0x9D:{// STA abs,X + int addr = GET_ADDR() + x; + pc += 2; + WRITE_FAST( addr, a ); + goto loop; + } + + case 0x95: // STA zp,x + data = BYTE( data + x ); + case 0x85: // STA zp + pc++; + WRITE_LOW( data, a ); + goto loop; + + case 0xAE:{// LDX abs + int addr = GET_ADDR(); + pc += 2; + READ_FAST( addr, nz ); + x = nz; + goto loop; + } + + case 0xA5: // LDA zp + a = nz = READ_LOW( data ); + pc++; + goto loop; + +// Load/store + + { + int addr; + case 0x91: // STA (ind),Y + addr = 0x100 * READ_LOW( BYTE( data + 1 ) ); + addr += READ_LOW( data ) + y; + pc++; + goto sta_ptr; + + case 0x81: // STA (ind,X) + data = BYTE( data + x ); + case 0x92: // STA (ind) + addr = 0x100 * READ_LOW( BYTE( data + 1 ) ); + addr += READ_LOW( data ); + pc++; + goto sta_ptr; + + case 0x99: // STA abs,Y + data += y; + case 0x8D: // STA abs + addr = data + 0x100 * GET_MSB(); + pc += 2; + sta_ptr: + WRITE_FAST( addr, a ); + goto loop; + } + + { + int addr; + case 0xA1: // LDA (ind,X) + data = BYTE( data + x ); + case 0xB2: // LDA (ind) + addr = 0x100 * READ_LOW( BYTE( data + 1 ) ); + addr += READ_LOW( data ); + pc++; + goto a_nz_read_addr; + + case 0xB1:// LDA (ind),Y + addr = READ_LOW( data ) + y; + PAGE_PENALTY( addr ); + addr += 0x100 * READ_LOW( BYTE( data + 1 ) ); + pc++; + goto a_nz_read_addr; + + case 0xB9: // LDA abs,Y + data += y; + PAGE_PENALTY( data ); + case 0xAD: // LDA abs + addr = data + 0x100 * GET_MSB(); + pc += 2; + a_nz_read_addr: + READ_FAST( addr, nz ); + a = nz; + goto loop; + } + + case 0xBE:{// LDX abs,y + PAGE_PENALTY( data + y ); + int addr = GET_ADDR() + y; + pc += 2; + FLUSH_TIME(); + x = nz = READ_MEM( addr ); + CACHE_TIME(); + goto loop; + } + + case 0xB5: // LDA zp,x + a = nz = READ_LOW( BYTE( data + x ) ); + pc++; + goto loop; + + case 0xA9: // LDA #imm + pc++; + a = data; + nz = data; + goto loop; + +// Bit operations + + case 0x3C: // BIT abs,x + data += x; + case 0x2C:{// BIT abs + int addr; + ADD_PAGE( addr ); + FLUSH_TIME(); + nz = READ_MEM( addr ); + CACHE_TIME(); + goto bit_common; + } + case 0x34: // BIT zp,x + data = BYTE( data + x ); + case 0x24: // BIT zp + data = READ_LOW( data ); + case 0x89: // BIT imm + nz = data; + bit_common: + pc++; + flags = (flags & ~v40) + (nz & v40); + if ( nz & a ) + goto loop; // Z should be clear, and nz must be non-zero if nz & a is + nz <<= 8; // set Z flag without affecting N flag + goto loop; + + { + int addr; + + case 0xB3: // TST abs,x + addr = GET_MSB() + x; + goto tst_abs; + + case 0x93: // TST abs + addr = GET_MSB(); + tst_abs: + addr += 0x100 * instr [2]; + pc++; + FLUSH_TIME(); + nz = READ_MEM( addr ); + CACHE_TIME(); + goto tst_common; + } + + case 0xA3: // TST zp,x + nz = READ_LOW( BYTE( GET_MSB() + x ) ); + goto tst_common; + + case 0x83: // TST zp + nz = READ_LOW( GET_MSB() ); + tst_common: + pc += 2; + flags = (flags & ~v40) + (nz & v40); + if ( nz & data ) + goto loop; // Z should be clear, and nz must be non-zero if nz & data is + nz <<= 8; // set Z flag without affecting N flag + goto loop; + + { + int addr; + case 0x0C: // TSB abs + case 0x1C: // TRB abs + addr = GET_ADDR(); + pc++; + goto txb_addr; + + // TODO: everyone lists different behaviors for the flags flags, ugh + case 0x04: // TSB zp + case 0x14: // TRB zp + addr = data + ram_addr; + txb_addr: + FLUSH_TIME(); + nz = a | READ_MEM( addr ); + if ( opcode & 0x10 ) + nz ^= a; // bits from a will already be set, so this clears them + flags = (flags & ~v40) + (nz & v40); + pc++; + WRITE_MEM( addr, nz ); + CACHE_TIME(); + goto loop; + } + + case 0x07: // RMBn + case 0x17: + case 0x27: + case 0x37: + case 0x47: + case 0x57: + case 0x67: + case 0x77: + pc++; + READ_LOW( data ) &= ~(1 << (opcode >> 4)); + goto loop; + + case 0x87: // SMBn + case 0x97: + case 0xA7: + case 0xB7: + case 0xC7: + case 0xD7: + case 0xE7: + case 0xF7: + pc++; + READ_LOW( data ) |= 1 << ((opcode >> 4) - 8); + goto loop; + +// Load/store + + case 0x9E: // STZ abs,x + data += x; + case 0x9C: // STZ abs + ADD_PAGE( data ); + pc++; + FLUSH_TIME(); + WRITE_MEM( data, 0 ); + CACHE_TIME(); + goto loop; + + case 0x74: // STZ zp,x + data = BYTE( data + x ); + case 0x64: // STZ zp + pc++; + WRITE_LOW( data, 0 ); + goto loop; + + case 0x94: // STY zp,x + data = BYTE( data + x ); + case 0x84: // STY zp + pc++; + WRITE_LOW( data, y ); + goto loop; + + case 0x96: // STX zp,y + data = BYTE( data + y ); + case 0x86: // STX zp + pc++; + WRITE_LOW( data, x ); + goto loop; + + case 0xB6: // LDX zp,y + data = BYTE( data + y ); + case 0xA6: // LDX zp + data = READ_LOW( data ); + case 0xA2: // LDX #imm + pc++; + x = data; + nz = data; + goto loop; + + case 0xB4: // LDY zp,x + data = BYTE( data + x ); + case 0xA4: // LDY zp + data = READ_LOW( data ); + case 0xA0: // LDY #imm + pc++; + y = data; + nz = data; + goto loop; + + case 0xBC: // LDY abs,X + data += x; + PAGE_PENALTY( data ); + case 0xAC:{// LDY abs + int addr = data + 0x100 * GET_MSB(); + pc += 2; + FLUSH_TIME(); + y = nz = READ_MEM( addr ); + CACHE_TIME(); + goto loop; + } + + { + int temp; + case 0x8C: // STY abs + temp = y; + if ( 0 ) + case 0x8E: // STX abs + temp = x; + int addr = GET_ADDR(); + pc += 2; + FLUSH_TIME(); + WRITE_MEM( addr, temp ); + CACHE_TIME(); + goto loop; + } + +// Compare + + case 0xEC:{// CPX abs + int addr = GET_ADDR(); + pc++; + FLUSH_TIME(); + data = READ_MEM( addr ); + CACHE_TIME(); + goto cpx_data; + } + + case 0xE4: // CPX zp + data = READ_LOW( data ); + case 0xE0: // CPX #imm + cpx_data: + nz = x - data; + pc++; + c = ~nz; + nz = BYTE( nz ); + goto loop; + + case 0xCC:{// CPY abs + int addr = GET_ADDR(); + pc++; + FLUSH_TIME(); + data = READ_MEM( addr ); + CACHE_TIME(); + goto cpy_data; + } + + case 0xC4: // CPY zp + data = READ_LOW( data ); + case 0xC0: // CPY #imm + cpy_data: + nz = y - data; + pc++; + c = ~nz; + nz = BYTE( nz ); + goto loop; + +// Logical + +#define ARITH_ADDR_MODES( op )\ + case op - 0x04: /* (ind,x) */\ + data = BYTE( data + x );\ + case op + 0x0D: /* (ind) */\ + data = 0x100 * READ_LOW( BYTE( data + 1 ) ) + READ_LOW( data );\ + goto ptr##op;\ + case op + 0x0C:{/* (ind),y */\ + int temp = READ_LOW( data ) + y;\ + PAGE_PENALTY( temp );\ + data = temp + 0x100 * READ_LOW( BYTE( data + 1 ) );\ + goto ptr##op;\ + }\ + case op + 0x10: /* zp,X */\ + data = BYTE( data + x );\ + case op + 0x00: /* zp */\ + data = READ_LOW( data );\ + goto imm##op;\ + case op + 0x14: /* abs,Y */\ + data += y;\ + goto ind##op;\ + case op + 0x18: /* abs,X */\ + data += x;\ + ind##op:\ + PAGE_PENALTY( data );\ + case op + 0x08: /* abs */\ + ADD_PAGE( data );\ + ptr##op:\ + FLUSH_TIME();\ + data = READ_MEM( data );\ + CACHE_TIME();\ + case op + 0x04: /* imm */\ + imm##op: + + ARITH_ADDR_MODES( 0xC5 ) // CMP + nz = a - data; + pc++; + c = ~nz; + nz = BYTE( nz ); + goto loop; + + ARITH_ADDR_MODES( 0x25 ) // AND + nz = (a &= data); + pc++; + goto loop; + + ARITH_ADDR_MODES( 0x45 ) // EOR + nz = (a ^= data); + pc++; + goto loop; + + ARITH_ADDR_MODES( 0x05 ) // ORA + nz = (a |= data); + pc++; + goto loop; + +// Add/subtract + + ARITH_ADDR_MODES( 0xE5 ) // SBC + data ^= 0xFF; + goto adc_imm; + + ARITH_ADDR_MODES( 0x65 ) // ADC + adc_imm: { + /* if ( flags & d08 ) + dprintf( "Decimal mode not supported\n" ); */ + int carry = c >> 8 & 1; + int ov = (a ^ 0x80) + carry + SBYTE( data ); + flags = (flags & ~v40) + (ov >> 2 & v40); + c = nz = a + data + carry; + pc++; + a = BYTE( nz ); + goto loop; + } + +// Shift/rotate + + case 0x4A: // LSR A + c = 0; + case 0x6A: // ROR A + nz = c >> 1 & 0x80; + c = a << 8; + nz += a >> 1; + a = nz; + goto loop; + + case 0x0A: // ASL A + nz = a << 1; + c = nz; + a = BYTE( nz ); + goto loop; + + case 0x2A: { // ROL A + nz = a << 1; + int temp = c >> 8 & 1; + c = nz; + nz += temp; + a = BYTE( nz ); + goto loop; + } + + case 0x5E: // LSR abs,X + data += x; + case 0x4E: // LSR abs + c = 0; + case 0x6E: // ROR abs + ror_abs: { + ADD_PAGE( data ); + FLUSH_TIME(); + int temp = READ_MEM( data ); + nz = (c >> 1 & 0x80) + (temp >> 1); + c = temp << 8; + goto rotate_common; + } + + case 0x3E: // ROL abs,X + data += x; + goto rol_abs; + + case 0x1E: // ASL abs,X + data += x; + case 0x0E: // ASL abs + c = 0; + case 0x2E: // ROL abs + rol_abs: + ADD_PAGE( data ); + nz = c >> 8 & 1; + FLUSH_TIME(); + nz += (c = READ_MEM( data ) << 1); + rotate_common: + pc++; + WRITE_MEM( data, BYTE( nz ) ); + CACHE_TIME(); + goto loop; + + case 0x7E: // ROR abs,X + data += x; + goto ror_abs; + + case 0x76: // ROR zp,x + data = BYTE( data + x ); + goto ror_zp; + + case 0x56: // LSR zp,x + data = BYTE( data + x ); + case 0x46: // LSR zp + c = 0; + case 0x66: // ROR zp + ror_zp: { + int temp = READ_LOW( data ); + nz = (c >> 1 & 0x80) + (temp >> 1); + c = temp << 8; + goto write_nz_zp; + } + + case 0x36: // ROL zp,x + data = BYTE( data + x ); + goto rol_zp; + + case 0x16: // ASL zp,x + data = BYTE( data + x ); + case 0x06: // ASL zp + c = 0; + case 0x26: // ROL zp + rol_zp: + nz = c >> 8 & 1; + nz += (c = READ_LOW( data ) << 1); + goto write_nz_zp; + +// Increment/decrement + +#define INC_DEC( reg, n ) reg = BYTE( nz = reg + n ); goto loop; + + case 0x1A: // INA + INC_DEC( a, +1 ) + + case 0xE8: // INX + INC_DEC( x, +1 ) + + case 0xC8: // INY + INC_DEC( y, +1 ) + + case 0x3A: // DEA + INC_DEC( a, -1 ) + + case 0xCA: // DEX + INC_DEC( x, -1 ) + + case 0x88: // DEY + INC_DEC( y, -1 ) + + case 0xF6: // INC zp,x + data = BYTE( data + x ); + case 0xE6: // INC zp + nz = 1; + goto add_nz_zp; + + case 0xD6: // DEC zp,x + data = BYTE( data + x ); + case 0xC6: // DEC zp + nz = -1; + add_nz_zp: + nz += READ_LOW( data ); + write_nz_zp: + pc++; + WRITE_LOW( data, nz ); + goto loop; + + case 0xFE: // INC abs,x + data = x + GET_ADDR(); + goto inc_ptr; + + case 0xEE: // INC abs + data = GET_ADDR(); + inc_ptr: + nz = 1; + goto inc_common; + + case 0xDE: // DEC abs,x + data = x + GET_ADDR(); + goto dec_ptr; + + case 0xCE: // DEC abs + data = GET_ADDR(); + dec_ptr: + nz = -1; + inc_common: + FLUSH_TIME(); + pc += 2; + nz += READ_MEM( data ); + WRITE_MEM( data, BYTE( nz ) ); + CACHE_TIME(); + goto loop; + +// Transfer + + case 0xA8: // TAY + y = nz = a; + goto loop; + + case 0x98: // TYA + a = nz = y; + goto loop; + + case 0xAA: // TAX + x = nz = a; + goto loop; + + case 0x8A: // TXA + a = nz = x; + goto loop; + + case 0x9A: // TXS + SET_SP( x ); // verified (no flag change) + goto loop; + + case 0xBA: // TSX + x = nz = GET_SP(); + goto loop; + + #define SWAP_REGS( r1, r2 ) {\ + int t = r1;\ + r1 = r2;\ + r2 = t;\ + goto loop;\ + } + + case 0x02: // SXY + SWAP_REGS( x, y ); + + case 0x22: // SAX + SWAP_REGS( a, x ); + + case 0x42: // SAY + SWAP_REGS( a, y ); + + case 0x62: // CLA + a = 0; + goto loop; + + case 0x82: // CLX + x = 0; + goto loop; + + case 0xC2: // CLY + y = 0; + goto loop; + +// Stack + + case 0x48: // PHA + sp = SP( -1 ); + WRITE_STACK( sp, a ); + goto loop; + + case 0x68: // PLA + a = nz = READ_STACK( sp ); + sp = SP( 1 ); + goto loop; + + case 0xDA: // PHX + sp = SP( -1 ); + WRITE_STACK( sp, x ); + goto loop; + + case 0x5A: // PHY + sp = SP( -1 ); + WRITE_STACK( sp, y ); + goto loop; + + case 0x40:{// RTI + pc = READ_STACK( SP( 1 ) ); + pc += READ_STACK( SP( 2 ) ) * 0x100; + int temp = READ_STACK( sp ); + sp = SP( 3 ); + data = flags; + SET_FLAGS( temp ); + cpu->r.flags = flags; // update externally-visible I flag + if ( (data ^ flags) & i04 ) + { + hes_time_t new_time = cpu->end_time_; + if ( !(flags & i04) && new_time > cpu->irq_time_ ) + new_time = cpu->irq_time_; + int delta = s.base - new_time; + s.base = new_time; + s_time += delta; + } + goto loop; + } + + case 0xFA: // PLX + x = nz = READ_STACK( sp ); + sp = SP( 1 ); + goto loop; + + case 0x7A: // PLY + y = nz = READ_STACK( sp ); + sp = SP( 1 ); + goto loop; + + case 0x28:{// PLP + int temp = READ_STACK( sp ); + sp = SP( 1 ); + int changed = flags ^ temp; + SET_FLAGS( temp ); + if ( !(changed & i04) ) + goto loop; // I flag didn't change + if ( flags & i04 ) + goto handle_sei; + goto handle_cli; + } + + case 0x08:{// PHP + int temp; + GET_FLAGS( temp ); + sp = SP( -1 ); + WRITE_STACK( sp, temp | b10 ); + goto loop; + } + +// Flags + + case 0x38: // SEC + c = 0x100; + goto loop; + + case 0x18: // CLC + c = 0; + goto loop; + + case 0xB8: // CLV + flags &= ~v40; + goto loop; + + case 0xD8: // CLD + flags &= ~d08; + goto loop; + + case 0xF8: // SED + flags |= d08; + goto loop; + + case 0x58: // CLI + if ( !(flags & i04) ) + goto loop; + flags &= ~i04; + handle_cli: { + //dprintf( "CLI at %d\n", TIME ); + cpu->r.flags = flags; // update externally-visible I flag + int delta = s.base - cpu->irq_time_; + if ( delta <= 0 ) + { + if ( TIME() < cpu->irq_time_ ) + goto loop; + goto delayed_cli; + } + s.base = cpu->irq_time_; + s_time += delta; + if ( s_time < 0 ) + goto loop; + + if ( delta >= s_time + 1 ) + { + // delayed irq until after next instruction + s.base += s_time + 1; + s_time = -1; + cpu->irq_time_ = s.base; // TODO: remove, as only to satisfy debug check in loop + goto loop; + } + + // TODO: implement + delayed_cli: + dprintf( "Delayed CLI not supported\n" ); + goto loop; + } + + case 0x78: // SEI + if ( flags & i04 ) + goto loop; + flags |= i04; + handle_sei: { + cpu->r.flags = flags; // update externally-visible I flag + int delta = s.base - cpu->end_time_; + s.base = cpu->end_time_; + s_time += delta; + if ( s_time < 0 ) + goto loop; + + dprintf( "Delayed SEI not supported\n" ); + goto loop; + } + +// Special + + case 0x53:{// TAM + int bits = data; // avoid using data across function call + pc++; + for ( int i = 0; i < 8; i++ ) + if ( bits & (1 << i) ) + SET_MMR( i, a ); + goto loop; + } + + case 0x43:{// TMA + pc++; + byte const* in = cpu->mmr; + do + { + if ( data & 1 ) + a = *in; + in++; + } + while ( (data >>= 1) != 0 ); + goto loop; + } + + case 0x03: // ST0 + case 0x13: // ST1 + case 0x23:{// ST2 + int addr = opcode >> 4; + if ( addr ) + addr++; + pc++; + FLUSH_TIME(); + WRITE_VDP( addr, data ); + CACHE_TIME(); + goto loop; + } + + case 0xEA: // NOP + goto loop; + + case 0x54: // CSL + dprintf( "CSL not supported\n" ); + illegal_encountered = true; + goto loop; + + case 0xD4: // CSH + goto loop; + + case 0xF4: { // SET + //int operand = GET_MSB(); + dprintf( "SET not handled\n" ); + //switch ( data ) + //{ + //} + illegal_encountered = true; + goto loop; + } + +// Block transfer + + { + int in_alt; + int in_inc; + int out_alt; + int out_inc; + + case 0xE3: // TIA + in_alt = 0; + goto bxfer_alt; + + case 0xF3: // TAI + in_alt = 1; + bxfer_alt: + in_inc = in_alt ^ 1; + out_alt = in_inc; + out_inc = in_alt; + goto bxfer; + + case 0xD3: // TIN + in_inc = 1; + out_inc = 0; + goto bxfer_no_alt; + + case 0xC3: // TDD + in_inc = -1; + out_inc = -1; + goto bxfer_no_alt; + + case 0x73: // TII + in_inc = 1; + out_inc = 1; + bxfer_no_alt: + in_alt = 0; + out_alt = 0; + bxfer: + { + int in = GET_LE16( instr + 0 ); + int out = GET_LE16( instr + 2 ); + int count = GET_LE16( instr + 4 ); + if ( !count ) + count = 0x10000; + pc += 6; + WRITE_STACK( SP( -1 ), y ); + WRITE_STACK( SP( -2 ), a ); + WRITE_STACK( SP( -3 ), x ); + FLUSH_TIME(); + do + { + // TODO: reads from $0800-$1400 in I/O page should do I/O + int t = READ_MEM( in ); + in = WORD( in + in_inc ); + s.time += 6; + if ( in_alt ) + in_inc = -in_inc; + WRITE_MEM( out, t ); + out = WORD( out + out_inc ); + if ( out_alt ) + out_inc = -out_inc; + } + while ( --count ); + CACHE_TIME(); + goto loop; + } + } + +// Illegal + + default: + check( (unsigned) opcode <= 0xFF ); + dprintf( "Illegal opcode $%02X at $%04X\n", (int) opcode, (int) pc - 1 ); + illegal_encountered = true; + goto loop; + } + assert( false ); // catch missing 'goto loop' or accidental 'break' + + int result_; +handle_brk: + pc++; + result_ = 6; + +interrupt: + { + s_time += 7; + + // Save PC and read vector + WRITE_STACK( SP( -1 ), pc >> 8 ); + WRITE_STACK( SP( -2 ), pc ); + pc = GET_LE16( &READ_CODE( 0xFFF0 ) + result_ ); + + // Save flags + int temp; + GET_FLAGS( temp ); + if ( result_ == 6 ) + temp |= b10; // BRK sets B bit + sp = SP( -3 ); + WRITE_STACK( sp, temp ); + + // Update I flag in externally-visible flags + flags &= ~d08; + cpu->r.flags = (flags |= i04); + + // Update time + int delta = s.base - cpu->end_time_; + if ( delta >= 0 ) + goto loop; + s_time += delta; + s.base = cpu->end_time_; + goto loop; + } + +idle_done: + s_time = 0; + +out_of_time: + pc--; + + // Optional action that triggers interrupt or changes irq/end time + #ifdef CPU_DONE + { + CPU_DONE( result_ ); + if ( result_ >= 0 ) + goto interrupt; + if ( s_time < 0 ) + goto loop; + } + #endif + + // Flush cached state + cpu->r.pc = pc; + cpu->r.sp = GET_SP(); + cpu->r.a = a; + cpu->r.x = x; + cpu->r.y = y; + + int temp; + GET_FLAGS( temp ); + cpu->r.flags = temp; + + cpu->cpu_state_.base = s.base; + cpu->cpu_state_.time = s_time; + cpu->cpu_state = &cpu->cpu_state_; +} diff --git a/apps/codecs/libgme/hes_emu.c b/apps/codecs/libgme/hes_emu.c index a428bee3fd..8ddbb9dc29 100644 --- a/apps/codecs/libgme/hes_emu.c +++ b/apps/codecs/libgme/hes_emu.c @@ -21,28 +21,14 @@ int const vdp_mask = 0x02; int const i_flag_mask = 0x04; int const unmapped = 0xFF; -long const period_60hz = 262 * 455L; // scanlines * clocks per scanline - -int const stereo = 2; // number of channels for stereo -int const silence_max = 6; // seconds -int const silence_threshold = 0x10; -long const fade_block_size = 512; -int const fade_shift = 8; // fade ends with gain at 1.0 / (1 << fade_shift) +int const period_60hz = 262 * 455; // scanlines * clocks per scanline const char gme_wrong_file_type [] = "Wrong file type for this emulator"; static void clear_track_vars( struct Hes_Emu* this ) { - this->current_track_ = -1; - this->out_time = 0; - this->emu_time = 0; - this->emu_track_ended_ = true; - this->track_ended = true; - this->fade_start = (blargg_long)(LONG_MAX / 2 + 1); - this->fade_step = 1; - this->silence_time = 0; - this->silence_count = 0; - this->buf_remain = 0; + this->current_track_ = -1; + track_stop( &this->track_filter ); } void Hes_init( struct Hes_Emu* this ) @@ -52,15 +38,12 @@ void Hes_init( struct Hes_Emu* this ) this->tempo_ = (int)(FP_ONE_TEMPO); // defaults - this->max_initial_silence = 2; - this->ignore_silence = false; - - // Unload - this->voice_count_ = 0; - clear_track_vars( this ); + this->tfilter = *track_get_setup( &this->track_filter ); + this->tfilter.max_initial = 2; + this->tfilter.lookahead = 6; + this->track_filter.silence_ignored_ = false; this->timer.raw_load = 0; - this->silence_lookahead = 6; Sound_set_gain( this, (int)(FP_ONE_GAIN*1.11) ); Rom_init( &this->rom, 0x2000 ); @@ -71,6 +54,11 @@ void Hes_init( struct Hes_Emu* this ) /* Set default track count */ this->track_count = 255; + + // clears fields + this->voice_count_ = 0; + this->voice_types_ = 0; + clear_track_vars( this ); } static blargg_err_t check_hes_header( void const* header ) @@ -82,10 +70,12 @@ static blargg_err_t check_hes_header( void const* header ) // Setup -blargg_err_t Hes_load( struct Hes_Emu* this, void* data, long size ) +blargg_err_t Hes_load_mem( struct Hes_Emu* this, void* data, long size ) { // Unload this->voice_count_ = 0; + this->track_count = 255; + this->m3u.size = 0; clear_track_vars( this ); assert( offsetof (struct header_t,unused [4]) == header_size ); @@ -106,15 +96,15 @@ blargg_err_t Hes_load( struct Hes_Emu* this, void* data, long size ) // many files have bad sizes in the only block, so it's simpler to // just try to load the damn data as best as possible. - long addr = get_le32( this->header.addr ); - /* long rom_size = get_le32( this->header.size ); */ - long const rom_max = 0x100000; - if ( addr & ~(rom_max - 1) ) + int addr = get_le32( this->header.addr ); + /* int rom_size = get_le32( this->header.size ); */ + int const rom_max = 0x100000; + if ( (unsigned) addr >= (unsigned) rom_max ) { /* warning( "Invalid address" ); */ addr &= rom_max - 1; } - /* if ( (unsigned long) (addr + size) > (unsigned long) rom_max ) + /* if ( (unsigned) (addr + size) > (unsigned) rom_max ) warning( "Invalid size" ); if ( rom_size != rom.file_size() ) @@ -130,6 +120,10 @@ blargg_err_t Hes_load( struct Hes_Emu* this, void* data, long size ) Rom_set_addr( &this->rom, addr ); this->voice_count_ = osc_count + adpcm_osc_count; + static int const types [osc_count + adpcm_osc_count] = { + wave_type+0, wave_type+1, wave_type+2, wave_type+3, mixed_type+0, mixed_type+1, mixed_type+2 + }; + this->voice_types_ = types; Apu_volume( &this->apu, this->gain_ ); Adpcm_volume( &this->adpcm, this->gain_ ); @@ -137,21 +131,17 @@ blargg_err_t Hes_load( struct Hes_Emu* this, void* data, long size ) // Setup buffer this->clock_rate_ = 7159091; Buffer_clock_rate( &this->stereo_buf, 7159091 ); + RETURN_ERR( Buffer_set_channel_count( &this->stereo_buf, this->voice_count_, this->voice_types_ ) ); this->buf_changed_count = Buffer_channels_changed_count( &this->stereo_buf ); Sound_set_tempo( this, this->tempo_ ); Sound_mute_voices( this, this->mute_mask_ ); - // Reset track count - this->track_count = 255; - this->m3u.size = 0; return 0; } - // Emulation -void recalc_timer_load( struct Hes_Emu* this ); void recalc_timer_load( struct Hes_Emu* this ) { this->timer.load = this->timer.raw_load * this->timer_base + 1; @@ -159,9 +149,25 @@ void recalc_timer_load( struct Hes_Emu* this ) // Hardware -void irq_changed( struct Hes_Emu* this ); -void run_until( struct Hes_Emu* this, hes_time_t present ); -void Cpu_write_vdp( struct Hes_Emu* this, int addr, int data ) +void run_until( struct Hes_Emu* this, hes_time_t present ) +{ + while ( this->vdp.next_vbl < present ) + this->vdp.next_vbl += this->play_period; + + hes_time_t elapsed = present - this->timer.last_time; + if ( elapsed > 0 ) + { + if ( this->timer.enabled ) + { + this->timer.count -= elapsed; + if ( this->timer.count <= 0 ) + this->timer.count += this->timer.load; + } + this->timer.last_time = present; + } +} + +void write_vdp( struct Hes_Emu* this, int addr, int data ) { switch ( addr ) { @@ -178,77 +184,33 @@ void Cpu_write_vdp( struct Hes_Emu* this, int addr, int data ) this->vdp.control = data; irq_changed( this ); } - else + /* else { - dprintf( "VDP not supported: $%02X <- $%02X\n", this->vdp.latch, data ); - } + dprintf( "VDP not supported: $%02X <- $%02X\n", vdp.latch, data ); + } */ break; case 3: - dprintf( "VDP MSB not supported: $%02X <- $%02X\n", this->vdp.latch, data ); + /* dprintf( "VDP MSB not supported: $%02X <- $%02X\n", vdp.latch, data ); */ break; } } -int Cpu_done( struct Hes_Emu* this ) -{ - check( time() >= end_time() || - (!(r.status & i_flag_mask) && time() >= irq_time()) ); - - if ( !(this->cpu.r.status & i_flag_mask) ) - { - hes_time_t present = Cpu_time( &this->cpu ); - - if ( this->irq.timer <= present && !(this->irq.disables & timer_mask) ) - { - this->timer.fired = true; - this->irq.timer = (hes_time_t)future_hes_time; - irq_changed( this ); // overkill, but not worth writing custom code - #if defined (GME_FRAME_HOOK_DEFINED) - { - unsigned const threshold = period_60hz / 30; - unsigned long elapsed = present - last_frame_hook; - if ( elapsed - period_60hz + threshold / 2 < threshold ) - { - last_frame_hook = present; - GME_FRAME_HOOK( this ); - } - } - #endif - return 0x0A; - } - - if ( this->irq.vdp <= present && !(this->irq.disables & vdp_mask) ) - { - // work around for bugs with music not acknowledging VDP - //run_until( present ); - //irq.vdp = future_hes_time; - //irq_changed(); - #if defined(GME_FRAME_HOOK_DEFINED) - last_frame_hook = present; - GME_FRAME_HOOK( this ); - #endif - return 0x08; - } - } - return 0; -} - -void Emu_cpu_write( struct Hes_Emu* this, hes_addr_t addr, int data ) +void write_mem_( struct Hes_Emu* this, hes_addr_t addr, int data ) { hes_time_t time = Cpu_time( &this->cpu ); - if ( (unsigned) (addr - start_addr) <= end_addr - start_addr ) + if ( (unsigned) (addr - apu_io_addr) < apu_io_size ) { - GME_APU_HOOK( this, addr - apu.start_addr, data ); - // avoid going way past end when a long block xfer is writing to I/O space - hes_time_t t = min( time, this->cpu.end_time + 8 ); + // Avoid going way past end when a long block xfer is writing to I/O space. + // Not a problem for other registers below because they don't write to + // Blip_Buffer. + hes_time_t t = min( time, Cpu_end_time( &this->cpu ) + 8 ); Apu_write_data( &this->apu, t, addr, data ); return; } - - if ( (unsigned) (addr - io_addr) < io_size ) + if ( (unsigned) (addr - adpcm_io_addr) < adpcm_io_size ) { - hes_time_t t = min( time, this->cpu.end_time + 6 ); + hes_time_t t = min( time, Cpu_end_time( &this->cpu ) + 6 ); Adpcm_write_data( &this->adpcm, t, addr, data ); return; } @@ -258,7 +220,7 @@ void Emu_cpu_write( struct Hes_Emu* this, hes_addr_t addr, int data ) case 0x0000: case 0x0002: case 0x0003: - Cpu_write_vdp( this, addr, data ); + write_vdp( this, addr, data ); return; case 0x0C00: { @@ -282,11 +244,8 @@ void Emu_cpu_write( struct Hes_Emu* this, hes_addr_t addr, int data ) case 0x1402: run_until( this, time ); this->irq.disables = data; - - // flag questionable values - if ( (data & 0xF8) && (data & 0xF8) != 0xF8 ) { - dprintf( "Int mask: $%02X\n", data ); - } + /* if ( (data & 0xF8) && (data & 0xF8) != 0xF8 ) // flag questionable values + dprintf( "Int mask: $%02X\n", data ); */ break; case 0x1403: @@ -305,7 +264,7 @@ void Emu_cpu_write( struct Hes_Emu* this, hes_addr_t addr, int data ) return; default: - dprintf( "unmapped write $%04X <- $%02X\n", addr, data ); + /* dprintf( "unmapped write $%04X <- $%02X\n", addr, data ); */ return; #endif } @@ -313,7 +272,7 @@ void Emu_cpu_write( struct Hes_Emu* this, hes_addr_t addr, int data ) irq_changed( this ); } -int Emu_cpu_read( struct Hes_Emu* this, hes_addr_t addr ) +int read_mem_( struct Hes_Emu* this, hes_addr_t addr ) { hes_time_t time = Cpu_time( &this->cpu ); addr &= page_size - 1; @@ -322,21 +281,21 @@ int Emu_cpu_read( struct Hes_Emu* this, hes_addr_t addr ) case 0x0000: if ( this->irq.vdp > time ) return 0; - this->irq.vdp = (hes_time_t)future_hes_time; + this->irq.vdp = future_time; run_until( this, time ); irq_changed( this ); return 0x20; - case 0x0002: + /* case 0x0002: case 0x0003: dprintf( "VDP read not supported: %d\n", addr ); - return 0; + return 0; */ case 0x0C01: //return timer.enabled; // TODO: remove? case 0x0C00: run_until( this, time ); - dprintf( "Timer count read\n" ); + /* dprintf( "Timer count read\n" ); */ return (unsigned) (this->timer.count - 1) / this->timer_base; case 0x1402: @@ -349,7 +308,7 @@ int Emu_cpu_read( struct Hes_Emu* this, hes_addr_t addr ) if ( this->irq.vdp <= time ) status |= vdp_mask; return status; } - + case 0x180A: case 0x180B: case 0x180C: @@ -358,71 +317,75 @@ int Emu_cpu_read( struct Hes_Emu* this, hes_addr_t addr ) #ifndef NDEBUG case 0x1000: // I/O port - // case 0x180C: // CD-ROM - // case 0x180D: + //case 0x180C: // CD-ROM + //case 0x180D: break; - default: - dprintf( "unmapped read $%04X\n", addr ); + /* default: + dprintf( "unmapped read $%04X\n", addr ); */ #endif } return unmapped; } -// see hes_cpu_io.h for core read/write functions - -// Emulation - -void run_until( struct Hes_Emu* this, hes_time_t present ) -{ - while ( this->vdp.next_vbl < present ) - this->vdp.next_vbl += this->play_period; - - hes_time_t elapsed = present - this->timer.last_time; - if ( elapsed > 0 ) - { - if ( this->timer.enabled ) - { - this->timer.count -= elapsed; - if ( this->timer.count <= 0 ) - this->timer.count += this->timer.load; - } - this->timer.last_time = present; - } -} - void irq_changed( struct Hes_Emu* this ) { hes_time_t present = Cpu_time( &this->cpu ); if ( this->irq.timer > present ) { - this->irq.timer = (hes_time_t)future_hes_time; + this->irq.timer = future_time; if ( this->timer.enabled && !this->timer.fired ) this->irq.timer = present + this->timer.count; } if ( this->irq.vdp > present ) { - this->irq.vdp = (hes_time_t)future_hes_time; + this->irq.vdp = future_time; if ( this->vdp.control & 0x08 ) this->irq.vdp = this->vdp.next_vbl; } - hes_time_t time = (hes_time_t)future_hes_time; + hes_time_t time = future_time; if ( !(this->irq.disables & timer_mask) ) time = this->irq.timer; if ( !(this->irq.disables & vdp_mask) ) time = min( time, this->irq.vdp ); - // Set cpu irq time - this->cpu.state->time += Cpu_update_end_time( &this->cpu, this->cpu.r.status, - this->cpu.end_time, (this->cpu.irq_time = time) ); + Cpu_set_irq_time( &this->cpu, time ); } -static void adjust_time( blargg_long* time, hes_time_t delta ); -static void adjust_time( blargg_long* time, hes_time_t delta ) +int cpu_done( struct Hes_Emu* this ) { - if ( *time < (blargg_long)future_hes_time ) + check( Cpu_time( &this->cpu ) >= Cpu_end_time( &this->cpu ) || + (!(this->cpu.r.flags & i_flag_mask) && Cpu_time( &this->cpu ) >= Cpu_irq_time( &this->cpu )) ); + + if ( !(this->cpu.r.flags & i_flag_mask) ) + { + hes_time_t present = Cpu_time( &this->cpu ); + + if ( this->irq.timer <= present && !(this->irq.disables & timer_mask) ) + { + this->timer.fired = true; + this->irq.timer = future_time; + irq_changed( this ); // overkill, but not worth writing custom code + return 0x0A; + } + + if ( this->irq.vdp <= present && !(this->irq.disables & vdp_mask) ) + { + // work around for bugs with music not acknowledging VDP + //run_until( present ); + //irq.vdp = cpu.future_time; + //irq_changed(); + return 0x08; + } + } + return -1; +} + +static void adjust_time( hes_time_t* time, hes_time_t delta ) +{ + if ( *time < future_time ) { *time -= delta; if ( *time < 0 ) @@ -430,15 +393,13 @@ static void adjust_time( blargg_long* time, hes_time_t delta ) } } -blargg_err_t run_clocks( struct Hes_Emu* this, blip_time_t* duration_ ); -blargg_err_t run_clocks( struct Hes_Emu* this, blip_time_t* duration_ ) +blargg_err_t end_frame( struct Hes_Emu* this, hes_time_t duration ) { - blip_time_t duration = *duration_; // cache + /* if ( run_cpu( this, duration ) ) + warning( "Emulation error (illegal instruction)" ); */ + run_cpu( this, duration ); - Cpu_run( this, duration ); - /* warning( "Emulation error (illegal instruction)" ); */ - - check( time() >= duration ); + check( Cpu_time( &this->cpu ) >= duration ); //check( time() - duration < 20 ); // Txx instruction could cause going way over run_until( this, duration ); @@ -446,15 +407,7 @@ blargg_err_t run_clocks( struct Hes_Emu* this, blip_time_t* duration_ ) // end time frame this->timer.last_time -= duration; this->vdp.next_vbl -= duration; - #if defined (GME_FRAME_HOOK_DEFINED) - last_frame_hook -= *duration; - #endif - - // End cpu frame - this->cpu.state_.base -= duration; - if ( this->cpu.irq_time < (hes_time_t)future_hes_time ) this->cpu.irq_time -= duration; - if ( this->cpu.end_time < (hes_time_t)future_hes_time ) this->cpu.end_time -= duration; - + Cpu_end_frame( &this->cpu, duration ); adjust_time( &this->irq.timer, duration ); adjust_time( &this->irq.vdp, duration ); Apu_end_frame( &this->apu, duration ); @@ -463,24 +416,31 @@ blargg_err_t run_clocks( struct Hes_Emu* this, blip_time_t* duration_ ) return 0; } -blargg_err_t play_( struct Hes_Emu* this, long count, sample_t* out ); -blargg_err_t play_( struct Hes_Emu* this, long count, sample_t* out ) +blargg_err_t run_clocks( struct Hes_Emu* this, blip_time_t* duration_ ) +{ + return end_frame( this, *duration_ ); +} + +blargg_err_t play_( void *emu, int count, sample_t out [] ) { - long remain = count; + struct Hes_Emu* this = (struct Hes_Emu*) emu; + + int remain = count; while ( remain ) { + Buffer_disable_immediate_removal( &this->stereo_buf ); remain -= Buffer_read_samples( &this->stereo_buf, &out [count - remain], remain ); if ( remain ) { if ( this->buf_changed_count != Buffer_channels_changed_count( &this->stereo_buf ) ) { this->buf_changed_count = Buffer_channels_changed_count( &this->stereo_buf ); + // Remute voices Sound_mute_voices( this, this->mute_mask_ ); } - int msec = Buffer_length( &this->stereo_buf ); - blip_time_t clocks_emulated = (blargg_long) msec * this->clock_rate_ / 1000; + blip_time_t clocks_emulated = msec * this->clock_rate_ / 1000 - 100; RETURN_ERR( run_clocks( this, &clocks_emulated ) ); assert( clocks_emulated ); Buffer_end_frame( &this->stereo_buf, clocks_emulated ); @@ -489,10 +449,9 @@ blargg_err_t play_( struct Hes_Emu* this, long count, sample_t* out ) return 0; } - // Music emu -blargg_err_t Hes_set_sample_rate( struct Hes_Emu* this, long rate ) +blargg_err_t Hes_set_sample_rate( struct Hes_Emu* this, int rate ) { require( !this->sample_rate_ ); // sample rate can't be changed once set Buffer_init( &this->stereo_buf ); @@ -502,6 +461,8 @@ blargg_err_t Hes_set_sample_rate( struct Hes_Emu* this, long rate ) Buffer_bass_freq( &this->stereo_buf, 60 ); this->sample_rate_ = rate; + RETURN_ERR( track_init( &this->track_filter, this ) ); + this->tfilter.max_silence = 6 * stereo * this->sample_rate_; return 0; } @@ -521,7 +482,7 @@ void Sound_mute_voices( struct Hes_Emu* this, int mask ) this->mute_mask_ = mask; // Set adpcm voice - struct channel_t ch = Buffer_channel( &this->stereo_buf ); + struct channel_t ch = Buffer_channel( &this->stereo_buf, this->voice_count_ ); if ( mask & (1 << this->voice_count_ ) ) Adpcm_set_output( &this->adpcm, 0, 0, 0, 0 ); else @@ -536,7 +497,8 @@ void Sound_mute_voices( struct Hes_Emu* this, int mask ) Apu_osc_output( &this->apu, i, 0, 0, 0 ); } else - { + { + struct channel_t ch = Buffer_channel( &this->stereo_buf, i ); assert( (ch.center && ch.left && ch.right) || (!ch.center && !ch.left && !ch.right) ); // all or nothing Apu_osc_output( &this->apu, i, ch.center, ch.left, ch.right ); @@ -557,7 +519,6 @@ void Sound_set_tempo( struct Hes_Emu* this, int t ) this->tempo_ = t; } -void fill_buf( struct Hes_Emu* this ); blargg_err_t Hes_start_track( struct Hes_Emu* this, int track ) { clear_track_vars( this ); @@ -572,7 +533,7 @@ blargg_err_t Hes_start_track( struct Hes_Emu* this, int track ) Buffer_clear( &this->stereo_buf ); - memset( this->cpu.ram, 0, sizeof this->cpu.ram ); // some HES music relies on zero fill + memset( this->ram, 0, sizeof this->ram ); // some HES music relies on zero fill memset( this->sgx, 0, sizeof this->sgx ); Apu_reset( &this->apu ); @@ -581,12 +542,12 @@ blargg_err_t Hes_start_track( struct Hes_Emu* this, int track ) unsigned i; for ( i = 0; i < sizeof this->header.banks; i++ ) - Cpu_set_mmr( this, i, this->header.banks [i] ); - Cpu_set_mmr( this, page_count, 0xFF ); // unmapped beyond end of address space + set_mmr( this, i, this->header.banks [i] ); + set_mmr( this, page_count, 0xFF ); // unmapped beyond end of address space this->irq.disables = timer_mask | vdp_mask; - this->irq.timer = (hes_time_t)future_hes_time; - this->irq.vdp = (hes_time_t)future_hes_time; + this->irq.timer = future_time; + this->irq.vdp = future_time; this->timer.enabled = false; this->timer.raw_load= 0x80; @@ -598,280 +559,86 @@ blargg_err_t Hes_start_track( struct Hes_Emu* this, int track ) this->vdp.control = 0; this->vdp.next_vbl = 0; - this->cpu.ram [0x1FF] = (idle_addr - 1) >> 8; - this->cpu.ram [0x1FE] = (idle_addr - 1) & 0xFF; + this->ram [0x1FF] = (idle_addr - 1) >> 8; + this->ram [0x1FE] = (idle_addr - 1) & 0xFF; this->cpu.r.sp = 0xFD; this->cpu.r.pc = get_le16( this->header.init_addr ); this->cpu.r.a = track; recalc_timer_load( this ); - this->last_frame_hook = 0; - this->emu_track_ended_ = false; - this->track_ended = false; + // convert filter times to samples + struct setup_t s = this->tfilter; + s.max_initial *= this->sample_rate_ * stereo; + #ifdef GME_DISABLE_SILENCE_LOOKAHEAD + s.lookahead = 1; + #endif + track_setup( &this->track_filter, &s ); - if ( !this->ignore_silence ) - { - // play until non-silence or end of track - long end; - for ( end =this-> max_initial_silence * stereo * this->sample_rate_; this->emu_time < end; ) - { - fill_buf( this ); - if ( this->buf_remain | (int) this->emu_track_ended_ ) - break; - } - - this->emu_time = this->buf_remain; - this->out_time = 0; - this->silence_time = 0; - this->silence_count = 0; - } - /* return track_ended() ? warning() : 0; */ - return 0; + return track_start( &this->track_filter ); } // Tell/Seek -static blargg_long msec_to_samples( blargg_long msec, long sample_rate ) +static int msec_to_samples( int msec, int sample_rate ) { - blargg_long sec = msec / 1000; + int sec = msec / 1000; msec -= sec * 1000; return (sec * sample_rate + msec * sample_rate / 1000) * stereo; } -long Track_tell( struct Hes_Emu* this ) +int Track_tell( struct Hes_Emu* this ) { - blargg_long rate = this->sample_rate_ * stereo; - blargg_long sec = this->out_time / rate; - return sec * 1000 + (this->out_time - sec * rate) * 1000 / rate; + int rate = this->sample_rate_ * stereo; + int sec = track_sample_count( &this->track_filter ) / rate; + return sec * 1000 + (track_sample_count( &this->track_filter ) - sec * rate) * 1000 / rate; } -blargg_err_t Track_seek( struct Hes_Emu* this, long msec ) +blargg_err_t Track_seek( struct Hes_Emu* this, int msec ) { - blargg_long time = msec_to_samples( msec, this->sample_rate_ ); - if ( time < this->out_time ) - RETURN_ERR( Hes_start_track( this, this->current_track_ ) ); - return Track_skip( this, time - this->out_time ); + int time = msec_to_samples( msec, this->sample_rate_ ); + if ( time < track_sample_count( &this->track_filter ) ) + RETURN_ERR( Hes_start_track( this, this->current_track_ ) ); + return Track_skip( this, time - track_sample_count( &this->track_filter ) ); } -blargg_err_t skip_( struct Hes_Emu* this, long count ); -blargg_err_t skip_( struct Hes_Emu* this, long count ) +blargg_err_t skip_( void* emu, int count ) { + struct Hes_Emu* this = (struct Hes_Emu*) emu; + // for long skip, mute sound - const long threshold = 30000; + const int threshold = 32768; if ( count > threshold ) { int saved_mute = this->mute_mask_; Sound_mute_voices( this, ~0 ); - - while ( count > threshold / 2 && !this->emu_track_ended_ ) - { - RETURN_ERR( play_( this, buf_size, this->buf ) ); - count -= buf_size; - } - - Sound_mute_voices( this, saved_mute ); - } - - while ( count && !this->emu_track_ended_ ) - { - long n = buf_size; - if ( n > count ) - n = count; - count -= n; - RETURN_ERR( play_( this, n, this->buf ) ); - } - return 0; -} -blargg_err_t Track_skip( struct Hes_Emu* this, long count ) -{ - require( this->current_track_ >= 0 ); // start_track() must have been called already - this->out_time += count; - - // remove from silence and buf first - { - long n = min( count, this->silence_count ); - this->silence_count -= n; - count -= n; - - n = min( count, this->buf_remain ); - this->buf_remain -= n; + int n = count - threshold/2; + n &= ~(2048-1); // round to multiple of 2048 count -= n; - } - - if ( count && !this->emu_track_ended_ ) - { - this->emu_time += count; - - // End track if error - if ( skip_( this, count ) ) - this->emu_track_ended_ = true; - } - - if ( !(this->silence_count | this->buf_remain) ) // caught up to emulator, so update track ended - this->track_ended |= this->emu_track_ended_; - - return 0; -} + RETURN_ERR( skippy_( &this->track_filter, n ) ); - - -// Fading - -void Track_set_fade( struct Hes_Emu* this, long start_msec, long length_msec ) -{ - this->fade_step = this->sample_rate_ * length_msec / (fade_block_size * fade_shift * 1000 / stereo); - this->fade_start = msec_to_samples( start_msec, this->sample_rate_ ); -} - -// unit / pow( 2.0, (double) x / step ) -static int int_log( blargg_long x, int step, int unit ); -static int int_log( blargg_long x, int step, int unit ) -{ - int shift = x / step; - int fraction = (x - shift * step) * unit / step; - return ((unit - fraction) + (fraction >> 1)) >> shift; -} - -void handle_fade( struct Hes_Emu* this, long out_count, sample_t* out ); -void handle_fade( struct Hes_Emu* this, long out_count, sample_t* out ) -{ - int i; - for ( i = 0; i < out_count; i += fade_block_size ) - { - int const shift = 14; - int const unit = 1 << shift; - int gain = int_log( (this->out_time + i - this->fade_start) / fade_block_size, - this->fade_step, unit ); - if ( gain < (unit >> fade_shift) ) - this->track_ended = this->emu_track_ended_ = true; - - sample_t* io = &out [i]; - int count; - for ( count = min( fade_block_size, out_count - i ); count; --count ) - { - *io = (sample_t) ((*io * gain) >> shift); - ++io; - } + Sound_mute_voices( this, saved_mute ); } -} - -// Silence detection -void emu_play( struct Hes_Emu* this, long count, sample_t* out ); -void emu_play( struct Hes_Emu* this, long count, sample_t* out ) -{ - check( current_track_ >= 0 ); - this->emu_time += count; - if ( this->current_track_ >= 0 && !this->emu_track_ended_ ) { - - // End track if error - if ( play_( this, count, out ) ) - this->emu_track_ended_ = true; - } - else - memset( out, 0, count * sizeof *out ); + return skippy_( &this->track_filter, count ); } -// number of consecutive silent samples at end -static long count_silence( sample_t* begin, long size ); -static long count_silence( sample_t* begin, long size ) +blargg_err_t Track_skip( struct Hes_Emu* this, int count ) { - sample_t first = *begin; - *begin = silence_threshold; // sentinel - sample_t* p = begin + size; - while ( (unsigned) (*--p + silence_threshold / 2) <= (unsigned) silence_threshold ) { } - *begin = first; - return size - (p - begin); + require( this->current_track_ >= 0 ); // start_track() must have been called already + return track_skip( &this->track_filter, count ); } -// fill internal buffer and check it for silence -void fill_buf( struct Hes_Emu* this ) +void Track_set_fade( struct Hes_Emu* this, int start_msec, int length_msec ) { - assert( !this->buf_remain ); - if ( !this->emu_track_ended_ ) - { - emu_play( this, buf_size, this->buf ); - long silence = count_silence( this->buf, buf_size ); - if ( silence < buf_size ) - { - this->silence_time = this->emu_time - silence; - this->buf_remain = buf_size; - return; - } - } - this->silence_count += buf_size; + track_set_fade( &this->track_filter, msec_to_samples( start_msec, this->sample_rate_ ), + length_msec * this->sample_rate_ / (1000 / stereo) ); } -blargg_err_t Hes_play( struct Hes_Emu* this, long out_count, sample_t* out ) +blargg_err_t Hes_play( struct Hes_Emu* this, int out_count, sample_t* out ) { - if ( this->track_ended ) - { - memset( out, 0, out_count * sizeof *out ); - } - else - { - require( this->current_track_ >= 0 ); - require( out_count % stereo == 0 ); - - assert( this->emu_time >= this->out_time ); - - // prints nifty graph of how far ahead we are when searching for silence - //dprintf( "%*s \n", int ((emu_time - out_time) * 7 / sample_rate()), "*" ); - - long pos = 0; - if ( this->silence_count ) - { - // during a run of silence, run emulator at >=2x speed so it gets ahead - long ahead_time = this->silence_lookahead * (this->out_time + out_count - this->silence_time) + this->silence_time; - while ( this->emu_time < ahead_time && !(this->buf_remain | this->emu_track_ended_) ) - fill_buf( this ); - - // fill with silence - pos = min( this->silence_count, out_count ); - memset( out, 0, pos * sizeof *out ); - this->silence_count -= pos; - - if ( this->emu_time - this->silence_time > silence_max * stereo * this->sample_rate_ ) - { - this->track_ended = this->emu_track_ended_ = true; - this->silence_count = 0; - this->buf_remain = 0; - } - } - - if ( this->buf_remain ) - { - // empty silence buf - long n = min( this->buf_remain, out_count - pos ); - memcpy( &out [pos], this->buf + (buf_size - this->buf_remain), n * sizeof *out ); - this->buf_remain -= n; - pos += n; - } - - // generate remaining samples normally - long remain = out_count - pos; - if ( remain ) - { - emu_play( this, remain, out + pos ); - this->track_ended |= this->emu_track_ended_; - - if ( !this->ignore_silence || this->out_time > this->fade_start ) - { - // check end for a new run of silence - long silence = count_silence( out + pos, remain ); - if ( silence < remain ) - this->silence_time = this->emu_time - silence; - - if ( this->emu_time - this->silence_time >= buf_size ) - fill_buf( this ); // cause silence detection on next play() - } - } - - if ( this->out_time > this->fade_start ) - handle_fade( this, out_count, out ); - } - this->out_time += out_count; - return 0; + require( this->current_track_ >= 0 ); + require( out_count % stereo == 0 ); + return track_play( &this->track_filter, out_count, out ); } diff --git a/apps/codecs/libgme/hes_emu.h b/apps/codecs/libgme/hes_emu.h index 0dcd29a9c6..a1dd048498 100644 --- a/apps/codecs/libgme/hes_emu.h +++ b/apps/codecs/libgme/hes_emu.h @@ -12,88 +12,67 @@ #include "hes_apu_adpcm.h" #include "hes_cpu.h" #include "m3u_playlist.h" - -typedef short sample_t; - -enum { buf_size = 2048 }; +#include "track_filter.h" // HES file header +enum { info_offset = 0x20 }; enum { header_size = 0x20 }; struct header_t { - byte tag [4]; + byte tag [4]; byte vers; byte first_track; byte init_addr [2]; - byte banks [8]; - byte data_tag [4]; - byte size [4]; - byte addr [4]; - byte unused [4]; + byte banks [8]; + byte data_tag [4]; + byte size [4]; + byte addr [4]; + byte unused [4]; }; struct timer_t { hes_time_t last_time; - blargg_long count; - blargg_long load; - int raw_load; - byte enabled; - byte fired; + int count; + int load; + int raw_load; + byte enabled; + byte fired; }; struct vdp_t { hes_time_t next_vbl; - byte latch; - byte control; + byte latch; + byte control; }; struct irq_t { hes_time_t timer; hes_time_t vdp; - byte disables; + byte disables; }; - struct Hes_Emu { hes_time_t play_period; - hes_time_t last_frame_hook; int timer_base; struct timer_t timer; - struct vdp_t vdp; - struct irq_t irq; + struct vdp_t vdp; + struct irq_t irq; // Sound - long clock_rate_; - long sample_rate_; + int clock_rate_; + int sample_rate_; unsigned buf_changed_count; int voice_count_; + int const* voice_types_; + int mute_mask_; int tempo_; int gain_; // track-specific byte track_count; - volatile bool track_ended; int current_track_; - blargg_long out_time; // number of samples played since start of track - blargg_long emu_time; // number of samples emulator has generated since start of track - bool emu_track_ended_; // emulator has reached end of track - - // fading - blargg_long fade_start; - int fade_step; - - // silence detection - // Disable automatic end-of-track detection and skipping of silence at beginning - bool ignore_silence; - - int max_initial_silence; - int mute_mask_; - int silence_lookahead; // speed to run emulator when looking ahead for silence - long silence_time; // number of samples where most recent silence began - long silence_count; // number of samples of silence to play before using buf - long buf_remain; // number of samples left in silence buffer // Larger files at the end // Header for currently loaded file @@ -102,19 +81,22 @@ struct Hes_Emu { // M3u Playlist struct M3u_Playlist m3u; + struct setup_t tfilter; + struct Track_Filter track_filter; + // Hes Cpu - byte* write_pages [page_count + 1]; // 0 if unmapped or I/O space struct Hes_Cpu cpu; + struct Rom_Data rom; struct Hes_Apu apu; struct Hes_Apu_Adpcm adpcm; - struct Stereo_Buffer stereo_buf; - sample_t buf [buf_size]; + struct Multi_Buffer stereo_buf; // rom & ram - struct Rom_Data rom; - byte sgx [3 * page_size + cpu_padding]; + byte* write_pages [page_count + 1]; // 0 if unmapped or I/O space + byte ram [page_size]; + byte sgx [3 * page_size + cpu_padding]; }; @@ -126,36 +108,48 @@ void Hes_init( struct Hes_Emu* this ); void Hes_stop( struct Hes_Emu* this ); // Loads a file from memory -blargg_err_t Hes_load( struct Hes_Emu* this, void* data, long size ); +blargg_err_t Hes_load_mem( struct Hes_Emu* this, void* data, long size ); // Set output sample rate. Must be called only once before loading file. -blargg_err_t Hes_set_sample_rate( struct Hes_Emu* this, long sample_rate ); +blargg_err_t Hes_set_sample_rate( struct Hes_Emu* this, int sample_rate ); // Start a track, where 0 is the first track. Also clears warning string. blargg_err_t Hes_start_track( struct Hes_Emu* this, int ); // Generate 'count' samples info 'buf'. Output is in stereo. Any emulation // errors set warning string, and major errors also end track. -blargg_err_t Hes_play( struct Hes_Emu* this, long count, sample_t* buf ); +blargg_err_t Hes_play( struct Hes_Emu* this, int count, sample_t* buf ); // Track status/control // Number of milliseconds (1000 msec = 1 second) played since ning of track -long Track_tell( struct Hes_Emu* this ); +int Track_tell( struct Hes_Emu* this ); // Seek to new time in track. Seeking backwards or far forward can take a while. -blargg_err_t Track_seek( struct Hes_Emu* this, long msec ); +blargg_err_t Track_seek( struct Hes_Emu* this, int msec ); // Skip n samples -blargg_err_t Track_skip( struct Hes_Emu* this, long n ); +blargg_err_t Track_skip( struct Hes_Emu* this, int n ); // Set start time and length of track fade out. Once fade ends track_ended() returns // true. Fade time can be changed while track is playing. -void Track_set_fade( struct Hes_Emu* this, long start_msec, long length_msec ); +void Track_set_fade( struct Hes_Emu* this, int start_msec, int length_msec ); + +// True if a track has reached its end +static inline bool Track_ended( struct Hes_Emu* this ) +{ + return track_ended( &this->track_filter ); +} + +// Disables automatic end-of-track detection and skipping of silence at beginning +static inline void Track_ignore_silence( struct Hes_Emu* this, bool disable ) +{ + this->track_filter.silence_ignored_ = disable; +} // Get track length in milliseconds -static inline long Track_get_length( struct Hes_Emu* this, int n ) +static inline int Track_get_length( struct Hes_Emu* this, int n ) { - long length = 120 * 1000; /* 2 minutes */ + int length = 120 * 1000; /* 2 minutes */ if ( (this->m3u.size > 0) && (n < this->m3u.size) ) { struct entry_t* entry = &this->m3u.entries [n]; length = entry->length; @@ -185,45 +179,17 @@ static inline void Sound_set_gain( struct Hes_Emu* this, int g ) this->gain_ = g; } - // Emulation (You shouldn't touch these) -int Cpu_read( struct Hes_Emu* this, hes_addr_t ); -void Cpu_write( struct Hes_Emu* this, hes_addr_t, int ); -void Cpu_write_vdp( struct Hes_Emu* this, int addr, int data ); -int Cpu_done( struct Hes_Emu* this ); - -int Emu_cpu_read( struct Hes_Emu* this, hes_addr_t ); -void Emu_cpu_write( struct Hes_Emu* this, hes_addr_t, int data ); - -static inline byte const* Emu_cpu_set_mmr( struct Hes_Emu* this, int page, int bank ) -{ - this->write_pages [page] = 0; - if ( bank < 0x80 ) - return Rom_at_addr( &this->rom, bank * (blargg_long) page_size ); - - byte* data = 0; - switch ( bank ) - { - case 0xF8: - data = this->cpu.ram; - break; - - case 0xF9: - case 0xFA: - case 0xFB: - data = &this->sgx [(bank - 0xF9) * page_size]; - break; - - default: - if ( bank != 0xFF ) { - dprintf( "Unmapped bank $%02X\n", bank ); - } - return this->rom.unmapped; - } - - this->write_pages [page] = data; - return data; -} +void irq_changed( struct Hes_Emu* this ); +void run_until( struct Hes_Emu* this, hes_time_t ); +bool run_cpu( struct Hes_Emu* this, hes_time_t end ); +int read_mem_( struct Hes_Emu* this, hes_addr_t ); +int read_mem( struct Hes_Emu* this, hes_addr_t ); +void write_mem_( struct Hes_Emu* this, hes_addr_t, int data ); +void write_mem( struct Hes_Emu* this, hes_addr_t, int ); +void write_vdp( struct Hes_Emu* this, int addr, int data ); +void set_mmr( struct Hes_Emu* this, int reg, int bank ); +int cpu_done( struct Hes_Emu* this ); #endif diff --git a/apps/codecs/libgme/kss_emu.c b/apps/codecs/libgme/kss_emu.c index 060a5b74b0..ba80ef613e 100644 --- a/apps/codecs/libgme/kss_emu.c +++ b/apps/codecs/libgme/kss_emu.c @@ -1,4 +1,4 @@ -// Game_Music_Emu 0.5.5. http://www.slack.net/~ant/ +// Game_Music_Emu 0.6-pre. http://www.slack.net/~ant/ #include "kss_emu.h" @@ -17,29 +17,14 @@ Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "blargg_source.h" -long const clock_rate = 3579545; +int const clock_rate = 3579545; const char gme_wrong_file_type [] = "Wrong file type for this emulator"; -int const stereo = 2; // number of channels for stereo -int const silence_max = 6; // seconds -int const silence_threshold = 0x10; -long const fade_block_size = 512; -int const fade_shift = 8; // fade ends with gain at 1.0 / (1 << fade_shift) - static void clear_track_vars( struct Kss_Emu* this ) { - this->current_track = -1; - this->out_time = 0; - this->emu_time = 0; - this->emu_track_ended_ = true; - this->track_ended = true; - this->fade_start = INT_MAX / 2 + 1; - this->fade_step = 1; - this->silence_time = 0; - this->silence_count = 0; - this->buf_remain = 0; - // warning(); // clear warning + this->current_track = -1; + track_stop( &this->track_filter ); } static blargg_err_t init_opl_apu( enum opl_type_t type, struct Opl_Apu* out ) @@ -58,17 +43,15 @@ void Kss_init( struct Kss_Emu* this ) this->chip_flags = 0; // defaults - this->max_initial_silence = 2; - this->silence_lookahead = 6; - this->ignore_silence = false; - - this->voice_count = 0; - clear_track_vars( this ); + this->tfilter = *track_get_setup( &this->track_filter ); + this->tfilter.max_initial = 2; + this->tfilter.lookahead = 6; + this->track_filter.silence_ignored_ = false; memset( this->unmapped_read, 0xFF, sizeof this->unmapped_read ); // Init all stuff - Buffer_init( &this->stereo_buffer ); + Buffer_init( &this->stereo_buf ); Z80_init( &this->cpu ); Rom_init( &this->rom, page_size ); @@ -83,6 +66,10 @@ void Kss_init( struct Kss_Emu* this ) init_opl_apu( type_msxmusic, &this->msx.music ); init_opl_apu( type_msxaudio, &this->msx.audio ); #endif + + this->voice_count = 0; + this->voice_types = 0; + clear_track_vars( this ); } // Track info @@ -96,7 +83,7 @@ static blargg_err_t check_kss_header( void const* header ) // Setup -static void update_gain( struct Kss_Emu* this ) +static void update_gain_( struct Kss_Emu* this ) { int g = this->gain; if ( msx_music_enabled( this ) || msx_audio_enabled( this ) @@ -118,6 +105,15 @@ static void update_gain( struct Kss_Emu* this ) if ( msx_audio_enabled( this ) ) Opl_volume( &this->msx.audio, g ); } +static void update_gain( struct Kss_Emu* this ) +{ + if ( this->scc_accessed ) + { + /* dprintf( "SCC accessed\n" ); */ + update_gain_( this ); + } +} + blargg_err_t Kss_load_mem( struct Kss_Emu* this, const void* data, long size ) { /* warning( core.warning() ); */ @@ -176,6 +172,10 @@ blargg_err_t Kss_load_mem( struct Kss_Emu* this, const void* data, long size ) // sms.psg this->voice_count = sms_osc_count; + static int const types [sms_osc_count + opl_osc_count] = { + wave_type+1, wave_type+3, wave_type+2, mixed_type+1, wave_type+0 + }; + this->voice_types = types; this->chip_flags |= sms_psg_flag; // sms.fm @@ -191,6 +191,10 @@ blargg_err_t Kss_load_mem( struct Kss_Emu* this, const void* data, long size ) // msx.psg this->voice_count = ay_osc_count; + static int const types [ay_osc_count + opl_osc_count] = { + wave_type+1, wave_type+3, wave_type+2, wave_type+0 + }; + this->voice_types = types; this->chip_flags |= msx_psg_flag; /* if ( this->header.device_flags & 0x10 ) @@ -220,21 +224,27 @@ blargg_err_t Kss_load_mem( struct Kss_Emu* this, const void* data, long size ) // msx.scc this->chip_flags |= msx_scc_flag; this->voice_count = ay_osc_count + scc_osc_count; + static int const types [ay_osc_count + scc_osc_count] = { + wave_type+1, wave_type+3, wave_type+2, + wave_type+0, wave_type+4, wave_type+5, wave_type+6, wave_type+7, + }; + this->voice_types = types; } } - this->silence_lookahead = 6; + this->tfilter.lookahead = 6; if ( sms_fm_enabled( this ) || msx_music_enabled( this ) || msx_audio_enabled( this ) ) { if ( !Opl_supported() ) ; /* warning( "FM sound not supported" ); */ else - this->silence_lookahead = 3; // Opl_Apu is really slow + this->tfilter.lookahead = 3; // Opl_Apu is really slow } this->clock_rate_ = clock_rate; - Buffer_clock_rate( &this->stereo_buffer, clock_rate ); - this->buf_changed_count = Buffer_channels_changed_count( &this->stereo_buffer ); + Buffer_clock_rate( &this->stereo_buf, clock_rate ); + RETURN_ERR( Buffer_set_channel_count( &this->stereo_buf, this->voice_count, this->voice_types ) ); + this->buf_changed_count = Buffer_channels_changed_count( &this->stereo_buf ); Sound_set_tempo( this, this->tempo ); Sound_mute_voices( this, this->mute_mask_ ); @@ -265,8 +275,8 @@ static void set_voice( struct Kss_Emu* this, int i, struct Blip_Buffer* center, } if ( msx_scc_enabled( this ) && i < scc_osc_count ) Scc_set_output( &this->msx.scc, i, center ); - if ( msx_music_enabled( this ) && i < opl_osc_count ) Opl_set_output( &this->msx.music, center ); - if ( msx_audio_enabled( this ) && i < opl_osc_count ) Opl_set_output( &this->msx.audio, center ); + if ( msx_music_enabled( this ) && i < opl_osc_count ) Opl_set_output( &this->msx.music, center ); + if ( msx_audio_enabled( this ) && i < opl_osc_count ) Opl_set_output( &this->msx.audio, center ); } } @@ -465,15 +475,17 @@ blargg_err_t end_frame( struct Kss_Emu* this, kss_time_t end ) // MUSIC - -blargg_err_t Kss_set_sample_rate( struct Kss_Emu* this, long rate ) +blargg_err_t Kss_set_sample_rate( struct Kss_Emu* this, int rate ) { require( !this->sample_rate ); // sample rate can't be changed once set - RETURN_ERR( Buffer_set_sample_rate( &this->stereo_buffer, rate, 1000 / 20 ) ); + RETURN_ERR( Buffer_set_sample_rate( &this->stereo_buf, rate, 1000 / 20 ) ); // Set bass frequency - Buffer_bass_freq( &this->stereo_buffer, 180 ); + Buffer_bass_freq( &this->stereo_buf, 180 ); + this->sample_rate = rate; + RETURN_ERR( track_init( &this->track_filter, this ) ); + this->tfilter.max_silence = 6 * stereo * this->sample_rate; return 0; } @@ -501,7 +513,7 @@ void Sound_mute_voices( struct Kss_Emu* this, int mask ) } else { - struct channel_t ch = Buffer_channel( &this->stereo_buffer ); + struct channel_t ch = Buffer_channel( &this->stereo_buf, i ); assert( (ch.center && ch.left && ch.right) || (!ch.center && !ch.left && !ch.right) ); // all or nothing set_voice( this, i, ch.center, ch.left, ch.right ); @@ -523,7 +535,6 @@ void Sound_set_tempo( struct Kss_Emu* this, int t ) this->play_period = (blip_time_t) ((period * FP_ONE_TEMPO) / t); } -void fill_buf( struct Kss_Emu* this ); blargg_err_t Kss_start_track( struct Kss_Emu* this, int track ) { clear_track_vars( this ); @@ -536,7 +547,7 @@ blargg_err_t Kss_start_track( struct Kss_Emu* this, int track ) this->current_track = track; - Buffer_clear( &this->stereo_buffer ); + Buffer_clear( &this->stereo_buf ); if ( sms_psg_enabled( this ) ) Sms_apu_reset( &this->sms.psg, 0, 0 ); if ( sms_fm_enabled( this ) ) Opl_reset( &this->sms.fm ); @@ -546,7 +557,7 @@ blargg_err_t Kss_start_track( struct Kss_Emu* this, int track ) if ( msx_audio_enabled( this ) ) Opl_reset( &this->msx.audio ); this->scc_accessed = false; - update_gain( this ); + update_gain_( this ); memset( this->ram, 0xC9, 0x4000 ); memset( this->ram + 0x4000, 0, sizeof this->ram - 0x4000 ); @@ -598,286 +609,106 @@ blargg_err_t Kss_start_track( struct Kss_Emu* this, int track ) this->gain_updated = false; jsr( this, this->header.init_addr ); - this->emu_track_ended_ = false; - this->track_ended = false; + // convert filter times to samples + struct setup_t s = this->tfilter; + s.max_initial *= this->sample_rate * stereo; + #ifdef GME_DISABLE_SILENCE_LOOKAHEAD + s.lookahead = 1; + #endif + track_setup( &this->track_filter, &s ); - if ( !this->ignore_silence ) - { - // play until non-silence or end of track - long end; - for ( end = this->max_initial_silence * stereo * this->sample_rate; this->emu_time < end; ) - { - fill_buf( this ); - if ( this->buf_remain | (int) this->emu_track_ended_ ) - break; - } - - this->emu_time = this->buf_remain; - this->out_time = 0; - this->silence_time = 0; - this->silence_count = 0; - } - /* return track_ended() ? warning() : 0; */ - return 0; + return track_start( &this->track_filter ); } // Tell/Seek -static blargg_long msec_to_samples( blargg_long msec, long sample_rate ) +static int msec_to_samples( int msec, int sample_rate ) { - blargg_long sec = msec / 1000; + int sec = msec / 1000; msec -= sec * 1000; return (sec * sample_rate + msec * sample_rate / 1000) * stereo; } -long Track_tell( struct Kss_Emu* this ) +int Track_tell( struct Kss_Emu* this ) { - blargg_long rate = this->sample_rate * stereo; - blargg_long sec = this->out_time / rate; - return sec * 1000 + (this->out_time - sec * rate) * 1000 / rate; + int rate = this->sample_rate * stereo; + int sec = track_sample_count( &this->track_filter ) / rate; + return sec * 1000 + (track_sample_count( &this->track_filter ) - sec * rate) * 1000 / rate; } -blargg_err_t Track_seek( struct Kss_Emu* this, long msec ) +blargg_err_t Track_seek( struct Kss_Emu* this, int msec ) { - blargg_long time = msec_to_samples( msec, this->sample_rate ); - if ( time < this->out_time ) - RETURN_ERR( Kss_start_track( this, this->current_track ) ); - return Track_skip( this, time - this->out_time ); + int time = msec_to_samples( msec, this->sample_rate ); + if ( time < track_sample_count( &this->track_filter ) ) + RETURN_ERR( Kss_start_track( this, this->current_track ) ); + return Track_skip( this, time - track_sample_count( &this->track_filter ) ); } -blargg_err_t play_( struct Kss_Emu* this, long count, sample_t* out ); -static blargg_err_t skip_( struct Kss_Emu* this, long count ) +blargg_err_t skip_( void *emu, int count ) { + struct Kss_Emu* this = (struct Kss_Emu*) emu; + // for long skip, mute sound - const long threshold = 30000; + const int threshold = 32768; if ( count > threshold ) { int saved_mute = this->mute_mask_; Sound_mute_voices( this, ~0 ); - - while ( count > threshold / 2 && !this->emu_track_ended_ ) - { - RETURN_ERR( play_( this, buf_size, this->buf ) ); - count -= buf_size; - } - - Sound_mute_voices( this, saved_mute ); - } - - while ( count && !this->emu_track_ended_ ) - { - long n = buf_size; - if ( n > count ) - n = count; - count -= n; - RETURN_ERR( play_( this, n, this->buf ) ); - } - return 0; -} -blargg_err_t Track_skip( struct Kss_Emu* this, long count ) -{ - require( this->current_track >= 0 ); // start_track() must have been called already - this->out_time += count; - - // remove from silence and buf first - { - long n = min( count, this->silence_count ); - this->silence_count -= n; - count -= n; - - n = min( count, this->buf_remain ); - this->buf_remain -= n; + int n = count - threshold/2; + n &= ~(2048-1); // round to multiple of 2048 count -= n; - } - - if ( count && !this->emu_track_ended_ ) - { - this->emu_time += count; - if ( skip_( this, count ) ) - this->emu_track_ended_ = true; - } - - if ( !(this->silence_count | this->buf_remain) ) // caught up to emulator, so update track ended - this->track_ended |= this->emu_track_ended_; - - return 0; -} - -// Fading + RETURN_ERR( skippy_( &this->track_filter, n ) ); -void Track_set_fade( struct Kss_Emu* this, long start_msec, long length_msec ) -{ - this->fade_step = this->sample_rate * length_msec / (fade_block_size * fade_shift * 1000 / stereo); - this->fade_start = msec_to_samples( start_msec, this->sample_rate ); -} - -// unit / pow( 2.0, (double) x / step ) -static int int_log( blargg_long x, int step, int unit ) -{ - int shift = x / step; - int fraction = (x - shift * step) * unit / step; - return ((unit - fraction) + (fraction >> 1)) >> shift; -} - -static void handle_fade( struct Kss_Emu *this, long out_count, sample_t* out ) -{ - int i; - for ( i = 0; i < out_count; i += fade_block_size ) - { - int const shift = 14; - int const unit = 1 << shift; - int gain = int_log( (this->out_time + i - this->fade_start) / fade_block_size, - this->fade_step, unit ); - if ( gain < (unit >> fade_shift) ) - this->track_ended = this->emu_track_ended_ = true; - - sample_t* io = &out [i]; - int count; - for ( count = min( fade_block_size, out_count - i ); count; --count ) - { - *io = (sample_t) ((*io * gain) >> shift); - ++io; - } + Sound_mute_voices( this, saved_mute ); } -} - -// Silence detection -static void emu_play( struct Kss_Emu* this, long count, sample_t* out ) -{ - check( current_track_ >= 0 ); - this->emu_time += count; - if ( this->current_track >= 0 && !this->emu_track_ended_ ) { - if ( play_( this, count, out ) ) - this->emu_track_ended_ = true; - } - else - memset( out, 0, count * sizeof *out ); + return skippy_( &this->track_filter, count ); } -// number of consecutive silent samples at end -static long count_silence( sample_t* begin, long size ) +blargg_err_t Track_skip( struct Kss_Emu* this, int count ) { - sample_t first = *begin; - *begin = silence_threshold; // sentinel - sample_t* p = begin + size; - while ( (unsigned) (*--p + silence_threshold / 2) <= (unsigned) silence_threshold ) { } - *begin = first; - return size - (p - begin); + require( this->current_track >= 0 ); // start_track() must have been called already + return track_skip( &this->track_filter, count ); } -// fill internal buffer and check it for silence -void fill_buf( struct Kss_Emu* this ) +void Track_set_fade( struct Kss_Emu* this, int start_msec, int length_msec ) { - assert( !this->buf_remain ); - if ( !this->emu_track_ended_ ) - { - emu_play( this, buf_size, this->buf ); - long silence = count_silence( this->buf, buf_size ); - if ( silence < buf_size ) - { - this->silence_time = this->emu_time - silence; - this->buf_remain = buf_size; - return; - } - } - this->silence_count += buf_size; + track_set_fade( &this->track_filter, msec_to_samples( start_msec, this->sample_rate ), + length_msec * this->sample_rate / (1000 / stereo) ); } -blargg_err_t Kss_play( struct Kss_Emu* this, long out_count, sample_t* out ) +blargg_err_t Kss_play( struct Kss_Emu* this, int out_count, sample_t* out ) { - if ( this->track_ended ) - { - memset( out, 0, out_count * sizeof *out ); - } - else - { - require( this->current_track >= 0 ); - require( out_count % stereo == 0 ); - - assert( this->emu_time >= this->out_time ); - - // prints nifty graph of how far ahead we are when searching for silence - //debug_printf( "%*s \n", int ((emu_time - out_time) * 7 / sample_rate()), "*" ); - - long pos = 0; - if ( this->silence_count ) - { - // during a run of silence, run emulator at >=2x speed so it gets ahead - long ahead_time = this->silence_lookahead * (this->out_time + out_count -this->silence_time) + this->silence_time; - while ( this->emu_time < ahead_time && !(this->buf_remain |this-> emu_track_ended_) ) - fill_buf( this ); - - // fill with silence - pos = min( this->silence_count, out_count ); - memset( out, 0, pos * sizeof *out ); - this->silence_count -= pos; - - if ( this->emu_time - this->silence_time > silence_max * stereo * this->sample_rate ) - { - this->track_ended = this->emu_track_ended_ = true; - this->silence_count = 0; - this->buf_remain = 0; - } - } - - if ( this->buf_remain ) - { - // empty silence buf - long n = min( this->buf_remain, out_count - pos ); - memcpy( &out [pos], this->buf + (buf_size - this->buf_remain), n * sizeof *out ); - this->buf_remain -= n; - pos += n; - } - - // generate remaining samples normally - long remain = out_count - pos; - if ( remain ) - { - emu_play( this, remain, out + pos ); - this->track_ended |= this->emu_track_ended_; - - if ( !this->ignore_silence || this->out_time > this->fade_start ) - { - // check end for a new run of silence - long silence = count_silence( out + pos, remain ); - if ( silence < remain ) - this->silence_time = this->emu_time - silence; - - if ( this->emu_time - this->silence_time >= buf_size ) - fill_buf( this ); // cause silence detection on next play() - } - } - - if ( this->out_time > this->fade_start ) - handle_fade( this, out_count, out ); - } - this->out_time += out_count; - return 0; + require( this->current_track >= 0 ); + require( out_count % stereo == 0 ); + return track_play( &this->track_filter, out_count, out ); } -blargg_err_t play_( struct Kss_Emu* this, long count, sample_t* out ) +blargg_err_t play_( void *emu, int count, sample_t* out ) { - long remain = count; + struct Kss_Emu* this = (struct Kss_Emu*) emu; + + int remain = count; while ( remain ) { - remain -= Buffer_read_samples( &this->stereo_buffer, &out [count - remain], remain ); + Buffer_disable_immediate_removal( &this->stereo_buf ); + remain -= Buffer_read_samples( &this->stereo_buf, &out [count - remain], remain ); if ( remain ) { - if ( this->buf_changed_count != Buffer_channels_changed_count( &this->stereo_buffer ) ) + if ( this->buf_changed_count != Buffer_channels_changed_count( &this->stereo_buf ) ) { - this->buf_changed_count = Buffer_channels_changed_count( &this->stereo_buffer ); + this->buf_changed_count = Buffer_channels_changed_count( &this->stereo_buf ); + + // Remute voices Sound_mute_voices( this, this->mute_mask_ ); } - int msec = Buffer_length( &this->stereo_buffer ); - /* blip_time_t clocks_emulated = (blargg_long) msec * clock_rate_ / 1000; */ + int msec = Buffer_length( &this->stereo_buf ); blip_time_t clocks_emulated = msec * this->clock_rate_ / 1000 - 100; RETURN_ERR( run_clocks( this, &clocks_emulated ) ); assert( clocks_emulated ); - Buffer_end_frame( &this->stereo_buffer, clocks_emulated ); + Buffer_end_frame( &this->stereo_buf, clocks_emulated ); } } return 0; } - diff --git a/apps/codecs/libgme/kss_emu.h b/apps/codecs/libgme/kss_emu.h index 43df964fcc..382e4b807b 100644 --- a/apps/codecs/libgme/kss_emu.h +++ b/apps/codecs/libgme/kss_emu.h @@ -16,8 +16,8 @@ #include "ay_apu.h" #include "opl_apu.h" #include "m3u_playlist.h" +#include "track_filter.h" -typedef short sample_t; typedef int kss_time_t; typedef int kss_addr_t; typedef struct Z80_Cpu Kss_Cpu; @@ -35,7 +35,6 @@ enum { enum { idle_addr = 0xFFFF }; enum { scc_enabled_true = 0xC000 }; enum { mem_size = 0x10000 }; -enum { buf_size = 2048 }; // KSS file header enum { header_size = 0x20 }; @@ -53,7 +52,7 @@ struct header_t byte bank_mode; byte extra_header; byte device_flags; - + // KSSX extended data, if extra_header==0x10 byte data_size [4]; byte unused [4]; @@ -69,7 +68,7 @@ struct sms_t { struct Sms_Apu psg; struct Opl_Apu fm; }; - + struct msx_t { struct Ay_Apu psg; struct Scc_Apu scc; @@ -84,8 +83,6 @@ struct Kss_Emu { bool scc_accessed; bool gain_updated; - int track_count; - unsigned scc_enabled; // 0 or 0xC000 int bank_count; @@ -94,48 +91,34 @@ struct Kss_Emu { int ay_latch; // general - int max_initial_silence; int voice_count; + int const* voice_types; int mute_mask_; int tempo; int gain; - long sample_rate; + int sample_rate; // track-specific + int track_count; int current_track; - blargg_long out_time; // number of samples played since start of track - blargg_long emu_time; // number of samples emulator has generated since start of track - bool emu_track_ended_; // emulator has reached end of track - volatile bool track_ended; - - // fading - blargg_long fade_start; - int fade_step; - - // silence detection - int silence_lookahead; // speed to run emulator when looking ahead for silence - bool ignore_silence; - long silence_time; // number of samples where most recent silence began - long silence_count; // number of samples of silence to play before using buf - long buf_remain; // number of samples left in silence buffer - struct Stereo_Buffer stereo_buffer; // NULL if using custom buffer - long clock_rate_; + int clock_rate_; unsigned buf_changed_count; - // M3u Playlist + // M3u Playlist struct M3u_Playlist m3u; - // large items - sample_t buf [buf_size]; + struct setup_t tfilter; + struct Track_Filter track_filter; struct sms_t sms; struct msx_t msx; Kss_Cpu cpu; + struct Multi_Buffer stereo_buf; // NULL if using custom buffer struct Rom_Data rom; - + byte unmapped_read [0x100]; byte unmapped_write [page_size]; byte ram [mem_size + cpu_padding]; @@ -148,34 +131,46 @@ blargg_err_t Kss_load_mem( struct Kss_Emu* this, const void* data, long size ); blargg_err_t end_frame( struct Kss_Emu* this, kss_time_t ); // Set output sample rate. Must be called only once before loading file. -blargg_err_t Kss_set_sample_rate( struct Kss_Emu* this, long sample_rate ); +blargg_err_t Kss_set_sample_rate( struct Kss_Emu* this, int sample_rate ); // Start a track, where 0 is the first track. Also clears warning string. blargg_err_t Kss_start_track( struct Kss_Emu* this, int track ); // Generate 'count' samples info 'buf'. Output is in stereo. Any emulation // errors set warning string, and major errors also end track. -blargg_err_t Kss_play( struct Kss_Emu* this, long count, sample_t* buf ); +blargg_err_t Kss_play( struct Kss_Emu* this, int count, sample_t* buf ); // Track status/control // Number of milliseconds (1000 msec = 1 second) played since beginning of track -long Track_tell( struct Kss_Emu* this ); +int Track_tell( struct Kss_Emu* this ); // Seek to new time in track. Seeking backwards or far forward can take a while. -blargg_err_t Track_seek( struct Kss_Emu* this, long msec ); +blargg_err_t Track_seek( struct Kss_Emu* this, int msec ); // Skip n samples -blargg_err_t Track_skip( struct Kss_Emu* this, long n ); +blargg_err_t Track_skip( struct Kss_Emu* this, int n ); // Set start time and length of track fade out. Once fade ends track_ended() returns // true. Fade time can be changed while track is playing. -void Track_set_fade( struct Kss_Emu* this, long start_msec, long length_msec ); +void Track_set_fade( struct Kss_Emu* this, int start_msec, int length_msec ); + +// True if a track has reached its end +static inline bool Track_ended( struct Kss_Emu* this ) +{ + return track_ended( &this->track_filter ); +} + +// Disables automatic end-of-track detection and skipping of silence at beginning +static inline void Track_ignore_silence( struct Kss_Emu* this, bool disable ) +{ + this->track_filter.silence_ignored_ = disable; +} // Get track length in milliseconds -static inline long Track_get_length( struct Kss_Emu* this, int n ) +static inline int Track_get_length( struct Kss_Emu* this, int n ) { - long length = 0; + int length = 0; if ( (this->m3u.size > 0) && (n < this->m3u.size) ) { struct entry_t* entry = &this->m3u.entries [n]; diff --git a/apps/codecs/libgme/kss_scc_apu.h b/apps/codecs/libgme/kss_scc_apu.h index bb20d1d64a..a6962469ac 100644 --- a/apps/codecs/libgme/kss_scc_apu.h +++ b/apps/codecs/libgme/kss_scc_apu.h @@ -34,7 +34,7 @@ void Scc_reset( struct Scc_Apu* this ); // Set overall volume, where 1.0 is normal void Scc_volume( struct Scc_Apu* this, int v ); - + static inline void Scc_set_output( struct Scc_Apu* this, int index, struct Blip_Buffer* b ) { assert( (unsigned) index < scc_osc_count ); diff --git a/apps/codecs/libgme/multi_buffer.c b/apps/codecs/libgme/multi_buffer.c index 26cb8cdec6..554778c3de 100644 --- a/apps/codecs/libgme/multi_buffer.c +++ b/apps/codecs/libgme/multi_buffer.c @@ -1,4 +1,4 @@ -// Blip_Buffer 0.4.1. http://www.slack.net/~ant/ +// Multi_Buffer 0.4.1. http://www.slack.net/~ant/ #include "multi_buffer.h" @@ -15,212 +15,272 @@ Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "blargg_source.h" -#ifdef BLARGG_ENABLE_OPTIMIZER - #include BLARGG_ENABLE_OPTIMIZER -#endif - -// Stereo_Buffer - -void Buffer_init( struct Stereo_Buffer* this ) -{ - Blip_init( &this->bufs [0] ); - Blip_init( &this->bufs [1] ); - Blip_init( &this->bufs [2] ); - - this->chan.center = &this->bufs [0]; - this->chan.left = &this->bufs [1]; - this->chan.right = &this->bufs [2]; - - this->length_ = 0; - this->sample_rate_ = 0; - this->channels_changed_count_ = 1; - this->samples_per_frame_ = 2; +// Tracked_Blip_Buffer + +int const blip_buffer_extra = 32; // TODO: explain why this value + +void Tracked_init( struct Tracked_Blip_Buffer* this ) +{ + Blip_init( &this->blip ); + this->last_non_silence = 0; } -blargg_err_t Buffer_set_sample_rate( struct Stereo_Buffer* this, long rate, int msec ) +void Tracked_clear( struct Tracked_Blip_Buffer* this ) { - int i; - for ( i = 0; i < buf_count; i++ ) - RETURN_ERR( Blip_set_sample_rate( &this->bufs[i], rate, msec ) ); - - this->sample_rate_ = Blip_sample_rate( &this->bufs [0] ); - this->length_ = Blip_length( &this->bufs [0] ); - return 0; + this->last_non_silence = 0; + Blip_clear( &this->blip ); } -void Buffer_clock_rate( struct Stereo_Buffer* this, long rate ) +void Tracked_end_frame( struct Tracked_Blip_Buffer* this, blip_time_t t ) { - int i; - for ( i = 0; i < buf_count; i++ ) - Blip_set_clock_rate( &this->bufs [i], rate ); + Blip_end_frame( &this->blip, t ); + if ( this->blip.modified ) + { + this->blip.modified = false; + this->last_non_silence = Blip_samples_avail( &this->blip ) + blip_buffer_extra; + } } -void Buffer_bass_freq( struct Stereo_Buffer* this, int bass ) +unsigned Tracked_non_silent( struct Tracked_Blip_Buffer* this ) { - unsigned i; - for ( i = 0; i < buf_count; i++ ) - Blip_bass_freq( &this->bufs [i], bass ); + return this->last_non_silence | unsettled( &this->blip ); } -struct channel_t Buffer_channel( struct Stereo_Buffer* this ) +static inline void remove_( struct Tracked_Blip_Buffer* this, int n ) { - return this->chan; + if ( (this->last_non_silence -= n) < 0 ) + this->last_non_silence = 0; } -void Buffer_clear( struct Stereo_Buffer* this ) +void Tracked_remove_silence( struct Tracked_Blip_Buffer* this, int n ) { - this->stereo_added = 0; - this->was_stereo = false; - int i; - for ( i = 0; i < buf_count; i++ ) - Blip_clear( &this->bufs [i], 1 ); + remove_( this, n ); + Blip_remove_silence( &this->blip, n ); } -void Buffer_end_frame( struct Stereo_Buffer* this, blip_time_t clock_count ) +void Tracked_remove_samples( struct Tracked_Blip_Buffer* this, int n ) +{ + remove_( this, n ); + Blip_remove_samples( &this->blip, n ); +} + +void Tracked_remove_all_samples( struct Tracked_Blip_Buffer* this ) +{ + int avail = Blip_samples_avail( &this->blip ); + if ( !Tracked_non_silent( this ) ) + Tracked_remove_silence( this, avail ); + else + Tracked_remove_samples( this, avail ); +} + +int Tracked_read_samples( struct Tracked_Blip_Buffer* this, blip_sample_t out [], int count ) +{ + count = Blip_read_samples( &this->blip, out, count, false ); + remove_( this, count ); + return count; +} + +// Stereo_Mixer + +// mixers use a single index value to improve performance on register-challenged processors +// offset goes from negative to zero + +void Mixer_init( struct Stereo_Mixer* this ) { - this->stereo_added = 0; - unsigned i; - for ( i = 0; i < buf_count; i++ ) + this->samples_read = 0; +} + +static void mix_mono( struct Stereo_Mixer* this, blip_sample_t out_ [], int count ) +{ + int const bass = this->bufs [2]->blip.bass_shift_; + delta_t const* center = this->bufs [2]->blip.buffer_ + this->samples_read; + int center_sum = this->bufs [2]->blip.reader_accum_; + + typedef blip_sample_t stereo_blip_sample_t [stereo]; + stereo_blip_sample_t* BLARGG_RESTRICT out = (stereo_blip_sample_t*) out_ + count; + int offset = -count; + do { - this->stereo_added |= Blip_clear_modified( &this->bufs [i] ) << i; - Blip_end_frame( &this->bufs [i], clock_count ); + int s = center_sum >> delta_bits; + + center_sum -= center_sum >> bass; + center_sum += center [offset]; + + BLIP_CLAMP( s, s ); + + out [offset] [0] = (blip_sample_t) s; + out [offset] [1] = (blip_sample_t) s; } + while ( ++offset ); + + this->bufs [2]->blip.reader_accum_ = center_sum; } -long Buffer_read_samples( struct Stereo_Buffer* this, blip_sample_t* out, long count ) +static void mix_stereo( struct Stereo_Mixer* this, blip_sample_t out_ [], int count ) { - require( !(count & 1) ); // count must be even - count = (unsigned) count / 2; - - long avail = Blip_samples_avail( &this->bufs [0] ); - if ( count > avail ) - count = avail; - if ( count ) + blip_sample_t* BLARGG_RESTRICT out = out_ + count * stereo; + // do left + center and right + center separately to reduce register load + struct Tracked_Blip_Buffer* const* buf = &this->bufs [2]; + while ( true ) // loop runs twice { - int bufs_used = this->stereo_added | this->was_stereo; - //dprintf( "%X\n", bufs_used ); - if ( bufs_used <= 1 ) - { - Buffer_mix_mono( this, out, count ); - Blip_remove_samples( &this->bufs [0], count ); - Blip_remove_silence( &this->bufs [1], count ); - Blip_remove_silence( &this->bufs [2], count ); - } - else if ( bufs_used & 1 ) - { - Buffer_mix_stereo( this, out, count ); - Blip_remove_samples( &this->bufs [0], count ); - Blip_remove_samples( &this->bufs [1], count ); - Blip_remove_samples( &this->bufs [2], count ); - } - else - { - Buffer_mix_stereo_no_center( this, out, count ); - Blip_remove_silence( &this->bufs [0], count ); - Blip_remove_samples( &this->bufs [1], count ); - Blip_remove_samples( &this->bufs [2], count ); - } - - // to do: this might miss opportunities for optimization - if ( !Blip_samples_avail( &this->bufs [0] ) ) + --buf; + --out; + + int const bass = this->bufs [2]->blip.bass_shift_; + delta_t const* side = (*buf)->blip.buffer_ + this->samples_read; + delta_t const* center = this->bufs [2]->blip.buffer_ + this->samples_read; + + int side_sum = (*buf)->blip.reader_accum_; + int center_sum = this->bufs [2]->blip.reader_accum_; + + int offset = -count; + do { - this->was_stereo = this->stereo_added; - this->stereo_added = 0; + int s = (center_sum + side_sum) >> delta_bits; + + side_sum -= side_sum >> bass; + center_sum -= center_sum >> bass; + + side_sum += side [offset]; + center_sum += center [offset]; + + BLIP_CLAMP( s, s ); + + ++offset; // before write since out is decremented to slightly before end + out [offset * stereo] = (blip_sample_t) s; } + while ( offset ); + + (*buf)->blip.reader_accum_ = side_sum; + + if ( buf != this->bufs ) + continue; + + // only end center once + this->bufs [2]->blip.reader_accum_ = center_sum; + break; } - - return count * 2; } -unsigned Buffer_channels_changed_count( struct Stereo_Buffer* this ) +void Mixer_read_pairs( struct Stereo_Mixer* this, blip_sample_t out [], int count ) { - return this->channels_changed_count_; + // TODO: if caller never marks buffers as modified, uses mono + // except that buffer isn't cleared, so caller can encounter + // subtle problems and not realize the cause. + this->samples_read += count; + if ( Tracked_non_silent( this->bufs [0] ) | Tracked_non_silent( this->bufs [1] ) ) + mix_stereo( this, out, count ); + else + mix_mono( this, out, count ); } -void Buffer_channels_changed( struct Stereo_Buffer* this ) +// Multi_Buffer + +void Buffer_init( struct Multi_Buffer* this ) { - this->channels_changed_count_++; + int const spf = 2; + + Tracked_init( &this->bufs [0] ); + Tracked_init( &this->bufs [1] ); + Tracked_init( &this->bufs [2] ); + + Mixer_init( &this->mixer ); + + this->length_ = 0; + this->sample_rate_ = 0; + this->channels_changed_count_ = 1; + this->channel_types_ = NULL; + this->channel_count_ = 0; + this->samples_per_frame_ = spf; + this->immediate_removal_ = true; + + this->mixer.bufs [2] = &this->bufs [2]; + this->mixer.bufs [0] = &this->bufs [0]; + this->mixer.bufs [1] = &this->bufs [1]; + + this->chan.center = &this->bufs [2].blip; + this->chan.left = &this->bufs [0].blip; + this->chan.right = &this->bufs [1].blip; } -void Buffer_mix_stereo( struct Stereo_Buffer* this, blip_sample_t* out_, blargg_long count ) +blargg_err_t Buffer_set_sample_rate( struct Multi_Buffer* this, int rate, int msec ) { - blip_sample_t* BLIP_RESTRICT out = out_; - int const bass = BLIP_READER_BASS( this->bufs [1] ); - BLIP_READER_BEGIN( left, this->bufs [1] ); - BLIP_READER_BEGIN( right, this->bufs [2] ); - BLIP_READER_BEGIN( center, this->bufs [0] ); - - for ( ; count; --count ) - { - int c = BLIP_READER_READ( center ); - blargg_long l = c + BLIP_READER_READ( left ); - blargg_long r = c + BLIP_READER_READ( right ); - if ( (int16_t) l != l ) - l = 0x7FFF - (l >> 24); - - BLIP_READER_NEXT( center, bass ); - if ( (int16_t) r != r ) - r = 0x7FFF - (r >> 24); - - BLIP_READER_NEXT( left, bass ); - BLIP_READER_NEXT( right, bass ); - - out [0] = l; - out [1] = r; - out += 2; - } - - BLIP_READER_END( center, this->bufs [0] ); - BLIP_READER_END( right, this->bufs [2] ); - BLIP_READER_END( left, this->bufs [1] ); + int i; + for ( i = bufs_size; --i >= 0; ) + RETURN_ERR( Blip_set_sample_rate( &this->bufs [i].blip, rate, msec ) ); + + this->sample_rate_ = Blip_sample_rate( &this->bufs [0].blip ); + this->length_ = Blip_length( &this->bufs [0].blip ); + return 0; } -void Buffer_mix_stereo_no_center( struct Stereo_Buffer* this, blip_sample_t* out_, blargg_long count ) +void Buffer_clock_rate( struct Multi_Buffer* this, int rate ) { - blip_sample_t* BLIP_RESTRICT out = out_; - int const bass = BLIP_READER_BASS( this->bufs [1] ); - BLIP_READER_BEGIN( left, this->bufs [1] ); - BLIP_READER_BEGIN( right, this->bufs [2] ); - - for ( ; count; --count ) - { - blargg_long l = BLIP_READER_READ( left ); - if ( (int16_t) l != l ) - l = 0x7FFF - (l >> 24); - - blargg_long r = BLIP_READER_READ( right ); - if ( (int16_t) r != r ) - r = 0x7FFF - (r >> 24); - - BLIP_READER_NEXT( left, bass ); - BLIP_READER_NEXT( right, bass ); - - out [0] = l; - out [1] = r; - out += 2; - } - - BLIP_READER_END( right, this->bufs [2] ); - BLIP_READER_END( left, this->bufs [1] ); + int i; + for ( i = bufs_size; --i >= 0; ) + Blip_set_clock_rate( &this->bufs [i].blip, rate ); +} + +void Buffer_bass_freq( struct Multi_Buffer* this, int bass ) +{ + int i; + for ( i = bufs_size; --i >= 0; ) + Blip_bass_freq( &this->bufs [i].blip, bass ); +} + +blargg_err_t Buffer_set_channel_count( struct Multi_Buffer* this, int n, int const* types ) +{ + this->channel_count_ = n; + this->channel_types_ = types; + return 0; } -void Buffer_mix_mono( struct Stereo_Buffer* this, blip_sample_t* out_, blargg_long count ) +struct channel_t Buffer_channel( struct Multi_Buffer* this, int i ) { - blip_sample_t* BLIP_RESTRICT out = out_; - int const bass = BLIP_READER_BASS( this->bufs [0] ); - BLIP_READER_BEGIN( center, this->bufs [0] ); - - for ( ; count; --count ) + (void) i; + return this->chan; +} + +void Buffer_clear( struct Multi_Buffer* this ) +{ + int i; + this->mixer.samples_read = 0; + for ( i = bufs_size; --i >= 0; ) + Tracked_clear( &this->bufs [i] ); +} + +void Buffer_end_frame( struct Multi_Buffer* this, blip_time_t clock_count ) +{ + int i; + for ( i = bufs_size; --i >= 0; ) + Tracked_end_frame( &this->bufs [i], clock_count ); +} + +int Buffer_read_samples( struct Multi_Buffer* this, blip_sample_t out [], int out_size ) +{ + require( (out_size & 1) == 0 ); // must read an even number of samples + out_size = min( out_size, Buffer_samples_avail( this ) ); + + int pair_count = (int) (out_size >> 1); + if ( pair_count ) { - blargg_long s = BLIP_READER_READ( center ); - if ( (int16_t) s != s ) - s = 0x7FFF - (s >> 24); - - BLIP_READER_NEXT( center, bass ); - out [0] = s; - out [1] = s; - out += 2; + Mixer_read_pairs( &this->mixer, out, pair_count ); + + if ( Buffer_samples_avail( this ) <= 0 || this->immediate_removal_ ) + { + int i; + for ( i = bufs_size; --i >= 0; ) + { + buf_t* b = &this->bufs [i]; + // TODO: might miss non-silence settling since it checks END of last read + if ( !Tracked_non_silent( b ) ) + Tracked_remove_silence( b, this->mixer.samples_read ); + else + Tracked_remove_samples( b, this->mixer.samples_read ); + } + this->mixer.samples_read = 0; + } } - - BLIP_READER_END( center, this->bufs [0] ); + + return out_size; } diff --git a/apps/codecs/libgme/multi_buffer.h b/apps/codecs/libgme/multi_buffer.h index cfdae4f077..e5efa5a230 100644 --- a/apps/codecs/libgme/multi_buffer.h +++ b/apps/codecs/libgme/multi_buffer.h @@ -1,4 +1,4 @@ -// Multi-channel sound buffer interface, and basic mono and stereo buffers +// Multi-channel sound buffer interface, stereo and effects buffers // Blip_Buffer 0.4.1 #ifndef MULTI_BUFFER_H @@ -16,57 +16,99 @@ struct channel_t { enum { type_index_mask = 0xFF }; enum { wave_type = 0x100, noise_type = 0x200, mixed_type = wave_type | noise_type }; -enum { buf_count = 3 }; - -struct Stereo_Buffer { - struct Blip_Buffer bufs [buf_count]; - struct channel_t chan; - int stereo_added; - int was_stereo; - +enum { stereo = 2 }; +enum { bufs_size = 3 }; + +// Tracked_Blip_Buffer +struct Tracked_Blip_Buffer { + struct Blip_Buffer blip; + int last_non_silence; +}; + +void Tracked_init( struct Tracked_Blip_Buffer* this ); +unsigned Tracked_non_silent( struct Tracked_Blip_Buffer* this ); +void Tracked_remove_all_samples( struct Tracked_Blip_Buffer * this ); +int Tracked_read_samples( struct Tracked_Blip_Buffer* this, blip_sample_t [], int ); +void Tracked_remove_silence( struct Tracked_Blip_Buffer* this, int ); +void Tracked_remove_samples( struct Tracked_Blip_Buffer* this, int ); +void Tracked_clear( struct Tracked_Blip_Buffer* this ); +void Tracked_end_frame( struct Tracked_Blip_Buffer* this, blip_time_t ); + +static inline delta_t unsettled( struct Blip_Buffer* this ) +{ + return this->reader_accum_ >> delta_bits; +} + +// Stereo Mixer +struct Stereo_Mixer { + struct Tracked_Blip_Buffer* bufs [3]; + int samples_read; +}; + +void Mixer_init( struct Stereo_Mixer* this ); +void Mixer_read_pairs( struct Stereo_Mixer* this, blip_sample_t out [], int count ); + +typedef struct Tracked_Blip_Buffer buf_t; + +// Multi_Buffer +struct Multi_Buffer { unsigned channels_changed_count_; - long sample_rate_; + int sample_rate_; int length_; + int channel_count_; int samples_per_frame_; + int const *channel_types_; + bool immediate_removal_; + + buf_t bufs [bufs_size]; + struct Stereo_Mixer mixer; + struct channel_t chan; }; -// Initializes Stereo_Buffer structure -void Buffer_init( struct Stereo_Buffer* this ); +blargg_err_t Buffer_set_channel_count( struct Multi_Buffer* this, int n, int const* types ); + +// Buffers used for all channels +static inline struct Blip_Buffer* center( struct Multi_Buffer* this ) { return &this->bufs [2].blip; } +static inline struct Blip_Buffer* left( struct Multi_Buffer* this ) { return &this->bufs [0].blip; } +static inline struct Blip_Buffer* right( struct Multi_Buffer* this ) { return &this->bufs [1].blip; } -blargg_err_t Buffer_set_sample_rate( struct Stereo_Buffer* this, long, int msec ); -void Buffer_clock_rate( struct Stereo_Buffer* this, long ); -void Buffer_bass_freq( struct Stereo_Buffer* this, int ); -void Buffer_clear( struct Stereo_Buffer* this ); -struct channel_t Buffer_channel( struct Stereo_Buffer* this ); -void Buffer_end_frame( struct Stereo_Buffer* this, blip_time_t ); +// Initializes Multi_Buffer structure +void Buffer_init( struct Multi_Buffer* this ); -long Buffer_read_samples( struct Stereo_Buffer* this, blip_sample_t*, long ); +blargg_err_t Buffer_set_sample_rate( struct Multi_Buffer* this, int, int msec ); +void Buffer_clock_rate( struct Multi_Buffer* this, int ); +void Buffer_bass_freq( struct Multi_Buffer* this, int ); +void Buffer_clear( struct Multi_Buffer* this ); +void Buffer_end_frame( struct Multi_Buffer* this, blip_time_t ) ICODE_ATTR; + +static inline int Buffer_length( struct Multi_Buffer* this ) +{ + return this->length_; +} // Count of changes to channel configuration. Incremented whenever // a change is made to any of the Blip_Buffers for any channel. -unsigned Buffer_channels_changed_count( struct Stereo_Buffer* this ); -void Buffer_channels_changed( struct Stereo_Buffer* this ); - -void Buffer_mix_stereo_no_center( struct Stereo_Buffer* this, blip_sample_t*, blargg_long ); -void Buffer_mix_stereo( struct Stereo_Buffer* this, blip_sample_t*, blargg_long ); -void Buffer_mix_mono( struct Stereo_Buffer* this, blip_sample_t*, blargg_long ); +static inline unsigned Buffer_channels_changed_count( struct Multi_Buffer* this ) +{ + return this->channels_changed_count_; +} -// Number of samples per output frame (1 = mono, 2 = stereo) -static inline int Buffer_samples_per_frame( struct Stereo_Buffer* this ) +static inline void Buffer_disable_immediate_removal( struct Multi_Buffer* this ) { - return this->samples_per_frame_; + this->immediate_removal_ = false; } -// See Blip_Buffer.h -static inline long Buffer_sample_rate( struct Stereo_Buffer* this ) +static inline int Buffer_sample_rate( struct Multi_Buffer* this ) { return this->sample_rate_; } -// Length of buffer, in milliseconds -static inline int Buffer_length( struct Stereo_Buffer* this ) +static inline int Buffer_samples_avail( struct Multi_Buffer* this ) { - return this->length_; + return (Blip_samples_avail(&this->bufs [0].blip) - this->mixer.samples_read) * 2; } +int Buffer_read_samples( struct Multi_Buffer* this, blip_sample_t*, int ) ICODE_ATTR; +struct channel_t Buffer_channel( struct Multi_Buffer* this, int i ); + #endif diff --git a/apps/codecs/libgme/nes_apu.c b/apps/codecs/libgme/nes_apu.c index 7d2814b3d8..630e71f450 100644 --- a/apps/codecs/libgme/nes_apu.c +++ b/apps/codecs/libgme/nes_apu.c @@ -39,19 +39,19 @@ void Apu_init( struct Nes_Apu* this ) this->oscs [4] = &this->dmc.osc; Apu_output( this, NULL ); + this->dmc.nonlinear = false; Apu_volume( this, (int)FP_ONE_VOLUME ); Apu_reset( this, false, 0 ); } -void Apu_enable_nonlinear( struct Nes_Apu* this, int v ) +void Apu_enable_nonlinear_( struct Nes_Apu* this, double sq, double tnd ) { this->dmc.nonlinear = true; - Synth_volume( &this->square_synth, (int)((long long)(1.3 * 0.25751258 / 0.742467605 * 0.25 * FP_ONE_VOLUME) / amp_range * v) ); + Synth_volume( &this->square_synth, (int)((long long)(sq * FP_ONE_VOLUME) / amp_range) ); - const int tnd = (int)(0.48 / 202 * 0.75 * FP_ONE_VOLUME); - Synth_volume( &this->triangle.synth, 3 * tnd ); - Synth_volume( &this->noise.synth, 2 * tnd ); - Synth_volume( &this->dmc.synth, tnd ); + Synth_volume( &this->triangle.synth, tnd * 2.752 ); + Synth_volume( &this->noise.synth , tnd * 1.849 ); + Synth_volume( &this->dmc.synth , tnd ); this->square1 .osc.last_amp = 0; this->square2 .osc.last_amp = 0; @@ -62,11 +62,13 @@ void Apu_enable_nonlinear( struct Nes_Apu* this, int v ) void Apu_volume( struct Nes_Apu* this, int v ) { - this->dmc.nonlinear = false; - Synth_volume( &this->square_synth, (int)((long long)(0.1128 *FP_ONE_VOLUME) * v / amp_range / FP_ONE_VOLUME) ); - Synth_volume( &this->triangle.synth,(int)((long long)(0.12765*FP_ONE_VOLUME) * v / amp_range / FP_ONE_VOLUME) ); - Synth_volume( &this->noise.synth, (int)((long long)(0.0741 *FP_ONE_VOLUME) * v / amp_range / FP_ONE_VOLUME) ); - Synth_volume( &this->dmc.synth, (int)((long long)(0.42545*FP_ONE_VOLUME) * v / 127 / FP_ONE_VOLUME) ); + if ( !this->dmc.nonlinear ) + { + Synth_volume( &this->square_synth, (int)((long long)((0.125 * (1.0 /1.11)) * FP_ONE_VOLUME) * v / amp_range / FP_ONE_VOLUME) ); // was 0.1128 1.108 + Synth_volume( &this->triangle.synth,(int)((long long)((0.150 * (1.0 /1.11)) * FP_ONE_VOLUME) * v / amp_range / FP_ONE_VOLUME) ); // was 0.12765 1.175 + Synth_volume( &this->noise.synth, (int)((long long)((0.095 * (1.0 /1.11)) * FP_ONE_VOLUME) * v / amp_range / FP_ONE_VOLUME) ); // was 0.0741 1.282 + Synth_volume( &this->dmc.synth, (int)((long long)((0.450 * (1.0 /1.11)) * FP_ONE_VOLUME) * v / 2048 / FP_ONE_VOLUME) ); // was 0.42545 1.058 + } } void Apu_output( struct Nes_Apu* this, struct Blip_Buffer* buffer ) diff --git a/apps/codecs/libgme/nes_apu.h b/apps/codecs/libgme/nes_apu.h index 11f1f26cc7..0a2b1f57cd 100644 --- a/apps/codecs/libgme/nes_apu.h +++ b/apps/codecs/libgme/nes_apu.h @@ -1,6 +1,6 @@ // NES 2A03 APU sound chip emulator -// Nes_Snd_Emu 0.1.8 +// Nes_Snd_Emu 0.2.0-pre #ifndef NES_APU_H #define NES_APU_H @@ -9,7 +9,7 @@ enum { apu_status_addr = 0x4015 }; enum { apu_osc_count = 5 }; -enum { apu_no_irq = INT_MAX / 2 + 1 }; +enum { apu_no_irq = INT_MAX/2 + 1 }; enum { apu_irq_waiting = 0 }; enum { apu_io_addr = 0x4000 }; @@ -17,24 +17,16 @@ enum { apu_io_size = 0x18 }; struct apu_state_t; -struct Nes_Apu { - nes_time_t last_dmc_time; - int osc_enables; - - struct Nes_Osc* oscs [apu_osc_count]; - struct Nes_Square square1; - struct Nes_Square square2; - struct Nes_Noise noise; - struct Nes_Triangle triangle; - struct Nes_Dmc dmc; - +struct Nes_Apu { int tempo_; nes_time_t last_time; // has been run until this time in current frame + nes_time_t last_dmc_time; nes_time_t earliest_irq_; nes_time_t next_irq; int frame_period; int frame_delay; // cycles until frame counter runs next int frame; // current frame (0-3) + int osc_enables; int frame_mode; bool irq_flag; @@ -42,6 +34,13 @@ struct Nes_Apu { void* irq_data; Synth square_synth; // shared by squares + + struct Nes_Osc* oscs [apu_osc_count]; + struct Nes_Square square1; + struct Nes_Square square2; + struct Nes_Noise noise; + struct Nes_Triangle triangle; + struct Nes_Dmc dmc; }; // Init Nes apu @@ -49,22 +48,22 @@ void Apu_init( struct Nes_Apu* this ); // Set buffer to generate all sound into, or disable sound if NULL void Apu_output( struct Nes_Apu* this, struct Blip_Buffer* ); - + // All time values are the number of cpu clock cycles relative to the // beginning of the current time frame. Before resetting the cpu clock // count, call end_frame( last_cpu_time ). // Write to register (0x4000-0x4017, except 0x4014 and 0x4016) void Apu_write_register( struct Nes_Apu* this, nes_time_t, addr_t, int data ); - + // Read from status register at 0x4015 int Apu_read_status( struct Nes_Apu* this, nes_time_t ); - + // Run all oscillators up to specified time, end current time frame, then // start a new time frame at time 0. Time frames have no effect on emulation // and each can be whatever length is convenient. void Apu_end_frame( struct Nes_Apu* this, nes_time_t ); - + // Additional optional features (can be ignored without any problem) // Reset internal frame counter, registers, and all oscillators. @@ -72,13 +71,13 @@ void Apu_end_frame( struct Nes_Apu* this, nes_time_t ); // Set the DMC oscillator's initial DAC value to initial_dmc_dac without // any audible click. void Apu_reset( struct Nes_Apu* this, bool pal_mode, int initial_dmc_dac ); - + // Adjust frame period void Apu_set_tempo( struct Nes_Apu* this, int ); - + // Set overall volume (default is 1.0) void Apu_volume( struct Nes_Apu* this, int ); - + // Run DMC until specified time, so that any DMC memory reads can be // accounted for (i.e. inserting cpu wait states). void Apu_run_until( struct Nes_Apu* this, nes_time_t ); @@ -124,11 +123,13 @@ static inline nes_time_t Dmc_next_read_time( struct Nes_Dmc* this ) if ( this->osc.length_counter == 0 ) return apu_no_irq; // not reading - return this->apu->last_dmc_time + this->osc.delay + (long) (this->bits_remain - 1) * this->period; + return this->apu->last_dmc_time + this->osc.delay + (this->bits_remain - 1) * this->period; } // Time when next DMC memory read will occur static inline nes_time_t Apu_next_dmc_read_time( struct Nes_Apu* this ) { return Dmc_next_read_time( &this->dmc ); } void Apu_irq_changed( struct Nes_Apu* this ); +// Experimental +void Apu_enable_nonlinear_( struct Nes_Apu* this, double sq, double tnd ); #endif diff --git a/apps/codecs/libgme/nes_cpu_io.h b/apps/codecs/libgme/nes_cpu_io.h deleted file mode 100644 index 4f9d416c2d..0000000000 --- a/apps/codecs/libgme/nes_cpu_io.h +++ /dev/null @@ -1,94 +0,0 @@ - -#include "nsf_emu.h" - -#ifndef NSF_EMU_APU_ONLY - #include "nes_namco_apu.h" - #include "nes_fds_apu.h" - #include "nes_mmc5_apu.h" -#endif - -#include "blargg_source.h" - -int Cpu_read( struct Nsf_Emu* this, nes_addr_t addr ) -{ - int result = this->cpu.low_mem [addr & 0x7FF]; - if ( addr & 0xE000 ) - { - result = *Cpu_get_code( &this->cpu, addr ); - if ( addr < sram_addr ) - { - if ( addr == status_addr ) - result = Apu_read_status( &this->apu, Cpu_time( &this->cpu ) ); - else - { - #ifndef NSF_EMU_APU_ONLY - if ( namco_enabled( this ) && addr == namco_data_reg_addr ) - return Namco_read_data( &this->namco ); - - if ( fds_enabled( this ) && (unsigned) (addr - fds_io_addr) < fds_io_size ) - return Fds_read( &this->fds, Cpu_time( &this->cpu ), addr ); - - if ( mmc5_enabled( this ) ) { - int i = addr - 0x5C00; - if ( (unsigned) i < mmc5_exram_size ) - return this->mmc5.exram [i]; - - int m = addr - 0x5205; - if ( (unsigned) m < 2 ) - return (this->mmc5_mul [0] * this->mmc5_mul [1]) >> (m * 8) & 0xFF; - } - #endif - result = addr >> 8; // simulate open bus - } - } - } - - /* if ( addr != 0x2002 ) - debug_printf( "Read unmapped $%.4X\n", (unsigned) addr ); */ - - return result; -} - -void Cpu_write( struct Nsf_Emu* this, nes_addr_t addr, int data ) -{ - int offset = addr - sram_addr; - if ( (unsigned) offset < sram_size ) - { - this->sram [offset] = data; - } - else - { - // after sram because cpu handles most low_ram accesses internally already - int temp = addr & (low_ram_size-1); // also handles wrap-around - if ( !(addr & 0xE000) ) - { - this->cpu.low_mem [temp] = data; - } - else - { - int bank = addr - banks_addr; - if ( (unsigned) bank < bank_count ) - { - Write_bank( this, bank, data ); - } - else if ( (unsigned) (addr - start_addr) <= end_addr - start_addr ) - { - Apu_write_register( &this->apu, Cpu_time( &this->cpu ), addr, data ); - } - else - { - #ifndef NSF_EMU_APU_ONLY - // 0x8000-0xDFFF is writable - int i = addr - 0x8000; - if ( fds_enabled( this ) && (unsigned) i < fdsram_size ) - fdsram( this ) [i] = data; - else - #endif - Cpu_write_misc( this, addr, data ); - } - } - } -} - -#define CPU_READ( emu, addr, time ) Cpu_read( emu, addr ) -#define CPU_WRITE( emu, addr, data, time ) Cpu_write( emu, addr, data ) diff --git a/apps/codecs/libgme/nes_fme7_apu.c b/apps/codecs/libgme/nes_fme7_apu.c index 8a2c21ea26..3e47e0b17c 100644 --- a/apps/codecs/libgme/nes_fme7_apu.c +++ b/apps/codecs/libgme/nes_fme7_apu.c @@ -64,8 +64,6 @@ void Fme7_run_until( struct Nes_Fme7_Apu* this, blip_time_t end_time ) struct Blip_Buffer* const osc_output = this->oscs [index].output; if ( !osc_output ) continue; - /* osc_output->set_modified(); */ - Blip_set_modified( osc_output ); // check for unsupported mode #ifndef NDEBUG @@ -92,11 +90,13 @@ void Fme7_run_until( struct Nes_Fme7_Apu* this, blip_time_t end_time ) int amp = volume; if ( !this->phases [index] ) amp = 0; + { int delta = amp - this->oscs [index].last_amp; if ( delta ) { this->oscs [index].last_amp = amp; + Blip_set_modified( osc_output ); Synth_offset( &this->synth, this->last_time, delta, osc_output ); } } @@ -105,6 +105,7 @@ void Fme7_run_until( struct Nes_Fme7_Apu* this, blip_time_t end_time ) if ( time < end_time ) { int delta = amp * 2 - volume; + Blip_set_modified( osc_output ); if ( volume ) { do @@ -123,7 +124,7 @@ void Fme7_run_until( struct Nes_Fme7_Apu* this, blip_time_t end_time ) // maintain phase when silent int count = (end_time - time + period - 1) / period; this->phases [index] ^= count & 1; - time += (blargg_long) count * period; + time += count * period; } } diff --git a/apps/codecs/libgme/nes_fme7_apu.h b/apps/codecs/libgme/nes_fme7_apu.h index 353d82d1df..c0eac4c765 100644 --- a/apps/codecs/libgme/nes_fme7_apu.h +++ b/apps/codecs/libgme/nes_fme7_apu.h @@ -1,6 +1,6 @@ // Sunsoft FME-7 sound emulator -// Game_Music_Emu 0.5.5 +// Game_Music_Emu 0.6-pre #ifndef NES_FME7_APU_H #define NES_FME7_APU_H diff --git a/apps/codecs/libgme/nes_namco_apu.c b/apps/codecs/libgme/nes_namco_apu.c index 0f64d3c84f..34df200bb6 100644 --- a/apps/codecs/libgme/nes_namco_apu.c +++ b/apps/codecs/libgme/nes_namco_apu.c @@ -68,8 +68,6 @@ void Namco_run_until( struct Nes_Namco_Apu* this, blip_time_t nes_end_time ) struct Blip_Buffer* output = osc->output; if ( !output ) continue; - /* output->set_modified(); */ - Blip_set_modified( output ); blip_resampled_time_t time = Blip_resampled_time( output, this->last_time ) + osc->delay; @@ -85,12 +83,17 @@ void Namco_run_until( struct Nes_Namco_Apu* this, blip_time_t nes_end_time ) if ( !volume ) continue; - blargg_long freq = (osc_reg [4] & 3) * 0x10000 + osc_reg [2] * 0x100L + osc_reg [0]; + int freq = (osc_reg [4] & 3) * 0x10000 + osc_reg [2] * 0x100L + osc_reg [0]; if ( freq < 64 * active_oscs ) continue; // prevent low frequencies from excessively delaying freq changes + + int const master_clock_divider = 12; // NES time derived via divider of master clock + int const n106_divider = 45; // N106 then divides master clock by this + int const max_freq = 0x3FFFF; + int const lowest_freq_period = (max_freq + 1) * n106_divider / master_clock_divider; + // divide by 8 to avoid overflow blip_resampled_time_t period = - /* output->resampled_duration( 983040 ) / freq * active_oscs; */ - Blip_resampled_duration( output, 983040 ) / freq * active_oscs; + Blip_resampled_duration( output, lowest_freq_period / 8 ) / freq * 8 * active_oscs; int wave_size = 32 - (osc_reg [4] >> 2 & 7) * 4; if ( !wave_size ) @@ -99,6 +102,8 @@ void Namco_run_until( struct Nes_Namco_Apu* this, blip_time_t nes_end_time ) int last_amp = osc->last_amp; int wave_pos = osc->wave_pos; + Blip_set_modified( output ); + do { // read wave sample diff --git a/apps/codecs/libgme/nes_namco_apu.h b/apps/codecs/libgme/nes_namco_apu.h index c47eacc4bb..c428c894c3 100644 --- a/apps/codecs/libgme/nes_namco_apu.h +++ b/apps/codecs/libgme/nes_namco_apu.h @@ -15,13 +15,13 @@ enum { namco_data_reg_addr = 0x4800 }; enum { namco_reg_count = 0x80 }; struct Namco_Osc { - blargg_long delay; + int delay; struct Blip_Buffer* output; short last_amp; short wave_pos; }; -struct Nes_Namco_Apu { +struct Nes_Namco_Apu { struct Namco_Osc oscs [namco_osc_count]; blip_time_t last_time; diff --git a/apps/codecs/libgme/nes_oscs.c b/apps/codecs/libgme/nes_oscs.c index 4402b60a61..f97ce2176d 100644 --- a/apps/codecs/libgme/nes_oscs.c +++ b/apps/codecs/libgme/nes_oscs.c @@ -90,7 +90,7 @@ static inline nes_time_t Square_maintain_phase( struct Nes_Square* this, nes_tim { int count = (remain + timer_period - 1) / timer_period; this->phase = (this->phase + count) & (square_phase_range - 1); - time += (blargg_long) count * timer_period; + time += count * timer_period; } return time; } @@ -107,8 +107,6 @@ void Square_run( struct Nes_Square* this, nes_time_t time, nes_time_t end_time ) return; } - Blip_set_modified( osc->output ); - int offset = period >> (osc->regs [1] & shift_mask); if ( osc->regs [1] & negate_flag ) offset = 0; @@ -117,6 +115,7 @@ void Square_run( struct Nes_Square* this, nes_time_t time, nes_time_t end_time ) if ( volume == 0 || period < 8 || (period + offset) >= 0x800 ) { if ( osc->last_amp ) { + Blip_set_modified( osc->output ); Synth_offset( this->synth, time, -osc->last_amp, osc->output ); osc->last_amp = 0; } @@ -137,6 +136,7 @@ void Square_run( struct Nes_Square* this, nes_time_t time, nes_time_t end_time ) if ( this->phase < duty ) amp ^= volume; + Blip_set_modified( osc->output ); { int delta = Osc_update_amp( osc, amp ); if ( delta ) @@ -201,7 +201,7 @@ static inline nes_time_t Triangle_maintain_phase( struct Nes_Triangle* this, nes int count = (remain + timer_period - 1) / timer_period; this->phase = ((unsigned) this->phase + 1 - count) & (Triangle_phase_range * 2 - 1); this->phase++; - time += (blargg_long) count * timer_period; + time += count * timer_period; } return time; } @@ -219,14 +219,15 @@ void Triangle_run( struct Nes_Triangle* this, nes_time_t time, nes_time_t end_ti return; } - Blip_set_modified( osc->output ); - // to do: track phase when period < 3 // to do: Output 7.5 on dac when period < 2? More accurate, but results in more clicks. int delta = Osc_update_amp( osc, Triangle_calc_amp( this ) ); if ( delta ) + { + Blip_set_modified( osc->output ); Synth_offset( &this->synth, time, delta, osc->output ); + } time += osc->delay; if ( osc->length_counter == 0 || this->linear_counter == 0 || timer_period < 3 ) @@ -243,13 +244,15 @@ void Triangle_run( struct Nes_Triangle* this, nes_time_t time, nes_time_t end_ti phase -= Triangle_phase_range; volume = -volume; } + Blip_set_modified( osc->output ); do { if ( --phase == 0 ) { phase = Triangle_phase_range; volume = -volume; } - else { + else + { Synth_offset_inline( &this->synth, time, volume, output ); } @@ -340,18 +343,27 @@ static inline void Dmc_reload_sample( struct Nes_Dmc* this ) this->osc.length_counter = this->osc.regs [3] * 0x10 + 1; } -static byte const dac_table [128] = +static int const dmc_table [128] = { - 0, 1, 2, 3, 4, 5, 6, 7, 7, 8, 9,10,11,12,13,14, - 15,15,16,17,18,19,20,20,21,22,23,24,24,25,26,27, - 27,28,29,30,31,31,32,33,33,34,35,36,36,37,38,38, - 39,40,41,41,42,43,43,44,45,45,46,47,47,48,48,49, - 50,50,51,52,52,53,53,54,55,55,56,56,57,58,58,59, - 59,60,60,61,61,62,63,63,64,64,65,65,66,66,67,67, - 68,68,69,70,70,71,71,72,72,73,73,74,74,75,75,75, - 76,76,77,77,78,78,79,79,80,80,81,81,82,82,82,83, + 0, 24, 48, 71, 94, 118, 141, 163, 186, 209, 231, 253, 275, 297, 319, 340, + 361, 383, 404, 425, 445, 466, 486, 507, 527, 547, 567, 587, 606, 626, 645, 664, + 683, 702, 721, 740, 758, 777, 795, 813, 832, 850, 867, 885, 903, 920, 938, 955, + 972, 989,1006,1023,1040,1056,1073,1089,1105,1122,1138,1154,1170,1185,1201,1217, +1232,1248,1263,1278,1293,1308,1323,1338,1353,1368,1382,1397,1411,1425,1440,1454, +1468,1482,1496,1510,1523,1537,1551,1564,1578,1591,1604,1618,1631,1644,1657,1670, +1683,1695,1708,1721,1733,1746,1758,1771,1783,1795,1807,1819,1831,1843,1855,1867, +1879,1890,1902,1914,1925,1937,1948,1959,1971,1982,1993,2004,2015,2026,2037,2048, }; +static inline int update_amp_nonlinear( struct Nes_Dmc* this, int in ) +{ + if ( !this->nonlinear ) + in = dmc_table [in]; + int delta = in - this->osc.last_amp; + this->osc.last_amp = in; + return delta; +} + void Dmc_write_register( struct Nes_Dmc* this, int addr, int data ) { if ( addr == 0 ) @@ -363,14 +375,7 @@ void Dmc_write_register( struct Nes_Dmc* this, int addr, int data ) } else if ( addr == 1 ) { - int old_dac = this->dac; this->dac = data & 0x7F; - - // adjust last_amp so that "pop" amplitude will be properly non-linear - // with respect to change in dac - int faked_nonlinear = this->dac - (dac_table [this->dac] - dac_table [old_dac]); - if ( !this->nonlinear ) - this->osc.last_amp = faked_nonlinear; } } @@ -407,16 +412,15 @@ void Dmc_fill_buffer( struct Nes_Dmc* this ) void Dmc_run( struct Nes_Dmc* this, nes_time_t time, nes_time_t end_time ) { struct Nes_Osc* osc = &this->osc; - int delta = Osc_update_amp( osc, this->dac ); + int delta = update_amp_nonlinear( this, this->dac ); if ( !osc->output ) { this->silence = true; } - else + else if ( delta ) { Blip_set_modified( osc->output ); - if ( delta ) - Synth_offset( &this->synth, time, delta, osc->output ); + Synth_offset( &this->synth, time, delta, osc->output ); } time += osc->delay; @@ -435,6 +439,8 @@ void Dmc_run( struct Nes_Dmc* this, nes_time_t time, nes_time_t end_time ) const int period = this->period; int bits = this->bits; int dac = this->dac; + if ( output ) + Blip_set_modified( output ); do { @@ -444,7 +450,7 @@ void Dmc_run( struct Nes_Dmc* this, nes_time_t time, nes_time_t end_time ) bits >>= 1; if ( (unsigned) (dac + step) <= 0x7F ) { dac += step; - Synth_offset_inline( &this->synth, time, step, output ); + Synth_offset_inline( &this->synth, time, update_amp_nonlinear( this, dac ), output ); } } @@ -456,7 +462,8 @@ void Dmc_run( struct Nes_Dmc* this, nes_time_t time, nes_time_t end_time ) if ( !this->buf_full ) { this->silence = true; } - else { + else + { this->silence = false; bits = this->buf; this->buf_full = false; @@ -469,7 +476,7 @@ void Dmc_run( struct Nes_Dmc* this, nes_time_t time, nes_time_t end_time ) while ( time < end_time ); this->dac = dac; - osc->last_amp = dac; + //osc->last_amp = dac; this->bits = bits; } this->bits_remain = bits_remain; @@ -519,14 +526,15 @@ void Noise_run( struct Nes_Noise* this, nes_time_t time, nes_time_t end_time ) return; } - Blip_set_modified( osc->output ); - const int volume = Noise_volume( this ); int amp = (this->noise & 1) ? volume : 0; { int delta = Osc_update_amp( osc, amp ); if ( delta ) + { + Blip_set_modified( osc->output ); Synth_offset( &this->synth, time, delta, osc->output ); + } } time += osc->delay; @@ -557,6 +565,7 @@ void Noise_run( struct Nes_Noise* this, nes_time_t time, nes_time_t end_time ) int noise = this->noise; int delta = amp * 2 - volume; const int tap = (osc->regs [2] & mode_flag ? 8 : 13); + Blip_set_modified( osc->output ); do { int feedback = (noise << tap) ^ (noise << 14); diff --git a/apps/codecs/libgme/nes_oscs.h b/apps/codecs/libgme/nes_oscs.h index fa6b8ab4b0..1eeb302e6c 100644 --- a/apps/codecs/libgme/nes_oscs.h +++ b/apps/codecs/libgme/nes_oscs.h @@ -46,7 +46,7 @@ enum { shift_mask = 0x07 }; enum { square_phase_range = 8 }; typedef struct Blip_Synth Synth; - + struct Nes_Square { struct Nes_Osc osc; @@ -54,12 +54,12 @@ struct Nes_Square int env_delay; int phase; int sweep_delay; - + Synth* synth; // shared between squares }; static inline void Square_set_synth( struct Nes_Square* this, Synth* s ) { this->synth = s; } - + void Square_clock_sweep( struct Nes_Square* this, int adjust ); void Square_run( struct Nes_Square* this, nes_time_t, nes_time_t ); @@ -73,15 +73,15 @@ static inline void Square_reset( struct Nes_Square* this ) void Square_clock_envelope( struct Nes_Square* this ); int Square_volume( struct Nes_Square* this ); - + // Nes_Triangle enum { Triangle_phase_range = 16 }; - + struct Nes_Triangle { struct Nes_Osc osc; - + int phase; int linear_counter; struct Blip_Synth synth; @@ -123,11 +123,11 @@ static inline void Noise_reset( struct Nes_Noise* this ) // Nes_Dmc enum { loop_flag = 0x40 }; - + struct Nes_Dmc { struct Nes_Osc osc; - + int address; // address of next byte to read int period; int buf; @@ -144,7 +144,7 @@ struct Nes_Dmc bool pal_mode; bool nonlinear; - int (*prg_reader)( void*, addr_t ); // needs to be initialized to prg read function + int (*prg_reader)( void*, int ); // needs to be initialized to prg read function void* prg_reader_data; struct Nes_Apu* apu; diff --git a/apps/codecs/libgme/nes_vrc6_apu.c b/apps/codecs/libgme/nes_vrc6_apu.c index f712216424..55fccb7b0f 100644 --- a/apps/codecs/libgme/nes_vrc6_apu.c +++ b/apps/codecs/libgme/nes_vrc6_apu.c @@ -83,7 +83,6 @@ void run_square( struct Nes_Vrc6_Apu* this, struct Vrc6_Osc* osc, blip_time_t en struct Blip_Buffer* output = osc->output; if ( !output ) return; - Blip_set_modified( output ); int volume = osc->regs [0] & 15; if ( !(osc->regs [2] & 0x80) ) @@ -96,6 +95,7 @@ void run_square( struct Nes_Vrc6_Apu* this, struct Vrc6_Osc* osc, blip_time_t en if ( delta ) { osc->last_amp += delta; + Blip_set_modified( output ); Synth_offset( &this->square_synth, time, delta, output ); } diff --git a/apps/codecs/libgme/nes_vrc6_apu.h b/apps/codecs/libgme/nes_vrc6_apu.h index 8f3cbf4629..57b8a42a79 100644 --- a/apps/codecs/libgme/nes_vrc6_apu.h +++ b/apps/codecs/libgme/nes_vrc6_apu.h @@ -1,6 +1,6 @@ // Konami VRC6 sound chip emulator -// Nes_Snd_Emu 0.1.8 +// Nes_Snd_Emu 0.2.0-pre #ifndef NES_VRC6_APU_H #define NES_VRC6_APU_H @@ -14,7 +14,7 @@ enum { vrc6_addr_step = 0x1000 }; struct Vrc6_Osc { - uint8_t regs [vrc6_reg_count]; + uint8_t regs [3]; struct Blip_Buffer* output; int delay; int last_amp; diff --git a/apps/codecs/libgme/nsf_emu.c b/apps/codecs/libgme/nsf_emu.c index a4873c81cb..705c345fa6 100644 --- a/apps/codecs/libgme/nsf_emu.c +++ b/apps/codecs/libgme/nsf_emu.c @@ -1,4 +1,4 @@ -// Game_Music_Emu 0.5.5. http://www.slack.net/~ant/ +// Game_Music_Emu 0.6-pre. http://www.slack.net/~ant/ #include "nsf_emu.h" #include "multi_buffer.h" @@ -19,33 +19,19 @@ Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "blargg_source.h" const char gme_wrong_file_type [] = "Wrong file type for this emulator"; -long const clock_divisor = 12; - -int const stereo = 2; // number of channels for stereo -int const silence_max = 6; // seconds -int const silence_threshold = 0x10; -long const fade_block_size = 512; -int const fade_shift = 8; // fade ends with gain at 1.0 / (1 << fade_shift) // number of frames until play interrupts init int const initial_play_delay = 7; // KikiKaikai needed this to work +int const bank_size = 0x1000; int const rom_addr = 0x8000; static void clear_track_vars( struct Nsf_Emu* this ) { this->current_track = -1; - this->out_time = 0; - this->emu_time = 0; - this->emu_track_ended_ = true; - this->track_ended = true; - this->fade_start = INT_MAX / 2 + 1; - this->fade_step = 1; - this->silence_time = 0; - this->silence_count = 0; - this->buf_remain = 0; + track_stop( &this->track_filter ); } -static int pcm_read( void* emu, addr_t addr ) +static int pcm_read( void* emu, int addr ) { return *Cpu_get_code( &((struct Nsf_Emu*) emu)->cpu, addr ); } @@ -58,18 +44,16 @@ void Nsf_init( struct Nsf_Emu* this ) this->gain = (int)(FP_ONE_GAIN); // defaults - this->max_initial_silence = 2; - this->ignore_silence = false; - this->voice_count = 0; + this->tfilter = *track_get_setup( &this->track_filter ); + this->tfilter.max_initial = 2; + this->tfilter.lookahead = 6; + this->track_filter.silence_ignored_ = false; // Set sound gain Sound_set_gain( this, (int)(FP_ONE_GAIN*1.2) ); - // Unload - clear_track_vars( this ); - // Init rom - Rom_init( &this->rom, 0x1000 ); + Rom_init( &this->rom, bank_size ); // Init & clear nsfe info Info_init( &this->info ); @@ -78,16 +62,36 @@ void Nsf_init( struct Nsf_Emu* this ) Cpu_init( &this->cpu ); Apu_init( &this->apu ); Apu_dmc_reader( &this->apu, pcm_read, this ); + + // Unload + this->voice_count = 0; + memset( this->voice_types, 0, sizeof this->voice_types ); + clear_track_vars( this ); } // Setup +static void append_voices( struct Nsf_Emu* this, int const types [], int count ) +{ + assert( this->voice_count + count < max_voices ); + int i; + for ( i = 0; i < count; i++ ) { + this->voice_types [this->voice_count + i] = types [i]; + } + this->voice_count += count; +} + static blargg_err_t init_sound( struct Nsf_Emu* this ) { - /* if ( header_.chip_flags & ~(fds_flag | namco_flag | vrc6_flag | fme7_flag) ) +/* if ( header_.chip_flags & ~(fds_flag | namco_flag | vrc6_flag | fme7_flag) ) warning( "Uses unsupported audio expansion hardware" ); **/ - this->voice_count = apu_osc_count; + { + static int const types [apu_osc_count] = { + wave_type+1, wave_type+2, mixed_type+1, noise_type+0, mixed_type+1 + }; + append_voices( this, types, apu_osc_count ); + } int adjusted_gain = (this->gain * 4) / 3; @@ -103,7 +107,10 @@ static blargg_err_t init_sound( struct Nsf_Emu* this ) Vrc6_init( &this->vrc6 ); adjusted_gain = (adjusted_gain*3) / 4; - this->voice_count += vrc6_osc_count; + static int const types [vrc6_osc_count] = { + wave_type+3, wave_type+4, wave_type+5, + }; + append_voices( this, types, vrc6_osc_count ); } if ( fme7_enabled( this ) ) @@ -111,7 +118,10 @@ static blargg_err_t init_sound( struct Nsf_Emu* this ) Fme7_init( &this->fme7 ); adjusted_gain = (adjusted_gain*3) / 4; - this->voice_count += fme7_osc_count; + static int const types [fme7_osc_count] = { + wave_type+3, wave_type+4, wave_type+5, + }; + append_voices( this, types, fme7_osc_count ); } if ( mmc5_enabled( this ) ) @@ -119,7 +129,11 @@ static blargg_err_t init_sound( struct Nsf_Emu* this ) Mmc5_init( &this->mmc5 ); adjusted_gain = (adjusted_gain*3) / 4; - this->voice_count += mmc5_osc_count; + + static int const types [mmc5_osc_count] = { + wave_type+3, wave_type+4, mixed_type+2 + }; + append_voices( this, types, mmc5_osc_count ); } if ( fds_enabled( this ) ) @@ -127,7 +141,10 @@ static blargg_err_t init_sound( struct Nsf_Emu* this ) Fds_init( &this->fds ); adjusted_gain = (adjusted_gain*3) / 4; - this->voice_count += fds_osc_count ; + static int const types [fds_osc_count] = { + wave_type+0 + }; + append_voices( this, types, fds_osc_count ); } if ( namco_enabled( this ) ) @@ -135,22 +152,31 @@ static blargg_err_t init_sound( struct Nsf_Emu* this ) Namco_init( &this->namco ); adjusted_gain = (adjusted_gain*3) / 4; - this->voice_count += namco_osc_count; + static int const types [namco_osc_count] = { + wave_type+3, wave_type+4, wave_type+5, wave_type+ 6, + wave_type+7, wave_type+8, wave_type+9, wave_type+10, + }; + append_voices( this, types, namco_osc_count ); } - if ( vrc7_enabled( this ) ) - { #ifndef NSF_EMU_NO_VRC7 + if ( vrc7_enabled( this ) ) + { + Vrc7_init( &this->vrc7 ); Vrc7_set_rate( &this->vrc7, this->sample_rate ); - #endif - adjusted_gain = (adjusted_gain*3) / 4; + adjusted_gain = (adjusted_gain*3) / 4; + + static int const types [vrc7_osc_count] = { + wave_type+3, wave_type+4, wave_type+5, wave_type+6, + wave_type+7, wave_type+8 + }; + append_voices( this, types, vrc7_osc_count ); + } - this->voice_count += vrc7_osc_count; - } - - if ( vrc7_enabled( this ) ) Vrc7_volume( &this->vrc7, adjusted_gain ); + if ( vrc7_enabled( this ) ) Vrc7_volume( &this->vrc7, adjusted_gain ); + #endif if ( namco_enabled( this ) ) Namco_volume( &this->namco, adjusted_gain ); if ( vrc6_enabled( this ) ) Vrc6_volume( &this->vrc6, adjusted_gain ); if ( fme7_enabled( this ) ) Fme7_volume( &this->fme7, adjusted_gain ); @@ -179,9 +205,9 @@ static bool pal_only( struct header_t* this ) return (this->speed_flags & 3) == 1; } -static long clock_rate( struct header_t* this ) +static int clock_rate( struct header_t* this ) { - return pal_only( this ) ? (long)1662607.125 : (long)1789772.727272727; + return pal_only( this ) ? (int)1662607.125 : (int)1789772.727272727; } static int play_period( struct header_t* this ) @@ -227,7 +253,7 @@ static blargg_err_t check_nsf_header( struct header_t* h ) return 0; } -blargg_err_t Nsf_load( struct Nsf_Emu* this, void* data, long size ) +blargg_err_t Nsf_load_mem( struct Nsf_Emu* this, void* data, long size ) { // Unload Info_unload( &this->info ); // TODO: extremely hacky! @@ -258,11 +284,10 @@ blargg_err_t Nsf_post_load( struct Nsf_Emu* this ) warning( "Unknown file version" ); */ // set up data - addr_t load_addr = get_le16( this->header.load_addr ); - + addr_t load_addr = get_addr( this->header.load_addr ); /* if ( load_addr < (fds_enabled() ? sram_addr : rom_addr) ) warning( "Load address is too low" ); */ - + Rom_set_addr( &this->rom, load_addr % this->rom.bank_size ); /* if ( header_.vers != 1 ) @@ -277,17 +302,16 @@ blargg_err_t Nsf_post_load( struct Nsf_Emu* this ) // Post load Sound_set_tempo( this, this->tempo ); - - // Remute voices Sound_mute_voices( this, this->mute_mask_ ); // Set track_count this->track_count = this->header.track_count; // Change clock rate & setup buffer - this->clock_rate__ = (long) clock_rate( &this->header ); + this->clock_rate__ = clock_rate( &this->header ); Buffer_clock_rate( &this->stereo_buf, this->clock_rate__ ); - this->buf_changed_count = Buffer_channels_changed_count( &this->stereo_buf ); + RETURN_ERR( Buffer_set_channel_count( &this->stereo_buf, this->voice_count, this->voice_types ) ); + this->buf_changed_count = Buffer_channels_changed_count( &this->stereo_buf ); return 0; } @@ -416,11 +440,13 @@ static void set_voice( struct Nsf_Emu* this, int i, struct Blip_Buffer* buf, str return; } - if ( vrc7_enabled( this ) && (i -= vrc7_osc_count) < 0 ) - { - Vrc7_set_output( &this->vrc7, i + vrc7_osc_count, buf ); - return; - } + #ifndef NSF_EMU_NO_VRC7 + if ( vrc7_enabled( this ) && (i -= vrc7_osc_count) < 0 ) + { + Vrc7_set_output( &this->vrc7, i + vrc7_osc_count, buf ); + return; + } + #endif } #endif } @@ -429,7 +455,7 @@ static void set_voice( struct Nsf_Emu* this, int i, struct Blip_Buffer* buf, str // Music Emu -blargg_err_t Nsf_set_sample_rate( struct Nsf_Emu* this, long rate ) +blargg_err_t Nsf_set_sample_rate( struct Nsf_Emu* this, int rate ) { require( !this->sample_rate ); // sample rate can't be changed once set Buffer_init( &this->stereo_buf ); @@ -439,6 +465,8 @@ blargg_err_t Nsf_set_sample_rate( struct Nsf_Emu* this, long rate ) Buffer_bass_freq( &this->stereo_buf, 80 ); this->sample_rate = rate; + RETURN_ERR( track_init( &this->track_filter, this ) ); + this->tfilter.max_silence = 6 * stereo * this->sample_rate; return 0; } @@ -466,7 +494,7 @@ void Sound_mute_voices( struct Nsf_Emu* this, int mask ) } else { - struct channel_t ch = Buffer_channel( &this->stereo_buf ); + struct channel_t ch = Buffer_channel( &this->stereo_buf, i ); assert( (ch.center && ch.left && ch.right) || (!ch.center && !ch.left && !ch.right) ); // all or nothing set_voice( this, i, ch.center, ch.left, ch.right ); @@ -533,32 +561,13 @@ int cpu_read( struct Nsf_Emu* this, addr_t addr ) return addr >> 8; } -#if 0 /* function currently unused */ -static int unmapped_read( struct Nsf_Emu* this, addr_t addr ) -{ - (void) this; - - switch ( addr ) - { - case 0x2002: - case 0x4016: - case 0x4017: - return addr >> 8; - } - - // Unmapped read - return addr >> 8; -} -#endif - void cpu_write( struct Nsf_Emu* this, addr_t addr, int data ) { #ifndef NSF_EMU_APU_ONLY { - nes_time_t time = Cpu_time( &this->cpu ); if ( fds_enabled( this) && (unsigned) (addr - fds_io_addr) < fds_io_size ) { - Fds_write( &this->fds, time, addr, data ); + Fds_write( &this->fds, Cpu_time( &this->cpu ), addr, data ); return; } @@ -572,7 +581,7 @@ void cpu_write( struct Nsf_Emu* this, addr_t addr, int data ) if ( addr == namco_data_reg_addr ) { - Namco_write_data( &this->namco, time, data ); + Namco_write_data( &this->namco, Cpu_time( &this->cpu ), data ); return; } } @@ -583,7 +592,7 @@ void cpu_write( struct Nsf_Emu* this, addr_t addr, int data ) int osc = (unsigned) (addr - vrc6_base_addr) / vrc6_addr_step; if ( (unsigned) osc < vrc6_osc_count && (unsigned) reg < vrc6_reg_count ) { - Vrc6_write_osc( &this->vrc6, time, osc, reg, data ); + Vrc6_write_osc( &this->vrc6, Cpu_time( &this->cpu ), osc, reg, data ); return; } } @@ -597,7 +606,7 @@ void cpu_write( struct Nsf_Emu* this, addr_t addr, int data ) return; case fme7_data_addr: - Fme7_write_data( &this->fme7, time, data ); + Fme7_write_data( &this->fme7, Cpu_time( &this->cpu ), data ); return; } } @@ -606,7 +615,7 @@ void cpu_write( struct Nsf_Emu* this, addr_t addr, int data ) { if ( (unsigned) (addr - mmc5_regs_addr) < mmc5_regs_size ) { - Mmc5_write_register( &this->mmc5, time, addr, data ); + Mmc5_write_register( &this->mmc5, Cpu_time( &this->cpu ), addr, data ); return; } @@ -625,49 +634,28 @@ void cpu_write( struct Nsf_Emu* this, addr_t addr, int data ) } } - if ( vrc7_enabled( this) ) - { - if ( addr == 0x9010 ) + #ifndef NSF_EMU_NO_VRC7 + if ( vrc7_enabled( this) ) { - Vrc7_write_reg( &this->vrc7, data ); - return; - } + if ( addr == 0x9010 ) + { + Vrc7_write_reg( &this->vrc7, data ); + return; + } - if ( (unsigned) (addr - 0x9028) <= 0x08 ) - { - Vrc7_write_data( &this->vrc7, time, data ); - return; + if ( (unsigned) (addr - 0x9028) <= 0x08 ) + { + Vrc7_write_data( &this->vrc7, Cpu_time( &this->cpu ), data ); + return; + } } - } + #endif } #endif // Unmapped_write } -#if 0 /* function currently unused */ -static void unmapped_write( struct Nsf_Emu* this, addr_t addr, int data ) -{ - (void) data; - - switch ( addr ) - { - case 0x8000: // some write to $8000 and $8001 repeatedly - case 0x8001: - case 0x4800: // probably namco sound mistakenly turned on in MCK - case 0xF800: - case 0xFFF8: // memory mapper? - return; - } - - if ( mmc5_enabled( this ) && addr == 0x5115 ) return; - - // FDS memory - if ( fds_enabled( this ) && (unsigned) (addr - 0x8000) < 0x6000 ) return; -} -#endif - -void fill_buf( struct Nsf_Emu* this ); blargg_err_t Nsf_start_track( struct Nsf_Emu* this, int track ) { clear_track_vars( this ); @@ -695,7 +683,9 @@ blargg_err_t Nsf_start_track( struct Nsf_Emu* this, int track ) if ( vrc6_enabled( this ) ) Vrc6_reset( &this->vrc6 ); if ( fme7_enabled( this ) ) Fme7_reset( &this->fme7 ); if ( mmc5_enabled( this ) ) Apu_reset( &this->mmc5.apu, false, 0 ); - if ( vrc7_enabled( this ) ) Vrc7_reset( &this->vrc7 ); + #ifndef NSF_EMU_NO_VRC7 + if ( vrc7_enabled( this ) ) Vrc7_reset( &this->vrc7 ); + #endif #endif int speed_flags = 0; @@ -728,27 +718,15 @@ blargg_err_t Nsf_start_track( struct Nsf_Emu* this, int track ) /* if ( this->cpu.r.pc < get_addr( header.load_addr ) ) warning( "Init address < load address" ); */ - this->emu_track_ended_ = false; - this->track_ended = false; + // convert filter times to samples + struct setup_t s = this->tfilter; + s.max_initial *= this->sample_rate * stereo; + #ifdef GME_DISABLE_SILENCE_LOOKAHEAD + s.lookahead = 1; + #endif + track_setup( &this->track_filter, &s ); - if ( !this->ignore_silence ) - { - // play until non-silence or end of track - long end; - for ( end = this->max_initial_silence * stereo * this->sample_rate; this->emu_time < end; ) - { - fill_buf( this ); - if ( this->buf_remain | (int) this->emu_track_ended_ ) - break; - } - - this->emu_time = this->buf_remain; - this->out_time = 0; - this->silence_time = 0; - this->silence_count = 0; - } - /* return track_ended() ? warning() : 0; */ - return 0; + return track_start( &this->track_filter ); } void run_once( struct Nsf_Emu* this, nes_time_t end ) @@ -831,265 +809,99 @@ static void end_frame( struct Nsf_Emu* this, nes_time_t end ) if ( mmc5_enabled( this ) ) Apu_end_frame( &this->mmc5.apu, end ); if ( namco_enabled( this ) ) Namco_end_frame( &this->namco, end ); if ( vrc6_enabled( this ) ) Vrc6_end_frame( &this->vrc6, end ); + #ifndef NSF_EMU_NO_VRC7 if ( vrc7_enabled( this ) ) Vrc7_end_frame( &this->vrc7, end ); + #endif #endif } // Tell/Seek -static blargg_long msec_to_samples( long sample_rate, blargg_long msec ) +static int msec_to_samples( int msec, int sample_rate ) { - blargg_long sec = msec / 1000; + int sec = msec / 1000; msec -= sec * 1000; return (sec * sample_rate + msec * sample_rate / 1000) * stereo; } -long Track_tell( struct Nsf_Emu* this ) +int Track_tell( struct Nsf_Emu* this ) { - blargg_long rate = this->sample_rate * stereo; - blargg_long sec = this->out_time / rate; - return sec * 1000 + (this->out_time - sec * rate) * 1000 / rate; + int rate = this->sample_rate * stereo; + int sec = track_sample_count( &this->track_filter ) / rate; + return sec * 1000 + (track_sample_count( &this->track_filter ) - sec * rate) * 1000 / rate; } -blargg_err_t Track_seek( struct Nsf_Emu* this, long msec ) +blargg_err_t Track_seek( struct Nsf_Emu* this, int msec ) { - blargg_long time = msec_to_samples( this->sample_rate, msec ); - if ( time < this->out_time ) - RETURN_ERR( Nsf_start_track( this, this->current_track ) ); - return Track_skip( this, time - this->out_time ); + int time = msec_to_samples( msec, this->sample_rate ); + if ( time < track_sample_count( &this->track_filter ) ) + RETURN_ERR( Nsf_start_track( this, this->current_track ) ); + return Track_skip( this, time - track_sample_count( &this->track_filter ) ); } -blargg_err_t skip_( struct Nsf_Emu* this, long count ); -blargg_err_t Track_skip( struct Nsf_Emu* this, long count ) +blargg_err_t Track_skip( struct Nsf_Emu* this, int count ) { require( this->current_track >= 0 ); // start_track() must have been called already - this->out_time += count; - - // remove from silence and buf first - { - long n = min( count, this->silence_count ); - this->silence_count -= n; - count -= n; - - n = min( count, this->buf_remain ); - this->buf_remain -= n; - count -= n; - } - - if ( count && !this->emu_track_ended_ ) - { - this->emu_time += count; - // End track if error - if ( skip_( this, count ) ) - this->emu_track_ended_ = true; - } - - if ( !(this->silence_count | this->buf_remain) ) // caught up to emulator, so update track ended - this->track_ended |= this->emu_track_ended_; - - return 0; + return track_skip( &this->track_filter, count ); } -blargg_err_t play_( struct Nsf_Emu* this, long count, sample_t* out ); -blargg_err_t skip_( struct Nsf_Emu* this, long count ) +blargg_err_t skip_( void *emu, int count ) { + struct Nsf_Emu* this = (struct Nsf_Emu*) emu; + // for long skip, mute sound - const long threshold = 30000; + const int threshold = 32768; if ( count > threshold ) { int saved_mute = this->mute_mask_; Sound_mute_voices( this, ~0 ); - - while ( count > threshold / 2 && !this->emu_track_ended_ ) - { - RETURN_ERR( play_( this, buf_size, this->buf ) ); - count -= buf_size; - } - - Sound_mute_voices( this, saved_mute ); - } - - while ( count && !this->emu_track_ended_ ) - { - long n = buf_size; - if ( n > count ) - n = count; - count -= n; - RETURN_ERR( play_( this, n, this->buf ) ); - } - return 0; -} - -// Fading -void Track_set_fade( struct Nsf_Emu* this, long start_msec, long length_msec ) -{ - this->fade_step = this->sample_rate * length_msec / (fade_block_size * fade_shift * 1000 / stereo); - this->fade_start = msec_to_samples( this->sample_rate, start_msec ); -} - -// unit / pow( 2.0, (double) x / step ) -static int int_log( blargg_long x, int step, int unit ) -{ - int shift = x / step; - int fraction = (x - shift * step) * unit / step; - return ((unit - fraction) + (fraction >> 1)) >> shift; -} + int n = count - threshold/2; + n &= ~(2048-1); // round to multiple of 2048 + count -= n; + RETURN_ERR( skippy_( &this->track_filter, n ) ); -static void handle_fade( struct Nsf_Emu* this, long out_count, sample_t* out ) -{ - int i; - for ( i = 0; i < out_count; i += fade_block_size ) - { - int const shift = 14; - int const unit = 1 << shift; - int gain = int_log( (this->out_time + i - this->fade_start) / fade_block_size, - this->fade_step, unit ); - if ( gain < (unit >> fade_shift) ) - this->track_ended = this->emu_track_ended_ = true; - - sample_t* io = &out [i]; - int count; - for ( count = min( fade_block_size, out_count - i ); count; --count ) - { - *io = (sample_t) ((*io * gain) >> shift); - ++io; - } + Sound_mute_voices( this, saved_mute ); } -} - -// Silence detection -void emu_play( struct Nsf_Emu* this, long count, sample_t* out ); -void emu_play( struct Nsf_Emu* this, long count, sample_t* out ) -{ - check( current_track_ >= 0 ); - this->emu_time += count; - if ( this->current_track >= 0 && !this->emu_track_ended_ ) { - - // End track if error - if ( play_( this, count, out ) ) - this->emu_track_ended_ = true; - } - else - memset( out, 0, count * sizeof *out ); + return skippy_( &this->track_filter, count ); } -// number of consecutive silent samples at end -static long count_silence( sample_t* begin, long size ) -{ - sample_t first = *begin; - *begin = silence_threshold; // sentinel - sample_t* p = begin + size; - while ( (unsigned) (*--p + silence_threshold / 2) <= (unsigned) silence_threshold ) { } - *begin = first; - return size - (p - begin); -} +// Fading -// fill internal buffer and check it for silence -void fill_buf( struct Nsf_Emu* this ) +void Track_set_fade( struct Nsf_Emu* this, int start_msec, int length_msec ) { - assert( !this->buf_remain ); - if ( !this->emu_track_ended_ ) - { - emu_play( this, buf_size, this->buf ); - long silence = count_silence( this->buf, buf_size ); - if ( silence < buf_size ) - { - this->silence_time = this->emu_time - silence; - this->buf_remain = buf_size; - return; - } - } - this->silence_count += buf_size; + track_set_fade( &this->track_filter, msec_to_samples( start_msec, this->sample_rate ), + length_msec * this->sample_rate / (1000 / stereo) ); } -blargg_err_t Nsf_play( struct Nsf_Emu* this, long out_count, sample_t* out ) +blargg_err_t Nsf_play( struct Nsf_Emu* this, int out_count, sample_t* out ) { - if ( this->track_ended ) - { - memset( out, 0, out_count * sizeof *out ); - } - else - { - require( this->current_track >= 0 ); - require( out_count % stereo == 0 ); - - assert( this->emu_time >= this->out_time ); - - long pos = 0; - if ( this->silence_count ) - { - // during a run of silence, run emulator at >=2x speed so it gets ahead - long ahead_time = this->silence_lookahead * (this->out_time + out_count - this->silence_time) + this->silence_time; - while ( this->emu_time < ahead_time && !(this->buf_remain | this->emu_track_ended_) ) - fill_buf( this ); - - // fill with silence - pos = min( this->silence_count, out_count ); - memset( out, 0, pos * sizeof *out ); - this->silence_count -= pos; - - if ( this->emu_time - this->silence_time > silence_max * stereo * this->sample_rate ) - { - this->track_ended = this->emu_track_ended_ = true; - this->silence_count = 0; - this->buf_remain = 0; - } - } - - if ( this->buf_remain ) - { - // empty silence buf - long n = min( this->buf_remain, out_count - pos ); - memcpy( &out [pos], this->buf + (buf_size - this->buf_remain), n * sizeof *out ); - this->buf_remain -= n; - pos += n; - } - - // generate remaining samples normally - long remain = out_count - pos; - if ( remain ) - { - emu_play( this, remain, out + pos ); - this->track_ended |= this->emu_track_ended_; - - if ( !this->ignore_silence || this->out_time > this->fade_start ) - { - // check end for a new run of silence - long silence = count_silence( out + pos, remain ); - if ( silence < remain ) - this->silence_time = this->emu_time - silence; - - if ( this->emu_time - this->silence_time >= buf_size ) - fill_buf( this ); // cause silence detection on next play() - } - } - - if ( this->out_time > this->fade_start ) - handle_fade( this, out_count, out ); - } - this->out_time += out_count; - return 0; + require( this->current_track >= 0 ); + require( out_count % stereo == 0 ); + return track_play( &this->track_filter, out_count, out ); } -blargg_err_t play_( struct Nsf_Emu* this, long count, sample_t* out ) +blargg_err_t play_( void* emu, int count, sample_t* out ) { - long remain = count; + struct Nsf_Emu* this = (struct Nsf_Emu*) emu; + + int remain = count; while ( remain ) { + Buffer_disable_immediate_removal( &this->stereo_buf ); remain -= Buffer_read_samples( &this->stereo_buf, &out [count - remain], remain ); if ( remain ) { if ( this->buf_changed_count != Buffer_channels_changed_count( &this->stereo_buf ) ) { this->buf_changed_count = Buffer_channels_changed_count( &this->stereo_buf ); - + // Remute voices Sound_mute_voices( this, this->mute_mask_ ); } int msec = Buffer_length( &this->stereo_buf ); - blip_time_t clocks_emulated = (blargg_long) msec * this->clock_rate__ / 1000 - 100; + blip_time_t clocks_emulated = msec * this->clock_rate__ / 1000 - 100; RETURN_ERR( run_clocks( this, &clocks_emulated, msec ) ); assert( clocks_emulated ); Buffer_end_frame( &this->stereo_buf, clocks_emulated ); diff --git a/apps/codecs/libgme/nsf_emu.h b/apps/codecs/libgme/nsf_emu.h index dccfa8c5d5..00bdad4a4e 100644 --- a/apps/codecs/libgme/nsf_emu.h +++ b/apps/codecs/libgme/nsf_emu.h @@ -10,6 +10,7 @@ #include "nes_cpu.h" #include "nsfe_info.h" #include "m3u_playlist.h" +#include "track_filter.h" #ifndef NSF_EMU_APU_ONLY #include "nes_namco_apu.h" @@ -17,11 +18,11 @@ #include "nes_fme7_apu.h" #include "nes_fds_apu.h" #include "nes_mmc5_apu.h" - #include "nes_vrc7_apu.h" + #ifndef NSF_EMU_NO_VRC7 + #include "nes_vrc7_apu.h" + #endif #endif -typedef short sample_t; - // Sound chip flags enum { vrc6_flag = 1 << 0, @@ -50,8 +51,7 @@ enum { fdsram_size = 0x6000 }; enum { fdsram_offset = 0x2000 + page_size + 8 }; enum { sram_addr = 0x6000 }; enum { unmapped_size= page_size + 8 }; - -enum { buf_size = 2048 }; +enum { max_voices = 32 }; // NSF file header enum { header_size = 0x80 }; @@ -82,37 +82,21 @@ struct Nsf_Emu { int play_extra; int play_delay; struct registers_t saved_state; // of interrupted init routine - - int track_count; // general - int max_initial_silence; int voice_count; + int voice_types [32]; int mute_mask_; int tempo; int gain; - long sample_rate; + int sample_rate; // track-specific + int track_count; int current_track; - blargg_long out_time; // number of samples played since start of track - blargg_long emu_time; // number of samples emulator has generated since start of track - bool emu_track_ended_; // emulator has reached end of track - volatile bool track_ended; - - // fading - blargg_long fade_start; - int fade_step; - // silence detection - int silence_lookahead; // speed to run emulator when looking ahead for silence - bool ignore_silence; - long silence_time; // number of samples where most recent silence began - long silence_count; // number of samples of silence to play before using buf - long buf_remain; // number of samples left in silence buffer - - long clock_rate__; + int clock_rate__; unsigned buf_changed_count; // M3u Playlist @@ -127,7 +111,9 @@ struct Nsf_Emu { struct Nes_Namco_Apu namco; struct Nes_Vrc6_Apu vrc6; struct Nes_Fme7_Apu fme7; - struct Nes_Vrc7_Apu vrc7; + #ifndef NSF_EMU_NO_VRC7 + struct Nes_Vrc7_Apu vrc7; + #endif #endif struct Nes_Cpu cpu; @@ -136,13 +122,15 @@ struct Nsf_Emu { // Header for currently loaded file struct header_t header; - struct Stereo_Buffer stereo_buf; + struct setup_t tfilter; + struct Track_Filter track_filter; + + struct Multi_Buffer stereo_buf; struct Rom_Data rom; // Extended nsf info struct Nsfe_Info info; - sample_t buf [buf_size]; byte high_ram[fdsram_size + fdsram_offset]; byte low_ram [low_ram_size]; }; @@ -150,18 +138,18 @@ struct Nsf_Emu { // Basic functionality (see Gme_File.h for file loading/track info functions) void Nsf_init( struct Nsf_Emu* this ); -blargg_err_t Nsf_load( struct Nsf_Emu* this, void* data, long size ); +blargg_err_t Nsf_load_mem( struct Nsf_Emu* this, void* data, long size ); blargg_err_t Nsf_post_load( struct Nsf_Emu* this ); // Set output sample rate. Must be called only once before loading file. -blargg_err_t Nsf_set_sample_rate( struct Nsf_Emu* this, long sample_rate ); - +blargg_err_t Nsf_set_sample_rate( struct Nsf_Emu* this, int sample_rate ); + // Start a track, where 0 is the first track. Also clears warning string. blargg_err_t Nsf_start_track( struct Nsf_Emu* this , int ); - + // Generate 'count' samples info 'buf'. Output is in stereo. Any emulation // errors set warning string, and major errors also end track. -blargg_err_t Nsf_play( struct Nsf_Emu* this, long count, sample_t* buf ); +blargg_err_t Nsf_play( struct Nsf_Emu* this, int count, sample_t* buf ); void Nsf_clear_playlist( struct Nsf_Emu* this ); void Nsf_disable_playlist( struct Nsf_Emu* this, bool b ); // use clear_playlist() @@ -169,20 +157,32 @@ void Nsf_disable_playlist( struct Nsf_Emu* this, bool b ); // use clear_playlist // Track status/control // Number of milliseconds (1000 msec = 1 second) played since beginning of track -long Track_tell( struct Nsf_Emu* this ); +int Track_tell( struct Nsf_Emu* this ); // Seek to new time in track. Seeking backwards or far forward can take a while. -blargg_err_t Track_seek( struct Nsf_Emu* this, long msec ); +blargg_err_t Track_seek( struct Nsf_Emu* this, int msec ); // Skip n samples -blargg_err_t Track_skip( struct Nsf_Emu* this, long n ); +blargg_err_t Track_skip( struct Nsf_Emu* this, int n ); // Set start time and length of track fade out. Once fade ends track_ended() returns // true. Fade time can be changed while track is playing. -void Track_set_fade( struct Nsf_Emu* this, long start_msec, long length_msec ); +void Track_set_fade( struct Nsf_Emu* this, int start_msec, int length_msec ); + +// True if a track has reached its end +static inline bool Track_ended( struct Nsf_Emu* this ) +{ + return track_ended( &this->track_filter ); +} + +// Disables automatic end-of-track detection and skipping of silence at beginning +static inline void Track_ignore_silence( struct Nsf_Emu* this, bool disable ) +{ + this->track_filter.silence_ignored_ = disable; +} // Get track length in milliseconds -long Track_length( struct Nsf_Emu* this, int n ); +int Track_length( struct Nsf_Emu* this, int n ); // Sound customization @@ -217,28 +217,28 @@ bool run_cpu_until( struct Nsf_Emu* this, nes_time_t end ); // Sets clocks between calls to play routine to p + 1/2 clock static inline void set_play_period( struct Nsf_Emu* this, int p ) { this->play_period = p; } - + // Time play routine will next be called static inline nes_time_t play_time( struct Nsf_Emu* this ) { return this->next_play; } - + // Emulates to at least time t. Might emulate a few clocks extra. void run_until( struct Nsf_Emu* this, nes_time_t t ); - + // Runs cpu to at least time t and returns false, or returns true // if it encounters illegal instruction (halt). bool run_cpu_until( struct Nsf_Emu* this, nes_time_t t ); - + // cpu calls through to these to access memory (except instructions) int read_mem( struct Nsf_Emu* this, addr_t ); void write_mem( struct Nsf_Emu* this, addr_t, int ); - + // Address of play routine static inline addr_t play_addr( struct Nsf_Emu* this ) { return get_addr( this->header.play_addr ); } - + // Same as run_until, except emulation stops for any event (routine returned, // play routine called, illegal instruction). void run_once( struct Nsf_Emu* this, nes_time_t ); - + // Reads byte as cpu would when executing code. Only works for RAM/ROM, // NOT I/O like sound chips. int read_code( struct Nsf_Emu* this, addr_t addr ); @@ -248,12 +248,14 @@ static inline byte* sram( struct Nsf_Emu* this ) { return this->high_ static inline byte* unmapped_code( struct Nsf_Emu* this ) { return &this->high_ram [sram_size]; } #ifndef NSF_EMU_APU_ONLY -static inline int fds_enabled( struct Nsf_Emu* this ) { return this->header.chip_flags & fds_flag; } -static inline int vrc6_enabled( struct Nsf_Emu* this ) { return this->header.chip_flags & vrc6_flag; } -static inline int vrc7_enabled( struct Nsf_Emu* this ) { return this->header.chip_flags & vrc7_flag; } -static inline int mmc5_enabled( struct Nsf_Emu* this ) { return this->header.chip_flags & mmc5_flag; } -static inline int namco_enabled( struct Nsf_Emu* this ) { return this->header.chip_flags & namco_flag; } -static inline int fme7_enabled( struct Nsf_Emu* this ) { return this->header.chip_flags & fme7_flag; } + static inline int fds_enabled( struct Nsf_Emu* this ) { return this->header.chip_flags & fds_flag; } + static inline int vrc6_enabled( struct Nsf_Emu* this ) { return this->header.chip_flags & vrc6_flag; } + #ifndef NSF_EMU_NO_VRC7 + static inline int vrc7_enabled( struct Nsf_Emu* this ) { return this->header.chip_flags & vrc7_flag; } + #endif + static inline int mmc5_enabled( struct Nsf_Emu* this ) { return this->header.chip_flags & mmc5_flag; } + static inline int namco_enabled( struct Nsf_Emu* this ) { return this->header.chip_flags & namco_flag; } + static inline int fme7_enabled( struct Nsf_Emu* this ) { return this->header.chip_flags & fme7_flag; } #endif #endif diff --git a/apps/codecs/libgme/nsfe_info.c b/apps/codecs/libgme/nsfe_info.c index d22b763173..337b1e580a 100644 --- a/apps/codecs/libgme/nsfe_info.c +++ b/apps/codecs/libgme/nsfe_info.c @@ -154,8 +154,8 @@ blargg_err_t Info_load( struct Nsfe_Info* this, void* data, long size, struct Ns byte block_header [2] [4]; RETURN_ERR( in_read( block_header, sizeof block_header, data, &offset, size ) ); - blargg_long chunk_size = get_le32( block_header [0] ); - blargg_long tag = get_le32( block_header [1] ); + int chunk_size = get_le32( block_header [0] ); + int tag = get_le32( block_header [1] ); switch ( tag ) { @@ -168,7 +168,7 @@ blargg_err_t Info_load( struct Nsfe_Info* this, void* data, long size, struct Ns finfo.track_count = 1; finfo.first_track = 0; - RETURN_ERR( in_read( &finfo, min( chunk_size, (blargg_long) nsfe_info_size ), + RETURN_ERR( in_read( &finfo, min( chunk_size, nsfe_info_size ), (char*) data, &offset, size ) ); if ( chunk_size > nsfe_info_size ) @@ -248,9 +248,9 @@ blargg_err_t Info_load( struct Nsfe_Info* this, void* data, long size, struct Ns return 0; } -long Track_length( struct Nsf_Emu* this, int n ) +int Track_length( struct Nsf_Emu* this, int n ) { - long length = 0; + int length = 0; if ( (this->m3u.size > 0) && (n < this->m3u.size) ) { struct entry_t* entry = &this->m3u.entries [n]; length = entry->length; diff --git a/apps/codecs/libgme/opl_apu.h b/apps/codecs/libgme/opl_apu.h index 5ea8185f5f..f24a8d60c2 100644 --- a/apps/codecs/libgme/opl_apu.h +++ b/apps/codecs/libgme/opl_apu.h @@ -37,10 +37,10 @@ struct Opl_Apu { blargg_err_t Opl_init( struct Opl_Apu* this, long clock, long rate, blip_time_t period, enum opl_type_t type ); void Opl_shutdown( struct Opl_Apu* this ); - + void Opl_reset( struct Opl_Apu* this ); static inline void Opl_volume( struct Opl_Apu* this, int v ) { Synth_volume( &this->synth, v / (4096 * 6) ); } - + static inline void Opl_osc_output( struct Opl_Apu* this, int i, struct Blip_Buffer* buf ) { #if defined(ROCKBOX) diff --git a/apps/codecs/libgme/resampler.c b/apps/codecs/libgme/resampler.c index 837f01ed13..91677cbb8a 100644 --- a/apps/codecs/libgme/resampler.c +++ b/apps/codecs/libgme/resampler.c @@ -64,7 +64,7 @@ void Resampler_resize( struct Resampler* this, int pairs ) } } -void mix_samples( struct Resampler* this, struct Blip_Buffer* blip_buf, sample_t out_ [] ) +void mix_samples( struct Resampler* this, struct Blip_Buffer* blip_buf, dsample_t out_ [] ) { int const bass = BLIP_READER_BASS( *blip_buf ); BLIP_READER_BEGIN( sn, *blip_buf ); @@ -72,7 +72,7 @@ void mix_samples( struct Resampler* this, struct Blip_Buffer* blip_buf, sample_t int count = this->sample_buf_size >> 1; BLIP_READER_ADJ_( sn, count ); - typedef sample_t stereo_dsample_t [2]; + typedef dsample_t stereo_dsample_t [2]; stereo_dsample_t* BLARGG_RESTRICT out = (stereo_dsample_t*) out_ + count; stereo_dsample_t const* BLARGG_RESTRICT in = (stereo_dsample_t const*) this->sample_buf + count; @@ -97,14 +97,14 @@ void mix_samples( struct Resampler* this, struct Blip_Buffer* blip_buf, sample_t BLIP_READER_END( sn, *blip_buf ); } -sample_t const* resample_( struct Resampler* this, sample_t** out_, - sample_t const* out_end, sample_t const in [], int in_size ) +dsample_t const* resample_( struct Resampler* this, dsample_t** out_, + dsample_t const* out_end, dsample_t const in [], int in_size ) { in_size -= write_offset; if ( in_size > 0 ) { - sample_t* BLARGG_RESTRICT out = *out_; - sample_t const* const in_end = in + in_size; + dsample_t* BLARGG_RESTRICT out = *out_; + dsample_t const* const in_end = in + in_size; int const step = this->step; int pos = this->pos; @@ -135,12 +135,12 @@ sample_t const* resample_( struct Resampler* this, sample_t** out_, return in; } -static inline int resample_wrapper( struct Resampler* this, sample_t out [], int* out_size, - sample_t const in [], int in_size ) +static inline int resample_wrapper( struct Resampler* this, dsample_t out [], int* out_size, + dsample_t const in [], int in_size ) { assert( Resampler_rate( this ) ); - sample_t* out_ = out; + dsample_t* out_ = out; int result = resample_( this, &out_, out + *out_size, in, in_size ) - in; assert( out_ <= out + *out_size ); assert( result <= in_size ); @@ -161,7 +161,7 @@ int skip_input( struct Resampler* this, int count ) return count; } -void play_frame_( struct Resampler* this, struct Blip_Buffer* blip_buf, sample_t* out ) +void play_frame_( struct Resampler* this, struct Blip_Buffer* blip_buf, dsample_t* out ) { int pair_count = this->sample_buf_size >> 1; blip_time_t blip_time = Blip_count_clocks( blip_buf, pair_count ); @@ -185,7 +185,7 @@ void play_frame_( struct Resampler* this, struct Blip_Buffer* blip_buf, sample_t Blip_remove_samples( blip_buf, pair_count ); } -void Resampler_play( struct Resampler* this, int count, sample_t* out, struct Blip_Buffer* blip_buf ) +void Resampler_play( struct Resampler* this, int count, dsample_t* out, struct Blip_Buffer* blip_buf ) { // empty extra buffer int remain = this->sample_buf_size - this->buf_pos; diff --git a/apps/codecs/libgme/resampler.h b/apps/codecs/libgme/resampler.h index 1d8f86670b..3f3710a54a 100644 --- a/apps/codecs/libgme/resampler.h +++ b/apps/codecs/libgme/resampler.h @@ -7,16 +7,15 @@ #include "blargg_common.h" #include "multi_buffer.h" -typedef short sample_t; +typedef short dsample_t; -enum { stereo = 2 }; enum { max_buf_size = 3960 }; enum { max_resampler_size = 5942 }; enum { write_offset = 8 * stereo }; enum { gain_bits = 14 }; struct Resampler { - int (*callback)( void*, blip_time_t, int, sample_t* ); + int (*callback)( void*, blip_time_t, int, dsample_t* ); void* callback_data; int sample_buffer_size; @@ -34,8 +33,8 @@ struct Resampler { int rate_; - sample_t sample_buf [max_buf_size]; - sample_t buf [max_resampler_size]; // Internal resampler + dsample_t sample_buf [max_buf_size]; + dsample_t buf [max_resampler_size]; // Internal resampler }; static inline void Resampler_init( struct Resampler* this ) @@ -50,9 +49,9 @@ static inline void Resampler_init( struct Resampler* this ) blargg_err_t Resampler_reset( struct Resampler* this, int max_pairs ); void Resampler_resize( struct Resampler* this, int pairs_per_frame ); -void Resampler_play( struct Resampler* this, int count, sample_t* out, struct Blip_Buffer* ); +void Resampler_play( struct Resampler* this, int count, dsample_t* out, struct Blip_Buffer* ); -static inline void Resampler_set_callback(struct Resampler* this, int (*func)( void*, blip_time_t, int, sample_t* ), void* user_data ) +static inline void Resampler_set_callback(struct Resampler* this, int (*func)( void*, blip_time_t, int, dsample_t* ), void* user_data ) { this->callback = func; this->callback_data = user_data; diff --git a/apps/codecs/libgme/rom_data.c b/apps/codecs/libgme/rom_data.c index 5fe3115130..9c36a99d2d 100644 --- a/apps/codecs/libgme/rom_data.c +++ b/apps/codecs/libgme/rom_data.c @@ -21,7 +21,7 @@ Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ blargg_err_t Rom_load( struct Rom_Data* this, const void* data, long size, int header_size, void* header_out, int fill ) { - long file_offset = this->pad_size; + int file_offset = this->pad_size; this->rom_addr = 0; this->mask = 0; @@ -43,11 +43,11 @@ blargg_err_t Rom_load( struct Rom_Data* this, const void* data, long size, return 0; } -void Rom_set_addr( struct Rom_Data* this, long addr ) +void Rom_set_addr( struct Rom_Data* this, int addr ) { this->rom_addr = addr - this->bank_size - pad_extra; - long rounded = (addr + this->file_size + this->bank_size - 1) / this->bank_size * this->bank_size; + int rounded = (addr + this->file_size + this->bank_size - 1) / this->bank_size * this->bank_size; if ( rounded <= 0 ) { rounded = 0; @@ -55,7 +55,7 @@ void Rom_set_addr( struct Rom_Data* this, long addr ) else { int shift = 0; - unsigned long max_addr = (unsigned long) (rounded - 1); + unsigned int max_addr = (unsigned int) (rounded - 1); while ( max_addr >> shift ) shift++; this->mask = (1L << shift) - 1; diff --git a/apps/codecs/libgme/rom_data.h b/apps/codecs/libgme/rom_data.h index 28b34f2a70..b8bc54cd3f 100644 --- a/apps/codecs/libgme/rom_data.h +++ b/apps/codecs/libgme/rom_data.h @@ -20,22 +20,22 @@ enum { max_rom_size = 2 * max_pad_size }; struct Rom_Data { byte* file_data; - blargg_ulong file_size; + unsigned file_size; - blargg_long rom_addr; - blargg_long bank_size; - blargg_long rom_size; - blargg_ulong pad_size; - blargg_long mask; - blargg_long size; // TODO: eliminate - blargg_long rsize_; + int rom_addr; + int bank_size; + int rom_size; + unsigned pad_size; + int mask; + int size; // TODO: eliminate + int rsize_; // Unmapped space byte unmapped [max_rom_size]; }; // Initialize rom -static inline void Rom_init( struct Rom_Data* this, blargg_long bank_size ) +static inline void Rom_init( struct Rom_Data* this, int bank_size ) { this->bank_size = bank_size; this->pad_size = this->bank_size + pad_extra; @@ -47,10 +47,10 @@ static inline void Rom_init( struct Rom_Data* this, blargg_long bank_size ) blargg_err_t Rom_load( struct Rom_Data* this, const void* data, long size, int header_size, void* header_out, int fill ); // Set address that file data should start at -void Rom_set_addr( struct Rom_Data* this, long addr ); +void Rom_set_addr( struct Rom_Data* this, int addr ); // Mask address to nearest power of two greater than size() -static inline blargg_long mask_addr( blargg_long addr, blargg_long mask ) +static inline int mask_addr( int addr, int mask ) { #ifdef check check( addr <= mask ); @@ -59,10 +59,10 @@ static inline blargg_long mask_addr( blargg_long addr, blargg_long mask ) } // Pointer to page starting at addr. Returns unmapped() if outside data. -static inline byte* Rom_at_addr( struct Rom_Data* this, blargg_long addr ) +static inline byte* Rom_at_addr( struct Rom_Data* this, int addr ) { - blargg_ulong offset = mask_addr( addr, this->mask ) - this->rom_addr; - if ( offset > (blargg_ulong) (this->rsize_ - this->pad_size) ) + unsigned offset = mask_addr( addr, this->mask ) - this->rom_addr; + if ( offset > (unsigned) (this->rsize_ - this->pad_size) ) offset = 0; // unmapped if ( offset < this->pad_size ) return &this->unmapped [offset]; diff --git a/apps/codecs/libgme/sgc_emu.c b/apps/codecs/libgme/sgc_emu.c index 3c3586611c..a2e6b8df29 100644 --- a/apps/codecs/libgme/sgc_emu.c +++ b/apps/codecs/libgme/sgc_emu.c @@ -10,34 +10,19 @@ module is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public -License along with this module; if not, write to the Free Software Foundation, +License aint with this module; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "blargg_source.h" int const osc_count = sms_osc_count + fm_apu_osc_count; -int const stereo = 2; // number of channels for stereo -int const silence_max = 6; // seconds -int const silence_threshold = 0x10; -long const fade_block_size = 512; -int const fade_shift = 8; // fade ends with gain at 1.0 / (1 << fade_shift) - const char gme_wrong_file_type [] = "Wrong file type for this emulator"; static void clear_track_vars( struct Sgc_Emu* this ) { this->current_track = -1; - this->out_time = 0; - this->emu_time = 0; - this->emu_track_ended_ = true; - this->track_ended = true; - this->fade_start = INT_MAX / 2 + 1; - this->fade_step = 1; - this->silence_time = 0; - this->silence_count = 0; - this->buf_remain = 0; - /* warning(); // clear warning */ + track_stop( &this->track_filter ); } void Sgc_init( struct Sgc_Emu* this ) @@ -48,12 +33,12 @@ void Sgc_init( struct Sgc_Emu* this ) this->mute_mask_ = 0; this->tempo = (int)FP_ONE_TEMPO; this->gain = (int)FP_ONE_GAIN; - this->voice_count = 0; // defaults - this->max_initial_silence = 2; - this->silence_lookahead = 6; - this->ignore_silence = false; + this->tfilter = *track_get_setup( &this->track_filter ); + this->tfilter.max_initial = 2; + this->tfilter.lookahead = 6; + this->track_filter.silence_ignored_ = false; Sms_apu_init( &this->apu ); Fm_apu_create( &this->fm_apu ); @@ -62,8 +47,10 @@ void Sgc_init( struct Sgc_Emu* this ) Z80_init( &this->cpu ); Sound_set_gain( this, (int)(FP_ONE_GAIN*1.2) ); - + // Unload + this->voice_count = 0; + this->voice_types = 0; clear_track_vars( this ); } @@ -95,6 +82,10 @@ blargg_err_t Sgc_load_mem( struct Sgc_Emu* this, const void* data, long size ) this->m3u.size = 0; this->track_count = this->header.song_count; this->voice_count = sega_mapping( this ) ? osc_count : sms_osc_count; + static int const types [osc_count + 1] = { + wave_type+1, wave_type+2, wave_type+3, mixed_type+1, mixed_type+2 + }; + this->voice_types = types; Sms_apu_volume( &this->apu, this->gain ); Fm_apu_volume( &this->fm_apu, this->gain ); @@ -102,10 +93,10 @@ blargg_err_t Sgc_load_mem( struct Sgc_Emu* this, const void* data, long size ) // Setup buffer this->clock_rate_ = clock_rate( this ); Buffer_clock_rate( &this->stereo_buf, clock_rate( this ) ); + RETURN_ERR( Buffer_set_channel_count( &this->stereo_buf, this->voice_count, this->voice_types ) ); this->buf_changed_count = Buffer_channels_changed_count( &this->stereo_buf ); - Sound_set_tempo( this, this->tempo ); - // Remute voices + Sound_set_tempo( this, this->tempo ); Sound_mute_voices( this, this->mute_mask_ ); return 0; } @@ -249,7 +240,7 @@ void cpu_write( struct Sgc_Emu* this, addr_t addr, int data ) } } -blargg_err_t Sgc_set_sample_rate( struct Sgc_Emu* this, long rate ) +blargg_err_t Sgc_set_sample_rate( struct Sgc_Emu* this, int rate ) { require( !this->sample_rate ); // sample rate can't be changed once set Buffer_init( &this->stereo_buf ); @@ -259,6 +250,8 @@ blargg_err_t Sgc_set_sample_rate( struct Sgc_Emu* this, long rate ) Buffer_bass_freq( &this->stereo_buf, 80 ); this->sample_rate = rate; + RETURN_ERR( track_init( &this->track_filter, this ) ); + this->tfilter.max_silence = 6 * stereo * this->sample_rate; return 0; } @@ -286,7 +279,7 @@ void Sound_mute_voices( struct Sgc_Emu* this, int mask ) } else { - struct channel_t ch = Buffer_channel( &this->stereo_buf ); + struct channel_t ch = Buffer_channel( &this->stereo_buf, i ); assert( (ch.center && ch.left && ch.right) || (!ch.center && !ch.left && !ch.right) ); // all or nothing Sound_set_voice( this, i, ch.center, ch.left, ch.right ); @@ -306,7 +299,6 @@ void Sound_set_tempo( struct Sgc_Emu* this, int t ) this->play_period = (int) ((clock_rate( this ) * FP_ONE_TEMPO) / (this->header.rate ? 50 : 60) / t); } -void fill_buf( struct Sgc_Emu* this ); blargg_err_t Sgc_start_track( struct Sgc_Emu* this, int track ) { clear_track_vars( this ); @@ -383,275 +375,90 @@ blargg_err_t Sgc_start_track( struct Sgc_Emu* this, int track ) Buffer_clear( &this->stereo_buf ); - this->emu_track_ended_ = false; - this->track_ended = false; - - if ( !this->ignore_silence ) - { - // play until non-silence or end of track - long end; - for ( end = this->max_initial_silence * stereo * this->sample_rate; this->emu_time < end; ) - { - fill_buf( this ); - if ( this->buf_remain | (int) this->emu_track_ended_ ) - break; - } - - this->emu_time = this->buf_remain; - this->out_time = 0; - this->silence_time = 0; - this->silence_count = 0; - } - /* return track_ended() ? warning() : 0; */ - return 0; + // convert filter times to samples + struct setup_t s = this->tfilter; + s.max_initial *= this->sample_rate * stereo; + #ifdef GME_DISABLE_SILENCE_LOOKAHEAD + s.lookahead = 1; + #endif + track_setup( &this->track_filter, &s ); + + return track_start( &this->track_filter ); } // Tell/Seek -static blargg_long msec_to_samples( blargg_long msec, long sample_rate ) +static int msec_to_samples( int msec, int sample_rate ) { - blargg_long sec = msec / 1000; + int sec = msec / 1000; msec -= sec * 1000; return (sec * sample_rate + msec * sample_rate / 1000) * stereo; } -long Track_tell( struct Sgc_Emu* this ) +int Track_tell( struct Sgc_Emu* this ) { - blargg_long rate = this->sample_rate * stereo; - blargg_long sec = this->out_time / rate; - return sec * 1000 + (this->out_time - sec * rate) * 1000 / rate; + int rate = this->sample_rate * stereo; + int sec = track_sample_count( &this->track_filter ) / rate; + return sec * 1000 + (track_sample_count( &this->track_filter ) - sec * rate) * 1000 / rate; } -blargg_err_t Track_seek( struct Sgc_Emu* this, long msec ) +blargg_err_t Track_seek( struct Sgc_Emu* this, int msec ) { - blargg_long time = msec_to_samples( msec, this->sample_rate ); - if ( time < this->out_time ) - RETURN_ERR( Sgc_start_track( this, this->current_track ) ); - return Track_skip( this, time - this->out_time ); + int time = msec_to_samples( msec, this->sample_rate ); + if ( time < track_sample_count( &this->track_filter ) ) + RETURN_ERR( Sgc_start_track( this, this->current_track ) ); + return Track_skip( this, time - track_sample_count( &this->track_filter ) ); } -blargg_err_t skip_( struct Sgc_Emu* this, long count ); -blargg_err_t Track_skip( struct Sgc_Emu* this, long count ) +blargg_err_t Track_skip( struct Sgc_Emu* this, int count ) { require( this->current_track >= 0 ); // start_track() must have been called already - this->out_time += count; - - // remove from silence and buf first - { - long n = min( count, this->silence_count ); - this->silence_count -= n; - count -= n; - - n = min( count, this->buf_remain ); - this->buf_remain -= n; - count -= n; - } - - if ( count && !this->emu_track_ended_ ) - { - this->emu_time += count; - - // End track if error - if ( skip_( this, count ) ) { - this->emu_track_ended_ = true; - } - } - - if ( !(this->silence_count | this->buf_remain) ) // caught up to emulator, so update track ended - this->track_ended |= this->emu_track_ended_; - - return 0; + return track_skip( &this->track_filter, count ); } -blargg_err_t play_( struct Sgc_Emu* this, long count, sample_t* out ); -blargg_err_t skip_( struct Sgc_Emu* this, long count ) +blargg_err_t skip_( void* emu, int count ) { + struct Sgc_Emu* this = (struct Sgc_Emu*) emu; + // for long skip, mute sound - const long threshold = 30000; + const int threshold = 32768; if ( count > threshold ) { int saved_mute = this->mute_mask_; Sound_mute_voices( this, ~0 ); - - while ( count > threshold / 2 && !this->emu_track_ended_ ) - { - RETURN_ERR( play_( this, buf_size, this->buf ) ); - count -= buf_size; - } - - Sound_mute_voices( this, saved_mute ); - } - - while ( count && !this->emu_track_ended_ ) - { - long n = buf_size; - if ( n > count ) - n = count; - count -= n; - RETURN_ERR( play_( this, n, this->buf ) ); - } - return 0; -} - -// Fading - -void Track_set_fade( struct Sgc_Emu* this, long start_msec, long length_msec ) -{ - this->fade_step = this->sample_rate * length_msec / (fade_block_size * fade_shift * 1000 / stereo); - this->fade_start = msec_to_samples( start_msec, this->sample_rate ); -} - -// unit / pow( 2.0, (double) x / step ) -static int int_log( blargg_long x, int step, int unit ) -{ - int shift = x / step; - int fraction = (x - shift * step) * unit / step; - return ((unit - fraction) + (fraction >> 1)) >> shift; -} - -static void handle_fade( struct Sgc_Emu* this, long out_count, sample_t* out ) -{ - int i; - for ( i = 0; i < out_count; i += fade_block_size ) - { - int const shift = 14; - int const unit = 1 << shift; - int gain = int_log( (this->out_time + i - this->fade_start) / fade_block_size, - this->fade_step, unit ); - if ( gain < (unit >> fade_shift) ) - this->track_ended = this->emu_track_ended_ = true; - - sample_t* io = &out [i]; - int count; - for ( count = min( fade_block_size, out_count - i ); count; --count ) - { - *io = (sample_t) ((*io * gain) >> shift); - ++io; - } - } -} -// Silence detection + int n = count - threshold/2; + n &= ~(2048-1); // round to multiple of 2048 + count -= n; + RETURN_ERR( skippy_( &this->track_filter, n ) ); -static void emu_play( struct Sgc_Emu* this, long count, sample_t* out ) -{ - check( this->current_track_ >= 0 ); - this->emu_time += count; - if ( this->current_track >= 0 && !this->emu_track_ended_ ) { - // End track if error - if ( play_( this, count, out ) ) - this->emu_track_ended_ = true; + Sound_mute_voices( this, saved_mute ); } - else - memset( out, 0, count * sizeof *out ); -} -// number of consecutive silent samples at end -static long count_silence( sample_t* begin, long size ) -{ - sample_t first = *begin; - *begin = silence_threshold; // sentinel - sample_t* p = begin + size; - while ( (unsigned) (*--p + silence_threshold / 2) <= (unsigned) silence_threshold ) { } - *begin = first; - return size - (p - begin); + return skippy_( &this->track_filter, count ); } -// fill internal buffer and check it for silence -void fill_buf( struct Sgc_Emu* this ) +void Track_set_fade( struct Sgc_Emu* this, int start_msec, int length_msec ) { - assert( !this->buf_remain ); - if ( !this->emu_track_ended_ ) - { - emu_play( this, buf_size, this->buf ); - long silence = count_silence( this->buf, buf_size ); - if ( silence < buf_size ) - { - this->silence_time = this->emu_time - silence; - this->buf_remain = buf_size; - return; - } - } - this->silence_count += buf_size; + track_set_fade( &this->track_filter, msec_to_samples( start_msec, this->sample_rate ), + length_msec * this->sample_rate / (1000 / stereo) ); } -blargg_err_t Sgc_play( struct Sgc_Emu* this, long out_count, sample_t* out ) +blargg_err_t Sgc_play( struct Sgc_Emu* this, int out_count, sample_t* out ) { - if ( this->track_ended ) - { - memset( out, 0, out_count * sizeof *out ); - } - else - { - require( this->current_track >= 0 ); - require( out_count % stereo == 0 ); - - assert( this->emu_time >= this->out_time ); - - // prints nifty graph of how far ahead we are when searching for silence - //debug_printf( "%*s \n", int ((emu_time - out_time) * 7 / sample_rate()), "*" ); - - long pos = 0; - if ( this->silence_count ) - { - // during a run of silence, run emulator at >=2x speed so it gets ahead - long ahead_time = this->silence_lookahead * (this->out_time + out_count - this->silence_time) + this->silence_time; - while ( this->emu_time < ahead_time && !(this->buf_remain | this->emu_track_ended_) ) - fill_buf( this ); - - // fill with silence - pos = min( this->silence_count, out_count ); - memset( out, 0, pos * sizeof *out ); - this->silence_count -= pos; - - if ( this->emu_time - this->silence_time > silence_max * stereo * this->sample_rate ) - { - this->track_ended = this->emu_track_ended_ = true; - this->silence_count = 0; - this->buf_remain = 0; - } - } - - if ( this->buf_remain ) - { - // empty silence buf - long n = min( this->buf_remain, out_count - pos ); - memcpy( &out [pos], this->buf + (buf_size - this->buf_remain), n * sizeof *out ); - this->buf_remain -= n; - pos += n; - } - - // generate remaining samples normally - long remain = out_count - pos; - if ( remain ) - { - emu_play( this, remain, out + pos ); - this->track_ended |= this->emu_track_ended_; - - if ( !this->ignore_silence || this->out_time > this->fade_start ) - { - // check end for a new run of silence - long silence = count_silence( out + pos, remain ); - if ( silence < remain ) - this->silence_time = this->emu_time - silence; - - if ( this->emu_time - this->silence_time >= buf_size ) - fill_buf( this ); // cause silence detection on next play() - } - } - - if ( this->out_time > this->fade_start ) - handle_fade( this, out_count, out ); - } - this->out_time += out_count; - return 0; + require( this->current_track >= 0 ); + require( out_count % stereo == 0 ); + return track_play( &this->track_filter, out_count, out ); } -blargg_err_t play_( struct Sgc_Emu* this, long count, sample_t* out ) +blargg_err_t play_( void* emu, int count, sample_t out [] ) { - long remain = count; + struct Sgc_Emu* this = (struct Sgc_Emu*) emu; + + int remain = count; while ( remain ) { + Buffer_disable_immediate_removal( &this->stereo_buf ); remain -= Buffer_read_samples( &this->stereo_buf, &out [count - remain], remain ); if ( remain ) { diff --git a/apps/codecs/libgme/sgc_emu.h b/apps/codecs/libgme/sgc_emu.h index 779ef2d923..6595c02daf 100644 --- a/apps/codecs/libgme/sgc_emu.h +++ b/apps/codecs/libgme/sgc_emu.h @@ -12,16 +12,14 @@ #include "sms_fm_apu.h" #include "sms_apu.h" #include "m3u_playlist.h" +#include "track_filter.h" -typedef short sample_t; typedef struct Z80_Cpu Sgc_Cpu; -enum { buf_size = 2048 }; - // SGC file header enum { header_size = 0xA0 }; struct header_t -{ +{ char tag [4]; // "SGC\x1A" byte vers; // 0x01 byte rate; // 0=NTSC 1=PAL @@ -48,12 +46,11 @@ struct header_t static inline bool valid_tag( struct header_t* h ) { return 0 == memcmp( h->tag, "SGC\x1A", 4 ); -} - -static inline int effect_count( struct header_t* h ) { return h->last_effect ? h->last_effect - h->first_effect + 1 : 0; } +} +static inline int effect_count( struct header_t* h ) { return h->last_effect ? h->last_effect - h->first_effect + 1 : 0; } -struct Sgc_Emu { +struct Sgc_Emu { bool fm_accessed; cpu_time_t play_period; @@ -65,40 +62,28 @@ struct Sgc_Emu { // general int voice_count; + int const* voice_types; int mute_mask_; int tempo; int gain; - long sample_rate; + int sample_rate; // track-specific - volatile bool track_ended; int current_track; int track_count; - blargg_long out_time; // number of samples played since start of track - blargg_long emu_time; // number of samples emulator has generated since start of track - bool emu_track_ended_; // emulator has reached end of track - - // fading - blargg_long fade_start; - int fade_step; - - // silence detection - bool ignore_silence; - int max_initial_silence; - int silence_lookahead; // speed to run emulator when looking ahead for silence - long silence_time; // number of samples where most recent silence began - long silence_count; // number of samples of silence to play before using buf - long buf_remain; // number of samples left in silence buffer - - long clock_rate_; + + int clock_rate_; unsigned buf_changed_count; // M3u Playlist struct M3u_Playlist m3u; + struct header_t header; - sample_t buf [buf_size]; - struct Stereo_Buffer stereo_buf; + struct setup_t tfilter; + struct Track_Filter track_filter; + + struct Multi_Buffer stereo_buf; struct Sms_Apu apu; struct Sms_Fm_Apu fm_apu; @@ -106,12 +91,11 @@ struct Sgc_Emu { Sgc_Cpu cpu; // large items - struct header_t header; struct Rom_Data rom; byte vectors [page_size + page_padding]; + byte unmapped_write [0x4000]; byte ram [0x2000 + page_padding]; byte ram2 [0x4000 + page_padding]; - byte unmapped_write [0x4000]; }; // Basic functionality (see Gme_File.h for file loading/track info functions) @@ -119,41 +103,53 @@ struct Sgc_Emu { void Sgc_init( struct Sgc_Emu* this ); blargg_err_t Sgc_load_mem( struct Sgc_Emu* this, const void* data, long size ); - + static inline int clock_rate( struct Sgc_Emu* this ) { return this->header.rate ? 3546893 : 3579545; } // 0x2000 bytes static inline void set_coleco_bios( struct Sgc_Emu* this, void* p ) { this->coleco_bios = p; } // Set output sample rate. Must be called only once before loading file. -blargg_err_t Sgc_set_sample_rate( struct Sgc_Emu* this, long sample_rate ); +blargg_err_t Sgc_set_sample_rate( struct Sgc_Emu* this, int sample_rate ); // Start a track, where 0 is the first track. Also clears warning string. blargg_err_t Sgc_start_track( struct Sgc_Emu* this, int track ); // Generate 'count' samples info 'buf'. Output is in stereo. Any emulation // errors set warning string, and major errors also end track. -blargg_err_t Sgc_play( struct Sgc_Emu* this, long count, sample_t* buf ); +blargg_err_t Sgc_play( struct Sgc_Emu* this, int count, sample_t* buf ); // Track status/control // Number of milliseconds (1000 msec = 1 second) played since beginning of track -long Track_tell( struct Sgc_Emu* this ); +int Track_tell( struct Sgc_Emu* this ); // Seek to new time in track. Seeking backwards or far forward can take a while. -blargg_err_t Track_seek( struct Sgc_Emu* this, long msec ); +blargg_err_t Track_seek( struct Sgc_Emu* this, int msec ); // Skip n samples -blargg_err_t Track_skip( struct Sgc_Emu* this, long n ); +blargg_err_t Track_skip( struct Sgc_Emu* this, int n ); // Set start time and length of track fade out. Once fade ends track_ended() returns // true. Fade time can be changed while track is playing. -void Track_set_fade( struct Sgc_Emu* this, long start_msec, long length_msec ); +void Track_set_fade( struct Sgc_Emu* this, int start_msec, int length_msec ); + +// True if a track has reached its end +static inline bool Track_ended( struct Sgc_Emu* this ) +{ + return track_ended( &this->track_filter ); +} + +// Disables automatic end-of-track detection and skipping of silence at beginning +static inline void Track_ignore_silence( struct Sgc_Emu* this, bool disable ) +{ + this->track_filter.silence_ignored_ = disable; +} // Get track length in milliseconds -static inline long Track_get_length( struct Sgc_Emu* this, int n ) +static inline int Track_get_length( struct Sgc_Emu* this, int n ) { - long length = 120 * 1000; /* 2 minutes */ + int length = 120 * 1000; /* 2 minutes */ if ( (this->m3u.size > 0) && (n < this->m3u.size) ) { struct entry_t* entry = &this->m3u.entries [n]; length = entry->length; diff --git a/apps/codecs/libgme/sms_apu.c b/apps/codecs/libgme/sms_apu.c index 965e4836c4..379fcf1cbf 100644 --- a/apps/codecs/libgme/sms_apu.c +++ b/apps/codecs/libgme/sms_apu.c @@ -158,9 +158,7 @@ static void run_until( struct Sms_Apu* this, blip_time_t end_time ) if ( delta ) { osc->last_amp = amp; - /* norm_synth.offset( last_time, delta, out ); */ Synth_offset( &this->synth, this->last_time, delta, out ); - /* out->set_modified(); */ Blip_set_modified( out ); } } @@ -200,7 +198,6 @@ static void run_until( struct Sms_Apu* this, blip_time_t end_time ) do { delta = -delta; - /* norm_synth.offset( time, delta, out ); */ Synth_offset( &this->synth, time, delta, out ); time += period; } @@ -218,7 +215,6 @@ static void run_until( struct Sms_Apu* this, blip_time_t end_time ) if ( changed & 2 ) // true if bits 0 and 1 differ { delta = -delta; - /* fast_synth.offset_inline( time, delta, out ); */ Synth_offset_inline( &this->synth, time, delta, out ); } time += period; diff --git a/apps/codecs/libgme/sms_apu.h b/apps/codecs/libgme/sms_apu.h index f887dc611e..6dd65591ce 100644 --- a/apps/codecs/libgme/sms_apu.h +++ b/apps/codecs/libgme/sms_apu.h @@ -44,10 +44,10 @@ void Sms_apu_set_output( struct Sms_Apu* this, int i, struct Blip_Buffer* center // Emulates to time t, then writes data to Game Gear left/right assignment byte void Sms_apu_write_ggstereo( struct Sms_Apu* this, blip_time_t t, int data ); - + // Emulates to time t, then writes data void Sms_apu_write_data( struct Sms_Apu* this, blip_time_t t, int data ); - + // Emulates to time t, then subtracts t from the current time. // OK if previous write call had time slightly after t. void Sms_apu_end_frame( struct Sms_Apu* this, blip_time_t t ); diff --git a/apps/codecs/libgme/sms_fm_apu.h b/apps/codecs/libgme/sms_fm_apu.h index cf8cd6c5cb..00bc2b409c 100644 --- a/apps/codecs/libgme/sms_fm_apu.h +++ b/apps/codecs/libgme/sms_fm_apu.h @@ -25,19 +25,19 @@ void Fm_apu_create( struct Sms_Fm_Apu* this ); static inline bool Fm_apu_supported( void ) { return Ym2413_supported(); } blargg_err_t Fm_apu_init( struct Sms_Fm_Apu* this, int clock_rate, int sample_rate ); - + static inline void Fm_apu_set_output( struct Sms_Fm_Apu* this, struct Blip_Buffer* b ) { this->output_ = b; } static inline void Fm_apu_volume( struct Sms_Fm_Apu* this, int v ) { Synth_volume( &this->synth, (v*2) / 5 / 4096 ); } - + void Fm_apu_reset( struct Sms_Fm_Apu* this ); - + static inline void Fm_apu_write_addr( struct Sms_Fm_Apu* this, int data ) { this->addr = data; } void Fm_apu_write_data( struct Sms_Fm_Apu* this, blip_time_t, int data ); - + void Fm_apu_end_frame( struct Sms_Fm_Apu* this, blip_time_t t ); #endif diff --git a/apps/codecs/libgme/track_filter.c b/apps/codecs/libgme/track_filter.c new file mode 100644 index 0000000000..f468a8c4b6 --- /dev/null +++ b/apps/codecs/libgme/track_filter.c @@ -0,0 +1,294 @@ +// Game_Music_Emu 0.6-pre. http://www.slack.net/~ant/ + +#include "track_filter.h" + +/* Copyright (C) 2003-2008 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +int const fade_block_size = 512; +int const fade_shift = 8; // fade ends with gain at 1.0 / (1 << fade_shift) +int const silence_threshold = 8; + +void track_create( struct Track_Filter* this ) +{ + this->emu_ = NULL; + this->setup_.max_initial = 0; + this->setup_.lookahead = 0; + this->setup_.max_silence = indefinite_count; + this->silence_ignored_ = false; + track_stop( this ); +} + +blargg_err_t track_init( struct Track_Filter* this, void* emu ) +{ + this->emu_ = emu; + return 0; +} + +void clear_time_vars( struct Track_Filter* this ) +{ + this->emu_time = this->buf_remain; + this->out_time = 0; + this->silence_time = 0; + this->silence_count = 0; +} + +void track_stop( struct Track_Filter* this ) +{ + this->emu_track_ended_ = true; + this->track_ended_ = true; + this->fade_start = indefinite_count; + this->fade_step = 1; + this->buf_remain = 0; + this->emu_error = NULL; + clear_time_vars( this ); +} + +blargg_err_t track_start( struct Track_Filter* this ) +{ + this->emu_error = NULL; + track_stop( this ); + + this->emu_track_ended_ = false; + this->track_ended_ = false; + + if ( !this->silence_ignored_ ) + { + // play until non-silence or end of track + while ( this->emu_time < this->setup_.max_initial ) + { + fill_buf( this ); + if ( this->buf_remain | this->emu_track_ended_ ) + break; + } + } + + clear_time_vars( this ); + return this->emu_error; +} + +void end_track_if_error( struct Track_Filter* this, blargg_err_t err ) +{ + if ( err ) + { + this->emu_error = err; + this->emu_track_ended_ = true; + } +} + +blargg_err_t track_skip( struct Track_Filter* this, int count ) +{ + this->emu_error = NULL; + this->out_time += count; + + // remove from silence and buf first + { + int n = min( count, this->silence_count ); + this->silence_count -= n; + count -= n; + + n = min( count, this->buf_remain ); + this->buf_remain -= n; + count -= n; + } + + if ( count && !this->emu_track_ended_ ) + { + this->emu_time += count; + this->silence_time = this->emu_time; // would otherwise be invalid + end_track_if_error( this, skip_( this->emu_, count ) ); + } + + if ( !(this->silence_count | this->buf_remain) ) // caught up to emulator, so update track ended + this->track_ended_ |= this->emu_track_ended_; + + return this->emu_error; +} + +blargg_err_t skippy_( struct Track_Filter* this, int count ) +{ + while ( count && !this->emu_track_ended_ ) + { + int n = buf_size; + if ( n > count ) + n = count; + count -= n; + RETURN_ERR( play_( this->emu_, n, this->buf ) ); + } + return 0; +} + +// Fading + +void track_set_fade( struct Track_Filter* this, int start, int length ) +{ + this->fade_start = start; + this->fade_step = length / (fade_block_size * fade_shift); + if ( this->fade_step < 1 ) + this->fade_step = 1; +} + +bool is_fading( struct Track_Filter* this ) +{ + return this->out_time >= this->fade_start && this->fade_start != indefinite_count; +} + +// unit / pow( 2.0, (double) x / step ) +static int int_log( int x, int step, int unit ) +{ + int shift = x / step; + int fraction = (x - shift * step) * unit / step; + return ((unit - fraction) + (fraction >> 1)) >> shift; +} + +void handle_fade( struct Track_Filter* this, sample_t out [], int out_count ) +{ + int i; + for ( i = 0; i < out_count; i += fade_block_size ) + { + int const shift = 14; + int const unit = 1 << shift; + int gain = int_log( (this->out_time + i - this->fade_start) / fade_block_size, + this->fade_step, unit ); + if ( gain < (unit >> fade_shift) ) + this->track_ended_ = this->emu_track_ended_ = true; + + sample_t* io = &out [i]; + for ( int count = min( fade_block_size, out_count - i ); count; --count ) + { + *io = (sample_t) ((*io * gain) >> shift); + ++io; + } + } +} + +// Silence detection + +void emu_play( struct Track_Filter* this, sample_t out [], int count ) +{ + this->emu_time += count; + if ( !this->emu_track_ended_ ) + end_track_if_error( this, play_( this->emu_, count, out ) ); + else + memset( out, 0, count * sizeof *out ); +} + +// number of consecutive silent samples at end +static int count_silence( sample_t begin [], int size ) +{ + sample_t first = *begin; + *begin = silence_threshold * 2; // sentinel + sample_t* p = begin + size; + while ( (unsigned) (*--p + silence_threshold) <= (unsigned) silence_threshold * 2 ) { } + *begin = first; + return size - (p - begin); +} + +// fill internal buffer and check it for silence +void fill_buf( struct Track_Filter* this ) +{ + assert( !this->buf_remain ); + if ( !this->emu_track_ended_ ) + { + emu_play( this, this->buf, buf_size ); + int silence = count_silence( this->buf, buf_size ); + if ( silence < buf_size ) + { + this->silence_time = this->emu_time - silence; + this->buf_remain = buf_size; + return; + } + } + this->silence_count += buf_size; +} + +blargg_err_t track_play( struct Track_Filter* this, int out_count, sample_t out [] ) +{ + this->emu_error = NULL; + if ( this->track_ended_ ) + { + memset( out, 0, out_count * sizeof *out ); + } + else + { + assert( this->emu_time >= this->out_time ); + + // prints nifty graph of how far ahead we are when searching for silence + //dprintf( "%*s \n", int ((emu_time - out_time) * 7 / 44100), "*" ); + + // use any remaining silence samples + int pos = 0; + if ( this->silence_count ) + { + if ( !this->silence_ignored_ ) + { + // during a run of silence, run emulator at >=2x speed so it gets ahead + int ahead_time = this->setup_.lookahead * (this->out_time + out_count - this->silence_time) + + this->silence_time; + while ( this->emu_time < ahead_time && !(this->buf_remain | this->emu_track_ended_) ) + fill_buf( this ); + + // end track if sufficient silence has been found + if ( this->emu_time - this->silence_time > this->setup_.max_silence ) + { + this->track_ended_ = this->emu_track_ended_ = true; + this->silence_count = out_count; + this->buf_remain = 0; + } + } + + // fill from remaining silence + pos = min( this->silence_count, out_count ); + memset( out, 0, pos * sizeof *out ); + this->silence_count -= pos; + } + + // use any remaining samples from buffer + if ( this->buf_remain ) + { + int n = min( this->buf_remain, (int) (out_count - pos) ); + memcpy( out + pos, this->buf + (buf_size - this->buf_remain), n * sizeof *out ); + this->buf_remain -= n; + pos += n; + } + + // generate remaining samples normally + int remain = out_count - pos; + if ( remain ) + { + emu_play( this, out + pos, remain ); + this->track_ended_ |= this->emu_track_ended_; + + if ( this->silence_ignored_ && !is_fading( this ) ) + { + // if left unupdated, ahead_time could become too large + this->silence_time = this->emu_time; + } + else + { + // check end for a new run of silence + int silence = count_silence( out + pos, remain ); + if ( silence < remain ) + this->silence_time = this->emu_time - silence; + + if ( this->emu_time - this->silence_time >= buf_size ) + fill_buf( this ); // cause silence detection on next play() + } + } + + if ( is_fading( this ) ) + handle_fade( this, out, out_count ); + } + this->out_time += out_count; + return this->emu_error; +} diff --git a/apps/codecs/libgme/track_filter.h b/apps/codecs/libgme/track_filter.h new file mode 100644 index 0000000000..35049b91bb --- /dev/null +++ b/apps/codecs/libgme/track_filter.h @@ -0,0 +1,90 @@ +// Removes silence from beginning of track, fades end of track. Also looks ahead +// for excessive silence, and if found, ends track. + +// Game_Music_Emu 0.6-pre +#ifndef TRACK_FILTER_H +#define TRACK_FILTER_H + +#include "blargg_common.h" + +typedef short sample_t; +typedef int sample_count_t; + +enum { indefinite_count = INT_MAX/2 + 1 }; +enum { buf_size = 2048 }; + +struct setup_t { + sample_count_t max_initial; // maximum silence to strip from beginning of track + sample_count_t max_silence; // maximum silence in middle of track without it ending + int lookahead; // internal speed when looking ahead for silence (2=200% etc.) +}; + +struct Track_Filter { + void* emu_; + struct setup_t setup_; + const char* emu_error; + bool silence_ignored_; + + // Timing + int out_time; // number of samples played since start of track + int emu_time; // number of samples emulator has generated since start of track + int emu_track_ended_; // emulator has reached end of track + volatile int track_ended_; + + // Fading + int fade_start; + int fade_step; + + // Silence detection + int silence_time; // absolute number of samples where most recent silence began + int silence_count; // number of samples of silence to play before using buf + int buf_remain; // number of samples left in silence buffer + sample_t buf [buf_size]; +}; + +// Initializes filter. Must be done once before using object. +blargg_err_t track_init( struct Track_Filter* this, void* ); +void track_create( struct Track_Filter* this ); + +// Gets/sets setup +static inline struct setup_t const* track_get_setup( struct Track_Filter* this ) { return &this->setup_; } +static inline void track_setup( struct Track_Filter* this, struct setup_t const* s ) { this->setup_ = *s; } + +// Disables automatic end-of-track detection and skipping of silence at beginning +static inline void track_ignore_silence( struct Track_Filter* this, bool disable ) { this->silence_ignored_ = disable; } + +// Clears state and skips initial silence in track +blargg_err_t track_start( struct Track_Filter* this ); + +// Sets time that fade starts, and how long until track ends. +void track_set_fade( struct Track_Filter* this, sample_count_t start, sample_count_t length ); + +// Generates n samples into buf +blargg_err_t track_play( struct Track_Filter* this, int n, sample_t buf [] ); + +// Skips n samples +blargg_err_t track_skip( struct Track_Filter* this, int n ); + +// Number of samples played/skipped since start_track() +static inline int track_sample_count( struct Track_Filter* this ) { return this->out_time; } + +// True if track ended. Causes are end of source samples, end of fade, +// or excessive silence. +static inline bool track_ended( struct Track_Filter* this ) { return this->track_ended_; } + +// Clears state +void track_stop( struct Track_Filter* this ); + +// For use by callbacks + +// Sets internal "track ended" flag and stops generation of further source samples +static inline void track_set_end( struct Track_Filter* this ) { this->emu_track_ended_ = true; } + +// For use by skip_() callback +blargg_err_t skippy_( struct Track_Filter* this, int count ); +void fill_buf( struct Track_Filter* this ); + +// Skip and play callbacks +blargg_err_t skip_( void* emu, int count ); +blargg_err_t play_( void* emu, int count, sample_t out [] ); +#endif diff --git a/apps/codecs/libgme/vgm_emu.c b/apps/codecs/libgme/vgm_emu.c index 391bd02d73..4f8a2adbc1 100644 --- a/apps/codecs/libgme/vgm_emu.c +++ b/apps/codecs/libgme/vgm_emu.c @@ -23,11 +23,6 @@ const char* const gme_wrong_file_type = "Wrong file type for this emulator"; int const fm_gain = 3; // FM emulators are internally quieter to avoid 16-bit overflow -int const silence_max = 6; // seconds -int const silence_threshold = 0x10; -long const fade_block_size = 512; -int const fade_shift = 8; // fade ends with gain at 1.0 / (1 << fade_shift) - // VGM commands (Spec v1.50) enum { cmd_gg_stereo = 0x4F, @@ -53,15 +48,8 @@ enum { static void clear_track_vars( struct Vgm_Emu* this ) { - this->out_time = 0; - this->emu_time = 0; - this->emu_track_ended_ = true; - this->track_ended = true; - this->fade_start = INT_MAX / 2 + 1; - this->fade_step = 1; - this->silence_time = 0; - this->silence_count = 0; - this->buf_remain = 0; + this->current_track = -1; + track_stop( &this->track_filter ); } int play_frame( struct Vgm_Emu* this, blip_time_t blip_time, int sample_count, sample_t* buf ); @@ -77,9 +65,10 @@ void Vgm_init( struct Vgm_Emu* this ) this->tempo = (int)(FP_ONE_TEMPO); // defaults - this->max_initial_silence = 2; - this->silence_lookahead = 1; // tracks should already be trimmed - this->ignore_silence = false; + this->tfilter = *track_get_setup( &this->track_filter ); + this->tfilter.max_initial = 2; + this->tfilter.lookahead = 1; + this->track_filter.silence_ignored_ = false; // Disable oversampling by default this->disable_oversampling = true; @@ -88,10 +77,10 @@ void Vgm_init( struct Vgm_Emu* this ) Sms_apu_init( &this->psg ); Synth_init( &this->pcm ); - Buffer_init( &this->buf ); + Buffer_init( &this->stereo_buf ); Blip_init( &this->blip_buf ); - // Init fm chips + // Init fm chips Ym2413_init( &this->ym2413 ); Ym2612_init( &this->ym2612 ); @@ -105,7 +94,8 @@ void Vgm_init( struct Vgm_Emu* this ) // Unload this->voice_count = 0; - clear_track_vars( this ); + this->voice_types = 0; + clear_track_vars( this ); } // Track info @@ -150,13 +140,13 @@ static void parse_gd3( byte const* in, byte const* end, struct track_info_t* out int const gd3_header_size = 12; -static long check_gd3_header( byte const* h, long remain ) +static int check_gd3_header( byte const* h, int remain ) { if ( remain < gd3_header_size ) return 0; if ( memcmp( h, "Gd3 ", 4 ) ) return 0; if ( get_le32( h + 4 ) >= 0x200 ) return 0; - long gd3_size = get_le32( h + 8 ); + int gd3_size = get_le32( h + 8 ); if ( gd3_size > remain - gd3_header_size ) gd3_size = remain - gd3_header_size; return gd3_size; @@ -167,12 +157,12 @@ static byte const* gd3_data( struct Vgm_Emu* this, int* size ) if ( size ) *size = 0; - long gd3_offset = get_le32( header( this )->gd3_offset ) - 0x2C; + int gd3_offset = get_le32( header( this )->gd3_offset ) - 0x2C; if ( gd3_offset < 0 ) return 0; byte const* gd3 = this->file_begin + header_size + gd3_offset; - long gd3_size = check_gd3_header( gd3, this->file_end - gd3 ); + int gd3_size = check_gd3_header( gd3, this->file_end - gd3 ); if ( !gd3_size ) return 0; @@ -184,10 +174,10 @@ static byte const* gd3_data( struct Vgm_Emu* this, int* size ) static void get_vgm_length( struct header_t const* h, struct track_info_t* out ) { - long length = get_le32( h->track_duration ) * 10 / 441; + int length = get_le32( h->track_duration ) * 10 / 441; if ( length > 0 ) { - long loop = get_le32( h->loop_duration ); + int loop = get_le32( h->loop_duration ); if ( loop > 0 && get_le32( h->loop_offset ) ) { out->loop_length = loop * 10 / 441; @@ -285,24 +275,25 @@ blargg_err_t Vgm_load_mem( struct Vgm_Emu* this, byte const* new_data, long new_ Ym2612_enable( &this->ym2612, false ); Ym2413_enable( &this->ym2413, false ); - Sound_set_tempo( this, (int)(FP_ONE_TEMPO) ); - this->voice_count = sms_osc_count; + static int const types [8] = { + wave_type+1, wave_type+2, wave_type+3, noise_type+1, + 0, 0, 0, 0 + }; + this->voice_types = types; RETURN_ERR( setup_fm( this ) ); // do after FM in case output buffer is changed // setup buffer this->clock_rate_ = this->psg_rate; - Buffer_clock_rate( &this->buf, this->psg_rate ); - - // Setup bass - this->buf_changed_count = Buffer_channels_changed_count( &this->buf ); + Buffer_clock_rate( &this->stereo_buf, this->psg_rate ); + RETURN_ERR( Buffer_set_channel_count( &this->stereo_buf, this->voice_count, this->voice_types ) ); + this->buf_changed_count = Buffer_channels_changed_count( &this->stereo_buf ); // Post load Sound_set_tempo( this, this->tempo ); Sound_mute_voices( this, this->mute_mask_ ); - return 0; } @@ -362,42 +353,56 @@ blargg_err_t setup_fm( struct Vgm_Emu* this ) // Emulation blip_time_t run( struct Vgm_Emu* this, vgm_time_t end_time ); -static blargg_err_t run_clocks( struct Vgm_Emu* this, blip_time_t* time_io, int msec ) +static blip_time_t run_psg( struct Vgm_Emu* this, int msec ) { - *time_io = run( this, msec * this->vgm_rate / 1000 ); - Sms_apu_end_frame( &this->psg, *time_io ); - return 0; + blip_time_t t = run( this, msec * this->vgm_rate / 1000 ); + Sms_apu_end_frame( &this->psg, t ); + return t; } +static void check_end( struct Vgm_Emu* this ) +{ + if( this->pos >= this->file_end ) + track_set_end( &this->track_filter ); +} +static blargg_err_t run_clocks( struct Vgm_Emu* this, blip_time_t* time_io, int msec ) +{ + check_end( this ); + *time_io = run_psg( this, msec ); + return 0; +} -static blargg_err_t play_( struct Vgm_Emu* this, long count, sample_t* out ) +blargg_err_t play_( void *emu, int count, sample_t out [] ) { + struct Vgm_Emu* this = (struct Vgm_Emu*) emu; + if ( !uses_fm( this ) ) { - long remain = count; + int remain = count; while ( remain ) { - remain -= Buffer_read_samples( &this->buf, &out [count - remain], remain ); + Buffer_disable_immediate_removal( &this->stereo_buf ); + remain -= Buffer_read_samples( &this->stereo_buf, &out [count - remain], remain ); if ( remain ) - { - if ( this->buf_changed_count != Buffer_channels_changed_count( &this->buf ) ) + { + if ( this->buf_changed_count != Buffer_channels_changed_count( &this->stereo_buf ) ) { - this->buf_changed_count = Buffer_channels_changed_count( &this->buf ); + this->buf_changed_count = Buffer_channels_changed_count( &this->stereo_buf ); // Remute voices Sound_mute_voices( this, this->mute_mask_ ); } - int msec = Buffer_length( &this->buf ); - blip_time_t clocks_emulated = (blargg_long) msec * this->clock_rate_ / 1000 - 100; + int msec = Buffer_length( &this->stereo_buf ); + blip_time_t clocks_emulated = msec * this->clock_rate_ / 1000 - 100; RETURN_ERR( run_clocks( this, &clocks_emulated, msec ) ); assert( clocks_emulated ); - Buffer_end_frame( &this->buf, clocks_emulated ); + Buffer_end_frame( &this->stereo_buf, clocks_emulated ); } } return 0; } - + Resampler_play( &this->resampler, count, out, &this->blip_buf ); return 0; } @@ -414,7 +419,7 @@ static inline int command_len( int command ) check( len != 1 ); return len; } - + static inline fm_time_t to_fm_time( struct Vgm_Emu* this, vgm_time_t t ) { return (t * this->fm_time_factor + this->fm_time_offset) >> fm_time_bits; @@ -443,12 +448,10 @@ blip_time_t run( struct Vgm_Emu* this, vgm_time_t end_time ) { vgm_time_t vgm_time = this->vgm_time; byte const* pos = this->pos; - if ( pos >= this->file_end ) + /* if ( pos > this->file_end ) { - this->emu_track_ended_ = true; - /* if ( pos > data_end ) - warning( "Stream lacked end event" ); */ - } + warning( "Stream lacked end event" ); + } */ while ( vgm_time < end_time && pos < this->file_end ) { @@ -517,7 +520,7 @@ blip_time_t run( struct Vgm_Emu* this, vgm_time_t end_time ) case cmd_data_block: { check( *pos == cmd_end ); int type = pos [1]; - long size = get_le32( pos + 2 ); + int size = get_le32( pos + 2 ); pos += 6; if ( type == pcm_block_type ) this->pcm_data = pos; @@ -564,6 +567,8 @@ blip_time_t run( struct Vgm_Emu* this, vgm_time_t end_time ) int play_frame( struct Vgm_Emu* this, blip_time_t blip_time, int sample_count, blip_sample_t out [] ) { + check_end( this); + // to do: timing is working mostly by luck int min_pairs = (unsigned) sample_count / 2; int vgm_time = (min_pairs << fm_time_bits) / this->fm_time_factor - 1; @@ -642,16 +647,18 @@ void update_fm_rates( struct Vgm_Emu* this, int* ym2413_rate, int* ym2612_rate ) // Music Emu -blargg_err_t Vgm_set_sample_rate( struct Vgm_Emu* this, long rate ) +blargg_err_t Vgm_set_sample_rate( struct Vgm_Emu* this, int rate ) { require( !this->sample_rate ); // sample rate can't be changed once set RETURN_ERR( Blip_set_sample_rate( &this->blip_buf, rate, 1000 / 30 ) ); - RETURN_ERR( Buffer_set_sample_rate( &this->buf, rate, 1000 / 20 ) ); + RETURN_ERR( Buffer_set_sample_rate( &this->stereo_buf, rate, 1000 / 20 ) ); // Set bass frequency - Buffer_bass_freq( &this->buf, 80 ); + Buffer_bass_freq( &this->stereo_buf, 80 ); this->sample_rate = rate; + RETURN_ERR( track_init( &this->track_filter, this ) ); + this->tfilter.max_silence = 6 * stereo * this->sample_rate; return 0; } @@ -679,7 +686,7 @@ void Sound_mute_voices( struct Vgm_Emu* this, int mask ) } else { - struct channel_t ch = Buffer_channel( &this->buf ); + struct channel_t ch = Buffer_channel( &this->stereo_buf, i ); assert( (ch.center && ch.left && ch.right) || (!ch.center && !ch.left && !ch.right) ); // all or nothing set_voice( this, i, ch.center, ch.left, ch.right ); @@ -735,7 +742,6 @@ void Sound_set_tempo( struct Vgm_Emu* this, int t ) } } -void fill_buf( struct Vgm_Emu *this ); blargg_err_t Vgm_start_track( struct Vgm_Emu* this ) { clear_track_vars( this ); @@ -750,7 +756,7 @@ blargg_err_t Vgm_start_track( struct Vgm_Emu* this ) this->vgm_time = 0; if ( get_le32( header( this )->version ) >= 0x150 ) { - long data_offset = get_le32( header( this )->data_offset ); + int data_offset = get_le32( header( this )->data_offset ); check( data_offset ); if ( data_offset ) this->pos += data_offset + offsetof (struct header_t,data_offset) - 0x40; @@ -764,266 +770,88 @@ blargg_err_t Vgm_start_track( struct Vgm_Emu* this ) if ( Ym2612_enabled( &this->ym2612 ) ) Ym2612_reset( &this->ym2612 ); - Blip_clear( &this->blip_buf, 1 ); + Blip_clear( &this->blip_buf ); Resampler_clear( &this->resampler ); } this->fm_time_offset = 0; - Buffer_clear( &this->buf ); + Buffer_clear( &this->stereo_buf ); - this->emu_track_ended_ = false; - this->track_ended = false; + // convert filter times to samples + struct setup_t s = this->tfilter; + s.max_initial *= this->sample_rate * stereo; + #ifdef GME_DISABLE_SILENCE_LOOKAHEAD + s.lookahead = 1; + #endif + track_setup( &this->track_filter, &s ); - if ( !this->ignore_silence ) - { - // play until non-silence or end of track - long end; - for ( end = this->max_initial_silence * stereo * this->sample_rate; this->emu_time < end; ) - { - fill_buf( this ); - if ( this->buf_remain | (int) this->emu_track_ended_ ) - break; - } - - this->emu_time = this->buf_remain; - this->out_time = 0; - this->silence_time = 0; - this->silence_count = 0; - } - /* return track_ended() ? warning() : 0; */ - return 0; + return track_start( &this->track_filter ); } // Tell/Seek -static blargg_long msec_to_samples( blargg_long msec, long sample_rate ) +static int msec_to_samples( int msec, int sample_rate ) { - blargg_long sec = msec / 1000; + int sec = msec / 1000; msec -= sec * 1000; return (sec * sample_rate + msec * sample_rate / 1000) * stereo; } -long Track_tell( struct Vgm_Emu* this ) +int Track_tell( struct Vgm_Emu* this ) { - blargg_long rate = this->sample_rate * stereo; - blargg_long sec = this->out_time / rate; - return sec * 1000 + (this->out_time - sec * rate) * 1000 / rate; + int rate = this->sample_rate * stereo; + int sec = track_sample_count( &this->track_filter ) / rate; + return sec * 1000 + (track_sample_count( &this->track_filter ) - sec * rate) * 1000 / rate; } -blargg_err_t Track_seek( struct Vgm_Emu* this, long msec ) +blargg_err_t Track_seek( struct Vgm_Emu* this, int msec ) { - blargg_long time = msec_to_samples( msec, this->sample_rate ); - if ( time < this->out_time ) - RETURN_ERR( Vgm_start_track( this ) ); - return Track_skip( this, time - this->out_time ); + int time = msec_to_samples( msec, this->sample_rate ); + if ( time < track_sample_count( &this->track_filter ) ) + RETURN_ERR( Vgm_start_track( this ) ); + return Track_skip( this, time - track_sample_count( &this->track_filter ) ); } -blargg_err_t skip_( struct Vgm_Emu* this, long count ); -blargg_err_t Track_skip( struct Vgm_Emu* this, long count ) +blargg_err_t Track_skip( struct Vgm_Emu* this, int count ) { - this->out_time += count; - - // remove from silence and buf first - { - long n = min( count, this->silence_count ); - this->silence_count -= n; - count -= n; - - n = min( count, this->buf_remain ); - this->buf_remain -= n; - count -= n; - } - - if ( count && !this->emu_track_ended_ ) - { - this->emu_time += count; - if ( skip_( this, count ) ) - this->emu_track_ended_ = true; - } - - if ( !(this->silence_count | this->buf_remain) ) // caught up to emulator, so update track ended - this->track_ended |= this->emu_track_ended_; - - return 0; + require( this->current_track >= 0 ); // start_track() must have been called already + return track_skip( &this->track_filter, count ); } -blargg_err_t skip_( struct Vgm_Emu* this, long count ) +blargg_err_t skip_( void* emu, int count ) { + struct Vgm_Emu* this = (struct Vgm_Emu*) emu; + // for long skip, mute sound - const long threshold = 30000; + const int threshold = 32768; if ( count > threshold ) { int saved_mute = this->mute_mask_; Sound_mute_voices( this, ~0 ); - - while ( count > threshold / 2 && !this->emu_track_ended_ ) - { - RETURN_ERR( play_( this, buf_size, this->buf_ ) ); - count -= buf_size; - } - - Sound_mute_voices( this, saved_mute ); - } - - while ( count && !this->emu_track_ended_ ) - { - long n = buf_size; - if ( n > count ) - n = count; - count -= n; - RETURN_ERR( play_( this, n, this->buf_ ) ); - } - return 0; -} -// Fading - -void Track_set_fade( struct Vgm_Emu* this, long start_msec, long length_msec ) -{ - this->fade_step = this->sample_rate * length_msec / (fade_block_size * fade_shift * 1000 / stereo); - this->fade_start = msec_to_samples( start_msec, this->sample_rate ); -} - -// unit / pow( 2.0, (double) x / step ) -static int int_log( blargg_long x, int step, int unit ) -{ - int shift = x / step; - int fraction = (x - shift * step) * unit / step; - return ((unit - fraction) + (fraction >> 1)) >> shift; -} + int n = count - threshold/2; + n &= ~(2048-1); // round to multiple of 2048 + count -= n; + RETURN_ERR( skippy_( &this->track_filter, n ) ); -static void handle_fade( struct Vgm_Emu* this, long out_count, sample_t* out ) -{ - int i; - for ( i = 0; i < out_count; i += fade_block_size ) - { - int const shift = 14; - int const unit = 1 << shift; - int gain = int_log( (this->out_time + i - this->fade_start) / fade_block_size, - this->fade_step, unit ); - if ( gain < (unit >> fade_shift) ) - this->track_ended = this->emu_track_ended_ = true; - - sample_t* io = &out [i]; - int count; - for ( count = min( fade_block_size, out_count - i ); count; --count ) - { - *io = (sample_t) ((*io * gain) >> shift); - ++io; - } + Sound_mute_voices( this, saved_mute ); } -} -// Silence detection - -static void emu_play( struct Vgm_Emu* this, long count, sample_t* out ) -{ - this->emu_time += count; - if ( !this->emu_track_ended_ ) { - if ( play_( this, count, out ) ) - this->emu_track_ended_ = true; - } - else - memset( out, 0, count * sizeof *out ); + return skippy_( &this->track_filter, count ); } -// number of consecutive silent samples at end -static long count_silence( sample_t* begin, long size ) -{ - sample_t first = *begin; - *begin = silence_threshold; // sentinel - sample_t* p = begin + size; - while ( (unsigned) (*--p + silence_threshold / 2) <= (unsigned) silence_threshold ) { } - *begin = first; - return size - (p - begin); -} +// Fading -// fill internal buffer and check it for silence -void fill_buf( struct Vgm_Emu* this ) +void Track_set_fade( struct Vgm_Emu* this, int start_msec, int length_msec ) { - assert( !this->buf_remain ); - if ( !this->emu_track_ended_ ) - { - emu_play( this, buf_size, this->buf_ ); - long silence = count_silence( this->buf_, buf_size ); - if ( silence < buf_size ) - { - this->silence_time = this->emu_time - silence; - this->buf_remain = buf_size; - return; - } - } - this->silence_count += buf_size; + track_set_fade( &this->track_filter, msec_to_samples( start_msec, this->sample_rate ), + length_msec * this->sample_rate / (1000 / stereo) ); } -blargg_err_t Vgm_play( struct Vgm_Emu* this, long out_count, sample_t* out ) +blargg_err_t Vgm_play( struct Vgm_Emu* this, int out_count, sample_t* out ) { - if ( this->track_ended ) - { - memset( out, 0, out_count * sizeof *out ); - } - else - { - require( out_count % stereo == 0 ); - - assert( this->emu_time >= this->out_time ); - - // prints nifty graph of how far ahead we are when searching for silence - //debug_printf( "%*s \n", int ((emu_time - out_time) * 7 / sample_rate()), "*" ); - - long pos = 0; - if ( this->silence_count ) - { - // during a run of silence, run emulator at >=2x speed so it gets ahead - long ahead_time = this->silence_lookahead * (this->out_time + out_count - this->silence_time) + this->silence_time; - while ( this->emu_time < ahead_time && !(this->buf_remain | this->emu_track_ended_) ) - fill_buf( this ); - - // fill with silence - pos = min( this->silence_count, out_count ); - memset( out, 0, pos * sizeof *out ); - this->silence_count -= pos; - - if ( this->emu_time - this->silence_time > silence_max * stereo * this->sample_rate ) - { - this->track_ended = this->emu_track_ended_ = true; - this->silence_count = 0; - this->buf_remain = 0; - } - } - - if ( this->buf_remain ) - { - // empty silence buf - long n = min( this->buf_remain, out_count - pos ); - memcpy( &out [pos], this->buf_ + (buf_size - this->buf_remain), n * sizeof *out ); - this->buf_remain -= n; - pos += n; - } - - // generate remaining samples normally - long remain = out_count - pos; - if ( remain ) - { - emu_play( this, remain, out + pos ); - this->track_ended |= this->emu_track_ended_; - - if ( !this->ignore_silence || this->out_time > this->fade_start ) - { - // check end for a new run of silence - long silence = count_silence( out + pos, remain ); - if ( silence < remain ) - this->silence_time = this->emu_time - silence; - - if ( this->emu_time - this->silence_time >= buf_size ) - fill_buf( this ); // cause silence detection on next play() - } - } - - if ( this->out_time > this->fade_start ) - handle_fade( this, out_count, out ); - } - this->out_time += out_count; - return 0; + require( this->current_track >= 0 ); + require( out_count % stereo == 0 ); + return track_play( &this->track_filter, out_count, out ); } diff --git a/apps/codecs/libgme/vgm_emu.h b/apps/codecs/libgme/vgm_emu.h index 773b850a09..8c39482008 100644 --- a/apps/codecs/libgme/vgm_emu.h +++ b/apps/codecs/libgme/vgm_emu.h @@ -1,11 +1,13 @@ // Sega Master System/Mark III, Sega Genesis/Mega Drive, BBC Micro VGM music file emulator -// Game_Music_Emu 0.5.5 +// Game_Music_Emu 0.6-pre #ifndef VGM_EMU_H #define VGM_EMU_H #include "blargg_common.h" #include "blargg_source.h" + +#include "track_filter.h" #include "resampler.h" #include "multi_buffer.h" #include "ym2413_emu.h" @@ -17,7 +19,6 @@ typedef int fm_time_t; enum { fm_time_bits = 12 }; enum { blip_time_bits = 12 }; -enum { buf_size = 2048 }; // VGM header format enum { header_size = 0x40 }; @@ -46,9 +47,9 @@ enum { gme_max_field = 63 }; struct track_info_t { /* times in milliseconds; -1 if unknown */ - long length; - long intro_length; - long loop_length; + int length; + int intro_length; + int loop_length; /* empty string if not available */ char game [64]; @@ -63,11 +64,11 @@ struct track_info_t // YM2413 sound chip emulator. I can provide one I've modified to work with the library. struct Vgm_Emu { int fm_rate; - long psg_rate; - long vgm_rate; + int psg_rate; + int vgm_rate; bool disable_oversampling; - long fm_time_offset; + int fm_time_offset; int fm_time_factor; int blip_time_factor; @@ -84,47 +85,34 @@ struct Vgm_Emu { int dac_amp; int dac_disabled; // -1 if disabled - struct Blip_Buffer blip_buf; - // general - long clock_rate_; + int current_track; + int clock_rate_; unsigned buf_changed_count; int max_initial_silence; int voice_count; + int const *voice_types; int mute_mask_; int tempo; int gain; - long sample_rate; - - // track-specific - blargg_long out_time; // number of samples played since start of track - blargg_long emu_time; // number of samples emulator has generated since start of track - bool emu_track_ended_; // emulator has reached end of track - volatile bool track_ended; - - // fading - blargg_long fade_start; - int fade_step; - - // silence detection - int silence_lookahead; // speed to run emulator when looking ahead for silence - bool ignore_silence; - long silence_time; // number of samples where most recent silence began - long silence_count; // number of samples of silence to play before using buf - long buf_remain; // number of samples left in silence buffer + int sample_rate; // larger items at the end struct track_info_t info; - sample_t buf_ [buf_size]; + + struct setup_t tfilter; + struct Track_Filter track_filter; struct Ym2612_Emu ym2612; struct Ym2413_Emu ym2413; struct Sms_Apu psg; struct Blip_Synth pcm; + struct Blip_Buffer blip_buf; + struct Resampler resampler; - struct Stereo_Buffer buf; + struct Multi_Buffer stereo_buf; }; void Vgm_init( struct Vgm_Emu* this ); @@ -135,7 +123,7 @@ static inline void Vgm_disable_oversampling( struct Vgm_Emu* this, bool disable // Header for currently loaded file static inline struct header_t *header( struct Vgm_Emu* this ) { return (struct header_t*) this->file_begin; } - + // Basic functionality (see Gme_File.h for file loading/track info functions) blargg_err_t Vgm_load_mem( struct Vgm_Emu* this, byte const* new_data, long new_size, bool parse_info ); @@ -144,34 +132,46 @@ blargg_err_t Vgm_load_mem( struct Vgm_Emu* this, byte const* new_data, long new_ static inline bool uses_fm( struct Vgm_Emu* this ) { return Ym2612_enabled( &this->ym2612 ) || Ym2413_enabled( &this->ym2413 ); } // Set output sample rate. Must be called only once before loading file. -blargg_err_t Vgm_set_sample_rate( struct Vgm_Emu* this, long sample_rate ); - +blargg_err_t Vgm_set_sample_rate( struct Vgm_Emu* this, int sample_rate ); + // Start a track, where 0 is the first track. Also clears warning string. blargg_err_t Vgm_start_track( struct Vgm_Emu* this ); - + // Generate 'count' samples info 'buf'. Output is in stereo. Any emulation // errors set warning string, and major errors also end track. -blargg_err_t Vgm_play( struct Vgm_Emu* this, long count, sample_t* buf ); - +blargg_err_t Vgm_play( struct Vgm_Emu* this, int count, sample_t* buf ); + // Track status/control // Number of milliseconds (1000 msec = 1 second) played since beginning of track -long Track_tell( struct Vgm_Emu* this ); - +int Track_tell( struct Vgm_Emu* this ); + // Seek to new time in track. Seeking backwards or far forward can take a while. -blargg_err_t Track_seek( struct Vgm_Emu* this, long msec ); - +blargg_err_t Track_seek( struct Vgm_Emu* this, int msec ); + // Skip n samples -blargg_err_t Track_skip( struct Vgm_Emu* this, long n ); - +blargg_err_t Track_skip( struct Vgm_Emu* this, int n ); + // Set start time and length of track fade out. Once fade ends track_ended() returns // true. Fade time can be changed while track is playing. -void Track_set_fade( struct Vgm_Emu* this, long start_msec, long length_msec ); +void Track_set_fade( struct Vgm_Emu* this, int start_msec, int length_msec ); + +// True if a track has reached its end +static inline bool Track_ended( struct Vgm_Emu* this ) +{ + return track_ended( &this->track_filter ); +} + +// Disables automatic end-of-track detection and skipping of silence at beginning +static inline void Track_ignore_silence( struct Vgm_Emu* this, bool disable ) +{ + this->track_filter.silence_ignored_ = disable; +} // Get track length in milliseconds -static inline long Track_get_length( struct Vgm_Emu* this ) +static inline int Track_get_length( struct Vgm_Emu* this ) { - long length = this->info.length; + int length = this->info.length; if ( length <= 0 ) { length = this->info.intro_length + 2 * this->info.loop_length; // intro + 2 loops @@ -181,20 +181,20 @@ static inline long Track_get_length( struct Vgm_Emu* this ) return length; } - + // Sound customization - + // Adjust song tempo, where 1.0 = normal, 0.5 = half speed, 2.0 = double speed. // Track length as returned by track_info() assumes a tempo of 1.0. void Sound_set_tempo( struct Vgm_Emu* this, int t ); - + // Mute/unmute voice i, where voice 0 is first voice void Sound_mute_voice( struct Vgm_Emu* this, int index, bool mute ); - + // Set muting state of all voices at once using a bit mask, where -1 mutes them all, // 0 unmutes them all, 0x01 mutes just the first voice, etc. void Sound_mute_voices( struct Vgm_Emu* this, int mask ); - + // Change overall output amplitude, where 1.0 results in minimal clamping. // Must be called before set_sample_rate(). static inline void Sound_set_gain( struct Vgm_Emu* this, int g ) @@ -203,5 +203,4 @@ static inline void Sound_set_gain( struct Vgm_Emu* this, int g ) this->gain = g; } - #endif diff --git a/apps/codecs/libgme/ym2612_emu.c b/apps/codecs/libgme/ym2612_emu.c index a1f96e3d70..ee7947ef3b 100644 --- a/apps/codecs/libgme/ym2612_emu.c +++ b/apps/codecs/libgme/ym2612_emu.c @@ -4,7 +4,6 @@ #include "ym2612_emu.h" -#include #include #include #include diff --git a/apps/codecs/nsf.c b/apps/codecs/nsf.c index 533972e110..7214b5d2a2 100644 --- a/apps/codecs/nsf.c +++ b/apps/codecs/nsf.c @@ -74,8 +74,8 @@ enum codec_status codec_run(void) return CODEC_ERROR; } - if ((err = Nsf_load(&nsf_emu, buf, ci->filesize))) { - DEBUGF("NSF: Nsf_load failed (%s)\n", err); + if ((err = Nsf_load_mem(&nsf_emu, buf, ci->filesize))) { + DEBUGF("NSF: Nsf_load_mem failed (%s)\n", err); return CODEC_ERROR; } @@ -116,7 +116,7 @@ next_track: /* Generate audio buffer */ err = Nsf_play(&nsf_emu, CHUNK_SIZE, samples); - if (err || nsf_emu.track_ended) { + if (err || Track_ended(&nsf_emu)) { track++; if (track >= nsf_emu.track_count) break; goto next_track; diff --git a/apps/codecs/sgc.c b/apps/codecs/sgc.c index 993c606c7e..348a54a2d3 100644 --- a/apps/codecs/sgc.c +++ b/apps/codecs/sgc.c @@ -83,7 +83,7 @@ enum codec_status codec_run(void) } if ((err = Sgc_load_mem(&sgc_emu, buf, ci->filesize))) { - DEBUGF("SGC: Sgc_load failed (%s)\n", err); + DEBUGF("SGC: Sgc_load_mem failed (%s)\n", err); return CODEC_ERROR; } @@ -110,7 +110,7 @@ next_track: /* Generate audio buffer */ err = Sgc_play(&sgc_emu, CHUNK_SIZE, samples); - if (err || sgc_emu.track_ended) { + if (err || Track_ended(&sgc_emu)) { track++; if (track >= sgc_emu.track_count) break; goto next_track; diff --git a/apps/codecs/vgm.c b/apps/codecs/vgm.c index 9e4f88779c..d7390291e1 100644 --- a/apps/codecs/vgm.c +++ b/apps/codecs/vgm.c @@ -130,7 +130,7 @@ enum codec_status codec_run(void) /* Generate audio buffer */ err = Vgm_play(&vgm_emu, CHUNK_SIZE, samples); - if (err || vgm_emu.track_ended) break; + if (err || Track_ended(&vgm_emu)) break; ci->pcmbuf_insert(samples, NULL, CHUNK_SIZE >> 1); -- cgit v1.2.3