2013-09-28 03:24:23 +00:00
|
|
|
// 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 <string.h>
|
|
|
|
#include <ctype.h>
|
|
|
|
|
|
|
|
/* 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_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;
|
|
|
|
}
|
|
|
|
|
|
|
|
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 ); }
|
2013-10-04 20:33:14 +00:00
|
|
|
void gme_set_fade ( Music_Emu* gme, int start_msec, int length_msec ) { gme->set_fade( start_msec, length_msec ); }
|
2013-09-28 03:24:23 +00:00
|
|
|
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 ); }
|
|
|
|
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 ); }
|
2013-10-26 16:00:59 +00:00
|
|
|
gme_err_t gme_save ( Music_Emu const* gme, gme_writer_t writer, void* your_data ) { return gme->save( writer, your_data ); }
|
2013-10-04 20:33:14 +00:00
|
|
|
|
|
|
|
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
|
2013-09-28 03:24:23 +00:00
|
|
|
{
|
|
|
|
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 );
|
|
|
|
}
|