// Game_Music_Emu 0.5.2. http://www.slack.net/~ant/ #include "Vgm_Emu.h" #include "blargg_endian.h" #include #include /* 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 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" double const fm_gain = 3.0; // FM emulators are internally quieter to avoid 16-bit overflow double const rolloff = 0.990; double const oversample_factor = 1.5; Vgm_Emu::Vgm_Emu() { disable_oversampling_ = false; psg_rate = 0; set_type( gme_vgm_type ); static int const types [8] = { wave_type | 1, wave_type | 0, wave_type | 2, noise_type | 0 }; set_voice_types( types ); set_silence_lookahead( 1 ); // tracks should already be trimmed static equalizer_t const eq = { -14.0, 80 }; set_equalizer( eq ); } Vgm_Emu::~Vgm_Emu() { } // Track info static byte const* skip_gd3_str( byte const* in, byte const* end ) { while ( end - in >= 2 ) { in += 2; if ( !(in [-2] | in [-1]) ) break; } return in; } static byte const* get_gd3_str( byte const* in, byte const* end, char* field ) { byte const* mid = skip_gd3_str( in, end ); int len = (mid - in) / 2 - 1; if ( len > 0 ) { len = min( len, (int) Gme_File::max_field_ ); field [len] = 0; for ( int i = 0; i < len; i++ ) field [i] = (in [i * 2 + 1] ? '?' : in [i * 2]); // TODO: convert to utf-8 } return mid; } static byte const* get_gd3_pair( byte const* in, byte const* end, char* field ) { return skip_gd3_str( get_gd3_str( in, end, field ), end ); } static void parse_gd3( byte const* in, byte const* end, track_info_t* out ) { in = get_gd3_pair( in, end, out->song ); in = get_gd3_pair( in, end, out->game ); in = get_gd3_pair( in, end, out->system ); in = get_gd3_pair( in, end, out->author ); in = get_gd3_str ( in, end, out->copyright ); in = get_gd3_pair( in, end, out->dumper ); in = get_gd3_str ( in, end, out->comment ); } int const gd3_header_size = 12; static long check_gd3_header( byte const* h, long 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 ); if ( gd3_size > remain - gd3_header_size ) return 0; return gd3_size; } byte const* Vgm_Emu::gd3_data( int* size ) const { if ( size ) *size = 0; long gd3_offset = get_le32( header().gd3_offset ) - 0x2C; if ( gd3_offset < 0 ) return 0; byte const* gd3 = data + header_size + gd3_offset; long gd3_size = check_gd3_header( gd3, data_end - gd3 ); if ( !gd3_size ) return 0; if ( size ) *size = gd3_size + gd3_header_size; return gd3; } static void get_vgm_length( Vgm_Emu::header_t const& h, track_info_t* out ) { long length = get_le32( h.track_duration ) * 10 / 441; if ( length > 0 ) { long loop = get_le32( h.loop_duration ); if ( loop > 0 && get_le32( h.loop_offset ) ) { out->loop_length = loop * 10 / 441; out->intro_length = length - out->loop_length; } else { out->length = length; // 1000 / 44100 (VGM files used 44100 as timebase) out->intro_length = length; // make it clear that track is no longer than length out->loop_length = 0; } } } blargg_err_t Vgm_Emu::track_info_( track_info_t* out, int ) const { get_vgm_length( header(), out ); int size; byte const* gd3 = gd3_data( &size ); if ( gd3 ) parse_gd3( gd3 + gd3_header_size, gd3 + size, out ); return 0; } static blargg_err_t check_vgm_header( Vgm_Emu::header_t const& h ) { if ( memcmp( h.tag, "Vgm ", 4 ) ) return gme_wrong_file_type; return 0; } struct Vgm_File : Gme_Info_ { Vgm_Emu::header_t h; blargg_vector gd3; Vgm_File() { set_type( gme_vgm_type ); } blargg_err_t load_( Data_Reader& in ) { long file_size = in.remain(); if ( file_size <= Vgm_Emu::header_size ) return gme_wrong_file_type; RETURN_ERR( in.read( &h, Vgm_Emu::header_size ) ); RETURN_ERR( check_vgm_header( h ) ); long gd3_offset = get_le32( h.gd3_offset ) - 0x2C; long remain = file_size - Vgm_Emu::header_size - gd3_offset; byte gd3_h [gd3_header_size]; if ( gd3_offset > 0 && remain >= gd3_header_size ) { RETURN_ERR( in.skip( gd3_offset ) ); RETURN_ERR( in.read( gd3_h, sizeof gd3_h ) ); long gd3_size = check_gd3_header( gd3_h, remain ); if ( gd3_size ) { RETURN_ERR( gd3.resize( gd3_size ) ); RETURN_ERR( in.read( gd3.begin(), gd3.size() ) ); } } return 0; } blargg_err_t track_info_( track_info_t* out, int ) const { get_vgm_length( h, out ); if ( gd3.size() ) parse_gd3( gd3.begin(), gd3.end(), out ); return 0; } }; static Music_Emu* new_vgm_emu () { return BLARGG_NEW Vgm_Emu ; } static Music_Emu* new_vgm_file() { return BLARGG_NEW Vgm_File; } gme_type_t_ const gme_vgm_type [1] = { "Sega SMS/Genesis", 1, &new_vgm_emu, &new_vgm_file, "VGM", 1 }; gme_type_t_ const gme_vgz_type [1] = { "Sega SMS/Genesis", 1, &new_vgm_emu, &new_vgm_file, "VGZ", 1 }; // Setup void Vgm_Emu::set_tempo_( double t ) { if ( psg_rate ) { vgm_rate = (long) (44100 * t + 0.5); blip_time_factor = (long) floor( double (1L << blip_time_bits) / vgm_rate * psg_rate + 0.5 ); //dprintf( "blip_time_factor: %ld\n", blip_time_factor ); //dprintf( "vgm_rate: %ld\n", vgm_rate ); // TODO: remove? calculates vgm_rate more accurately (above differs at most by one Hz only) //blip_time_factor = (long) floor( double (1L << blip_time_bits) * psg_rate / 44100 / t + 0.5 ); //vgm_rate = (long) floor( double (1L << blip_time_bits) * psg_rate / blip_time_factor + 0.5 ); fm_time_factor = 2 + (long) floor( fm_rate * (1L << fm_time_bits) / vgm_rate + 0.5 ); } } blargg_err_t Vgm_Emu::set_sample_rate_( long sample_rate ) { RETURN_ERR( blip_buf.set_sample_rate( sample_rate, 1000 / 30 ) ); return Classic_Emu::set_sample_rate_( sample_rate ); } void Vgm_Emu::update_eq( blip_eq_t const& eq ) { psg.treble_eq( eq ); dac_synth.treble_eq( eq ); } void Vgm_Emu::set_voice( int i, Blip_Buffer* c, Blip_Buffer* l, Blip_Buffer* r ) { if ( i < psg.osc_count ) psg.osc_output( i, c, l, r ); } void Vgm_Emu::mute_voices_( int mask ) { Classic_Emu::mute_voices_( mask ); dac_synth.output( &blip_buf ); if ( uses_fm ) { psg.output( (mask & 0x80) ? 0 : &blip_buf ); if ( ym2612.enabled() ) { dac_synth.volume( (mask & 0x40) ? 0.0 : 0.1115 / 256 * fm_gain * gain() ); ym2612.mute_voices( mask ); } if ( ym2413.enabled() ) { int m = mask & 0x3F; if ( mask & 0x20 ) m |= 0x01E0; // channels 5-8 if ( mask & 0x40 ) m |= 0x3E00; ym2413.mute_voices( m ); } } } blargg_err_t Vgm_Emu::load_mem_( byte const* new_data, long new_size ) { assert( offsetof (header_t,unused2 [8]) == header_size ); if ( new_size <= header_size ) return gme_wrong_file_type; header_t const& h = *(header_t const*) new_data; RETURN_ERR( check_vgm_header( h ) ); check( get_le32( h.version ) <= 0x150 ); // psg rate psg_rate = get_le32( h.psg_rate ); if ( !psg_rate ) psg_rate = 3579545; blip_buf.clock_rate( psg_rate ); data = new_data; data_end = new_data + new_size; // get loop loop_begin = data_end; if ( get_le32( h.loop_offset ) ) loop_begin = &data [get_le32( h.loop_offset ) + offsetof (header_t,loop_offset)]; set_voice_count( psg.osc_count ); RETURN_ERR( setup_fm() ); static const char* const fm_names [] = { "FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "PCM", "PSG" }; static const char* const psg_names [] = { "Square 1", "Square 2", "Square 3", "Noise" }; set_voice_names( uses_fm ? fm_names : psg_names ); // do after FM in case output buffer is changed return Classic_Emu::setup_buffer( psg_rate ); } blargg_err_t Vgm_Emu::setup_fm() { long ym2612_rate = get_le32( header().ym2612_rate ); long ym2413_rate = get_le32( header().ym2413_rate ); if ( ym2413_rate && get_le32( header().version ) < 0x110 ) update_fm_rates( &ym2413_rate, &ym2612_rate ); uses_fm = false; fm_rate = blip_buf.sample_rate() * oversample_factor; if ( ym2612_rate ) { uses_fm = true; if ( disable_oversampling_ ) fm_rate = ym2612_rate / 144.0; Dual_Resampler::setup( fm_rate / blip_buf.sample_rate(), rolloff, fm_gain * gain() ); RETURN_ERR( ym2612.set_rate( fm_rate, ym2612_rate ) ); ym2612.enable( true ); set_voice_count( 8 ); } if ( !uses_fm && ym2413_rate ) { uses_fm = true; if ( disable_oversampling_ ) fm_rate = ym2413_rate / 72.0; Dual_Resampler::setup( fm_rate / blip_buf.sample_rate(), rolloff, fm_gain * gain() ); int result = ym2413.set_rate( fm_rate, ym2413_rate ); if ( result == 2 ) return "YM2413 FM sound isn't supported"; CHECK_ALLOC( !result ); ym2413.enable( true ); set_voice_count( 8 ); } if ( uses_fm ) { RETURN_ERR( Dual_Resampler::reset( blip_buf.length() * blip_buf.sample_rate() / 1000 ) ); psg.volume( 0.135 * fm_gain * gain() ); } else { ym2612.enable( false ); ym2413.enable( false ); psg.volume( gain() ); } return 0; } // Emulation blargg_err_t Vgm_Emu::start_track_( int track ) { RETURN_ERR( Classic_Emu::start_track_( track ) ); psg.reset( get_le16( header().noise_feedback ), header().noise_width ); dac_disabled = -1; pos = data + header_size; pcm_data = pos; pcm_pos = pos; dac_amp = -1; vgm_time = 0; if ( get_le32( header().version ) >= 0x150 ) { long data_offset = get_le32( header().data_offset ); check( data_offset ); if ( data_offset ) pos += data_offset + offsetof (header_t,data_offset) - 0x40; } if ( uses_fm ) { if ( ym2413.enabled() ) ym2413.reset(); if ( ym2612.enabled() ) ym2612.reset(); fm_time_offset = 0; blip_buf.clear(); Dual_Resampler::clear(); } return 0; } blargg_err_t Vgm_Emu::run_clocks( blip_time_t& time_io, int msec ) { time_io = run_commands( msec * vgm_rate / 1000 ); psg.end_frame( time_io ); return 0; } blargg_err_t Vgm_Emu::play_( long count, sample_t* out ) { if ( !uses_fm ) return Classic_Emu::play_( count, out ); Dual_Resampler::dual_play( count, out, blip_buf ); return 0; }