// Game_Music_Emu $vers. http://www.slack.net/~ant/ #include "Vgm_Emu.h" #include "blargg_endian.h" #include "blargg_common.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" // FM emulators are internally quieter to avoid 16-bit overflow double const fm_gain = 3.0; double const rolloff = 0.990; double const oversample_factor = 1.5; Vgm_Emu::Vgm_Emu() { muted_voices = 0; set_type( gme_vgm_type ); set_max_initial_silence( 1 ); set_silence_lookahead( 1 ); // tracks should already be trimmed voice_names_assigned_ = false; } Vgm_Emu::~Vgm_Emu() { // XXX ugly use of deprecated functions to free allocated voice names const char ** voice_names_ = voice_names(); if (voice_names_assigned_ && voice_names_) { for (int i = 0; i < 32; ++i) { if (voice_names_[i]) core.free_voice_name((char*)voice_names_[i]); else break; } free((void *)voice_names_); } } void Vgm_Emu::unload() { core.unload(); } // 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 = (int)((mid - in) / 2) - 1; if ( len > 0 ) { char * in_utf8 = blargg_to_utf8( (blargg_wchar_t *) in ); len = min( len, (int) Gme_File::max_field_ ); field [len] = 0; for ( int i = 0; i < len; i++ ) field [i] = in_utf8 [i]; free(in_utf8); } return mid; } static byte const* get_gd3_pair( byte const* in, byte const* end, char field [], char field_j [] ) { return get_gd3_str( get_gd3_str( in, end, field ), end, field_j ); } static void parse_gd3( byte const in [], byte const* end, track_info_t* out, track_info_t* out_j ) { in = get_gd3_pair( in, end, out->song , out_j->song ); in = get_gd3_pair( in, end, out->game , out_j->game ); in = get_gd3_pair( in, end, out->system , out_j->system ); in = get_gd3_pair( in, end, out->author , out_j->author ); in = get_gd3_str ( in, end, out->copyright ); in = get_gd3_pair( in, end, out->dumper , out_j->dumper ); in = get_gd3_str ( in, end, out->comment ); } static blargg_err_t write_gd3_str( gme_writer_t writer, void* your_data, const char field [] ) { blargg_wchar_t * wstring = blargg_to_wide( field ); if (!wstring) return "Out of memory"; blargg_err_t err = writer( your_data, wstring, blargg_wcslen( wstring ) * 2 + 2 ); free( wstring ); return err; } static blargg_err_t write_gd3_pair( gme_writer_t writer, void* your_data, const char field [], const char field_j [] ) { RETURN_ERR(write_gd3_str( writer, your_data, field )); RETURN_ERR(write_gd3_str( writer, your_data, field )); return blargg_ok; } static blargg_err_t write_gd3_strings( gme_writer_t writer, void* your_data, const track_info_t* in, const track_info_t* in_j ) { RETURN_ERR(write_gd3_pair( writer, your_data, in->song , in_j->song )); RETURN_ERR(write_gd3_pair( writer, your_data, in->game , in_j->game )); RETURN_ERR(write_gd3_pair( writer, your_data, in->system , in_j->system )); RETURN_ERR(write_gd3_pair( writer, your_data, in->author , in_j->author )); RETURN_ERR(write_gd3_str ( writer, your_data, in->copyright )); RETURN_ERR(write_gd3_pair( writer, your_data, in->dumper , in_j->dumper )); RETURN_ERR(write_gd3_str ( writer, your_data, in->comment )); return blargg_ok; } static gme_err_t writer_calc_size(void* param, const void* ptr, long count) { *(long *)param += count; return blargg_ok; } static blargg_err_t write_gd3( gme_writer_t writer, void* your_data, const track_info_t* in, const track_info_t* in_j ) { long string_size = 0; byte version[4]; RETURN_ERR(writer( your_data, "Gd3 ", 4 )); set_le32(version, 0x100); RETURN_ERR(writer( your_data, version, 4 )); write_gd3_strings( &writer_calc_size, &string_size, in, in_j ); if ( string_size > 1000000000 ) return "GD3 tag too large"; set_le32(version, (int)string_size); RETURN_ERR(writer( your_data, version, 4)); return write_gd3_strings( writer, your_data, in, in_j ); } int const gd3_header_size = 12; 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; int gd3_size = get_le32( h + 8 ); if ( gd3_size > remain - gd3_header_size ) return 0; return gd3_size; } static void get_vgm_length( Vgm_Emu::header_t const& h, track_info_t* out ) { int length = h.lngTotalSamples * 10 / 441; // 1000 / 44100 if ( length > 0 ) { int loop = h.lngLoopSamples; if ( loop > 0 && h.lngLoopOffset ) { out->length = 0; out->loop_length = loop * 10 / 441; out->intro_length = length - out->loop_length; check( out->loop_length <= length ); // TODO: Also set out->length? We now have play_length for suggested play time. } else { out->length = length; out->intro_length = length; out->loop_length = 0; } } } blargg_err_t Vgm_Emu::track_info_( track_info_t* out, int ) const { *out = metadata; return blargg_ok; } blargg_err_t Vgm_Emu::gd3_data( const unsigned char ** data, int * size ) { *data = 0; *size = 0; int gd3_offset = header().lngGD3Offset; if ( gd3_offset <= 0 ) return blargg_ok; byte const* gd3 = core.file_begin() + gd3_offset; int gd3_size = check_gd3_header( gd3, (int)(core.file_end() - gd3) ); if ( gd3_size ) { *data = gd3; *size = gd3_size + gd3_header_size; } return blargg_ok; } static void hash_vgm_file( Vgm_Emu::header_t const& h, byte const* data, int data_size, Music_Emu::Hash_Function& out ) { out.hash_( (const byte *) &h.lngEOFOffset, sizeof(h.lngEOFOffset) ); out.hash_( (const byte *) &h.lngVersion, sizeof(h.lngVersion) ); out.hash_( (const byte *) &h.lngHzPSG, sizeof(h.lngHzPSG) ); out.hash_( (const byte *) &h.lngHzYM2413, sizeof(h.lngHzYM2413) ); out.hash_( (const byte *) &h.lngTotalSamples, sizeof(h.lngTotalSamples) ); out.hash_( (const byte *) &h.lngLoopOffset, sizeof(h.lngLoopOffset) ); out.hash_( (const byte *) &h.lngLoopSamples, sizeof(h.lngLoopSamples) ); out.hash_( (const byte *) &h.lngRate, sizeof(h.lngRate) ); out.hash_( (const byte *) &h.shtPSG_Feedback, sizeof(h.shtPSG_Feedback) ); out.hash_( (const byte *) &h.bytPSG_SRWidth, sizeof(h.bytPSG_SRWidth) ); out.hash_( (const byte *) &h.bytPSG_Flags, sizeof(h.bytPSG_Flags) ); out.hash_( (const byte *) &h.lngHzYM2612, sizeof(h.lngHzYM2612) ); out.hash_( (const byte *) &h.lngHzYM2151, sizeof(h.lngHzYM2151) ); out.hash_( (const byte *) &h.lngDataOffset, sizeof(h.lngDataOffset) ); out.hash_( (const byte *) &h.lngHzSPCM, sizeof(h.lngHzSPCM) ); out.hash_( (const byte *) &h.lngSPCMIntf, sizeof(h.lngSPCMIntf) ); out.hash_( (const byte *) &h.lngHzRF5C68, sizeof(h.lngHzRF5C68) ); out.hash_( (const byte *) &h.lngHzYM2203, sizeof(h.lngHzYM2203) ); out.hash_( (const byte *) &h.lngHzYM2608, sizeof(h.lngHzYM2608) ); out.hash_( (const byte *) &h.lngHzYM2610, sizeof(h.lngHzYM2610) ); out.hash_( (const byte *) &h.lngHzYM3812, sizeof(h.lngHzYM3812) ); out.hash_( (const byte *) &h.lngHzYM3526, sizeof(h.lngHzYM3526) ); out.hash_( (const byte *) &h.lngHzY8950, sizeof(h.lngHzY8950) ); out.hash_( (const byte *) &h.lngHzYMF262, sizeof(h.lngHzYMF262) ); out.hash_( (const byte *) &h.lngHzYMF278B, sizeof(h.lngHzYMF278B) ); out.hash_( (const byte *) &h.lngHzYMF271, sizeof(h.lngHzYMF271) ); out.hash_( (const byte *) &h.lngHzYMZ280B, sizeof(h.lngHzYMZ280B) ); out.hash_( (const byte *) &h.lngHzRF5C164, sizeof(h.lngHzRF5C164) ); out.hash_( (const byte *) &h.lngHzPWM, sizeof(h.lngHzPWM) ); out.hash_( (const byte *) &h.lngHzAY8910, sizeof(h.lngHzAY8910) ); out.hash_( (const byte *) &h.bytAYType, sizeof(h.bytAYType) ); out.hash_( (const byte *) &h.bytAYFlag, sizeof(h.bytAYFlag) ); out.hash_( (const byte *) &h.bytAYFlagYM2203, sizeof(h.bytAYFlagYM2203) ); out.hash_( (const byte *) &h.bytAYFlagYM2608, sizeof(h.bytAYFlagYM2608) ); out.hash_( (const byte *) &h.bytReserved2, sizeof(h.bytReserved2) ); out.hash_( (const byte *) &h.lngHzGBDMG, sizeof(h.lngHzGBDMG) ); out.hash_( (const byte *) &h.lngHzNESAPU, sizeof(h.lngHzNESAPU) ); out.hash_( (const byte *) &h.lngHzMultiPCM, sizeof(h.lngHzMultiPCM) ); out.hash_( (const byte *) &h.lngHzUPD7759, sizeof(h.lngHzUPD7759) ); out.hash_( (const byte *) &h.lngHzOKIM6258, sizeof(h.lngHzOKIM6258) ); out.hash_( (const byte *) &h.bytOKI6258Flags, sizeof(h.bytOKI6258Flags) ); out.hash_( (const byte *) &h.bytK054539Flags, sizeof(h.bytK054539Flags) ); out.hash_( (const byte *) &h.bytC140Type, sizeof(h.bytC140Type) ); out.hash_( (const byte *) &h.bytReservedFlags, sizeof(h.bytReservedFlags) ); out.hash_( (const byte *) &h.lngHzOKIM6295, sizeof(h.lngHzOKIM6295) ); out.hash_( (const byte *) &h.lngHzK051649, sizeof(h.lngHzK051649) ); out.hash_( (const byte *) &h.lngHzK054539, sizeof(h.lngHzK054539) ); out.hash_( (const byte *) &h.lngHzHuC6280, sizeof(h.lngHzHuC6280) ); out.hash_( (const byte *) &h.lngHzC140, sizeof(h.lngHzC140) ); out.hash_( (const byte *) &h.lngHzK053260, sizeof(h.lngHzK053260) ); out.hash_( (const byte *) &h.lngHzPokey, sizeof(h.lngHzPokey) ); out.hash_( (const byte *) &h.lngHzQSound, sizeof(h.lngHzQSound) ); out.hash_( (const byte *) &h.lngHzSCSP, sizeof(h.lngHzSCSP) ); // out.hash_( (const byte *) &h.lngExtraOffset, sizeof(h.lngExtraOffset) ); out.hash_( (const byte *) &h.lngHzWSwan, sizeof(h.lngHzWSwan) ); out.hash_( (const byte *) &h.lngHzVSU, sizeof(h.lngHzVSU) ); out.hash_( (const byte *) &h.lngHzSAA1099, sizeof(h.lngHzSAA1099) ); out.hash_( (const byte *) &h.lngHzES5503, sizeof(h.lngHzES5503) ); out.hash_( (const byte *) &h.lngHzES5506, sizeof(h.lngHzES5506) ); out.hash_( (const byte *) &h.bytES5503Chns, sizeof(h.bytES5503Chns) ); out.hash_( (const byte *) &h.bytES5506Chns, sizeof(h.bytES5506Chns) ); out.hash_( (const byte *) &h.bytC352ClkDiv, sizeof(h.bytC352ClkDiv) ); out.hash_( (const byte *) &h.bytESReserved, sizeof(h.bytESReserved) ); out.hash_( (const byte *) &h.lngHzX1_010, sizeof(h.lngHzX1_010) ); out.hash_( (const byte *) &h.lngHzC352, sizeof(h.lngHzC352) ); out.hash_( (const byte *) &h.lngHzGA20, sizeof(h.lngHzGA20) ); out.hash_( data, data_size ); } struct VGM_FILE_mem { VGM_FILE vf; const BOOST::uint8_t* buffer; UINT32 ptr; UINT32 size; }; static int VGMF_mem_Read(VGM_FILE* f, void* out, UINT32 count) { VGM_FILE_mem* mf = (VGM_FILE_mem *) f; if (count + mf->ptr > mf->size) count = mf->size - mf->ptr; memcpy(out, mf->buffer + mf->ptr, count); mf->ptr += count; return count; } static int VGMF_mem_Seek(VGM_FILE* f, UINT32 offset) { VGM_FILE_mem* mf = (VGM_FILE_mem *) f; if (offset > mf->size) offset = mf->size; mf->ptr = offset; return 0; } static UINT32 VGMF_mem_GetSize(VGM_FILE* f) { VGM_FILE_mem* mf = (VGM_FILE_mem *) f; return mf->size; } static UINT32 VGMF_mem_Tell(VGM_FILE* f) { VGM_FILE_mem* mf = (VGM_FILE_mem *) f; return mf->ptr; } struct Vgm_File : Gme_Info_ { Vgm_Emu::header_t h; blargg_vector original_header; blargg_vector data; blargg_vector gd3; track_info_t metadata; track_info_t metadata_j; Vgm_File() { set_type( gme_vgm_type ); } blargg_err_t load_mem_( const byte* in, int file_size ) { VGM_FILE_mem memFile; memFile.vf.Read = &VGMF_mem_Read; memFile.vf.Seek = &VGMF_mem_Seek; memFile.vf.GetSize = &VGMF_mem_GetSize; memFile.vf.Tell = &VGMF_mem_Tell; memFile.buffer = in; memFile.ptr = 0; memFile.size = file_size; if (!GetVGMFileInfo_Handle((VGM_FILE *) &memFile, &h, 0)) return blargg_err_file_type; int data_offset = get_le32( &h.lngDataOffset ); int data_size = file_size - data_offset; int gd3_offset = get_le32( &h.lngGD3Offset ); if ( gd3_offset > 0 && gd3_offset > data_offset ) { data_size = gd3_offset - data_offset; RETURN_ERR( data.resize( data_size ) ); memcpy( data.begin(), in + data_offset, data_size ); } int remain = file_size - gd3_offset; byte gd3_h [gd3_header_size]; if ( gd3_offset > 0 && remain >= gd3_header_size ) { memcpy( gd3_h, in + gd3_offset, sizeof gd3_h ); int gd3_size = check_gd3_header( gd3_h, remain ); if ( gd3_size ) { RETURN_ERR( gd3.resize( gd3_size ) ); memcpy( gd3.begin(), in + sizeof gd3_h + gd3_offset, gd3.size() ); } if ( data_offset > gd3_offset ) { RETURN_ERR( data.resize( data_size ) ); memcpy( data.begin(), in + data_offset, data_size ); } } int header_size = data_offset; if ( gd3_offset && data_offset > gd3_offset ) header_size = gd3_offset; RETURN_ERR( original_header.resize( header_size ) ); memcpy( original_header.begin(), in, header_size ); memset( &metadata, 0, sizeof(metadata) ); memset( &metadata_j, 0, sizeof(metadata_j) ); get_vgm_length( h, &metadata ); if ( gd3.size() ) parse_gd3( gd3.begin(), gd3.end(), &metadata, &metadata_j ); return blargg_ok; } blargg_err_t track_info_( track_info_t* out, int ) const { *out = metadata; return blargg_ok; } blargg_err_t hash_( Hash_Function& out ) const { hash_vgm_file( h, data.begin(), (int)(data.end() - data.begin()), out ); return blargg_ok; } blargg_err_t set_track_info_( const track_info_t* in, int ) { metadata = *in; return blargg_ok; } blargg_err_t save_( gme_writer_t writer, void* your_data ) const { byte buffer[4]; int data_size = (int)(data.end() - data.begin()); int gd3_offset = (int)(original_header.end() - original_header.begin()) + data_size; RETURN_ERR( writer( your_data, original_header.begin(), 0x14 ) ); set_le32(buffer, gd3_offset - 0x14); RETURN_ERR( writer( your_data, buffer, 4 ) ); RETURN_ERR( writer( your_data, original_header.begin() + 0x18, original_header.end() - original_header.begin() - 0x18 ) ); RETURN_ERR( writer( your_data, data.begin(), data_size ) ); return write_gd3( writer, your_data, &metadata, &metadata_j ); } }; 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", 0 }}; gme_type_t_ const gme_vgz_type [1] = {{ "Sega SMS/Genesis", 1, &new_vgm_emu, &new_vgm_file, "VGZ", 0 }}; // Setup void Vgm_Emu::set_tempo_( double t ) { core.set_tempo( t ); } blargg_err_t Vgm_Emu::set_sample_rate_( int sample_rate ) { core.set_sample_rate(sample_rate); return blargg_ok; } void Vgm_Emu::mute_voices_( int mask ) { muted_voices = mask; core.set_mute(mask); } // Emulation blargg_err_t Vgm_Emu::start_track_( int track ) { core.start_track(); mute_voices_(muted_voices); return blargg_ok; } inline void Vgm_Emu::check_end() { if ( core.track_ended() ) set_track_ended(); } blargg_err_t Vgm_Emu::play_( int count, sample_t out [] ) { core.play_(count, out); check_end(); return blargg_ok; } blargg_err_t Vgm_Emu::hash_( Hash_Function& out ) const { byte const* p = file_begin(); byte const* e = file_end(); int data_offset = header().lngDataOffset; if ( data_offset ) p += data_offset; int gd3_offset = header().lngGD3Offset; if ( gd3_offset > 0 && gd3_offset > data_offset ) e = file_begin() + gd3_offset; hash_vgm_file( header(), p, (int)(e - p), out ); return blargg_ok; } blargg_err_t Vgm_Emu::load_mem_( const byte* in, int file_size ) { RETURN_ERR( core.load_mem(in, file_size) ); int voice_count = core.get_channel_count(); set_voice_count( voice_count ); char ** voice_names = (char **) calloc( sizeof(char *), voice_count + 1 ); if (voice_names) { int i; for (i = 0; i < voice_count; i++) { voice_names[i] = core.get_voice_name(i); if (!voice_names[i]) break; } if (i == voice_count) { set_voice_names(voice_names); voice_names_assigned_ = true; } else { for (i = 0; i < voice_count; i++) { if (voice_names[i]) free(voice_names[i]); } free(voice_names); } } get_vgm_length( header(), &metadata ); int data_offset = header().lngDataOffset; int gd3_offset = header().lngGD3Offset; int data_size = file_size - data_offset; if (gd3_offset > 0) { if (gd3_offset > data_offset) data_size = gd3_offset - data_offset; byte const* gd3 = core.file_begin() + gd3_offset; int gd3_size = check_gd3_header( gd3, (int)(core.file_end() - gd3) ); if ( gd3_size ) { byte const* gd3_data = gd3 + gd3_header_size; parse_gd3( gd3_data, gd3_data + gd3_size, &metadata, &metadata_j ); } } int header_size = data_offset; if ( gd3_offset && data_offset > gd3_offset ) header_size = gd3_offset; RETURN_ERR( original_header.resize( header_size ) ); memcpy( original_header.begin(), in, header_size ); RETURN_ERR( data.resize(data_size) ); memcpy( data.begin(), in + data_offset, data_size ); return blargg_ok; } blargg_err_t Vgm_Emu::skip_( int count ) { core.skip_(count); return blargg_ok; } blargg_err_t Vgm_Emu::set_track_info_( const track_info_t* in, int ) { metadata = *in; return blargg_ok; } blargg_err_t Vgm_Emu::save_(gme_writer_t writer, void* your_data) { byte buffer[4]; int data_size = (int)(data.end() - data.begin()); int gd3_offset = (int)(original_header.end() - original_header.begin()) + data_size; RETURN_ERR( writer( your_data, original_header.begin(), 0x14 ) ); set_le32(buffer, gd3_offset - 0x14); RETURN_ERR( writer( your_data, buffer, 4 ) ); RETURN_ERR( writer( your_data, original_header.begin() + 0x18, original_header.end() - original_header.begin() - 0x18 ) ); RETURN_ERR( writer( your_data, data.begin(), data_size ) ); return write_gd3( writer, your_data, &metadata, &metadata_j ); }