// Game_Music_Emu $vers. http://www.slack.net/~ant/ #include "Music_Emu.h" #if !GME_DISABLE_EFFECTS #include "Effects_Buffer.h" #endif #include "blargg_endian.h" #include #include /* Copyright (C) 2003-2009 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" #ifndef GME_TYPE_LIST // Default list of all supported game music types (copy this to blargg_config.h // if you want to modify it) #define GME_TYPE_LIST \ gme_ay_type,\ gme_gbs_type,\ gme_gym_type,\ gme_hes_type,\ gme_kss_type,\ gme_nsf_type,\ gme_nsfe_type,\ gme_sap_type,\ gme_sfm_type,\ gme_sgc_type,\ gme_spc_type,\ gme_vgm_type,\ gme_vgz_type #endif static gme_type_t const gme_type_list_ [] = { GME_TYPE_LIST, 0 }; gme_type_t const* gme_type_list() { return gme_type_list_; } const char* gme_identify_header( void const* header ) { switch ( get_be32( header ) ) { case BLARGG_4CHAR('Z','X','A','Y'): return "AY"; case BLARGG_4CHAR('G','B','S',0x01): return "GBS"; case BLARGG_4CHAR('G','Y','M','X'): return "GYM"; case BLARGG_4CHAR('H','E','S','M'): return "HES"; case BLARGG_4CHAR('K','S','C','C'): case BLARGG_4CHAR('K','S','S','X'): return "KSS"; case BLARGG_4CHAR('N','E','S','M'): return "NSF"; case BLARGG_4CHAR('N','S','F','E'): return "NSFE"; case BLARGG_4CHAR('S','A','P',0x0D): return "SAP"; case BLARGG_4CHAR('S','F','M','1'): return "SFM"; case BLARGG_4CHAR('S','G','C',0x1A): return "SGC"; case BLARGG_4CHAR('S','N','E','S'): return "SPC"; case BLARGG_4CHAR('V','g','m',' '): return "VGM"; } return ""; } static void to_uppercase( const char in [], int len, char out [] ) { for ( int i = 0; i < len; i++ ) { if ( !(out [i] = toupper( in [i] )) ) return; } *out = 0; // extension too long } gme_type_t gme_identify_extension( const char extension_ [] ) { char const* end = strrchr( extension_, '.' ); if ( end ) extension_ = end + 1; char extension [6]; to_uppercase( extension_, sizeof extension, extension ); gme_type_t const* types = gme_type_list_; for ( ; *types; types++ ) if ( !strcmp( extension, (*types)->extension_ ) ) break; return *types; } gme_err_t gme_identify_file( const char path [], gme_type_t* type_out ) { *type_out = gme_identify_extension( path ); // TODO: don't examine header if file has extension? if ( !*type_out ) { char header [4]; GME_FILE_READER in; RETURN_ERR( in.open( path ) ); RETURN_ERR( in.read( header, sizeof header ) ); *type_out = gme_identify_extension( gme_identify_header( header ) ); } return blargg_ok; } gme_err_t gme_open_data( void const* data, long size, Music_Emu** out, int sample_rate ) { require( (data || !size) && out ); *out = NULL; gme_type_t file_type = 0; if ( size >= 4 ) file_type = gme_identify_extension( gme_identify_header( data ) ); if ( !file_type ) return blargg_err_file_type; Music_Emu* emu = gme_new_emu( file_type, sample_rate ); CHECK_ALLOC( emu ); gme_err_t err = gme_load_data( emu, data, size ); if ( err ) delete emu; else *out = emu; return err; } gme_err_t gme_open_file( const char path [], Music_Emu** out, int sample_rate ) { require( path && out ); *out = NULL; GME_FILE_READER in; RETURN_ERR( in.open( path ) ); char header [4]; int header_size = 0; gme_type_t file_type = gme_identify_extension( path ); if ( !file_type ) { header_size = sizeof header; RETURN_ERR( in.read( header, sizeof header ) ); file_type = gme_identify_extension( gme_identify_header( header ) ); } if ( !file_type ) return blargg_err_file_type; Music_Emu* emu = gme_new_emu( file_type, sample_rate ); CHECK_ALLOC( emu ); // optimization: avoids seeking/re-reading header Remaining_Reader rem( header, header_size, &in ); gme_err_t err = emu->load( rem ); in.close(); if ( err ) delete emu; else *out = emu; return err; } Music_Emu* gme_new_emu( gme_type_t type, int rate ) { if ( type ) { if ( rate == gme_info_only ) return type->new_info(); Music_Emu* gme = type->new_emu(); if ( gme ) { #if !GME_DISABLE_EFFECTS if ( type->flags_ & 1 ) { gme->effects_buffer_ = BLARGG_NEW Simple_Effects_Buffer; if ( gme->effects_buffer_ ) gme->set_buffer( gme->effects_buffer_ ); } if ( !(type->flags_ & 1) || gme->effects_buffer_ ) #endif { if ( !gme->set_sample_rate( rate ) ) { check( gme->type() == type ); return gme; } } delete gme; } } return NULL; } gme_err_t gme_load_file( Music_Emu* gme, const char path [] ) { return gme->load_file( path ); } gme_err_t gme_load_data( Music_Emu* gme, void const* data, long size ) { Mem_File_Reader in( data, size ); return gme->load( in ); } gme_err_t gme_load_custom( Music_Emu* gme, gme_reader_t func, long size, void* data ) { Callback_Reader in( func, size, data ); return gme->load( in ); } void gme_delete( Music_Emu* gme ) { delete gme; } gme_type_t gme_type( Music_Emu const* gme ) { return gme->type(); } const char* gme_type_system( gme_type_t_ const* type ) { return type->system; } const char* gme_warning( Music_Emu* gme ) { return gme->warning(); } int gme_track_count( Music_Emu const* gme ) { return gme->track_count(); } struct gme_info_t_ : gme_info_t { track_info_t info; BLARGG_DISABLE_NOTHROW }; gme_err_t gme_track_info( Music_Emu const* me, gme_info_t** out, int track ) { *out = NULL; gme_info_t_* info = BLARGG_NEW gme_info_t_; CHECK_ALLOC( info ); gme_err_t err = me->track_info( &info->info, track ); if ( err ) { gme_free_info( info ); return err; } #define COPY(name) info->name = info->info.name; COPY( length ); COPY( intro_length ); COPY( loop_length ); info->i4 = -1; info->i5 = -1; info->i6 = -1; info->i7 = -1; info->i8 = -1; info->i9 = -1; info->i10 = -1; info->i11 = -1; info->i12 = -1; info->i13 = -1; info->i14 = -1; info->i15 = -1; info->s7 = ""; info->s8 = ""; info->s9 = ""; info->s10 = ""; info->s11 = ""; info->s12 = ""; info->s13 = ""; info->s14 = ""; info->s15 = ""; COPY( system ); COPY( game ); COPY( song ); COPY( author ); COPY( copyright ); COPY( comment ); COPY( dumper ); #undef COPY info->play_length = info->length; if ( info->play_length <= 0 ) { info->play_length = info->intro_length + 2 * info->loop_length; // intro + 2 loops if ( info->play_length <= 0 ) info->play_length = 150 * 1000; // 2.5 minutes } *out = info; return blargg_ok; } gme_err_t gme_set_track_info( Music_Emu * me, gme_info_t* in, int track ) { track_info_t* info = BLARGG_NEW track_info_t; CHECK_ALLOC( info ); #define COPY(name) info->name = in->name; COPY( length ); COPY( intro_length ); COPY( loop_length ); #undef COPY #define COPY(name) if ( in->name ) strncpy( info->name, in->name, sizeof(info->name) - 1 ), info->name[sizeof(info->name)-1] = '\0'; else info->name[0] = '\0'; COPY( system ); COPY( game ); COPY( song ); COPY( author ); COPY( copyright ); COPY( comment ); COPY( dumper ); #undef COPY blargg_err_t err = me->set_track_info( info, track ); delete info; return err; } void gme_free_info( gme_info_t* info ) { delete STATIC_CAST(gme_info_t_*,info); } void* gme_user_data ( Music_Emu const* gme ) { return gme->user_data(); } void gme_set_user_data ( Music_Emu* gme, void* new_user_data ) { gme->set_user_data( new_user_data ); } void gme_set_user_cleanup(Music_Emu* gme, gme_user_cleanup_t func ){ gme->set_user_cleanup( func ); } gme_err_t gme_start_track ( Music_Emu* gme, int index ) { return gme->start_track( index ); } gme_err_t gme_play ( Music_Emu* gme, int n, short p [] ) { return gme->play( n, p ); } void gme_set_fade ( Music_Emu* gme, int start_msec, int length_msec ) { gme->set_fade( start_msec, length_msec ); } gme_bool gme_track_ended ( Music_Emu const* gme ) { return gme->track_ended(); } int gme_tell ( Music_Emu const* gme ) { return gme->tell(); } gme_err_t gme_seek ( Music_Emu* gme, int msec ) { return gme->seek( msec ); } gme_err_t gme_skip ( Music_Emu* gme, int samples ) { return gme->skip( samples ); } int gme_voice_count ( Music_Emu const* gme ) { return gme->voice_count(); } void gme_ignore_silence ( Music_Emu* gme, gme_bool disable ) { gme->ignore_silence( disable != 0 ); } void gme_set_tempo ( Music_Emu* gme, double t ) { gme->set_tempo( t ); } void gme_mute_voice ( Music_Emu* gme, int index, gme_bool mute ){ gme->mute_voice( index, mute != 0 ); } void gme_mute_voices ( Music_Emu* gme, int mask ) { gme->mute_voices( mask ); } void gme_set_equalizer ( Music_Emu* gme, gme_equalizer_t const* eq ) { gme->set_equalizer( *eq ); } void gme_equalizer ( Music_Emu const* gme, gme_equalizer_t* o ) { *o = gme->equalizer(); } const char* gme_voice_name ( Music_Emu const* gme, int i ) { return gme->voice_name( i ); } gme_err_t gme_save ( Music_Emu const* gme, gme_writer_t writer, void* your_data ) { return gme->save( writer, your_data ); } void gme_effects( Music_Emu const* gme, gme_effects_t* out ) { static gme_effects_t const zero = { 0, 0, 0,0,0,0,0,0, 0, 0, 0,0,0,0,0,0 }; *out = zero; #if !GME_DISABLE_EFFECTS { Simple_Effects_Buffer* b = STATIC_CAST(Simple_Effects_Buffer*,gme->effects_buffer_); if ( b ) { out->enabled = b->config().enabled; out->echo = b->config().echo; out->stereo = b->config().stereo; out->surround = b->config().surround; } } #endif } void gme_set_effects( Music_Emu* gme, gme_effects_t const* in ) { #if !GME_DISABLE_EFFECTS { Simple_Effects_Buffer* b = STATIC_CAST(Simple_Effects_Buffer*,gme->effects_buffer_); if ( b ) { b->config().enabled = false; if ( in ) { b->config().enabled = in->enabled; b->config().echo = in->echo; b->config().stereo = in->stereo; b->config().surround = in->surround; } b->apply_config(); } } #endif } void gme_set_stereo_depth( Music_Emu* gme, double depth ) { #if !GME_DISABLE_EFFECTS { if ( gme->effects_buffer_ ) { gme_effects_t cfg; gme_effects( gme, &cfg ); cfg.enabled = (depth > 0.0); cfg.echo = depth; cfg.stereo = depth; cfg.surround = true; gme_set_effects( gme, &cfg ); } } #endif } #define ENTRY( name ) { blargg_err_##name, gme_err_##name } static blargg_err_to_code_t const gme_codes [] = { ENTRY( generic ), ENTRY( memory ), ENTRY( caller ), ENTRY( internal ), ENTRY( limitation ), ENTRY( file_missing ), ENTRY( file_read ), ENTRY( file_io ), ENTRY( file_eof ), ENTRY( file_type ), ENTRY( file_feature ), ENTRY( file_corrupt ), { 0, -1 } }; #undef ENTRY static int err_code( gme_err_t err ) { return blargg_err_to_code( err, gme_codes ); } int gme_err_code( gme_err_t err ) { int code = err_code( err ); return (code >= 0 ? code : gme_err_generic); } gme_err_t gme_code_to_err( int code ) { return blargg_code_to_err( code, gme_codes ); } const char* gme_err_details( gme_err_t err ) { // If we don't have error code assigned, return entire string return (err_code( err ) >= 0 ? blargg_err_details( err ) : blargg_err_str( err )); } const char* gme_err_str( gme_err_t err ) { return blargg_err_str( err ); }