Mercurial > audlegacy-plugins
changeset 316:fb513e10174e trunk
[svn] - merge libconsole-blargg into mainline libconsole:
+ obsoletes plugins-ugly:sapplug
line wrap: on
line diff
--- a/ChangeLog Wed Nov 29 14:42:11 2006 -0800 +++ b/ChangeLog Thu Nov 30 19:54:33 2006 -0800 @@ -1,3 +1,12 @@ +2006-11-29 22:42:11 +0000 Tony Vroon <chainsaw@gentoo.org> + revision [690] + Metronom (a.k.a. tact generator) input plugin, ported from XMMS. + trunk/configure.ac | 3 + trunk/src/metronom/Makefile | 14 + + trunk/src/metronom/metronom.c | 296 ++++++++++++++++++++++++++++++++++++++++++ + 3 files changed, 312 insertions(+), 1 deletion(-) + + 2006-11-28 20:09:34 +0000 William Pitcock <nenolod@nenolod.net> revision [688] - MirBSD support from <bsiegert -at gmx.de>
--- a/src/console/Audacious_Config.cxx Wed Nov 29 14:42:11 2006 -0800 +++ b/src/console/Audacious_Config.cxx Thu Nov 30 19:54:33 2006 -0800 @@ -14,6 +14,7 @@ #include "audacious/configdb.h" #include "Audacious_Config.h" +// TODO: add UI for echo void console_cfg_load( void ) { @@ -25,6 +26,7 @@ bmp_cfg_db_get_int(db, "console", "treble", &audcfg.treble); bmp_cfg_db_get_int(db, "console", "bass", &audcfg.bass); bmp_cfg_db_get_bool(db, "console", "ignore_spc_length", &audcfg.ignore_spc_length); + bmp_cfg_db_get_int(db, "console", "echo", &audcfg.echo); bmp_cfg_db_close(db); } @@ -39,6 +41,7 @@ bmp_cfg_db_set_int(db, "console", "treble", audcfg.treble); bmp_cfg_db_set_int(db, "console", "bass", audcfg.bass); bmp_cfg_db_set_bool(db, "console", "ignore_spc_length", audcfg.ignore_spc_length); + bmp_cfg_db_set_int(db, "console", "echo", audcfg.echo); bmp_cfg_db_close(db); }
--- a/src/console/Audacious_Config.h Wed Nov 29 14:42:11 2006 -0800 +++ b/src/console/Audacious_Config.h Thu Nov 30 19:54:33 2006 -0800 @@ -17,10 +17,11 @@ gint loop_length; // length of tracks that lack timing information gboolean resample; // whether or not to resample gint resample_rate; // rate to resample at - gboolean nsfe_playlist; // if true, use optional NSFE playlist + gboolean nsfe_playlist; // TODO: remove (nsfe playlist is now always enabled) gint treble; // -100 to +100 gint bass; // -100 to +100 gboolean ignore_spc_length; // if true, ignore length from SPC tags + gint echo; // 0 to +100 } AudaciousConsoleConfig;
--- a/src/console/Audacious_Driver.cxx Wed Nov 29 14:42:11 2006 -0800 +++ b/src/console/Audacious_Driver.cxx Thu Nov 30 19:54:33 2006 -0800 @@ -1,6 +1,6 @@ /* * Audacious: Cross platform multimedia player - * Copyright (c) 2005 Audacious Team + * Copyright (c) 2005-2006 Audacious Team * * Driver for Game_Music_Emu library. See details at: * http://www.slack.net/~ant/libs/ @@ -17,537 +17,189 @@ } #include <string.h> #include <stdlib.h> -#include <ctype.h> #include <math.h> // configdb and prefs ui #include "Audacious_Config.h" -// Game_Music_Emu -#include "Nsf_Emu.h" -#include "Nsfe_Emu.h" -#include "Gbs_Emu.h" -#include "Vgm_Emu.h" -#include "Gym_Emu.h" -#include "Spc_Emu.h" +#include "Music_Emu.h" +#include "Vfs_File.h" -#include "Track_Emu.h" -#include "Vfs_File.h" -#include "Gzip_File.h" -#include "blargg_endian.h" +int const fade_threshold = 10 * 1000; +int const fade_length = 8 * 1000; -//typedef Vfs_File_Reader Audacious_Reader; // will use VFS once it handles gzip transparently -typedef Gzip_File_Reader Audacious_Reader; - -AudaciousConsoleConfig audcfg = { 180, FALSE, 32000, TRUE, 0, 0, FALSE }; +AudaciousConsoleConfig audcfg = { 180, FALSE, 32000, TRUE, 0, 0, FALSE, 0 }; static GThread* decode_thread; static GStaticMutex playback_mutex = G_STATIC_MUTEX_INIT; static int console_ip_is_going; static volatile long pending_seek; extern InputPlugin console_ip; static Music_Emu* emu = 0; -static Track_Emu track_emu; static int track_ended; +static blargg_err_t log_err( blargg_err_t err ) +{ + if ( err ) + printf( "console error: %s\n", err ); + return err; +} + +static void log_warning( Music_Emu* emu ) +{ + const char* w = emu->warning(); + if ( w ) + printf( "console warning: %s\n", w ); +} + static void unload_file() { - delete emu; + if ( emu ) + log_warning( emu ); + gme_delete( emu ); emu = NULL; } -// Information - -typedef unsigned char byte; - -#define DUPE_FIELD( field ) g_strndup( field, sizeof (field) ); +// Extracts track number from file path, also frees memory at end of block -struct track_info_t +struct Url_Parser { - int track; // track to get info for - int length; // in msec, -1 = unknown - int loop; // in msec, -1 = unknown, 0 = not looped - int intro; // in msec, -1 = unknown - - TitleInput* ti; + gchar* path; // path without track number specification + int track; // track number (0 = first track) + bool track_specified; // false if no track number was specified in path + Url_Parser( gchar* path ); + ~Url_Parser() { g_free( path ); } }; -// NSFE - -void get_nsfe_info( Nsfe_Info const& nsfe, track_info_t* out ) -{ - Nsfe_Info::info_t const& h = nsfe.info(); - out->ti->performer = DUPE_FIELD( h.author ); - out->ti->album_name = DUPE_FIELD( h.game ); - out->ti->comment = DUPE_FIELD( h.copyright ); - out->ti->track_name = g_strdup( nsfe.track_name( out->track ) ); - int time = nsfe.track_time( out->track ); - if ( time > 0 ) - out->length = time; - if ( nsfe.info().track_count > 1 ) - out->ti->track_number = out->track + 1; -} - -inline void get_info_emu( Nsfe_Emu& emu, track_info_t* out ) -{ - emu.enable_playlist( audcfg.nsfe_playlist ); // to do: kind of hacky - get_nsfe_info( emu, out ); -} - -inline void get_file_info( Nsfe_Emu::header_t const& h, Data_Reader& in, track_info_t* out ) -{ - Nsfe_Info nsfe; - if ( !nsfe.load( h, in ) ) - { - nsfe.enable_playlist( audcfg.nsfe_playlist ); - get_nsfe_info( nsfe, out ); - } -} - -// NSF - -static void get_nsf_info_( Nsf_Emu::header_t const& h, track_info_t* out ) +Url_Parser::Url_Parser( gchar* path_in ) { - out->ti->performer = DUPE_FIELD( h.author ); - out->ti->album_name = DUPE_FIELD( h.game ); - out->ti->comment = DUPE_FIELD( h.copyright ); - if ( h.track_count > 1 ) - out->ti->track_number = out->track + 1; -} - -inline void get_info_emu( Nsf_Emu& emu, track_info_t* out ) -{ - get_nsf_info_( emu.header(), out ); -} - -inline void get_file_info( Nsf_Emu::header_t const& h, Data_Reader& in, track_info_t* out ) -{ - get_nsf_info_( h, out ); -} - -// GBS - -static void get_gbs_info_( Gbs_Emu::header_t const& h, track_info_t* out ) -{ - out->ti->performer = DUPE_FIELD( h.author ); - out->ti->album_name = DUPE_FIELD( h.game ); - out->ti->comment = DUPE_FIELD( h.copyright ); - if ( h.track_count > 1 ) - out->ti->track_number = out->track + 1; -} - -inline void get_info_emu( Gbs_Emu& emu, track_info_t* out ) -{ - get_gbs_info_( emu.header(), out ); -} - -inline void get_file_info( Gbs_Emu::header_t const& h, Data_Reader& in, track_info_t* out ) -{ - get_gbs_info_( h, out ); -} - -// GYM - -static void get_gym_info_( Gym_Emu::header_t const& h, track_info_t* out ) -{ - if ( !memcmp( h.tag, "GYMX", 4 ) ) + track = 0; + track_specified = false; + + path = g_strdup( path_in ); + if ( path ) { - out->ti->performer = DUPE_FIELD( h.copyright ); - out->ti->album_name = DUPE_FIELD( h.game ); - out->ti->track_name = DUPE_FIELD( h.song ); - out->ti->comment = DUPE_FIELD( h.comment ); - } -} - -static void get_gym_timing_( Gym_Emu const& emu, track_info_t* out ) -{ - out->length = emu.track_length() * 50 / 3; // 1000 / 60 - out->loop = 0; - - long loop = get_le32( emu.header().loop_start ); - if ( loop ) - { - out->intro = loop * 50 / 3; - out->loop = out->length - out->intro; - out->length = -1; - } -} - -inline void get_info_emu( Gym_Emu& emu, track_info_t* out ) -{ - get_gym_info_( emu.header(), out ); - get_gym_timing_( emu, out ); -} - -inline void get_file_info( Gym_Emu::header_t const& h, Data_Reader& in, track_info_t* out ) -{ - get_gym_info_( h, out ); - - // have to load and parse entire GYM file to determine length - // to do: could make more efficient by manually parsing data (format is simple) - // rather than loading into emulator with its FM chips and resampler - Gym_Emu* emu = new Gym_Emu; - if ( emu && !emu->set_sample_rate( 44100 ) && !emu->load( h, in ) ) - get_gym_timing_( *emu, out ); - delete emu; -} - -// SPC - -static void get_spc_xid6( byte const* begin, long size, track_info_t* out ) -{ - // header - byte const* end = begin + size; - if ( size < 8 || memcmp( begin, "xid6", 4 ) ) - return; - long info_size = get_le32( begin + 4 ); - byte const* in = begin + 8; - if ( end - in > info_size ) - end = in + info_size; - - while ( end - in >= 4 ) - { - // header - int id = in [0]; - int data = in [3] * 0x100 + in [2]; - int type = in [1]; - int len = type ? data : 0; - in += 4; - if ( len > end - in ) - break; // block goes past end of data - - // handle specific block types - switch ( id ) + gchar* args = strchr( path, '?' ); + if ( args ) { - case 0x01: out->ti->track_name = g_strndup( (char*) in, len ); break; - case 0x02: out->ti->album_name = g_strndup( (char*) in, len ); break; - case 0x03: out->ti->performer = g_strndup( (char*) in, len ); break; - case 0x07: out->ti->comment = g_strndup( (char*) in, len ); break; - //case 0x31: // loop length, but I haven't found any SPC files that use this - } - - // skip to next block - in += len; - - // blocks are supposed to be 4-byte aligned with zero-padding... - byte const* unaligned = in; - while ( (in - begin) & 3 && in < end ) - { - if ( *in++ != 0 ) - { - // ...but some files have no padding - in = unaligned; - break; - } + *args = '\0'; + track = atoi( args + 1 ); + if ( track ) + track_specified = true; } } } -static void get_spc_info_( Spc_Emu::header_t const& h, byte const* xid6, long xid6_size, - track_info_t* out ) -{ - // decode length (can be in text or binary format) - char s [4] = { h.len_secs [0], h.len_secs [1], h.len_secs [2], 0 }; - int len_secs = (unsigned char) s [1] * 0x100 + s [0]; - if ( s [1] >= ' ' || (!s [1] && isdigit( s [0] )) ) - len_secs = atoi( s ); - if ( len_secs ) - out->length = len_secs * 1000; - - if ( xid6_size ) - get_spc_xid6( xid6, xid6_size, out ); - - // use header to fill any remaining fields - if ( !out->ti->performer ) out->ti->performer = DUPE_FIELD( h.author ); - if ( !out->ti->album_name ) out->ti->album_name = DUPE_FIELD( h.game ); - if ( !out->ti->track_name ) out->ti->track_name = DUPE_FIELD( h.song ); -} - -inline void get_info_emu( Spc_Emu& emu, track_info_t* out ) -{ - get_spc_info_( emu.header(), emu.trailer(), emu.trailer_size(), out ); -} - -inline void get_file_info( Spc_Emu::header_t const& h, Data_Reader& in, track_info_t* out ) +// Determine file type based on header contents. Returns 0 if unrecognized or path is NULL. +static gme_type_t identify_file( gchar* path ) { - // handle xid6 data at end of file - long const xid6_skip = 0x10200 - sizeof (Spc_Emu::header_t); - long xid6_size = in.remain() - xid6_skip; - blargg_vector<byte> xid6; - if ( xid6_size <= 0 || xid6.resize( xid6_size ) || in.skip( xid6_skip ) || - in.read( xid6.begin(), xid6.size() ) ) - xid6_size = 0; - - get_spc_info_( h, xid6.begin(), xid6_size, out ); -} - -// VGM - -static void get_gd3_str( byte const* in, byte const* end, gchar** out ) -{ - int len = (end - in) / 2 - 1; - if ( len > 0 ) + if ( path ) { - *out = g_strndup( "", len ); - if ( !*out ) - return; - for ( int i = 0; i < len; i++ ) - (*out) [i] = in [i * 2]; // to do: convert to utf-8 + char header [4] = { }; + GME_FILE_READER in; + if ( !log_err( in.open( path ) ) && !log_err( in.read( header, sizeof header ) ) ) + return gme_identify_extension( gme_identify_header( header ), gme_type_list() ); } -} - -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_pair( byte const* in, byte const* end, gchar** out ) -{ - byte const* mid = skip_gd3_str( in, end ); - if ( out ) - get_gd3_str( in, mid, out ); - return skip_gd3_str( mid, end ); -} - -static void get_vgm_gd3( byte const* in, long size, track_info_t* out ) -{ - byte const* end = in + size; - in = get_gd3_pair( in, end, &out->ti->track_name ); - in = get_gd3_pair( in, end, &out->ti->album_name ); - in = get_gd3_pair( in, end, 0 ); // system - in = get_gd3_pair( in, end, &out->ti->performer ); - in = get_gd3_pair( in, end, 0 ); // copyright - // ... other fields (release date, dumper, notes) + return 0; } -static void get_vgm_length( Vgm_Emu::header_t const& h, track_info_t* out ) -{ - long length = get_le32( h.track_duration ); - if ( length > 0 ) - { - out->length = length * 10 / 441; // 1000 / 44100 (VGM files used 44100 as timebase) - out->loop = 0; - - long loop = get_le32( h.loop_duration ); - if ( loop > 0 && get_le32( h.loop_offset ) ) - { - out->loop = loop * 10 / 441; - out->intro = out->length - out->loop; - out->length = -1; - } - } -} - -inline void get_info_emu( Vgm_Emu& emu, track_info_t* out ) +// Load file into emulator/info reader and load m3u in same directory, if present. +// If emu is NULL, returns out of memory error. +static blargg_err_t load_in_emu( Music_Emu* emu, const char* path, VFSFile* fd = 0 ) { - get_vgm_length( emu.header(), out ); + if ( !emu ) + return "Out of memory"; - int size; - byte const* data = emu.gd3_data( &size ); - if ( data ) - get_vgm_gd3( data + 12, size, out ); -} - -inline void get_file_info( Vgm_Emu::header_t const& vgm_h, Data_Reader& in, track_info_t* out ) -{ - get_vgm_length( vgm_h, out ); + Vfs_File_Reader in; + blargg_err_t err = 0; + if ( fd ) + in.reset( fd ); // use fd and let caller close it + else + err = in.open( path ); - // find gd3 header - long gd3_offset = get_le32( vgm_h.gd3_offset ) + offsetof(Vgm_Emu::header_t,gd3_offset) - - sizeof vgm_h; - long gd3_max_size = in.remain() - gd3_offset; - byte gd3_h [12]; - if ( gd3_offset <= 0 || gd3_max_size < (int) sizeof gd3_h ) - return; + if ( !err ) + emu->load( in ); + in.close(); - // read gd3 header - if ( in.skip( gd3_offset ) || in.read( gd3_h, sizeof gd3_h ) ) - return; - - // check header signature and version - if ( memcmp( gd3_h, "Gd3 ", 4 ) || get_le32( gd3_h + 4 ) >= 0x200 ) - return; - - // get and check size - long gd3_size = get_le32( gd3_h + 8 ); - if ( gd3_size > gd3_max_size - 12 ) - return; - - // read and parse gd3 data - blargg_vector<byte> gd3; - if ( gd3.resize( gd3_size ) || in.read( gd3.begin(), gd3.size() ) ) - return; + if ( !err ) + { + log_warning( emu ); + + // load .m3u in same directory + int const path_max = 4096; + char m3u_path [path_max + 5]; + strncpy( m3u_path, path, path_max ); + m3u_path [path_max] = 0; + char* p = strrchr( m3u_path, '.' ); + if ( !p ) + p = m3u_path + strlen( m3u_path ); + strcpy( p, ".m3u" ); + + if ( emu->load_m3u( m3u_path ) ) { } // TODO: log error if m3u file exists + } - get_vgm_gd3( gd3.begin(), gd3.size(), out ); -} - -// File identification - -enum { type_none = 0, type_spc, type_nsf, type_nsfe, type_vgm, type_gbs, type_gym }; - -int const tag_size = 4; -typedef char tag_t [tag_size]; - -static int identify_file( gchar* path, tag_t tag ) -{ - // GYM file format doesn't require *any* header, just the ".gym" extension - if ( g_str_has_suffix( path, ".gym" ) ) // to do: is pathname in unicode? - return type_gym; - // to do: trust suffix for all file types, avoiding having to look inside files? - - int result = type_none; - if ( !memcmp( tag, "SNES", 4 ) ) result = type_spc; - if ( !memcmp( tag, "NESM", 4 ) ) result = type_nsf; - if ( !memcmp( tag, "NSFE", 4 ) ) result = type_nsfe; - if ( !memcmp( tag, "GYMX", 4 ) ) result = type_gym; - if ( !memcmp( tag, "GBS" , 3 ) ) result = type_gbs; - if ( !memcmp( tag, "Vgm ", 4 ) ) result = type_vgm; - return result; + return err; } // Get info -static int begin_get_info( const char* path, track_info_t* out ) +static TitleInput* get_track_ti( const char* path, track_info_t const& info, int track ) { - out->track = 0; - out->length = -1; - out->loop = -1; - out->intro = -1; - TitleInput* fields = bmp_title_input_new(); - out->ti = fields; - if ( !fields ) - return true; - - fields->file_name = g_path_get_basename( path ); - fields->file_path = g_path_get_dirname( path ); - return false; + TitleInput* ti = bmp_title_input_new(); + if ( ti ) + { + ti->file_name = g_path_get_basename( path ); + ti->file_path = g_path_get_dirname ( path ); + ti->performer = g_strdup( info.author ); + ti->album_name = g_strdup( info.game ); + ti->track_name = g_strdup( info.song ? info.song : ti->file_name ); + if ( info.track_count > 1 ) + ti->track_number = track + 1; + ti->comment = g_strdup( info.copyright ); + + int length = info.length; + if ( length <= 0 ) + length = info.intro_length + 2 * info.loop_length; + if ( length <= 0 ) + length = audcfg.loop_length * 1000; + else if ( length >= fade_threshold ) + length += fade_length; + ti->length = length; + } + return ti; } -static char* end_get_info( track_info_t const& info, int* length, bool* has_length ) +static char* format_and_free_ti( TitleInput* ti, int* length ) { - *length = info.length; - if ( has_length ) - *has_length = (*length > 0); + char* result = xmms_get_titlestring( xmms_get_gentitle_format(), ti ); + if ( result ) + *length = ti->length; + bmp_title_input_free( ti ); - if ( *length <= 0 ) - *length = audcfg.loop_length * 1000; - - // use filename for formats that don't have field for name of game - // to do: strip off file extension - if ( !info.ti->track_name ) - info.ti->track_name = g_strdup( info.ti->file_name ); - - char* result = xmms_get_titlestring( xmms_get_gentitle_format(), info.ti ); - g_free( info.ti ); return result; } -template<class Header> -inline void get_info_t( tag_t tag, Data_Reader& in, track_info_t* out, Header* ) +static TitleInput *get_song_tuple( gchar *path ) { - Header h; - memcpy( &h, tag, tag_size ); - if ( !in.read( (char*) &h + tag_size, sizeof h - tag_size ) ) - get_file_info( h, in, out ); + TitleInput* result = 0; + + Url_Parser url( path ); + Music_Emu* emu = gme_new_info( identify_file( url.path ) ); + track_info_t info; + if ( !log_err( load_in_emu( emu, url.path ) ) && + !log_err( emu->track_info( &info, url.track ) ) ) + result = get_track_ti( url.path, info, url.track ); + delete emu; + return result; } static void get_song_info( char* path, char** title, int* length ) { - int track = 0; // to do: way to select other tracks - - // extract the subsong id from the virtual path - gchar *path2 = g_strdup(path); - gchar *_path = strchr(path2, '?'); - - if (_path != NULL && *_path == '?') - { - *_path = '\0'; - _path++; - track = atoi(_path); - } - *length = -1; *title = NULL; - Audacious_Reader in; - tag_t tag; - if ( in.open( path2 ) || in.read( tag, sizeof tag ) ) - return; - int type = identify_file( path2, tag ); - if ( !type ) - return; - - track_info_t info; - if ( begin_get_info( path2, &info ) ) - return; - info.track = track; - - switch ( type ) - { - case type_nsf: get_info_t( tag, in, &info, (Nsf_Emu::header_t*) 0 ); break; - case type_gbs: get_info_t( tag, in, &info, (Gbs_Emu::header_t*) 0 ); break; - case type_gym: get_info_t( tag, in, &info, (Gym_Emu::header_t*) 0 ); break; - case type_vgm: get_info_t( tag, in, &info, (Vgm_Emu::header_t*) 0 ); break; - case type_spc: get_info_t( tag, in, &info, (Spc_Emu::header_t*) 0 ); break; - case type_nsfe:get_info_t( tag, in, &info, (Nsfe_Emu::header_t*)0 ); break; - } - *title = end_get_info( info, length, 0 ); - - g_free(path2); -} - -// Get tuple - -static TitleInput *get_song_tuple( char *path ) -{ - int track = 0; // to do: way to select other tracks - - // extract the subsong id from the virtual path - gchar *path2 = g_strdup(path); - gchar *_path = strchr(path2, '?'); - - if (_path != NULL && *_path == '?') - { - *_path = '\0'; - _path++; - track = atoi(_path); - } - - Audacious_Reader in; - tag_t tag; - if ( in.open( path2 ) || in.read( tag, sizeof tag ) ) - return NULL; - - int type = identify_file( path2, tag ); - if ( !type ) - return NULL; - - track_info_t info; - if ( begin_get_info( path2, &info ) ) - return NULL; - info.track = track; - - switch ( type ) - { - case type_nsf: get_info_t( tag, in, &info, (Nsf_Emu::header_t*) 0 ); break; - case type_gbs: get_info_t( tag, in, &info, (Gbs_Emu::header_t*) 0 ); break; - case type_gym: get_info_t( tag, in, &info, (Gym_Emu::header_t*) 0 ); break; - case type_vgm: get_info_t( tag, in, &info, (Vgm_Emu::header_t*) 0 ); break; - case type_spc: get_info_t( tag, in, &info, (Spc_Emu::header_t*) 0 ); break; - case type_nsfe:get_info_t( tag, in, &info, (Nsfe_Emu::header_t*)0 ); break; - } - - info.ti->length = info.length; - - if ( info.ti->length <= 0 ) - info.ti->length = audcfg.loop_length * 1000; - - return info.ti; + TitleInput* ti = get_song_tuple( path ); + if ( ti ) + *title = format_and_free_ti( ti, length ); } // Playback @@ -563,23 +215,26 @@ // handle pending seek long s = pending_seek; - pending_seek = -1; // to do: use atomic swap + pending_seek = -1; // TODO: use atomic swap if ( s >= 0 ) { console_ip.output->flush( s * 1000 ); - track_emu.seek( s * 1000 ); + emu->seek( s * 1000 ); } // fill buffer if ( track_ended ) { - if ( track_ended++ > emu->sample_rate() * 3 / (buf_size / 2) ) + // TODO: remove delay once host doesn't cut the end of track off + int const delay = 0; // seconds + if ( track_ended++ > delay * emu->sample_rate() / (buf_size / 2) ) console_ip_is_going = false; memset( buf, 0, sizeof buf ); } - else if ( track_emu.play( buf_size, buf ) ) + else { - track_ended = 1; + emu->play( buf_size, buf ); + track_ended = emu->track_ended(); } produce_audio( console_ip.output->written_time(), FMT_S16_NE, 1, sizeof buf, buf, @@ -591,105 +246,48 @@ console_ip.output->close_audio(); console_ip_is_going = 0; g_static_mutex_unlock( &playback_mutex ); - // to do: should decode_thread be cleared here? + // TODO: should decode_thread be cleared here? g_thread_exit( NULL ); return NULL; } -template<class Emu> -void load_file( tag_t tag, Data_Reader& in, long rate, track_info_t* out, Emu* dummy ) -{ - typename Emu::header_t h; - memcpy( &h, tag, tag_size ); - if ( in.read( (char*) &h + tag_size, sizeof h - tag_size ) ) - return; - - if ( rate == 0 ) - rate = 44100; - - Emu* local_emu = new Emu; - if ( !local_emu || local_emu->set_sample_rate( rate ) || local_emu->load( h, in ) ) - { - delete local_emu; // delete NULL is safe - return; - } - - emu = local_emu; - if (out != NULL) - get_info_emu( *local_emu, out ); -} - static void play_file( char* path ) { - int track = 0; // to do: some way to select other tracks - - // open and identify file unload_file(); - Audacious_Reader in; - tag_t tag; - - // extract the subsong id from the virtual path - gchar *path2 = g_strdup(path); - gchar *_path = strchr(path2, '?'); - - if (_path != NULL && *_path == '?') - { - *_path = '\0'; - _path++; - track = atoi(_path); - } - - if ( in.open( path2 ) || in.read( tag, sizeof tag ) ) - return; - int type = identify_file( path2, tag ); - // setup info - long sample_rate = 44100; - if ( type == type_spc ) - sample_rate = Spc_Emu::native_sample_rate; + // identify file + Url_Parser url( path ); + gme_type_t type = identify_file( url.path ); + if ( !type ) return; + + // sample rate + long sample_rate = 0; + if ( type == gme_spc_type ) + sample_rate = 32000; if ( audcfg.resample ) sample_rate = audcfg.resample_rate; - track_info_t info; - if ( begin_get_info( path2, &info ) ) - return; - info.track = track; - - // load in emulator and get info - switch ( type ) + if ( !sample_rate ) + sample_rate = 44100; + + // create emulator and load + emu = gme_new_emu( type, sample_rate ); + if ( load_in_emu( emu, url.path ) ) { - case type_nsf: load_file( tag, in, sample_rate, &info, (Nsf_Emu*) 0 ); break; - case type_nsfe:load_file( tag, in, sample_rate, &info, (Nsfe_Emu*)0 ); break; - case type_gbs: load_file( tag, in, sample_rate, &info, (Gbs_Emu*) 0 ); break; - case type_gym: load_file( tag, in, sample_rate, &info, (Gym_Emu*) 0 ); break; - case type_vgm: load_file( tag, in, sample_rate, &info, (Vgm_Emu*) 0 ); break; - case type_spc: load_file( tag, in, sample_rate, &info, (Spc_Emu*) 0 ); break; - } - in.close(); - if ( !emu ) + unload_file(); return; - - // set info - int length = -1; - bool has_length = false; - - if (( type == type_spc ) && ( audcfg.ignore_spc_length == TRUE )) - info.length = -1; - - char* title = end_get_info( info, &length, &has_length ); - if ( title ) - { - console_ip.set_info( title, length, emu->voice_count() * 1000, sample_rate, 2 ); - g_free( title ); } - // set frequency equalization + // stereo echo depth + gme_set_stereo_depth( emu, 1.0 / 100 * audcfg.echo ); + + // set equalizer if ( audcfg.treble || audcfg.bass ) { - Music_Emu::equalizer_t eq = emu->equalizer(); + Music_Emu::equalizer_t eq; // bass - logarithmic, 2 to 8194 Hz double bass = 1.0 - (audcfg.bass / 200.0 + 0.5); - eq.bass = (long int) pow(2.0, bass * 13.0) + (long int) 2.0; + eq.bass = (long) (2.0 + pow( 2.0, bass * 13 )); // treble - -50 to 0 to +5 dB double treble = audcfg.treble / 100.0; @@ -698,21 +296,50 @@ emu->set_equalizer(eq); } - // start + // get info + int length = -1; + track_info_t info; + if ( !log_err( emu->track_info( &info, url.track ) ) ) + { + if ( type == gme_spc_type && audcfg.ignore_spc_length ) + info.length = -1; + TitleInput* ti = get_track_ti( url.path, info, url.track ); + if ( ti ) + { + char* title = format_and_free_ti( ti, &length ); + if ( title ) + { + console_ip.set_info( title, length, emu->voice_count() * 1000, sample_rate, 2 ); + g_free( title ); + } + } + } + if ( length <= 0 ) + length = audcfg.loop_length * 1000; + + if ( log_err( emu->start_track( url.track ) ) ) + { + unload_file(); + return; + } + log_warning( emu ); + + // start track if ( !console_ip.output->open_audio( FMT_S16_NE, sample_rate, 2 ) ) return; pending_seek = -1; track_ended = 0; - track_emu.start_track( emu, track, length, !has_length ); + if ( length >= fade_threshold + fade_length ) + length -= fade_length; + emu->set_fade( length, fade_length ); console_ip_is_going = 1; decode_thread = g_thread_create( play_loop_track, NULL, TRUE, NULL ); - g_free(path2); } static void seek( gint time ) { - // to do: be sure seek works at all - // to do: disallow seek on slow formats (SPC, GYM, VGM using FM)? + // TODO: be sure seek works at all + // TODO: disallow seek on slow formats (SPC, GYM, VGM using FM)? pending_seek = time; } @@ -738,63 +365,67 @@ return console_ip_is_going ? console_ip.output->output_time() : -1; } -static gint is_our_file( gchar* path ) +static gint is_our_file_from_vfs( gchar* filename, VFSFile* fd ) { - Audacious_Reader in; - tag_t tag; - - // extract the subsong id from the virtual path - gchar *path2 = g_strdup(path); - gchar *_path = strchr(path2, '?'); - gboolean is_subsong = FALSE; - gint type; - - if (_path != NULL && *_path == '?') + Url_Parser url( filename ); + if ( !url.path ) return false; + + // open file if not already open + Vfs_File_Reader in; + if ( !fd ) + { + if ( log_err( in.open( url.path ) ) ) return false; + fd = in.file(); + // in will be closed when function ends + } + + // read header and identify type + gchar header [4] = { }; + vfs_fread( header, sizeof header, 1, fd ); + gme_type_t type = gme_identify_extension( gme_identify_header( header ), gme_type_list() ); + + gint result = 0; + if ( type ) { - *_path = '\0'; - _path++; - is_subsong = TRUE; - } - - gint ret = !in.open( path2 ) && !in.read( tag, sizeof tag ) && (type = identify_file( path2, tag )); - - if (ret == TRUE && is_subsong == FALSE) - { - if (type == type_spc || type == type_gym || type == type_vgm) + if ( url.track_specified || type->track_count == 1 ) + { + // don't even need to read file if track is specified or + // that file format can't have more than one track per file + result = 1; + } + else { - g_free(path2); - return ret; - } + // format requires reading file info to get track count + Music_Emu* emu = gme_new_info( type ); + vfs_rewind( fd ); + if ( !log_err( load_in_emu( emu, url.path, fd ) ) ) + { + if ( emu->track_count() == 1 ) + { + result = 1; + } + else + { + // for multi-track types, add each track to playlist + for (int i = 0; i < emu->track_count(); i++) + { + gchar _buf[4096]; + g_snprintf(_buf, 4096, "%s?%d", url.path, i); - switch ( type ) - { - case type_nsf: load_file( tag, in, 0, NULL, (Nsf_Emu*) 0 ); break; - case type_nsfe:load_file( tag, in, 0, NULL, (Nsfe_Emu*)0 ); break; - case type_gbs: load_file( tag, in, 0, NULL, (Gbs_Emu*) 0 ); break; - case type_gym: load_file( tag, in, 0, NULL, (Gym_Emu*) 0 ); break; - case type_vgm: load_file( tag, in, 0, NULL, (Vgm_Emu*) 0 ); break; - case type_spc: load_file( tag, in, 0, NULL, (Spc_Emu*) 0 ); break; - default: return FALSE; + playlist_add_url(_buf); + } + result = -1; + } + } + delete emu; } - - if (emu == NULL) - return FALSE; + } + return result; +} - for (int i = 0; i < emu->track_count(); i++) - { - gchar _buf[65535]; - g_snprintf(_buf, 65535, "%s?%d", path2, i); - - playlist_add_url(_buf); - } - - ret = -1; - - unload_file(); - } - - g_free(path2); - return ret; +static gint is_our_file( gchar* filename ) +{ + return is_our_file_from_vfs( filename, 0 ); } // Setup @@ -811,10 +442,9 @@ if (!aboutbox) { aboutbox = xmms_show_message(_("About the Console Music Decoder"), - _("Console music decoder engine based on Game_Music_Emu 0.3.0.\n" + _("Console music decoder engine based on Game_Music_Emu 0.5.1.\n" "Audacious implementation by: William Pitcock <nenolod@nenolod.net>, \n" - // Please do not put my hotpop.com address in the clear (I hate spam) - " Shay Green <hotpop.com@blargg>"), + " Shay Green <gblargg@gmail.com>"), _("Ok"), FALSE, NULL, NULL); gtk_signal_connect(GTK_OBJECT(aboutbox), "destroy", @@ -849,11 +479,13 @@ NULL, NULL, get_song_tuple, + NULL, + NULL, + is_our_file_from_vfs }; extern "C" InputPlugin *get_iplugin_info(void) { - console_ip.description = g_strdup_printf(_("SPC, VGM, NSF/NSFE, GBS, and GYM module decoder")); + console_ip.description = g_strdup_printf(_("AY, GBS, GYM, HES, KSS, NSF, NSFE, SAP, SPC, VGM, VGZ module decoder")); return &console_ip; } -
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/console/Ay_Apu.cxx Thu Nov 30 19:54:33 2006 -0800 @@ -0,0 +1,391 @@ +// Game_Music_Emu 0.5.1. http://www.slack.net/~ant/ + +#include "Ay_Apu.h" + +/* Copyright (C) 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" + +// Emulation inaccuracies: +// * Noise isn't run when not in use +// * Changes to envelope and noise periods are delayed until next reload +// * Super-sonic tone should attenuate output to about 60%, not 50% + +// Tones above this frequency are treated as disabled tone at half volume. +// Power of two is more efficient (avoids division). +unsigned const inaudible_freq = 16384; + +int const period_factor = 16; + +static byte const amp_table [16] = +{ +#define ENTRY( n ) byte (n * Ay_Apu::amp_range + 0.5) + // With channels tied together and 1K resistor to ground (as datasheet recommends), + // output nearly matches logarithmic curve as claimed. Approx. 1.5 dB per step. + ENTRY(0.000000),ENTRY(0.007813),ENTRY(0.011049),ENTRY(0.015625), + ENTRY(0.022097),ENTRY(0.031250),ENTRY(0.044194),ENTRY(0.062500), + ENTRY(0.088388),ENTRY(0.125000),ENTRY(0.176777),ENTRY(0.250000), + ENTRY(0.353553),ENTRY(0.500000),ENTRY(0.707107),ENTRY(1.000000), + + /* + // Measured from an AY-3-8910A chip with date code 8611. + + // Direct voltages without any load (very linear) + ENTRY(0.000000),ENTRY(0.046237),ENTRY(0.064516),ENTRY(0.089785), + ENTRY(0.124731),ENTRY(0.173118),ENTRY(0.225806),ENTRY(0.329032), + ENTRY(0.360215),ENTRY(0.494624),ENTRY(0.594624),ENTRY(0.672043), + ENTRY(0.766129),ENTRY(0.841935),ENTRY(0.926882),ENTRY(1.000000), + // With only some load + ENTRY(0.000000),ENTRY(0.011940),ENTRY(0.017413),ENTRY(0.024876), + ENTRY(0.036318),ENTRY(0.054229),ENTRY(0.072637),ENTRY(0.122388), + ENTRY(0.174129),ENTRY(0.239303),ENTRY(0.323881),ENTRY(0.410945), + ENTRY(0.527363),ENTRY(0.651741),ENTRY(0.832338),ENTRY(1.000000), + */ +#undef ENTRY +}; + +static byte const modes [8] = +{ +#define MODE( a0,a1, b0,b1, c0,c1 ) \ + (a0 | a1<<1 | b0<<2 | b1<<3 | c0<<4 | c1<<5) + MODE( 1,0, 1,0, 1,0 ), + MODE( 1,0, 0,0, 0,0 ), + MODE( 1,0, 0,1, 1,0 ), + MODE( 1,0, 1,1, 1,1 ), + MODE( 0,1, 0,1, 0,1 ), + MODE( 0,1, 1,1, 1,1 ), + MODE( 0,1, 1,0, 0,1 ), + MODE( 0,1, 0,0, 0,0 ), +}; + +Ay_Apu::Ay_Apu() +{ + // build full table of the upper 8 envelope waveforms + for ( int m = 8; m--; ) + { + byte* out = env.modes [m]; + int flags = modes [m]; + for ( int n = 3; --n >= 0; ) + { + int amp = flags & 1; + int end = flags >> 1 & 1; + int step = end - amp; + amp *= 15; + for ( int n = 16; --n >= 0; ) + { + *out++ = amp_table [amp]; + amp += step; + } + flags >>= 2; + } + } + + output( 0 ); + volume( 1.0 ); + reset(); +} + +void Ay_Apu::reset() +{ + last_time = 0; + noise.delay = 0; + noise.lfsr = 1; + + osc_t* osc = &oscs [osc_count]; + do + { + osc--; + osc->period = period_factor; + osc->delay = 0; + osc->last_amp = 0; + osc->phase = 0; + } + while ( osc != oscs ); + + for ( int i = sizeof regs; --i >= 0; ) + regs [i] = 0; + regs [7] = 0xFF; + write_data_( 13, 0 ); +} + +void Ay_Apu::write_data_( int addr, int data ) +{ + if ( (unsigned) addr >= 14 ) + { + #ifdef dprintf + dprintf( "Wrote to I/O port %02X\n", (int) addr ); + #endif + } + + // envelope mode + if ( addr == 13 ) + { + if ( !(data & 8) ) // convert modes 0-7 to proper equivalents + data = (data & 4) ? 15 : 9; + env.wave = env.modes [data - 7]; + env.pos = -48; + env.delay = 0; // will get set to envelope period in run_until() + } + regs [addr] = data; + + // handle period changes accurately + int i = addr >> 1; + if ( i < osc_count ) + { + blip_time_t period = (regs [i * 2 + 1] & 0x0F) * (0x100L * period_factor) + + regs [i * 2] * period_factor; + if ( !period ) + period = period_factor; + + // adjust time of next timer expiration based on change in period + osc_t& osc = oscs [i]; + if ( (osc.delay += period - osc.period) < 0 ) + osc.delay = 0; + osc.period = period; + } + + // TODO: same as above for envelope timer, and it also has a divide by two after it +} + +int const noise_off = 0x08; +int const tone_off = 0x01; + +void Ay_Apu::run_until( blip_time_t final_end_time ) +{ + require( final_end_time >= last_time ); + + // noise period and initial values + blip_time_t const noise_period_factor = period_factor * 2; // verified + blip_time_t noise_period = (regs [6] & 0x1F) * noise_period_factor; + if ( !noise_period ) + noise_period = noise_period_factor; + blip_time_t const old_noise_delay = noise.delay; + blargg_ulong const old_noise_lfsr = noise.lfsr; + + // envelope period + blip_time_t const env_period_factor = period_factor * 2; // verified + blip_time_t env_period = (regs [12] * 0x100L + regs [11]) * env_period_factor; + if ( !env_period ) + env_period = env_period_factor; // same as period 1 on my AY chip + if ( !env.delay ) + env.delay = env_period; + + // run each osc separately + for ( int index = 0; index < osc_count; index++ ) + { + osc_t* const osc = &oscs [index]; + int osc_mode = regs [7] >> index; + + // output + Blip_Buffer* const osc_output = osc->output; + if ( !osc_output ) + continue; + osc_output->set_modified(); + + // period + int half_vol = 0; + blip_time_t inaudible_period = (blargg_ulong) (osc_output->clock_rate() + + inaudible_freq) / (inaudible_freq * 2); + if ( osc->period <= inaudible_period && !(osc_mode & tone_off) ) + { + half_vol = 1; // Actually around 60%, but 50% is close enough + osc_mode |= tone_off; + } + + // envelope + blip_time_t start_time = last_time; + blip_time_t end_time = final_end_time; + int const vol_mode = regs [0x08 + index]; + int volume = amp_table [vol_mode & 0x0F] >> half_vol; + int osc_env_pos = env.pos; + if ( vol_mode & 0x10 ) + { + volume = env.wave [osc_env_pos] >> half_vol; + // use envelope only if it's a repeating wave or a ramp that hasn't finished + if ( !(regs [13] & 1) || osc_env_pos < -32 ) + { + end_time = start_time + env.delay; + if ( end_time >= final_end_time ) + end_time = final_end_time; + + //if ( !(regs [12] | regs [11]) ) + // dprintf( "Used envelope period 0\n" ); + } + else if ( !volume ) + { + osc_mode = noise_off | tone_off; + } + } + else if ( !volume ) + { + osc_mode = noise_off | tone_off; + } + + // tone time + blip_time_t const period = osc->period; + blip_time_t time = start_time + osc->delay; + if ( osc_mode & tone_off ) // maintain tone's phase when off + { + blargg_long count = (final_end_time - time + period - 1) / period; + time += count * period; + osc->phase ^= count & 1; + } + + // noise time + blip_time_t ntime = final_end_time; + blargg_ulong noise_lfsr = 1; + if ( !(osc_mode & noise_off) ) + { + ntime = start_time + old_noise_delay; + noise_lfsr = old_noise_lfsr; + //if ( (regs [6] & 0x1F) == 0 ) + // dprintf( "Used noise period 0\n" ); + } + + // The following efficiently handles several cases (least demanding first): + // * Tone, noise, and envelope disabled, where channel acts as 4-bit DAC + // * Just tone or just noise, envelope disabled + // * Envelope controlling tone and/or noise + // * Tone and noise disabled, envelope enabled with high frequency + // * Tone and noise together + // * Tone and noise together with envelope + + // This loop only runs one iteration if envelope is disabled. If envelope + // is being used as a waveform (tone and noise disabled), this loop will + // still be reasonably efficient since the bulk of it will be skipped. + while ( 1 ) + { + // current amplitude + int amp = 0; + if ( (osc_mode | osc->phase) & 1 & (osc_mode >> 3 | noise_lfsr) ) + amp = volume; + int delta = amp - osc->last_amp; + if ( delta ) + { + osc->last_amp = amp; + synth_.offset( start_time, delta, osc_output ); + } + + // Run wave and noise interleved with each catching up to the other. + // If one or both are disabled, their "current time" will be past end time, + // so there will be no significant performance hit. + if ( ntime < end_time || time < end_time ) + { + // Since amplitude was updated above, delta will always be +/- volume, + // so we can avoid using last_amp every time to calculate the delta. + int delta = amp * 2 - volume; + int delta_non_zero = delta != 0; + int phase = osc->phase | (osc_mode & tone_off); assert( tone_off == 0x01 ); + do + { + // run noise + blip_time_t end = end_time; + if ( end_time > time ) end = time; + if ( phase & delta_non_zero ) + { + while ( ntime <= end ) // must advance *past* time to avoid hang + { + int changed = noise_lfsr + 1; + noise_lfsr = (-(noise_lfsr & 1) & 0x12000) ^ (noise_lfsr >> 1); + if ( changed & 2 ) + { + delta = -delta; + synth_.offset( ntime, delta, osc_output ); + } + ntime += noise_period; + } + } + else + { + // 20 or more noise periods on average for some music + blargg_long remain = end - ntime; + blargg_long count = remain / noise_period; + if ( remain >= 0 ) + ntime += noise_period + count * noise_period; + } + + // run tone + end = end_time; + if ( end_time > ntime ) end = ntime; + if ( noise_lfsr & delta_non_zero ) + { + while ( time < end ) + { + delta = -delta; + synth_.offset( time, delta, osc_output ); + time += period; + //phase ^= 1; + } + //assert( phase == (delta > 0) ); + phase = unsigned (-delta) >> (CHAR_BIT * sizeof (unsigned) - 1); + // (delta > 0) + } + else + { + // loop usually runs less than once + //SUB_CASE_COUNTER( (time < end) * (end - time + period - 1) / period ); + + while ( time < end ) + { + time += period; + phase ^= 1; + } + } + } + while ( time < end_time || ntime < end_time ); + + osc->last_amp = (delta + volume) >> 1; + if ( !(osc_mode & tone_off) ) + osc->phase = phase; + } + + if ( end_time >= final_end_time ) + break; // breaks first time when envelope is disabled + + // next envelope step + if ( ++osc_env_pos >= 0 ) + osc_env_pos -= 32; + volume = env.wave [osc_env_pos] >> half_vol; + + start_time = end_time; + end_time += env_period; + if ( end_time > final_end_time ) + end_time = final_end_time; + } + osc->delay = time - final_end_time; + + if ( !(osc_mode & noise_off) ) + { + noise.delay = ntime - final_end_time; + noise.lfsr = noise_lfsr; + } + } + + // TODO: optimized saw wave envelope? + + // maintain envelope phase + blip_time_t remain = final_end_time - last_time - env.delay; + if ( remain >= 0 ) + { + blargg_long count = (remain + env_period) / env_period; + env.pos += count; + if ( env.pos >= 0 ) + env.pos = (env.pos & 31) - 32; + remain -= count * env_period; + assert( -remain <= env_period ); + } + env.delay = -remain; + assert( env.delay > 0 ); + assert( env.pos < 0 ); + + last_time = final_end_time; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/console/Ay_Apu.h Thu Nov 30 19:54:33 2006 -0800 @@ -0,0 +1,106 @@ +// AY-3-8910 sound chip emulator + +// Game_Music_Emu 0.5.1 +#ifndef AY_APU_H +#define AY_APU_H + +#include "blargg_common.h" +#include "Blip_Buffer.h" + +class Ay_Apu { +public: + // Set buffer to generate all sound into, or disable sound if NULL + void output( Blip_Buffer* ); + + // Reset sound chip + void reset(); + + // Write to register at specified time + void write( blip_time_t time, int addr, int data ); + + // Run sound 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 end_frame( blip_time_t length ); + +// Additional features + + // Set sound output of specific oscillator to buffer, where index is + // 0, 1, or 2. If buffer is NULL, the specified oscillator is muted. + enum { osc_count = 3 }; + void osc_output( int index, Blip_Buffer* ); + + // Set overall volume (default is 1.0) + void volume( double ); + + // Set treble equalization (see documentation) + void treble_eq( blip_eq_t const& ); + +public: + Ay_Apu(); + typedef unsigned char byte; +private: + struct osc_t + { + blip_time_t period; + blip_time_t delay; + short last_amp; + short phase; + Blip_Buffer* output; + } oscs [osc_count]; + blip_time_t last_time; + byte latch; + byte regs [16]; + + struct { + blip_time_t delay; + blargg_ulong lfsr; + } noise; + + struct { + blip_time_t delay; + byte const* wave; + int pos; + byte modes [8] [48]; // values already passed through volume table + } env; + + void run_until( blip_time_t ); + void write_data_( int addr, int data ); +public: + enum { amp_range = 255 }; + Blip_Synth<blip_good_quality,1> synth_; +}; + +inline void Ay_Apu::volume( double v ) { synth_.volume( 0.7 / osc_count / amp_range * v ); } + +inline void Ay_Apu::treble_eq( blip_eq_t const& eq ) { synth_.treble_eq( eq ); } + +inline void Ay_Apu::write( blip_time_t time, int addr, int data ) +{ + run_until( time ); + write_data_( addr, data ); +} + +inline void Ay_Apu::osc_output( int i, Blip_Buffer* buf ) +{ + assert( (unsigned) i < osc_count ); + oscs [i].output = buf; +} + +inline void Ay_Apu::output( Blip_Buffer* buf ) +{ + osc_output( 0, buf ); + osc_output( 1, buf ); + osc_output( 2, buf ); +} + +inline void Ay_Apu::end_frame( blip_time_t time ) +{ + if ( time > last_time ) + run_until( time ); + + assert( last_time >= time ); + last_time -= time; +} + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/console/Ay_Cpu.cxx Thu Nov 30 19:54:33 2006 -0800 @@ -0,0 +1,1653 @@ +// Game_Music_Emu 0.5.1. http://www.slack.net/~ant/ + +// Last validated with zexall 2006.11.21 5:26 PM +// Doesn't implement interrupts or the R register, though both would be +// easy to support. + +#include "Ay_Cpu.h" + +#include "blargg_endian.h" +#include <string.h> + +//#include "z80_cpu_log.h" + +/* Copyright (C) 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 */ + +#define SYNC_TIME() (void) (s.time = s_time) +#define RELOAD_TIME() (void) (s_time = s.time) + +// Callbacks to emulator + +#define CPU_OUT( cpu, addr, data, TIME ) \ + ay_cpu_out( cpu, TIME, addr, data ) + +#define CPU_IN( cpu, addr, TIME ) \ + ay_cpu_in( cpu, addr ) + +#include "blargg_source.h" + +// flags, named with hex value for clarity +int const S80 = 0x80; +int const Z40 = 0x40; +int const F20 = 0x20; +int const H10 = 0x10; +int const F08 = 0x08; +int const V04 = 0x04; +int const P04 = 0x04; +int const N02 = 0x02; +int const C01 = 0x01; + +#define SZ28P( n ) szpc [n] +#define SZ28PC( n ) szpc [n] +#define SZ28C( n ) (szpc [n] & ~P04) +#define SZ28( n ) SZ28C( n ) + +#define SET_R( n ) (void) (r.r = n) +#define GET_R() (r.r) + +Ay_Cpu::Ay_Cpu() +{ + state = &state_; + for ( int i = 0x100; --i >= 0; ) + { + int even = 1; + for ( int p = i; p; p >>= 1 ) + even ^= p; + int n = (i & (S80 | F20 | F08)) | ((even & 1) * P04); + szpc [i] = n; + szpc [i + 0x100] = n | C01; + } + szpc [0x000] |= Z40; + szpc [0x100] |= Z40; +} + +void Ay_Cpu::reset( void* m ) +{ + mem = (uint8_t*) m; + + check( state == &state_ ); + state = &state_; + state_.time = 0; + state_.base = 0; + end_time_ = 0; + + memset( &r, 0, sizeof r ); +} + +#define TIME (s_time + s.base) +#define READ_PROG( addr ) (mem [addr]) +#define INSTR( offset ) READ_PROG( pc + (offset) ) +#define GET_ADDR() GET_LE16( &READ_PROG( pc ) ) +#define READ( addr ) READ_PROG( addr ) +#define WRITE( addr, data ) (void) (READ_PROG( addr ) = data) +#define READ_WORD( addr ) GET_LE16( &READ_PROG( addr ) ) +#define WRITE_WORD( addr, data ) SET_LE16( &READ_PROG( addr ), data ) +#define IN( addr ) CPU_IN( this, addr, TIME ) +#define OUT( addr, data ) CPU_OUT( this, addr, data, TIME ) + +#if BLARGG_BIG_ENDIAN + #define R8( n, offset ) ((r8_ - offset) [n]) +#elif BLARGG_LITTLE_ENDIAN + #define R8( n, offset ) ((r8_ - offset) [(n) ^ 1]) +#else + #error "Byte order of CPU must be known" +#endif + +//#define R16( n, shift, offset ) (r16_ [((n) >> shift) - (offset >> shift)]) + +// help compiler see that it can just adjust stack offset, saving an extra instruction +#define R16( n, shift, offset ) \ + (*(uint16_t*) ((char*) r16_ - (offset >> (shift - 1)) + ((n) >> (shift - 1)))) + +#define CASE5( a, b, c, d, e ) case 0x##a:case 0x##b:case 0x##c:case 0x##d:case 0x##e +#define CASE6( a, b, c, d, e, f ) CASE5( a, b, c, d, e ): case 0x##f +#define CASE7( a, b, c, d, e, f, g ) CASE6( a, b, c, d, e, f ): case 0x##g +#define CASE8( a, b, c, d, e, f, g, h ) CASE7( a, b, c, d, e, f, g ): case 0x##h + +// high four bits are $ED time - 8, low four bits are $DD/$FD time - 8 +static byte const ed_dd_timing [0x100] = { +//0 1 2 3 4 5 6 7 8 9 A B C D E F +0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x07,0x00,0x00,0x00,0x00,0x00,0x00, +0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x07,0x00,0x00,0x00,0x00,0x00,0x00, +0x00,0x06,0x0C,0x02,0x00,0x00,0x03,0x00,0x00,0x07,0x0C,0x02,0x00,0x00,0x03,0x00, +0x00,0x00,0x00,0x00,0x0F,0x0F,0x0B,0x00,0x00,0x07,0x00,0x00,0x00,0x00,0x00,0x00, +0x40,0x40,0x70,0xC0,0x00,0x60,0x0B,0x10,0x40,0x40,0x70,0xC0,0x00,0x60,0x0B,0x10, +0x40,0x40,0x70,0xC0,0x00,0x60,0x0B,0x10,0x40,0x40,0x70,0xC0,0x00,0x60,0x0B,0x10, +0x40,0x40,0x70,0xC0,0x00,0x60,0x0B,0xA0,0x40,0x40,0x70,0xC0,0x00,0x60,0x0B,0xA0, +0x4B,0x4B,0x7B,0xCB,0x0B,0x6B,0x00,0x0B,0x40,0x40,0x70,0xC0,0x00,0x60,0x0B,0x00, +0x00,0x00,0x00,0x00,0x00,0x00,0x0B,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0B,0x00, +0x00,0x00,0x00,0x00,0x00,0x00,0x0B,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0B,0x00, +0x80,0x80,0x80,0x80,0x00,0x00,0x0B,0x00,0x80,0x80,0x80,0x80,0x00,0x00,0x0B,0x00, +0xD0,0xD0,0xD0,0xD0,0x00,0x00,0x0B,0x00,0xD0,0xD0,0xD0,0xD0,0x00,0x00,0x0B,0x00, +0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0F,0x00,0x00,0x00,0x00, +0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +0x00,0x06,0x00,0x0F,0x00,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x00, +}; + +// even on x86, using short and unsigned char was slower +typedef int fint16; +typedef unsigned fuint16; +typedef unsigned fuint8; + +bool Ay_Cpu::run( cpu_time_t end_time ) +{ + set_end_time( end_time ); + state_t s = this->state_; + this->state = &s; + bool warning = false; + + typedef BOOST::int8_t int8_t; + + union { + regs_t rg; + pairs_t rp; + uint8_t r8_ [8]; // indexed + uint16_t r16_ [4]; + }; + rg = this->r.b; + + cpu_time_t s_time = s.time; + uint8_t* const mem = this->mem; // cache + fuint16 pc = r.pc; + fuint16 sp = r.sp; + fuint16 ix = r.ix; // TODO: keep in memory for direct access? + fuint16 iy = r.iy; + int flags = r.b.flags; + + goto loop; +jr_not_taken: + s_time -= 5; + goto loop; +call_not_taken: + s_time -= 7; +jp_not_taken: + pc += 2; +loop: + + check( (unsigned long) pc < 0x10000 ); + check( (unsigned long) sp < 0x10000 ); + check( (unsigned) flags < 0x100 ); + check( (unsigned) ix < 0x10000 ); + check( (unsigned) iy < 0x10000 ); + + fuint8 opcode; + opcode = READ_PROG( pc ); + pc++; + + static byte const base_timing [0x100] = { + // 0 1 2 3 4 5 6 7 8 9 A B C D E F + 4,10, 7, 6, 4, 4, 7, 4, 4,11, 7, 6, 4, 4, 7, 4, // 0 + 13,10, 7, 6, 4, 4, 7, 4,12,11, 7, 6, 4, 4, 7, 4, // 1 + 12,10,16, 6, 4, 4, 7, 4,12,11,16, 6, 4, 4, 7, 4, // 2 + 12,10,13, 6,11,11,10, 4,12,11,13, 6, 4, 4, 7, 4, // 3 + 4, 4, 4, 4, 4, 4, 7, 4, 4, 4, 4, 4, 4, 4, 7, 4, // 4 + 4, 4, 4, 4, 4, 4, 7, 4, 4, 4, 4, 4, 4, 4, 7, 4, // 5 + 4, 4, 4, 4, 4, 4, 7, 4, 4, 4, 4, 4, 4, 4, 7, 4, // 6 + 7, 7, 7, 7, 7, 7, 4, 7, 4, 4, 4, 4, 4, 4, 7, 4, // 7 + 4, 4, 4, 4, 4, 4, 7, 4, 4, 4, 4, 4, 4, 4, 7, 4, // 8 + 4, 4, 4, 4, 4, 4, 7, 4, 4, 4, 4, 4, 4, 4, 7, 4, // 9 + 4, 4, 4, 4, 4, 4, 7, 4, 4, 4, 4, 4, 4, 4, 7, 4, // A + 4, 4, 4, 4, 4, 4, 7, 4, 4, 4, 4, 4, 4, 4, 7, 4, // B + 11,10,10,10,17,11, 7,11,11,10,10, 8,17,17, 7,11, // C + 11,10,10,11,17,11, 7,11,11, 4,10,11,17, 8, 7,11, // D + 11,10,10,19,17,11, 7,11,11, 4,10, 4,17, 8, 7,11, // E + 11,10,10, 4,17,11, 7,11,11, 6,10, 4,17, 8, 7,11, // F + }; + + fuint16 data; + data = base_timing [opcode]; + if ( (s_time += data) >= 0 ) + goto possibly_out_of_time; +almost_out_of_time: + + data = READ_PROG( pc ); + + #ifdef Z80_CPU_LOG_H + //log_opcode( opcode, READ_PROG( pc ) ); + z80_log_regs( rg.a, rp.bc, rp.de, rp.hl, sp, ix, iy ); + z80_cpu_log( "new", pc - 1, opcode, READ_PROG( pc ), + READ_PROG( pc + 1 ), READ_PROG( pc + 2 ) ); + #endif + + switch ( opcode ) + { +possibly_out_of_time: + if ( s_time < (int) data ) + goto almost_out_of_time; + s_time -= data; + goto out_of_time; + +// Common + + case 0x00: // NOP + CASE7( 40, 49, 52, 5B, 64, 6D, 7F ): // LD B,B etc. + goto loop; + + case 0x08:{// EX AF,AF' + int temp = r.alt.b.a; + r.alt.b.a = rg.a; + rg.a = temp; + + temp = r.alt.b.flags; + r.alt.b.flags = flags; + flags = temp; + goto loop; + } + + case 0xD3: // OUT (imm),A + pc++; + OUT( data + rg.a * 0x100, rg.a ); + goto loop; + + case 0x2E: // LD L,imm + pc++; + rg.l = data; + goto loop; + + case 0x3E: // LD A,imm + pc++; + rg.a = data; + goto loop; + + case 0x3A:{// LD A,(addr) + fuint16 addr = GET_ADDR(); + pc += 2; + rg.a = READ( addr ); + goto loop; + } + +// Conditional + +#define ZERO (flags & Z40) +#define CARRY (flags & C01) +#define EVEN (flags & P04) +#define MINUS (flags & S80) + +// JR +#define JR( cond ) {\ + int disp = (BOOST::int8_t) data;\ + pc++;\ + if ( !(cond) )\ + goto jr_not_taken;\ + pc += disp;\ + goto loop;\ +} + + case 0x20: JR( !ZERO ) // JR NZ,disp + case 0x28: JR( ZERO ) // JR Z,disp + case 0x30: JR( !CARRY ) // JR NC,disp + case 0x38: JR( CARRY ) // JR C,disp + case 0x18: JR( true ) // JR disp + + case 0x10:{// DJNZ disp + int temp = rg.b - 1; + rg.b = temp; + JR( temp ) + } + +// JP +#define JP( cond ) if ( !(cond) ) goto jp_not_taken; pc = GET_ADDR(); goto loop; + + case 0xC2: JP( !ZERO ) // JP NZ,addr + case 0xCA: JP( ZERO ) // JP Z,addr + case 0xD2: JP( !CARRY ) // JP NC,addr + case 0xDA: JP( CARRY ) // JP C,addr + case 0xE2: JP( !EVEN ) // JP PO,addr + case 0xEA: JP( EVEN ) // JP PE,addr + case 0xF2: JP( !MINUS ) // JP P,addr + case 0xFA: JP( MINUS ) // JP M,addr + + case 0xC3: // JP addr + pc = GET_ADDR(); + goto loop; + + case 0xE9: // JP HL + pc = rp.hl; + goto loop; + +// RET +#define RET( cond ) if ( cond ) goto ret_taken; s_time -= 6; goto loop; + + case 0xC0: RET( !ZERO ) // RET NZ + case 0xC8: RET( ZERO ) // RET Z + case 0xD0: RET( !CARRY ) // RET NC + case 0xD8: RET( CARRY ) // RET C + case 0xE0: RET( !EVEN ) // RET PO + case 0xE8: RET( EVEN ) // RET PE + case 0xF0: RET( !MINUS ) // RET P + case 0xF8: RET( MINUS ) // RET M + + case 0xC9: // RET + ret_taken: + pc = READ_WORD( sp ); + sp = uint16_t (sp + 2); + goto loop; + +// CALL +#define CALL( cond ) if ( cond ) goto call_taken; goto call_not_taken; + + case 0xC4: CALL( !ZERO ) // CALL NZ,addr + case 0xCC: CALL( ZERO ) // CALL Z,addr + case 0xD4: CALL( !CARRY ) // CALL NC,addr + case 0xDC: CALL( CARRY ) // CALL C,addr + case 0xE4: CALL( !EVEN ) // CALL PO,addr + case 0xEC: CALL( EVEN ) // CALL PE,addr + case 0xF4: CALL( !MINUS ) // CALL P,addr + case 0xFC: CALL( MINUS ) // CALL M,addr + + case 0xCD:{// CALL addr + call_taken: + fuint16 addr = pc + 2; + pc = GET_ADDR(); + sp = uint16_t (sp - 2); + WRITE_WORD( sp, addr ); + goto loop; + } + + CASE8( C7, CF, D7, DF, E7, EF, F7, FF ): // RST + data = pc; + pc = opcode & 0x38; + goto push_data; + +// PUSH/POP + case 0xF5: // PUSH AF + data = rg.a * 0x100u + flags; + goto push_data; + + case 0xC5: // PUSH BC + case 0xD5: // PUSH DE + case 0xE5: // PUSH HL + data = R16( opcode, 4, 0xC5 ); + push_data: + sp = uint16_t (sp - 2); + WRITE_WORD( sp, data ); + goto loop; + + case 0xF1: // POP AF + flags = READ( sp ); + rg.a = READ( sp + 1 ); + sp = uint16_t (sp + 2); + goto loop; + + case 0xC1: // POP BC + case 0xD1: // POP DE + case 0xE1: // POP HL + R16( opcode, 4, 0xC1 ) = READ_WORD( sp ); + sp = uint16_t (sp + 2); + goto loop; + +// ADC/ADD/SBC/SUB + case 0x96: // SUB (HL) + case 0x86: // ADD (HL) + flags &= ~C01; + case 0x9E: // SBC (HL) + case 0x8E: // ADC (HL) + data = READ( rp.hl ); + goto adc_data; + + case 0xD6: // SUB A,imm + case 0xC6: // ADD imm + flags &= ~C01; + case 0xDE: // SBC A,imm + case 0xCE: // ADC imm + pc++; + goto adc_data; + + CASE7( 90, 91, 92, 93, 94, 95, 97 ): // SUB r + CASE7( 80, 81, 82, 83, 84, 85, 87 ): // ADD r + flags &= ~C01; + CASE7( 98, 99, 9A, 9B, 9C, 9D, 9F ): // SBC r + CASE7( 88, 89, 8A, 8B, 8C, 8D, 8F ): // ADC r + data = R8( opcode & 7, 0 ); + adc_data: { + int result = data + (flags & C01); + data ^= rg.a; + flags = opcode >> 3 & N02; // bit 4 is set in subtract opcodes + if ( flags ) + result = -result; + result += rg.a; + data ^= result; + flags |=(data & H10) | + ((data - -0x80) >> 6 & V04) | + SZ28C( result & 0x1FF ); + rg.a = result; + goto loop; + } + +// CP + case 0xBE: // CP (HL) + data = READ( rp.hl ); + goto cp_data; + + case 0xFE: // CP imm + pc++; + goto cp_data; + + CASE7( B8, B9, BA, BB, BC, BD, BF ): // CP r + data = R8( opcode, 0xB8 ); + cp_data: { + int result = rg.a - data; + flags = N02 | (data & (F20 | F08)) | (result >> 8 & C01); + data ^= rg.a; + flags |=(((result ^ rg.a) & data) >> 5 & V04) | + (((data & H10) ^ result) & (S80 | H10)); + if ( (uint8_t) result ) + goto loop; + flags |= Z40; + goto loop; + } + +// ADD HL,rp + + case 0x39: // ADD HL,SP + data = sp; + goto add_hl_data; + + case 0x09: // ADD HL,BC + case 0x19: // ADD HL,DE + case 0x29: // ADD HL,HL + data = R16( opcode, 4, 0x09 ); + add_hl_data: { + blargg_ulong sum = rp.hl + data; + data ^= rp.hl; + rp.hl = sum; + flags = (flags & (S80 | Z40 | V04)) | + (sum >> 16) | + (sum >> 8 & (F20 | F08)) | + ((data ^ sum) >> 8 & H10); + goto loop; + } + + case 0x27:{// DAA + int a = rg.a; + if ( a > 0x99 ) + flags |= C01; + + int adjust = 0x60 & -(flags & C01); + + if ( flags & H10 || (a & 0x0F) > 9 ) + adjust |= 0x06; + + if ( flags & N02 ) + adjust = -adjust; + a += adjust; + + flags = (flags & (C01 | N02)) | + ((rg.a ^ a) & H10) | + SZ28P( (uint8_t) a ); + rg.a = a; + goto loop; + } + /* + case 0x27:{// DAA + // more optimized, but probably not worth the obscurity + int f = (rg.a + (0xFF - 0x99)) >> 8 | flags; // (a > 0x99 ? C01 : 0) | flags + int adjust = 0x60 & -(f & C01); // f & C01 ? 0x60 : 0 + + if ( (((rg.a + (0x0F - 9)) ^ rg.a) | f) & H10 ) // flags & H10 || (rg.a & 0x0F) > 9 + adjust |= 0x06; + + if ( f & N02 ) + adjust = -adjust; + int a = rg.a + adjust; + + flags = (f & (N02 | C01)) | ((rg.a ^ a) & H10) | SZ28P( (uint8_t) a ); + rg.a = a; + goto loop; + } + */ + +// INC/DEC + case 0x34: // INC (HL) + data = READ( rp.hl ) + 1; + WRITE( rp.hl, data ); + goto inc_set_flags; + + CASE7( 04, 0C, 14, 1C, 24, 2C, 3C ): // INC r + data = ++R8( opcode >> 3, 0 ); + inc_set_flags: + flags = (flags & C01) | + (((data & 0x0F) - 1) & H10) | + SZ28( (uint8_t) data ); + if ( data != 0x80 ) + goto loop; + flags |= V04; + goto loop; + + case 0x35: // DEC (HL) + data = READ( rp.hl ) - 1; + WRITE( rp.hl, data ); + goto dec_set_flags; + + CASE7( 05, 0D, 15, 1D, 25, 2D, 3D ): // DEC r + data = --R8( opcode >> 3, 0 ); + dec_set_flags: + flags = (flags & C01) | N02 | + (((data & 0x0F) + 1) & H10) | + SZ28( (uint8_t) data ); + if ( data != 0x7F ) + goto loop; + flags |= V04; + goto loop; + + case 0x03: // INC BC + case 0x13: // INC DE + case 0x23: // INC HL + R16( opcode, 4, 0x03 )++; + goto loop; + + case 0x33: // INC SP + sp = uint16_t (sp + 1); + goto loop; + + case 0x0B: // DEC BC + case 0x1B: // DEC DE + case 0x2B: // DEC HL + R16( opcode, 4, 0x0B )--; + goto loop; + + case 0x3B: // DEC SP + sp = uint16_t (sp - 1); + goto loop; + +// AND + case 0xA6: // AND (HL) + data = READ( rp.hl ); + goto and_data; + + case 0xE6: // AND imm + pc++; + goto and_data; + + CASE7( A0, A1, A2, A3, A4, A5, A7 ): // AND r + data = R8( opcode, 0xA0 ); + and_data: + rg.a &= data; + flags = SZ28P( rg.a ) | H10; + goto loop; + +// OR + case 0xB6: // OR (HL) + data = READ( rp.hl ); + goto or_data; + + case 0xF6: // OR imm + pc++; + goto or_data; + + CASE7( B0, B1, B2, B3, B4, B5, B7 ): // OR r + data = R8( opcode, 0xB0 ); + or_data: + rg.a |= data; + flags = SZ28P( rg.a ); + goto loop; + +// XOR + case 0xAE: // XOR (HL) + data = READ( rp.hl ); + goto xor_data; + + case 0xEE: // XOR imm + pc++; + goto xor_data; + + CASE7( A8, A9, AA, AB, AC, AD, AF ): // XOR r + data = R8( opcode, 0xA8 ); + xor_data: + rg.a ^= data; + flags = SZ28P( rg.a ); + goto loop; + +// LD + CASE7( 70, 71, 72, 73, 74, 75, 77 ): // LD (HL),r + WRITE( rp.hl, R8( opcode, 0x70 ) ); + goto loop; + + CASE6( 41, 42, 43, 44, 45, 47 ): // LD B,r + CASE6( 48, 4A, 4B, 4C, 4D, 4F ): // LD C,r + CASE6( 50, 51, 53, 54, 55, 57 ): // LD D,r + CASE6( 58, 59, 5A, 5C, 5D, 5F ): // LD E,r + CASE6( 60, 61, 62, 63, 65, 67 ): // LD H,r + CASE6( 68, 69, 6A, 6B, 6C, 6F ): // LD L,r + CASE6( 78, 79, 7A, 7B, 7C, 7D ): // LD A,r + R8( opcode >> 3 & 7, 0 ) = R8( opcode & 7, 0 ); + goto loop; + + CASE5( 06, 0E, 16, 1E, 26 ): // LD r,imm + R8( opcode >> 3, 0 ) = data; + pc++; + goto loop; + + case 0x36: // LD (HL),imm + pc++; + WRITE( rp.hl, data ); + goto loop; + + CASE7( 46, 4E, 56, 5E, 66, 6E, 7E ): // LD r,(HL) + R8( opcode >> 3, 8 ) = READ( rp.hl ); + goto loop; + + case 0x01: // LD rp,imm + case 0x11: + case 0x21: + R16( opcode, 4, 0x01 ) = GET_ADDR(); + pc += 2; + goto loop; + + case 0x31: // LD sp,imm + sp = GET_ADDR(); + pc += 2; + goto loop; + + case 0x2A:{// LD HL,(addr) + fuint16 addr = GET_ADDR(); + pc += 2; + rp.hl = READ_WORD( addr ); + goto loop; + } + + case 0x32:{// LD (addr),A + fuint16 addr = GET_ADDR(); + pc += 2; + WRITE( addr, rg.a ); + goto loop; + } + + case 0x22:{// LD (addr),HL + fuint16 addr = GET_ADDR(); + pc += 2; + WRITE_WORD( addr, rp.hl ); + goto loop; + } + + case 0x02: // LD (BC),A + case 0x12: // LD (DE),A + WRITE( R16( opcode, 4, 0x02 ), rg.a ); + goto loop; + + case 0x0A: // LD A,(BC) + case 0x1A: // LD A,(DE) + rg.a = READ( R16( opcode, 4, 0x0A ) ); + goto loop; + + case 0xF9: // LD SP,HL + sp = rp.hl; + goto loop; + +// Rotate + + case 0x07:{// RLCA + fuint16 temp = rg.a; + temp = (temp << 1) | (temp >> 7); + flags = (flags & (S80 | Z40 | P04)) | + (temp & (F20 | F08 | C01)); + rg.a = temp; + goto loop; + } + + case 0x0F:{// RRCA + fuint16 temp = rg.a; + flags = (flags & (S80 | Z40 | P04)) | + (temp & C01); + temp = (temp << 7) | (temp >> 1); + flags |= temp & (F20 | F08); + rg.a = temp; + goto loop; + } + + case 0x17:{// RLA + blargg_ulong temp = (rg.a << 1) | (flags & C01); + flags = (flags & (S80 | Z40 | P04)) | + (temp & (F20 | F08)) | + (temp >> 8); + rg.a = temp; + goto loop; + } + + case 0x1F:{// RRA + fuint16 temp = (flags << 7) | (rg.a >> 1); + flags = (flags & (S80 | Z40 | P04)) | + (temp & (F20 | F08)) | + (rg.a & C01); + rg.a = temp; + goto loop; + } + +// Misc + case 0x2F:{// CPL + fuint16 temp = ~rg.a; + flags = (flags & (S80 | Z40 | P04 | C01)) | + (temp & (F20 | F08)) | + (H10 | N02); + rg.a = temp; + goto loop; + } + + case 0x3F:{// CCF + flags = ((flags & (S80 | Z40 | P04 | C01)) ^ C01) | + (flags << 4 & H10) | + (rg.a & (F20 | F08)); + goto loop; + } + + case 0x37: // SCF + flags = (flags & (S80 | Z40 | P04)) | C01 | + (rg.a & (F20 | F08)); + goto loop; + + case 0xDB: // IN A,(imm) + pc++; + rg.a = IN( data + rg.a * 0x100 ); + goto loop; + + case 0xE3:{// EX (SP),HL + fuint16 temp = READ_WORD( sp ); + WRITE_WORD( sp, rp.hl ); + rp.hl = temp; + goto loop; + } + + case 0xEB:{// EX DE,HL + fuint16 temp = rp.hl; + rp.hl = rp.de; + rp.de = temp; + goto loop; + } + + case 0xD9:{// EXX DE,HL + fuint16 temp = r.alt.w.bc; + r.alt.w.bc = rp.bc; + rp.bc = temp; + + temp = r.alt.w.de; + r.alt.w.de = rp.de; + rp.de = temp; + + temp = r.alt.w.hl; + r.alt.w.hl = rp.hl; + rp.hl = temp; + goto loop; + } + + case 0xF3: // DI + r.iff1 = 0; + r.iff2 = 0; + goto loop; + + case 0xFB: // EI + r.iff1 = 1; + r.iff2 = 1; + // TODO: delayed effect + goto loop; + + case 0x76: // HALT + goto halt; + +//////////////////////////////////////// CB prefix + { + case 0xCB: + unsigned data2; + data2 = INSTR( 1 ); + pc++; + switch ( data ) + { + + // Rotate left + + #define RLC( read, write ) {\ + fuint8 result = read;\ + result = uint8_t (result << 1) | (result >> 7);\ + flags = SZ28P( result ) | (result & C01);\ + write;\ + goto loop;\ + } + + case 0x06: // RLC (HL) + s_time += 7; + data = rp.hl; + rlc_data_addr: + RLC( READ( data ), WRITE( data, result ) ) + + CASE7( 00, 01, 02, 03, 04, 05, 07 ):{// RLC r + uint8_t& reg = R8( data, 0 ); + RLC( reg, reg = result ) + } + + #define RL( read, write ) {\ + fuint16 result = (read << 1) | (flags & C01);\ + flags = SZ28PC( result );\ + write;\ + goto loop;\ + } + + case 0x16: // RL (HL) + s_time += 7; + data = rp.hl; + rl_data_addr: + RL( READ( data ), WRITE( data, result ) ) + + CASE7( 10, 11, 12, 13, 14, 15, 17 ):{// RL r + uint8_t& reg = R8( data, 0x10 ); + RL( reg, reg = result ) + } + + #define SLA( read, add, write ) {\ + fuint16 result = (read << 1) | add;\ + flags = SZ28PC( result );\ + write;\ + goto loop;\ + } + + case 0x26: // SLA (HL) + s_time += 7; + data = rp.hl; + sla_data_addr: + SLA( READ( data ), 0, WRITE( data, result ) ) + + CASE7( 20, 21, 22, 23, 24, 25, 27 ):{// SLA r + uint8_t& reg = R8( data, 0x20 ); + SLA( reg, 0, reg = result ) + } + + case 0x36: // SLL (HL) + s_time += 7; + data = rp.hl; + sll_data_addr: + SLA( READ( data ), 1, WRITE( data, result ) ) + + CASE7( 30, 31, 32, 33, 34, 35, 37 ):{// SLL r + uint8_t& reg = R8( data, 0x30 ); + SLA( reg, 1, reg = result ) + } + + // Rotate right + + #define RRC( read, write ) {\ + fuint8 result = read;\ + flags = result & C01;\ + result = uint8_t (result << 7) | (result >> 1);\ + flags |= SZ28P( result );\ + write;\ + goto loop;\ + } + + case 0x0E: // RRC (HL) + s_time += 7; + data = rp.hl; + rrc_data_addr: + RRC( READ( data ), WRITE( data, result ) ) + + CASE7( 08, 09, 0A, 0B, 0C, 0D, 0F ):{// RRC r + uint8_t& reg = R8( data, 0x08 ); + RRC( reg, reg = result ) + } + + #define RR( read, write ) {\ + fuint8 result = read;\ + fuint8 temp = result & C01;\ + result = uint8_t (flags << 7) | (result >> 1);\ + flags = SZ28P( result ) | temp;\ + write;\ + goto loop;\ + } + + case 0x1E: // RR (HL) + s_time += 7; + data = rp.hl; + rr_data_addr: + RR( READ( data ), WRITE( data, result ) ) + + CASE7( 18, 19, 1A, 1B, 1C, 1D, 1F ):{// RR r + uint8_t& reg = R8( data, 0x18 ); + RR( reg, reg = result ) + } + + #define SRA( read, write ) {\ + fuint8 result = read;\ + flags = result & C01;\ + result = (result & 0x80) | (result >> 1);\ + flags |= SZ28P( result );\ + write;\ + goto loop;\ + } + + case 0x2E: // SRA (HL) + data = rp.hl; + s_time += 7; + sra_data_addr: + SRA( READ( data ), WRITE( data, result ) ) + + CASE7( 28, 29, 2A, 2B, 2C, 2D, 2F ):{// SRA r + uint8_t& reg = R8( data, 0x28 ); + SRA( reg, reg = result ) + } + + #define SRL( read, write ) {\ + fuint8 result = read;\ + flags = result & C01;\ + result >>= 1;\ + flags |= SZ28P( result );\ + write;\ + goto loop;\ + } + + case 0x3E: // SRL (HL) + s_time += 7; + data = rp.hl; + srl_data_addr: + SRL( READ( data ), WRITE( data, result ) ) + + CASE7( 38, 39, 3A, 3B, 3C, 3D, 3F ):{// SRL r + uint8_t& reg = R8( data, 0x38 ); + SRL( reg, reg = result ) + } + + // BIT + { + unsigned temp; + CASE8( 46, 4E, 56, 5E, 66, 6E, 76, 7E ): // BIT b,(HL) + s_time += 4; + temp = READ( rp.hl ); + flags &= C01; + goto bit_temp; + CASE7( 40, 41, 42, 43, 44, 45, 47 ): // BIT 0,r + CASE7( 48, 49, 4A, 4B, 4C, 4D, 4F ): // BIT 1,r + CASE7( 50, 51, 52, 53, 54, 55, 57 ): // BIT 2,r + CASE7( 58, 59, 5A, 5B, 5C, 5D, 5F ): // BIT 3,r + CASE7( 60, 61, 62, 63, 64, 65, 67 ): // BIT 4,r + CASE7( 68, 69, 6A, 6B, 6C, 6D, 6F ): // BIT 5,r + CASE7( 70, 71, 72, 73, 74, 75, 77 ): // BIT 6,r + CASE7( 78, 79, 7A, 7B, 7C, 7D, 7F ): // BIT 7,r + temp = R8( data & 7, 0 ); + flags = (flags & C01) | (temp & (F20 | F08)); + bit_temp: + int masked = temp & 1 << (data >> 3 & 7); + flags |=(masked & S80) | H10 | + ((masked - 1) >> 8 & (Z40 | P04)); + goto loop; + } + + // SET/RES + CASE8( 86, 8E, 96, 9E, A6, AE, B6, BE ): // RES b,(HL) + CASE8( C6, CE, D6, DE, E6, EE, F6, FE ):{// SET b,(HL) + s_time += 7; + int temp = READ( rp.hl ); + int bit = 1 << (data >> 3 & 7); + temp |= bit; // SET + if ( !(data & 0x40) ) + temp ^= bit; // RES + WRITE( rp.hl, temp ); + goto loop; + } + + CASE7( C0, C1, C2, C3, C4, C5, C7 ): // SET 0,r + CASE7( C8, C9, CA, CB, CC, CD, CF ): // SET 1,r + CASE7( D0, D1, D2, D3, D4, D5, D7 ): // SET 2,r + CASE7( D8, D9, DA, DB, DC, DD, DF ): // SET 3,r + CASE7( E0, E1, E2, E3, E4, E5, E7 ): // SET 4,r + CASE7( E8, E9, EA, EB, EC, ED, EF ): // SET 5,r + CASE7( F0, F1, F2, F3, F4, F5, F7 ): // SET 6,r + CASE7( F8, F9, FA, FB, FC, FD, FF ): // SET 7,r + R8( data & 7, 0 ) |= 1 << (data >> 3 & 7); + goto loop; + + CASE7( 80, 81, 82, 83, 84, 85, 87 ): // RES 0,r + CASE7( 88, 89, 8A, 8B, 8C, 8D, 8F ): // RES 1,r + CASE7( 90, 91, 92, 93, 94, 95, 97 ): // RES 2,r + CASE7( 98, 99, 9A, 9B, 9C, 9D, 9F ): // RES 3,r + CASE7( A0, A1, A2, A3, A4, A5, A7 ): // RES 4,r + CASE7( A8, A9, AA, AB, AC, AD, AF ): // RES 5,r + CASE7( B0, B1, B2, B3, B4, B5, B7 ): // RES 6,r + CASE7( B8, B9, BA, BB, BC, BD, BF ): // RES 7,r + R8( data & 7, 0 ) &= ~(1 << (data >> 3 & 7)); + goto loop; + } + assert( false ); + } + +//////////////////////////////////////// ED prefix + { + case 0xED: + pc++; + s_time += ed_dd_timing [data] >> 4; + switch ( data ) + { + { + blargg_ulong temp; + case 0x72: // SBC HL,SP + case 0x7A: // ADC HL,SP + temp = sp; + if ( 0 ) + case 0x42: // SBC HL,BC + case 0x52: // SBC HL,DE + case 0x62: // SBC HL,HL + case 0x4A: // ADC HL,BC + case 0x5A: // ADC HL,DE + case 0x6A: // ADC HL,HL + temp = R16( data >> 3 & 6, 1, 0 ); + blargg_ulong sum = temp + (flags & C01); + flags = ~data >> 2 & N02; + if ( flags ) + sum = -sum; + sum += rp.hl; + temp ^= rp.hl; + temp ^= sum; + flags |=(sum >> 16 & C01) | + (temp >> 8 & H10) | + (sum >> 8 & (S80 | F20 | F08)) | + ((temp - -0x8000) >> 14 & V04); + rp.hl = sum; + if ( (uint16_t) sum ) + goto loop; + flags |= Z40; + goto loop; + } + + CASE8( 40, 48, 50, 58, 60, 68, 70, 78 ):{// IN r,(C) + int temp = IN( rp.bc ); + R8( data >> 3, 8 ) = temp; + flags = (flags & C01) | SZ28P( temp ); + goto loop; + } + + case 0x71: // OUT (C),0 + rg.flags = 0; + CASE7( 41, 49, 51, 59, 61, 69, 79 ): // OUT (C),r + OUT( rp.bc, R8( data >> 3, 8 ) ); + goto loop; + + { + unsigned temp; + case 0x73: // LD (ADDR),SP + temp = sp; + if ( 0 ) + case 0x43: // LD (ADDR),BC + case 0x53: // LD (ADDR),DE + temp = R16( data, 4, 0x43 ); + fuint16 addr = GET_ADDR(); + pc += 2; + WRITE_WORD( addr, temp ); + goto loop; + } + + case 0x4B: // LD BC,(ADDR) + case 0x5B:{// LD DE,(ADDR) + fuint16 addr = GET_ADDR(); + pc += 2; + R16( data, 4, 0x4B ) = READ_WORD( addr ); + goto loop; + } + + case 0x7B:{// LD SP,(ADDR) + fuint16 addr = GET_ADDR(); + pc += 2; + sp = READ_WORD( addr ); + goto loop; + } + + case 0x67:{// RRD + fuint8 temp = READ( rp.hl ); + WRITE( rp.hl, (rg.a << 4) | (temp >> 4) ); + temp = (rg.a & 0xF0) | (temp & 0x0F); + flags = (flags & C01) | SZ28P( temp ); + rg.a = temp; + goto loop; + } + + case 0x6F:{// RLD + fuint8 temp = READ( rp.hl ); + WRITE( rp.hl, (temp << 4) | (rg.a & 0x0F) ); + temp = (rg.a & 0xF0) | (temp >> 4); + flags = (flags & C01) | SZ28P( temp ); + rg.a = temp; + goto loop; + } + + CASE8( 44, 4C, 54, 5C, 64, 6C, 74, 7C ): // NEG + opcode = 0x10; // flag to do SBC instead of ADC + flags &= ~C01; + data = rg.a; + rg.a = 0; + goto adc_data; + + { + int inc; + case 0xA9: // CPD + case 0xB9: // CPDR + inc = -1; + if ( 0 ) + case 0xA1: // CPI + case 0xB1: // CPIR + inc = +1; + fuint16 addr = rp.hl; + rp.hl = addr + inc; + int temp = READ( addr ); + + int result = rg.a - temp; + flags = (flags & C01) | N02 | + ((((temp ^ rg.a) & H10) ^ result) & (S80 | H10)); + + if ( !(uint8_t) result ) flags |= Z40; + result -= (flags & H10) >> 4; + flags |= result & F08; + flags |= result << 4 & F20; + if ( !--rp.bc ) + goto loop; + + flags |= V04; + if ( flags & Z40 || data < 0xB0 ) + goto loop; + + pc -= 2; + s_time += 5; + goto loop; + } + + { + int inc; + case 0xA8: // LDD + case 0xB8: // LDDR + inc = -1; + if ( 0 ) + case 0xA0: // LDI + case 0xB0: // LDIR + inc = +1; + fuint16 addr = rp.hl; + rp.hl = addr + inc; + int temp = READ( addr ); + + addr = rp.de; + rp.de = addr + inc; + WRITE( addr, temp ); + + temp += rg.a; + flags = (flags & (S80 | Z40 | C01)) | + (temp & F08) | (temp << 4 & F20); + if ( !--rp.bc ) + goto loop; + + flags |= V04; + if ( data < 0xB0 ) + goto loop; + + pc -= 2; + s_time += 5; + goto loop; + } + + { + int inc; + case 0xAB: // OUTD + case 0xBB: // OTDR + inc = -1; + if ( 0 ) + case 0xA3: // OUTI + case 0xB3: // OTIR + inc = +1; + fuint16 addr = rp.hl; + rp.hl = addr + inc; + int temp = READ( addr ); + + int b = --rg.b; + flags = (temp >> 6 & N02) | SZ28( b ); + if ( b && data >= 0xB0 ) + { + pc -= 2; + s_time += 5; + } + + OUT( rp.bc, temp ); + goto loop; + } + + { + int inc; + case 0xAA: // IND + case 0xBA: // INDR + inc = -1; + if ( 0 ) + case 0xA2: // INI + case 0xB2: // INIR + inc = +1; + + fuint16 addr = rp.hl; + rp.hl = addr + inc; + + int temp = IN( rp.bc ); + + int b = --rg.b; + flags = (temp >> 6 & N02) | SZ28( b ); + if ( b && data >= 0xB0 ) + { + pc -= 2; + s_time += 5; + } + + WRITE( addr, temp ); + goto loop; + } + + case 0x47: // LD I,A + r.i = rg.a; + goto loop; + + case 0x4F: // LD R,A + SET_R( rg.a ); + dprintf( "LD R,A not supported\n" ); + warning = true; + goto loop; + + case 0x57: // LD A,I + rg.a = r.i; + goto ld_ai_common; + + case 0x5F: // LD A,R + rg.a = GET_R(); + dprintf( "LD A,R not supported\n" ); + warning = true; + ld_ai_common: + flags = (flags & C01) | SZ28( rg.a ) | (r.iff2 << 2 & V04); + goto loop; + + CASE8( 45, 4D, 55, 5D, 65, 6D, 75, 7D ): // RETI/RETN + r.iff1 = r.iff2; + goto ret_taken; + + case 0x46: case 0x4E: case 0x66: case 0x6E: // IM 0 + r.im = 0; + goto loop; + + case 0x56: case 0x76: // IM 1 + r.im = 1; + goto loop; + + case 0x5E: case 0x7E: // IM 2 + r.im = 2; + goto loop; + + default: + dprintf( "Opcode $ED $%02X not supported\n", data ); + warning = true; + goto loop; + } + assert( false ); + } + +//////////////////////////////////////// DD/FD prefix + { + fuint16 ixy; + case 0xDD: + ixy = ix; + goto ix_prefix; + case 0xFD: + ixy = iy; + ix_prefix: + pc++; + unsigned data2 = READ_PROG( pc ); + s_time += ed_dd_timing [data] & 0x0F; + switch ( data ) + { + #define SET_IXY( in ) if ( opcode == 0xDD ) ix = in; else iy = in; + + // ADD/ADC/SUB/SBC + + case 0x96: // SUB (IXY+disp) + case 0x86: // ADD (IXY+disp) + flags &= ~C01; + case 0x9E: // SBC (IXY+disp) + case 0x8E: // ADC (IXY+disp) + pc++; + opcode = data; + data = READ( ixy + (int8_t) data2 ); + goto adc_data; + + case 0x94: // SUB HXY + case 0x84: // ADD HXY + flags &= ~C01; + case 0x9C: // SBC HXY + case 0x8C: // ADC HXY + opcode = data; + data = ixy >> 8; + goto adc_data; + + case 0x95: // SUB LXY + case 0x85: // ADD LXY + flags &= ~C01; + case 0x9D: // SBC LXY + case 0x8D: // ADC LXY + opcode = data; + data = (uint8_t) ixy; + goto adc_data; + + { + unsigned data2; + case 0x39: // ADD IXY,SP + data2 = sp; + goto add_ixy_data; + + case 0x29: // ADD IXY,HL + data2 = ixy; + goto add_ixy_data; + + case 0x09: // ADD IXY,BC + case 0x19: // ADD IXY,DE + data2 = R16( data, 4, 0x09 ); + add_ixy_data: { + blargg_ulong sum = ixy + data2; + data2 ^= ixy; + ixy = sum; + flags = (flags & (S80 | Z40 | V04)) | + (sum >> 16) | + (sum >> 8 & (F20 | F08)) | + ((data2 ^ sum) >> 8 & H10); + goto set_ixy; + } + } + + // AND + case 0xA6: // AND (IXY+disp) + pc++; + data = READ( ixy + (int8_t) data2 ); + goto and_data; + + case 0xA4: // AND HXY + data = ixy >> 8; + goto and_data; + + case 0xA5: // AND LXY + data = (uint8_t) ixy; + goto and_data; + + // OR + case 0xB6: // OR (IXY+disp) + pc++; + data = READ( ixy + (int8_t) data2 ); + goto or_data; + + case 0xB4: // OR HXY + data = ixy >> 8; + goto or_data; + + case 0xB5: // OR LXY + data = (uint8_t) ixy; + goto or_data; + + // XOR + case 0xAE: // XOR (IXY+disp) + pc++; + data = READ( ixy + (int8_t) data2 ); + goto xor_data; + + case 0xAC: // XOR HXY + data = ixy >> 8; + goto xor_data; + + case 0xAD: // XOR LXY + data = (uint8_t) ixy; + goto xor_data; + + // CP + case 0xBE: // CP (IXY+disp) + pc++; + data = READ( ixy + (int8_t) data2 ); + goto cp_data; + + case 0xBC: // CP HXY + data = ixy >> 8; + goto cp_data; + + case 0xBD: // CP LXY + data = (uint8_t) ixy; + goto cp_data; + + // LD + CASE7( 70, 71, 72, 73, 74, 75, 77 ): // LD (IXY+disp),r + data = R8( data, 0x70 ); + if ( 0 ) + case 0x36: // LD (IXY+disp),imm + pc++, data = READ_PROG( pc ); + pc++; + WRITE( ixy + (int8_t) data2, data ); + goto loop; + + CASE5( 44, 4C, 54, 5C, 7C ): // LD r,HXY + R8( data >> 3, 8 ) = ixy >> 8; + goto loop; + + case 0x64: // LD HXY,HXY + case 0x6D: // LD LXY,LXY + goto loop; + + CASE5( 45, 4D, 55, 5D, 7D ): // LD r,LXY + R8( data >> 3, 8 ) = ixy; + goto loop; + + CASE7( 46, 4E, 56, 5E, 66, 6E, 7E ): // LD r,(IXY+disp) + pc++; + R8( data >> 3, 8 ) = READ( ixy + (int8_t) data2 ); + goto loop; + + case 0x26: // LD HXY,imm + pc++; + goto ld_hxy_data; + + case 0x65: // LD HXY,LXY + data2 = (uint8_t) ixy; + goto ld_hxy_data; + + CASE5( 60, 61, 62, 63, 67 ): // LD HXY,r + data2 = R8( data, 0x60 ); + ld_hxy_data: + ixy = (uint8_t) ixy | (data2 << 8); + goto set_ixy; + + case 0x2E: // LD LXY,imm + pc++; + goto ld_lxy_data; + + case 0x6C: // LD LXY,HXY + data2 = ixy >> 8; + goto ld_lxy_data; + + CASE5( 68, 69, 6A, 6B, 6F ): // LD LXY,r + data2 = R8( data, 0x68 ); + ld_lxy_data: + ixy = (ixy & 0xFF00) | data2; + set_ixy: + if ( opcode == 0xDD ) + { + ix = ixy; + goto loop; + } + iy = ixy; + goto loop; + + case 0xF9: // LD SP,IXY + sp = ixy; + goto loop; + + case 0x22:{// LD (ADDR),IXY + fuint16 addr = GET_ADDR(); + pc += 2; + WRITE_WORD( addr, ixy ); + goto loop; + } + + case 0x21: // LD IXY,imm + ixy = GET_ADDR(); + pc += 2; + goto set_ixy; + + case 0x2A:{// LD IXY,(addr) + fuint16 addr = GET_ADDR(); + ixy = READ_WORD( addr ); + pc += 2; + goto set_ixy; + } + + // DD/FD CB prefix + case 0xCB: { + data = ixy + (int8_t) data2; + pc++; + data2 = READ_PROG( pc ); + pc++; + switch ( data2 ) + { + case 0x06: goto rlc_data_addr; // RLC (IXY) + case 0x16: goto rl_data_addr; // RL (IXY) + case 0x26: goto sla_data_addr; // SLA (IXY) + case 0x36: goto sll_data_addr; // SLL (IXY) + case 0x0E: goto rrc_data_addr; // RRC (IXY) + case 0x1E: goto rr_data_addr; // RR (IXY) + case 0x2E: goto sra_data_addr; // SRA (IXY) + case 0x3E: goto srl_data_addr; // SRL (IXY) + + CASE8( 46, 4E, 56, 5E, 66, 6E, 76, 7E ):{// BIT b,(IXY+disp) + fuint8 temp = READ( data ); + int masked = temp & 1 << (data2 >> 3 & 7); + flags = (flags & C01) | H10 | + (masked & S80) | + ((masked - 1) >> 8 & (Z40 | P04)); + goto loop; + } + + CASE8( 86, 8E, 96, 9E, A6, AE, B6, BE ): // RES b,(IXY+disp) + CASE8( C6, CE, D6, DE, E6, EE, F6, FE ):{// SET b,(IXY+disp) + int temp = READ( data ); + int bit = 1 << (data2 >> 3 & 7); + temp |= bit; // SET + if ( !(data2 & 0x40) ) + temp ^= bit; // RES + WRITE( data, temp ); + goto loop; + } + + default: + dprintf( "Opcode $%02X $CB $%02X not supported\n", opcode, data2 ); + warning = true; + goto loop; + } + assert( false ); + } + + // INC/DEC + case 0x23: // INC IXY + ixy = uint16_t (ixy + 1); + goto set_ixy; + + case 0x2B: // DEC IXY + ixy = uint16_t (ixy - 1); + goto set_ixy; + + case 0x34: // INC (IXY+disp) + ixy += (int8_t) data2; + pc++; + data = READ( ixy ) + 1; + WRITE( ixy, data ); + goto inc_set_flags; + + case 0x35: // DEC (IXY+disp) + ixy += (int8_t) data2; + pc++; + data = READ( ixy ) - 1; + WRITE( ixy, data ); + goto dec_set_flags; + + case 0x24: // INC HXY + ixy = uint16_t (ixy + 0x100); + data = ixy >> 8; + goto inc_xy_common; + + case 0x2C: // INC LXY + data = uint8_t (ixy + 1); + ixy = (ixy & 0xFF00) | data; + inc_xy_common: + if ( opcode == 0xDD ) + { + ix = ixy; + goto inc_set_flags; + } + iy = ixy; + goto inc_set_flags; + + case 0x25: // DEC HXY + ixy = uint16_t (ixy - 0x100); + data = ixy >> 8; + goto dec_xy_common; + + case 0x2D: // DEC LXY + data = uint8_t (ixy - 1); + ixy = (ixy & 0xFF00) | data; + dec_xy_common: + if ( opcode == 0xDD ) + { + ix = ixy; + goto dec_set_flags; + } + iy = ixy; + goto dec_set_flags; + + // PUSH/POP + case 0xE5: // PUSH IXY + data = ixy; + goto push_data; + + case 0xE1:{// POP IXY + ixy = READ_WORD( sp ); + sp = uint16_t (sp + 2); + goto set_ixy; + } + + // Misc + + case 0xE9: // JP (IXY) + pc = ixy; + goto loop; + + case 0xE3:{// EX (SP),IXY + fuint16 temp = READ_WORD( sp ); + WRITE_WORD( sp, ixy ); + ixy = temp; + goto set_ixy; + } + + default: + dprintf( "Unnecessary DD/FD prefix encountered\n" ); + warning = true; + pc--; + goto loop; + } + assert( false ); + } + + } + dprintf( "Unhandled main opcode: $%02X\n", opcode ); + assert( false ); + +halt: + s_time &= 3; // increment by multiple of 4 +out_of_time: + pc--; + + s.time = s_time; + rg.flags = flags; + r.ix = ix; + r.iy = iy; + r.sp = sp; + r.pc = pc; + this->r.b = rg; + this->state_ = s; + this->state = &this->state_; + + return warning; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/console/Ay_Cpu.h Thu Nov 30 19:54:33 2006 -0800 @@ -0,0 +1,92 @@ +// Z80 CPU emulator + +// Game_Music_Emu 0.5.1 +#ifndef AY_CPU_H +#define AY_CPU_H + +#include "blargg_endian.h" + +typedef blargg_long cpu_time_t; + +// must be defined by caller +void ay_cpu_out( class Ay_Cpu*, cpu_time_t, unsigned addr, int data ); +int ay_cpu_in( class Ay_Cpu*, unsigned addr ); + +class Ay_Cpu { +public: + // Clear all registers and keep pointer to 64K memory passed in + void reset( void* mem_64k ); + + // Run until specified time is reached. Returns true if suspicious/unsupported + // instruction was encountered at any point during run. + bool run( cpu_time_t end_time ); + + // Time of beginning of next instruction + cpu_time_t time() const { return state->time + state->base; } + + // Alter current time. Not supported during run() call. + void set_time( cpu_time_t t ) { state->time = t - state->base; } + void adjust_time( int delta ) { state->time += delta; } + + typedef BOOST::uint8_t uint8_t; + typedef BOOST::uint16_t uint16_t; + + #if BLARGG_BIG_ENDIAN + struct regs_t { uint8_t b, c, d, e, h, l, flags, a; }; + #else + struct regs_t { uint8_t c, b, e, d, l, h, a, flags; }; + #endif + BOOST_STATIC_ASSERT( sizeof (regs_t) == 8 ); + + struct pairs_t { uint16_t bc, de, hl, fa; }; + + // Registers are not updated until run() returns + struct registers_t { + uint16_t pc; + uint16_t sp; + uint16_t ix; + uint16_t iy; + union { + regs_t b; // b.b, b.c, b.d, b.e, b.h, b.l, b.flags, b.a + pairs_t w; // w.bc, w.de, w.hl. w.fa + }; + union { + regs_t b; + pairs_t w; + } alt; + uint8_t iff1; + uint8_t iff2; + uint8_t r; + uint8_t i; + uint8_t im; + }; + //registers_t r; (below for efficiency) + + // can read this far past end of memory + enum { cpu_padding = 0x100 }; + +public: + Ay_Cpu(); +private: + uint8_t szpc [0x200]; + uint8_t* mem; + cpu_time_t end_time_; + struct state_t { + cpu_time_t base; + cpu_time_t time; + }; + state_t* state; // points to state_ or a local copy within run() + state_t state_; + void set_end_time( cpu_time_t t ); +public: + registers_t r; +}; + +inline void Ay_Cpu::set_end_time( cpu_time_t t ) +{ + cpu_time_t delta = state->base - t; + state->base = t; + state->time += delta; +} + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/console/Ay_Emu.cxx Thu Nov 30 19:54:33 2006 -0800 @@ -0,0 +1,346 @@ +// Game_Music_Emu 0.5.1. http://www.slack.net/~ant/ + +#include "Ay_Emu.h" + +#include "blargg_endian.h" +#include <string.h> + +/* Copyright (C) 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" + +unsigned const ram_start = 0x4000; +int const osc_count = Ay_Apu::osc_count + 1; + +Ay_Emu::Ay_Emu() +{ + beeper_output = 0; + set_type( gme_ay_type ); + + static const char* const names [osc_count] = { + "Wave 1", "Wave 2", "Wave 3", "Beeper" + }; + set_voice_names( names ); + + static int const types [osc_count] = { + wave_type | 0, wave_type | 1, wave_type | 2, mixed_type | 0 + }; + set_voice_types( types ); + set_silence_lookahead( 6 ); +} + +Ay_Emu::~Ay_Emu() { } + +// Track info + +static byte const* get_data( Ay_Emu::file_t const& file, byte const* ptr, int min_size ) +{ + long pos = ptr - (byte const*) file.header; + long file_size = file.end - (byte const*) file.header; + assert( (unsigned long) pos <= (unsigned long) file_size - 2 ); + int offset = (BOOST::int16_t) get_be16( ptr ); + if ( !offset || blargg_ulong (pos + offset) > blargg_ulong (file_size - min_size) ) + return 0; + return ptr + offset; +} + +static blargg_err_t parse_header( byte const* in, long size, Ay_Emu::file_t* out ) +{ + typedef Ay_Emu::header_t header_t; + out->header = (header_t const*) in; + out->end = in + size; + + if ( size < (long) sizeof (header_t) ) + return gme_wrong_file_type; + + header_t const& h = *(header_t const*) in; + if ( memcmp( h.tag, "ZXAYEMUL", 8 ) ) + return gme_wrong_file_type; + + out->tracks = get_data( *out, h.track_info, (h.max_track + 1) * 4 ); + if ( !out->tracks ) + return "Missing track data"; + + return 0; +} + +static void copy_ay_fields( Ay_Emu::file_t const& file, track_info_t* out, int track ) +{ + Gme_File::copy_field_( out->song, (char const*) get_data( file, file.tracks + track * 4, 1 ) ); + byte const* track_info = get_data( file, file.tracks + track * 4 + 2, 6 ); + if ( track_info ) + out->length = get_be16( track_info + 4 ) * (1000L / 50); // frames to msec + + Gme_File::copy_field_( out->author, (char const*) get_data( file, file.header->author, 1 ) ); + Gme_File::copy_field_( out->comment, (char const*) get_data( file, file.header->comment, 1 ) ); +} + +blargg_err_t Ay_Emu::track_info_( track_info_t* out, int track ) const +{ + copy_ay_fields( file, out, track ); + return 0; +} + +struct Ay_File : Gme_Info_ +{ + Ay_Emu::file_t file; + + Ay_File() { set_type( gme_ay_type ); } + + blargg_err_t load_mem_( byte const* begin, long size ) + { + RETURN_ERR( parse_header( begin, size, &file ) ); + set_track_count( file.header->max_track + 1 ); + return 0; + } + + blargg_err_t track_info_( track_info_t* out, int track ) const + { + copy_ay_fields( file, out, track ); + return 0; + } +}; + +static Music_Emu* new_ay_emu () { return BLARGG_NEW Ay_Emu ; } +static Music_Emu* new_ay_file() { return BLARGG_NEW Ay_File; } + +gme_type_t_ const gme_ay_type [1] = { "Sinclair Spectrum", 0, &new_ay_emu, &new_ay_file, "AY", 1 }; + +// Setup + +blargg_err_t Ay_Emu::load_mem_( byte const* in, long size ) +{ + RETURN_ERR( parse_header( in, size, &file ) ); + set_track_count( file.header->max_track + 1 ); + + if ( file.header->vers > 2 ) + set_warning( "Unknown file version" ); + + set_voice_count( osc_count ); + apu.volume( gain() ); + + return setup_buffer( 3546900 ); +} + +void Ay_Emu::update_eq( blip_eq_t const& eq ) +{ + apu.treble_eq( eq ); +} + +void Ay_Emu::set_voice( int i, Blip_Buffer* center, Blip_Buffer*, Blip_Buffer* ) +{ + if ( i >= Ay_Apu::osc_count ) + beeper_output = center; + else + apu.osc_output( i, center ); +} + +// Emulation + +void Ay_Emu::set_tempo_( double t ) +{ + play_period = blip_time_t (clock_rate() / 50 / t); +} + +blargg_err_t Ay_Emu::start_track_( int track ) +{ + RETURN_ERR( Classic_Emu::start_track_( track ) ); + + memset( mem + 0x0000, 0xC9, 0x100 ); // fill RST vectors with RET + memset( mem + 0x0100, 0xFF, 0x4000 - 0x100 ); + memset( mem + ram_start, 0x00, sizeof mem - ram_start ); + + // locate data blocks + byte const* const data = get_data( file, file.tracks + track * 4 + 2, 14 ); + if ( !data ) return "File data missing"; + + byte const* const more_data = get_data( file, data + 10, 6 ); + if ( !more_data ) return "File data missing"; + + byte const* blocks = get_data( file, data + 12, 8 ); + if ( !blocks ) return "File data missing"; + + // initial addresses + cpu::reset( mem ); + r.sp = get_be16( more_data ); + r.b.a = r.b.b = r.b.d = r.b.h = data [8]; + r.b.flags = r.b.c = r.b.e = r.b.l = data [9]; + r.alt.w = r.w; + r.ix = r.iy = r.w.hl; + + unsigned addr = get_be16( blocks ); + if ( !addr ) return "File data missing"; + + unsigned init = get_be16( more_data + 2 ); + if ( !init ) + init = addr; + + // copy blocks into memory + do + { + blocks += 2; + unsigned len = get_be16( blocks ); blocks += 2; + if ( addr + len > 0x10000 ) + { + set_warning( "Bad data block size" ); + len = 0x10000 - addr; + } + check( len ); + byte const* in = get_data( file, blocks, 0 ); blocks += 2; + if ( len > blargg_ulong (file.end - in) ) + { + set_warning( "Missing file data" ); + len = file.end - in; + } + //dprintf( "addr: $%04X, len: $%04X\n", addr, len ); + if ( addr < ram_start && addr >= 0x400 ) // several tracks use low data + dprintf( "Block addr in ROM\n" ); + memcpy( mem + addr, in, len ); + + if ( file.end - blocks < 8 ) + { + set_warning( "Missing file data" ); + break; + } + } + while ( (addr = get_be16( blocks )) != 0 ); + + // copy and configure driver + static byte const passive [] = { + 0xF3, // DI + 0xCD, 0, 0, // CALL init + 0xED, 0x5E, // LOOP: IM 2 + 0xFB, // EI + 0x76, // HALT + 0x18, 0xFA // JR LOOP + }; + static byte const active [] = { + 0xF3, // DI + 0xCD, 0, 0, // CALL init + 0xED, 0x56, // LOOP: IM 1 + 0xFB, // EI + 0x76, // HALT + 0xCD, 0, 0, // CALL play + 0x18, 0xF7 // JR LOOP + }; + memcpy( mem, passive, sizeof passive ); + unsigned play_addr = get_be16( more_data + 4 ); + //dprintf( "Play: $%04X\n", play_addr ); + if ( play_addr ) + { + memcpy( mem, active, sizeof active ); + mem [ 9] = play_addr; + mem [10] = play_addr >> 8; + } + mem [2] = init; + mem [3] = init >> 8; + + mem [0x38] = 0xFB; // Put EI at interrupt vector (followed by RET) + + memcpy( mem + 0x10000, mem, sizeof mem - 0x10000 ); // some code wraps around (ugh) + + beeper_delta = int (apu.amp_range * 0.65); + last_beeper = 0; + apu.reset(); + next_play = play_period; + + return 0; +} + +// Emulation + +void ay_cpu_out( Ay_Cpu* cpu, cpu_time_t time, unsigned addr, int data ) +{ + Ay_Emu& emu = STATIC_CAST(Ay_Emu&,*cpu); + + if ( (addr & 0xFF) == 0xFE ) + { + int delta = emu.beeper_delta; + data &= 0x10; + if ( emu.last_beeper != data ) + { + emu.last_beeper = data; + emu.beeper_delta = -delta; + if ( emu.beeper_output ) + emu.apu.synth_.offset( time, delta, emu.beeper_output ); + } + return; + } + + switch ( addr & 0xFEFF ) + { + case 0xFEFD: + emu.apu_addr = data & 0x0F; + return; + + case 0xBEFD: + emu.apu.write( time, emu.apu_addr, data ); + //remote_write( apu_addr, data ); + return; + } + + dprintf( "Unmapped OUT: $%04X <- $%02X\n", addr, data ); +} + +int ay_cpu_in( Ay_Cpu*, unsigned addr ) +{ + // keyboard read and other things + if ( (addr & 0xFF) == 0xFE ) return 0xFF; // other values break some beeper tunes + + dprintf( "Unmapped IN : $%04X\n", addr ); + return 0xFF; +} + +blargg_err_t Ay_Emu::run_clocks( blip_time_t& duration, int ) +{ + set_time( 0 ); + while ( time() < duration ) + { + //long start = time(); + cpu::run( min( duration, next_play ) ); + + if ( time() >= next_play ) + { + next_play += play_period; + + if ( r.iff1 ) + { + // TODO: don't interrupt if not enabled + if ( mem [r.pc] == 0x76 ) + r.pc++; + + r.iff1 = r.iff2 = 0; + + mem [--r.sp] = r.pc >> 8; + mem [--r.sp] = r.pc; + r.pc = 0x38; + cpu::adjust_time( 12 ); + if ( r.im == 2 ) + { + cpu::adjust_time( 6 ); + unsigned addr = r.i * 0x100u + 0xFF; + r.pc = mem [(addr + 1) & 0xFFFF] * 0x100u + mem [addr]; + } + } + } + //dprintf( "elapsed: %d\n", time() - start ); + //remote_frame(); + } + duration = time(); + next_play -= duration; + check( next_play >= 0 ); + adjust_time( -duration ); + + apu.end_frame( duration ); + + return 0; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/console/Ay_Emu.h Thu Nov 30 19:54:33 2006 -0800 @@ -0,0 +1,63 @@ +// Sinclair Spectrum AY music file emulator + +// Game_Music_Emu 0.5.1 +#ifndef AY_EMU_H +#define AY_EMU_H + +#include "Classic_Emu.h" +#include "Ay_Apu.h" +#include "Ay_Cpu.h" + +class Ay_Emu : private Ay_Cpu, public Classic_Emu { + typedef Ay_Cpu cpu; +public: + // AY file header + struct header_t + { + byte tag [8]; + byte vers; + byte player; + byte unused [2]; + byte author [2]; + byte comment [2]; + byte max_track; + byte first_track; + byte track_info [2]; + }; + BOOST_STATIC_ASSERT( sizeof (header_t) == 0x14 ); + + static gme_type_t static_type() { return gme_ay_type; } +public: + Ay_Emu(); + ~Ay_Emu(); + struct file_t { + header_t const* header; + byte const* end; + byte const* tracks; + }; +protected: + blargg_err_t track_info_( track_info_t*, int track ) const; + blargg_err_t load_mem_( byte const*, long ); + blargg_err_t start_track_( int ); + blargg_err_t run_clocks( blip_time_t&, int ); + void set_tempo_( double ); + void set_voice( int, Blip_Buffer*, Blip_Buffer*, Blip_Buffer* ); + void update_eq( blip_eq_t const& ); +private: + file_t file; + + unsigned play_addr; + cpu_time_t play_period; + cpu_time_t next_play; + Blip_Buffer* beeper_output; + int beeper_delta; + int last_beeper; + int apu_addr; + + // large items + byte mem [0x10000 + cpu_padding]; + Ay_Apu apu; + friend void ay_cpu_out( Ay_Cpu*, cpu_time_t, unsigned addr, int data ); +}; + +#endif
--- a/src/console/Blip_Buffer.cxx Wed Nov 29 14:42:11 2006 -0800 +++ b/src/console/Blip_Buffer.cxx Thu Nov 30 19:54:33 2006 -0800 @@ -1,5 +1,4 @@ - -// Blip_Buffer 0.4.0. http://www.slack.net/~ant/ +// Blip_Buffer 0.4.1. http://www.slack.net/~ant/ #include "Blip_Buffer.h" @@ -15,25 +14,29 @@ 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 */ +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 */ -int const buffer_extra = blip_widest_impulse_ + 2; +#ifdef BLARGG_ENABLE_OPTIMIZER + #include BLARGG_ENABLE_OPTIMIZER +#endif + +int const silent_buf_size = 1; // size used for Silent_Blip_Buffer Blip_Buffer::Blip_Buffer() { - factor_ = LONG_MAX; - offset_ = 0; - buffer_ = 0; - buffer_size_ = 0; - sample_rate_ = 0; - reader_accum = 0; - bass_shift = 0; - clock_rate_ = 0; - bass_freq_ = 16; - length_ = 0; + factor_ = LONG_MAX; + offset_ = 0; + buffer_ = 0; + buffer_size_ = 0; + sample_rate_ = 0; + reader_accum_ = 0; + bass_shift_ = 0; + clock_rate_ = 0; + bass_freq_ = 16; + length_ = 0; // assumptions code makes about implementation-defined features #ifndef NDEBUG @@ -49,24 +52,40 @@ Blip_Buffer::~Blip_Buffer() { - free( buffer_ ); + if ( buffer_size_ != silent_buf_size ) + free( buffer_ ); +} + +Silent_Blip_Buffer::Silent_Blip_Buffer() +{ + factor_ = 0; + buffer_ = buf; + buffer_size_ = silent_buf_size; + memset( buf, 0, sizeof buf ); // in case machine takes exception for signed overflow } void Blip_Buffer::clear( int entire_buffer ) { - offset_ = 0; - reader_accum = 0; + offset_ = 0; + reader_accum_ = 0; + modified_ = 0; if ( buffer_ ) { long count = (entire_buffer ? buffer_size_ : samples_avail()); - memset( buffer_, 0, (count + buffer_extra) * sizeof (buf_t_) ); + memset( buffer_, 0, (count + blip_buffer_extra_) * sizeof (buf_t_) ); } } Blip_Buffer::blargg_err_t Blip_Buffer::set_sample_rate( long new_rate, int msec ) { + if ( 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) - buffer_extra - 64; + 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; @@ -78,13 +97,14 @@ if ( buffer_size_ != new_size ) { - void* p = realloc( buffer_, (new_size + buffer_extra) * sizeof *buffer_ ); + void* p = realloc( buffer_, (new_size + blip_buffer_extra_) * sizeof *buffer_ ); if ( !p ) return "Out of memory"; buffer_ = (buf_t_*) p; } buffer_size_ = new_size; + assert( buffer_size_ != silent_buf_size ); // update things based on the sample rate sample_rate_ = new_rate; @@ -103,7 +123,7 @@ blip_resampled_time_t Blip_Buffer::clock_rate_factor( long clock_rate ) const { double ratio = (double) sample_rate_ / clock_rate; - long factor = (long) floor( ratio * (1L << BLIP_BUFFER_ACCURACY) + 0.5 ); + blip_long factor = (blip_long) floor( ratio * (1L << BLIP_BUFFER_ACCURACY) + 0.5 ); assert( factor > 0 || !sample_rate_ ); // fails if clock/output ratio is too large return (blip_resampled_time_t) factor; } @@ -118,7 +138,7 @@ long f = (freq << 16) / sample_rate_; while ( (f >>= 1) && --shift ) { } } - bass_shift = shift; + bass_shift_ = shift; } void Blip_Buffer::end_frame( blip_time_t t ) @@ -142,6 +162,12 @@ blip_time_t Blip_Buffer::count_clocks( long count ) const { + if ( !factor_ ) + { + assert( 0 ); // sample rate and clock rates must be set first + return 0; + } + if ( count > buffer_size_ ) count = buffer_size_; blip_resampled_time_t time = (blip_resampled_time_t) count << BLIP_BUFFER_ACCURACY; @@ -155,7 +181,7 @@ remove_silence( count ); // copy remaining samples to beginning and clear old samples - long remain = samples_avail() + buffer_extra; + long remain = samples_avail() + blip_buffer_extra_; memmove( buffer_, buffer_ + count, remain * sizeof *buffer_ ); memset( buffer_ + remain, 0, count * sizeof *buffer_ ); } @@ -163,6 +189,20 @@ // Blip_Synth_ +#if BLIP_BUFFER_FAST + Blip_Synth_::Blip_Synth_() + { + buf = 0; + last_amp = 0; + delta_factor = 0; + } + + void Blip_Synth_::volume_unit( double new_unit ) + { + delta_factor = int (new_unit * (1L << blip_sample_bits) + 0.5); + } +#else + Blip_Synth_::Blip_Synth_( short* p, int w ) : impulses( p ), width( w ) @@ -174,7 +214,8 @@ delta_factor = 0; } -static double const pi = 3.1415926535897932384626433832795029; +#undef PI +#define PI 3.1415926535897932384626433832795029 static void gen_sinc( float* out, int count, double oversample, double treble, double cutoff ) { @@ -189,7 +230,7 @@ double const maxh = 4096.0; double const rolloff = pow( 10.0, 1.0 / (maxh * 20.0) * treble / (1.0 - cutoff) ); double const pow_a_n = pow( rolloff, maxh - maxh * cutoff ); - double const to_angle = pi / 2 / maxh / oversample; + double const to_angle = PI / 2 / maxh / oversample; for ( int i = 0; i < count; i++ ) { double angle = ((i - count) * 2 + 1) * to_angle; @@ -220,7 +261,7 @@ gen_sinc( out, count, blip_res * oversample, treble, cutoff ); // apply (half of) hamming window - double to_fraction = pi / (count - 1); + double to_fraction = PI / (count - 1); for ( int i = count; i--; ) out [i] *= 0.54 - 0.46 * cos( i * to_fraction ); } @@ -338,8 +379,9 @@ //printf( "delta_factor: %d, kernel_unit: %d\n", delta_factor, kernel_unit ); } } +#endif -long Blip_Buffer::read_samples( blip_sample_t* out, long max_samples, int stereo ) +long Blip_Buffer::read_samples( blip_sample_t* BLIP_RESTRICT out, long max_samples, int stereo ) { long count = samples_avail(); if ( count > max_samples ) @@ -347,42 +389,34 @@ if ( count ) { - int const sample_shift = blip_sample_bits - 16; - int const bass_shift = this->bass_shift; - long accum = reader_accum; - buf_t_* in = buffer_; + int const bass = BLIP_READER_BASS( *this ); + BLIP_READER_BEGIN( reader, *this ); if ( !stereo ) { - for ( long n = count; n--; ) + for ( blip_long n = count; n; --n ) { - long s = accum >> sample_shift; - accum -= accum >> bass_shift; - accum += *in++; + blip_long s = BLIP_READER_READ( reader ); + if ( (blip_sample_t) s != s ) + s = 0x7FFF - (s >> 24); *out++ = (blip_sample_t) s; - - // clamp sample - if ( (blip_sample_t) s != s ) - out [-1] = (blip_sample_t) (0x7FFF - (s >> 24)); + BLIP_READER_NEXT( reader, bass ); } } else { - for ( long n = count; n--; ) + for ( blip_long n = count; n; --n ) { - long s = accum >> sample_shift; - accum -= accum >> bass_shift; - accum += *in++; + blip_long s = BLIP_READER_READ( reader ); + if ( (blip_sample_t) s != s ) + s = 0x7FFF - (s >> 24); *out = (blip_sample_t) s; out += 2; - - // clamp sample - if ( (blip_sample_t) s != s ) - out [-2] = (blip_sample_t) (0x7FFF - (s >> 24)); + BLIP_READER_NEXT( reader, bass ); } } + BLIP_READER_END( reader, *this ); - reader_accum = accum; remove_samples( count ); } return count; @@ -390,13 +424,19 @@ void Blip_Buffer::mix_samples( blip_sample_t const* in, long count ) { + if ( buffer_size_ == silent_buf_size ) + { + assert( 0 ); + return; + } + buf_t_* out = buffer_ + (offset_ >> BLIP_BUFFER_ACCURACY) + blip_widest_impulse_ / 2; int const sample_shift = blip_sample_bits - 16; int prev = 0; while ( count-- ) { - long s = (long) *in++ << sample_shift; + blip_long s = (blip_long) *in++ << sample_shift; *out += s - prev; prev = s; ++out;
--- a/src/console/Blip_Buffer.h Wed Nov 29 14:42:11 2006 -0800 +++ b/src/console/Blip_Buffer.h Thu Nov 30 19:54:33 2006 -0800 @@ -1,13 +1,21 @@ - -// Band-limited sound synthesis and buffering +// Band-limited sound synthesis buffer -// Blip_Buffer 0.4.0 - +// Blip_Buffer 0.4.1 #ifndef BLIP_BUFFER_H #define BLIP_BUFFER_H + // internal + #include <limits.h> + #if INT_MAX >= 0x7FFFFFFF + typedef int blip_long; + typedef unsigned blip_ulong; + #else + typedef long blip_long; + typedef unsigned long blip_ulong; + #endif + // Time unit at source clock rate -typedef long blip_time_t; +typedef blip_long blip_time_t; // Output samples are 16-bit signed, with a range of -32768 to 32767 typedef short blip_sample_t; @@ -65,19 +73,21 @@ // Experimental features + // 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 count_clocks( long count ) const; + // Number of raw samples that can be mixed within frame of specified duration. long count_samples( blip_time_t duration ) const; // Mix 'count' samples from 'buf' into buffer. void mix_samples( blip_sample_t const* buf, long count ); - // 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 count_clocks( long count ) const; - // not documented yet - typedef unsigned long blip_resampled_time_t; + void set_modified() { modified_ = 1; } + int clear_modified() { int b = modified_; modified_ = 0; return b; } + typedef blip_ulong blip_resampled_time_t; void remove_silence( long count ); blip_resampled_time_t resampled_duration( int t ) const { return t * factor_; } blip_resampled_time_t resampled_time( blip_time_t t ) const { return t * factor_ + offset_; } @@ -95,18 +105,19 @@ Blip_Buffer( const Blip_Buffer& ); Blip_Buffer& operator = ( const Blip_Buffer& ); public: - typedef long buf_t_; - unsigned long factor_; + typedef blip_time_t buf_t_; + blip_ulong factor_; blip_resampled_time_t offset_; buf_t_* buffer_; - long buffer_size_; + blip_long buffer_size_; + blip_long reader_accum_; + int bass_shift_; private: - long reader_accum; - int bass_shift; long sample_rate_; long clock_rate_; int bass_freq_; int length_; + int modified_; friend class Blip_Reader; }; @@ -124,30 +135,41 @@ // noticeable broadband noise when synthesizing high frequency square waves. // Affects size of Blip_Synth objects since they store the waveform directly. #ifndef BLIP_PHASE_BITS - #define BLIP_PHASE_BITS 6 + #if BLIP_BUFFER_FAST + #define BLIP_PHASE_BITS 8 + #else + #define BLIP_PHASE_BITS 6 + #endif #endif // Internal - typedef unsigned long blip_resampled_time_t; + typedef blip_ulong blip_resampled_time_t; int const blip_widest_impulse_ = 16; + int const blip_buffer_extra_ = blip_widest_impulse_ + 2; int const blip_res = 1 << BLIP_PHASE_BITS; class blip_eq_t; class Blip_Synth_ { - double volume_unit_; - short* const impulses; - int const width; - long kernel_unit; - int impulses_size() const { return blip_res / 2 * width + 1; } - void adjust_impulse(); public: Blip_Buffer* buf; int last_amp; int delta_factor; + void volume_unit( double ); + #if BLIP_BUFFER_FAST + Blip_Synth_(); + void treble_eq( blip_eq_t const& ) { } + #else Blip_Synth_( short* impulses, int width ); void treble_eq( blip_eq_t const& ); - void volume_unit( double ); + private: + double volume_unit_; + short* const impulses; + int const width; + blip_long kernel_unit; + int impulses_size() const { return blip_res / 2 * width + 1; } + void adjust_impulse(); + #endif }; // Quality level. Start with blip_good_quality. @@ -164,7 +186,7 @@ // Set overall volume of waveform void volume( double v ) { impl.volume_unit( v * (1.0 / (range < 0 ? -range : range)) ); } - // Configure low-pass filter (see notes.txt) + // Configure low-pass filter (see blip_buffer.txt) void treble_eq( blip_eq_t const& eq ) { impl.treble_eq( eq ); } // Get/set Blip_Buffer used for output @@ -183,7 +205,7 @@ void offset( blip_time_t, int delta, Blip_Buffer* ) const; void offset( blip_time_t t, int delta ) const { offset( t, delta, impl.buf ); } - // Works directly in terms of fractional output samples. Contact author for more. + // Works directly in terms of fractional output samples. Contact author for more info. void offset_resampled( blip_resampled_time_t, int delta, Blip_Buffer* ) const; // Same as offset(), except code is inlined for higher performance @@ -194,12 +216,14 @@ offset_resampled( t * impl.buf->factor_ + impl.buf->offset_, delta, impl.buf ); } +private: + Blip_Synth_ impl; +#if !BLIP_BUFFER_FAST + typedef short imp_t; + imp_t impulses [blip_res * (quality / 2) + 1]; public: Blip_Synth() : impl( impulses, quality ) { } -private: - typedef short imp_t; - imp_t impulses [blip_res * (quality / 2) + 1]; - Blip_Synth_ impl; +#endif }; // Low-pass equalization parameters @@ -209,7 +233,7 @@ // treble, small positive values (0 to 5.0) increase treble. blip_eq_t( double treble_db = 0 ); - // See notes.txt + // See blip_buffer.txt blip_eq_t( double treble, long rolloff_freq, long sample_rate, long cutoff_freq = 0 ); private: @@ -223,55 +247,84 @@ int const blip_sample_bits = 30; -// Optimized inline sample reader for custom sample formats and mixing of Blip_Buffer samples -class Blip_Reader { +// Dummy Blip_Buffer to direct sound output to, for easy muting without +// having to stop sound code. +class Silent_Blip_Buffer : public Blip_Buffer { + buf_t_ buf [blip_buffer_extra_ + 1]; public: - // Begin reading samples from buffer. Returns value to pass to next() (can - // be ignored if default bass_freq is acceptable). - int begin( Blip_Buffer& ); - - // Current sample - long read() const { return accum >> (blip_sample_bits - 16); } + // The following cannot be used (an assertion will fail if attempted): + blargg_err_t set_sample_rate( long samples_per_sec, int msec_length ); + blip_time_t count_clocks( long count ) const; + void mix_samples( blip_sample_t const* buf, long count ); - // Current raw sample in full internal resolution - long read_raw() const { return accum; } - - // Advance to next sample - void next( int bass_shift = 9 ) { accum += *buf++ - (accum >> bass_shift); } - - // End reading samples from buffer. The number of samples read must now be removed - // using Blip_Buffer::remove_samples(). - void end( Blip_Buffer& b ) { b.reader_accum = accum; } - -private: - const Blip_Buffer::buf_t_* buf; - long accum; + Silent_Blip_Buffer(); }; + #if defined (__GNUC__) || _MSC_VER >= 1100 + #define BLIP_RESTRICT __restrict + #else + #define BLIP_RESTRICT + #endif -// End of public interface +// 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 ) \ + const Blip_Buffer::buf_t_* BLIP_RESTRICT name##_reader_buf = (blip_buffer).buffer_;\ + blip_long 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 control +int const blip_reader_default_bass = 9; -#include <assert.h> +// Current sample +#define BLIP_READER_READ( name ) (name##_reader_accum >> (blip_sample_bits - 16)) + +// Current raw sample in full internal resolution +#define BLIP_READER_READ_RAW( name ) (name##_reader_accum) + +// Advance to next sample +#define BLIP_READER_NEXT( name, bass ) \ + (void) (name##_reader_accum += *name##_reader_buf++ - (name##_reader_accum >> (bass))) + +// End reading samples from buffer. The number of samples read must now be removed +// using Blip_Buffer::remove_samples(). +#define BLIP_READER_END( name, blip_buffer ) \ + (void) ((blip_buffer).reader_accum_ = name##_reader_accum) + // Compatibility with older version const long blip_unscaled = 65535; const int blip_low_quality = blip_med_quality; const int blip_best_quality = blip_high_quality; -#define BLIP_FWD( i ) { \ - long t0 = i0 * delta + buf [fwd + i]; \ - long t1 = imp [blip_res * (i + 1)] * delta + buf [fwd + 1 + i]; \ - i0 = imp [blip_res * (i + 2)]; \ - buf [fwd + i] = t0; \ - buf [fwd + 1 + i] = t1; } +// Deprecated; use BLIP_READER macros as follows: +// Blip_Reader r; r.begin( buf ); -> BLIP_READER_BEGIN( r, buf ); +// int bass = r.begin( buf ) -> BLIP_READER_BEGIN( r, buf ); int bass = BLIP_READER_BASS( buf ); +// r.read() -> BLIP_READER_READ( r ) +// r.read_raw() -> BLIP_READER_READ_RAW( r ) +// r.next( bass ) -> BLIP_READER_NEXT( r, bass ) +// r.next() -> BLIP_READER_NEXT( r, blip_reader_default_bass ) +// r.end( buf ) -> BLIP_READER_END( r, buf ) +class Blip_Reader { +public: + int begin( Blip_Buffer& ); + blip_long read() const { return accum >> (blip_sample_bits - 16); } + blip_long read_raw() const { return accum; } + void next( int bass_shift = 9 ) { accum += *buf++ - (accum >> bass_shift); } + void end( Blip_Buffer& b ) { b.reader_accum_ = accum; } + +private: + const Blip_Buffer::buf_t_* buf; + blip_long accum; +}; -#define BLIP_REV( r ) { \ - long t0 = i0 * delta + buf [rev - r]; \ - long t1 = imp [blip_res * r] * delta + buf [rev + 1 - r]; \ - i0 = imp [blip_res * (r - 1)]; \ - buf [rev - r] = t0; \ - buf [rev + 1 - r] = t1; } +// End of public interface + +#include <assert.h> template<int quality,int range> inline void Blip_Synth<quality,range>::offset_resampled( blip_resampled_time_t time, @@ -279,48 +332,123 @@ { // 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( (long) (time >> BLIP_BUFFER_ACCURACY) < blip_buf->buffer_size_ ); + assert( (blip_long) (time >> BLIP_BUFFER_ACCURACY) < blip_buf->buffer_size_ ); delta *= impl.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)); - imp_t const* imp = impulses + blip_res - phase; - long* buf = blip_buf->buffer_ + (time >> BLIP_BUFFER_ACCURACY); - long i0 = *imp; + +#if BLIP_BUFFER_FAST + blip_long 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; + left -= right; + right += buf [1]; + + buf [0] = left; + buf [1] = right; +#else + int const fwd = (blip_widest_impulse_ - quality) / 2; int const rev = fwd + quality - 2; + int const mid = quality / 2 - 1; - BLIP_FWD( 0 ) - if ( quality > 8 ) BLIP_FWD( 2 ) - if ( quality > 12 ) BLIP_FWD( 4 ) - { - int const mid = quality / 2 - 1; - long t0 = i0 * delta + buf [fwd + mid - 1]; - long t1 = imp [blip_res * mid] * delta + buf [fwd + mid]; - imp = impulses + phase; - i0 = imp [blip_res * mid]; - buf [fwd + mid - 1] = t0; - buf [fwd + mid] = t1; + imp_t const* BLIP_RESTRICT imp = impulses + blip_res - phase; + + #if defined (_M_IX86) || defined (_M_IA64) || defined (__i486__) || \ + defined (__x86_64__) || defined (__ia64__) || defined (__i386__) + + // straight forward implementation resulted in better code on GCC for x86 + + #define ADD_IMP( out, in ) \ + buf [out] += (blip_long) imp [blip_res * (in)] * delta + + #define BLIP_FWD( i ) {\ + ADD_IMP( fwd + i, i );\ + ADD_IMP( fwd + 1 + i, i + 1 );\ + } + #define BLIP_REV( r ) {\ + ADD_IMP( rev - r, r + 1 );\ + ADD_IMP( rev + 1 - r, r );\ } - if ( quality > 12 ) BLIP_REV( 6 ) - if ( quality > 8 ) BLIP_REV( 4 ) - BLIP_REV( 2 ) + + BLIP_FWD( 0 ) + if ( quality > 8 ) BLIP_FWD( 2 ) + if ( quality > 12 ) BLIP_FWD( 4 ) + { + ADD_IMP( fwd + mid - 1, mid - 1 ); + ADD_IMP( fwd + mid , mid ); + imp = impulses + phase; + } + if ( quality > 12 ) BLIP_REV( 6 ) + if ( quality > 8 ) BLIP_REV( 4 ) + BLIP_REV( 2 ) + + ADD_IMP( rev , 1 ); + ADD_IMP( rev + 1, 0 ); + + #else + + // for RISC processors, help compiler by reading ahead of writes - long t0 = i0 * delta + buf [rev]; - long t1 = *imp * delta + buf [rev + 1]; - buf [rev] = t0; - buf [rev + 1] = t1; + #define BLIP_FWD( i ) {\ + blip_long t0 = i0 * delta + buf [fwd + i];\ + blip_long t1 = imp [blip_res * (i + 1)] * delta + buf [fwd + 1 + i];\ + i0 = imp [blip_res * (i + 2)];\ + buf [fwd + i] = t0;\ + buf [fwd + 1 + i] = t1;\ + } + #define BLIP_REV( r ) {\ + blip_long t0 = i0 * delta + buf [rev - r];\ + blip_long t1 = imp [blip_res * r] * delta + buf [rev + 1 - r];\ + i0 = imp [blip_res * (r - 1)];\ + buf [rev - r] = t0;\ + buf [rev + 1 - r] = t1;\ + } + + blip_long i0 = *imp; + BLIP_FWD( 0 ) + if ( quality > 8 ) BLIP_FWD( 2 ) + if ( quality > 12 ) BLIP_FWD( 4 ) + { + blip_long t0 = i0 * delta + buf [fwd + mid - 1]; + blip_long t1 = imp [blip_res * mid] * delta + buf [fwd + mid ]; + imp = impulses + phase; + i0 = imp [blip_res * mid]; + buf [fwd + mid - 1] = t0; + buf [fwd + mid ] = t1; + } + if ( quality > 12 ) BLIP_REV( 6 ) + if ( quality > 8 ) BLIP_REV( 4 ) + BLIP_REV( 2 ) + + blip_long t0 = i0 * delta + buf [rev ]; + blip_long t1 = *imp * delta + buf [rev + 1]; + buf [rev ] = t0; + buf [rev + 1] = t1; + #endif + +#endif } #undef BLIP_FWD #undef BLIP_REV template<int quality,int range> +#if BLIP_BUFFER_FAST + inline +#endif void Blip_Synth<quality,range>::offset( blip_time_t t, int delta, Blip_Buffer* buf ) const { offset_resampled( t * buf->factor_ + buf->offset_, delta, buf ); } template<int quality,int range> +#if BLIP_BUFFER_FAST + inline +#endif void Blip_Synth<quality,range>::update( blip_time_t t, int amp ) { int delta = amp - impl.last_amp; @@ -343,12 +471,11 @@ inline int Blip_Reader::begin( Blip_Buffer& blip_buf ) { buf = blip_buf.buffer_; - accum = blip_buf.reader_accum; - return blip_buf.bass_shift; + accum = blip_buf.reader_accum_; + return blip_buf.bass_shift_; } int const blip_max_length = 0; int const blip_default_length = 250; #endif -
--- a/src/console/Classic_Emu.cxx Wed Nov 29 14:42:11 2006 -0800 +++ b/src/console/Classic_Emu.cxx Thu Nov 30 19:54:33 2006 -0800 @@ -1,9 +1,9 @@ - -// Game_Music_Emu 0.3.0. http://www.slack.net/~ant/ +// Game_Music_Emu 0.5.1. http://www.slack.net/~ant/ #include "Classic_Emu.h" #include "Multi_Buffer.h" +#include <string.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 @@ -11,17 +11,23 @@ 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 */ +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_BEGIN +#include "blargg_source.h" Classic_Emu::Classic_Emu() { - buf = NULL; - stereo_buffer = NULL; + buf = 0; + stereo_buffer = 0; + voice_types = 0; + + // avoid inconsistency in our duplicated constants + assert( (int) wave_type == (int) Multi_Buffer::wave_type ); + assert( (int) noise_type == (int) Multi_Buffer::noise_type ); + assert( (int) mixed_type == (int) Multi_Buffer::mixed_type ); } Classic_Emu::~Classic_Emu() @@ -29,89 +35,145 @@ delete stereo_buffer; } -void Classic_Emu::set_equalizer( equalizer_t const& eq ) +void Classic_Emu::set_equalizer_( equalizer_t const& eq ) { - Music_Emu::set_equalizer( eq ); + Music_Emu::set_equalizer_( eq ); update_eq( eq.treble ); if ( buf ) buf->bass_freq( equalizer().bass ); } -blargg_err_t Classic_Emu::set_sample_rate( long sample_rate ) +blargg_err_t Classic_Emu::set_sample_rate_( long sample_rate ) { if ( !buf ) { if ( !stereo_buffer ) - BLARGG_CHECK_ALLOC( stereo_buffer = BLARGG_NEW Stereo_Buffer ); + CHECK_ALLOC( stereo_buffer = BLARGG_NEW Stereo_Buffer ); buf = stereo_buffer; } - - BLARGG_RETURN_ERR( buf->set_sample_rate( sample_rate, 1000 / 20 ) ); - return Music_Emu::set_sample_rate( sample_rate ); + return buf->set_sample_rate( sample_rate, 1000 / 20 ); } -void Classic_Emu::mute_voices( int mask ) +void Classic_Emu::mute_voices_( int mask ) { - require( buf ); // set_sample_rate() must have been called - - Music_Emu::mute_voices( mask ); + Music_Emu::mute_voices_( mask ); for ( int i = voice_count(); i--; ) { if ( mask & (1 << i) ) { - set_voice( i, NULL, NULL, NULL ); + set_voice( i, 0, 0, 0 ); } else { - Multi_Buffer::channel_t ch = buf->channel( i ); + Multi_Buffer::channel_t ch = buf->channel( i, (voice_types ? voice_types [i] : 0) ); + assert( (ch.center && ch.left && ch.right) || + (!ch.center && !ch.left && !ch.right) ); // all or nothing set_voice( i, ch.center, ch.left, ch.right ); } } } -blargg_err_t Classic_Emu::setup_buffer( long new_clock_rate ) +blargg_err_t Classic_Emu::setup_buffer( long rate ) { - require( sample_rate() ); // fails if set_sample_rate() hasn't been called yet - - clock_rate = new_clock_rate; - buf->clock_rate( clock_rate ); - BLARGG_RETURN_ERR( buf->set_channel_count( voice_count() ) ); + clock_rate_ = rate; + buf->clock_rate( rate ); + RETURN_ERR( buf->set_channel_count( voice_count() ) ); set_equalizer( equalizer() ); - remute_voices(); - return blargg_success; + buf_changed_count = buf->channels_changed_count(); + return 0; } -void Classic_Emu::start_track( int track ) +blargg_err_t Classic_Emu::start_track_( int track ) { - Music_Emu::start_track( track ); + RETURN_ERR( Music_Emu::start_track_( track ) ); buf->clear(); + return 0; } -blip_time_t Classic_Emu::run_clocks( blip_time_t t, bool* ) -{ - assert( false ); - return t; -} - -blip_time_t Classic_Emu::run( int msec, bool* added_stereo ) +blargg_err_t Classic_Emu::play_( long count, sample_t* out ) { - return run_clocks( (long) msec * clock_rate / 1000, added_stereo ); -} - -void Classic_Emu::play( long count, sample_t* out ) -{ - require( sample_rate() ); // fails if set_sample_rate() hasn't been called yet - long remain = count; while ( remain ) { remain -= buf->read_samples( &out [count - remain], remain ); if ( remain ) { - bool added_stereo = false; - blip_time_t clocks_emulated = run( buf->length(), &added_stereo ); - buf->end_frame( clocks_emulated, added_stereo ); + if ( buf_changed_count != buf->channels_changed_count() ) + { + buf_changed_count = buf->channels_changed_count(); + remute_voices(); + } + int msec = buf->length(); + blip_time_t clocks_emulated = (blargg_long) msec * clock_rate_ / 1000; + RETURN_ERR( run_clocks( clocks_emulated, msec ) ); + assert( clocks_emulated ); + buf->end_frame( clocks_emulated ); } } + return 0; } +// Rom_Data + +blargg_err_t Rom_Data_::load_rom_data_( Data_Reader& in, + int header_size, void* header_out, int fill, long pad_size ) +{ + long file_offset = pad_size - header_size; + + rom_addr = 0; + mask = 0; + size_ = 0; + rom.clear(); + + file_size_ = in.remain(); + if ( file_size_ <= header_size ) // <= because there must be data after header + return gme_wrong_file_type; + blargg_err_t err = rom.resize( file_offset + file_size_ + pad_size ); + if ( !err ) + err = in.read( rom.begin() + file_offset, file_size_ ); + if ( err ) + { + rom.clear(); + return err; + } + + file_size_ -= header_size; + memcpy( header_out, &rom [file_offset], header_size ); + + memset( rom.begin() , fill, pad_size ); + memset( rom.end() - pad_size, fill, pad_size ); + + return 0; +} + +void Rom_Data_::set_addr_( long addr, int unit ) +{ + rom_addr = addr - unit - pad_extra; + + long rounded = (addr + file_size_ + unit - 1) / unit * unit; + if ( rounded <= 0 ) + { + rounded = 0; + } + else + { + int shift = 0; + unsigned long max_addr = (unsigned long) (rounded - 1); + while ( max_addr >> shift ) + shift++; + mask = (1L << shift) - 1; + } + + if ( addr < 0 ) + addr = 0; + size_ = rounded; + if ( rom.resize( rounded - rom_addr + pad_extra ) ) { } // OK if shrink fails + + if ( 0 ) + { + dprintf( "addr: %X\n", addr ); + dprintf( "file_size: %d\n", file_size_ ); + dprintf( "rounded: %d\n", rounded ); + dprintf( "mask: $%X\n", mask ); + } +}
--- a/src/console/Classic_Emu.h Wed Nov 29 14:42:11 2006 -0800 +++ b/src/console/Classic_Emu.h Thu Nov 30 19:54:33 2006 -0800 @@ -1,41 +1,42 @@ +// Common aspects of emulators which use Blip_Buffer for sound output -// Classic game music emulator interface base class for emulators which use Blip_Buffer -// for sound output. - -// Game_Music_Emu 0.3.0 - +// Game_Music_Emu 0.5.1 #ifndef CLASSIC_EMU_H #define CLASSIC_EMU_H +#include "blargg_common.h" +#include "Blip_Buffer.h" #include "Music_Emu.h" -class Blip_Buffer; -class blip_eq_t; -typedef long blip_time_t; class Classic_Emu : public Music_Emu { public: Classic_Emu(); ~Classic_Emu(); - blargg_err_t set_sample_rate( long sample_rate ); void set_buffer( Multi_Buffer* ); - void mute_voices( int ); - void play( long, sample_t* ); - void start_track( int track ); - void set_equalizer( equalizer_t const& ); -public: - // deprecated - blargg_err_t init( long rate ) { return set_sample_rate( rate ); } protected: - virtual blargg_err_t setup_buffer( long clock_rate ); + // Services + enum { wave_type = 0x100, noise_type = 0x200, mixed_type = wave_type | noise_type }; + void set_voice_types( int const* t ) { voice_types = t; } + blargg_err_t setup_buffer( long clock_rate ); + long clock_rate() const { return clock_rate_; } + + // Overridable virtual void set_voice( int index, Blip_Buffer* center, Blip_Buffer* left, Blip_Buffer* right ) = 0; - virtual blip_time_t run( int msec, bool* added_stereo ); - virtual blip_time_t run_clocks( blip_time_t, bool* added_stereo ); virtual void update_eq( blip_eq_t const& ) = 0; + virtual blargg_err_t start_track_( int track ) = 0; + virtual blargg_err_t run_clocks( blip_time_t& time_io, int msec ) = 0; +protected: + blargg_err_t set_sample_rate_( long sample_rate ); + void mute_voices_( int ); + void set_equalizer_( equalizer_t const& ); + blargg_err_t play_( long, sample_t* ); private: Multi_Buffer* buf; - Multi_Buffer* stereo_buffer; - long clock_rate; + Multi_Buffer* stereo_buffer; // NULL if using custom buffer + long clock_rate_; + unsigned buf_changed_count; + int const* voice_types; }; inline void Classic_Emu::set_buffer( Multi_Buffer* new_buf ) @@ -44,5 +45,72 @@ buf = new_buf; } +// ROM data handler, used by several Classic_Emu derivitives. Loads file data +// with padding on both sides, allowing direct use in bank mapping. The main purpose +// is to allow all file data to be loaded with only one read() call (for efficiency). + +class Rom_Data_ { +public: + typedef unsigned char byte; +protected: + enum { pad_extra = 8 }; + blargg_vector<byte> rom; + long file_size_; + blargg_long rom_addr; + blargg_long mask; + blargg_long size_; // TODO: eliminate + + blargg_err_t load_rom_data_( Data_Reader& in, int header_size, void* header_out, + int fill, long pad_size ); + void set_addr_( long addr, int unit ); +}; + +template<int unit> +class Rom_Data : public Rom_Data_ { + enum { pad_size = unit + pad_extra }; +public: + // Load file data, using already-loaded header 'h' if not NULL. Copy header + // from loaded file data into *out and fill unmapped bytes with 'fill'. + blargg_err_t load( Data_Reader& in, int header_size, void* header_out, int fill ) + { + return load_rom_data_( in, header_size, header_out, fill, pad_size ); + } + + // Size of file data read in (excluding header) + long file_size() const { return file_size_; } + + // Pointer to beginning of file data + byte* begin() const { return rom.begin() + pad_size; } + + // Set address that file data should start at + void set_addr( long addr ) { set_addr_( addr, unit ); } + + // Free data + void clear() { rom.clear(); } + + // Size of data + start addr, rounded to a multiple of unit + long size() const { return size_; } + + // Pointer to unmapped page filled with same value + byte* unmapped() { return rom.begin(); } + + // Mask address to nearest power of two greater than size() + blargg_long mask_addr( blargg_long addr ) const + { + #ifdef check + check( addr <= mask ); + #endif + return addr & mask; + } + + // Pointer to page starting at addr. Returns unmapped() if outside data. + byte* at_addr( blargg_long addr ) + { + blargg_ulong offset = mask_addr( addr ) - rom_addr; + if ( offset > blargg_ulong (rom.size() - pad_size) ) + offset = 0; // unmapped + return &rom [offset]; + } +}; + #endif -
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/console/Data_Reader.cxx Thu Nov 30 19:54:33 2006 -0800 @@ -0,0 +1,316 @@ +// File_Extractor 0.4.0. http://www.slack.net/~ant/ + +#include "Data_Reader.h" + +#include <assert.h> +#include <string.h> +#include <stdio.h> + +/* Copyright (C) 2005-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 */ + +const char Data_Reader::eof_error [] = "Unexpected end of file"; + +typedef Data_Reader::error_t error_t; + +error_t Data_Reader::read( void* p, long s ) +{ + long result = read_avail( p, s ); + if ( result != s ) + { + if ( result >= 0 && result < s ) + return eof_error; + + return "Read error"; + } + + return 0; +} + +error_t Data_Reader::skip( long count ) +{ + char buf [512]; + while ( count ) + { + long n = sizeof buf; + if ( n > count ) + n = count; + count -= n; + error_t err = read( buf, n ); + if ( err ) + return err; + } + return 0; +} + +long File_Reader::remain() const { return size() - tell(); } + +error_t File_Reader::skip( long n ) +{ + assert( n >= 0 ); + return n ? seek( tell() + n ) : 0; +} + +// Subset_Reader + +Subset_Reader::Subset_Reader( Data_Reader* dr, long size ) +{ + in = dr; + remain_ = dr->remain(); + if ( remain_ > size ) + remain_ = size; +} + +long Subset_Reader::remain() const { return remain_; } + +long Subset_Reader::read_avail( void* p, long s ) +{ + if ( s > remain_ ) + s = remain_; + remain_ -= s; + return in->read_avail( p, s ); +} + +// Remaining_Reader + +Remaining_Reader::Remaining_Reader( void const* h, long size, Data_Reader* r ) +{ + header = (char const*) h; + header_end = header + size; + in = r; +} + +long Remaining_Reader::remain() const { return header_end - header + in->remain(); } + +long Remaining_Reader::read_first( void* out, long count ) +{ + long first = header_end - header; + if ( first ) + { + if ( first > count ) + first = count; + void const* in = header; + header += first; + memcpy( out, in, first ); + } + return first; +} + +long Remaining_Reader::read_avail( void* out, long count ) +{ + long first = read_first( out, count ); + long remain = count - first; + if ( remain ) + { + remain = in->read_avail( (char*) out + first, remain ); + if ( remain <= 0 ) + return remain; + } + return first + remain; +} + +error_t Remaining_Reader::read( void* out, long count ) +{ + long first = read_first( out, count ); + long remain = count - first; + if ( !remain ) + return 0; + return in->read( (char*) out + first, remain ); +} + +// Mem_File_Reader + +Mem_File_Reader::Mem_File_Reader( const void* p, long s ) : + begin( (const char*) p ), + size_( s ) +{ + pos = 0; +} + +long Mem_File_Reader::size() const { return size_; } + +long Mem_File_Reader::read_avail( void* p, long s ) +{ + long r = remain(); + if ( s > r ) + s = r; + memcpy( p, begin + pos, s ); + pos += s; + return s; +} + +long Mem_File_Reader::tell() const { return pos; } + +error_t Mem_File_Reader::seek( long n ) +{ + if ( n > size_ ) + return eof_error; + pos = n; + return 0; +} + +// Std_File_Reader + +Std_File_Reader::Std_File_Reader() : file_( 0 ) { } + +Std_File_Reader::~Std_File_Reader() { close(); } + +error_t Std_File_Reader::open( const char* path ) +{ + file_ = fopen( path, "rb" ); + if ( !file_ ) + return "Couldn't open file"; + return 0; +} + +long Std_File_Reader::size() const +{ + long pos = tell(); + fseek( (FILE*) file_, 0, SEEK_END ); + long result = tell(); + fseek( (FILE*) file_, pos, SEEK_SET ); + return result; +} + +long Std_File_Reader::read_avail( void* p, long s ) +{ + return fread( p, 1, s, (FILE*) file_ ); +} + +error_t Std_File_Reader::read( void* p, long s ) +{ + if ( s == (long) fread( p, 1, s, (FILE*) file_ ) ) + return 0; + if ( feof( (FILE*) file_ ) ) + return eof_error; + return "Couldn't read from file"; +} + +long Std_File_Reader::tell() const { return ftell( (FILE*) file_ ); } + +error_t Std_File_Reader::seek( long n ) +{ + if ( !fseek( (FILE*) file_, n, SEEK_SET ) ) + return 0; + if ( n > size() ) + return eof_error; + return "Error seeking in file"; +} + +void Std_File_Reader::close() +{ + if ( file_ ) + { + fclose( (FILE*) file_ ); + file_ = 0; + } +} + +// Callback_Reader + +Callback_Reader::Callback_Reader( callback_t c, long size, void* d ) : + callback( c ), + data( d ) +{ + remain_ = size; +} + +long Callback_Reader::remain() const { return remain_; } + +long Callback_Reader::read_avail( void* out, long count ) +{ + if ( count > remain_ ) + count = remain_; + if ( Callback_Reader::read( out, count ) ) + count = -1; + return count; +} + +Callback_Reader::error_t Callback_Reader::read( void* out, long count ) +{ + if ( count > remain_ ) + return eof_error; + return callback( data, out, count ); +} + +// Gzip_File_Reader + +#ifdef HAVE_ZLIB_H + +#include "zlib.h" + +static const char* get_gzip_eof( const char* path, long* eof ) +{ + FILE* file = fopen( path, "rb" ); + if ( !file ) + return "Couldn't open file"; + + unsigned char buf [4]; + if ( fread( buf, 2, 1, file ) == 1 && buf [0] == 0x1F && buf [1] == 0x8B ) + { + fseek( file, -4, SEEK_END ); + fread( buf, 4, 1, file ); + *eof = buf [3] * 0x1000000L + buf [2] * 0x10000L + buf [1] * 0x100L + buf [0]; + } + else + { + fseek( file, 0, SEEK_END ); + *eof = ftell( file ); + } + const char* err = (ferror( file ) || feof( file )) ? "Couldn't get file size" : 0; + fclose( file ); + return err; +} + +Gzip_File_Reader::Gzip_File_Reader() : file_( 0 ) { } + +Gzip_File_Reader::~Gzip_File_Reader() { close(); } + +error_t Gzip_File_Reader::open( const char* path ) +{ + close(); + + error_t err = get_gzip_eof( path, &size_ ); + if ( err ) + return err; + + file_ = gzopen( path, "rb" ); + if ( !file_ ) + return "Couldn't open file"; + + return 0; +} + +long Gzip_File_Reader::size() const { return size_; } + +long Gzip_File_Reader::read_avail( void* p, long s ) { return gzread( file_, p, s ); } + +long Gzip_File_Reader::tell() const { return gztell( file_ ); } + +error_t Gzip_File_Reader::seek( long n ) +{ + if ( gzseek( file_, n, SEEK_SET ) >= 0 ) + return 0; + if ( n > size_ ) + return eof_error; + return "Error seeking in file"; +} + +void Gzip_File_Reader::close() +{ + if ( file_ ) + { + gzclose( file_ ); + file_ = 0; + } +} + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/console/Data_Reader.h Thu Nov 30 19:54:33 2006 -0800 @@ -0,0 +1,156 @@ +// Data reader interface for uniform access + +// File_Extractor 0.4.0 +#ifndef DATA_READER_H +#define DATA_READER_H + +#undef DATA_READER_H +// allow blargg_config.h to #include Data_Reader.h +#include "blargg_config.h" +#ifndef DATA_READER_H +#define DATA_READER_H + +// Supports reading and finding out how many bytes are remaining +class Data_Reader { +public: + Data_Reader() { } + virtual ~Data_Reader() { } + + static const char eof_error []; // returned by read() when request goes beyond end + + typedef const char* error_t; // NULL if successful + + // Read at most count bytes and return number actually read, or <= 0 if error + virtual long read_avail( void*, long n ) = 0; + + // Read exactly count bytes and return error if they couldn't be read + virtual error_t read( void*, long count ); + + // Number of bytes remaining until end of file + virtual long remain() const = 0; + + // Read and discard count bytes + virtual error_t skip( long count ); + +private: + // noncopyable + Data_Reader( const Data_Reader& ); + Data_Reader& operator = ( const Data_Reader& ); +}; + +// Supports seeking in addition to Data_Reader operations +class File_Reader : public Data_Reader { +public: + // Size of file + virtual long size() const = 0; + + // Current position in file + virtual long tell() const = 0; + + // Go to new position + virtual error_t seek( long ) = 0; + + long remain() const; + error_t skip( long n ); +}; + +// Disk file reader +class Std_File_Reader : public File_Reader { +public: + error_t open( const char* path ); + void close(); + +public: + Std_File_Reader(); + ~Std_File_Reader(); + long size() const; + error_t read( void*, long ); + long read_avail( void*, long ); + long tell() const; + error_t seek( long ); +private: + void* file_; +}; + +// Treats range of memory as a file +class Mem_File_Reader : public File_Reader { +public: + Mem_File_Reader( const void*, long size ); + +public: + long size() const; + long read_avail( void*, long ); + long tell() const; + error_t seek( long ); +private: + const char* const begin; + const long size_; + long pos; +}; + +// Makes it look like there are only count bytes remaining +class Subset_Reader : public Data_Reader { +public: + Subset_Reader( Data_Reader*, long count ); + +public: + long remain() const; + long read_avail( void*, long ); +private: + Data_Reader* in; + long remain_; +}; + +// Joins already-read header and remaining data into original file (to avoid seeking) +class Remaining_Reader : public Data_Reader { +public: + Remaining_Reader( void const* header, long size, Data_Reader* ); + +public: + long remain() const; + long read_avail( void*, long ); + error_t read( void*, long ); +private: + char const* header; + char const* header_end; + Data_Reader* in; + long read_first( void* out, long count ); +}; + +// Invokes callback function to read data. Size of data must be specified in advance. +class Callback_Reader : public Data_Reader { +public: + typedef error_t (*callback_t)( void* data, void* out, long count ); + Callback_Reader( callback_t, long size, void* data = 0 ); +public: + long read_avail( void*, long ); + error_t read( void*, long ); + long remain() const; +private: + callback_t const callback; + void* const data; + long remain_; +}; + +#ifdef HAVE_ZLIB_H +// Gzip compressed file reader +class Gzip_File_Reader : public File_Reader { +public: + error_t open( const char* path ); + void close(); + +public: + Gzip_File_Reader(); + ~Gzip_File_Reader(); + long size() const; + long read_avail( void*, long ); + long tell() const; + error_t seek( long ); +private: + void* file_; + long size_; +}; +#endif + +#endif +#endif
--- a/src/console/Dual_Resampler.cxx Wed Nov 29 14:42:11 2006 -0800 +++ b/src/console/Dual_Resampler.cxx Thu Nov 30 19:54:33 2006 -0800 @@ -1,5 +1,4 @@ - -// Game_Music_Emu 0.3.0. http://www.slack.net/~ant/ +// Game_Music_Emu 0.5.1. http://www.slack.net/~ant/ #include "Dual_Resampler.h" @@ -12,56 +11,69 @@ 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 */ +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" + +unsigned const resampler_extra = 256; -#include BLARGG_SOURCE_BEGIN +Dual_Resampler::Dual_Resampler() { } + +Dual_Resampler::~Dual_Resampler() { } -const unsigned resampler_extra = 256; - -Dual_Resampler::Dual_Resampler() +blargg_err_t Dual_Resampler::reset( int pairs ) { + // expand allocations a bit + RETURN_ERR( sample_buf.resize( (pairs + (pairs >> 2)) * 2 ) ); + resize( pairs ); + resampler_size = oversamples_per_frame + (oversamples_per_frame >> 2); + return resampler.buffer_size( resampler_size ); } -Dual_Resampler::~Dual_Resampler() +void Dual_Resampler::resize( int pairs ) { + int new_sample_buf_size = pairs * 2; + if ( sample_buf_size != new_sample_buf_size ) + { + if ( (unsigned) new_sample_buf_size > sample_buf.size() ) + { + check( false ); + return; + } + sample_buf_size = new_sample_buf_size; + oversamples_per_frame = int (pairs * resampler.ratio()) * 2 + 2; + clear(); + } } -blargg_err_t Dual_Resampler::resize( int pairs ) +void Dual_Resampler::play_frame_( Blip_Buffer& blip_buf, dsample_t* out ) { - BLARGG_RETURN_ERR( sample_buf.resize( pairs * 2 ) ); - buf_pos = sample_buf.size(); - oversamples_per_frame = int (pairs * resampler.ratio()) * 2 + 2; - return resampler.buffer_size( oversamples_per_frame + resampler_extra ); -} - -void Dual_Resampler::play_frame_( Blip_Buffer& blip_buf, sample_t* out ) -{ - long pair_count = sample_buf.size() >> 1; + long pair_count = sample_buf_size >> 1; blip_time_t blip_time = blip_buf.count_clocks( pair_count ); int sample_count = oversamples_per_frame - resampler.written(); int new_count = play_frame( blip_time, sample_count, resampler.buffer() ); - assert( unsigned (new_count - sample_count) < resampler_extra ); + assert( new_count < resampler_size ); blip_buf.end_frame( blip_time ); assert( blip_buf.samples_avail() == pair_count ); resampler.write( new_count ); - long count = resampler.read( sample_buf.begin(), sample_buf.size() ); - assert( count == (long) sample_buf.size() ); + long count = resampler.read( sample_buf.begin(), sample_buf_size ); + assert( count == (long) sample_buf_size ); mix_samples( blip_buf, out ); blip_buf.remove_samples( pair_count ); } -void Dual_Resampler::play( long count, sample_t* out, Blip_Buffer& blip_buf ) +void Dual_Resampler::dual_play( long count, dsample_t* out, Blip_Buffer& blip_buf ) { // empty extra buffer - long remain = sample_buf.size() - buf_pos; + long remain = sample_buf_size - buf_pos; if ( remain ) { if ( remain > count ) @@ -73,11 +85,11 @@ } // entire frames - while ( count >= (long) sample_buf.size() ) + while ( count >= (long) sample_buf_size ) { play_frame_( blip_buf, out ); - out += sample_buf.size(); - count -= sample_buf.size(); + out += sample_buf_size; + count -= sample_buf_size; } // extra @@ -90,29 +102,28 @@ } } -#include BLARGG_ENABLE_OPTIMIZER - -void Dual_Resampler::mix_samples( Blip_Buffer& blip_buf, sample_t* out ) +void Dual_Resampler::mix_samples( Blip_Buffer& blip_buf, dsample_t* out ) { Blip_Reader sn; int bass = sn.begin( blip_buf ); - const sample_t* in = sample_buf.begin(); + const dsample_t* in = sample_buf.begin(); - for ( int n = sample_buf.size() >> 1; n--; ) + for ( int n = sample_buf_size >> 1; n--; ) { int s = sn.read(); - long l = (long) in [0] * 2 + s; - sn.next( bass ); - long r = in [1]; + blargg_long l = (blargg_long) in [0] * 2 + s; if ( (BOOST::int16_t) l != l ) l = 0x7FFF - (l >> 24); - r = r * 2 + s; + + sn.next( bass ); + blargg_long r = (blargg_long) in [1] * 2 + s; + if ( (BOOST::int16_t) r != r ) + r = 0x7FFF - (r >> 24); + in += 2; out [0] = l; out [1] = r; out += 2; - if ( (BOOST::int16_t) r != r ) - out [-1] = 0x7FFF - (r >> 24); } sn.end( blip_buf );
--- a/src/console/Dual_Resampler.h Wed Nov 29 14:42:11 2006 -0800 +++ b/src/console/Dual_Resampler.h Thu Nov 30 19:54:33 2006 -0800 @@ -1,8 +1,6 @@ - // Combination of Fir_Resampler and Blip_Buffer mixing. Used by Sega FM emulators. -// Game_Music_Emu 0.3.0 - +// Game_Music_Emu 0.5.1 #ifndef DUAL_RESAMPLER_H #define DUAL_RESAMPLER_H @@ -14,24 +12,28 @@ Dual_Resampler(); virtual ~Dual_Resampler(); - typedef short sample_t; + typedef short dsample_t; double setup( double oversample, double rolloff, double gain ); - blargg_err_t resize( int pairs ); + blargg_err_t reset( int max_pairs ); + void resize( int pairs_per_frame ); void clear(); - void play( long count, sample_t* out, Blip_Buffer& ); + void dual_play( long count, dsample_t* out, Blip_Buffer& ); protected: - virtual int play_frame( blip_time_t, int pcm_count, sample_t* pcm_out ) = 0; + virtual int play_frame( blip_time_t, int pcm_count, dsample_t* pcm_out ) = 0; private: - blargg_vector<sample_t> sample_buf; + blargg_vector<dsample_t> sample_buf; + int sample_buf_size; int oversamples_per_frame; int buf_pos; + int resampler_size; + Fir_Resampler<12> resampler; - void mix_samples( Blip_Buffer&, sample_t* ); - void play_frame_( Blip_Buffer&, sample_t* ); + void mix_samples( Blip_Buffer&, dsample_t* ); + void play_frame_( Blip_Buffer&, dsample_t* ); }; inline double Dual_Resampler::setup( double oversample, double rolloff, double gain ) @@ -41,9 +43,8 @@ inline void Dual_Resampler::clear() { - buf_pos = sample_buf.size(); + buf_pos = sample_buf_size; resampler.clear(); } #endif -
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/console/Effects_Buffer.cxx Thu Nov 30 19:54:33 2006 -0800 @@ -0,0 +1,529 @@ +// Game_Music_Emu 0.5.1. http://www.slack.net/~ant/ + +#include "Effects_Buffer.h" + +#include <string.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 +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" + +#ifdef BLARGG_ENABLE_OPTIMIZER + #include BLARGG_ENABLE_OPTIMIZER +#endif + +typedef blargg_long fixed_t; + +#define TO_FIXED( f ) fixed_t ((f) * (1L << 15) + 0.5) +#define FMUL( x, y ) (((x) * (y)) >> 15) + +const unsigned echo_size = 4096; +const unsigned echo_mask = echo_size - 1; +BOOST_STATIC_ASSERT( (echo_size & echo_mask) == 0 ); // must be power of 2 + +const unsigned reverb_size = 8192 * 2; +const unsigned reverb_mask = reverb_size - 1; +BOOST_STATIC_ASSERT( (reverb_size & reverb_mask) == 0 ); // must be power of 2 + +Effects_Buffer::config_t::config_t() +{ + pan_1 = -0.15f; + pan_2 = 0.15f; + reverb_delay = 88.0f; + reverb_level = 0.12f; + echo_delay = 61.0f; + echo_level = 0.10f; + delay_variance = 18.0f; + effects_enabled = false; +} + +void Effects_Buffer::set_depth( double d ) +{ + float f = (float) d; + config_t c; + c.pan_1 = -0.6f * f; + c.pan_2 = 0.6f * f; + c.reverb_delay = 880 * 0.1f; + c.echo_delay = 610 * 0.1f; + if ( f > 0.5 ) + f = 0.5; // TODO: more linear reduction of extreme reverb/echo + c.reverb_level = 0.5f * f; + c.echo_level = 0.30f * f; + c.delay_variance = 180 * 0.1f; + c.effects_enabled = (d > 0.0f); + config( c ); +} + +Effects_Buffer::Effects_Buffer( bool center_only ) : Multi_Buffer( 2 ) +{ + buf_count = center_only ? max_buf_count - 4 : max_buf_count; + + echo_pos = 0; + reverb_pos = 0; + + stereo_remain = 0; + effect_remain = 0; + effects_enabled = false; + set_depth( 0 ); +} + +Effects_Buffer::~Effects_Buffer() { } + +blargg_err_t Effects_Buffer::set_sample_rate( long rate, int msec ) +{ + if ( !echo_buf.size() ) + RETURN_ERR( echo_buf.resize( echo_size ) ); + + if ( !reverb_buf.size() ) + RETURN_ERR( reverb_buf.resize( reverb_size ) ); + + for ( int i = 0; i < buf_count; i++ ) + RETURN_ERR( bufs [i].set_sample_rate( rate, msec ) ); + + config( config_ ); + clear(); + + return Multi_Buffer::set_sample_rate( bufs [0].sample_rate(), bufs [0].length() ); +} + +void Effects_Buffer::clock_rate( long rate ) +{ + for ( int i = 0; i < buf_count; i++ ) + bufs [i].clock_rate( rate ); +} + +void Effects_Buffer::bass_freq( int freq ) +{ + for ( int i = 0; i < buf_count; i++ ) + bufs [i].bass_freq( freq ); +} + +void Effects_Buffer::clear() +{ + stereo_remain = 0; + effect_remain = 0; + if ( echo_buf.size() ) + memset( &echo_buf [0], 0, echo_size * sizeof echo_buf [0] ); + + if ( reverb_buf.size() ) + memset( &reverb_buf [0], 0, reverb_size * sizeof reverb_buf [0] ); + + for ( int i = 0; i < buf_count; i++ ) + bufs [i].clear(); +} + +inline int pin_range( int n, int max, int min = 0 ) +{ + if ( n < min ) + return min; + if ( n > max ) + return max; + return n; +} + +void Effects_Buffer::config( const config_t& cfg ) +{ + channels_changed(); + + // clear echo and reverb buffers + if ( !config_.effects_enabled && cfg.effects_enabled && echo_buf.size() ) + { + memset( &echo_buf [0], 0, echo_size * sizeof echo_buf [0] ); + memset( &reverb_buf [0], 0, reverb_size * sizeof reverb_buf [0] ); + } + + config_ = cfg; + + if ( config_.effects_enabled ) + { + // convert to internal format + + chans.pan_1_levels [0] = TO_FIXED( 1 ) - TO_FIXED( config_.pan_1 ); + chans.pan_1_levels [1] = TO_FIXED( 2 ) - chans.pan_1_levels [0]; + + chans.pan_2_levels [0] = TO_FIXED( 1 ) - TO_FIXED( config_.pan_2 ); + chans.pan_2_levels [1] = TO_FIXED( 2 ) - chans.pan_2_levels [0]; + + chans.reverb_level = TO_FIXED( config_.reverb_level ); + chans.echo_level = TO_FIXED( config_.echo_level ); + + int delay_offset = int (1.0 / 2000 * config_.delay_variance * sample_rate()); + + int reverb_sample_delay = int (1.0 / 1000 * config_.reverb_delay * sample_rate()); + chans.reverb_delay_l = pin_range( reverb_size - + (reverb_sample_delay - delay_offset) * 2, reverb_size - 2, 0 ); + chans.reverb_delay_r = pin_range( reverb_size + 1 - + (reverb_sample_delay + delay_offset) * 2, reverb_size - 1, 1 ); + + int echo_sample_delay = int (1.0 / 1000 * config_.echo_delay * sample_rate()); + chans.echo_delay_l = pin_range( echo_size - 1 - (echo_sample_delay - delay_offset), + echo_size - 1 ); + chans.echo_delay_r = pin_range( echo_size - 1 - (echo_sample_delay + delay_offset), + echo_size - 1 ); + + chan_types [0].center = &bufs [0]; + chan_types [0].left = &bufs [3]; + chan_types [0].right = &bufs [4]; + + chan_types [1].center = &bufs [1]; + chan_types [1].left = &bufs [3]; + chan_types [1].right = &bufs [4]; + + chan_types [2].center = &bufs [2]; + chan_types [2].left = &bufs [5]; + chan_types [2].right = &bufs [6]; + assert( 2 < chan_types_count ); + } + else + { + // set up outputs + for ( unsigned i = 0; i < chan_types_count; i++ ) + { + channel_t& c = chan_types [i]; + c.center = &bufs [0]; + c.left = &bufs [1]; + c.right = &bufs [2]; + } + } + + if ( buf_count < max_buf_count ) + { + for ( int i = 0; i < chan_types_count; i++ ) + { + channel_t& c = chan_types [i]; + c.left = c.center; + c.right = c.center; + } + } +} + +Effects_Buffer::channel_t Effects_Buffer::channel( int i, int type ) +{ + int out = 2; + if ( !type ) + { + out = i % 5; + if ( out > 2 ) + out = 2; + } + else if ( !(type & noise_type) && (type & type_index_mask) % 3 != 0 ) + { + out = type & 1; + } + return chan_types [out]; +} + +void Effects_Buffer::end_frame( blip_time_t clock_count ) +{ + int bufs_used = 0; + for ( int i = 0; i < buf_count; i++ ) + { + bufs_used |= bufs [i].clear_modified() << i; + bufs [i].end_frame( clock_count ); + } + + int stereo_mask = (config_.effects_enabled ? 0x78 : 0x06); + if ( (bufs_used & stereo_mask) && buf_count == max_buf_count ) + stereo_remain = bufs [0].samples_avail() + bufs [0].output_latency(); + + if ( effects_enabled || config_.effects_enabled ) + effect_remain = bufs [0].samples_avail() + bufs [0].output_latency(); + + effects_enabled = config_.effects_enabled; +} + +long Effects_Buffer::samples_avail() const +{ + return bufs [0].samples_avail() * 2; +} + +long Effects_Buffer::read_samples( blip_sample_t* out, long total_samples ) +{ + require( total_samples % 2 == 0 ); // count must be even + + long remain = bufs [0].samples_avail(); + if ( remain > (total_samples >> 1) ) + remain = (total_samples >> 1); + total_samples = remain; + while ( remain ) + { + int active_bufs = buf_count; + long count = remain; + + // optimizing mixing to skip any channels which had nothing added + if ( effect_remain ) + { + if ( count > effect_remain ) + count = effect_remain; + + if ( stereo_remain ) + { + mix_enhanced( out, count ); + } + else + { + mix_mono_enhanced( out, count ); + active_bufs = 3; + } + } + else if ( stereo_remain ) + { + mix_stereo( out, count ); + active_bufs = 3; + } + else + { + mix_mono( out, count ); + active_bufs = 1; + } + + out += count * 2; + remain -= count; + + stereo_remain -= count; + if ( stereo_remain < 0 ) + stereo_remain = 0; + + effect_remain -= count; + if ( effect_remain < 0 ) + effect_remain = 0; + + for ( int i = 0; i < buf_count; i++ ) + { + if ( i < active_bufs ) + bufs [i].remove_samples( count ); + else + bufs [i].remove_silence( count ); // keep time synchronized + } + } + + return total_samples * 2; +} + +void Effects_Buffer::mix_mono( blip_sample_t* out_, blargg_long count ) +{ + blip_sample_t* BLIP_RESTRICT out = out_; + int const bass = BLIP_READER_BASS( bufs [0] ); + BLIP_READER_BEGIN( c, bufs [0] ); + + // unrolled loop + for ( blargg_long n = count >> 1; n; --n ) + { + blargg_long cs0 = BLIP_READER_READ( c ); + BLIP_READER_NEXT( c, bass ); + + blargg_long cs1 = BLIP_READER_READ( c ); + BLIP_READER_NEXT( c, bass ); + + if ( (BOOST::int16_t) cs0 != cs0 ) + cs0 = 0x7FFF - (cs0 >> 24); + ((BOOST::uint32_t*) out) [0] = ((BOOST::uint16_t) cs0) | (cs0 << 16); + + if ( (BOOST::int16_t) cs1 != cs1 ) + cs1 = 0x7FFF - (cs1 >> 24); + ((BOOST::uint32_t*) out) [1] = ((BOOST::uint16_t) cs1) | (cs1 << 16); + out += 4; + } + + if ( count & 1 ) + { + int s = BLIP_READER_READ( c ); + BLIP_READER_NEXT( c, bass ); + out [0] = s; + out [1] = s; + if ( (BOOST::int16_t) s != s ) + { + s = 0x7FFF - (s >> 24); + out [0] = s; + out [1] = s; + } + } + + BLIP_READER_END( c, bufs [0] ); +} + +void Effects_Buffer::mix_stereo( blip_sample_t* out_, blargg_long count ) +{ + blip_sample_t* BLIP_RESTRICT out = out_; + int const bass = BLIP_READER_BASS( bufs [0] ); + BLIP_READER_BEGIN( c, bufs [0] ); + BLIP_READER_BEGIN( l, bufs [1] ); + BLIP_READER_BEGIN( r, bufs [2] ); + + while ( count-- ) + { + int cs = BLIP_READER_READ( c ); + BLIP_READER_NEXT( c, bass ); + int left = cs + BLIP_READER_READ( l ); + int right = cs + BLIP_READER_READ( r ); + BLIP_READER_NEXT( l, bass ); + BLIP_READER_NEXT( r, bass ); + + if ( (BOOST::int16_t) left != left ) + left = 0x7FFF - (left >> 24); + + out [0] = left; + out [1] = right; + + out += 2; + + if ( (BOOST::int16_t) right != right ) + out [-1] = 0x7FFF - (right >> 24); + } + + BLIP_READER_END( r, bufs [2] ); + BLIP_READER_END( l, bufs [1] ); + BLIP_READER_END( c, bufs [0] ); +} + +void Effects_Buffer::mix_mono_enhanced( blip_sample_t* out_, blargg_long count ) +{ + blip_sample_t* BLIP_RESTRICT out = out_; + int const bass = BLIP_READER_BASS( bufs [2] ); + BLIP_READER_BEGIN( center, bufs [2] ); + BLIP_READER_BEGIN( sq1, bufs [0] ); + BLIP_READER_BEGIN( sq2, bufs [1] ); + + blip_sample_t* const reverb_buf = this->reverb_buf.begin(); + blip_sample_t* const echo_buf = this->echo_buf.begin(); + int echo_pos = this->echo_pos; + int reverb_pos = this->reverb_pos; + + while ( count-- ) + { + int sum1_s = BLIP_READER_READ( sq1 ); + int sum2_s = BLIP_READER_READ( sq2 ); + + BLIP_READER_NEXT( sq1, bass ); + BLIP_READER_NEXT( sq2, bass ); + + int new_reverb_l = FMUL( sum1_s, chans.pan_1_levels [0] ) + + FMUL( sum2_s, chans.pan_2_levels [0] ) + + reverb_buf [(reverb_pos + chans.reverb_delay_l) & reverb_mask]; + + int new_reverb_r = FMUL( sum1_s, chans.pan_1_levels [1] ) + + FMUL( sum2_s, chans.pan_2_levels [1] ) + + reverb_buf [(reverb_pos + chans.reverb_delay_r) & reverb_mask]; + + fixed_t reverb_level = chans.reverb_level; + reverb_buf [reverb_pos] = FMUL( new_reverb_l, reverb_level ); + reverb_buf [reverb_pos + 1] = FMUL( new_reverb_r, reverb_level ); + reverb_pos = (reverb_pos + 2) & reverb_mask; + + int sum3_s = BLIP_READER_READ( center ); + BLIP_READER_NEXT( center, bass ); + + int left = new_reverb_l + sum3_s + FMUL( chans.echo_level, + echo_buf [(echo_pos + chans.echo_delay_l) & echo_mask] ); + int right = new_reverb_r + sum3_s + FMUL( chans.echo_level, + echo_buf [(echo_pos + chans.echo_delay_r) & echo_mask] ); + + echo_buf [echo_pos] = sum3_s; + echo_pos = (echo_pos + 1) & echo_mask; + + if ( (BOOST::int16_t) left != left ) + left = 0x7FFF - (left >> 24); + + out [0] = left; + out [1] = right; + + out += 2; + + if ( (BOOST::int16_t) right != right ) + out [-1] = 0x7FFF - (right >> 24); + } + this->reverb_pos = reverb_pos; + this->echo_pos = echo_pos; + + BLIP_READER_END( sq1, bufs [0] ); + BLIP_READER_END( sq2, bufs [1] ); + BLIP_READER_END( center, bufs [2] ); +} + +void Effects_Buffer::mix_enhanced( blip_sample_t* out_, blargg_long count ) +{ + blip_sample_t* BLIP_RESTRICT out = out_; + int const bass = BLIP_READER_BASS( bufs [2] ); + BLIP_READER_BEGIN( center, bufs [2] ); + BLIP_READER_BEGIN( l1, bufs [3] ); + BLIP_READER_BEGIN( r1, bufs [4] ); + BLIP_READER_BEGIN( l2, bufs [5] ); + BLIP_READER_BEGIN( r2, bufs [6] ); + BLIP_READER_BEGIN( sq1, bufs [0] ); + BLIP_READER_BEGIN( sq2, bufs [1] ); + + blip_sample_t* const reverb_buf = this->reverb_buf.begin(); + blip_sample_t* const echo_buf = this->echo_buf.begin(); + int echo_pos = this->echo_pos; + int reverb_pos = this->reverb_pos; + + while ( count-- ) + { + int sum1_s = BLIP_READER_READ( sq1 ); + int sum2_s = BLIP_READER_READ( sq2 ); + + BLIP_READER_NEXT( sq1, bass ); + BLIP_READER_NEXT( sq2, bass ); + + int new_reverb_l = FMUL( sum1_s, chans.pan_1_levels [0] ) + + FMUL( sum2_s, chans.pan_2_levels [0] ) + BLIP_READER_READ( l1 ) + + reverb_buf [(reverb_pos + chans.reverb_delay_l) & reverb_mask]; + + int new_reverb_r = FMUL( sum1_s, chans.pan_1_levels [1] ) + + FMUL( sum2_s, chans.pan_2_levels [1] ) + BLIP_READER_READ( r1 ) + + reverb_buf [(reverb_pos + chans.reverb_delay_r) & reverb_mask]; + + BLIP_READER_NEXT( l1, bass ); + BLIP_READER_NEXT( r1, bass ); + + fixed_t reverb_level = chans.reverb_level; + reverb_buf [reverb_pos] = FMUL( new_reverb_l, reverb_level ); + reverb_buf [reverb_pos + 1] = FMUL( new_reverb_r, reverb_level ); + reverb_pos = (reverb_pos + 2) & reverb_mask; + + int sum3_s = BLIP_READER_READ( center ); + BLIP_READER_NEXT( center, bass ); + + int left = new_reverb_l + sum3_s + BLIP_READER_READ( l2 ) + FMUL( chans.echo_level, + echo_buf [(echo_pos + chans.echo_delay_l) & echo_mask] ); + int right = new_reverb_r + sum3_s + BLIP_READER_READ( r2 ) + FMUL( chans.echo_level, + echo_buf [(echo_pos + chans.echo_delay_r) & echo_mask] ); + + BLIP_READER_NEXT( l2, bass ); + BLIP_READER_NEXT( r2, bass ); + + echo_buf [echo_pos] = sum3_s; + echo_pos = (echo_pos + 1) & echo_mask; + + if ( (BOOST::int16_t) left != left ) + left = 0x7FFF - (left >> 24); + + out [0] = left; + out [1] = right; + + out += 2; + + if ( (BOOST::int16_t) right != right ) + out [-1] = 0x7FFF - (right >> 24); + } + this->reverb_pos = reverb_pos; + this->echo_pos = echo_pos; + + BLIP_READER_END( l1, bufs [3] ); + BLIP_READER_END( r1, bufs [4] ); + BLIP_READER_END( l2, bufs [5] ); + BLIP_READER_END( r2, bufs [6] ); + BLIP_READER_END( sq1, bufs [0] ); + BLIP_READER_END( sq2, bufs [1] ); + BLIP_READER_END( center, bufs [2] ); +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/console/Effects_Buffer.h Thu Nov 30 19:54:33 2006 -0800 @@ -0,0 +1,86 @@ +// Multi-channel effects buffer with panning, echo and reverb + +// Game_Music_Emu 0.5.1 +#ifndef EFFECTS_BUFFER_H +#define EFFECTS_BUFFER_H + +#include "Multi_Buffer.h" + +// Effects_Buffer uses several buffers and outputs stereo sample pairs. +class Effects_Buffer : public Multi_Buffer { +public: + // If center_only is true, only center buffers are created and + // less memory is used. + Effects_Buffer( bool center_only = false ); + + // Channel Effect Center Pan + // --------------------------------- + // 0,5 reverb pan_1 + // 1,6 reverb pan_2 + // 2,7 echo - + // 3 echo - + // 4 echo - + + // Channel configuration + struct config_t { + double pan_1; // -1.0 = left, 0.0 = center, 1.0 = right + double pan_2; + double echo_delay; // msec + double echo_level; // 0.0 to 1.0 + double reverb_delay; // msec + double delay_variance; // difference between left/right delays (msec) + double reverb_level; // 0.0 to 1.0 + bool effects_enabled; // if false, use optimized simple mixer + config_t(); + }; + + // Set configuration of buffer + virtual void config( const config_t& ); + void set_depth( double ); + +public: + ~Effects_Buffer(); + blargg_err_t set_sample_rate( long samples_per_sec, int msec = blip_default_length ); + void clock_rate( long ); + void bass_freq( int ); + void clear(); + channel_t channel( int, int ); + void end_frame( blip_time_t ); + long read_samples( blip_sample_t*, long ); + long samples_avail() const; +private: + typedef long fixed_t; + + enum { max_buf_count = 7 }; + Blip_Buffer bufs [max_buf_count]; + enum { chan_types_count = 3 }; + channel_t chan_types [3]; + config_t config_; + long stereo_remain; + long effect_remain; + int buf_count; + bool effects_enabled; + + blargg_vector<blip_sample_t> reverb_buf; + blargg_vector<blip_sample_t> echo_buf; + int reverb_pos; + int echo_pos; + + struct { + fixed_t pan_1_levels [2]; + fixed_t pan_2_levels [2]; + int echo_delay_l; + int echo_delay_r; + fixed_t echo_level; + int reverb_delay_l; + int reverb_delay_r; + fixed_t reverb_level; + } chans; + + void mix_mono( blip_sample_t*, blargg_long ); + void mix_stereo( blip_sample_t*, blargg_long ); + void mix_enhanced( blip_sample_t*, blargg_long ); + void mix_mono_enhanced( blip_sample_t*, blargg_long ); +}; + +#endif
--- a/src/console/Fir_Resampler.cxx Wed Nov 29 14:42:11 2006 -0800 +++ b/src/console/Fir_Resampler.cxx Thu Nov 30 19:54:33 2006 -0800 @@ -1,5 +1,4 @@ - -// Game_Music_Emu 0.3.0. http://www.slack.net/~ant/ +// Game_Music_Emu 0.5.1. http://www.slack.net/~ant/ #include "Fir_Resampler.h" @@ -14,81 +13,45 @@ 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 */ +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_BEGIN - -// to do: fix problems with rolloff < 0.99 or so, and rolloff == 1.0, and related problems - -// Sinc impulse genertor - -const bool show_impulse = 0; +#include "blargg_source.h" -static const double pi = 3.1415926535897932384626433832795029L; +#undef PI +#define PI 3.1415926535897932384626433832795029 -class Dsf { - double rolloff; - double factor; -public: - Dsf( double r ) : rolloff( r ) - { - factor = 1.0; - //if ( rolloff < 1.0 ) - // factor = 1.0 / (*this)( 0 ); - } +static void gen_sinc( double rolloff, int width, double offset, double spacing, double scale, + int count, short* out ) +{ + double const maxh = 256; + double const step = PI / maxh * spacing; + double const to_w = maxh * 2 / width; + double const pow_a_n = pow( rolloff, maxh ); + scale /= maxh * 2; - double operator () ( double angle ) const - { - double const n_harm = 256; - angle /= n_harm; - double pow_a_n = pow( rolloff, n_harm ); - //double rescale = 1.0 / n_harm; - - double num = 1.0 - rolloff * cos( angle ) - - pow_a_n * cos( n_harm * angle ) + - pow_a_n * rolloff * cos( (n_harm - 1) * angle ); - double den = 1 + rolloff * (rolloff - 2 * cos( angle )); - - return (num / den - 1) / n_harm * factor; - } -}; - -template<class Sinc> -void gen_sinc( int width, double offset, double spacing, int count, double scale, short* p, - const Sinc& sinc ) -{ - double range = pi * (width / 2); - double step = pi * spacing; - double a = -step * (count / 2 - 1); - a -= offset * step; - + double angle = (count / 2 - 1 + offset) * -step; while ( count-- ) { - double w = a / range; - double y = 0.0; - if ( fabs( w ) < 1.0 ) + *out++ = 0; + double w = angle * to_w; + if ( fabs( w ) < PI ) { - double window = cos( pi * w ) * 0.5 + 0.5; - y = sinc( a ) * window; + double rolloff_cos_a = rolloff * cos( angle ); + double num = 1 - rolloff_cos_a - + pow_a_n * cos( maxh * angle ) + + pow_a_n * rolloff * cos( (maxh - 1) * angle ); + double den = 1 - rolloff_cos_a - rolloff_cos_a + rolloff * rolloff; + double sinc = scale * num / den - scale; + + out [-1] = (short) (cos( w ) * sinc + sinc); } - - *p++ = (short) (y * scale); - a += step; + angle += step; } } -/* -static double plain_sinc( double a ) -{ - return fabs( a ) < 0.00001 ? 1.0 : sin( a ) / a; -} -*/ - -// Fir_Resampler - Fir_Resampler_::Fir_Resampler_( int width, sample_t* impulses_ ) : width_( width ), write_offset( width * stereo - stereo ), @@ -102,9 +65,7 @@ ratio_ = 1.0; } -Fir_Resampler_::~Fir_Resampler_() -{ -} +Fir_Resampler_::~Fir_Resampler_() { } void Fir_Resampler_::clear() { @@ -118,9 +79,9 @@ blargg_err_t Fir_Resampler_::buffer_size( int new_size ) { - BLARGG_RETURN_ERR( buf.resize( new_size + write_offset ) ); + RETURN_ERR( buf.resize( new_size + write_offset ) ); clear(); - return blargg_success; + return 0; } double Fir_Resampler_::time_ratio( double new_factor, double rolloff, double gain ) @@ -156,21 +117,11 @@ double filter = (ratio_ < 1.0) ? 1.0 : 1.0 / ratio_; double pos = 0.0; input_per_cycle = 0; - Dsf dsf( rolloff ); for ( int i = 0; i < res; i++ ) { - if ( show_impulse ) - printf( "pos = %f\n", pos ); - - gen_sinc( int (width_ * filter + 1) & ~1, pos, filter, (int) width_, - double (0x7fff * gain * filter), impulses + i * width_, dsf ); - - if ( show_impulse ) - { - for ( int j = 0; j < width_; j++ ) - printf( "%d ", (int) impulses [i * width_ + j] ); - printf( "\n" ); - } + gen_sinc( rolloff, int (width_ * filter + 1) & ~1, pos, filter, + double (0x7FFF * gain * filter), + (int) width_, impulses + i * width_ ); pos += fstep; input_per_cycle += step; @@ -182,20 +133,14 @@ } } - if ( show_impulse ) - { - printf( "skip = %8lX\n", (long) skip_bits ); - printf( "step = %d\n", step ); - } - clear(); return ratio_; } -int Fir_Resampler_::input_needed( long output_count ) const +int Fir_Resampler_::input_needed( blargg_long output_count ) const { - long input_count = 0; + blargg_long input_count = 0; unsigned long skip = skip_bits >> imp; int remain = res - imp; @@ -217,13 +162,13 @@ return input_extra; } -int Fir_Resampler_::avail_( long input_count ) const +int Fir_Resampler_::avail_( blargg_long input_count ) const { int cycle_count = input_count / input_per_cycle; int output_count = cycle_count * res * stereo; input_count -= cycle_count * input_per_cycle; - unsigned long skip = skip_bits >> imp; + blargg_ulong skip = skip_bits >> imp; int remain = res - imp; while ( input_count >= 0 ) { @@ -243,7 +188,6 @@ { int remain = write_pos - buf.begin(); int avail = remain - width_ * stereo; - if ( avail < 0 ) avail = 0; // inserted if ( count > avail ) count = avail; @@ -253,4 +197,3 @@ return count; } -
--- a/src/console/Fir_Resampler.h Wed Nov 29 14:42:11 2006 -0800 +++ b/src/console/Fir_Resampler.h Thu Nov 30 19:54:33 2006 -0800 @@ -1,8 +1,6 @@ - // Finite impulse response (FIR) resampler with adjustable FIR size -// Game_Music_Emu 0.3.0 - +// Game_Music_Emu 0.5.1 #ifndef FIR_RESAMPLER_H #define FIR_RESAMPLER_H @@ -50,7 +48,7 @@ // Output // Number of extra input samples needed until 'count' output samples are available - int input_needed( long count ) const; + int input_needed( blargg_long count ) const; // Number of output samples available int avail() const { return avail_( write_pos - &buf [width_ * stereo] ); } @@ -66,14 +64,14 @@ int imp; int const width_; int const write_offset; - unsigned long skip_bits; + blargg_ulong skip_bits; int step; int input_per_cycle; double ratio_; sample_t* impulses; Fir_Resampler_( int width, sample_t* ); - int avail_( long input_count ) const; + int avail_( blargg_long input_count ) const; }; // Width is number of points in FIR. Must be even and 4 or more. More points give @@ -87,7 +85,7 @@ // Read at most 'count' samples. Returns number of samples actually read. typedef short sample_t; - int read( sample_t* out, long count ); + int read( sample_t* out, blargg_long count ); }; // End of public interface @@ -99,12 +97,12 @@ } template<int width> -int Fir_Resampler<width>::read( sample_t* out_begin, long count ) +int Fir_Resampler<width>::read( sample_t* out_begin, blargg_long count ) { sample_t* out = out_begin; const sample_t* in = buf.begin(); sample_t* end_pos = write_pos; - unsigned long skip = skip_bits >> this->imp; + blargg_ulong skip = skip_bits >> this->imp; sample_t const* imp = impulses [this->imp]; int remain = res - this->imp; int const step = this->step; @@ -119,8 +117,8 @@ count--; // accumulate in extended precision - long l = 0; - long r = 0; + blargg_long l = 0; + blargg_long r = 0; const sample_t* i = in; if ( count < 0 ) @@ -154,8 +152,8 @@ remain = res; } - out [0] = l; - out [1] = r; + out [0] = (sample_t) l; + out [1] = (sample_t) r; out += 2; } while ( in <= end_pos ); @@ -171,4 +169,3 @@ } #endif -
--- a/src/console/Gb_Apu.cxx Wed Nov 29 14:42:11 2006 -0800 +++ b/src/console/Gb_Apu.cxx Thu Nov 30 19:54:33 2006 -0800 @@ -1,5 +1,4 @@ - -// Gb_Snd_Emu 0.1.4. http://www.slack.net/~ant/ +// Gb_Snd_Emu 0.1.5. http://www.slack.net/~ant/ #include "Gb_Apu.h" @@ -11,15 +10,15 @@ 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 */ +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_BEGIN +#include "blargg_source.h" -const unsigned vol_reg = 0xFF24; -const unsigned status_reg = 0xFF26; +unsigned const vol_reg = 0xFF24; +unsigned const status_reg = 0xFF26; Gb_Apu::Gb_Apu() { @@ -37,21 +36,18 @@ { Gb_Osc& osc = *oscs [i]; osc.regs = ®s [i * 5]; - osc.output = NULL; - osc.outputs [0] = NULL; - osc.outputs [1] = NULL; - osc.outputs [2] = NULL; - osc.outputs [3] = NULL; + osc.output = 0; + osc.outputs [0] = 0; + osc.outputs [1] = 0; + osc.outputs [2] = 0; + osc.outputs [3] = 0; } + set_tempo( 1.0 ); volume( 1.0 ); reset(); } -Gb_Apu::~Gb_Apu() -{ -} - void Gb_Apu::treble_eq( const blip_eq_t& eq ) { square_synth.treble_eq( eq ); @@ -77,14 +73,15 @@ void Gb_Apu::update_volume() { - // to do: doesn't handle differing left/right global volume + // TODO: doesn't handle differing left/right global volume (support would + // require modification to all oscillator code) int data = regs [vol_reg - start_addr]; double vol = (max( data & 7, data >> 4 & 7 ) + 1) * volume_unit; square_synth.volume( vol ); other_synth.volume( vol ); } -static unsigned char const powerup_regs [0x30] = { +static unsigned char const powerup_regs [0x20] = { 0x80,0x3F,0x00,0xFF,0xBF, // square 1 0xFF,0x3F,0x00,0xFF,0xBF, // square 2 0x7F,0xFF,0x9F,0xFF,0xBF, // wave @@ -92,17 +89,21 @@ 0x00, // left/right enables 0x77, // master volume 0x80, // power - 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, - 0x84,0x40,0x43,0xAA,0x2D,0x78,0x92,0x3C, // wave table - 0x60,0x59,0x59,0xB0,0x34,0xB8,0x2E,0xDA + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF }; +void Gb_Apu::set_tempo( double t ) +{ + frame_period = 4194304 / 256; // 256 Hz + if ( t != 1.0 ) + frame_period = blip_time_t (frame_period / t); +} + void Gb_Apu::reset() { next_frame_time = 0; - last_time = 0; - frame_count = 0; - stereo_found = false; + last_time = 0; + frame_count = 0; square1.reset(); square2.reset(); @@ -117,12 +118,15 @@ regs [status_reg - start_addr] = 0x01; // force power write_register( 0, status_reg, 0x00 ); + + static unsigned char const initial_wave [] = { + 0x84,0x40,0x43,0xAA,0x2D,0x78,0x92,0x3C, // wave table + 0x60,0x59,0x59,0xB0,0x34,0xB8,0x2E,0xDA + }; + memcpy( wave.wave, initial_wave, sizeof wave.wave ); } -// to do: remove -static unsigned long abs_time; - -void Gb_Apu::run_until( gb_time_t end_time ) +void Gb_Apu::run_until( blip_time_t end_time ) { require( end_time >= last_time ); // end_time must not be before previous time if ( end_time == last_time ) @@ -130,7 +134,7 @@ while ( true ) { - gb_time_t time = next_frame_time; + blip_time_t time = next_frame_time; if ( time > end_time ) time = end_time; @@ -140,12 +144,11 @@ Gb_Osc& osc = *oscs [i]; if ( osc.output ) { + osc.output->set_modified(); // TODO: misses optimization opportunities? int playing = false; if ( osc.enabled && osc.volume && (!(osc.regs [4] & osc.len_enabled_mask) || osc.length) ) playing = -1; - if ( osc.output != osc.outputs [3] ) - stereo_found = true; switch ( i ) { case 0: square1.run( last_time, time, playing ); break; @@ -160,7 +163,7 @@ if ( time == end_time ) break; - next_frame_time += 4194304 / 256; // 256 Hz + next_frame_time += frame_period; // 256 Hz actions square1.clock_length(); @@ -182,25 +185,19 @@ } } -bool Gb_Apu::end_frame( gb_time_t end_time ) +void Gb_Apu::end_frame( blip_time_t end_time ) { if ( end_time > last_time ) run_until( end_time ); - abs_time += end_time; - assert( next_frame_time >= end_time ); next_frame_time -= end_time; assert( last_time >= end_time ); last_time -= end_time; - - bool result = stereo_found; - stereo_found = false; - return result; } -void Gb_Apu::write_register( gb_time_t time, gb_addr_t addr, int data ) +void Gb_Apu::write_register( blip_time_t time, unsigned addr, int data ) { require( (unsigned) data < 0x100 ); @@ -266,7 +263,7 @@ { if ( !(data & 0x80) ) { - for ( int i = 0; i < (int) sizeof powerup_regs; i++ ) + for ( unsigned i = 0; i < sizeof powerup_regs; i++ ) { if ( i != status_reg - start_addr ) write_register( time, i + start_addr, powerup_regs [i] ); @@ -280,14 +277,13 @@ } else if ( addr >= 0xFF30 ) { - int index = (addr & 0x0F) * 2; wave.wave [index] = data >> 4; wave.wave [index + 1] = data & 0x0F; } } -int Gb_Apu::read_register( gb_time_t time, gb_addr_t addr ) +int Gb_Apu::read_register( blip_time_t time, unsigned addr ) { run_until( time ); @@ -308,4 +304,3 @@ return data; } -
--- a/src/console/Gb_Apu.h Wed Nov 29 14:42:11 2006 -0800 +++ b/src/console/Gb_Apu.h Thu Nov 30 19:54:33 2006 -0800 @@ -1,14 +1,9 @@ - // Nintendo Game Boy PAPU sound chip emulator -// Gb_Snd_Emu 0.1.4 - +// Gb_Snd_Emu 0.1.5 #ifndef GB_APU_H #define GB_APU_H -typedef long gb_time_t; // clock cycle count -typedef unsigned gb_addr_t; // 16-bit address - #include "Gb_Oscs.h" class Gb_Apu { @@ -40,35 +35,34 @@ // Reads and writes at addr must satisfy start_addr <= addr <= end_addr enum { start_addr = 0xFF10 }; - enum { end_addr = 0xFF3f }; + enum { end_addr = 0xFF3F }; enum { register_count = end_addr - start_addr + 1 }; // Write 'data' to address at specified time - void write_register( gb_time_t, gb_addr_t, int data ); + void write_register( blip_time_t, unsigned addr, int data ); // Read from address at specified time - int read_register( gb_time_t, gb_addr_t ); + int read_register( blip_time_t, unsigned addr ); // Run all oscillators up to specified time, end current time frame, then - // start a new frame at time 0. Returns true if any oscillators added - // sound to one of the left/right buffers, false if they only added - // to the center buffer. - bool end_frame( gb_time_t ); + // start a new frame at time 0. + void end_frame( blip_time_t ); + + void set_tempo( double ); public: Gb_Apu(); - ~Gb_Apu(); private: // noncopyable Gb_Apu( const Gb_Apu& ); Gb_Apu& operator = ( const Gb_Apu& ); Gb_Osc* oscs [osc_count]; - gb_time_t next_frame_time; - gb_time_t last_time; + blip_time_t next_frame_time; + blip_time_t last_time; + blip_time_t frame_period; double volume_unit; int frame_count; - bool stereo_found; Gb_Square square1; Gb_Square square2; @@ -79,7 +73,7 @@ Gb_Wave::Synth other_synth; // used by wave and noise void update_volume(); - void run_until( gb_time_t ); + void run_until( blip_time_t ); void write_osc( int index, int reg, int data ); }; @@ -94,4 +88,3 @@ } #endif -
--- a/src/console/Gb_Cpu.cxx Wed Nov 29 14:42:11 2006 -0800 +++ b/src/console/Gb_Cpu.cxx Thu Nov 30 19:54:33 2006 -0800 @@ -1,12 +1,10 @@ - -// Game_Music_Emu 0.3.0. http://www.slack.net/~ant/ +// Game_Music_Emu 0.5.1. http://www.slack.net/~ant/ #include "Gb_Cpu.h" #include <string.h> -#include <limits.h> -#include "blargg_endian.h" +//#include "gb_cpu_log.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 @@ -14,12 +12,14 @@ 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 */ +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_BEGIN +#include "gb_cpu_io.h" + +#include "blargg_source.h" // Common instructions: // @@ -47,43 +47,28 @@ #define PAGE_OFFSET( addr ) ((addr) & (page_size - 1)) #endif -Gb_Cpu::Gb_Cpu( Gbs_Emu* gbs_emu ) +inline void Gb_Cpu::set_code_page( int i, uint8_t* p ) { - callback_data = gbs_emu; - rst_base = 0; - reset(); -} - -inline void Gb_Cpu::set_code_page( int i, uint8_t const* p ) -{ - code_map [i] = p - PAGE_OFFSET( i * page_size ); + state->code_map [i] = p - PAGE_OFFSET( i * (blargg_long) page_size ); } -void Gb_Cpu::reset( const void* unmapped_code_page, reader_t read, writer_t write ) +void Gb_Cpu::reset( void* unmapped ) { - interrupts_enabled = false; - remain_ = 0; + check( state == &state_ ); + state = &state_; - r.pc = 0; - r.sp = 0; - r.flags = 0; - r.a = 0; - r.b = 0; - r.c = 0; - r.d = 0; - r.e = 0; - r.h = 0; - r.l = 0; + state_.remain = 0; for ( int i = 0; i < page_count + 1; i++ ) - { - set_code_page( i, (uint8_t*) unmapped_code_page ); - data_reader [i] = read; - data_writer [i] = write; - } + set_code_page( i, (uint8_t*) unmapped ); + + memset( &r, 0, sizeof r ); + //interrupts_enabled = false; + + blargg_verify_byte_order(); } -void Gb_Cpu::map_code( gb_addr_t start, unsigned long size, const void* data ) +void Gb_Cpu::map_code( gb_addr_t start, unsigned size, void* data ) { // address range must begin and end on page boundaries require( start % page_size == 0 ); @@ -94,79 +79,33 @@ set_code_page( first_page + i, (uint8_t*) data + i * page_size ); } -void Gb_Cpu::map_memory( gb_addr_t start, unsigned long size, reader_t read, writer_t write ) -{ - // address range must begin and end on page boundaries - require( start % page_size == 0 ); - require( size % page_size == 0 ); - - unsigned first_page = start / page_size; - for ( unsigned i = size / page_size; i--; ) - { - data_reader [first_page + i] = read; - data_writer [first_page + i] = write; - } -} +#define READ( addr ) CPU_READ( this, (addr), s.remain ) +#define WRITE( addr, data ) {CPU_WRITE( this, (addr), (data), s.remain );} +#define READ_FAST( addr, out ) CPU_READ_FAST( this, (addr), s.remain, out ) +#define READ_PROG( addr ) (s.code_map [(addr) >> page_shift] [PAGE_OFFSET( addr )]) -// Note: 'addr' is evaulated more than once in the following macros, so it -// must not contain side-effects. - -#define READ( addr ) (data_reader [(addr) >> page_bits]( callback_data, addr )) -#define WRITE( addr, data ) (data_writer [(addr) >> page_bits]( callback_data, addr, data )) +unsigned const z_flag = 0x80; +unsigned const n_flag = 0x40; +unsigned const h_flag = 0x20; +unsigned const c_flag = 0x10; -#define READ_PROG( addr ) (code_map [(addr) >> page_bits] [PAGE_OFFSET( addr )]) -#define READ_PROG16( addr ) GET_LE16( &READ_PROG( addr ) ) - -int Gb_Cpu::read( gb_addr_t addr ) -{ - return READ( addr ); -} - -void Gb_Cpu::write( gb_addr_t addr, int data ) +bool Gb_Cpu::run( blargg_long cycle_count ) { - WRITE( addr, data ); -} - -BOOST::uint8_t* Gb_Cpu::get_code( gb_addr_t addr ) -{ - return (uint8_t*) &READ_PROG( addr ); -} - -#ifndef GB_CPU_GLUE_ONLY - -const unsigned z_flag = 0x80; -const unsigned n_flag = 0x40; -const unsigned h_flag = 0x20; -const unsigned c_flag = 0x10; - -#include BLARGG_ENABLE_OPTIMIZER - -Gb_Cpu::result_t Gb_Cpu::run( long cycle_count ) -{ - const int cycles_per_instruction = 4; + state_.remain = blargg_ulong (cycle_count + clocks_per_instr) / clocks_per_instr; + state_t s; + this->state = &s; + memcpy( &s, &this->state_, sizeof s ); - // to do: use cycle table - remain_ = cycle_count + cycles_per_instruction; - - Gb_Cpu::result_t result = result_cycles; - -#if BLARGG_CPU_POWERPC - const reader_t* data_reader = this->data_reader; // cache - const writer_t* data_writer = this->data_writer; // cache +#if BLARGG_BIG_ENDIAN + #define R8( n ) (r8_ [n]) +#elif BLARGG_LITTLE_ENDIAN + #define R8( n ) (r8_ [(n) ^ 1]) +#else + #error "Byte order of CPU must be known" #endif union { - struct { -#if BLARGG_BIG_ENDIAN - uint8_t b, c, d, e, h, l, unused, a; -# define R8( n ) (r8_ [n]) -#elif BLARGG_LITTLE_ENDIAN - uint8_t c, b, e, d, l, h, a, unused; -# define R8( n ) (r8_ [(n) ^ 1]) -#else -# error "Byte order of CPU must be known" -#endif - } rg; // registers + core_regs_t rg; // individual registers struct { BOOST::uint16_t bc, de, hl, unused; // pairs @@ -177,35 +116,42 @@ }; BOOST_STATIC_ASSERT( sizeof rg == 8 && sizeof rp == 8 ); - rg.a = r.a; - rg.b = r.b; - rg.c = r.c; - rg.d = r.d; - rg.e = r.e; - rg.h = r.h; - rg.l = r.l; + rg = r; unsigned pc = r.pc; unsigned sp = r.sp; unsigned flags = r.flags; loop: - int new_remain = remain_ - cycles_per_instruction; + check( (unsigned long) pc < 0x10000 ); + check( (unsigned long) sp < 0x10000 ); + check( (flags & ~0xF0) == 0 ); - check( (unsigned) pc < 0x10000 ); - check( (unsigned) sp < 0x10000 ); - check( (flags & ~0xf0) == 0 ); + uint8_t const* instr = s.code_map [pc >> page_shift]; + unsigned op; - uint8_t const* page = code_map [pc >> page_bits]; - unsigned op = page [PAGE_OFFSET( pc )]; - unsigned data = page [PAGE_OFFSET( pc ) + 1]; - check( op == 0xC9 || &READ_PROG( pc + 1 ) == &page [PAGE_OFFSET( pc ) + 1] ); + // TODO: eliminate this special case + #if BLARGG_NONPORTABLE + op = instr [pc]; + pc++; + instr += pc; + #else + instr += PAGE_OFFSET( pc ); + op = *instr++; + pc++; + #endif - pc++; +#define GET_ADDR() GET_LE16( instr ) + + if ( !--s.remain ) + goto stop; - remain_ = new_remain; - if ( new_remain <= 0 ) - goto stop; + unsigned data; + data = *instr; + + #ifdef GB_CPU_LOG_H + gb_cpu_log( "new", pc - 1, op, data, instr [1] ); + #endif switch ( op ) { @@ -225,7 +171,7 @@ BRANCH( !(flags & z_flag) ) case 0x21: // LD HL,IMM (common) - rp.hl = READ_PROG16( pc ); + rp.hl = GET_ADDR(); pc += 2; goto loop; @@ -234,14 +180,13 @@ { unsigned temp; - - case 0xF0: // LD A,(0xff00+imm) - temp = data + 0xff00; + case 0xF0: // LD A,(0xFF00+imm) + temp = data | 0xFF00; pc++; goto ld_a_ind_comm; - case 0xF2: // LD A,(0xff00+C) - temp = rg.c + 0xff00; + case 0xF2: // LD A,(0xFF00+C) + temp = rg.c | 0xFF00; goto ld_a_ind_comm; case 0x0A: // LD A,(BC) @@ -263,10 +208,10 @@ goto ld_a_ind_comm; case 0xFA: // LD A,IND16 (common) - temp = READ_PROG16( pc ); + temp = GET_ADDR(); pc += 2; ld_a_ind_comm: - rg.a = READ( temp ); + READ_FAST( temp, rg.a ); goto loop; } @@ -292,7 +237,7 @@ flags = ((op & 15) - (data & 15)) & h_flag; flags |= (data >> 4) & c_flag; flags |= n_flag; - if ( data & 0xff ) + if ( data & 0xFF ) goto loop; flags |= z_flag; goto loop; @@ -303,9 +248,11 @@ case 0x5E: // LD E,(HL) case 0x66: // LD H,(HL) case 0x6E: // LD L,(HL) - case 0x7E: // LD A,(HL) - R8( (op >> 3) & 7 ) = READ( rp.hl ); + case 0x7E:{// LD A,(HL) + unsigned addr = rp.hl; + READ_FAST( addr, R8( (op >> 3) & 7 ) ); goto loop; + } case 0xC4: // CNZ (next-most-common) pc += 2; @@ -315,12 +262,12 @@ pc -= 2; case 0xCD: // CALL (most-common) data = pc + 2; - pc = READ_PROG16( pc ); + pc = GET_ADDR(); push: sp = (sp - 1) & 0xFFFF; WRITE( sp, data >> 8 ); sp = (sp - 1) & 0xFFFF; - WRITE( sp, data & 0xff ); + WRITE( sp, data & 0xFF ); goto loop; case 0xC8: // RNZ (next-most-common) @@ -329,7 +276,7 @@ case 0xC9: // RET (most common) ret: pc = READ( sp ); - pc += 0x100 * READ( (sp + 1) & 0xFFFF ); + pc += 0x100 * READ( sp + 1 ); sp = (sp + 2) & 0xFFFF; goto loop; @@ -352,7 +299,6 @@ { int temp; - case 0x46: // BIT b,(HL) case 0x4E: case 0x56: @@ -361,8 +307,11 @@ case 0x6E: case 0x76: case 0x7E: - temp = READ( rp.hl ); - goto bit_comm; + { + unsigned addr = rp.hl; + READ_FAST( addr, temp ); + goto bit_comm; + } case 0x40: case 0x41: case 0x42: case 0x43: // BIT b,r case 0x44: case 0x45: case 0x47: case 0x48: @@ -526,7 +475,7 @@ op |= (op << 1) & 0x80; shift_comm: data &= 7; - if ( !(op & 0xff) ) + if ( !(op & 0xFF) ) flags |= z_flag; if ( data == 6 ) goto write_hl_op_ff; @@ -544,7 +493,7 @@ case 0x77: // LD (HL),A op = R8( op & 7 ); write_hl_op_ff: - WRITE( rp.hl, op & 0xff ); + WRITE( rp.hl, op & 0xFF ); goto loop; case 0x41: case 0x42: case 0x43: case 0x44: case 0x45: case 0x47: // LD r,r @@ -558,9 +507,9 @@ goto loop; case 0x08: // LD IND16,SP - data = READ_PROG16( pc ); + data = GET_ADDR(); pc += 2; - WRITE( data, sp&0xff ); + WRITE( data, sp&0xFF ); data++; WRITE( data, sp >> 8 ); goto loop; @@ -570,25 +519,25 @@ goto loop; case 0x31: // LD SP,IMM - sp = READ_PROG16( pc ); + sp = GET_ADDR(); pc += 2; goto loop; case 0x01: // LD BC,IMM case 0x11: // LD DE,IMM - r16 [op >> 4] = READ_PROG16( pc ); + r16 [op >> 4] = GET_ADDR(); pc += 2; goto loop; { unsigned temp; - case 0xE0: // LD (0xff00+imm),A - temp = data + 0xff00; + case 0xE0: // LD (0xFF00+imm),A + temp = data | 0xFF00; pc++; goto write_data_rg_a; - case 0xE2: // LD (0xff00+C),A - temp = rg.c + 0xff00; + case 0xE2: // LD (0xFF00+C),A + temp = rg.c | 0xFF00; goto write_data_rg_a; case 0x32: // LD (HL-),A @@ -610,7 +559,7 @@ goto write_data_rg_a; case 0xEA: // LD IND16,A (common) - temp = READ_PROG16( pc ); + temp = GET_ADDR(); pc += 2; write_data_rg_a: WRITE( temp, rg.a ); @@ -683,7 +632,7 @@ op = rp.hl; data = READ( op ); data++; - WRITE( op, data & 0xff ); + WRITE( op, data & 0xFF ); goto inc_comm; case 0x04: // INC B @@ -703,7 +652,7 @@ op = rp.hl; data = READ( op ); data--; - WRITE( op, data & 0xff ); + WRITE( op, data & 0xFF ); goto dec_comm; case 0x05: // DEC B @@ -718,7 +667,7 @@ R8( op ) = data; dec_comm: flags = (flags & c_flag) | n_flag | (((data & 15) + 0x31) & h_flag); - if ( data & 0xff ) + if ( data & 0xFF ) goto loop; flags |= z_flag; goto loop; @@ -726,7 +675,7 @@ // Add 16-bit { - unsigned long temp; // need more than 16 bits for carry + blargg_ulong temp; // need more than 16 bits for carry unsigned prev; case 0xF8: // LD HL,SP+imm @@ -743,7 +692,7 @@ flags = 0; temp += sp; prev = sp; - sp = temp & 0xffff; + sp = temp & 0xFFFF; goto add_16_comm; case 0x39: // ADD HL,SP @@ -762,7 +711,7 @@ rp.hl = temp; add_16_comm: flags |= (temp >> 12) & c_flag; - flags |= (((temp & 0x0fff) - (prev & 0x0fff)) >> 7) & h_flag; + flags |= (((temp & 0x0FFF) - (prev & 0x0FFF)) >> 7) & h_flag; goto loop; } @@ -788,7 +737,7 @@ flags = ((data & 15) - (flags & 15)) & h_flag; flags |= (data >> 4) & c_flag; rg.a = data; - if ( data & 0xff ) + if ( data & 0xFF ) goto loop; flags |= z_flag; goto loop; @@ -813,7 +762,7 @@ pc++; adc_comm: data += (flags >> 4) & 1; - data &= 0xff; // to do: does carry get set when sum + carry = 0x100? + data &= 0xFF; // to do: does carry get set when sum + carry = 0x100? goto add_comm; case 0x96: // SUB (HL) @@ -856,7 +805,7 @@ pc++; sbc_comm: data += (flags >> 4) & 1; - data &= 0xff; // to do: does carry get set when sum + carry = 0x100? + data &= 0xFF; // to do: does carry get set when sum + carry = 0x100? goto sub_comm; // Logical @@ -929,21 +878,18 @@ // Stack + case 0xF1: // POP FA case 0xC1: // POP BC case 0xD1: // POP DE - case 0xE1:{// POP HL (common) - int temp = READ( sp ); - r16 [(op >> 4) & 3] = temp + 0x100 * READ( (sp + 1) & 0xFFFF ); + case 0xE1: // POP HL (common) + data = READ( sp ); + r16 [(op >> 4) & 3] = data + 0x100 * READ( sp + 1 ); sp = (sp + 2) & 0xFFFF; + if ( op != 0xF1 ) + goto loop; + flags = rg.flags & 0xF0; goto loop; - } - case 0xF1: // POP FA - rg.a = READ( sp ); - flags = READ( (sp + 1) & 0xFFFF ) & 0xf0; - sp = (sp + 2) & 0xFFFF; - goto loop; - case 0xC5: // PUSH BC data = rp.bc; goto push; @@ -961,13 +907,16 @@ goto push; // Flow control - + + case 0xFF: + if ( pc == idle_addr + 1 ) + goto stop; case 0xC7: case 0xCF: case 0xD7: case 0xDF: // RST - case 0xE7: case 0xEF: case 0xF7: case 0xFF: + case 0xE7: case 0xEF: case 0xF7: data = pc; pc = (op & 0x38) + rst_base; goto push; - + case 0xCC: // CZ pc += 2; if ( flags & z_flag ) @@ -987,7 +936,7 @@ goto loop; case 0xD9: // RETI - Gb_Cpu::interrupts_enabled = 1; + //interrupts_enabled = 1; goto ret; case 0xC0: // RZ @@ -1019,7 +968,7 @@ goto loop; case 0xC3: // JP (next-most-common) - pc = READ_PROG16( pc ); + pc = GET_ADDR(); goto loop; case 0xC2: // JP NZ @@ -1034,7 +983,7 @@ goto loop; jp_taken: pc -= 2; - pc = READ_PROG16( pc ); + pc = GET_ADDR(); goto loop; case 0xD2: // JP NC @@ -1065,11 +1014,11 @@ goto loop; case 0xF3: // DI - interrupts_enabled = 0; + //interrupts_enabled = 0; goto loop; case 0xFB: // EI - interrupts_enabled = 1; + //interrupts_enabled = 1; goto loop; // Special @@ -1080,11 +1029,8 @@ case 0x27: // DAA (I'll have to implement this eventually...) case 0xBF: case 0xED: // Z80 prefix - result = Gb_Cpu::result_badop; - goto stop; - case 0x76: // HALT - result = Gb_Cpu::result_halt; + s.remain++; goto stop; } @@ -1095,19 +1041,13 @@ pc--; // copy state back + STATIC_CAST(core_regs_t&,r) = rg; r.pc = pc; r.sp = sp; r.flags = flags; - r.a = rg.a; - r.b = rg.b; - r.c = rg.c; - r.d = rg.d; - r.e = rg.e; - r.h = rg.h; - r.l = rg.l; - return result; + this->state = &state_; + memcpy( &this->state_, &s, sizeof this->state_ ); + + return s.remain > 0; } - -#endif -
--- a/src/console/Gb_Cpu.h Wed Nov 29 14:42:11 2006 -0800 +++ b/src/console/Gb_Cpu.h Thu Nov 30 19:54:33 2006 -0800 @@ -1,103 +1,93 @@ - // Nintendo Game Boy CPU emulator +// Treats every instruction as taking 4 cycles -// Game_Music_Emu 0.3.0 - +// Game_Music_Emu 0.5.1 #ifndef GB_CPU_H #define GB_CPU_H #include "blargg_common.h" +#include "blargg_endian.h" typedef unsigned gb_addr_t; // 16-bit CPU address -class Gbs_Emu; - -// Game Boy CPU emulator. Currently treats every instruction as taking 4 cycles. class Gb_Cpu { + enum { clocks_per_instr = 4 }; +public: typedef BOOST::uint8_t uint8_t; - enum { page_bits = 8 }; - enum { page_count = 0x10000 >> page_bits }; - uint8_t const* code_map [page_count + 1]; - long remain_; - Gbs_Emu* callback_data; -public: - - Gb_Cpu( Gbs_Emu* ); - - // Memory read/write function types. Reader must return value from 0 to 255. - typedef int (*reader_t)( Gbs_Emu*, gb_addr_t ); - typedef void (*writer_t)( Gbs_Emu*, gb_addr_t, int data ); - // Clear registers, unmap memory, and map code pages to unmapped_page. - void reset( const void* unmapped_page = NULL, reader_t read = NULL, writer_t write = NULL ); - - // Memory mapping functions take a block of memory of specified 'start' address - // and 'size' in bytes. Both start address and size must be a multiple of page_size. - enum { page_size = 1L << page_bits }; + // Clear registers and map all pages to unmapped + void reset( void* unmapped = 0 ); - // Map code memory to 'code' (memory accessed via the program counter) - void map_code( gb_addr_t start, unsigned long size, const void* code ); + // Map code memory (memory accessed via the program counter). Start and size + // must be multiple of page_size. + enum { page_size = 0x2000 }; + void map_code( gb_addr_t start, unsigned size, void* code ); - // Map data memory to read and write functions - void map_memory( gb_addr_t start, unsigned long size, reader_t, writer_t ); - - // Access memory as the emulated CPU does. - int read( gb_addr_t ); - void write( gb_addr_t, int data ); - uint8_t* get_code( gb_addr_t ); // non-const to allow debugger to modify code + uint8_t* get_code( gb_addr_t ); // Push a byte on the stack void push_byte( int ); // Game Boy Z80 registers. *Not* kept updated during a call to run(). - struct registers_t { - gb_addr_t pc; // more than 16 bits to allow overflow detection + struct core_regs_t { + #if BLARGG_BIG_ENDIAN + uint8_t b, c, d, e, h, l, flags, a; + #else + uint8_t c, b, e, d, l, h, a, flags; + #endif + }; + + struct registers_t : core_regs_t { + long pc; // more than 16 bits to allow overflow detection BOOST::uint16_t sp; - uint8_t flags; - uint8_t a; - uint8_t b; - uint8_t c; - uint8_t d; - uint8_t e; - uint8_t h; - uint8_t l; }; registers_t r; // Interrupt enable flag set by EI and cleared by DI - bool interrupts_enabled; + //bool interrupts_enabled; // unused // Base address for RST vectors (normally 0) gb_addr_t rst_base; - // Reasons that run() returns - enum result_t { - result_cycles, // Requested number of cycles (or more) were executed - result_halt, // PC is at HALT instruction - result_badop // PC is at bad (unimplemented) instruction - }; + // If CPU executes opcode 0xFF at this address, it treats as illegal instruction + enum { idle_addr = 0xF00D }; - // Run CPU for at least 'count' cycles, or until one of the above conditions - // arises. Returns reason for stopping. - result_t run( long count ); + // Run CPU for at least 'count' cycles and return false, or return true if + // illegal instruction is encountered. + bool run( blargg_long count ); // Number of clock cycles remaining for most recent run() call - long remain() const; + blargg_long remain() const { return state->remain * clocks_per_instr; } + + // Can read this many bytes past end of a page + enum { cpu_padding = 8 }; +public: + Gb_Cpu() : rst_base( 0 ) { state = &state_; } + enum { page_shift = 13 }; + enum { page_count = 0x10000 >> page_shift }; private: // noncopyable Gb_Cpu( const Gb_Cpu& ); Gb_Cpu& operator = ( const Gb_Cpu& ); - reader_t data_reader [page_count + 1]; // extra entry catches address overflow - writer_t data_writer [page_count + 1]; - void set_code_page( int, uint8_t const* ); + struct state_t { + uint8_t* code_map [page_count + 1]; + blargg_long remain; + }; + state_t* state; // points to state_ or a local copy within run() + state_t state_; + + void set_code_page( int, uint8_t* ); }; -inline long Gb_Cpu::remain() const +inline BOOST::uint8_t* Gb_Cpu::get_code( gb_addr_t addr ) { - return remain_; + return state->code_map [addr >> page_shift] + addr + #if !BLARGG_NONPORTABLE + % (unsigned) page_size + #endif + ; } #endif -
--- a/src/console/Gb_Oscs.cxx Wed Nov 29 14:42:11 2006 -0800 +++ b/src/console/Gb_Oscs.cxx Thu Nov 30 19:54:33 2006 -0800 @@ -1,5 +1,4 @@ - -// Gb_Snd_Emu 0.1.4. http://www.slack.net/~ant/ +// Gb_Snd_Emu 0.1.5. http://www.slack.net/~ant/ #include "Gb_Apu.h" @@ -11,12 +10,12 @@ 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 */ +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_BEGIN +#include "blargg_source.h" // Gb_Osc @@ -53,7 +52,7 @@ switch ( reg ) { case 1: - length = 64 - (regs [1] & 0x3f); + length = 64 - (regs [1] & 0x3F); break; case 2: @@ -111,7 +110,7 @@ } } -void Gb_Square::run( gb_time_t time, gb_time_t end_time, int playing ) +void Gb_Square::run( blip_time_t time, blip_time_t end_time, int playing ) { if ( sweep_freq == 2048 ) playing = false; @@ -167,9 +166,7 @@ // Gb_Noise -#include BLARGG_ENABLE_OPTIMIZER - -void Gb_Noise::run( gb_time_t time, gb_time_t end_time, int playing ) +void Gb_Noise::run( blip_time_t time, blip_time_t end_time, int playing ) { int amp = volume & playing; int tap = 13 - (regs [3] & 8); @@ -251,7 +248,7 @@ } } -void Gb_Wave::run( gb_time_t time, gb_time_t end_time, int playing ) +void Gb_Wave::run( blip_time_t time, blip_time_t end_time, int playing ) { int volume_shift = (volume - 1) & 7; // volume = 0 causes shift = 7 int amp = (wave [wave_pos] >> volume_shift & playing) * 2; @@ -330,4 +327,3 @@ noise.bits = 0x7FFF; } } -
--- a/src/console/Gb_Oscs.h Wed Nov 29 14:42:11 2006 -0800 +++ b/src/console/Gb_Oscs.h Thu Nov 30 19:54:33 2006 -0800 @@ -1,8 +1,6 @@ - // Private oscillators used by Gb_Apu -// Gb_Snd_Emu 0.1.4 - +// Gb_Snd_Emu 0.1.5 #ifndef GB_OSCS_H #define GB_OSCS_H @@ -52,7 +50,7 @@ void reset(); void clock_sweep(); - void run( gb_time_t, gb_time_t, int playing ); + void run( blip_time_t, blip_time_t, int playing ); }; struct Gb_Noise : Gb_Env @@ -61,7 +59,7 @@ Synth const* synth; unsigned bits; - void run( gb_time_t, gb_time_t, int playing ); + void run( blip_time_t, blip_time_t, int playing ); }; struct Gb_Wave : Gb_Osc @@ -73,7 +71,7 @@ BOOST::uint8_t wave [wave_size]; void write_register( int, int ); - void run( gb_time_t, gb_time_t, int playing ); + void run( blip_time_t, blip_time_t, int playing ); }; inline void Gb_Env::reset() @@ -83,4 +81,3 @@ } #endif -
--- a/src/console/Gbs_Emu.cxx Wed Nov 29 14:42:11 2006 -0800 +++ b/src/console/Gbs_Emu.cxx Thu Nov 30 19:54:33 2006 -0800 @@ -1,178 +1,137 @@ - -// Game_Music_Emu 0.3.0. http://www.slack.net/~ant/ +// Game_Music_Emu 0.5.1. http://www.slack.net/~ant/ #include "Gbs_Emu.h" +#include "blargg_endian.h" #include <string.h> -#include "blargg_endian.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 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_BEGIN +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 */ -#ifndef RUN_GB_CPU - #define RUN_GB_CPU( cpu, n ) cpu.run( n ) -#endif - -const long bank_size = 0x4000; -const gb_addr_t ram_addr = 0xa000; -const gb_addr_t halt_addr = 0x9EFE; -static BOOST::uint8_t unmapped_code [Gb_Cpu::page_size]; +#include "blargg_source.h" Gbs_Emu::equalizer_t const Gbs_Emu::handheld_eq = { -47.0, 2000 }; -Gbs_Emu::equalizer_t const Gbs_Emu::headphones_eq = { 0.0, 300 }; - -// RAM - -int Gbs_Emu::read_ram( Gbs_Emu* emu, gb_addr_t addr ) -{ - return emu->ram [addr - ram_addr]; -} - -void Gbs_Emu::write_ram( Gbs_Emu* emu, gb_addr_t addr, int data ) -{ - emu->ram [addr - ram_addr] = data; -} - -// Unmapped - -int Gbs_Emu::read_unmapped( Gbs_Emu*, gb_addr_t addr ) -{ - dprintf( "Read from unmapped memory $%.4x\n", (unsigned) addr ); - return 0xFF; // open bus value -} - -void Gbs_Emu::write_unmapped( Gbs_Emu*, gb_addr_t addr, int ) -{ - dprintf( "Wrote to unmapped memory $%.4x\n", (unsigned) addr ); -} - -// ROM +Gbs_Emu::equalizer_t const Gbs_Emu::headphones_eq = { 0.0, 300 }; -int Gbs_Emu::read_rom( Gbs_Emu* emu, gb_addr_t addr ) -{ - return emu->rom [addr]; -} - -int Gbs_Emu::read_bank( Gbs_Emu* emu, gb_addr_t addr ) -{ - return emu->rom_bank [addr & (bank_size - 1)]; -} - -void Gbs_Emu::set_bank( int n ) -{ - if ( n >= bank_count ) - { - n = 0; - dprintf( "Set to non-existent bank %d\n", (int) n ); - } - if ( n == 0 && bank_count > 1 ) - { - // to do: what is the correct behavior? Current Wario Land 3 and - // Tetris DX GBS rips require that this have no effect or set to bank 1. - //return; - //dprintf( "Selected ROM bank 0\n" ); - } - rom_bank = &rom [n * bank_size]; - cpu.map_code( bank_size, bank_size, rom_bank ); -} - -void Gbs_Emu::write_rom( Gbs_Emu* emu, gb_addr_t addr, int data ) +Gbs_Emu::Gbs_Emu() { - if ( unsigned (addr - 0x2000) < 0x2000 ) - emu->set_bank( data & 0x1F ); -} - -// I/O: Timer, APU - -void Gbs_Emu::set_timer( int modulo, int rate ) -{ - if ( timer_mode ) - { - static byte const rates [4] = { 10, 4, 6, 8 }; - play_period = (gb_time_t) (256 - modulo) << (rates [rate & 3] - double_speed); - } -} - -inline gb_time_t Gbs_Emu::clock() const -{ - return cpu_time - cpu.remain(); -} + set_type( gme_gbs_type ); -int Gbs_Emu::read_io( Gbs_Emu* emu, gb_addr_t addr ) -{ - // hi_page is accessed most - if ( addr >= 0xFF80 ) - return emu->hi_page [addr & 0xFF]; - - if ( unsigned (addr - Gb_Apu::start_addr) <= Gb_Apu::register_count ) - return emu->apu.read_register( emu->clock(), addr ); + static const char* const names [Gb_Apu::osc_count] = { + "Square 1", "Square 2", "Wave", "Noise" + }; + set_voice_names( names ); - if ( addr == 0xFF00 ) - return 0; // joypad - - dprintf( "Unhandled I/O read 0x%4X\n", (unsigned) addr ); + static int const types [Gb_Apu::osc_count] = { + wave_type | 1, wave_type | 2, wave_type | 0, mixed_type | 0 + }; + set_voice_types( types ); - return 0xFF; -} - -void Gbs_Emu::write_io( Gbs_Emu* emu, gb_addr_t addr, int data ) -{ - // apu is accessed most - if ( unsigned (addr - Gb_Apu::start_addr) < Gb_Apu::register_count ) - { - emu->apu.write_register( emu->clock(), addr, data ); - } - else - { - emu->hi_page [addr & 0xFF] = data; - - if ( addr == 0xFF06 || addr == 0xFF07 ) - emu->set_timer( emu->hi_page [6], emu->hi_page [7] ); - - //if ( addr == 0xFFFF ) - // dprintf( "Wrote interrupt mask\n" ); - } -} - -Gbs_Emu::Gbs_Emu( double gain ) : cpu( this ) -{ - apu.volume( gain ); + set_silence_lookahead( 6 ); + set_max_initial_silence( 21 ); + set_gain( 1.2 ); static equalizer_t const eq = { -1.0, 120 }; set_equalizer( eq ); - - // unmapped code is all HALT instructions - memset( unmapped_code, 0x76, sizeof unmapped_code ); - - // cpu - cpu.reset( unmapped_code, read_unmapped, write_unmapped ); - cpu.map_memory( 0x0000, 0x4000, read_rom, write_rom ); - cpu.map_memory( 0x4000, 0x4000, read_bank, write_rom ); - cpu.map_memory( ram_addr, 0x4000, read_ram, write_ram ); - cpu.map_code( ram_addr, 0x4000, ram ); - cpu.map_code( 0xFF00, 0x0100, hi_page ); - cpu.map_memory( 0xFF00, 0x0100, read_io, write_io ); } -Gbs_Emu::~Gbs_Emu() -{ -} +Gbs_Emu::~Gbs_Emu() { } void Gbs_Emu::unload() { - cpu.r.pc = halt_addr; rom.clear(); + Music_Emu::unload(); +} + +// Track info + +static void copy_gbs_fields( Gbs_Emu::header_t const& h, track_info_t* out ) +{ + GME_COPY_FIELD( h, out, game ); + GME_COPY_FIELD( h, out, author ); + GME_COPY_FIELD( h, out, copyright ); +} + +blargg_err_t Gbs_Emu::track_info_( track_info_t* out, int ) const +{ + copy_gbs_fields( header_, out ); + return 0; +} + +static blargg_err_t check_gbs_header( void const* header ) +{ + if ( memcmp( header, "GBS", 3 ) ) + return gme_wrong_file_type; + return 0; +} + +struct Gbs_File : Gme_Info_ +{ + Gbs_Emu::header_t h; + + Gbs_File() { set_type( gme_gbs_type ); } + + blargg_err_t load_( Data_Reader& in ) + { + blargg_err_t err = in.read( &h, sizeof h ); + if ( err ) + return (err == in.eof_error ? gme_wrong_file_type : err); + + set_track_count( h.track_count ); + return check_gbs_header( &h ); + } + + blargg_err_t track_info_( track_info_t* out, int ) const + { + copy_gbs_fields( h, out ); + return 0; + } +}; + +static Music_Emu* new_gbs_emu () { return BLARGG_NEW Gbs_Emu ; } +static Music_Emu* new_gbs_file() { return BLARGG_NEW Gbs_File; } + +gme_type_t_ const gme_gbs_type [1] = { "Game Boy", 0, &new_gbs_emu, &new_gbs_file, "GBS", 1 }; + +// Setup + +blargg_err_t Gbs_Emu::load_( Data_Reader& in ) +{ + unload(); + RETURN_ERR( rom.load( in, sizeof header_, &header_, 0 ) ); + + set_track_count( header_.track_count ); + RETURN_ERR( check_gbs_header( &header_ ) ); + + if ( header_.vers != 1 ) + set_warning( "Unknown file version" ); + + if ( header_.timer_mode & 0x78 ) + set_warning( "Invalid timer mode" ); + + unsigned load_addr = get_le16( header_.load_addr ); + if ( (header_.load_addr [1] | header_.init_addr [1] | header_.play_addr [1]) > 0x7F || + load_addr < 0x400 ) + set_warning( "Invalid load/init/play address" ); + + set_voice_count( Gb_Apu::osc_count ); + + apu.volume( gain() ); + + return setup_buffer( 4194304 ); +} + +void Gbs_Emu::update_eq( blip_eq_t const& eq ) +{ + apu.treble_eq( eq ); } void Gbs_Emu::set_voice( int i, Blip_Buffer* c, Blip_Buffer* l, Blip_Buffer* r ) @@ -180,82 +139,41 @@ apu.osc_output( i, c, l, r ); } -void Gbs_Emu::update_eq( blip_eq_t const& eq ) -{ - apu.treble_eq( eq ); -} +// Emulation + +// see gb_cpu_io.h for read/write functions -blargg_err_t Gbs_Emu::load( Data_Reader& in ) +void Gbs_Emu::set_bank( int n ) { - header_t h; - BLARGG_RETURN_ERR( in.read( &h, sizeof h ) ); - return load( h, in ); + blargg_long addr = rom.mask_addr( n * (blargg_long) bank_size ); + if ( addr == 0 && rom.size() > bank_size ) + { + // TODO: what is the correct behavior? Current Game & Watch Gallery + // rip requires that this have no effect or set to bank 1. + //dprintf( "Selected ROM bank 0\n" ); + return; + //n = 1; + } + cpu::map_code( bank_size, bank_size, rom.at_addr( addr ) ); } -blargg_err_t Gbs_Emu::load( const header_t& h, Data_Reader& in ) +void Gbs_Emu::update_timer() { - header_ = h; - unload(); - - // check compatibility - if ( 0 != memcmp( header_.tag, "GBS", 3 ) ) - return "Not a GBS file"; - if ( header_.vers != 1 ) - return "Unsupported GBS format"; - - // gather relevant fields - load_addr = get_le16( header_.load_addr ); - init_addr = get_le16( header_.init_addr ); - play_addr = get_le16( header_.play_addr ); - stack_ptr = get_le16( header_.stack_ptr ); - double_speed = (header_.timer_mode & 0x80) != 0; - timer_modulo_init = header_.timer_modulo; - timer_mode = header_.timer_mode; - if ( !(timer_mode & 0x04) ) - timer_mode = 0; // using vbl - - #ifndef NDEBUG + if ( header_.timer_mode & 0x04 ) { - if ( header_.timer_mode & 0x78 ) - dprintf( "TAC field has extra bits set: 0x%02x\n", (unsigned) header_.timer_mode ); - - if ( load_addr < 0x400 || load_addr >= 0x8000 || - init_addr < 0x400 || init_addr >= 0x8000 || - play_addr < 0x400 || play_addr >= 0x8000 ) - dprintf( "Load/init/play address violates GBS spec.\n" ); + static byte const rates [4] = { 10, 4, 6, 8 }; + int shift = rates [ram [hi_page + 7] & 3] - (header_.timer_mode >> 7); + play_period = (256L - ram [hi_page + 6]) << shift; } - #endif - - // rom - bank_count = (load_addr + in.remain() + bank_size - 1) / bank_size; - BLARGG_RETURN_ERR( rom.resize( bank_count * bank_size ) ); - memset( rom.begin(), 0, rom.size() ); - blargg_err_t err = in.read( &rom [load_addr], in.remain() ); - if ( err ) + else { - unload(); - return err; + play_period = 70224; // 59.73 Hz } - - // cpu - cpu.rst_base = load_addr; - cpu.map_code( 0x0000, 0x4000, rom.begin() ); - - set_voice_count( Gb_Apu::osc_count ); - set_track_count( header_.track_count ); - - return setup_buffer( 4194304 ); + if ( tempo() != 1.0 ) + play_period = blip_time_t (play_period / tempo()); } -const char** Gbs_Emu::voice_names() const -{ - static const char* names [] = { "Square 1", "Square 2", "Wave", "Noise" }; - return names; -} - -// Emulation - -static const BOOST::uint8_t sound_data [Gb_Apu::register_count] = { +static BOOST::uint8_t const sound_data [Gb_Apu::register_count] = { 0x80, 0xBF, 0x00, 0x00, 0xBF, // square 1 0x00, 0x3F, 0x00, 0x00, 0xBF, // square 2 0x7F, 0xFF, 0x9F, 0x00, 0xBF, // wave @@ -268,99 +186,101 @@ void Gbs_Emu::cpu_jsr( gb_addr_t addr ) { - cpu.write( --cpu.r.sp, cpu.r.pc >> 8 ); - cpu.write( --cpu.r.sp, cpu.r.pc&0xFF ); - cpu.r.pc = addr; + check( cpu::r.sp == get_le16( header_.stack_ptr ) ); + cpu::r.pc = addr; + cpu_write( --cpu::r.sp, idle_addr >> 8 ); + cpu_write( --cpu::r.sp, idle_addr&0xFF ); } -void Gbs_Emu::start_track( int track_index ) +void Gbs_Emu::set_tempo_( double t ) { - require( rom.size() ); // file must be loaded + apu.set_tempo( t ); + update_timer(); +} + +blargg_err_t Gbs_Emu::start_track_( int track ) +{ + RETURN_ERR( Classic_Emu::start_track_( track ) ); - Classic_Emu::start_track( track_index ); + memset( ram, 0, 0x4000 ); + memset( ram + 0x4000, 0xFF, 0x1F80 ); + memset( ram + 0x5F80, 0, sizeof ram - 0x5F80 ); + ram [hi_page] = 0; // joypad reads back as 0 apu.reset(); - - memset( ram, 0, sizeof ram ); - memset( hi_page, 0, sizeof hi_page ); - - // configure hardware - set_bank( bank_count > 1 ); for ( int i = 0; i < (int) sizeof sound_data; i++ ) apu.write_register( 0, i + apu.start_addr, sound_data [i] ); - play_period = 70224; // 59.73 Hz - set_timer( timer_modulo_init, timer_mode ); // ignored if using vbl + + cpu::reset( rom.unmapped() ); + + unsigned load_addr = get_le16( header_.load_addr ); + cpu::rst_base = load_addr; + rom.set_addr( load_addr ); + + cpu::map_code( ram_addr, 0x10000 - ram_addr, ram ); + cpu::map_code( 0, bank_size, rom.at_addr( 0 ) ); + set_bank( rom.size() > bank_size ); + + ram [hi_page + 6] = header_.timer_modulo; + ram [hi_page + 7] = header_.timer_mode; + update_timer(); next_play = play_period; - // set up init call - cpu.r.a = track_index; - cpu.r.b = 0; - cpu.r.c = 0; - cpu.r.d = 0; - cpu.r.e = 0; - cpu.r.h = 0; - cpu.r.l = 0; - cpu.r.flags = 0; - cpu.r.pc = halt_addr; - cpu.r.sp = stack_ptr; - cpu_jsr( init_addr ); + cpu::r.a = track; + cpu::r.pc = idle_addr; + cpu::r.sp = get_le16( header_.stack_ptr ); + cpu_time = 0; + cpu_jsr( get_le16( header_.init_addr ) ); + + return 0; } -blip_time_t Gbs_Emu::run_clocks( blip_time_t duration, bool* added_stereo ) +blargg_err_t Gbs_Emu::run_clocks( blip_time_t& duration, int ) { - require( rom.size() ); // file must be loaded - cpu_time = 0; while ( cpu_time < duration ) { - // check for idle cpu - if ( cpu.r.pc == halt_addr ) - { - if ( next_play > duration ) - { - cpu_time = duration; - break; - } - - if ( cpu_time < next_play ) - cpu_time = next_play; - next_play += play_period; - cpu_jsr( play_addr ); - } - long count = duration - cpu_time; cpu_time = duration; - Gb_Cpu::result_t result = RUN_GB_CPU( cpu, count ); - cpu_time -= cpu.remain(); + bool result = cpu::run( count ); + cpu_time -= cpu::remain(); - if ( (result == Gb_Cpu::result_halt && cpu.r.pc != halt_addr) || - result == Gb_Cpu::result_badop ) + if ( result ) { - if ( cpu.r.pc > 0xFFFF ) + if ( cpu::r.pc == idle_addr ) + { + if ( next_play > duration ) + { + cpu_time = duration; + break; + } + + if ( cpu_time < next_play ) + cpu_time = next_play; + next_play += play_period; + cpu_jsr( get_le16( header_.play_addr ) ); + } + else if ( cpu::r.pc > 0xFFFF ) { dprintf( "PC wrapped around\n" ); - cpu.r.pc &= 0xFFFF; + cpu::r.pc &= 0xFFFF; } else { - log_error(); + set_warning( "Emulation error (illegal/unsupported instruction)" ); dprintf( "Bad opcode $%.2x at $%.4x\n", - (int) cpu.read( cpu.r.pc ), (int) cpu.r.pc ); - cpu.r.pc = (cpu.r.pc + 1) & 0xFFFF; + (int) *cpu::get_code( cpu::r.pc ), (int) cpu::r.pc ); + cpu::r.pc = (cpu::r.pc + 1) & 0xFFFF; cpu_time += 6; } } } - // end time frame - + duration = cpu_time; next_play -= cpu_time; if ( next_play < 0 ) // could go negative if routine is taking too long to return next_play = 0; - - if ( apu.end_frame( cpu_time ) && added_stereo ) - *added_stereo = true; + apu.end_frame( cpu_time ); - return cpu_time; + return 0; } -
--- a/src/console/Gbs_Emu.h Wed Nov 29 14:42:11 2006 -0800 +++ b/src/console/Gbs_Emu.h Thu Nov 30 19:54:33 2006 -0800 @@ -1,8 +1,6 @@ - // Nintendo Game Boy GBS music file emulator -// Game_Music_Emu 0.3.0 - +// Game_Music_Emu 0.5.1 #ifndef GBS_EMU_H #define GBS_EMU_H @@ -10,12 +8,12 @@ #include "Gb_Apu.h" #include "Gb_Cpu.h" -class Gbs_Emu : public Classic_Emu { +class Gbs_Emu : private Gb_Cpu, public Classic_Emu { + typedef Gb_Cpu cpu; public: - - // Sets internal gain, where 1.0 results in almost no clamping. Default gain - // roughly matches volume of other emulators. - Gbs_Emu( double gain = 1.2 ); + // Equalizer profiles for Game Boy Color speaker and headphones + static equalizer_t const handheld_eq; + static equalizer_t const headphones_eq; // GBS file header struct header_t @@ -33,76 +31,58 @@ char game [32]; char author [32]; char copyright [32]; - - enum { song = 0 }; // no song titles }; BOOST_STATIC_ASSERT( sizeof (header_t) == 112 ); - // Load GBS data - blargg_err_t load( Data_Reader& ); - - // Load GBS using already-loaded header and remaining data - blargg_err_t load( header_t const&, Data_Reader& ); - - // Header for currently loaded GBS + // Header for currently loaded file header_t const& header() const { return header_; } - // Equalizer profiles for Game Boy Color speaker and headphones - static equalizer_t const handheld_eq; - static equalizer_t const headphones_eq; + static gme_type_t static_type() { return gme_gbs_type; } public: + // deprecated + Music_Emu::load; + blargg_err_t load( header_t const& h, Data_Reader& in ) // use Remaining_Reader + { return load_remaining_( &h, sizeof h, in ); } + +public: + Gbs_Emu(); ~Gbs_Emu(); - const char** voice_names() const; - void start_track( int ); protected: + blargg_err_t track_info_( track_info_t*, int track ) const; + blargg_err_t load_( Data_Reader& ); + blargg_err_t start_track_( int ); + blargg_err_t run_clocks( blip_time_t&, int ); + void set_tempo_( double ); void set_voice( int, Blip_Buffer*, Blip_Buffer*, Blip_Buffer* ); void update_eq( blip_eq_t const& ); - blip_time_t run_clocks( blip_time_t, bool* ); + void unload(); private: // rom - const byte* rom_bank; - blargg_vector<byte> rom; - void unload(); - int bank_count; + enum { bank_size = 0x4000 }; + Rom_Data<bank_size> rom; void set_bank( int ); - static void write_rom( Gbs_Emu*, gb_addr_t, int ); - static int read_rom( Gbs_Emu*, gb_addr_t ); - static int read_bank( Gbs_Emu*, gb_addr_t ); - - // state - gb_addr_t load_addr; - gb_addr_t init_addr; - gb_addr_t play_addr; - gb_addr_t stack_ptr; - int timer_modulo_init; - int timer_mode; // timer - gb_time_t cpu_time; - gb_time_t play_period; - gb_time_t next_play; - int double_speed; - - // hardware - Gb_Apu apu; - void set_timer( int tma, int tmc ); - static int read_io( Gbs_Emu*, gb_addr_t ); - static void write_io( Gbs_Emu*, gb_addr_t, int ); - static int read_unmapped( Gbs_Emu*, gb_addr_t ); - static void write_unmapped( Gbs_Emu*, gb_addr_t, int ); - - // large objects + blip_time_t cpu_time; + blip_time_t play_period; + blip_time_t next_play; + void update_timer(); header_t header_; - byte hi_page [0x100]; - Gb_Cpu cpu; void cpu_jsr( gb_addr_t ); - gb_time_t clock() const; - byte ram [0x4000]; - static int read_ram( Gbs_Emu*, gb_addr_t ); - static void write_ram( Gbs_Emu*, gb_addr_t, int ); + +public: private: friend class Gb_Cpu; + blip_time_t clock() const { return cpu_time - cpu::remain(); } + + enum { joypad_addr = 0xFF00 }; + enum { ram_addr = 0xA000 }; + enum { hi_page = 0xFF00 - ram_addr }; + byte ram [0x4000 + 0x2000 + Gb_Cpu::cpu_padding]; + Gb_Apu apu; + + int cpu_read( gb_addr_t ); + void cpu_write( gb_addr_t, int ); }; #endif -
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/console/Gme_File.cxx Thu Nov 30 19:54:33 2006 -0800 @@ -0,0 +1,210 @@ +// Game_Music_Emu 0.5.1. http://www.slack.net/~ant/ + +#include "Gme_File.h" + +#include "blargg_endian.h" +#include <string.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 +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" + +const char gme_wrong_file_type [] = "Wrong file type for this emulator"; + +void Gme_File::clear_playlist() +{ + playlist.clear(); + clear_playlist_(); + track_count_ = raw_track_count_; +} + +void Gme_File::unload() +{ + clear_playlist(); // *before* clearing track count + track_count_ = 0; + raw_track_count_ = 0; + file_data.clear(); +} + +Gme_File::Gme_File() +{ + type_ = 0; + unload(); // clears fields + blargg_verify_byte_order(); // used by most emulator types, so save them the trouble +} + +Gme_File::~Gme_File() { } + +blargg_err_t Gme_File::load_mem_( byte const* data, long size ) +{ + require( data != file_data.begin() ); // load_mem_() or load_() must be overridden + Mem_File_Reader in( data, size ); + return load_( in ); +} + +blargg_err_t Gme_File::load_( Data_Reader& in ) +{ + RETURN_ERR( file_data.resize( in.remain() ) ); + RETURN_ERR( in.read( file_data.begin(), file_data.size() ) ); + return load_mem_( file_data.begin(), file_data.size() ); +} + +// public load functions call this at beginning +void Gme_File::pre_load() { unload(); } + +void Gme_File::post_load_() { } + +// public load functions call this at end +blargg_err_t Gme_File::post_load( blargg_err_t err ) +{ + if ( !track_count() ) + set_track_count( type()->track_count ); + if ( !err ) + post_load_(); + else + unload(); + + return err; +} + +// Public load functions + +blargg_err_t Gme_File::load_mem( void const* in, long size ) +{ + pre_load(); + return post_load( load_mem_( (byte const*) in, size ) ); +} + +blargg_err_t Gme_File::load( Data_Reader& in ) +{ + pre_load(); + return post_load( load_( in ) ); +} + +blargg_err_t Gme_File::load_file( const char* path ) +{ + pre_load(); + GME_FILE_READER in; + RETURN_ERR( in.open( path ) ); + return post_load( load_( in ) ); +} + +blargg_err_t Gme_File::load_remaining_( void const* h, long s, Data_Reader& in ) +{ + Remaining_Reader rem( h, s, &in ); + return load( rem ); +} + +// Track info + +void Gme_File::copy_field_( char* out, const char* in, int in_size ) +{ + if ( !in || !*in ) + return; + + // remove spaces/junk from beginning + while ( in_size && unsigned (*in - 1) <= ' ' - 1 ) + { + in++; + in_size--; + } + + // truncate + if ( in_size > max_field_ ) + in_size = max_field_; + + // find terminator + int len = 0; + while ( len < in_size && in [len] ) + len++; + + // remove spaces/junk from end + while ( len && unsigned (in [len - 1]) <= ' ' ) + len--; + + // copy + out [len] = 0; + memcpy( out, in, len ); + + // strip out stupid fields that should have been left blank + if ( !strcmp( out, "?" ) || !strcmp( out, "<?>" ) || !strcmp( out, "< ? >" ) ) + out [0] = 0; +} + +void Gme_File::copy_field_( char* out, const char* in ) +{ + copy_field_( out, in, max_field_ ); +} + +blargg_err_t Gme_File::remap_track( int* track_io ) const +{ + if ( (unsigned) *track_io >= (unsigned) track_count() ) + return "Invalid track"; + + if ( (unsigned) *track_io < (unsigned) playlist.size() ) + { + M3u_Playlist::entry_t const& e = playlist [*track_io]; + *track_io = 0; + if ( e.track >= 0 ) + { + *track_io = e.track; + if ( !(type_->flags_ & 0x02) ) + *track_io -= e.decimal_track; + } + if ( *track_io >= raw_track_count_ ) + return "Invalid track in m3u playlist"; + } + else + { + check( !playlist.size() ); + } + return 0; +} + +blargg_err_t Gme_File::track_info( track_info_t* out, int track ) const +{ + out->track_count = track_count(); + out->length = -1; + out->loop_length = -1; + out->intro_length = -1; + out->song [0] = 0; + + out->game [0] = 0; + out->author [0] = 0; + out->copyright [0] = 0; + out->comment [0] = 0; + out->dumper [0] = 0; + out->system [0] = 0; + + copy_field_( out->system, type()->system ); + + int remapped = track; + RETURN_ERR( remap_track( &remapped ) ); + RETURN_ERR( track_info_( out, remapped ) ); + + // override with m3u info + if ( playlist.size() ) + { + M3u_Playlist::info_t const& i = playlist.info(); + copy_field_( out->game , i.title ); + copy_field_( out->author, i.engineer ); + copy_field_( out->author, i.composer ); + copy_field_( out->dumper, i.ripping ); + + M3u_Playlist::entry_t const& e = playlist [track]; + copy_field_( out->song, e.name ); + if ( e.length >= 0 ) out->length = e.length * 1000L; + if ( e.intro >= 0 ) out->intro_length = e.intro * 1000L; + if ( e.loop >= 0 ) out->loop_length = e.loop * 1000L; + } + return 0; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/console/Gme_File.h Thu Nov 30 19:54:33 2006 -0800 @@ -0,0 +1,129 @@ +// Common interface to game music file loading and information + +// Game_Music_Emu 0.5.1 +#ifndef GME_FILE_H +#define GME_FILE_H + +#include "gme.h" +#include "blargg_common.h" +#include "Data_Reader.h" +#include "M3u_Playlist.h" + +// Error returned if file is wrong type +//extern const char gme_wrong_file_type []; // declared in gme.h + +struct Gme_File { +public: +// File loading + + // Each loads game music data from a file and returns an error if + // file is wrong type or is seriously corrupt. They also set warning + // string for minor problems. + + // Load from file on disk + blargg_err_t load_file( const char* path ); + + // Load from custom data source (see Data_Reader.h) + blargg_err_t load( Data_Reader& ); + + // Load from file already read into memory. Keeps pointer to data, so you + // must not free it until you're done with the file. + blargg_err_t load_mem( void const* data, long size ); + + // Load an m3u playlist. Must be done after loading main music file. + blargg_err_t load_m3u( const char* path ); + blargg_err_t load_m3u( Data_Reader& in ); + + // Clears any loaded m3u playlist and any internal playlist that the music + // format supports (NSFE for example). + void clear_playlist(); + +// Informational + + // Type of emulator. For example if this returns gme_nsfe_type, this object + // is an NSFE emulator, and you can cast to an Nsfe_Emu* if necessary. + gme_type_t type() const; + + // Most recent warning string, or NULL if none. Clears current warning after + // returning. + const char* warning(); + + // Number of tracks or 0 if no file has been loaded + int track_count() const; + + // Get information for a track (length, name, author, etc.) + // See gme.h for definition of struct track_info_t. + blargg_err_t track_info( track_info_t* out, int track ) const; + +public: + // deprecated + int error_count() const; // use warning() +public: + Gme_File(); + virtual ~Gme_File(); + BLARGG_DISABLE_NOTHROW + typedef BOOST::uint8_t byte; +protected: + // Services + void set_track_count( int n ) { track_count_ = raw_track_count_ = n; } + void set_warning( const char* s ) { warning_ = s; } + void set_type( gme_type_t t ) { type_ = t; } + blargg_err_t load_remaining_( void const* header, long header_size, Data_Reader& remaining ); + + // Overridable + virtual void unload(); // called before loading file and if loading fails + virtual blargg_err_t load_( Data_Reader& ); // default loads then calls load_mem_() + virtual blargg_err_t load_mem_( byte const* data, long size ); // use data in memory + virtual blargg_err_t track_info_( track_info_t* out, int track ) const = 0; + virtual void pre_load(); + virtual void post_load_(); + virtual void clear_playlist_() { } + +protected: + blargg_err_t remap_track( int* track_io ) const; // need by Music_Emu +private: + // noncopyable + Gme_File( const Gme_File& ); + Gme_File& operator = ( const Gme_File& ); + + gme_type_t type_; + int track_count_; + int raw_track_count_; + const char* warning_; + M3u_Playlist playlist; + blargg_vector<byte> file_data; // only if loaded into memory using default load + + blargg_err_t load_m3u_( blargg_err_t ); + blargg_err_t post_load( blargg_err_t err ); +public: + // track_info field copying + enum { max_field_ = 255 }; + static void copy_field_( char* out, const char* in ); + static void copy_field_( char* out, const char* in, int len ); +}; + +Music_Emu* gme_new_( Music_Emu*, long sample_rate ); + +#define GME_COPY_FIELD( in, out, name ) \ + { Gme_File::copy_field_( out->name, in.name, sizeof in.name ); } + +#ifndef GME_FILE_READER + #ifdef HAVE_ZLIB_H + #define GME_FILE_READER Gzip_File_Reader + #else + #define GME_FILE_READER Std_File_Reader + #endif +#endif + +inline gme_type_t Gme_File::type() const { return type_; } +inline int Gme_File::error_count() const { return warning_ != 0; } +inline int Gme_File::track_count() const { return track_count_; } + +inline const char* Gme_File::warning() +{ + const char* s = warning_; + warning_ = 0; + return s; +} + +#endif
--- a/src/console/Gym_Emu.cxx Wed Nov 29 14:42:11 2006 -0800 +++ b/src/console/Gym_Emu.cxx Thu Nov 30 19:54:33 2006 -0800 @@ -1,10 +1,9 @@ - -// Game_Music_Emu 0.3.0. http://www.slack.net/~ant/ +// Game_Music_Emu 0.5.1. http://www.slack.net/~ant/ #include "Gym_Emu.h" +#include "blargg_endian.h" #include <string.h> -#include "blargg_endian.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 @@ -12,152 +11,83 @@ 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 */ +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_BEGIN +#include "blargg_source.h" -double const gain = 3.0; +double const min_tempo = 0.25; double const oversample_factor = 5 / 3.0; +double const fm_gain = 3.0; const long base_clock = 53700300; const long clock_rate = base_clock / 15; Gym_Emu::Gym_Emu() { - data = NULL; - pos = NULL; -} - -Gym_Emu::~Gym_Emu() -{ - unload(); -} - -void Gym_Emu::unload() -{ - data = NULL; - pos = NULL; - set_track_ended( false ); - mem.clear(); -} - -blargg_err_t Gym_Emu::set_sample_rate( long sample_rate ) -{ - blip_eq_t eq( -32, 8000, sample_rate ); - apu.treble_eq( eq ); - apu.volume( 0.135 * gain ); - dac_synth.treble_eq( eq ); - dac_synth.volume( 0.125 / 256 * gain ); + data = 0; + pos = 0; + set_type( gme_gym_type ); - BLARGG_RETURN_ERR( blip_buf.set_sample_rate( sample_rate, 1000 / 60 ) ); - blip_buf.clock_rate( clock_rate ); - - double factor = Dual_Resampler::setup( oversample_factor, 0.990, gain ); - double fm_sample_rate = sample_rate * factor; - BLARGG_RETURN_ERR( fm.set_rate( fm_sample_rate, base_clock / 7.0 ) ); - BLARGG_RETURN_ERR( Dual_Resampler::resize( sample_rate / 60 ) ); - - return Music_Emu::set_sample_rate( sample_rate ); -} - -void Gym_Emu::mute_voices( int mask ) -{ - Music_Emu::mute_voices( mask ); - fm.mute_voices( mask ); - dac_muted = mask & 0x40; - apu.output( (mask & 0x80) ? NULL : &blip_buf ); -} - -const char** Gym_Emu::voice_names() const -{ - static const char* names [] = { + static const char* const names [] = { "FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "PCM", "PSG" }; - return names; + set_voice_names( names ); + set_silence_lookahead( 1 ); // tracks should already be trimmed } -static blargg_err_t check_header( const Gym_Emu::header_t& h, int* data_offset = NULL ) +Gym_Emu::~Gym_Emu() { } + +// Track info + +static void get_gym_info( Gym_Emu::header_t const& h, long length, track_info_t* out ) { - if ( memcmp( h.tag, "GYMX", 4 ) == 0 ) + if ( !memcmp( h.tag, "GYMX", 4 ) ) { - if ( memcmp( h.packed, "\0\0\0\0", 4 ) != 0 ) - return "Packed GYM file not supported"; + length = length * 50 / 3; // 1000 / 60 + long loop = get_le32( h.loop_start ); + if ( loop ) + { + out->intro_length = loop * 50 / 3; + out->loop_length = length - out->intro_length; + } + else + { + out->length = length; + out->intro_length = length; // make it clear that track is no longer than length + out->loop_length = 0; + } - if ( data_offset ) - *data_offset = sizeof h; - } - else if ( h.tag [0] != 0 && h.tag [0] != 1 ) - { - // not a headerless GYM - // to do: more thorough check, or just require a damn header - return "Not a GYM file"; + // more stupidity where the field should have been left + if ( strcmp( h.song, "Unknown Song" ) ) + GME_COPY_FIELD( h, out, song ); + + if ( strcmp( h.game, "Unknown Game" ) ) + GME_COPY_FIELD( h, out, game ); + + if ( strcmp( h.copyright, "Unknown Publisher" ) ) + GME_COPY_FIELD( h, out, copyright ); + + if ( strcmp( h.dumper, "Unknown Person" ) ) + GME_COPY_FIELD( h, out, dumper ); + + if ( strcmp( h.comment, "Header added by YMAMP" ) ) + GME_COPY_FIELD( h, out, comment ); } - - return blargg_success; -} - -blargg_err_t Gym_Emu::load_( const void* file, long data_offset, long file_size ) -{ - require( blip_buf.length() ); - - data = (const byte*) file + data_offset; - data_end = (const byte*) file + file_size; - - loop_begin = NULL; - if ( data_offset ) - header_ = *(header_t*) file; - else - memset( &header_, 0, sizeof header_ ); - - set_voice_count( 8 ); - set_track_count( 1 ); - remute_voices(); - - return blargg_success; } -blargg_err_t Gym_Emu::load( const void* file, long file_size ) +blargg_err_t Gym_Emu::track_info_( track_info_t* out, int ) const { - unload(); - - if ( file_size < (int) sizeof (header_t) ) - return "Not a GYM file"; - - int data_offset = 0; - BLARGG_RETURN_ERR( check_header( *(header_t*) file, &data_offset ) ); - - return load_( file, data_offset, file_size ); -} - -blargg_err_t Gym_Emu::load( Data_Reader& in ) -{ - header_t h; - BLARGG_RETURN_ERR( in.read( &h, sizeof h ) ); - return load( h, in ); + get_gym_info( header_, track_length(), out ); + return 0; } -blargg_err_t Gym_Emu::load( const header_t& h, Data_Reader& in ) -{ - unload(); - - int data_offset = 0; - BLARGG_RETURN_ERR( check_header( h, &data_offset ) ); - - BLARGG_RETURN_ERR( mem.resize( sizeof h + in.remain() ) ); - memcpy( mem.begin(), &h, sizeof h ); - BLARGG_RETURN_ERR( in.read( &mem [sizeof h], mem.size() - sizeof h ) ); - - return load_( mem.begin(), data_offset, mem.size() ); -} - -long Gym_Emu::track_length() const +static long gym_track_length( byte const* p, byte const* end ) { long time = 0; - const byte* p = data; - while ( p < data_end ) + while ( p < end ) { switch ( *p++ ) { @@ -178,23 +108,141 @@ return time; } -void Gym_Emu::start_track( int track ) +long Gym_Emu::track_length() const { return gym_track_length( data, data_end ); } + +static blargg_err_t check_header( byte const* in, long size, int* data_offset = 0 ) { - require( data ); + if ( size < 4 ) + return gme_wrong_file_type; + + if ( memcmp( in, "GYMX", 4 ) == 0 ) + { + if ( size < (long) sizeof (Gym_Emu::header_t) + 1 ) + return gme_wrong_file_type; + + if ( memcmp( ((Gym_Emu::header_t const*) in)->packed, "\0\0\0\0", 4 ) != 0 ) + return "Packed GYM file not supported"; + + if ( data_offset ) + *data_offset = sizeof (Gym_Emu::header_t); + } + else if ( *in != 0 && *in != 1 ) + { + return gme_wrong_file_type; + } + + return 0; +} + +struct Gym_File : Gme_Info_ +{ + byte const* file_begin; + byte const* file_end; + int data_offset; + + Gym_File() { set_type( gme_gym_type ); } + + blargg_err_t load_mem_( byte const* in, long size ) + { + file_begin = in; + file_end = in + size; + data_offset = 0; + return check_header( in, size, &data_offset ); + } - Music_Emu::start_track( track ); + blargg_err_t track_info_( track_info_t* out, int ) const + { + long length = gym_track_length( &file_begin [data_offset], file_end ); + get_gym_info( *(Gym_Emu::header_t const*) file_begin, length, out ); + return 0; + } +}; + +static Music_Emu* new_gym_emu () { return BLARGG_NEW Gym_Emu ; } +static Music_Emu* new_gym_file() { return BLARGG_NEW Gym_File; } + +gme_type_t_ const gme_gym_type [1] = { "Sega Genesis", 1, &new_gym_emu, &new_gym_file, "GYM", 0 }; + +// Setup + +blargg_err_t Gym_Emu::set_sample_rate_( long sample_rate ) +{ + blip_eq_t eq( -32, 8000, sample_rate ); + apu.treble_eq( eq ); + dac_synth.treble_eq( eq ); + apu.volume( 0.135 * fm_gain * gain() ); + dac_synth.volume( 0.125 / 256 * fm_gain * gain() ); + double factor = Dual_Resampler::setup( oversample_factor, 0.990, fm_gain * gain() ); + fm_sample_rate = sample_rate * factor; + + RETURN_ERR( blip_buf.set_sample_rate( sample_rate, int (1000 / 60.0 / min_tempo) ) ); + blip_buf.clock_rate( clock_rate ); + + RETURN_ERR( fm.set_rate( fm_sample_rate, base_clock / 7.0 ) ); + RETURN_ERR( Dual_Resampler::reset( long (1.0 / 60 / min_tempo * sample_rate) ) ); - pos = &data [0]; + return 0; +} + +void Gym_Emu::set_tempo_( double t ) +{ + if ( t < min_tempo ) + { + set_tempo( min_tempo ); + return; + } + + if ( blip_buf.sample_rate() ) + { + clocks_per_frame = long (clock_rate / 60 / tempo()); + Dual_Resampler::resize( long (sample_rate() / (60.0 * tempo())) ); + } +} + +void Gym_Emu::mute_voices_( int mask ) +{ + Music_Emu::mute_voices_( mask ); + fm.mute_voices( mask ); + dac_muted = mask & 0x40; + apu.output( (mask & 0x80) ? 0 : &blip_buf ); +} + +blargg_err_t Gym_Emu::load_mem_( byte const* in, long size ) +{ + int offset = 0; + RETURN_ERR( check_header( in, size, &offset ) ); + set_voice_count( 8 ); + + data = in + offset; + data_end = in + size; + loop_begin = 0; + + if ( offset ) + header_ = *(header_t const*) in; + else + memset( &header_, 0, sizeof header_ ); + + return 0; +} + +// Emulation + +blargg_err_t Gym_Emu::start_track_( int track ) +{ + RETURN_ERR( Music_Emu::start_track_( track ) ); + + pos = data; loop_remain = get_le32( header_.loop_start ); prev_dac_count = 0; - dac_enabled = false; - dac_amp = -1; + dac_enabled = false; + dac_amp = -1; fm.reset(); apu.reset(); blip_buf.clear(); Dual_Resampler::clear(); + return 0; } void Gym_Emu::run_dac( int dac_count ) @@ -228,8 +276,7 @@ } // Evenly space samples within buffer section being used - blip_resampled_time_t period = - blip_buf.resampled_duration( clock_rate / 60 ) / rate_count; + blip_resampled_time_t period = blip_buf.resampled_duration( clocks_per_frame ) / rate_count; blip_resampled_time_t time = blip_buf.resampled_time( 0 ) + period * start + (period >> 1); @@ -296,8 +343,7 @@ // loop if ( pos >= data_end ) { - if ( pos > data_end ) - log_error(); + check( pos == data_end ); if ( loop_begin ) pos = loop_begin; @@ -325,26 +371,8 @@ return sample_count; } -void Gym_Emu::play( long count, sample_t* out ) -{ - require( pos ); - - Dual_Resampler::play( count, out, blip_buf ); -} - -void Gym_Emu::skip( long count ) +blargg_err_t Gym_Emu::play_( long count, sample_t* out ) { - // to do: figure out why total muting generated access violation on MorphOS - const int buf_size = 1024; - sample_t buf [buf_size]; - - while ( count ) - { - int n = buf_size; - if ( n > count ) - n = count; - count -= n; - play( n, buf ); - } + Dual_Resampler::dual_play( count, out, blip_buf ); + return 0; } -
--- a/src/console/Gym_Emu.h Wed Nov 29 14:42:11 2006 -0800 +++ b/src/console/Gym_Emu.h Thu Nov 30 19:54:33 2006 -0800 @@ -1,8 +1,7 @@ - -// Sega Genesis GYM music file emulator +// Sega Genesis/Mega Drive GYM music file emulator +// Includes with PCM timing recovery to improve sample quality. -// Game_Music_Emu 0.3.0 - +// Game_Music_Emu 0.5.1 #ifndef GYM_EMU_H #define GYM_EMU_H @@ -13,7 +12,6 @@ class Gym_Emu : public Music_Emu, private Dual_Resampler { public: - // GYM file header struct header_t { @@ -26,43 +24,33 @@ char comment [256]; byte loop_start [4]; // in 1/60 seconds, 0 if not looped byte packed [4]; - - enum { track_count = 1 }; // one track per file - enum { author = 0 }; // no author field }; BOOST_STATIC_ASSERT( sizeof (header_t) == 428 ); - // Load GYM data - blargg_err_t load( Data_Reader& ); - - // Load GYM file using already-loaded header and remaining data - blargg_err_t load( header_t const&, Data_Reader& ); - blargg_err_t load( void const* data, long size ); // keeps pointer to data - - // Header for currently loaded GYM (cleared to zero if GYM lacks header) + // Header for currently loaded file header_t const& header() const { return header_; } - // Length of track in 1/60 seconds - enum { gym_rate = 60 }; // GYM time units (frames) per second - long track_length() const; + static gme_type_t static_type() { return gme_gym_type; } + +public: + // deprecated + Music_Emu::load; + blargg_err_t load( header_t const& h, Data_Reader& in ) // use Remaining_Reader + { return load_remaining_( &h, sizeof h, in ); } + enum { gym_rate = 60 }; + long track_length() const; // use track_info() public: - typedef Music_Emu::sample_t sample_t; Gym_Emu(); ~Gym_Emu(); - blargg_err_t set_sample_rate( long sample_rate ); - void mute_voices( int ); - void start_track( int ); - void play( long count, sample_t* ); - const char** voice_names() const; - void skip( long count ); -public: - // deprecated - blargg_err_t init( long r, double gain = 1.5, double oversample = 5 / 3.0 ) - { - return set_sample_rate( r ); - } protected: + blargg_err_t load_mem_( byte const*, long ); + blargg_err_t track_info_( track_info_t*, int track ) const; + blargg_err_t set_sample_rate_( long sample_rate ); + blargg_err_t start_track_( int ); + blargg_err_t play_( long count, sample_t* ); + void mute_voices_( int ); + void set_tempo_( double ); int play_frame( blip_time_t blip_time, int sample_count, sample_t* buf ); private: // sequence data begin, loop begin, current position, end @@ -70,11 +58,10 @@ const byte* loop_begin; const byte* pos; const byte* data_end; - long loop_remain; // frames remaining until loop beginning has been located - blargg_vector<byte> mem; + blargg_long loop_remain; // frames remaining until loop beginning has been located header_t header_; - blargg_err_t load_( const void* file, long data_offset, long file_size ); - void unload(); + double fm_sample_rate; + blargg_long clocks_per_frame; void parse_frame(); // dac (pcm) @@ -93,4 +80,3 @@ }; #endif -
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/console/Hes_Apu.cxx Thu Nov 30 19:54:33 2006 -0800 @@ -0,0 +1,315 @@ +// Game_Music_Emu 0.5.1. http://www.slack.net/~ant/ + +#include "Hes_Apu.h" + +#include <string.h> + +/* Copyright (C) 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" + +bool const center_waves = true; // reduces asymmetry and clamping when starting notes + +Hes_Apu::Hes_Apu() +{ + Hes_Osc* osc = &oscs [osc_count]; + do + { + osc--; + osc->outputs [0] = 0; + osc->outputs [1] = 0; + osc->chans [0] = 0; + osc->chans [1] = 0; + osc->chans [2] = 0; + } + while ( osc != oscs ); + + reset(); +} + +void Hes_Apu::reset() +{ + latch = 0; + balance = 0xFF; + + Hes_Osc* osc = &oscs [osc_count]; + do + { + osc--; + memset( osc, 0, offsetof (Hes_Osc,outputs) ); + osc->noise_lfsr = 1; + osc->control = 0x40; + osc->balance = 0xFF; + } + while ( osc != oscs ); +} + +void Hes_Apu::osc_output( int index, Blip_Buffer* center, Blip_Buffer* left, Blip_Buffer* right ) +{ + require( (unsigned) index < osc_count ); + oscs [index].chans [0] = center; + oscs [index].chans [1] = left; + oscs [index].chans [2] = right; + + Hes_Osc* osc = &oscs [osc_count]; + do + { + osc--; + balance_changed( *osc ); + } + while ( osc != oscs ); +} + +void Hes_Osc::run_until( synth_t& synth_, blip_time_t end_time ) +{ + Blip_Buffer* const osc_outputs_0 = outputs [0]; // cache often-used values + if ( osc_outputs_0 && control & 0x80 ) + { + int dac = this->dac; + + int const volume_0 = volume [0]; + { + int delta = dac * volume_0 - last_amp [0]; + if ( delta ) + synth_.offset( last_time, delta, osc_outputs_0 ); + osc_outputs_0->set_modified(); + } + + Blip_Buffer* const osc_outputs_1 = outputs [1]; + int const volume_1 = volume [1]; + if ( osc_outputs_1 ) + { + int delta = dac * volume_1 - last_amp [1]; + if ( delta ) + synth_.offset( last_time, delta, osc_outputs_1 ); + osc_outputs_1->set_modified(); + } + + blip_time_t time = last_time + delay; + if ( time < end_time ) + { + if ( noise & 0x80 ) + { + if ( volume_0 | volume_1 ) + { + // noise + int const period = (32 - (noise & 0x1F)) * 64; // TODO: correct? + unsigned noise_lfsr = this->noise_lfsr; + do + { + 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( time, delta * volume_0, osc_outputs_0 ); + if ( osc_outputs_1 ) + synth_.offset( time, delta * volume_1, osc_outputs_1 ); + } + time += period; + } + while ( time < end_time ); + + this->noise_lfsr = noise_lfsr; + assert( noise_lfsr ); + } + } + else if ( !(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) ) + { + do + { + int new_dac = wave [phase]; + phase = (phase + 1) & 0x1F; + int delta = new_dac - dac; + if ( delta ) + { + dac = new_dac; + synth_.offset( time, delta * volume_0, osc_outputs_0 ); + if ( osc_outputs_1 ) + synth_.offset( time, delta * volume_1, osc_outputs_1 ); + } + time += period; + } + while ( time < end_time ); + } + else + { + 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; + } + this->phase = (phase - 1) & 0x1F; // undo pre-advance + } + } + time -= end_time; + if ( time < 0 ) + time = 0; + delay = time; + + this->dac = dac; + last_amp [0] = dac * volume_0; + last_amp [1] = dac * volume_1; + } + last_time = end_time; +} + +void Hes_Apu::balance_changed( Hes_Osc& osc ) +{ + static short const log_table [32] = { // ~1.5 db per step + #define ENTRY( factor ) short (factor * Hes_Osc::amp_range / 31.0 + 0.5) + ENTRY( 0.000000 ),ENTRY( 0.005524 ),ENTRY( 0.006570 ),ENTRY( 0.007813 ), + ENTRY( 0.009291 ),ENTRY( 0.011049 ),ENTRY( 0.013139 ),ENTRY( 0.015625 ), + ENTRY( 0.018581 ),ENTRY( 0.022097 ),ENTRY( 0.026278 ),ENTRY( 0.031250 ), + ENTRY( 0.037163 ),ENTRY( 0.044194 ),ENTRY( 0.052556 ),ENTRY( 0.062500 ), + ENTRY( 0.074325 ),ENTRY( 0.088388 ),ENTRY( 0.105112 ),ENTRY( 0.125000 ), + ENTRY( 0.148651 ),ENTRY( 0.176777 ),ENTRY( 0.210224 ),ENTRY( 0.250000 ), + ENTRY( 0.297302 ),ENTRY( 0.353553 ),ENTRY( 0.420448 ),ENTRY( 0.500000 ), + ENTRY( 0.594604 ),ENTRY( 0.707107 ),ENTRY( 0.840896 ),ENTRY( 1.000000 ), + #undef ENTRY + }; + + int vol = (osc.control & 0x1F) - 0x1E * 2; + + int left = vol + (osc.balance >> 3 & 0x1E) + (balance >> 3 & 0x1E); + if ( left < 0 ) left = 0; + + int right = vol + (osc.balance << 1 & 0x1E) + (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 ) + { + osc.outputs [0] = osc.chans [1]; // left + osc.outputs [1] = osc.chans [2]; // right + } + + if ( center_waves ) + { + osc.last_amp [0] += (left - osc.volume [0]) * 16; + osc.last_amp [1] += (right - osc.volume [1]) * 16; + } + + osc.volume [0] = left; + osc.volume [1] = right; +} + +void Hes_Apu::write_data( blip_time_t time, int addr, int data ) +{ + if ( addr == 0x800 ) + { + latch = data & 7; + } + else if ( addr == 0x801 ) + { + if ( balance != data ) + { + balance = data; + + Hes_Osc* osc = &oscs [osc_count]; + do + { + osc--; + osc->run_until( synth, time ); + balance_changed( *oscs ); + } + while ( osc != oscs ); + } + } + else if ( latch < osc_count ) + { + Hes_Osc& osc = oscs [latch]; + osc.run_until( synth, time ); + switch ( addr ) + { + case 0x802: + osc.period = (osc.period & 0xF00) | data; + break; + + case 0x803: + osc.period = (osc.period & 0x0FF) | ((data & 0x0F) << 8); + break; + + case 0x804: + if ( osc.control & 0x40 & ~data ) + osc.phase = 0; + osc.control = data; + balance_changed( osc ); + break; + + case 0x805: + osc.balance = data; + balance_changed( osc ); + break; + + case 0x806: + data &= 0x1F; + if ( !(osc.control & 0x40) ) + { + osc.wave [osc.phase] = data; + osc.phase = (osc.phase + 1) & 0x1F; + } + else if ( osc.control & 0x80 ) + { + osc.dac = data; + } + break; + + case 0x807: + if ( &osc >= &oscs [4] ) + osc.noise = data; + break; + + case 0x809: + if ( !(data & 0x80) && (data & 0x03) != 0 ) + dprintf( "HES LFO not supported\n" ); + } + } +} + +void Hes_Apu::end_frame( blip_time_t end_time ) +{ + Hes_Osc* osc = &oscs [osc_count]; + do + { + osc--; + if ( end_time > osc->last_time ) + osc->run_until( synth, end_time ); + assert( osc->last_time >= end_time ); + osc->last_time -= end_time; + } + while ( osc != oscs ); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/console/Hes_Apu.h Thu Nov 30 19:54:33 2006 -0800 @@ -0,0 +1,65 @@ +// Turbo Grafx 16 (PC Engine) PSG sound chip emulator + +#ifndef HES_APU_H +#define HES_APU_H + +#include "blargg_common.h" +#include "Blip_Buffer.h" + +struct Hes_Osc +{ + unsigned char wave [32]; + 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; + + Blip_Buffer* outputs [2]; + Blip_Buffer* chans [3]; + unsigned noise_lfsr; + unsigned char control; + + enum { amp_range = 0x8000 }; + typedef Blip_Synth<blip_med_quality,1> synth_t; + + void run_until( synth_t& synth, blip_time_t ); +}; + +class Hes_Apu { +public: + void treble_eq( blip_eq_t const& ); + void volume( double ); + + enum { osc_count = 6 }; + void osc_output( int index, Blip_Buffer* center, Blip_Buffer* left, Blip_Buffer* right ); + + void reset(); + + enum { start_addr = 0x0800 }; + enum { end_addr = 0x0809 }; + void write_data( blip_time_t, int addr, int data ); + + void end_frame( blip_time_t ); + +public: + Hes_Apu(); +private: + Hes_Osc oscs [osc_count]; + int latch; + int balance; + Hes_Osc::synth_t synth; + + void balance_changed( Hes_Osc& ); + void recalc_chans(); +}; + +inline void Hes_Apu::volume( double v ) { synth.volume( 1.8 / osc_count / Hes_Osc::amp_range * v ); } + +inline void Hes_Apu::treble_eq( blip_eq_t const& eq ) { synth.treble_eq( eq ); } + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/console/Hes_Cpu.cxx Thu Nov 30 19:54:33 2006 -0800 @@ -0,0 +1,1302 @@ +// Game_Music_Emu 0.5.1. http://www.slack.net/~ant/ + +#include "Hes_Cpu.h" + +#include "blargg_endian.h" + +//#include "hes_cpu_log.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 +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; + +#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" + +#if 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 Hes_Cpu::reset() +{ + check( state == &state_ ); + state = &state_; + + state_.time = 0; + state_.base = 0; + irq_time_ = future_hes_time; + end_time_ = future_hes_time; + + r.status = st_i; + r.sp = 0; + r.pc = 0; + r.a = 0; + r.x = 0; + r.y = 0; + + blargg_verify_byte_order(); +} + +void Hes_Cpu::set_mmr( int reg, int bank ) +{ + assert( (unsigned) reg <= page_count ); // allow page past end to be set + assert( (unsigned) bank < 0x100 ); + mmr [reg] = bank; + uint8_t const* code = CPU_SET_MMR( this, reg, bank ); + 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 ) (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 Hes_Cpu::run( hes_time_t end_time ) +{ + bool illegal_encountered = false; + set_end_time( end_time ); + state_t s = this->state_; + this->state = &s; + // even on x86, using s.time in place of s_time was slower + fint16 s_time = s.time; + + // 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 + #if 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 + +#define BRANCH( cond )\ +{\ + fint16 offset = (BOOST::int8_t) data;\ + pc++;\ + if ( !(cond) ) goto branch_not_taken;\ + 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 + + { + 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; + + { + fuint16 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( 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; + } + +// 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 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;\ + }\ + 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:\ + FLUSH_TIME();\ + data = READ( 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 + (BOOST::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 ); + this->r.status = status; // update externally-visible I flag + if ( (data ^ status) & st_i ) + { + hes_time_t new_time = end_time_; + if ( !(status & st_i) && new_time > irq_time_ ) + new_time = 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: { + this->r.status = status; // update externally-visible I flag + blargg_long delta = s.base - irq_time_; + if ( delta <= 0 ) + { + if ( TIME < irq_time_ ) + goto loop; + goto delayed_cli; + } + s.base = 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; + 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: { + this->r.status = status; // update externally-visible I flag + blargg_long delta = s.base - end_time_; + s.base = 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++; + for ( int i = 0; i < 8; i++ ) + if ( bits & (1 << i) ) + set_mmr( i, a ); + goto loop; + } + + case 0x43:{// TMA + pc++; + byte const* in = 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; + } + +// Illegal + + 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; + this->r.status = status; // update externally-visible I flag + + blargg_long delta = s.base - end_time_; + s.base = 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; + } + + this->state_ = s; + this->state = &this->state_; + + return illegal_encountered; +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/console/Hes_Cpu.h Thu Nov 30 19:54:33 2006 -0800 @@ -0,0 +1,124 @@ +// PC Engine CPU emulator for use with HES music files + +// Game_Music_Emu 0.5.1 +#ifndef HES_CPU_H +#define HES_CPU_H + +#include "blargg_common.h" + +typedef blargg_long hes_time_t; // clock cycle count +typedef unsigned hes_addr_t; // 16-bit address +enum { future_hes_time = LONG_MAX / 2 + 1 }; + +class Hes_Cpu { +public: + typedef BOOST::uint8_t uint8_t; + + void reset(); + + enum { page_size = 0x2000 }; + enum { page_shift = 13 }; + enum { page_count = 8 }; + void set_mmr( int reg, int bank ); + + uint8_t const* get_code( hes_addr_t ); + + uint8_t ram [page_size]; + + // not kept updated during a call to run() + struct registers_t { + BOOST::uint16_t pc; + uint8_t a; + uint8_t x; + uint8_t y; + uint8_t status; + uint8_t sp; + }; + registers_t r; + + // page mapping registers + uint8_t mmr [page_count + 1]; + + // Set end_time and run CPU from current time. Returns true if any illegal + // instructions were encountered. + bool run( hes_time_t end_time ); + + // Time of beginning of next instruction to be executed + hes_time_t time() const { return state->time + state->base; } + void set_time( hes_time_t t ) { state->time = t - state->base; } + void adjust_time( int delta ) { state->time += delta; } + + hes_time_t irq_time() const { return irq_time_; } + void set_irq_time( hes_time_t ); + + hes_time_t end_time() const { return end_time_; } + void set_end_time( hes_time_t ); + + void end_frame( hes_time_t ); + + // 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 }; + +public: + Hes_Cpu() { state = &state_; } + enum { irq_inhibit = 0x04 }; +private: + // noncopyable + Hes_Cpu( const Hes_Cpu& ); + Hes_Cpu& operator = ( const Hes_Cpu& ); + + struct state_t { + uint8_t const* code_map [page_count + 1]; + hes_time_t base; + blargg_long time; + }; + state_t* state; // points to state_ or a local copy within run() + state_t state_; + hes_time_t irq_time_; + hes_time_t end_time_; + + void set_code_page( int, void const* ); + inline int update_end_time( hes_time_t end, hes_time_t irq ); +}; + +inline BOOST::uint8_t const* Hes_Cpu::get_code( hes_addr_t addr ) +{ + return state->code_map [addr >> page_shift] + addr + #if !BLARGG_NONPORTABLE + % (unsigned) page_size + #endif + ; +} + +inline int Hes_Cpu::update_end_time( hes_time_t t, hes_time_t irq ) +{ + if ( irq < t && !(r.status & irq_inhibit) ) t = irq; + int delta = state->base - t; + state->base = t; + return delta; +} + +inline void Hes_Cpu::set_irq_time( hes_time_t t ) +{ + state->time += update_end_time( end_time_, (irq_time_ = t) ); +} + +inline void Hes_Cpu::set_end_time( hes_time_t t ) +{ + state->time += update_end_time( (end_time_ = t), irq_time_ ); +} + +inline void Hes_Cpu::end_frame( hes_time_t t ) +{ + assert( state == &state_ ); + state_.base -= t; + if ( irq_time_ < future_hes_time ) irq_time_ -= t; + if ( end_time_ < future_hes_time ) end_time_ -= t; +} + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/console/Hes_Emu.cxx Thu Nov 30 19:54:33 2006 -0800 @@ -0,0 +1,507 @@ +// Game_Music_Emu 0.5.1. http://www.slack.net/~ant/ + +#include "Hes_Emu.h" + +#include "blargg_endian.h" +#include <string.h> + +/* Copyright (C) 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" + +int const timer_mask = 0x04; +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 + +Hes_Emu::Hes_Emu() +{ + timer.raw_load = 0; + set_type( gme_hes_type ); + + static const char* const names [Hes_Apu::osc_count] = { + "Wave 1", "Wave 2", "Wave 3", "Wave 4", "Multi 1", "Multi 2" + }; + set_voice_names( names ); + + static int const types [Hes_Apu::osc_count] = { + wave_type | 0, wave_type | 1, wave_type | 2, wave_type | 3, + mixed_type | 0, mixed_type | 1 + }; + set_voice_types( types ); + set_silence_lookahead( 6 ); + set_gain( 1.11 ); +} + +Hes_Emu::~Hes_Emu() { } + +void Hes_Emu::unload() +{ + rom.clear(); + Music_Emu::unload(); +} + +// Track info + +static byte const* copy_field( byte const* in, char* out ) +{ + if ( in ) + { + int len = 0x20; + if ( in [0x1F] && !in [0x2F] ) + len = 0x30; // fields are sometimes 16 bytes longer (ugh) + + // since text fields are where any data could be, detect non-text + // and fields with data after zero byte terminator + + int i = 0; + for ( i = 0; i < len && in [i]; i++ ) + if ( ((in [i] + 1) & 0xFF) < ' ' + 1 ) // also treat 0xFF as non-text + return 0; // non-ASCII found + + for ( ; i < len; i++ ) + if ( in [i] ) + return 0; // data after terminator + + Gme_File::copy_field_( out, (char const*) in, len ); + in += len; + } + return in; +} + +static void copy_hes_fields( byte const* in, track_info_t* out ) +{ + if ( *in >= ' ' ) + { + in = copy_field( in, out->game ); + in = copy_field( in, out->author ); + in = copy_field( in, out->copyright ); + } +} + +blargg_err_t Hes_Emu::track_info_( track_info_t* out, int ) const +{ + copy_hes_fields( rom.begin() + 0x20, out ); + return 0; +} + +static blargg_err_t check_hes_header( void const* header ) +{ + if ( memcmp( header, "HESM", 4 ) ) + return gme_wrong_file_type; + return 0; +} + +struct Hes_File : Gme_Info_ +{ + struct { + Hes_Emu::header_t h; + char unused [0x20]; + byte fields [0x30 * 3]; + } h; + + Hes_File() { set_type( gme_hes_type ); } + + blargg_err_t load_( Data_Reader& in ) + { + blargg_err_t err = in.read( &h, sizeof h ); + if ( err ) + return (err == in.eof_error ? gme_wrong_file_type : err); + return check_hes_header( &h ); + } + + blargg_err_t track_info_( track_info_t* out, int ) const + { + copy_hes_fields( h.fields, out ); + return 0; + } +}; + +static Music_Emu* new_hes_emu () { return BLARGG_NEW Hes_Emu ; } +static Music_Emu* new_hes_file() { return BLARGG_NEW Hes_File; } + +gme_type_t_ const gme_hes_type [1] = { "PC Engine", 256, &new_hes_emu, &new_hes_file, "HES", 1 }; + +// Setup + +blargg_err_t Hes_Emu::load_( Data_Reader& in ) +{ + RETURN_ERR( rom.load( in, sizeof header_, &header_, unmapped ) ); + + RETURN_ERR( check_hes_header( header_.tag ) ); + + if ( header_.vers != 0 ) + set_warning( "Unknown file version" ); + + if ( memcmp( header_.data_tag, "DATA", 4 ) ) + set_warning( "Data header missing" ); + + if ( memcmp( header_.unused, "\0\0\0\0", 4 ) ) + set_warning( "Unknown header data" ); + + // File spec supports multiple blocks, but I haven't found any, and + // 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( header_.addr ); + long size = get_le32( header_.size ); + long const rom_max = 0x100000; + if ( addr & ~(rom_max - 1) ) + { + set_warning( "Invalid address" ); + addr &= rom_max - 1; + } + if ( (unsigned long) (addr + size) > (unsigned long) rom_max ) + set_warning( "Invalid size" ); + + if ( size != rom.file_size() ) + { + if ( size <= rom.file_size() - 4 && !memcmp( rom.begin() + size, "DATA", 4 ) ) + set_warning( "Multiple DATA not supported" ); + else if ( size < rom.file_size() ) + set_warning( "Extra file data" ); + else + set_warning( "Missing file data" ); + } + + rom.set_addr( addr ); + + set_voice_count( apu.osc_count ); + + apu.volume( gain() ); + + return setup_buffer( 7159091 ); +} + +void Hes_Emu::update_eq( blip_eq_t const& eq ) +{ + apu.treble_eq( eq ); +} + +void Hes_Emu::set_voice( int i, Blip_Buffer* center, Blip_Buffer* left, Blip_Buffer* right ) +{ + apu.osc_output( i, center, left, right ); +} + +// Emulation + +void Hes_Emu::recalc_timer_load() +{ + timer.load = timer.raw_load * timer_base + 1; +} + +void Hes_Emu::set_tempo_( double t ) +{ + play_period = hes_time_t (period_60hz / t); + timer_base = int (1024 / t); + recalc_timer_load(); +} + +blargg_err_t Hes_Emu::start_track_( int track ) +{ + RETURN_ERR( Classic_Emu::start_track_( track ) ); + + memset( ram, 0, sizeof ram ); // some HES music relies on zero fill + memset( sgx, 0, sizeof sgx ); + + apu.reset(); + cpu::reset(); + + for ( unsigned i = 0; i < sizeof header_.banks; i++ ) + set_mmr( i, header_.banks [i] ); + set_mmr( page_count, 0xFF ); // unmapped beyond end of address space + + irq.disables = timer_mask | vdp_mask; + irq.timer = future_hes_time; + irq.vdp = future_hes_time; + + timer.enabled = false; + timer.raw_load= 0x80; + timer.count = timer.load; + timer.fired = false; + timer.last_time = 0; + + vdp.latch = 0; + vdp.control = 0; + vdp.next_vbl = 0; + + ram [0x1FF] = (idle_addr - 1) >> 8; + ram [0x1FE] = (idle_addr - 1); + r.sp = 0xFD; + r.pc = get_le16( header_.init_addr ); + r.a = track; + + recalc_timer_load(); + + return 0; +} + +// Hardware + +void Hes_Emu::cpu_write_vdp( int addr, int data ) +{ + switch ( addr ) + { + case 0: + vdp.latch = data & 0x1F; + break; + + case 2: + if ( vdp.latch == 5 ) + { + if ( data & 0x04 ) + set_warning( "Scanline interrupt unsupported" ); + run_until( time() ); + vdp.control = data; + irq_changed(); + } + else + { + dprintf( "VDP not supported: $%02X <- $%02X\n", vdp.latch, data ); + } + break; + + case 3: + dprintf( "VDP MSB not supported: $%02X <- $%02X\n", vdp.latch, data ); + break; + } +} + +void Hes_Emu::cpu_write_( hes_addr_t addr, int data ) +{ + if ( unsigned (addr - apu.start_addr) <= apu.end_addr - apu.start_addr ) + { + // avoid going way past end when a long block xfer is writing to I/O space + hes_time_t t = min( time(), end_time() + 8 ); + apu.write_data( t, addr, data ); + return; + } + + hes_time_t time = this->time(); + switch ( addr ) + { + case 0x0000: + case 0x0002: + case 0x0003: + cpu_write_vdp( addr, data ); + return; + + case 0x0C00: { + run_until( time ); + timer.raw_load = (data & 0x7F) + 1; + recalc_timer_load(); + timer.count = timer.load; + break; + } + + case 0x0C01: + data &= 1; + if ( timer.enabled == data ) + return; + run_until( time ); + timer.enabled = data; + if ( data ) + timer.count = timer.load; + break; + + case 0x1402: + run_until( time ); + irq.disables = data; + if ( (data & 0xF8) && (data & 0xF8) != 0xF8 ) // flag questionable values + dprintf( "Int mask: $%02X\n", data ); + break; + + case 0x1403: + run_until( time ); + if ( timer.enabled ) + timer.count = timer.load; + timer.fired = false; + break; + +#ifndef NDEBUG + case 0x1000: // I/O port + case 0x0402: // palette + case 0x0403: + case 0x0404: + case 0x0405: + return; + + default: + dprintf( "unmapped write $%04X <- $%02X\n", addr, data ); + return; +#endif + } + + irq_changed(); +} + +int Hes_Emu::cpu_read_( hes_addr_t addr ) +{ + hes_time_t time = this->time(); + addr &= page_size - 1; + switch ( addr ) + { + case 0x0000: + if ( irq.vdp > time ) + return 0; + irq.vdp = future_hes_time; + run_until( time ); + irq_changed(); + return 0x20; + + case 0x0002: + case 0x0003: + dprintf( "VDP read not supported: %d\n", addr ); + return 0; + + case 0x0C01: + //return timer.enabled; // TODO: remove? + case 0x0C00: + run_until( time ); + dprintf( "Timer count read\n" ); + return (unsigned) (timer.count - 1) / timer_base; + + case 0x1402: + return irq.disables; + + case 0x1403: + { + int status = 0; + if ( irq.timer <= time ) status |= timer_mask; + if ( irq.vdp <= time ) status |= vdp_mask; + return status; + } + + #ifndef NDEBUG + case 0x1000: // I/O port + case 0x180C: // CD-ROM + case 0x180D: + break; + + default: + dprintf( "unmapped read $%04X\n", addr ); + #endif + } + + return unmapped; +} + +// see hes_cpu_io.h for core read/write functions + +// Emulation + +void Hes_Emu::run_until( hes_time_t present ) +{ + while ( vdp.next_vbl < present ) + vdp.next_vbl += play_period; + + hes_time_t elapsed = present - timer.last_time; + if ( elapsed > 0 ) + { + if ( timer.enabled ) + { + timer.count -= elapsed; + if ( timer.count <= 0 ) + timer.count += timer.load; + } + timer.last_time = present; + } +} + +void Hes_Emu::irq_changed() +{ + hes_time_t present = time(); + + if ( irq.timer > present ) + { + irq.timer = future_hes_time; + if ( timer.enabled && !timer.fired ) + irq.timer = present + timer.count; + } + + if ( irq.vdp > present ) + { + irq.vdp = future_hes_time; + if ( vdp.control & 0x08 ) + irq.vdp = vdp.next_vbl; + } + + hes_time_t time = future_hes_time; + if ( !(irq.disables & timer_mask) ) time = irq.timer; + if ( !(irq.disables & vdp_mask) ) time = min( time, irq.vdp ); + + set_irq_time( time ); +} + +int Hes_Emu::cpu_done() +{ + check( time() >= end_time() || + (!(r.status & i_flag_mask) && time() >= irq_time()) ); + + if ( !(r.status & i_flag_mask) ) + { + hes_time_t present = time(); + + if ( irq.timer <= present && !(irq.disables & timer_mask) ) + { + timer.fired = true; + irq.timer = future_hes_time; + irq_changed(); // overkill, but not worth writing custom code + return 0x0A; + } + + if ( irq.vdp <= present && !(irq.disables & vdp_mask) ) + { + // work around for bugs with music not acknowledging VDP + //run_until( present ); + //irq.vdp = future_hes_time; + //irq_changed(); + return 0x08; + } + } + return 0; +} + +static void adjust_time( blargg_long& time, hes_time_t delta ) +{ + if ( time < future_hes_time ) + { + time -= delta; + if ( time < 0 ) + time = 0; + } +} + +blargg_err_t Hes_Emu::run_clocks( blip_time_t& duration_, int ) +{ + blip_time_t const duration = duration_; // cache + + if ( cpu::run( duration ) ) + set_warning( "Emulation error (illegal instruction)" ); + + check( time() >= duration ); + //check( time() - duration < 20 ); // Txx instruction could cause going way over + + run_until( duration ); + + // end time frame + timer.last_time -= duration; + vdp.next_vbl -= duration; + cpu::end_frame( duration ); + ::adjust_time( irq.timer, duration ); + ::adjust_time( irq.vdp, duration ); + apu.end_frame( duration ); + + return 0; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/console/Hes_Emu.h Thu Nov 30 19:54:33 2006 -0800 @@ -0,0 +1,93 @@ +// TurboGrafx-16/PC Engine HES music file emulator + +// Game_Music_Emu 0.5.1 +#ifndef HES_EMU_H +#define HES_EMU_H + +#include "Classic_Emu.h" +#include "Hes_Apu.h" +#include "Hes_Cpu.h" + +class Hes_Emu : private Hes_Cpu, public Classic_Emu { + typedef Hes_Cpu cpu; +public: + // HES file header + struct header_t + { + 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]; + }; + BOOST_STATIC_ASSERT( sizeof (header_t) == 0x20 ); + + // Header for currently loaded file + header_t const& header() const { return header_; } + + static gme_type_t static_type() { return gme_hes_type; } + +public: + Hes_Emu(); + ~Hes_Emu(); +protected: + blargg_err_t track_info_( track_info_t*, int track ) const; + blargg_err_t load_( Data_Reader& ); + blargg_err_t start_track_( int ); + blargg_err_t run_clocks( blip_time_t&, int ); + void set_tempo_( double ); + void set_voice( int, Blip_Buffer*, Blip_Buffer*, Blip_Buffer* ); + void update_eq( blip_eq_t const& ); + void unload(); +public: private: friend class Hes_Cpu; + byte* write_pages [page_count + 1]; // 0 if unmapped or I/O space + + int cpu_read_( hes_addr_t ); + int cpu_read( hes_addr_t ); + void cpu_write_( hes_addr_t, int data ); + void cpu_write( hes_addr_t, int ); + void cpu_write_vdp( int addr, int data ); + byte const* cpu_set_mmr( int page, int bank ); + int cpu_done(); +private: + Rom_Data<page_size> rom; + header_t header_; + hes_time_t play_period; + int timer_base; + + struct { + hes_time_t last_time; + blargg_long count; + blargg_long load; + int raw_load; + byte enabled; + byte fired; + } timer; + + struct { + hes_time_t next_vbl; + byte latch; + byte control; + } vdp; + + struct { + hes_time_t timer; + hes_time_t vdp; + byte disables; + } irq; + + void recalc_timer_load(); + + // large items + Hes_Apu apu; + byte sgx [3 * page_size + cpu_padding]; + + void irq_changed(); + void run_until( hes_time_t ); +}; + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/console/Kss_Cpu.cxx Thu Nov 30 19:54:33 2006 -0800 @@ -0,0 +1,1699 @@ +// Game_Music_Emu 0.5.1. http://www.slack.net/~ant/ + +// Last validated with zexall 2006.11.14 2:19 PM +// Doesn't implement interrupts or the R register, though both would be +// easy to support. + +#include "Kss_Cpu.h" + +#include "blargg_endian.h" +#include <string.h> + +//#include "z80_cpu_log.h" + +/* Copyright (C) 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 */ + +#define SYNC_TIME() (void) (s.time = s_time) +#define RELOAD_TIME() (void) (s_time = s.time) + +// Callbacks to emulator + +#define CPU_OUT( cpu, addr, data, time ) \ + kss_cpu_out( this, time, addr, data ) + +#define CPU_IN( cpu, addr, time ) \ + kss_cpu_in( this, time, addr ) + +#define CPU_WRITE( cpu, addr, data, time ) \ + (SYNC_TIME(), kss_cpu_write( this, addr, data )) + +#include "blargg_source.h" + +// flags, named with hex value for clarity +int const S80 = 0x80; +int const Z40 = 0x40; +int const F20 = 0x20; +int const H10 = 0x10; +int const F08 = 0x08; +int const V04 = 0x04; +int const P04 = 0x04; +int const N02 = 0x02; +int const C01 = 0x01; + +#define SZ28P( n ) szpc [n] +#define SZ28PC( n ) szpc [n] +#define SZ28C( n ) (szpc [n] & ~P04) +#define SZ28( n ) SZ28C( n ) + +#define SET_R( n ) (void) (r.r = n) +#define GET_R() (r.r) + +Kss_Cpu::Kss_Cpu() +{ + state = &state_; + + for ( int i = 0x100; --i >= 0; ) + { + int even = 1; + for ( int p = i; p; p >>= 1 ) + even ^= p; + int n = (i & (S80 | F20 | F08)) | ((even & 1) * P04); + szpc [i] = n; + szpc [i + 0x100] = n | C01; + } + szpc [0x000] |= Z40; + szpc [0x100] |= Z40; +} + +inline void Kss_Cpu::set_page( int i, void* write, void const* read ) +{ + blargg_long offset = KSS_CPU_PAGE_OFFSET( i * (blargg_long) page_size ); + state->write [i] = (byte *) write - offset; + state->read [i] = (byte const*) read - offset; +} + +void Kss_Cpu::reset( void* unmapped_write, void const* unmapped_read ) +{ + check( state == &state_ ); + state = &state_; + state_.time = 0; + state_.base = 0; + end_time_ = 0; + + for ( int i = 0; i < page_count + 1; i++ ) + set_page( i, unmapped_write, unmapped_read ); + + memset( &r, 0, sizeof r ); +} + +void Kss_Cpu::map_mem( unsigned addr, blargg_ulong size, void* write, void const* read ) +{ + // address range must begin and end on page boundaries + require( addr % page_size == 0 ); + require( size % page_size == 0 ); + + unsigned first_page = addr / page_size; + for ( unsigned i = size / page_size; i--; ) + { + blargg_long offset = i * (blargg_long) page_size; + set_page( first_page + i, (byte*) write + offset, (byte const*) read + offset ); + } +} + +#define TIME (s_time + s.base) +#define RW_MEM( addr, rw ) (s.rw [(addr) >> page_shift] [KSS_CPU_PAGE_OFFSET( addr )]) +#define READ_PROG( addr ) RW_MEM( addr, read ) +#define READ( addr ) READ_PROG( addr ) +//#define WRITE( addr, data ) (void) (RW_MEM( addr, write ) = data) +#define WRITE( addr, data ) CPU_WRITE( this, addr, data, TIME ) +#define READ_WORD( addr ) GET_LE16( &READ( addr ) ) +#define WRITE_WORD( addr, data ) SET_LE16( &RW_MEM( addr, write ), data ) +#define IN( addr ) CPU_IN( this, addr, TIME ) +#define OUT( addr, data ) CPU_OUT( this, addr, data, TIME ) + +#if BLARGG_BIG_ENDIAN + #define R8( n, offset ) ((r8_ - offset) [n]) +#elif BLARGG_LITTLE_ENDIAN + #define R8( n, offset ) ((r8_ - offset) [(n) ^ 1]) +#else + #error "Byte order of CPU must be known" +#endif + +//#define R16( n, shift, offset ) (r16_ [((n) >> shift) - (offset >> shift)]) + +// help compiler see that it can adjust stack offset +#define R16( n, shift, offset ) \ + (*(uint16_t*) ((char*) r16_ - (offset >> (shift - 1)) + ((n) >> (shift - 1)))) + +#define CASE5( a, b, c, d, e ) case 0x##a:case 0x##b:case 0x##c:case 0x##d:case 0x##e +#define CASE6( a, b, c, d, e, f ) CASE5( a, b, c, d, e ): case 0x##f +#define CASE7( a, b, c, d, e, f, g ) CASE6( a, b, c, d, e, f ): case 0x##g +#define CASE8( a, b, c, d, e, f, g, h ) CASE7( a, b, c, d, e, f, g ): case 0x##h + +// high four bits are $ED time - 8, low four bits are $DD/$FD time - 8 +static byte const ed_dd_timing [0x100] = { +//0 1 2 3 4 5 6 7 8 9 A B C D E F +0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x07,0x00,0x00,0x00,0x00,0x00,0x00, +0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x07,0x00,0x00,0x00,0x00,0x00,0x00, +0x00,0x06,0x0C,0x02,0x00,0x00,0x03,0x00,0x00,0x07,0x0C,0x02,0x00,0x00,0x03,0x00, +0x00,0x00,0x00,0x00,0x0F,0x0F,0x0B,0x00,0x00,0x07,0x00,0x00,0x00,0x00,0x00,0x00, +0x40,0x40,0x70,0xC0,0x00,0x60,0x0B,0x10,0x40,0x40,0x70,0xC0,0x00,0x60,0x0B,0x10, +0x40,0x40,0x70,0xC0,0x00,0x60,0x0B,0x10,0x40,0x40,0x70,0xC0,0x00,0x60,0x0B,0x10, +0x40,0x40,0x70,0xC0,0x00,0x60,0x0B,0xA0,0x40,0x40,0x70,0xC0,0x00,0x60,0x0B,0xA0, +0x4B,0x4B,0x7B,0xCB,0x0B,0x6B,0x00,0x0B,0x40,0x40,0x70,0xC0,0x00,0x60,0x0B,0x00, +0x00,0x00,0x00,0x00,0x00,0x00,0x0B,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0B,0x00, +0x00,0x00,0x00,0x00,0x00,0x00,0x0B,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0B,0x00, +0x80,0x80,0x80,0x80,0x00,0x00,0x0B,0x00,0x80,0x80,0x80,0x80,0x00,0x00,0x0B,0x00, +0xD0,0xD0,0xD0,0xD0,0x00,0x00,0x0B,0x00,0xD0,0xD0,0xD0,0xD0,0x00,0x00,0x0B,0x00, +0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0F,0x00,0x00,0x00,0x00, +0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +0x00,0x06,0x00,0x0F,0x00,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x00, +}; + +// even on x86, using short and unsigned char was slower +typedef int fint16; +typedef unsigned fuint16; +typedef unsigned fuint8; + +bool Kss_Cpu::run( cpu_time_t end_time ) +{ + set_end_time( end_time ); + state_t s = this->state_; + this->state = &s; + bool warning = false; + + typedef BOOST::int8_t int8_t; + + union { + regs_t rg; + pairs_t rp; + uint8_t r8_ [8]; // indexed + uint16_t r16_ [4]; + }; + rg = this->r.b; + + cpu_time_t s_time = s.time; + fuint16 pc = r.pc; + fuint16 sp = r.sp; + fuint16 ix = r.ix; // TODO: keep in memory for direct access? + fuint16 iy = r.iy; + int flags = r.b.flags; + + goto loop; +jr_not_taken: + s_time -= 5; + goto loop; +call_not_taken: + s_time -= 7; +jp_not_taken: + pc += 2; +loop: + + check( (unsigned long) pc < 0x10000 ); + check( (unsigned long) sp < 0x10000 ); + check( (unsigned) flags < 0x100 ); + check( (unsigned) ix < 0x10000 ); + check( (unsigned) iy < 0x10000 ); + + uint8_t const* instr = s.read [pc >> page_shift]; +#define GET_ADDR() GET_LE16( instr ) + + fuint8 opcode; + + // TODO: eliminate this special case + #if BLARGG_NONPORTABLE + opcode = instr [pc]; + pc++; + instr += pc; + #else + instr += KSS_CPU_PAGE_OFFSET( pc ); + opcode = *instr++; + pc++; + #endif + + static byte const base_timing [0x100] = { + // 0 1 2 3 4 5 6 7 8 9 A B C D E F + 4,10, 7, 6, 4, 4, 7, 4, 4,11, 7, 6, 4, 4, 7, 4, // 0 + 13,10, 7, 6, 4, 4, 7, 4,12,11, 7, 6, 4, 4, 7, 4, // 1 + 12,10,16, 6, 4, 4, 7, 4,12,11,16, 6, 4, 4, 7, 4, // 2 + 12,10,13, 6,11,11,10, 4,12,11,13, 6, 4, 4, 7, 4, // 3 + 4, 4, 4, 4, 4, 4, 7, 4, 4, 4, 4, 4, 4, 4, 7, 4, // 4 + 4, 4, 4, 4, 4, 4, 7, 4, 4, 4, 4, 4, 4, 4, 7, 4, // 5 + 4, 4, 4, 4, 4, 4, 7, 4, 4, 4, 4, 4, 4, 4, 7, 4, // 6 + 7, 7, 7, 7, 7, 7, 4, 7, 4, 4, 4, 4, 4, 4, 7, 4, // 7 + 4, 4, 4, 4, 4, 4, 7, 4, 4, 4, 4, 4, 4, 4, 7, 4, // 8 + 4, 4, 4, 4, 4, 4, 7, 4, 4, 4, 4, 4, 4, 4, 7, 4, // 9 + 4, 4, 4, 4, 4, 4, 7, 4, 4, 4, 4, 4, 4, 4, 7, 4, // A + 4, 4, 4, 4, 4, 4, 7, 4, 4, 4, 4, 4, 4, 4, 7, 4, // B + 11,10,10,10,17,11, 7,11,11,10,10, 8,17,17, 7,11, // C + 11,10,10,11,17,11, 7,11,11, 4,10,11,17, 8, 7,11, // D + 11,10,10,19,17,11, 7,11,11, 4,10, 4,17, 8, 7,11, // E + 11,10,10, 4,17,11, 7,11,11, 6,10, 4,17, 8, 7,11, // F + }; + + fuint16 data; + data = base_timing [opcode]; + if ( (s_time += data) >= 0 ) + goto possibly_out_of_time; +almost_out_of_time: + + data = READ_PROG( pc ); + + #ifdef Z80_CPU_LOG_H + //log_opcode( opcode, READ_PROG( pc ) ); + z80_log_regs( rg.a, rp.bc, rp.de, rp.hl, sp, ix, iy ); + z80_cpu_log( "new", pc - 1, opcode, READ_PROG( pc ), + READ_PROG( pc + 1 ), READ_PROG( pc + 2 ) ); + #endif + + switch ( opcode ) + { +possibly_out_of_time: + if ( s_time < (int) data ) + goto almost_out_of_time; + s_time -= data; + goto out_of_time; + +// Common + + case 0x00: // NOP + CASE7( 40, 49, 52, 5B, 64, 6D, 7F ): // LD B,B etc. + goto loop; + + case 0x08:{// EX AF,AF' + int temp = r.alt.b.a; + r.alt.b.a = rg.a; + rg.a = temp; + + temp = r.alt.b.flags; + r.alt.b.flags = flags; + flags = temp; + goto loop; + } + + case 0xD3: // OUT (imm),A + pc++; + OUT( data + rg.a * 0x100, rg.a ); + goto loop; + + case 0x2E: // LD L,imm + pc++; + rg.l = data; + goto loop; + + case 0x3E: // LD A,imm + pc++; + rg.a = data; + goto loop; + + case 0x3A:{// LD A,(addr) + fuint16 addr = GET_ADDR(); + pc += 2; + rg.a = READ( addr ); + goto loop; + } + +// Conditional + +#define ZERO (flags & Z40) +#define CARRY (flags & C01) +#define EVEN (flags & P04) +#define MINUS (flags & S80) + +// JR +#define JR( cond ) {\ + int disp = (BOOST::int8_t) data;\ + pc++;\ + if ( !(cond) )\ + goto jr_not_taken;\ + pc += disp;\ + goto loop;\ +} + + case 0x20: JR( !ZERO ) // JR NZ,disp + case 0x28: JR( ZERO ) // JR Z,disp + case 0x30: JR( !CARRY ) // JR NC,disp + case 0x38: JR( CARRY ) // JR C,disp + case 0x18: JR( true ) // JR disp + + case 0x10:{// DJNZ disp + int temp = rg.b - 1; + rg.b = temp; + JR( temp ) + } + +// JP +#define JP( cond ) if ( !(cond) ) goto jp_not_taken; pc = GET_ADDR(); goto loop; + + case 0xC2: JP( !ZERO ) // JP NZ,addr + case 0xCA: JP( ZERO ) // JP Z,addr + case 0xD2: JP( !CARRY ) // JP NC,addr + case 0xDA: JP( CARRY ) // JP C,addr + case 0xE2: JP( !EVEN ) // JP PO,addr + case 0xEA: JP( EVEN ) // JP PE,addr + case 0xF2: JP( !MINUS ) // JP P,addr + case 0xFA: JP( MINUS ) // JP M,addr + + case 0xC3: // JP addr + pc = GET_ADDR(); + goto loop; + + case 0xE9: // JP HL + pc = rp.hl; + goto loop; + +// RET +#define RET( cond ) if ( cond ) goto ret_taken; s_time -= 6; goto loop; + + case 0xC0: RET( !ZERO ) // RET NZ + case 0xC8: RET( ZERO ) // RET Z + case 0xD0: RET( !CARRY ) // RET NC + case 0xD8: RET( CARRY ) // RET C + case 0xE0: RET( !EVEN ) // RET PO + case 0xE8: RET( EVEN ) // RET PE + case 0xF0: RET( !MINUS ) // RET P + case 0xF8: RET( MINUS ) // RET M + + case 0xC9: // RET + ret_taken: + pc = READ_WORD( sp ); + sp = uint16_t (sp + 2); + goto loop; + +// CALL +#define CALL( cond ) if ( cond ) goto call_taken; goto call_not_taken; + + case 0xC4: CALL( !ZERO ) // CALL NZ,addr + case 0xCC: CALL( ZERO ) // CALL Z,addr + case 0xD4: CALL( !CARRY ) // CALL NC,addr + case 0xDC: CALL( CARRY ) // CALL C,addr + case 0xE4: CALL( !EVEN ) // CALL PO,addr + case 0xEC: CALL( EVEN ) // CALL PE,addr + case 0xF4: CALL( !MINUS ) // CALL P,addr + case 0xFC: CALL( MINUS ) // CALL M,addr + + case 0xCD:{// CALL addr + call_taken: + fuint16 addr = pc + 2; + pc = GET_ADDR(); + sp = uint16_t (sp - 2); + WRITE_WORD( sp, addr ); + goto loop; + } + + case 0xFF: // RST + if ( pc > idle_addr ) + goto hit_idle_addr; + CASE7( C7, CF, D7, DF, E7, EF, F7 ): + data = pc; + pc = opcode & 0x38; + goto push_data; + +// PUSH/POP + case 0xF5: // PUSH AF + data = rg.a * 0x100u + flags; + goto push_data; + + case 0xC5: // PUSH BC + case 0xD5: // PUSH DE + case 0xE5: // PUSH HL + data = R16( opcode, 4, 0xC5 ); + push_data: + sp = uint16_t (sp - 2); + WRITE_WORD( sp, data ); + goto loop; + + case 0xF1: // POP AF + flags = READ( sp ); + rg.a = READ( sp + 1 ); + sp = uint16_t (sp + 2); + goto loop; + + case 0xC1: // POP BC + case 0xD1: // POP DE + case 0xE1: // POP HL + R16( opcode, 4, 0xC1 ) = READ_WORD( sp ); + sp = uint16_t (sp + 2); + goto loop; + +// ADC/ADD/SBC/SUB + case 0x96: // SUB (HL) + case 0x86: // ADD (HL) + flags &= ~C01; + case 0x9E: // SBC (HL) + case 0x8E: // ADC (HL) + data = READ( rp.hl ); + goto adc_data; + + case 0xD6: // SUB A,imm + case 0xC6: // ADD imm + flags &= ~C01; + case 0xDE: // SBC A,imm + case 0xCE: // ADC imm + pc++; + goto adc_data; + + CASE7( 90, 91, 92, 93, 94, 95, 97 ): // SUB r + CASE7( 80, 81, 82, 83, 84, 85, 87 ): // ADD r + flags &= ~C01; + CASE7( 98, 99, 9A, 9B, 9C, 9D, 9F ): // SBC r + CASE7( 88, 89, 8A, 8B, 8C, 8D, 8F ): // ADC r + data = R8( opcode & 7, 0 ); + adc_data: { + int result = data + (flags & C01); + data ^= rg.a; + flags = opcode >> 3 & N02; // bit 4 is set in subtract opcodes + if ( flags ) + result = -result; + result += rg.a; + data ^= result; + flags |=(data & H10) | + ((data - -0x80) >> 6 & V04) | + SZ28C( result & 0x1FF ); + rg.a = result; + goto loop; + } + +// CP + case 0xBE: // CP (HL) + data = READ( rp.hl ); + goto cp_data; + + case 0xFE: // CP imm + pc++; + goto cp_data; + + CASE7( B8, B9, BA, BB, BC, BD, BF ): // CP r + data = R8( opcode, 0xB8 ); + cp_data: { + int result = rg.a - data; + flags = N02 | (data & (F20 | F08)) | (result >> 8 & C01); + data ^= rg.a; + flags |=(((result ^ rg.a) & data) >> 5 & V04) | + (((data & H10) ^ result) & (S80 | H10)); + if ( (uint8_t) result ) + goto loop; + flags |= Z40; + goto loop; + } + +// ADD HL,rp + + case 0x39: // ADD HL,SP + data = sp; + goto add_hl_data; + + case 0x09: // ADD HL,BC + case 0x19: // ADD HL,DE + case 0x29: // ADD HL,HL + data = R16( opcode, 4, 0x09 ); + add_hl_data: { + blargg_ulong sum = rp.hl + data; + data ^= rp.hl; + rp.hl = sum; + flags = (flags & (S80 | Z40 | V04)) | + (sum >> 16) | + (sum >> 8 & (F20 | F08)) | + ((data ^ sum) >> 8 & H10); + goto loop; + } + + case 0x27:{// DAA + int a = rg.a; + if ( a > 0x99 ) + flags |= C01; + + int adjust = 0x60 & -(flags & C01); + + if ( flags & H10 || (a & 0x0F) > 9 ) + adjust |= 0x06; + + if ( flags & N02 ) + adjust = -adjust; + a += adjust; + + flags = (flags & (C01 | N02)) | + ((rg.a ^ a) & H10) | + SZ28P( (uint8_t) a ); + rg.a = a; + goto loop; + } + /* + case 0x27:{// DAA + // more optimized, but probably not worth the obscurity + int f = (rg.a + (0xFF - 0x99)) >> 8 | flags; // (a > 0x99 ? C01 : 0) | flags + int adjust = 0x60 & -(f & C01); // f & C01 ? 0x60 : 0 + + if ( (((rg.a + (0x0F - 9)) ^ rg.a) | f) & H10 ) // flags & H10 || (rg.a & 0x0F) > 9 + adjust |= 0x06; + + if ( f & N02 ) + adjust = -adjust; + int a = rg.a + adjust; + + flags = (f & (N02 | C01)) | ((rg.a ^ a) & H10) | SZ28P( (uint8_t) a ); + rg.a = a; + goto loop; + } + */ + +// INC/DEC + case 0x34: // INC (HL) + data = READ( rp.hl ) + 1; + WRITE( rp.hl, data ); + goto inc_set_flags; + + CASE7( 04, 0C, 14, 1C, 24, 2C, 3C ): // INC r + data = ++R8( opcode >> 3, 0 ); + inc_set_flags: + flags = (flags & C01) | + (((data & 0x0F) - 1) & H10) | + SZ28( (uint8_t) data ); + if ( data != 0x80 ) + goto loop; + flags |= V04; + goto loop; + + case 0x35: // DEC (HL) + data = READ( rp.hl ) - 1; + WRITE( rp.hl, data ); + goto dec_set_flags; + + CASE7( 05, 0D, 15, 1D, 25, 2D, 3D ): // DEC r + data = --R8( opcode >> 3, 0 ); + dec_set_flags: + flags = (flags & C01) | N02 | + (((data & 0x0F) + 1) & H10) | + SZ28( (uint8_t) data ); + if ( data != 0x7F ) + goto loop; + flags |= V04; + goto loop; + + case 0x03: // INC BC + case 0x13: // INC DE + case 0x23: // INC HL + R16( opcode, 4, 0x03 )++; + goto loop; + + case 0x33: // INC SP + sp = uint16_t (sp + 1); + goto loop; + + case 0x0B: // DEC BC + case 0x1B: // DEC DE + case 0x2B: // DEC HL + R16( opcode, 4, 0x0B )--; + goto loop; + + case 0x3B: // DEC SP + sp = uint16_t (sp - 1); + goto loop; + +// AND + case 0xA6: // AND (HL) + data = READ( rp.hl ); + goto and_data; + + case 0xE6: // AND imm + pc++; + goto and_data; + + CASE7( A0, A1, A2, A3, A4, A5, A7 ): // AND r + data = R8( opcode, 0xA0 ); + and_data: + rg.a &= data; + flags = SZ28P( rg.a ) | H10; + goto loop; + +// OR + case 0xB6: // OR (HL) + data = READ( rp.hl ); + goto or_data; + + case 0xF6: // OR imm + pc++; + goto or_data; + + CASE7( B0, B1, B2, B3, B4, B5, B7 ): // OR r + data = R8( opcode, 0xB0 ); + or_data: + rg.a |= data; + flags = SZ28P( rg.a ); + goto loop; + +// XOR + case 0xAE: // XOR (HL) + data = READ( rp.hl ); + goto xor_data; + + case 0xEE: // XOR imm + pc++; + goto xor_data; + + CASE7( A8, A9, AA, AB, AC, AD, AF ): // XOR r + data = R8( opcode, 0xA8 ); + xor_data: + rg.a ^= data; + flags = SZ28P( rg.a ); + goto loop; + +// LD + CASE7( 70, 71, 72, 73, 74, 75, 77 ): // LD (HL),r + WRITE( rp.hl, R8( opcode, 0x70 ) ); + goto loop; + + CASE6( 41, 42, 43, 44, 45, 47 ): // LD B,r + CASE6( 48, 4A, 4B, 4C, 4D, 4F ): // LD C,r + CASE6( 50, 51, 53, 54, 55, 57 ): // LD D,r + CASE6( 58, 59, 5A, 5C, 5D, 5F ): // LD E,r + CASE6( 60, 61, 62, 63, 65, 67 ): // LD H,r + CASE6( 68, 69, 6A, 6B, 6C, 6F ): // LD L,r + CASE6( 78, 79, 7A, 7B, 7C, 7D ): // LD A,r + R8( opcode >> 3 & 7, 0 ) = R8( opcode & 7, 0 ); + goto loop; + + CASE5( 06, 0E, 16, 1E, 26 ): // LD r,imm + R8( opcode >> 3, 0 ) = data; + pc++; + goto loop; + + case 0x36: // LD (HL),imm + pc++; + WRITE( rp.hl, data ); + goto loop; + + CASE7( 46, 4E, 56, 5E, 66, 6E, 7E ): // LD r,(HL) + R8( opcode >> 3, 8 ) = READ( rp.hl ); + goto loop; + + case 0x01: // LD rp,imm + case 0x11: + case 0x21: + R16( opcode, 4, 0x01 ) = GET_ADDR(); + pc += 2; + goto loop; + + case 0x31: // LD sp,imm + sp = GET_ADDR(); + pc += 2; + goto loop; + + case 0x2A:{// LD HL,(addr) + fuint16 addr = GET_ADDR(); + pc += 2; + rp.hl = READ_WORD( addr ); + goto loop; + } + + case 0x32:{// LD (addr),A + fuint16 addr = GET_ADDR(); + pc += 2; + WRITE( addr, rg.a ); + goto loop; + } + + case 0x22:{// LD (addr),HL + fuint16 addr = GET_ADDR(); + pc += 2; + WRITE_WORD( addr, rp.hl ); + goto loop; + } + + case 0x02: // LD (BC),A + case 0x12: // LD (DE),A + WRITE( R16( opcode, 4, 0x02 ), rg.a ); + goto loop; + + case 0x0A: // LD A,(BC) + case 0x1A: // LD A,(DE) + rg.a = READ( R16( opcode, 4, 0x0A ) ); + goto loop; + + case 0xF9: // LD SP,HL + sp = rp.hl; + goto loop; + +// Rotate + + case 0x07:{// RLCA + fuint16 temp = rg.a; + temp = (temp << 1) | (temp >> 7); + flags = (flags & (S80 | Z40 | P04)) | + (temp & (F20 | F08 | C01)); + rg.a = temp; + goto loop; + } + + case 0x0F:{// RRCA + fuint16 temp = rg.a; + flags = (flags & (S80 | Z40 | P04)) | + (temp & C01); + temp = (temp << 7) | (temp >> 1); + flags |= temp & (F20 | F08); + rg.a = temp; + goto loop; + } + + case 0x17:{// RLA + blargg_ulong temp = (rg.a << 1) | (flags & C01); + flags = (flags & (S80 | Z40 | P04)) | + (temp & (F20 | F08)) | + (temp >> 8); + rg.a = temp; + goto loop; + } + + case 0x1F:{// RRA + fuint16 temp = (flags << 7) | (rg.a >> 1); + flags = (flags & (S80 | Z40 | P04)) | + (temp & (F20 | F08)) | + (rg.a & C01); + rg.a = temp; + goto loop; + } + +// Misc + case 0x2F:{// CPL + fuint16 temp = ~rg.a; + flags = (flags & (S80 | Z40 | P04 | C01)) | + (temp & (F20 | F08)) | + (H10 | N02); + rg.a = temp; + goto loop; + } + + case 0x3F:{// CCF + flags = ((flags & (S80 | Z40 | P04 | C01)) ^ C01) | + (flags << 4 & H10) | + (rg.a & (F20 | F08)); + goto loop; + } + + case 0x37: // SCF + flags = (flags & (S80 | Z40 | P04)) | C01 | + (rg.a & (F20 | F08)); + goto loop; + + case 0xDB: // IN A,(imm) + pc++; + rg.a = IN( data + rg.a * 0x100 ); + goto loop; + + case 0xE3:{// EX (SP),HL + fuint16 temp = READ_WORD( sp ); + WRITE_WORD( sp, rp.hl ); + rp.hl = temp; + goto loop; + } + + case 0xEB:{// EX DE,HL + fuint16 temp = rp.hl; + rp.hl = rp.de; + rp.de = temp; + goto loop; + } + + case 0xD9:{// EXX DE,HL + fuint16 temp = r.alt.w.bc; + r.alt.w.bc = rp.bc; + rp.bc = temp; + + temp = r.alt.w.de; + r.alt.w.de = rp.de; + rp.de = temp; + + temp = r.alt.w.hl; + r.alt.w.hl = rp.hl; + rp.hl = temp; + goto loop; + } + + case 0xF3: // DI + r.iff1 = 0; + r.iff2 = 0; + goto loop; + + case 0xFB: // EI + r.iff1 = 1; + r.iff2 = 1; + // TODO: delayed effect + goto loop; + + case 0x76: // HALT + goto halt; + +//////////////////////////////////////// CB prefix + { + case 0xCB: + unsigned data2; + data2 = instr [1]; + pc++; + switch ( data ) + { + + // Rotate left + + #define RLC( read, write ) {\ + fuint8 result = read;\ + result = uint8_t (result << 1) | (result >> 7);\ + flags = SZ28P( result ) | (result & C01);\ + write;\ + goto loop;\ + } + + case 0x06: // RLC (HL) + s_time += 7; + data = rp.hl; + rlc_data_addr: + RLC( READ( data ), WRITE( data, result ) ) + + CASE7( 00, 01, 02, 03, 04, 05, 07 ):{// RLC r + uint8_t& reg = R8( data, 0 ); + RLC( reg, reg = result ) + } + + #define RL( read, write ) {\ + fuint16 result = (read << 1) | (flags & C01);\ + flags = SZ28PC( result );\ + write;\ + goto loop;\ + } + + case 0x16: // RL (HL) + s_time += 7; + data = rp.hl; + rl_data_addr: + RL( READ( data ), WRITE( data, result ) ) + + CASE7( 10, 11, 12, 13, 14, 15, 17 ):{// RL r + uint8_t& reg = R8( data, 0x10 ); + RL( reg, reg = result ) + } + + #define SLA( read, add, write ) {\ + fuint16 result = (read << 1) | add;\ + flags = SZ28PC( result );\ + write;\ + goto loop;\ + } + + case 0x26: // SLA (HL) + s_time += 7; + data = rp.hl; + sla_data_addr: + SLA( READ( data ), 0, WRITE( data, result ) ) + + CASE7( 20, 21, 22, 23, 24, 25, 27 ):{// SLA r + uint8_t& reg = R8( data, 0x20 ); + SLA( reg, 0, reg = result ) + } + + case 0x36: // SLL (HL) + s_time += 7; + data = rp.hl; + sll_data_addr: + SLA( READ( data ), 1, WRITE( data, result ) ) + + CASE7( 30, 31, 32, 33, 34, 35, 37 ):{// SLL r + uint8_t& reg = R8( data, 0x30 ); + SLA( reg, 1, reg = result ) + } + + // Rotate right + + #define RRC( read, write ) {\ + fuint8 result = read;\ + flags = result & C01;\ + result = uint8_t (result << 7) | (result >> 1);\ + flags |= SZ28P( result );\ + write;\ + goto loop;\ + } + + case 0x0E: // RRC (HL) + s_time += 7; + data = rp.hl; + rrc_data_addr: + RRC( READ( data ), WRITE( data, result ) ) + + CASE7( 08, 09, 0A, 0B, 0C, 0D, 0F ):{// RRC r + uint8_t& reg = R8( data, 0x08 ); + RRC( reg, reg = result ) + } + + #define RR( read, write ) {\ + fuint8 result = read;\ + fuint8 temp = result & C01;\ + result = uint8_t (flags << 7) | (result >> 1);\ + flags = SZ28P( result ) | temp;\ + write;\ + goto loop;\ + } + + case 0x1E: // RR (HL) + s_time += 7; + data = rp.hl; + rr_data_addr: + RR( READ( data ), WRITE( data, result ) ) + + CASE7( 18, 19, 1A, 1B, 1C, 1D, 1F ):{// RR r + uint8_t& reg = R8( data, 0x18 ); + RR( reg, reg = result ) + } + + #define SRA( read, write ) {\ + fuint8 result = read;\ + flags = result & C01;\ + result = (result & 0x80) | (result >> 1);\ + flags |= SZ28P( result );\ + write;\ + goto loop;\ + } + + case 0x2E: // SRA (HL) + data = rp.hl; + s_time += 7; + sra_data_addr: + SRA( READ( data ), WRITE( data, result ) ) + + CASE7( 28, 29, 2A, 2B, 2C, 2D, 2F ):{// SRA r + uint8_t& reg = R8( data, 0x28 ); + SRA( reg, reg = result ) + } + + #define SRL( read, write ) {\ + fuint8 result = read;\ + flags = result & C01;\ + result >>= 1;\ + flags |= SZ28P( result );\ + write;\ + goto loop;\ + } + + case 0x3E: // SRL (HL) + s_time += 7; + data = rp.hl; + srl_data_addr: + SRL( READ( data ), WRITE( data, result ) ) + + CASE7( 38, 39, 3A, 3B, 3C, 3D, 3F ):{// SRL r + uint8_t& reg = R8( data, 0x38 ); + SRL( reg, reg = result ) + } + + // BIT + { + unsigned temp; + CASE8( 46, 4E, 56, 5E, 66, 6E, 76, 7E ): // BIT b,(HL) + s_time += 4; + temp = READ( rp.hl ); + flags &= C01; + goto bit_temp; + CASE7( 40, 41, 42, 43, 44, 45, 47 ): // BIT 0,r + CASE7( 48, 49, 4A, 4B, 4C, 4D, 4F ): // BIT 1,r + CASE7( 50, 51, 52, 53, 54, 55, 57 ): // BIT 2,r + CASE7( 58, 59, 5A, 5B, 5C, 5D, 5F ): // BIT 3,r + CASE7( 60, 61, 62, 63, 64, 65, 67 ): // BIT 4,r + CASE7( 68, 69, 6A, 6B, 6C, 6D, 6F ): // BIT 5,r + CASE7( 70, 71, 72, 73, 74, 75, 77 ): // BIT 6,r + CASE7( 78, 79, 7A, 7B, 7C, 7D, 7F ): // BIT 7,r + temp = R8( data & 7, 0 ); + flags = (flags & C01) | (temp & (F20 | F08)); + bit_temp: + int masked = temp & 1 << (data >> 3 & 7); + flags |=(masked & S80) | H10 | + ((masked - 1) >> 8 & (Z40 | P04)); + goto loop; + } + + // SET/RES + CASE8( 86, 8E, 96, 9E, A6, AE, B6, BE ): // RES b,(HL) + CASE8( C6, CE, D6, DE, E6, EE, F6, FE ):{// SET b,(HL) + s_time += 7; + int temp = READ( rp.hl ); + int bit = 1 << (data >> 3 & 7); + temp |= bit; // SET + if ( !(data & 0x40) ) + temp ^= bit; // RES + WRITE( rp.hl, temp ); + goto loop; + } + + CASE7( C0, C1, C2, C3, C4, C5, C7 ): // SET 0,r + CASE7( C8, C9, CA, CB, CC, CD, CF ): // SET 1,r + CASE7( D0, D1, D2, D3, D4, D5, D7 ): // SET 2,r + CASE7( D8, D9, DA, DB, DC, DD, DF ): // SET 3,r + CASE7( E0, E1, E2, E3, E4, E5, E7 ): // SET 4,r + CASE7( E8, E9, EA, EB, EC, ED, EF ): // SET 5,r + CASE7( F0, F1, F2, F3, F4, F5, F7 ): // SET 6,r + CASE7( F8, F9, FA, FB, FC, FD, FF ): // SET 7,r + R8( data & 7, 0 ) |= 1 << (data >> 3 & 7); + goto loop; + + CASE7( 80, 81, 82, 83, 84, 85, 87 ): // RES 0,r + CASE7( 88, 89, 8A, 8B, 8C, 8D, 8F ): // RES 1,r + CASE7( 90, 91, 92, 93, 94, 95, 97 ): // RES 2,r + CASE7( 98, 99, 9A, 9B, 9C, 9D, 9F ): // RES 3,r + CASE7( A0, A1, A2, A3, A4, A5, A7 ): // RES 4,r + CASE7( A8, A9, AA, AB, AC, AD, AF ): // RES 5,r + CASE7( B0, B1, B2, B3, B4, B5, B7 ): // RES 6,r + CASE7( B8, B9, BA, BB, BC, BD, BF ): // RES 7,r + R8( data & 7, 0 ) &= ~(1 << (data >> 3 & 7)); + goto loop; + } + assert( false ); + } + +#undef GET_ADDR +#define GET_ADDR() GET_LE16( instr + 1 ) + +//////////////////////////////////////// ED prefix + { + case 0xED: + pc++; + s_time += ed_dd_timing [data] >> 4; + switch ( data ) + { + { + blargg_ulong temp; + case 0x72: // SBC HL,SP + case 0x7A: // ADC HL,SP + temp = sp; + if ( 0 ) + case 0x42: // SBC HL,BC + case 0x52: // SBC HL,DE + case 0x62: // SBC HL,HL + case 0x4A: // ADC HL,BC + case 0x5A: // ADC HL,DE + case 0x6A: // ADC HL,HL + temp = R16( data >> 3 & 6, 1, 0 ); + blargg_ulong sum = temp + (flags & C01); + flags = ~data >> 2 & N02; + if ( flags ) + sum = -sum; + sum += rp.hl; + temp ^= rp.hl; + temp ^= sum; + flags |=(sum >> 16 & C01) | + (temp >> 8 & H10) | + (sum >> 8 & (S80 | F20 | F08)) | + ((temp - -0x8000) >> 14 & V04); + rp.hl = sum; + if ( (uint16_t) sum ) + goto loop; + flags |= Z40; + goto loop; + } + + CASE8( 40, 48, 50, 58, 60, 68, 70, 78 ):{// IN r,(C) + int temp = IN( rp.bc ); + R8( data >> 3, 8 ) = temp; + flags = (flags & C01) | SZ28P( temp ); + goto loop; + } + + case 0x71: // OUT (C),0 + rg.flags = 0; + CASE7( 41, 49, 51, 59, 61, 69, 79 ): // OUT (C),r + OUT( rp.bc, R8( data >> 3, 8 ) ); + goto loop; + + { + unsigned temp; + case 0x73: // LD (ADDR),SP + temp = sp; + if ( 0 ) + case 0x43: // LD (ADDR),BC + case 0x53: // LD (ADDR),DE + temp = R16( data, 4, 0x43 ); + fuint16 addr = GET_ADDR(); + pc += 2; + WRITE_WORD( addr, temp ); + goto loop; + } + + case 0x4B: // LD BC,(ADDR) + case 0x5B:{// LD DE,(ADDR) + fuint16 addr = GET_ADDR(); + pc += 2; + R16( data, 4, 0x4B ) = READ_WORD( addr ); + goto loop; + } + + case 0x7B:{// LD SP,(ADDR) + fuint16 addr = GET_ADDR(); + pc += 2; + sp = READ_WORD( addr ); + goto loop; + } + + case 0x67:{// RRD + fuint8 temp = READ( rp.hl ); + WRITE( rp.hl, (rg.a << 4) | (temp >> 4) ); + temp = (rg.a & 0xF0) | (temp & 0x0F); + flags = (flags & C01) | SZ28P( temp ); + rg.a = temp; + goto loop; + } + + case 0x6F:{// RLD + fuint8 temp = READ( rp.hl ); + WRITE( rp.hl, (temp << 4) | (rg.a & 0x0F) ); + temp = (rg.a & 0xF0) | (temp >> 4); + flags = (flags & C01) | SZ28P( temp ); + rg.a = temp; + goto loop; + } + + CASE8( 44, 4C, 54, 5C, 64, 6C, 74, 7C ): // NEG + opcode = 0x10; // flag to do SBC instead of ADC + flags &= ~C01; + data = rg.a; + rg.a = 0; + goto adc_data; + + { + int inc; + case 0xA9: // CPD + case 0xB9: // CPDR + inc = -1; + if ( 0 ) + case 0xA1: // CPI + case 0xB1: // CPIR + inc = +1; + fuint16 addr = rp.hl; + rp.hl = addr + inc; + int temp = READ( addr ); + + int result = rg.a - temp; + flags = (flags & C01) | N02 | + ((((temp ^ rg.a) & H10) ^ result) & (S80 | H10)); + + if ( !(uint8_t) result ) flags |= Z40; + result -= (flags & H10) >> 4; + flags |= result & F08; + flags |= result << 4 & F20; + if ( !--rp.bc ) + goto loop; + + flags |= V04; + if ( flags & Z40 || data < 0xB0 ) + goto loop; + + pc -= 2; + s_time += 5; + goto loop; + } + + { + int inc; + case 0xA8: // LDD + case 0xB8: // LDDR + inc = -1; + if ( 0 ) + case 0xA0: // LDI + case 0xB0: // LDIR + inc = +1; + fuint16 addr = rp.hl; + rp.hl = addr + inc; + int temp = READ( addr ); + + addr = rp.de; + rp.de = addr + inc; + WRITE( addr, temp ); + + temp += rg.a; + flags = (flags & (S80 | Z40 | C01)) | + (temp & F08) | (temp << 4 & F20); + if ( !--rp.bc ) + goto loop; + + flags |= V04; + if ( data < 0xB0 ) + goto loop; + + pc -= 2; + s_time += 5; + goto loop; + } + + { + int inc; + case 0xAB: // OUTD + case 0xBB: // OTDR + inc = -1; + if ( 0 ) + case 0xA3: // OUTI + case 0xB3: // OTIR + inc = +1; + fuint16 addr = rp.hl; + rp.hl = addr + inc; + int temp = READ( addr ); + + int b = --rg.b; + flags = (temp >> 6 & N02) | SZ28( b ); + if ( b && data >= 0xB0 ) + { + pc -= 2; + s_time += 5; + } + + OUT( rp.bc, temp ); + goto loop; + } + + { + int inc; + case 0xAA: // IND + case 0xBA: // INDR + inc = -1; + if ( 0 ) + case 0xA2: // INI + case 0xB2: // INIR + inc = +1; + + fuint16 addr = rp.hl; + rp.hl = addr + inc; + + int temp = IN( rp.bc ); + + int b = --rg.b; + flags = (temp >> 6 & N02) | SZ28( b ); + if ( b && data >= 0xB0 ) + { + pc -= 2; + s_time += 5; + } + + WRITE( addr, temp ); + goto loop; + } + + case 0x47: // LD I,A + r.i = rg.a; + goto loop; + + case 0x4F: // LD R,A + SET_R( rg.a ); + dprintf( "LD R,A not supported\n" ); + warning = true; + goto loop; + + case 0x57: // LD A,I + rg.a = r.i; + goto ld_ai_common; + + case 0x5F: // LD A,R + rg.a = GET_R(); + dprintf( "LD A,R not supported\n" ); + warning = true; + ld_ai_common: + flags = (flags & C01) | SZ28( rg.a ) | (r.iff2 << 2 & V04); + goto loop; + + CASE8( 45, 4D, 55, 5D, 65, 6D, 75, 7D ): // RETI/RETN + r.iff1 = r.iff2; + goto ret_taken; + + case 0x46: case 0x4E: case 0x66: case 0x6E: // IM 0 + r.im = 0; + goto loop; + + case 0x56: case 0x76: // IM 1 + r.im = 1; + goto loop; + + case 0x5E: case 0x7E: // IM 2 + r.im = 2; + goto loop; + + default: + dprintf( "Opcode $ED $%02X not supported\n", data ); + warning = true; + goto loop; + } + assert( false ); + } + +//////////////////////////////////////// DD/FD prefix + { + fuint16 ixy; + case 0xDD: + ixy = ix; + goto ix_prefix; + case 0xFD: + ixy = iy; + ix_prefix: + pc++; + unsigned data2 = READ_PROG( pc ); + s_time += ed_dd_timing [data] & 0x0F; + switch ( data ) + { + #define SET_IXY( in ) if ( opcode == 0xDD ) ix = in; else iy = in; + + // ADD/ADC/SUB/SBC + + case 0x96: // SUB (IXY+disp) + case 0x86: // ADD (IXY+disp) + flags &= ~C01; + case 0x9E: // SBC (IXY+disp) + case 0x8E: // ADC (IXY+disp) + pc++; + opcode = data; + data = READ( ixy + (int8_t) data2 ); + goto adc_data; + + case 0x94: // SUB HXY + case 0x84: // ADD HXY + flags &= ~C01; + case 0x9C: // SBC HXY + case 0x8C: // ADC HXY + opcode = data; + data = ixy >> 8; + goto adc_data; + + case 0x95: // SUB LXY + case 0x85: // ADD LXY + flags &= ~C01; + case 0x9D: // SBC LXY + case 0x8D: // ADC LXY + opcode = data; + data = (uint8_t) ixy; + goto adc_data; + + { + unsigned data2; + case 0x39: // ADD IXY,SP + data2 = sp; + goto add_ixy_data; + + case 0x29: // ADD IXY,HL + data2 = ixy; + goto add_ixy_data; + + case 0x09: // ADD IXY,BC + case 0x19: // ADD IXY,DE + data2 = R16( data, 4, 0x09 ); + add_ixy_data: { + blargg_ulong sum = ixy + data2; + data2 ^= ixy; + ixy = (uint16_t) sum; + flags = (flags & (S80 | Z40 | V04)) | + (sum >> 16) | + (sum >> 8 & (F20 | F08)) | + ((data2 ^ sum) >> 8 & H10); + goto set_ixy; + } + } + + // AND + case 0xA6: // AND (IXY+disp) + pc++; + data = READ( ixy + (int8_t) data2 ); + goto and_data; + + case 0xA4: // AND HXY + data = ixy >> 8; + goto and_data; + + case 0xA5: // AND LXY + data = (uint8_t) ixy; + goto and_data; + + // OR + case 0xB6: // OR (IXY+disp) + pc++; + data = READ( ixy + (int8_t) data2 ); + goto or_data; + + case 0xB4: // OR HXY + data = ixy >> 8; + goto or_data; + + case 0xB5: // OR LXY + data = (uint8_t) ixy; + goto or_data; + + // XOR + case 0xAE: // XOR (IXY+disp) + pc++; + data = READ( ixy + (int8_t) data2 ); + goto xor_data; + + case 0xAC: // XOR HXY + data = ixy >> 8; + goto xor_data; + + case 0xAD: // XOR LXY + data = (uint8_t) ixy; + goto xor_data; + + // CP + case 0xBE: // CP (IXY+disp) + pc++; + data = READ( ixy + (int8_t) data2 ); + goto cp_data; + + case 0xBC: // CP HXY + data = ixy >> 8; + goto cp_data; + + case 0xBD: // CP LXY + data = (uint8_t) ixy; + goto cp_data; + + // LD + CASE7( 70, 71, 72, 73, 74, 75, 77 ): // LD (IXY+disp),r + data = R8( data, 0x70 ); + if ( 0 ) + case 0x36: // LD (IXY+disp),imm + pc++, data = READ_PROG( pc ); + pc++; + WRITE( ixy + (int8_t) data2, data ); + goto loop; + + CASE5( 44, 4C, 54, 5C, 7C ): // LD r,HXY + R8( data >> 3, 8 ) = ixy >> 8; + goto loop; + + case 0x64: // LD HXY,HXY + case 0x6D: // LD LXY,LXY + goto loop; + + CASE5( 45, 4D, 55, 5D, 7D ): // LD r,LXY + R8( data >> 3, 8 ) = ixy; + goto loop; + + CASE7( 46, 4E, 56, 5E, 66, 6E, 7E ): // LD r,(IXY+disp) + pc++; + R8( data >> 3, 8 ) = READ( ixy + (int8_t) data2 ); + goto loop; + + case 0x26: // LD HXY,imm + pc++; + goto ld_hxy_data; + + case 0x65: // LD HXY,LXY + data2 = (uint8_t) ixy; + goto ld_hxy_data; + + CASE5( 60, 61, 62, 63, 67 ): // LD HXY,r + data2 = R8( data, 0x60 ); + ld_hxy_data: + ixy = (uint8_t) ixy | (data2 << 8); + goto set_ixy; + + case 0x2E: // LD LXY,imm + pc++; + goto ld_lxy_data; + + case 0x6C: // LD LXY,HXY + data2 = ixy >> 8; + goto ld_lxy_data; + + CASE5( 68, 69, 6A, 6B, 6F ): // LD LXY,r + data2 = R8( data, 0x68 ); + ld_lxy_data: + ixy = (ixy & 0xFF00) | data2; + set_ixy: + if ( opcode == 0xDD ) + { + ix = ixy; + goto loop; + } + iy = ixy; + goto loop; + + case 0xF9: // LD SP,IXY + sp = ixy; + goto loop; + + case 0x22:{// LD (ADDR),IXY + fuint16 addr = GET_ADDR(); + pc += 2; + WRITE_WORD( addr, ixy ); + goto loop; + } + + case 0x21: // LD IXY,imm + ixy = GET_ADDR(); + pc += 2; + goto set_ixy; + + case 0x2A:{// LD IXY,(addr) + fuint16 addr = GET_ADDR(); + ixy = READ_WORD( addr ); + pc += 2; + goto set_ixy; + } + + // DD/FD CB prefix + case 0xCB: { + data = ixy + (int8_t) data2; + pc++; + data2 = READ_PROG( pc ); + pc++; + switch ( data2 ) + { + case 0x06: goto rlc_data_addr; // RLC (IXY) + case 0x16: goto rl_data_addr; // RL (IXY) + case 0x26: goto sla_data_addr; // SLA (IXY) + case 0x36: goto sll_data_addr; // SLL (IXY) + case 0x0E: goto rrc_data_addr; // RRC (IXY) + case 0x1E: goto rr_data_addr; // RR (IXY) + case 0x2E: goto sra_data_addr; // SRA (IXY) + case 0x3E: goto srl_data_addr; // SRL (IXY) + + CASE8( 46, 4E, 56, 5E, 66, 6E, 76, 7E ):{// BIT b,(IXY+disp) + fuint8 temp = READ( data ); + int masked = temp & 1 << (data2 >> 3 & 7); + flags = (flags & C01) | H10 | + (masked & S80) | + ((masked - 1) >> 8 & (Z40 | P04)); + goto loop; + } + + CASE8( 86, 8E, 96, 9E, A6, AE, B6, BE ): // RES b,(IXY+disp) + CASE8( C6, CE, D6, DE, E6, EE, F6, FE ):{// SET b,(IXY+disp) + int temp = READ( data ); + int bit = 1 << (data2 >> 3 & 7); + temp |= bit; // SET + if ( !(data2 & 0x40) ) + temp ^= bit; // RES + WRITE( data, temp ); + goto loop; + } + + default: + dprintf( "Opcode $%02X $CB $%02X not supported\n", opcode, data2 ); + warning = true; + goto loop; + } + assert( false ); + } + + // INC/DEC + case 0x23: // INC IXY + ixy = uint16_t (ixy + 1); + goto set_ixy; + + case 0x2B: // DEC IXY + ixy = uint16_t (ixy - 1); + goto set_ixy; + + case 0x34: // INC (IXY+disp) + ixy += (int8_t) data2; + pc++; + data = READ( ixy ) + 1; + WRITE( ixy, data ); + goto inc_set_flags; + + case 0x35: // DEC (IXY+disp) + ixy += (int8_t) data2; + pc++; + data = READ( ixy ) - 1; + WRITE( ixy, data ); + goto dec_set_flags; + + case 0x24: // INC HXY + ixy = uint16_t (ixy + 0x100); + data = ixy >> 8; + goto inc_xy_common; + + case 0x2C: // INC LXY + data = uint8_t (ixy + 1); + ixy = (ixy & 0xFF00) | data; + inc_xy_common: + if ( opcode == 0xDD ) + { + ix = ixy; + goto inc_set_flags; + } + iy = ixy; + goto inc_set_flags; + + case 0x25: // DEC HXY + ixy = uint16_t (ixy - 0x100); + data = ixy >> 8; + goto dec_xy_common; + + case 0x2D: // DEC LXY + data = uint8_t (ixy - 1); + ixy = (ixy & 0xFF00) | data; + dec_xy_common: + if ( opcode == 0xDD ) + { + ix = ixy; + goto dec_set_flags; + } + iy = ixy; + goto dec_set_flags; + + // PUSH/POP + case 0xE5: // PUSH IXY + data = ixy; + goto push_data; + + case 0xE1:{// POP IXY + ixy = READ_WORD( sp ); + sp = uint16_t (sp + 2); + goto set_ixy; + } + + // Misc + + case 0xE9: // JP (IXY) + pc = ixy; + goto loop; + + case 0xE3:{// EX (SP),IXY + fuint16 temp = READ_WORD( sp ); + WRITE_WORD( sp, ixy ); + ixy = temp; + goto set_ixy; + } + + default: + dprintf( "Unnecessary DD/FD prefix encountered\n" ); + warning = true; + pc--; + goto loop; + } + assert( false ); + } + + } + dprintf( "Unhandled main opcode: $%02X\n", opcode ); + assert( false ); + +hit_idle_addr: + s_time -= 11; + goto out_of_time; +halt: + s_time &= 3; // increment by multiple of 4 +out_of_time: + pc--; + + s.time = s_time; + rg.flags = flags; + r.ix = ix; + r.iy = iy; + r.sp = sp; + r.pc = pc; + this->r.b = rg; + this->state_ = s; + this->state = &this->state_; + + return warning; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/console/Kss_Cpu.h Thu Nov 30 19:54:33 2006 -0800 @@ -0,0 +1,124 @@ +// Z80 CPU emulator + +// Game_Music_Emu 0.5.1 +#ifndef KSS_CPU_H +#define KSS_CPU_H + +#include "blargg_endian.h" + +typedef blargg_long cpu_time_t; + +// must be defined by caller +void kss_cpu_out( class Kss_Cpu*, cpu_time_t, unsigned addr, int data ); +int kss_cpu_in( class Kss_Cpu*, cpu_time_t, unsigned addr ); +void kss_cpu_write( class Kss_Cpu*, unsigned addr, int data ); + +class Kss_Cpu { +public: + typedef BOOST::uint8_t uint8_t; + + // Clear registers and map all pages to unmapped + void reset( void* unmapped_write, void const* unmapped_read ); + + // Map memory. Start and size must be multiple of page_size. + enum { page_size = 0x2000 }; + void map_mem( unsigned addr, blargg_ulong size, void* write, void const* read ); + + // Map address to page + uint8_t* write( unsigned addr ); + uint8_t const* read( unsigned addr ); + + // Run until specified time is reached. Returns true if suspicious/unsupported + // instruction was encountered at any point during run. + bool run( cpu_time_t end_time ); + + // Time of beginning of next instruction + cpu_time_t time() const { return state->time + state->base; } + + // Alter current time. Not supported during run() call. + void set_time( cpu_time_t t ) { state->time = t - state->base; } + void adjust_time( int delta ) { state->time += delta; } + + typedef BOOST::uint16_t uint16_t; + + #if BLARGG_BIG_ENDIAN + struct regs_t { uint8_t b, c, d, e, h, l, flags, a; }; + #else + struct regs_t { uint8_t c, b, e, d, l, h, a, flags; }; + #endif + BOOST_STATIC_ASSERT( sizeof (regs_t) == 8 ); + + struct pairs_t { uint16_t bc, de, hl, fa; }; + + // Registers are not updated until run() returns + struct registers_t { + uint16_t pc; + uint16_t sp; + uint16_t ix; + uint16_t iy; + union { + regs_t b; // b.b, b.c, b.d, b.e, b.h, b.l, b.flags, b.a + pairs_t w; // w.bc, w.de, w.hl. w.fa + }; + union { + regs_t b; + pairs_t w; + } alt; + uint8_t iff1; + uint8_t iff2; + uint8_t r; + uint8_t i; + uint8_t im; + }; + //registers_t r; (below for efficiency) + + enum { idle_addr = 0xFFFF }; + + // can read this far past end of a page + enum { cpu_padding = 0x100 }; + +public: + Kss_Cpu(); + enum { page_shift = 13 }; + enum { page_count = 0x10000 >> page_shift }; +private: + uint8_t szpc [0x200]; + cpu_time_t end_time_; + struct state_t { + uint8_t const* read [page_count + 1]; + uint8_t * write [page_count + 1]; + cpu_time_t base; + cpu_time_t time; + }; + state_t* state; // points to state_ or a local copy within run() + state_t state_; + void set_end_time( cpu_time_t t ); + void set_page( int i, void* write, void const* read ); +public: + registers_t r; +}; + +#if BLARGG_NONPORTABLE + #define KSS_CPU_PAGE_OFFSET( addr ) (addr) +#else + #define KSS_CPU_PAGE_OFFSET( addr ) ((addr) & (page_size - 1)) +#endif + +inline BOOST::uint8_t* Kss_Cpu::write( unsigned addr ) +{ + return state->write [addr >> page_shift] + KSS_CPU_PAGE_OFFSET( addr ); +} + +inline BOOST::uint8_t const* Kss_Cpu::read( unsigned addr ) +{ + return state->read [addr >> page_shift] + KSS_CPU_PAGE_OFFSET( addr ); +} + +inline void Kss_Cpu::set_end_time( cpu_time_t t ) +{ + cpu_time_t delta = state->base - t; + state->base = t; + state->time += delta; +} + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/console/Kss_Emu.cxx Thu Nov 30 19:54:33 2006 -0800 @@ -0,0 +1,403 @@ +// Game_Music_Emu 0.5.1. http://www.slack.net/~ant/ + +#include "Kss_Emu.h" + +#include "blargg_endian.h" +#include <string.h> + +/* Copyright (C) 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" + +long const clock_rate = 3579545; +int const osc_count = Ay_Apu::osc_count + Scc_Apu::osc_count; + +Kss_Emu::Kss_Emu() +{ + sn = 0; + set_type( gme_kss_type ); + set_silence_lookahead( 6 ); + static const char* const names [osc_count] = { + "Square 1", "Square 2", "Square 3", + "Wave 1", "Wave 2", "Wave 3", "Wave 4", "Wave 5" + }; + set_voice_names( names ); + + static int const types [osc_count] = { + wave_type | 0, wave_type | 1, wave_type | 2, + wave_type | 3, wave_type | 4, wave_type | 5, wave_type | 6, wave_type | 7 + }; + set_voice_types( types ); +} + +Kss_Emu::~Kss_Emu() { unload(); } + +void Kss_Emu::unload() +{ + delete sn; + sn = 0; + Classic_Emu::unload(); +} + +// Track info + +static void copy_kss_fields( Kss_Emu::header_t const& h, track_info_t* out ) +{ + const char* system = "MSX"; + if ( h.device_flags & 0x02 ) + system = "Sega Master System"; + Gme_File::copy_field_( out->system, system ); +} + +blargg_err_t Kss_Emu::track_info_( track_info_t* out, int ) const +{ + copy_kss_fields( header_, out ); + return 0; +} + +static blargg_err_t check_kss_header( void const* header ) +{ + if ( memcmp( header, "KSCC", 4 ) && memcmp( header, "KSSX", 4 ) ) + return gme_wrong_file_type; + return 0; +} + +struct Kss_File : Gme_Info_ +{ + Kss_Emu::header_t header_; + + Kss_File() { set_type( gme_kss_type ); } + + blargg_err_t load_( Data_Reader& in ) + { + long file_size = in.remain(); + if ( file_size < (long) sizeof (Kss_Emu::composite_header_t) ) + return gme_wrong_file_type; + RETURN_ERR( in.read( &header_, sizeof header_ ) ); + return check_kss_header( &header_ ); + } + + blargg_err_t track_info_( track_info_t* out, int ) const + { + copy_kss_fields( header_, out ); + return 0; + } +}; + +static Music_Emu* new_kss_emu () { return BLARGG_NEW Kss_Emu ; } +static Music_Emu* new_kss_file() { return BLARGG_NEW Kss_File; } + +gme_type_t_ const gme_kss_type [1] = { "MSX", 256, &new_kss_emu, &new_kss_file, "KSS", 0x03 }; + +// Setup + +void Kss_Emu::update_gain() +{ + double g = gain() * 1.4; + if ( scc_accessed ) + g *= 1.5; + ay.volume( g ); + scc.volume( g ); + if ( sn ) + sn->volume( g ); +} + +blargg_err_t Kss_Emu::load_( Data_Reader& in ) +{ + memset( &header_, 0, sizeof header_ ); + RETURN_ERR( rom.load( in, sizeof (header_t), STATIC_CAST(header_t*,&header_), 0 ) ); + + RETURN_ERR( check_kss_header( header_.tag ) ); + + if ( header_.tag [3] == 'C' ) + { + if ( header_.extra_header ) + { + header_.extra_header = 0; + set_warning( "Unknown data in header" ); + } + if ( header_.device_flags & ~0x0F ) + { + header_.device_flags &= 0x0F; + set_warning( "Unknown data in header" ); + } + } + else + { + ext_header_t& ext = header_; + memcpy( &ext, rom.begin(), min( (int) sizeof ext, (int) header_.extra_header ) ); + if ( header_.extra_header > 0x10 ) + set_warning( "Unknown data in header" ); + } + + if ( header_.device_flags & 0x09 ) + set_warning( "FM sound not supported" ); + + scc_enabled = 0xC000; + if ( header_.device_flags & 0x04 ) + scc_enabled = 0; + + if ( header_.device_flags & 0x02 && !sn ) + CHECK_ALLOC( sn = BLARGG_NEW( Sms_Apu ) ); + + set_voice_count( osc_count ); + + return setup_buffer( ::clock_rate ); +} + +void Kss_Emu::update_eq( blip_eq_t const& eq ) +{ + ay.treble_eq( eq ); + scc.treble_eq( eq ); + if ( sn ) + sn->treble_eq( eq ); +} + +void Kss_Emu::set_voice( int i, Blip_Buffer* center, Blip_Buffer* left, Blip_Buffer* right ) +{ + int i2 = i - ay.osc_count; + if ( i2 >= 0 ) + scc.osc_output( i2, center ); + else + ay.osc_output( i, center ); + if ( sn && i < sn->osc_count ) + sn->osc_output( i, center, left, right ); +} + +// Emulation + +void Kss_Emu::set_tempo_( double t ) +{ + blip_time_t period = + (header_.device_flags & 0x40 ? ::clock_rate / 50 : ::clock_rate / 60); + play_period = blip_time_t (period / t); +} + +blargg_err_t Kss_Emu::start_track_( int track ) +{ + RETURN_ERR( Classic_Emu::start_track_( track ) ); + + memset( ram, 0xC9, 0x4000 ); + memset( ram + 0x4000, 0, sizeof ram - 0x4000 ); + + // copy driver code to lo RAM + static byte const bios [] = { + 0xD3, 0xA0, 0xF5, 0x7B, 0xD3, 0xA1, 0xF1, 0xC9, // $0001: WRTPSG + 0xD3, 0xA0, 0xDB, 0xA2, 0xC9 // $0009: RDPSG + }; + static byte const vectors [] = { + 0xC3, 0x01, 0x00, // $0093: WRTPSG vector + 0xC3, 0x09, 0x00, // $0096: RDPSG vector + }; + memcpy( ram + 0x01, bios, sizeof bios ); + memcpy( ram + 0x93, vectors, sizeof vectors ); + + // copy non-banked data into RAM + unsigned load_addr = get_le16( header_.load_addr ); + long orig_load_size = get_le16( header_.load_size ); + long load_size = min( orig_load_size, rom.file_size() ); + load_size = min( load_size, long (mem_size - load_addr) ); + if ( load_size != orig_load_size ) + set_warning( "Excessive data size" ); + memcpy( ram + load_addr, rom.begin() + header_.extra_header, load_size ); + + rom.set_addr( -load_size - header_.extra_header ); + + // check available bank data + blargg_long const bank_size = this->bank_size(); + int max_banks = (rom.file_size() - load_size + bank_size - 1) / bank_size; + bank_count = header_.bank_mode & 0x7F; + if ( bank_count > max_banks ) + { + bank_count = max_banks; + set_warning( "Bank data missing" ); + } + //dprintf( "load_size : $%X\n", load_size ); + //dprintf( "bank_size : $%X\n", bank_size ); + //dprintf( "bank_count: %d (%d claimed)\n", bank_count, header_.bank_mode & 0x7F ); + + ram [idle_addr] = 0xFF; + cpu::reset( ram, ram ); + cpu::map_mem( 0, mem_size, ram, ram ); + + ay.reset(); + scc.reset(); + if ( sn ) + sn->reset(); + r.sp = 0xF380; + ram [--r.sp] = idle_addr >> 8; + ram [--r.sp] = idle_addr; + r.b.a = track; + r.pc = get_le16( header_.init_addr ); + next_play = play_period; + scc_accessed = false; + gain_updated = false; + update_gain(); + + return 0; +} + +void Kss_Emu::set_bank( int logical, int physical ) +{ + unsigned const bank_size = this->bank_size(); + + unsigned addr = 0x8000; + if ( logical && bank_size == 8 * 1024 ) + addr = 0xA000; + + physical -= header_.first_bank; + if ( (unsigned) physical >= (unsigned) bank_count ) + { + byte* data = ram + addr; + cpu::map_mem( addr, bank_size, data, data ); + } + else + { + long phys = physical * (blargg_long) bank_size; + for ( unsigned offset = 0; offset < bank_size; offset += page_size ) + cpu::map_mem( addr + offset, page_size, + unmapped_write(), rom.at_addr( phys + offset ) ); + } +} + +void Kss_Emu::cpu_write( unsigned addr, int data ) +{ + data &= 0xFF; + switch ( addr ) + { + case 0x9000: + set_bank( 0, data ); + return; + + case 0xB000: + set_bank( 1, data ); + return; + } + + int scc_addr = (addr & 0xDFFF) ^ 0x9800; + if ( scc_addr < scc.reg_count ) + { + scc_accessed = true; + scc.write( time(), scc_addr, data ); + return; + } + + dprintf( "LD ($%04X),$%02X\n", addr, data ); +} + +void kss_cpu_write( Kss_Cpu* cpu, unsigned addr, int data ) +{ + *cpu->write( addr ) = data; + if ( (addr & STATIC_CAST(Kss_Emu&,*cpu).scc_enabled) == 0x8000 ) + STATIC_CAST(Kss_Emu&,*cpu).cpu_write( addr, data ); +} + +void kss_cpu_out( Kss_Cpu* cpu, cpu_time_t time, unsigned addr, int data ) +{ + data &= 0xFF; + Kss_Emu& emu = STATIC_CAST(Kss_Emu&,*cpu); + switch ( addr & 0xFF ) + { + case 0xA0: + emu.ay_latch = data & 0x0F; + return; + + case 0xA1: + emu.ay.write( time, emu.ay_latch, data ); + return; + + case 0x06: + if ( emu.sn && (emu.header_.device_flags & 0x04) ) + { + emu.sn->write_ggstereo( time, data ); + return; + } + break; + + case 0x7E: + case 0x7F: + if ( emu.sn ) + { + emu.sn->write_data( time, data ); + return; + } + break; + + case 0xFE: + emu.set_bank( 0, data ); + return; + + #ifndef NDEBUG + case 0xF1: // FM data + if ( data ) + break; // trap non-zero data + case 0xF0: // FM addr + case 0xA8: // PPI + return; + #endif + } + + dprintf( "OUT $%04X,$%02X\n", addr, data ); +} + +int kss_cpu_in( Kss_Cpu*, cpu_time_t, unsigned addr ) +{ + //Kss_Emu& emu = STATIC_CAST(Kss_Emu&,*cpu); + switch ( addr & 0xFF ) + { + } + + dprintf( "IN $%04X\n", addr ); + return 0; +} + +// Emulation + +blargg_err_t Kss_Emu::run_clocks( blip_time_t& duration, int ) +{ + while ( time() < duration ) + { + blip_time_t end = min( duration, next_play ); + cpu::run( min( duration, next_play ) ); + if ( r.pc == idle_addr ) + set_time( end ); + + if ( time() >= next_play ) + { + next_play += play_period; + if ( r.pc == idle_addr ) + { + if ( !gain_updated ) + { + gain_updated = true; + if ( scc_accessed ) + update_gain(); + } + + ram [--r.sp] = idle_addr >> 8; + ram [--r.sp] = idle_addr; + r.pc = get_le16( header_.play_addr ); + } + } + } + + duration = time(); + next_play -= duration; + check( next_play >= 0 ); + adjust_time( -duration ); + ay.end_frame( duration ); + scc.end_frame( duration ); + if ( sn ) + sn->end_frame( duration ); + + return 0; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/console/Kss_Emu.h Thu Nov 30 19:54:33 2006 -0800 @@ -0,0 +1,95 @@ +// MSX computer KSS music file emulator + +// Game_Music_Emu 0.5.1 +#ifndef KSS_EMU_H +#define KSS_EMU_H + +#include "Classic_Emu.h" +#include "Kss_Scc_Apu.h" +#include "Kss_Cpu.h" +#include "Sms_Apu.h" +#include "Ay_Apu.h" + +class Kss_Emu : private Kss_Cpu, public Classic_Emu { + typedef Kss_Cpu cpu; +public: + // KSS file header + struct header_t + { + byte tag [4]; + byte load_addr [2]; + byte load_size [2]; + byte init_addr [2]; + byte play_addr [2]; + byte first_bank; + byte bank_mode; + byte extra_header; + byte device_flags; + }; + BOOST_STATIC_ASSERT( sizeof (header_t) == 0x10 ); + + struct ext_header_t + { + byte data_size [4]; + byte unused [4]; + byte first_track [2]; + byte last_tack [2]; + byte psg_vol; + byte scc_vol; + byte msx_music_vol; + byte msx_audio_vol; + }; + BOOST_STATIC_ASSERT( sizeof (ext_header_t) == 0x10 ); + + struct composite_header_t : header_t, ext_header_t { }; + + // Header for currently loaded file + composite_header_t const& header() const { return header_; } + + static gme_type_t static_type() { return gme_kss_type; } +public: + Kss_Emu(); + ~Kss_Emu(); +protected: + blargg_err_t track_info_( track_info_t*, int track ) const; + blargg_err_t load_( Data_Reader& ); + blargg_err_t start_track_( int ); + blargg_err_t run_clocks( blip_time_t&, int ); + void set_tempo_( double ); + void set_voice( int, Blip_Buffer*, Blip_Buffer*, Blip_Buffer* ); + void update_eq( blip_eq_t const& ); + void unload(); +private: + Rom_Data<page_size> rom; + composite_header_t header_; + byte* unmapped_write() { return rom.unmapped(); } + + bool scc_accessed; + bool gain_updated; + void update_gain(); + + unsigned scc_enabled; // 0 or 0xC000 + byte const* bank_data; + int bank_count; + void set_bank( int logical, int physical ); + blargg_long bank_size() const { return (16 * 1024L) >> (header_.bank_mode >> 7 & 1); } + + blip_time_t play_period; + blip_time_t next_play; + int ay_latch; + + friend void kss_cpu_out( class Kss_Cpu*, cpu_time_t, unsigned addr, int data ); + friend int kss_cpu_in( class Kss_Cpu*, cpu_time_t, unsigned addr ); + void cpu_write( unsigned addr, int data ); + friend void kss_cpu_write( class Kss_Cpu*, unsigned addr, int data ); + + // large items + enum { mem_size = 0x10000 }; + byte ram [mem_size + cpu_padding]; + + Ay_Apu ay; + Scc_Apu scc; + Sms_Apu* sn; +}; + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/console/Kss_Scc_Apu.cxx Thu Nov 30 19:54:33 2006 -0800 @@ -0,0 +1,95 @@ +// Game_Music_Emu 0.5.1. http://www.slack.net/~ant/ + +#include "Kss_Scc_Apu.h" + +/* Copyright (C) 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" + +// Tones above this frequency are treated as disabled tone at half volume. +// Power of two is more efficient (avoids division). +unsigned const inaudible_freq = 16384; + +int const wave_size = 0x20; + +void Scc_Apu::run_until( blip_time_t end_time ) +{ + for ( int index = 0; index < osc_count; index++ ) + { + osc_t& osc = oscs [index]; + + Blip_Buffer* const output = osc.output; + if ( !output ) + continue; + output->set_modified(); + + blip_time_t period = (regs [0x80 + index * 2 + 1] & 0x0F) * 0x100 + + regs [0x80 + index * 2] + 1; + int volume = 0; + if ( regs [0x8F] & (1 << index) ) + { + blip_time_t inaudible_period = (blargg_ulong) (output->clock_rate() + + inaudible_freq * 32) / (inaudible_freq * 16); + if ( period > inaudible_period ) + volume = (regs [0x8A + index] & 0x0F) * (amp_range / 256 / 15); + } + + BOOST::int8_t const* wave = (BOOST::int8_t*) regs + index * wave_size; + if ( index == osc_count - 1 ) + wave -= wave_size; // last two oscs share wave + int amp = wave [osc.phase] * volume; + int delta = amp - osc.last_amp; + if ( delta ) + { + osc.last_amp = amp; + synth.offset( last_time, delta, output ); + } + + blip_time_t time = last_time + osc.delay; + if ( time < end_time ) + { + if ( !volume ) + { + // maintain phase + blargg_long count = (end_time - time + period - 1) / period; + osc.phase = (osc.phase + count) & (wave_size - 1); + time += count * period; + } + else + { + + int phase = osc.phase; + int last_wave = wave [phase]; + phase = (phase + 1) & (wave_size - 1); // pre-advance for optimal inner loop + + do + { + int amp = wave [phase]; + phase = (phase + 1) & (wave_size - 1); + int delta = amp - last_wave; + if ( delta ) + { + last_wave = amp; + synth.offset( time, delta * volume, output ); + } + time += period; + } + while ( time < end_time ); + + osc.phase = phase = (phase - 1) & (wave_size - 1); // undo pre-advance + osc.last_amp = wave [phase] * volume; + } + } + osc.delay = time - end_time; + } + last_time = end_time; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/console/Kss_Scc_Apu.h Thu Nov 30 19:54:33 2006 -0800 @@ -0,0 +1,111 @@ +// Konami SCC sound chip emulator + +// Game_Music_Emu 0.5.1 +#ifndef KSS_SCC_APU_H +#define KSS_SCC_APU_H + +#include "blargg_common.h" +#include "Blip_Buffer.h" +#include <string.h> + +class Scc_Apu { +public: + // Set buffer to generate all sound into, or disable sound if NULL + void output( Blip_Buffer* ); + + // Reset sound chip + void reset(); + + // Write to register at specified time + enum { reg_count = 0x90 }; + void write( blip_time_t time, int reg, int data ); + + // Run sound 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 end_frame( blip_time_t length ); + +// Additional features + + // Set sound output of specific oscillator to buffer, where index is + // 0 to 4. If buffer is NULL, the specified oscillator is muted. + enum { osc_count = 5 }; + void osc_output( int index, Blip_Buffer* ); + + // Set overall volume (default is 1.0) + void volume( double ); + + // Set treble equalization (see documentation) + void treble_eq( blip_eq_t const& ); + +public: + Scc_Apu(); +private: + enum { amp_range = 0x8000 }; + struct osc_t + { + int delay; + int phase; + int last_amp; + Blip_Buffer* output; + }; + osc_t oscs [osc_count]; + blip_time_t last_time; + unsigned char regs [reg_count]; + Blip_Synth<blip_med_quality,1> synth; + + void run_until( blip_time_t ); +}; + +inline void Scc_Apu::volume( double v ) { synth.volume( 0.43 / osc_count / amp_range * v ); } + +inline void Scc_Apu::treble_eq( blip_eq_t const& eq ) { synth.treble_eq( eq ); } + +inline void Scc_Apu::osc_output( int index, Blip_Buffer* b ) +{ + assert( (unsigned) index < osc_count ); + oscs [index].output = b; +} + +inline void Scc_Apu::write( blip_time_t time, int addr, int data ) +{ + assert( (unsigned) addr < reg_count ); + run_until( time ); + regs [addr] = data; +} + +inline void Scc_Apu::end_frame( blip_time_t end_time ) +{ + if ( end_time > last_time ) + run_until( end_time ); + last_time -= end_time; + assert( last_time >= 0 ); +} + +inline void Scc_Apu::output( Blip_Buffer* buf ) +{ + for ( int i = 0; i < osc_count; i++ ) + oscs [i].output = buf; +} + +inline Scc_Apu::Scc_Apu() +{ + output( 0 ); +} + +inline void Scc_Apu::reset() +{ + last_time = 0; + + osc_t* osc = &oscs [osc_count]; + do + { + osc--; + memset( osc, 0, offsetof (osc_t,output) ); + } + while ( osc != oscs ); + + memset( regs, 0, sizeof regs ); +} + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/console/M3u_Playlist.cxx Thu Nov 30 19:54:33 2006 -0800 @@ -0,0 +1,406 @@ +// Game_Music_Emu 0.5.1. http://www.slack.net/~ant/ + +#include "M3u_Playlist.h" +#include "Music_Emu.h" + +#include <string.h> + +/* Copyright (C) 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" + +// gme functions defined here to avoid linking in m3u code unless it's used + +blargg_err_t Gme_File::load_m3u_( blargg_err_t err ) +{ + if ( !err && playlist.size() ) + track_count_ = playlist.size(); + require( raw_track_count_ ); // file must be loaded first + return err; +} + +blargg_err_t Gme_File::load_m3u( const char* path ) { return load_m3u_( playlist.load( path ) ); } + +blargg_err_t Gme_File::load_m3u( Data_Reader& in ) { return load_m3u_( playlist.load( in ) ); } + +const char* gme_load_m3u( Music_Emu* me, const char* path ) { return me->load_m3u( path ); } + +const char* gme_load_m3u_data( Music_Emu* me, const void* data, long size ) +{ + Mem_File_Reader in( data, size ); + return me->load_m3u( in ); +} + + + +static char* skip_white( char* in ) +{ + while ( *in == ' ' ) + in++; + return in; +} + +inline unsigned from_dec( unsigned n ) { return n - '0'; } + +static char* parse_filename( char* in, M3u_Playlist::entry_t& entry ) +{ + entry.file = in; + entry.type = ""; + char* out = in; + while ( 1 ) + { + int c = *in; + if ( !c ) break; + in++; + + if ( c == ',' ) // commas in filename + { + char* p = skip_white( in ); + if ( *p == '$' || from_dec( *p ) <= 9 ) + { + in = p; + break; + } + } + + if ( c == ':' && in [0] == ':' && in [1] && in [2] != ',' ) // ::type suffix + { + entry.type = ++in; + while ( (c = *in) != 0 && c != ',' ) + in++; + if ( c == ',' ) + { + *in++ = 0; // terminate type + in = skip_white( in ); + } + break; + } + + if ( c == '\\' ) // \ prefix for special characters + { + c = *in; + if ( !c ) break; + in++; + } + *out++ = (char) c; + } + *out = 0; // terminate string + return in; +} + +static char* next_field( char* in, int* result ) +{ + while ( 1 ) + { + in = skip_white( in ); + + if ( !*in ) + break; + + if ( *in == ',' ) + { + in++; + break; + } + + *result = 1; + in++; + } + return skip_white( in ); +} + +static char* parse_int_( char* in, int* out ) +{ + int n = 0; + while ( 1 ) + { + unsigned d = from_dec( *in ); + if ( d > 9 ) + break; + in++; + n = n * 10 + d; + *out = n; + } + return in; +} + +static char* parse_int( char* in, int* out, int* result ) +{ + return next_field( parse_int_( in, out ), result ); +} + +// Returns 16 or greater if not hex +inline int from_hex_char( int h ) +{ + h -= 0x30; + if ( (unsigned) h > 9 ) + h = ((h - 0x11) & 0xDF) + 10; + return h; +} + +static char* parse_track( char* in, M3u_Playlist::entry_t& entry, int* result ) +{ + if ( *in == '$' ) + { + in++; + int n = 0; + while ( 1 ) + { + int h = from_hex_char( *in ); + if ( h > 15 ) + break; + in++; + n = n * 16 + h; + entry.track = n; + } + } + else + { + in = parse_int_( in, &entry.track ); + if ( entry.track >= 0 ) + entry.decimal_track = 1; + } + return next_field( in, result ); +} + +static char* parse_time_( char* in, int* out ) +{ + *out = -1; + int n = -1; + in = parse_int_( in, &n ); + if ( n >= 0 ) + { + *out = n; + if ( *in == ':' ) + { + n = -1; + in = parse_int_( in + 1, &n ); + if ( n >= 0 ) + *out = *out * 60 + n; + } + } + return in; +} + +static char* parse_time( char* in, int* out, int* result ) +{ + return next_field( parse_time_( in, out ), result ); +} + +static char* parse_name( char* in ) +{ + char* out = in; + while ( 1 ) + { + int c = *in; + if ( !c ) break; + in++; + + if ( c == ',' ) // commas in string + { + char* p = skip_white( in ); + if ( *p == ',' || *p == '-' || from_dec( *p ) <= 9 ) + { + in = p; + break; + } + } + + if ( c == '\\' ) // \ prefix for special characters + { + c = *in; + if ( !c ) break; + in++; + } + *out++ = (char) c; + } + *out = 0; // terminate string + return in; +} + +static int parse_line( char* in, M3u_Playlist::entry_t& entry ) +{ + int result = 0; + + // file + entry.file = in; + entry.type = ""; + in = parse_filename( in, entry ); + + // track + entry.track = -1; + entry.decimal_track = 0; + in = parse_track( in, entry, &result ); + + // name + entry.name = in; + in = parse_name( in ); + + // time + entry.length = -1; + in = parse_time( in, &entry.length, &result ); + + // loop + entry.intro = -1; + entry.loop = -1; + if ( *in == '-' ) + { + entry.loop = entry.length; + in++; + } + else + { + in = parse_time_( in, &entry.loop ); + if ( entry.loop >= 0 ) + { + entry.intro = 0; + if ( *in == '-' ) // trailing '-' means that intro length was specified + { + in++; + entry.intro = entry.loop; + entry.loop = entry.length - entry.intro; + } + } + } + in = next_field( in, &result ); + + // fade + entry.fade = -1; + in = parse_time( in, &entry.fade, &result ); + + // repeat + entry.repeat = -1; + in = parse_int( in, &entry.repeat, &result ); + + return result; +} + +static void parse_comment( char* in, M3u_Playlist::info_t& info, bool first ) +{ + in = skip_white( in + 1 ); + const char* field = in; + while ( *in && *in != ':' ) + in++; + + if ( *in == ':' ) + { + const char* text = skip_white( in + 1 ); + if ( *text ) + { + *in = 0; + if ( !strcmp( "Composer", field ) ) info.composer = text; + else if ( !strcmp( "Engineer", field ) ) info.engineer = text; + else if ( !strcmp( "Ripping" , field ) ) info.ripping = text; + else if ( !strcmp( "Tagging" , field ) ) info.tagging = text; + else + text = 0; + if ( text ) + return; + *in = ':'; + } + } + + if ( first ) + info.title = field; +} + +blargg_err_t M3u_Playlist::parse_() +{ + info_.title = ""; + info_.composer = ""; + info_.engineer = ""; + info_.ripping = ""; + info_.tagging = ""; + + int const CR = 13; + int const LF = 10; + + data.end() [-1] = LF; // terminate input + + first_error_ = 0; + bool first_comment = true; + int line = 0; + int count = 0; + char* in = data.begin(); + while ( in < data.end() ) + { + // find end of line and terminate it + line++; + char* begin = in; + while ( *in != CR && *in != LF ) + { + if ( !*in ) + return "Not an m3u playlist"; + in++; + } + if ( in [0] == CR && in [1] == LF ) // treat CR,LF as a single line + *in++ = 0; + *in++ = 0; + + // parse line + if ( *begin == '#' ) + { + parse_comment( begin, info_, first_comment ); + first_comment = false; + } + else if ( *begin ) + { + if ( (int) entries.size() <= count ) + RETURN_ERR( entries.resize( count * 2 + 64 ) ); + + if ( !parse_line( begin, entries [count] ) ) + count++; + else if ( !first_error_ ) + first_error_ = line; + first_comment = false; + } + } + if ( count <= 0 ) + return "Not an m3u playlist"; + + if ( !(info_.composer [0] | info_.engineer [0] | info_.ripping [0] | info_.tagging [0]) ) + info_.title = ""; + + return entries.resize( count ); +} + +blargg_err_t M3u_Playlist::parse() +{ + blargg_err_t err = parse_(); + if ( err ) + { + entries.clear(); + data.clear(); + } + return err; +} + +blargg_err_t M3u_Playlist::load( Data_Reader& in ) +{ + RETURN_ERR( data.resize( in.remain() + 1 ) ); + RETURN_ERR( in.read( data.begin(), data.size() - 1 ) ); + return parse(); +} + +blargg_err_t M3u_Playlist::load( const char* path ) +{ + GME_FILE_READER in; + RETURN_ERR( in.open( path ) ); + return load( in ); +} + +blargg_err_t M3u_Playlist::load( void const* in, long size ) +{ + RETURN_ERR( data.resize( size + 1 ) ); + memcpy( data.begin(), in, size ); + return parse(); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/console/M3u_Playlist.h Thu Nov 30 19:54:33 2006 -0800 @@ -0,0 +1,67 @@ +// M3U playlist file parser, with support for subtrack information + +// Game_Music_Emu 0.5.1 +#ifndef M3U_PLAYLIST_H +#define M3U_PLAYLIST_H + +#include "blargg_common.h" +#include "Data_Reader.h" + +class M3u_Playlist { +public: + // Load playlist data + blargg_err_t load( const char* path ); + blargg_err_t load( Data_Reader& in ); + blargg_err_t load( void const* data, long size ); + + // Line number of first parse error, 0 if no error. Any lines with parse + // errors are ignored. + int first_error() const { return first_error_; } + + struct info_t + { + const char* title; + const char* composer; + const char* engineer; + const char* ripping; + const char* tagging; + }; + info_t const& info() const { return info_; } + + struct entry_t + { + const char* file; // filename without stupid ::TYPE suffix + const char* type; // if filename has ::TYPE suffix, this will be "TYPE". "" if none. + const char* name; + bool decimal_track; // true if track was specified in hex + // integers are -1 if not present + int track; // 1-based + int length; // seconds + int intro; + int loop; + int fade; + int repeat; // count + }; + entry_t const& operator [] ( int i ) const { return entries [i]; } + int size() const { return entries.size(); } + + void clear(); + +private: + blargg_vector<entry_t> entries; + blargg_vector<char> data; + int first_error_; + info_t info_; + + blargg_err_t parse(); + blargg_err_t parse_(); +}; + +inline void M3u_Playlist::clear() +{ + first_error_ = 0; + entries.clear(); + data.clear(); +} + +#endif
--- a/src/console/Makefile Wed Nov 29 14:42:11 2006 -0800 +++ b/src/console/Makefile Thu Nov 30 19:54:33 2006 -0800 @@ -6,40 +6,55 @@ LIBDIR = $(plugindir)/$(INPUT_PLUGIN_DIR) SOURCES = \ - Blip_Buffer.cxx \ - Classic_Emu.cxx \ - Dual_Resampler.cxx \ - Fir_Resampler.cxx \ - Gb_Apu.cxx \ - Gb_Cpu.cxx \ - Gb_Oscs.cxx \ - Gbs_Emu.cxx \ - Gym_Emu.cxx \ - Multi_Buffer.cxx \ - Music_Emu.cxx \ - Nes_Apu.cxx \ - Nes_Cpu.cxx \ - Nes_Fme7_Apu.cxx \ - Nes_Namco_Apu.cxx \ - Nes_Oscs.cxx \ - Nes_Vrc6_Apu.cxx \ - Nsfe_Emu.cxx \ - Nsf_Emu.cxx \ - Sms_Apu.cxx \ - Snes_Spc.cxx \ - Spc_Cpu.cxx \ - Spc_Dsp.cxx \ - Spc_Emu.cxx \ - Vgm_Emu.cxx \ - abstract_file.cxx \ - Vfs_File.cxx \ - Gzip_File.cxx \ - Vgm_Emu_Impl.cxx \ - Ym2413_Emu.cxx \ - Ym2612_Emu.cxx \ - Track_Emu.cxx \ - Audacious_Config.cxx \ - Audacious_Driver.cxx + Audacious_Config.cxx \ + Audacious_Driver.cxx \ + Ay_Apu.cxx \ + Ay_Cpu.cxx \ + Ay_Emu.cxx \ + Blip_Buffer.cxx \ + Classic_Emu.cxx \ + Data_Reader.cxx \ + Dual_Resampler.cxx \ + Effects_Buffer.cxx \ + Fir_Resampler.cxx \ + Gbs_Emu.cxx \ + Gb_Apu.cxx \ + Gb_Cpu.cxx \ + Gb_Oscs.cxx \ + gme.cxx \ + Gme_File.cxx \ + gme_type_list.cxx \ + Gym_Emu.cxx \ + Hes_Apu.cxx \ + Hes_Cpu.cxx \ + Hes_Emu.cxx \ + Kss_Cpu.cxx \ + Kss_Emu.cxx \ + Kss_Scc_Apu.cxx \ + M3u_Playlist.cxx \ + Multi_Buffer.cxx \ + Music_Emu.cxx \ + Nes_Apu.cxx \ + Nes_Cpu.cxx \ + Nes_Fme7_Apu.cxx \ + Nes_Namco_Apu.cxx \ + Nes_Oscs.cxx \ + Nes_Vrc6_Apu.cxx \ + Nsfe_Emu.cxx \ + Nsf_Emu.cxx \ + Sap_Apu.cxx \ + Sap_Cpu.cxx \ + Sap_Emu.cxx \ + Sms_Apu.cxx \ + Snes_Spc.cxx \ + Spc_Cpu.cxx \ + Spc_Dsp.cxx \ + Spc_Emu.cxx \ + Vfs_File.cxx \ + Vgm_Emu.cxx \ + Vgm_Emu_Impl.cxx \ + Ym2413_Emu.cxx \ + Ym2612_Emu.cxx OBJECTS = ${SOURCES:.cxx=.o}
--- a/src/console/Multi_Buffer.cxx Wed Nov 29 14:42:11 2006 -0800 +++ b/src/console/Multi_Buffer.cxx Thu Nov 30 19:54:33 2006 -0800 @@ -1,5 +1,4 @@ - -// Blip_Buffer 0.4.0. http://www.slack.net/~ant/ +// Blip_Buffer 0.4.1. http://www.slack.net/~ant/ #include "Multi_Buffer.h" @@ -9,12 +8,16 @@ 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 */ +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_BEGIN +#include "blargg_source.h" + +#ifdef BLARGG_ENABLE_OPTIMIZER + #include BLARGG_ENABLE_OPTIMIZER +#endif Multi_Buffer::Multi_Buffer( int spf ) : samples_per_frame_( spf ) { @@ -23,48 +26,33 @@ channels_changed_count_ = 1; } -blargg_err_t Multi_Buffer::set_channel_count( int ) -{ - return blargg_success; -} - -Mono_Buffer::Mono_Buffer() : Multi_Buffer( 1 ) -{ -} - -Mono_Buffer::~Mono_Buffer() -{ -} - -blargg_err_t Mono_Buffer::set_sample_rate( long rate, int msec ) -{ - BLARGG_RETURN_ERR( buf.set_sample_rate( rate, msec ) ); - return Multi_Buffer::set_sample_rate( buf.sample_rate(), buf.length() ); -} +blargg_err_t Multi_Buffer::set_channel_count( int ) { return 0; } // Silent_Buffer Silent_Buffer::Silent_Buffer() : Multi_Buffer( 1 ) // 0 channels would probably confuse { - chan.left = NULL; - chan.center = NULL; - chan.right = NULL; + // TODO: better to use empty Blip_Buffer so caller never has to check for NULL? + chan.left = 0; + chan.center = 0; + chan.right = 0; } // Mono_Buffer -Mono_Buffer::channel_t Mono_Buffer::channel( int ) +Mono_Buffer::Mono_Buffer() : Multi_Buffer( 1 ) { - channel_t ch; - ch.center = &buf; - ch.left = &buf; - ch.right = &buf; - return ch; + chan.center = &buf; + chan.left = &buf; + chan.right = &buf; } -void Mono_Buffer::end_frame( blip_time_t t, bool ) +Mono_Buffer::~Mono_Buffer() { } + +blargg_err_t Mono_Buffer::set_sample_rate( long rate, int msec ) { - buf.end_frame( t ); + RETURN_ERR( buf.set_sample_rate( rate, msec ) ); + return Multi_Buffer::set_sample_rate( buf.sample_rate(), buf.length() ); } // Stereo_Buffer @@ -76,14 +64,12 @@ chan.right = &bufs [2]; } -Stereo_Buffer::~Stereo_Buffer() -{ -} +Stereo_Buffer::~Stereo_Buffer() { } blargg_err_t Stereo_Buffer::set_sample_rate( long rate, int msec ) { for ( int i = 0; i < buf_count; i++ ) - BLARGG_RETURN_ERR( bufs [i].set_sample_rate( rate, msec ) ); + RETURN_ERR( bufs [i].set_sample_rate( rate, msec ) ); return Multi_Buffer::set_sample_rate( bufs [0].sample_rate(), bufs [0].length() ); } @@ -101,18 +87,20 @@ void Stereo_Buffer::clear() { - stereo_added = false; - was_stereo = false; + stereo_added = 0; + was_stereo = false; for ( int i = 0; i < buf_count; i++ ) bufs [i].clear(); } -void Stereo_Buffer::end_frame( blip_time_t clock_count, bool stereo ) +void Stereo_Buffer::end_frame( blip_time_t clock_count ) { + stereo_added = 0; for ( unsigned i = 0; i < buf_count; i++ ) + { + stereo_added |= bufs [i].clear_modified() << i; bufs [i].end_frame( clock_count ); - - stereo_added |= stereo; + } } long Stereo_Buffer::read_samples( blip_sample_t* out, long count ) @@ -125,91 +113,120 @@ count = avail; if ( count ) { - if ( stereo_added || was_stereo ) + int bufs_used = stereo_added | was_stereo; + //dprintf( "%X\n", bufs_used ); + if ( bufs_used <= 1 ) + { + mix_mono( out, count ); + bufs [0].remove_samples( count ); + bufs [1].remove_silence( count ); + bufs [2].remove_silence( count ); + } + else if ( bufs_used & 1 ) { mix_stereo( out, count ); - bufs [0].remove_samples( count ); bufs [1].remove_samples( count ); bufs [2].remove_samples( count ); } else { - mix_mono( out, count ); - - bufs [0].remove_samples( count ); - - bufs [1].remove_silence( count ); - bufs [2].remove_silence( count ); + mix_stereo_no_center( out, count ); + bufs [0].remove_silence( count ); + bufs [1].remove_samples( count ); + bufs [2].remove_samples( count ); } // to do: this might miss opportunities for optimization - if ( !bufs [0].samples_avail() ) { - was_stereo = stereo_added; - stereo_added = false; + if ( !bufs [0].samples_avail() ) + { + was_stereo = stereo_added; + stereo_added = 0; } } return count * 2; } -#include BLARGG_ENABLE_OPTIMIZER - -void Stereo_Buffer::mix_stereo( blip_sample_t* out, long count ) +void Stereo_Buffer::mix_stereo( blip_sample_t* out_, blargg_long count ) { - Blip_Reader left; - Blip_Reader right; - Blip_Reader center; + blip_sample_t* BLIP_RESTRICT out = out_; + int const bass = BLIP_READER_BASS( bufs [1] ); + BLIP_READER_BEGIN( left, bufs [1] ); + BLIP_READER_BEGIN( right, bufs [2] ); + BLIP_READER_BEGIN( center, bufs [0] ); - left.begin( bufs [1] ); - right.begin( bufs [2] ); - int bass = center.begin( bufs [0] ); - - while ( count-- ) + for ( ; count; --count ) { - int c = center.read(); - long l = c + left.read(); - long r = c + right.read(); - center.next( bass ); + int c = BLIP_READER_READ( center ); + blargg_long l = c + BLIP_READER_READ( left ); + blargg_long r = c + BLIP_READER_READ( right ); + if ( (BOOST::int16_t) l != l ) + l = 0x7FFF - (l >> 24); + + BLIP_READER_NEXT( center, bass ); + if ( (BOOST::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; - - if ( (BOOST::int16_t) l != l ) - out [-2] = 0x7FFF - (l >> 24); - - left.next( bass ); - right.next( bass ); - - if ( (BOOST::int16_t) r != r ) - out [-1] = 0x7FFF - (r >> 24); } - center.end( bufs [0] ); - right.end( bufs [2] ); - left.end( bufs [1] ); + BLIP_READER_END( center, bufs [0] ); + BLIP_READER_END( right, bufs [2] ); + BLIP_READER_END( left, bufs [1] ); } -void Stereo_Buffer::mix_mono( blip_sample_t* out, long count ) +void Stereo_Buffer::mix_stereo_no_center( blip_sample_t* out_, blargg_long count ) { - Blip_Reader in; - int bass = in.begin( bufs [0] ); + blip_sample_t* BLIP_RESTRICT out = out_; + int const bass = BLIP_READER_BASS( bufs [1] ); + BLIP_READER_BEGIN( left, bufs [1] ); + BLIP_READER_BEGIN( right, bufs [2] ); - while ( count-- ) + for ( ; count; --count ) { - long s = in.read(); - in.next( bass ); + blargg_long l = BLIP_READER_READ( left ); + if ( (BOOST::int16_t) l != l ) + l = 0x7FFF - (l >> 24); + + blargg_long r = BLIP_READER_READ( right ); + if ( (BOOST::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, bufs [2] ); + BLIP_READER_END( left, bufs [1] ); +} + +void Stereo_Buffer::mix_mono( blip_sample_t* out_, blargg_long count ) +{ + blip_sample_t* BLIP_RESTRICT out = out_; + int const bass = BLIP_READER_BASS( bufs [0] ); + BLIP_READER_BEGIN( center, bufs [0] ); + + for ( ; count; --count ) + { + blargg_long s = BLIP_READER_READ( center ); + if ( (BOOST::int16_t) s != s ) + s = 0x7FFF - (s >> 24); + + BLIP_READER_NEXT( center, bass ); out [0] = s; out [1] = s; out += 2; - - if ( (BOOST::int16_t) s != s ) { - s = 0x7FFF - (s >> 24); - out [-2] = s; - out [-1] = s; - } } - in.end( bufs [0] ); + BLIP_READER_END( center, bufs [0] ); } -
--- a/src/console/Multi_Buffer.h Wed Nov 29 14:42:11 2006 -0800 +++ b/src/console/Multi_Buffer.h Thu Nov 30 19:54:33 2006 -0800 @@ -1,8 +1,6 @@ - // Multi-channel sound buffer interface, and basic mono and stereo buffers -// Blip_Buffer 0.4.0 - +// Blip_Buffer 0.4.1 #ifndef MULTI_BUFFER_H #define MULTI_BUFFER_H @@ -25,7 +23,9 @@ Blip_Buffer* left; Blip_Buffer* right; }; - virtual channel_t channel( int index ) = 0; + enum { type_index_mask = 0xFF }; + enum { wave_type = 0x100, noise_type = 0x200, mixed_type = wave_type | noise_type }; + virtual channel_t channel( int index, int type ) = 0; // See Blip_Buffer.h virtual blargg_err_t set_sample_rate( long rate, int msec = blip_default_length ) = 0; @@ -37,10 +37,8 @@ // Length of buffer, in milliseconds int length() const; - // See Blip_Buffer.h. For optimal operation, pass false for 'added_stereo' - // if nothing was added to the left and right buffers of any channel for - // this time frame. - virtual void end_frame( blip_time_t, bool added_stereo = true ) = 0; + // See Blip_Buffer.h + virtual void end_frame( blip_time_t ) = 0; // Number of samples per output frame (1 = mono, 2 = stereo) int samples_per_frame() const; @@ -53,6 +51,8 @@ virtual long read_samples( blip_sample_t*, long ) = 0; virtual long samples_avail() const = 0; +public: + BLARGG_DISABLE_NOTHROW protected: void channels_changed() { channels_changed_count_++; } private: @@ -69,55 +69,56 @@ // Uses a single buffer and outputs mono samples. class Mono_Buffer : public Multi_Buffer { Blip_Buffer buf; + channel_t chan; public: - Mono_Buffer(); - ~Mono_Buffer(); - // Buffer used for all channels Blip_Buffer* center() { return &buf; } - // See Multi_Buffer +public: + Mono_Buffer(); + ~Mono_Buffer(); blargg_err_t set_sample_rate( long rate, int msec = blip_default_length ); - void clock_rate( long ); - void bass_freq( int ); - void clear(); - channel_t channel( int ); - void end_frame( blip_time_t, bool unused = true ); - long samples_avail() const; - long read_samples( blip_sample_t*, long ); + void clock_rate( long rate ) { buf.clock_rate( rate ); } + void bass_freq( int freq ) { buf.bass_freq( freq ); } + void clear() { buf.clear(); } + long samples_avail() const { return buf.samples_avail(); } + long read_samples( blip_sample_t* p, long s ) { return buf.read_samples( p, s ); } + channel_t channel( int, int ) { return chan; } + void end_frame( blip_time_t t ) { buf.end_frame( t ); } }; // Uses three buffers (one for center) and outputs stereo sample pairs. class Stereo_Buffer : public Multi_Buffer { public: - Stereo_Buffer(); - ~Stereo_Buffer(); // Buffers used for all channels Blip_Buffer* center() { return &bufs [0]; } Blip_Buffer* left() { return &bufs [1]; } Blip_Buffer* right() { return &bufs [2]; } - // See Multi_Buffer +public: + Stereo_Buffer(); + ~Stereo_Buffer(); blargg_err_t set_sample_rate( long, int msec = blip_default_length ); void clock_rate( long ); void bass_freq( int ); void clear(); - channel_t channel( int index ); - void end_frame( blip_time_t, bool added_stereo = true ); + channel_t channel( int, int ) { return chan; } + void end_frame( blip_time_t ); - long samples_avail() const; + long samples_avail() const { return bufs [0].samples_avail() * 2; } long read_samples( blip_sample_t*, long ); private: enum { buf_count = 3 }; Blip_Buffer bufs [buf_count]; channel_t chan; - bool stereo_added; - bool was_stereo; + int stereo_added; + int was_stereo; - void mix_stereo( blip_sample_t*, long ); - void mix_mono( blip_sample_t*, long ); + void mix_stereo_no_center( blip_sample_t*, blargg_long ); + void mix_stereo( blip_sample_t*, blargg_long ); + void mix_mono( blip_sample_t*, blargg_long ); }; // Silent_Buffer generates no samples, useful where no sound is wanted @@ -125,51 +126,31 @@ channel_t chan; public: Silent_Buffer(); - - blargg_err_t set_sample_rate( long rate, int msec = blip_default_length ); + blargg_err_t set_sample_rate( long rate, int msec = blip_default_length ) + { + return Multi_Buffer::set_sample_rate( rate, msec ); + } void clock_rate( long ) { } void bass_freq( int ) { } void clear() { } - channel_t channel( int ) { return chan; } - void end_frame( blip_time_t, bool unused = true ) { } + channel_t channel( int, int ) { return chan; } + void end_frame( blip_time_t ) { } long samples_avail() const { return 0; } long read_samples( blip_sample_t*, long ) { return 0; } }; -// End of public interface - inline blargg_err_t Multi_Buffer::set_sample_rate( long rate, int msec ) { sample_rate_ = rate; length_ = msec; - return blargg_success; -} - -inline blargg_err_t Silent_Buffer::set_sample_rate( long rate, int msec ) -{ - return Multi_Buffer::set_sample_rate( rate, msec ); + return 0; } inline int Multi_Buffer::samples_per_frame() const { return samples_per_frame_; } -inline long Stereo_Buffer::samples_avail() const { return bufs [0].samples_avail() * 2; } - -inline Stereo_Buffer::channel_t Stereo_Buffer::channel( int ) { return chan; } - inline long Multi_Buffer::sample_rate() const { return sample_rate_; } inline int Multi_Buffer::length() const { return length_; } -inline void Mono_Buffer::clock_rate( long rate ) { buf.clock_rate( rate ); } - -inline void Mono_Buffer::clear() { buf.clear(); } - -inline void Mono_Buffer::bass_freq( int freq ) { buf.bass_freq( freq ); } - -inline long Mono_Buffer::read_samples( blip_sample_t* p, long s ) { return buf.read_samples( p, s ); } - -inline long Mono_Buffer::samples_avail() const { return buf.samples_avail(); } - #endif -
--- a/src/console/Music_Emu.cxx Wed Nov 29 14:42:11 2006 -0800 +++ b/src/console/Music_Emu.cxx Thu Nov 30 19:54:33 2006 -0800 @@ -1,8 +1,8 @@ - -// Game_Music_Emu 0.3.0. http://www.slack.net/~ant/ +// Game_Music_Emu 0.5.1. http://www.slack.net/~ant/ #include "Music_Emu.h" +#include "Multi_Buffer.h" #include <string.h> /* Copyright (C) 2003-2006 Shay Green. This module is free software; you @@ -11,74 +11,400 @@ 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 */ +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_BEGIN +#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) Music_Emu::equalizer_t const Music_Emu::tv_eq = { -8.0, 180 }; +void Music_Emu::clear_track_vars() +{ + current_track_ = -1; + out_time = 0; + emu_time = 0; + emu_track_ended_ = true; + track_ended_ = true; + fade_start = LONG_MAX / 2 + 1; + fade_step = 1; + silence_time = 0; + silence_count = 0; + buf_remain = 0; + warning(); // clear warning +} + +void Music_Emu::unload() +{ + voice_count_ = 0; + clear_track_vars(); + Gme_File::unload(); +} + Music_Emu::Music_Emu() { - equalizer_.treble = -1.0; - equalizer_.bass = 60; + effects_buffer = 0; + sample_rate_ = 0; - voice_count_ = 0; - mute_mask_ = 0; - track_count_ = 0; - error_count_ = 0; - track_ended_ = false; + mute_mask_ = 0; + tempo_ = 1.0; + gain_ = 1.0; + + // defaults + max_initial_silence = 2; + silence_lookahead = 3; + ignore_silence_ = false; + equalizer_.treble = -1.0; + equalizer_.bass = 60; + + static const char* const names [] = { + "Voice 1", "Voice 2", "Voice 3", "Voice 4", + "Voice 5", "Voice 6", "Voice 7", "Voice 8" + }; + set_voice_names( names ); + Music_Emu::unload(); // non-virtual +} + +Music_Emu::~Music_Emu() { delete effects_buffer; } + +blargg_err_t Music_Emu::set_sample_rate( long rate ) +{ + require( !sample_rate() ); // sample rate can't be changed once set + RETURN_ERR( set_sample_rate_( rate ) ); + RETURN_ERR( buf.resize( buf_size ) ); + sample_rate_ = rate; + return 0; +} + +void Music_Emu::pre_load() +{ + require( sample_rate() ); // set_sample_rate() must be called before loading a file + Gme_File::pre_load(); +} + +void Music_Emu::set_equalizer( equalizer_t const& eq ) +{ + equalizer_ = eq; + set_equalizer_( eq ); +} + +void Music_Emu::mute_voice( int index, bool mute ) +{ + require( (unsigned) index < (unsigned) voice_count() ); + int bit = 1 << index; + int mask = mute_mask_ | bit; + if ( !mute ) + mask ^= bit; + mute_voices( mask ); +} + +void Music_Emu::mute_voices( int mask ) +{ + require( sample_rate() ); // sample rate must be set first + mute_mask_ = mask; + mute_voices_( mask ); +} + +void Music_Emu::set_tempo( double t ) +{ + require( sample_rate() ); // sample rate must be set first + double const min = 0.02; + double const max = 4.00; + if ( t < min ) t = min; + if ( t > max ) t = max; + tempo_ = t; + set_tempo_( t ); +} + +void Music_Emu::post_load_() +{ + set_tempo( tempo_ ); + remute_voices(); } -Music_Emu::~Music_Emu() +blargg_err_t Music_Emu::start_track( int track ) { + clear_track_vars(); + + int remapped = track; + RETURN_ERR( remap_track( &remapped ) ); + current_track_ = track; + RETURN_ERR( start_track_( remapped ) ); + + emu_track_ended_ = false; + track_ended_ = false; + + if ( !ignore_silence_ ) + { + // play until non-silence or end of track + for ( long end = max_initial_silence * stereo * sample_rate(); emu_time < end; ) + { + fill_buf(); + if ( buf_remain | emu_track_ended_ ) + break; + } + + emu_time = buf_remain; + out_time = 0; + silence_time = 0; + silence_count = 0; + } + return track_ended() ? warning() : 0; +} + +void Music_Emu::end_track_if_error( blargg_err_t err ) +{ + if ( err ) + { + emu_track_ended_ = true; + set_warning( err ); + } } -blargg_err_t Music_Emu::load_file( const char* path ) +// Tell/Seek + +blargg_long Music_Emu::msec_to_samples( blargg_long msec ) const +{ + blargg_long sec = msec / 1000; + msec -= sec * 1000; + return (sec * sample_rate() + msec * sample_rate() / 1000) * stereo; +} + +long Music_Emu::tell() const { - Std_File_Reader in; - BLARGG_RETURN_ERR( in.open( path ) ); - return load( in ); + blargg_long rate = sample_rate() * stereo; + blargg_long sec = out_time / rate; + return sec * 1000 + (out_time - sec * rate) * 1000 / rate; +} + +blargg_err_t Music_Emu::seek( long msec ) +{ + blargg_long time = msec_to_samples( msec ); + if ( time < out_time ) + RETURN_ERR( start_track( current_track_ ) ); + return skip( time - out_time ); } -void Music_Emu::skip( long count ) +blargg_err_t Music_Emu::skip( long count ) { - const int buf_size = 1024; - sample_t buf [buf_size]; + require( current_track() >= 0 ); // start_track() must have been called already + out_time += count; + // remove from silence and buf first + { + long n = min( count, silence_count ); + silence_count -= n; + count -= n; + + n = min( count, buf_remain ); + buf_remain -= n; + count -= n; + } + + if ( count && !emu_track_ended_ ) + { + emu_time += count; + end_track_if_error( skip_( count ) ); + } + + if ( !(silence_count | buf_remain) ) // caught up to emulator, so update track ended + track_ended_ |= emu_track_ended_; + + return 0; +} + +blargg_err_t Music_Emu::skip_( long count ) +{ + // for long skip, mute sound const long threshold = 30000; if ( count > threshold ) { int saved_mute = mute_mask_; mute_voices( ~0 ); - while ( count > threshold / 2 ) + while ( count > threshold / 2 && !emu_track_ended_ ) { - play( buf_size, buf ); + RETURN_ERR( play_( buf_size, buf.begin() ) ); count -= buf_size; } mute_voices( saved_mute ); } - while ( count ) + while ( count && !emu_track_ended_ ) { - int n = buf_size; + long n = buf_size; if ( n > count ) n = count; count -= n; - play( n, buf ); + RETURN_ERR( play_( n, buf.begin() ) ); + } + return 0; +} + +// Fading + +void Music_Emu::set_fade( long start_msec, long length_msec ) +{ + fade_step = sample_rate() * length_msec / (fade_block_size * fade_shift * 1000 / stereo); + fade_start = msec_to_samples( 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; +} + +void Music_Emu::handle_fade( long out_count, sample_t* out ) +{ + for ( int i = 0; i < out_count; i += fade_block_size ) + { + int const shift = 14; + int const unit = 1 << shift; + int gain = int_log( (out_time + i - fade_start) / fade_block_size, + fade_step, unit ); + if ( gain < (unit >> fade_shift) ) + track_ended_ = 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; + } } } -const char** Music_Emu::voice_names() const +// Silence detection + +void Music_Emu::emu_play( long count, sample_t* out ) +{ + check( current_track_ >= 0 ); + emu_time += count; + if ( current_track_ >= 0 && !emu_track_ended_ ) + end_track_if_error( play_( count, out ) ); + else + memset( out, 0, count * sizeof *out ); +} + +// number of consecutive silent samples at end +static long count_silence( Music_Emu::sample_t* begin, long size ) { - static const char* names [] = { - "Voice 1", "Voice 2", "Voice 3", "Voice 4", - "Voice 5", "Voice 6", "Voice 7", "Voice 8" - }; - return names; + Music_Emu::sample_t first = *begin; + *begin = silence_threshold; // sentinel + Music_Emu::sample_t* p = begin + size; + while ( (unsigned) (*--p + silence_threshold / 2) <= (unsigned) silence_threshold ) { } + *begin = first; + return size - (p - begin); +} + +// fill internal buffer and check it for silence +void Music_Emu::fill_buf() +{ + assert( !buf_remain ); + if ( !emu_track_ended_ ) + { + emu_play( buf_size, buf.begin() ); + long silence = count_silence( buf.begin(), buf_size ); + if ( silence < buf_size ) + { + silence_time = emu_time - silence; + buf_remain = buf_size; + return; + } + } + silence_count += buf_size; } +blargg_err_t Music_Emu::play( long out_count, sample_t* out ) +{ + if ( track_ended_ ) + { + memset( out, 0, out_count * sizeof *out ); + } + else + { + require( current_track() >= 0 ); + require( out_count % stereo == 0 ); + + assert( emu_time >= 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 ( silence_count ) + { + // during a run of silence, run emulator at >=2x speed so it gets ahead + long ahead_time = silence_lookahead * (out_time + out_count - silence_time) + silence_time; + while ( emu_time < ahead_time && !(buf_remain | emu_track_ended_) ) + fill_buf(); + + // fill with silence + pos = min( silence_count, out_count ); + memset( out, 0, pos * sizeof *out ); + silence_count -= pos; + + if ( emu_time - silence_time > silence_max * stereo * sample_rate() ) + { + track_ended_ = emu_track_ended_ = true; + silence_count = 0; + buf_remain = 0; + } + } + + if ( buf_remain ) + { + // empty silence buf + long n = min( buf_remain, out_count - pos ); + memcpy( &out [pos], buf.begin() + (buf_size - buf_remain), n * sizeof *out ); + buf_remain -= n; + pos += n; + } + + // generate remaining samples normally + long remain = out_count - pos; + if ( remain ) + { + emu_play( remain, out + pos ); + track_ended_ |= emu_track_ended_; + + if ( !ignore_silence_ || out_time > fade_start ) + { + // check end for a new run of silence + long silence = count_silence( out + pos, remain ); + if ( silence < remain ) + silence_time = emu_time - silence; + + if ( emu_time - silence_time >= buf_size ) + fill_buf(); // cause silence detection on next play() + } + } + + if ( out_time > fade_start ) + handle_fade( out_count, out ); + } + out_time += out_count; + return 0; +} + +// Gme_Info_ + +blargg_err_t Gme_Info_::set_sample_rate_( long ) { return 0; } +void Gme_Info_::pre_load() { Gme_File::pre_load(); } // skip Music_Emu +void Gme_Info_::post_load_() { Gme_File::post_load_(); } // skip Music_Emu +void Gme_Info_::set_equalizer_( equalizer_t const& ){ check( false ); } +void Gme_Info_::mute_voices_( int ) { check( false ); } +void Gme_Info_::set_tempo_( double ) { } +blargg_err_t Gme_Info_::start_track_( int ) { return "Use full emulator for playback"; } +blargg_err_t Gme_Info_::play_( long, sample_t* ) { return "Use full emulator for playback"; }
--- a/src/console/Music_Emu.h Wed Nov 29 14:42:11 2006 -0800 +++ b/src/console/Music_Emu.h Thu Nov 30 19:54:33 2006 -0800 @@ -1,136 +1,212 @@ - -// Game music emulator interface base class +// Common interface to game music file emulators -// Game_Music_Emu 0.3.0 - +// Game_Music_Emu 0.5.1 #ifndef MUSIC_EMU_H #define MUSIC_EMU_H #include "blargg_common.h" -#include "abstract_file.h" +#include "Gme_File.h" class Multi_Buffer; -class Music_Emu { +struct Music_Emu : public Gme_File { public: - - // Initialize emulator with specified sample rate. Currently should only be - // called once. - virtual blargg_err_t set_sample_rate( long sample_rate ) = 0; - - // Load music file - blargg_err_t load_file( const char* path ); - - // Start a track, where 0 is the first track. Might un-mute any muted voices. - virtual void start_track( int ) = 0; +// Basic functionality (see Gme_File.h for file loading/track info functions) + + // Set output sample rate. Must be called only once before loading file. + blargg_err_t set_sample_rate( long sample_rate ); - // Generate 'count' samples info 'buf'. Output is in stereo unless using custom - // buffer that generates mono output. - typedef short sample_t; - virtual void play( long count, sample_t* buf ) = 0; - -// Additional optional features + // Start a track, where 0 is the first track. Also clears warning string. + blargg_err_t start_track( int ); - // Request use of custom multichannel buffer. Only supported by "classic" emulators; - // on others this has no effect. Should be called only once *before* set_sample_rate(). - virtual void set_buffer( Multi_Buffer* ) { } + // Generate 'count' samples info 'buf'. Output is in stereo. Any emulation + // errors set warning string, and major errors also end track. + typedef short sample_t; + blargg_err_t play( long count, sample_t* buf ); - // Load music file data from custom source - virtual blargg_err_t load( Data_Reader& ) = 0; +// Informational // Sample rate sound is generated at long sample_rate() const; + // Index of current track or -1 if one hasn't been started + int current_track() const; + // Number of voices used by currently loaded file int voice_count() const; // Names of voices - virtual const char** voice_names() const; + const char** voice_names() const; + +// Track status/control + + // Number of milliseconds (1000 msec = 1 second) played since beginning of track + long tell() const; + + // Seek to new time in track. Seeking backwards or far forward can take a while. + blargg_err_t seek( long msec ); + + // Skip n samples + blargg_err_t skip( long n ); - // Mute voice n if bit n (1 << n) of mask is set - virtual void mute_voices( int mask ); + // True if a track has reached its end + bool track_ended() const; + + // 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 set_fade( long start_msec, long length_msec = 8000 ); + + // Disable automatic end-of-track detection and skipping of silence at beginning + void ignore_silence( bool disable = true ); + + // Info for current track + Gme_File::track_info; + blargg_err_t track_info( track_info_t* out ) const; - // Frequency equalizer parameters (see notes.txt) - struct equalizer_t { - double treble; // -50.0 = muffled, 0 = flat, +5.0 = extra-crisp - long bass; // 1 = full bass, 90 = average, 16000 = almost no bass - }; +// 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 set_tempo( double ); + + // Mute/unmute voice i, where voice 0 is first voice + void mute_voice( int index, bool mute = true ); + + // 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 mute_voices( int mask ); + + // Change overall output amplitude, where 1.0 results in minimal clamping. + // Must be called before set_sample_rate(). + void set_gain( double ); + + // Request use of custom multichannel buffer. Only supported by "classic" emulators; + // on others this has no effect. Should be called only once *before* set_sample_rate(). + virtual void set_buffer( Multi_Buffer* ) { } + +// Sound equalization (treble/bass) + + // Frequency equalizer parameters (see gme.txt) + // See gme.h for definition of struct gme_equalizer_t. + typedef gme_equalizer_t equalizer_t; // Current frequency equalizater parameters - const equalizer_t& equalizer() const; + equalizer_t const& equalizer() const; // Set frequency equalizer parameters - virtual void set_equalizer( equalizer_t const& ); + void set_equalizer( equalizer_t const& ); // Equalizer settings for TV speaker static equalizer_t const tv_eq; - // Number of tracks. Zero if file hasn't been loaded yet. - int track_count() const; - - // Skip 'count' samples - virtual void skip( long count ); - - // True if a track was started and has since ended. Currently only logged - // format tracks (VGM, GYM) without loop points have an ending. - bool track_ended() const; - - // Number of errors encountered while playing track due to undefined CPU - // instructions in emulated formats and undefined stream events in - // logged formats. - int error_count() const; - +public: Music_Emu(); - virtual ~Music_Emu(); - + ~Music_Emu(); protected: - typedef BOOST::uint8_t byte; - void set_voice_count( int n ) { voice_count_ = n; } - void set_track_count( int n ) { track_count_ = n; } - void set_track_ended( bool b = true ) { track_ended_ = b; } - void log_error() { error_count_++; } + void set_max_initial_silence( int n ) { max_initial_silence = n; } + void set_silence_lookahead( int n ) { silence_lookahead = n; } + void set_voice_count( int n ) { voice_count_ = n; } + void set_voice_names( const char* const* names ); + void set_track_ended() { emu_track_ended_ = true; } + double gain() const { return gain_; } + double tempo() const { return tempo_; } void remute_voices(); + + virtual blargg_err_t set_sample_rate_( long sample_rate ) = 0; + virtual void set_equalizer_( equalizer_t const& ) { }; + virtual void mute_voices_( int mask ) = 0; + virtual void set_tempo_( double ) = 0; + virtual blargg_err_t start_track_( int ) = 0; // tempo is set before this + virtual blargg_err_t play_( long count, sample_t* out ) = 0; + virtual blargg_err_t skip_( long count ); +protected: + virtual void unload(); + virtual void pre_load(); + virtual void post_load_(); private: - // noncopyable - Music_Emu( const Music_Emu& ); - Music_Emu& operator = ( const Music_Emu& ); - + // general equalizer_t equalizer_; - long sample_rate_; + int max_initial_silence; + const char** voice_names_; int voice_count_; int mute_mask_; - int track_count_; - int error_count_; - bool track_ended_; + double tempo_; + double gain_; + + long sample_rate_; + blargg_long msec_to_samples( blargg_long msec ) const; + + // track-specific + 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_; + void clear_track_vars(); + void end_track_if_error( blargg_err_t ); + + // fading + blargg_long fade_start; + int fade_step; + void handle_fade( long count, sample_t* out ); + + // 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 + enum { buf_size = 2048 }; + blargg_vector<sample_t> buf; + void fill_buf(); + void emu_play( long count, sample_t* out ); + + Multi_Buffer* effects_buffer; + friend Music_Emu* gme_new_emu( gme_type_t, long ); + friend void gme_set_stereo_depth( Music_Emu*, double ); }; -// Deprecated -typedef Data_Reader Emu_Reader; -typedef Std_File_Reader Emu_Std_Reader; -typedef Mem_File_Reader Emu_Mem_Reader; +// base class for info-only derivations +struct Gme_Info_ : Music_Emu +{ + virtual blargg_err_t set_sample_rate_( long sample_rate ); + virtual void set_equalizer_( equalizer_t const& ); + virtual void mute_voices_( int mask ); + virtual void set_tempo_( double ); + virtual blargg_err_t start_track_( int ); + virtual blargg_err_t play_( long count, sample_t* out ); + virtual void pre_load(); + virtual void post_load_(); +}; -inline int Music_Emu::error_count() const { return error_count_; } -inline int Music_Emu::voice_count() const { return voice_count_; } -inline int Music_Emu::track_count() const { return track_count_; } -inline bool Music_Emu::track_ended() const { return track_ended_; } -inline void Music_Emu::mute_voices( int mask ) { mute_mask_ = mask; } -inline void Music_Emu::remute_voices() { mute_voices( mute_mask_ ); } -inline const Music_Emu::equalizer_t& Music_Emu::equalizer() const { return equalizer_; } -inline void Music_Emu::set_equalizer( const equalizer_t& eq ) { equalizer_ = eq; } -inline long Music_Emu::sample_rate() const { return sample_rate_; } - -inline blargg_err_t Music_Emu::set_sample_rate( long r ) +inline blargg_err_t Music_Emu::track_info( track_info_t* out ) const { - assert( !sample_rate_ ); // sample rate can't be changed once set - sample_rate_ = r; - return blargg_success; + return track_info( out, current_track_ ); } -inline void Music_Emu::start_track( int track ) +inline long Music_Emu::sample_rate() const { return sample_rate_; } +inline const char** Music_Emu::voice_names() const { return voice_names_; } +inline int Music_Emu::voice_count() const { return voice_count_; } +inline int Music_Emu::current_track() const { return current_track_; } +inline bool Music_Emu::track_ended() const { return track_ended_; } +inline const Music_Emu::equalizer_t& Music_Emu::equalizer() const { return equalizer_; } + +inline void Music_Emu::set_tempo_( double t ) { tempo_ = t; } +inline void Music_Emu::remute_voices() { mute_voices( mute_mask_ ); } +inline void Music_Emu::ignore_silence( bool b ) { ignore_silence_ = b; } +inline blargg_err_t Music_Emu::start_track_( int ) { return 0; } + +inline void Music_Emu::set_voice_names( const char* const* names ) { - assert( sample_rate_ ); // set_sample_rate() must have been called first - track_ended_ = false; - error_count_ = 0; + // Intentional removal of const, so users don't have to remember obscure const in middle + voice_names_ = (const char**) names; +} + +inline void Music_Emu::mute_voices_( int ) { } + +inline void Music_Emu::set_gain( double g ) +{ + assert( !sample_rate() ); // you must set gain before setting sample rate + gain_ = g; } #endif -
--- a/src/console/Nes_Apu.cxx Wed Nov 29 14:42:11 2006 -0800 +++ b/src/console/Nes_Apu.cxx Thu Nov 30 19:54:33 2006 -0800 @@ -1,5 +1,4 @@ - -// Nes_Snd_Emu 0.1.7. http://www.slack.net/~ant/ +// Nes_Snd_Emu 0.1.8. http://www.slack.net/~ant/ #include "Nes_Apu.h" @@ -9,12 +8,12 @@ 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 */ +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_BEGIN +#include "blargg_source.h" int const amp_range = 15; @@ -22,8 +21,9 @@ square1( &square_synth ), square2( &square_synth ) { + tempo_ = 1.0; dmc.apu = this; - dmc.rom_reader = NULL; + dmc.prg_reader = NULL; irq_notifier_ = NULL; oscs [0] = &square1; @@ -37,10 +37,6 @@ reset( false ); } -Nes_Apu::~Nes_Apu() -{ -} - void Nes_Apu::treble_eq( const blip_eq_t& eq ) { square_synth.treble_eq( eq ); @@ -81,11 +77,18 @@ osc_output( i, buffer ); } +void Nes_Apu::set_tempo( double t ) +{ + tempo_ = t; + frame_period = (dmc.pal_mode ? 8314 : 7458); + if ( t != 1.0 ) + frame_period = (int) (frame_period / t) & ~1; // must be even +} + void Nes_Apu::reset( bool pal_mode, int initial_dmc_dac ) { - // to do: time pal frame periods exactly - frame_period = pal_mode ? 8314 : 7458; dmc.pal_mode = pal_mode; + set_tempo( tempo_ ); square1.reset(); square2.reset(); @@ -106,8 +109,10 @@ write_register( 0, addr, (addr & 3) ? 0x00 : 0x10 ); dmc.dac = initial_dmc_dac; - //if ( !dmc.nonlinear ) // to do: remove? - // dmc.last_amp = initial_dmc_dac; // prevent output transition + if ( !dmc.nonlinear ) + triangle.last_amp = 15; + if ( !dmc.nonlinear ) // TODO: remove? + dmc.last_amp = initial_dmc_dac; // prevent output transition } void Nes_Apu::irq_changed() @@ -177,8 +182,8 @@ switch ( frame++ ) { case 0: - if ( !(frame_mode & 0xc0) ) { - next_irq = time + frame_period * 4 + 1; + if ( !(frame_mode & 0xC0) ) { + next_irq = time + frame_period * 4 + 2; irq_flag = true; } // fall through @@ -191,11 +196,16 @@ square1.clock_sweep( -1 ); square2.clock_sweep( 0 ); + + // frame 2 is slightly shorter in mode 1 + if ( dmc.pal_mode && frame == 3 ) + frame_delay -= 2; break; case 1: - // frame 1 is slightly shorter - frame_delay -= 2; + // frame 1 is slightly shorter in mode 0 + if ( !dmc.pal_mode ) + frame_delay -= 2; break; case 3: @@ -203,7 +213,7 @@ // frame 3 is almost twice as long in mode 1 if ( frame_mode & 0x80 ) - frame_delay += frame_period - 6; + frame_delay += frame_period - (dmc.pal_mode ? 2 : 6); break; } @@ -215,9 +225,6 @@ } } -// to do: remove -static long abs_time; - template<class T> inline void zero_apu_osc( T* osc, nes_time_t time ) { @@ -233,8 +240,6 @@ if ( end_time > last_time ) run_until_( end_time ); - abs_time += end_time; - if ( dmc.nonlinear ) { zero_apu_osc( &square1, last_time ); @@ -253,11 +258,11 @@ if ( next_irq != no_irq ) { next_irq -= end_time; - assert( next_irq >= 0 ); + check( next_irq >= 0 ); } if ( dmc.next_irq != no_irq ) { dmc.next_irq -= end_time; - assert( dmc.next_irq >= 0 ); + check( dmc.next_irq >= 0 ); } if ( earliest_irq_ != no_irq ) { earliest_irq_ -= end_time; @@ -278,10 +283,10 @@ void Nes_Apu::write_register( nes_time_t time, nes_addr_t addr, int data ) { require( addr > 0x20 ); // addr must be actual address (i.e. 0x40xx) - require( (unsigned) data <= 0xff ); + require( (unsigned) data <= 0xFF ); // Ignore addresses outside range - if ( addr < start_addr || end_addr < addr ) + if ( unsigned (addr - start_addr) > end_addr - start_addr ) return; run_until_( time ); @@ -305,7 +310,7 @@ { // load length counter if ( (osc_enables >> osc_index) & 1 ) - osc->length_counter = length_table [(data >> 3) & 0x1f]; + osc->length_counter = length_table [(data >> 3) & 0x1F]; // reset square phase if ( osc_index < 2 ) @@ -354,7 +359,7 @@ frame = 1; frame_delay += frame_period; if ( irq_enabled ) - next_irq = time + frame_delay + frame_period * 3; + next_irq = time + frame_delay + frame_period * 3 + 1; } irq_changed(); @@ -373,11 +378,14 @@ run_until_( time ); - if ( irq_flag ) { + if ( irq_flag ) + { + result |= 0x40; irq_flag = false; irq_changed(); } + //dprintf( "%6d/%d Read $4015->$%02X\n", frame_delay, frame, result ); + return result; } -
--- a/src/console/Nes_Apu.h Wed Nov 29 14:42:11 2006 -0800 +++ b/src/console/Nes_Apu.h Thu Nov 30 19:54:33 2006 -0800 @@ -1,24 +1,21 @@ - // NES 2A03 APU sound chip emulator -// Nes_Snd_Emu 0.1.7 - +// Nes_Snd_Emu 0.1.8 #ifndef NES_APU_H #define NES_APU_H -typedef long nes_time_t; // CPU clock cycle count +#include "blargg_common.h" + +typedef blargg_long nes_time_t; // CPU clock cycle count typedef unsigned nes_addr_t; // 16-bit memory address #include "Nes_Oscs.h" -struct apu_snapshot_t; -class Nonlinear_Buffer; +struct apu_state_t; +class Nes_Buffer; class Nes_Apu { public: - Nes_Apu(); - ~Nes_Apu(); - // Set buffer to generate all sound into, or disable sound if NULL void output( Blip_Buffer* ); @@ -51,11 +48,14 @@ // Use PAL timing if pal_timing is true, otherwise use NTSC timing. // Set the DMC oscillator's initial DAC value to initial_dmc_dac without // any audible click. - void reset( bool pal_timing = false, int initial_dmc_dac = 0 ); + void reset( bool pal_mode = false, int initial_dmc_dac = 0 ); - // Save/load snapshot of exact emulation state - void save_snapshot( apu_snapshot_t* out ) const; - void load_snapshot( apu_snapshot_t const& ); + // Adjust frame period + void set_tempo( double ); + + // Save/load exact emulation state + void save_state( apu_state_t* out ) const; + void load_state( apu_state_t const& ); // Set overall volume (default is 1.0) void volume( double ); @@ -94,7 +94,9 @@ // accounted for (i.e. inserting CPU wait states). void run_until( nes_time_t ); -// End of public interface. +public: + Nes_Apu(); + BLARGG_DISABLE_NOTHROW private: friend class Nes_Nonlinearizer; void enable_nonlinear( double volume ); @@ -113,6 +115,7 @@ Nes_Triangle triangle; Nes_Dmc dmc; + double 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_; @@ -130,6 +133,9 @@ void irq_changed(); void state_restored(); void run_until_( nes_time_t ); + + // TODO: remove + friend class Nes_Core; }; inline void Nes_Apu::osc_output( int osc, Blip_Buffer* buf ) @@ -145,8 +151,8 @@ inline void Nes_Apu::dmc_reader( int (*func)( void*, nes_addr_t ), void* user_data ) { - dmc.rom_reader_data = user_data; - dmc.rom_reader = func; + dmc.prg_reader_data = user_data; + dmc.prg_reader = func; } inline void Nes_Apu::irq_notifier( void (*func)( void* user_data ), void* user_data ) @@ -171,4 +177,3 @@ inline nes_time_t Nes_Apu::next_dmc_read_time() const { return dmc.next_read_time(); } #endif -
--- a/src/console/Nes_Cpu.cxx Wed Nov 29 14:42:11 2006 -0800 +++ b/src/console/Nes_Cpu.cxx Thu Nov 30 19:54:33 2006 -0800 @@ -1,12 +1,11 @@ - -// Game_Music_Emu 0.3.0. http://www.slack.net/~ant/nes-emu/ +// Game_Music_Emu 0.5.1. http://www.slack.net/~ant/ #include "Nes_Cpu.h" -#include <string.h> +#include "blargg_endian.h" #include <limits.h> -#include "blargg_endian.h" +#define BLARGG_CPU_X86 1 /* 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 @@ -14,12 +13,34 @@ 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 */ +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 */ + +#ifdef BLARGG_ENABLE_OPTIMIZER + #include BLARGG_ENABLE_OPTIMIZER +#endif + +#define SYNC_TIME() (void) (s.time = s_time) +#define RELOAD_TIME() (void) (s_time = s.time) + +#include "nes_cpu_io.h" -#include BLARGG_SOURCE_BEGIN +#include "blargg_source.h" + +#ifndef CPU_DONE + #define CPU_DONE( cpu, time, result_out ) { result_out = -1; } +#endif + +#ifndef CPU_READ_PPU + #define CPU_READ_PPU( cpu, addr, out, time )\ + {\ + SYNC_TIME();\ + out = CPU_READ( cpu, addr, time );\ + RELOAD_TIME();\ + } +#endif #if BLARGG_NONPORTABLE #define PAGE_OFFSET( addr ) (addr) @@ -27,240 +48,220 @@ #define PAGE_OFFSET( addr ) ((addr) & (page_size - 1)) #endif -Nes_Cpu::Nes_Cpu() +inline void Nes_Cpu::set_code_page( int i, void const* p ) { - callback_data = NULL; - reset(); -} - -inline void Nes_Cpu::set_code_page( int i, uint8_t const* p ) -{ - code_map [i] = p - PAGE_OFFSET( i * page_size ); + state->code_map [i] = (uint8_t const*) p - PAGE_OFFSET( i * page_size ); } -void Nes_Cpu::reset( const void* unmapped_code_page, reader_t read, writer_t write ) +int const st_n = 0x80; +int const st_v = 0x40; +int const st_r = 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 Nes_Cpu::reset( void const* unmapped_page ) { - r.status = 0; - r.sp = 0; + check( state == &state_ ); + state = &state_; + r.status = st_i; + r.sp = 0xFF; r.pc = 0; - r.a = 0; - r.x = 0; - r.y = 0; + r.a = 0; + r.x = 0; + r.y = 0; + state_.time = 0; + state_.base = 0; + irq_time_ = future_nes_time; + end_time_ = future_nes_time; + error_count_ = 0; - clock_count = 0; - base_time = 0; - clock_limit = 0; - irq_time_ = LONG_MAX / 2 + 1; - end_time_ = LONG_MAX / 2 + 1; + assert( page_size == 0x800 ); // assumes this + set_code_page( page_count, unmapped_page ); + map_code( 0x2000, 0xE000, unmapped_page, true ); + map_code( 0x0000, 0x2000, low_mem, true ); - for ( int i = 0; i < page_count + 1; i++ ) - { - set_code_page( i, (uint8_t*) unmapped_code_page ); - data_reader [i] = read; - data_writer [i] = write; - } + blargg_verify_byte_order(); } -void Nes_Cpu::map_code( nes_addr_t start, unsigned long size, const void* data ) +void Nes_Cpu::map_code( nes_addr_t start, unsigned size, void const* data, bool mirror ) { // address range must begin and end on page boundaries require( start % page_size == 0 ); require( size % page_size == 0 ); require( start + size <= 0x10000 ); - unsigned first_page = start / page_size; - for ( unsigned i = size / page_size; i--; ) - set_code_page( first_page + i, (uint8_t*) data + i * page_size ); -} - -void Nes_Cpu::set_reader( nes_addr_t start, unsigned long size, reader_t func ) -{ - // address range must begin and end on page boundaries - require( start % page_size == 0 ); - require( size % page_size == 0 ); - require( start + size <= 0x10000 + page_size ); - - unsigned first_page = start / page_size; - for ( unsigned i = size / page_size; i--; ) - data_reader [first_page + i] = func; -} - -void Nes_Cpu::set_writer( nes_addr_t start, unsigned long size, writer_t func ) -{ - // address range must begin and end on page boundaries - require( start % page_size == 0 ); - require( size % page_size == 0 ); - require( start + size <= 0x10000 + page_size ); - - unsigned first_page = start / page_size; - for ( unsigned i = size / page_size; i--; ) - data_writer [first_page + i] = func; -} - -// Note: 'addr' is evaulated more than once in the following macros, so it -// must not contain side-effects. - -#define READ( addr ) (data_reader [(addr) >> page_bits]( callback_data, addr )) -#define WRITE( addr, data ) (data_writer [(addr) >> page_bits]( callback_data, addr, data )) - -#define READ_LOW( addr ) (low_mem [int (addr)]) -#define WRITE_LOW( addr, data ) (void) (READ_LOW( addr ) = (data)) - -#define READ_PROG( addr ) (code_map [(addr) >> page_bits] [PAGE_OFFSET( addr )]) -#define READ_PROG16( addr ) GET_LE16( &READ_PROG( 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 )) - -int Nes_Cpu::read( nes_addr_t addr ) -{ - return READ( addr ); -} - -void Nes_Cpu::write( nes_addr_t addr, int value ) -{ - WRITE( addr, value ); + unsigned page = start / page_size; + for ( unsigned n = size / page_size; n; --n ) + { + set_code_page( page++, data ); + if ( !mirror ) + data = (char const*) data + page_size; + } } -#ifndef NES_CPU_GLUE_ONLY +#define TIME (s_time + s.base) +#define READ_LIKELY_PPU( addr, out ) {CPU_READ_PPU( this, (addr), out, TIME );} +#define READ( addr ) CPU_READ( this, (addr), TIME ) +#define WRITE( addr, data ) {CPU_WRITE( this, (addr), (data), TIME );} +#define READ_LOW( addr ) (low_mem [int (addr)]) +#define WRITE_LOW( addr, data ) (void) (READ_LOW( addr ) = (data)) +#define READ_PROG( addr ) (s.code_map [(addr) >> page_bits] [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 )) -static const unsigned char clock_table [256] = { -// 0 1 2 3 4 5 6 7 8 9 A B C D E F - 7,6,2,8,3,3,5,5,3,2,2,2,4,4,6,6,// 0 - 3,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7,// 1 - 6,6,2,8,3,3,5,5,4,2,2,2,4,4,6,6,// 2 - 3,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7,// 3 - 6,6,2,8,3,3,5,5,3,2,2,2,3,4,6,6,// 4 - 3,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7,// 5 - 6,6,2,8,3,3,5,5,4,2,2,2,5,4,6,6,// 6 - 3,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7,// 7 - 2,6,2,6,3,3,3,3,2,2,2,2,4,4,4,4,// 8 - 3,6,2,6,4,4,4,4,2,5,2,5,5,5,5,5,// 9 - 2,6,2,6,3,3,3,3,2,2,2,2,4,4,4,4,// A - 3,5,2,5,4,4,4,4,2,4,2,4,4,4,4,4,// B - 2,6,2,8,3,3,5,5,2,2,2,2,4,4,6,6,// C - 3,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7,// D - 2,6,3,8,3,3,5,5,2,2,2,2,4,4,6,6,// E - 3,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7 // F -}; +// even on x86, using short and unsigned char was slower +typedef int fint16; +typedef unsigned fuint16; +typedef unsigned fuint8; -#include BLARGG_ENABLE_OPTIMIZER - -Nes_Cpu::result_t Nes_Cpu::run( nes_time_t end ) +bool Nes_Cpu::run( nes_time_t end_time ) { - set_end_time( end ); - - volatile result_t result = result_cycles; - -#if BLARGG_CPU_POWERPC - // cache commonly-used values in registers - long clock_count = this->clock_count; - writer_t* const data_writer = this->data_writer; - reader_t* const data_reader = this->data_reader; - uint8_t* const low_mem = this->low_mem; -#endif + set_end_time( end_time ); + state_t s = this->state_; + this->state = &s; + // even on x86, using s.time in place of s_time was slower + fint16 s_time = s.time; // registers - unsigned pc = r.pc; - int sp; + fuint16 pc = r.pc; + fuint8 a = r.a; + fuint8 x = r.x; + fuint8 y = r.y; + fuint16 sp; SET_SP( r.sp ); - int a = r.a; - int x = r.x; - int y = r.y; // status flags - - const int st_n = 0x80; - const int st_v = 0x40; - const int st_r = 0x20; - const int st_b = 0x10; - const int st_d = 0x08; - const int st_i = 0x04; - const int st_z = 0x02; - const int st_c = 0x01; - - #define IS_NEG (nz & 0x880) + #define IS_NEG (nz & 0x8080) #define CALC_STATUS( out ) do { \ out = status & (st_v | st_d | st_i); \ - out |= (c >> 8) & st_c; \ - if ( IS_NEG ) out |= st_n; \ + out |= ((nz >> 8) | nz) & st_n; \ + out |= c >> 8 & st_c; \ if ( !(nz & 0xFF) ) out |= st_z; \ - } while ( false ) + } while ( 0 ) #define SET_STATUS( in ) do { \ status = in & (st_v | st_d | st_i); \ - c = in << 8; \ - nz = (in << 4) & 0x800; \ + nz = in << 8; \ + c = nz; \ nz |= ~in & st_z; \ - } while ( false ) + } while ( 0 ) - int status; - int c; // carry set if (c & 0x100) != 0 - int nz; // Z set if (nz & 0xff) == 0, N set if (nz & 0x880) != 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 { - int temp = r.status; + fuint8 temp = r.status; SET_STATUS( temp ); } goto loop; dec_clock_loop: - clock_count--; + s_time--; loop: - assert( unsigned (GET_SP()) < 0x100 ); - assert( unsigned (a) < 0x100 ); - assert( unsigned (x) < 0x100 ); - assert( unsigned (y) < 0x100 ); + check( (unsigned) GET_SP() < 0x100 ); + check( (unsigned) a < 0x100 ); + check( (unsigned) x < 0x100 ); + check( (unsigned) y < 0x100 ); + check( -32768 <= s_time && s_time < 32767 ); + + uint8_t const* instr = s.code_map [pc >> page_bits]; + fuint8 opcode; - uint8_t const* page = code_map [pc >> page_bits]; - unsigned opcode = page [PAGE_OFFSET( pc )]; - unsigned data = page [PAGE_OFFSET( pc ) + 1]; - pc++; + // TODO: eliminate this special case + #if BLARGG_NONPORTABLE + opcode = instr [pc]; + pc++; + instr += pc; + #else + instr += PAGE_OFFSET( pc ); + opcode = *instr++; + pc++; + #endif - // page crossing - //check( opcode == 0x60 || &READ_PROG( pc ) == &page [PAGE_OFFSET( pc )] ); - - if ( clock_count >= clock_limit ) - goto stop; + static uint8_t const clock_table [256] = + {// 0 1 2 3 4 5 6 7 8 9 A B C D E F + 0,6,2,8,3,3,5,5,3,2,2,2,4,4,6,6,// 0 + 3,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7,// 1 + 6,6,2,8,3,3,5,5,4,2,2,2,4,4,6,6,// 2 + 3,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7,// 3 + 6,6,2,8,3,3,5,5,3,2,2,2,3,4,6,6,// 4 + 3,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7,// 5 + 6,6,2,8,3,3,5,5,4,2,2,2,5,4,6,6,// 6 + 3,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7,// 7 + 2,6,2,6,3,3,3,3,2,2,2,2,4,4,4,4,// 8 + 3,6,2,6,4,4,4,4,2,5,2,5,5,5,5,5,// 9 + 2,6,2,6,3,3,3,3,2,2,2,2,4,4,4,4,// A + 3,5,2,5,4,4,4,4,2,4,2,4,4,4,4,4,// B + 2,6,2,8,3,3,5,5,2,2,2,2,4,4,6,6,// C + 3,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7,// D + 2,6,2,8,3,3,5,5,2,2,2,2,4,4,6,6,// E + 3,5,0,8,4,4,6,6,2,4,2,7,4,4,7,7 // F + }; // 0x00 was 7 and 0xF2 was 2 - clock_count += clock_table [opcode]; + fuint16 data; - #if BLARGG_CPU_POWERPC - this->clock_count = clock_count; - #endif +#if !BLARGG_CPU_X86 + if ( s_time >= 0 ) + goto out_of_time; + s_time += clock_table [opcode]; + + data = *instr; switch ( opcode ) { +#else + + data = clock_table [opcode]; + if ( (s_time += data) >= 0 ) + goto possibly_out_of_time; +almost_out_of_time: + + data = *instr; + + switch ( opcode ) + { +possibly_out_of_time: + if ( s_time < (int) data ) + goto almost_out_of_time; + s_time -= data; + goto out_of_time; +#endif // Macros -#define ADD_PAGE (pc++, data += 0x100 * READ_PROG( pc )); -#define GET_ADDR() READ_PROG16( pc ) +#define GET_MSB() (instr [1]) +#define ADD_PAGE (pc++, data += 0x100 * GET_MSB()); +#define GET_ADDR() GET_LE16( instr ) -#define HANDLE_PAGE_CROSSING( lsb ) clock_count += (lsb) >> 8; +#define NO_PAGE_CROSSING( lsb ) +#define HANDLE_PAGE_CROSSING( lsb ) s_time += (lsb) >> 8; #define INC_DEC_XY( reg, n ) reg = uint8_t (nz = reg + n); goto loop; -#define IND_Y { \ - int temp = READ_LOW( data ) + y; \ - data = temp + 0x100 * READ_LOW( uint8_t (data + 1) ); \ - HANDLE_PAGE_CROSSING( temp ); \ +#define IND_Y( cross, out ) { \ + fuint16 temp = READ_LOW( data ) + y; \ + out = temp + 0x100 * READ_LOW( uint8_t (data + 1) ); \ + cross( temp ); \ } -#define IND_X { \ - int temp = data + x; \ - data = 0x100 * READ_LOW( uint8_t (temp + 1) ) + READ_LOW( uint8_t (temp) ); \ +#define IND_X( out ) { \ + fuint16 temp = data + x; \ + out = 0x100 * READ_LOW( uint8_t (temp + 1) ) + READ_LOW( uint8_t (temp) ); \ } -#define ARITH_ADDR_MODES( op ) \ +#define ARITH_ADDR_MODES( op ) \ case op - 0x04: /* (ind,x) */ \ - IND_X \ + IND_X( data ) \ goto ptr##op; \ case op + 0x0C: /* (ind),y */ \ - IND_Y \ + IND_Y( HANDLE_PAGE_CROSSING, data ) \ goto ptr##op; \ case op + 0x10: /* zp,X */ \ data = uint8_t (data + x); \ @@ -277,25 +278,29 @@ case op + 0x08: /* abs */ \ ADD_PAGE \ ptr##op: \ + SYNC_TIME(); \ data = READ( data ); \ + RELOAD_TIME(); \ case op + 0x04: /* imm */ \ imm##op: \ -#define BRANCH( cond ) \ -{ \ - pc++; \ - int offset = (BOOST::int8_t) data; \ - int extra_clock = (pc & 0xff) + offset; \ - if ( !(cond) ) goto dec_clock_loop; \ - pc += offset; \ - clock_count += (extra_clock >> 8) & 1; \ - goto loop; \ +#define BRANCH( cond ) \ +{ \ + fint16 offset = (BOOST::int8_t) data; \ + fuint16 extra_clock = (++pc & 0xFF) + offset;\ + if ( !(cond) ) goto dec_clock_loop; \ + pc += offset; \ + s_time += extra_clock >> 8 & 1; \ + goto loop; \ } // Often-Used case 0xB5: // LDA zp,x - data = uint8_t (data + x); + a = nz = READ_LOW( uint8_t (data + x) ); + pc++; + goto loop; + case 0xA5: // LDA zp a = nz = READ_LOW( data ); pc++; @@ -305,8 +310,8 @@ BRANCH( (uint8_t) nz ); case 0x20: { // JSR - int temp = pc + 1; - pc = READ_PROG16( pc ); + fuint16 temp = pc + 1; + pc = GET_ADDR(); WRITE_LOW( 0x100 | (sp - 1), temp >> 8 ); sp = (sp - 2) | 0x100; WRITE_LOW( sp, temp ); @@ -314,10 +319,11 @@ } case 0x4C: // JMP abs - pc = READ_PROG16( pc ); + pc = GET_ADDR(); goto loop; - case 0xE8: INC_DEC_XY( x, 1 ) // INX + case 0xE8: // INX + INC_DEC_XY( x, 1 ) case 0x10: // BPL BRANCH( !IS_NEG ) @@ -326,7 +332,7 @@ nz = a - data; pc++; c = ~nz; - nz &= 0xff; + nz &= 0xFF; goto loop; case 0x30: // BMI @@ -342,56 +348,131 @@ WRITE_LOW( data, a ); goto loop; - case 0xC8: INC_DEC_XY( y, 1 ) // INY + case 0xC8: // INY + INC_DEC_XY( y, 1 ) case 0xA8: // TAY - y = a; - case 0x98: // TYA - a = nz = y; + y = a; + nz = a; goto loop; - case 0xB9: // LDA abs,Y - data += y; - goto lda_ind_common; + case 0x98: // TYA + a = y; + nz = y; + goto loop; - case 0xBD: // LDA abs,X - data += x; - lda_ind_common: - HANDLE_PAGE_CROSSING( data ); - case 0xAD: // LDA abs - ADD_PAGE - lda_ptr: - a = nz = READ( data ); - pc++; + case 0xAD:{// LDA abs + unsigned addr = GET_ADDR(); + pc += 2; + READ_LIKELY_PPU( addr, nz ); + a = nz; goto loop; + } case 0x60: // RTS pc = 1 + READ_LOW( sp ); - pc += READ_LOW( 0x100 | (sp - 0xff) ) * 0x100; - sp = (sp - 0xfe) | 0x100; + pc += 0x100 * READ_LOW( 0x100 | (sp - 0xFF) ); + sp = (sp - 0xFE) | 0x100; goto loop; - + + { + fuint16 addr; + case 0x99: // STA abs,Y - data += y; - goto sta_ind_common; + addr = y + GET_ADDR(); + pc += 2; + if ( addr <= 0x7FF ) + { + WRITE_LOW( addr, a ); + goto loop; + } + goto sta_ptr; - case 0x9D: // STA abs,X - data += x; - sta_ind_common: - HANDLE_PAGE_CROSSING( data ); case 0x8D: // STA abs - ADD_PAGE + addr = GET_ADDR(); + pc += 2; + if ( addr <= 0x7FF ) + { + WRITE_LOW( addr, a ); + goto loop; + } + goto sta_ptr; + + case 0x9D: // STA abs,X (slightly more common than STA abs) + addr = x + GET_ADDR(); + pc += 2; + if ( addr <= 0x7FF ) + { + WRITE_LOW( addr, a ); + goto loop; + } sta_ptr: + SYNC_TIME(); + WRITE( addr, a ); + RELOAD_TIME(); + goto loop; + + case 0x91: // STA (ind),Y + IND_Y( NO_PAGE_CROSSING, addr ) pc++; - WRITE( data, a ); - goto loop; + goto sta_ptr; + + case 0x81: // STA (ind,X) + IND_X( addr ) + pc++; + goto sta_ptr; + + } case 0xA9: // LDA #imm pc++; - a = data; + a = data; nz = data; goto loop; + // common read instructions + { + fuint16 addr; + + case 0xA1: // LDA (ind,X) + IND_X( addr ) + pc++; + goto a_nz_read_addr; + + case 0xB1:// LDA (ind),Y + addr = READ_LOW( data ) + y; + HANDLE_PAGE_CROSSING( addr ); + addr += 0x100 * READ_LOW( (uint8_t) (data + 1) ); + pc++; + a = nz = READ_PROG( addr ); + if ( (addr ^ 0x8000) <= 0x9FFF ) + goto loop; + goto a_nz_read_addr; + + case 0xB9: // LDA abs,Y + HANDLE_PAGE_CROSSING( data + y ); + addr = GET_ADDR() + y; + pc += 2; + a = nz = READ_PROG( addr ); + if ( (addr ^ 0x8000) <= 0x9FFF ) + goto loop; + goto a_nz_read_addr; + + case 0xBD: // LDA abs,X + HANDLE_PAGE_CROSSING( data + x ); + addr = GET_ADDR() + x; + pc += 2; + a = nz = READ_PROG( addr ); + if ( (addr ^ 0x8000) <= 0x9FFF ) + goto loop; + a_nz_read_addr: + SYNC_TIME(); + a = nz = READ( addr ); + RELOAD_TIME(); + goto loop; + + } + // Branch case 0x50: // BVC @@ -442,30 +523,15 @@ nz = data; goto loop; - case 0xB1: // LDA (ind),Y - IND_Y - goto lda_ptr; - - case 0xA1: // LDA (ind,X) - IND_X - goto lda_ptr; - - case 0x91: // STA (ind),Y - IND_Y - goto sta_ptr; - - case 0x81: // STA (ind,X) - IND_X - goto sta_ptr; - case 0xBC: // LDY abs,X data += x; HANDLE_PAGE_CROSSING( data ); case 0xAC:{// LDY abs - pc++; - unsigned addr = data + 0x100 * READ_PROG( pc ); - pc++; + unsigned addr = data + 0x100 * GET_MSB(); + pc += 2; + SYNC_TIME(); y = nz = READ( addr ); + RELOAD_TIME(); goto loop; } @@ -473,15 +539,16 @@ data += y; HANDLE_PAGE_CROSSING( data ); case 0xAE:{// LDX abs - pc++; - unsigned addr = data + 0x100 * READ_PROG( pc ); - pc++; + unsigned addr = data + 0x100 * GET_MSB(); + pc += 2; + SYNC_TIME(); x = nz = READ( addr ); + RELOAD_TIME(); goto loop; } { - int temp; + fuint8 temp; case 0x8C: // STY abs temp = y; goto store_abs; @@ -490,8 +557,15 @@ temp = x; store_abs: unsigned addr = GET_ADDR(); + pc += 2; + if ( addr <= 0x7FF ) + { + WRITE_LOW( addr, temp ); + goto loop; + } + SYNC_TIME(); WRITE( addr, temp ); - pc += 2; + RELOAD_TIME(); goto loop; } @@ -500,7 +574,9 @@ case 0xEC:{// CPX abs unsigned addr = GET_ADDR(); pc++; + SYNC_TIME(); data = READ( addr ); + RELOAD_TIME(); goto cpx_data; } @@ -511,13 +587,15 @@ nz = x - data; pc++; c = ~nz; - nz &= 0xff; + nz &= 0xFF; goto loop; case 0xCC:{// CPY abs unsigned addr = GET_ADDR(); pc++; + SYNC_TIME(); data = READ( addr ); + RELOAD_TIME(); goto cpy_data; } @@ -528,7 +606,7 @@ nz = y - data; pc++; c = ~nz; - nz &= 0xff; + nz &= 0xFF; goto loop; // Logical @@ -550,20 +628,24 @@ case 0x2C:{// BIT abs unsigned addr = GET_ADDR(); - pc++; - nz = READ( addr ); - goto bit_common; + pc += 2; + status &= ~st_v; + READ_LIKELY_PPU( addr, nz ); + status |= nz & st_v; + if ( a & nz ) + goto loop; + nz <<= 8; // result must be zero, even if N bit is set + goto loop; } case 0x24: // BIT zp nz = READ_LOW( data ); - bit_common: pc++; status &= ~st_v; status |= nz & st_v; - // if result is zero, might also be negative, so use secondary N bit - if ( !(a & nz) ) - nz = (nz << 4) & 0x800; + if ( a & nz ) + goto loop; + nz <<= 8; // result must be zero, even if N bit is set goto loop; // Add/subtract @@ -575,10 +657,10 @@ ARITH_ADDR_MODES( 0x65 ) // ADC adc_imm: { - int carry = (c >> 8) & 1; - int ov = (a ^ 0x80) + carry + (BOOST::int8_t) data; // sign-extend + fint16 carry = c >> 8 & 1; + fint16 ov = (a ^ 0x80) + carry + (BOOST::int8_t) data; // sign-extend status &= ~st_v; - status |= (ov >> 2) & 0x40; + status |= ov >> 2 & 0x40; c = nz = a + data + carry; pc++; a = (uint8_t) nz; @@ -590,7 +672,7 @@ case 0x4A: // LSR A c = 0; case 0x6A: // ROR A - nz = (c >> 1) & 0x80; // could use bit insert macro here + nz = c >> 1 & 0x80; c = a << 8; nz |= a >> 1; a = nz; @@ -604,13 +686,27 @@ case 0x2A: { // ROL A nz = a << 1; - int temp = (c >> 8) & 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 + SYNC_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; @@ -621,33 +717,20 @@ c = 0; case 0x2E: // ROL abs rol_abs: - HANDLE_PAGE_CROSSING( data ); ADD_PAGE - nz = (c >> 8) & 1; + nz = c >> 8 & 1; + SYNC_TIME(); nz |= (c = READ( data ) << 1); rotate_common: pc++; WRITE( data, (uint8_t) nz ); + RELOAD_TIME(); goto loop; case 0x7E: // ROR abs,X data += x; goto ror_abs; - case 0x5E: // LSR abs,X - data += x; - case 0x4E: // LSR abs - c = 0; - case 0x6E: // ROR abs - ror_abs: { - HANDLE_PAGE_CROSSING( data ); - ADD_PAGE - int temp = READ( data ); - nz = ((c >> 1) & 0x80) | (temp >> 1); - c = temp << 8; - goto rotate_common; - } - case 0x76: // ROR zp,x data = uint8_t (data + x); goto ror_zp; @@ -659,7 +742,7 @@ case 0x66: // ROR zp ror_zp: { int temp = READ_LOW( data ); - nz = ((c >> 1) & 0x80) | (temp >> 1); + nz = (c >> 1 & 0x80) | (temp >> 1); c = temp << 8; goto write_nz_zp; } @@ -674,15 +757,17 @@ c = 0; case 0x26: // ROL zp rol_zp: - nz = (c >> 8) & 1; + nz = c >> 8 & 1; nz |= (c = READ_LOW( data ) << 1); goto write_nz_zp; // Increment/decrement - case 0xCA: INC_DEC_XY( x, -1 ) // DEX + case 0xCA: // DEX + INC_DEC_XY( x, -1 ) - case 0x88: INC_DEC_XY( y, -1 ) // DEY + case 0x88: // DEY + INC_DEC_XY( y, -1 ) case 0xF6: // INC zp,x data = uint8_t (data + x); @@ -693,7 +778,7 @@ case 0xD6: // DEC zp,x data = uint8_t (data + x); case 0xC6: // DEC zp - nz = -1; + nz = (unsigned) -1; add_nz_zp: nz += READ_LOW( data ); write_nz_zp: @@ -702,7 +787,6 @@ goto loop; case 0xFE: // INC abs,x - HANDLE_PAGE_CROSSING( data + x ); data = x + GET_ADDR(); goto inc_ptr; @@ -713,26 +797,31 @@ goto inc_common; case 0xDE: // DEC abs,x - HANDLE_PAGE_CROSSING( data + x ); data = x + GET_ADDR(); goto dec_ptr; case 0xCE: // DEC abs data = GET_ADDR(); dec_ptr: - nz = -1; + nz = (unsigned) -1; inc_common: + SYNC_TIME(); nz += READ( data ); pc += 2; WRITE( data, (uint8_t) nz ); + RELOAD_TIME(); goto loop; // Transfer case 0xAA: // TAX - x = a; + x = a; + nz = a; + goto loop; + case 0x8A: // TXA - a = nz = x; + a = x; + nz = x; goto loop; case 0x9A: // TXS @@ -751,74 +840,62 @@ case 0x68: // PLA a = nz = READ_LOW( sp ); - sp = (sp - 0xff) | 0x100; + sp = (sp - 0xFF) | 0x100; goto loop; - case 0x40: // RTI - { - int 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 ); - } - if ( !((data ^ status) & st_i) ) - goto loop; // I flag didn't change - i_flag_changed: - //dprintf( "%6d %s\n", time(), (status & st_i ? "SEI" : "CLI") ); + 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 ); + if ( !((data ^ status) & st_i) ) goto loop; // I flag didn't change this->r.status = status; // update externally-visible I flag - // update clock_limit based on modified I flag - clock_limit = end_time_; - if ( end_time_ <= irq_time_ ) - goto loop; - if ( status & st_i ) - goto loop; - clock_limit = irq_time_; + blargg_long delta = s.base - irq_time_; + if ( delta <= 0 ) goto loop; + if ( status & st_i ) goto loop; + s_time += delta; + s.base = irq_time_; goto loop; + } case 0x28:{// PLP - int temp = READ_LOW( sp ); - sp = (sp - 0xff) | 0x100; - data = status; + fuint8 temp = READ_LOW( sp ); + sp = (sp - 0xFF) | 0x100; + fuint8 changed = status ^ temp; SET_STATUS( temp ); - if ( !((data ^ status) & st_i) ) + if ( !(changed & st_i) ) goto loop; // I flag didn't change - if ( !(status & st_i) ) - goto handle_cli; - goto handle_sei; + if ( status & st_i ) + goto handle_sei; + goto handle_cli; } case 0x08: { // PHP - int temp; + fuint8 temp; CALC_STATUS( temp ); - PUSH( temp | st_b | st_r ); + PUSH( temp | (st_b | st_r) ); goto loop; } - case 0x6C: // JMP (ind) + case 0x6C:{// JMP (ind) data = GET_ADDR(); - pc = READ( data ); - pc |= READ( (data & 0xff00) | ((data + 1) & 0xff) ) << 8; + check( unsigned (data - 0x2000) >= 0x4000 ); // ensure it's outside I/O space + uint8_t const* page = s.code_map [data >> page_bits]; + pc = page [PAGE_OFFSET( data )]; + data = (data & 0xFF00) | ((data + 1) & 0xFF); + pc |= page [PAGE_OFFSET( data )] << 8; goto loop; + } - case 0x00: { // BRK - pc++; - WRITE_LOW( 0x100 | (sp - 1), pc >> 8 ); - WRITE_LOW( 0x100 | (sp - 2), pc ); - int temp; - CALC_STATUS( temp ); - sp = (sp - 3) | 0x100; - WRITE_LOW( sp, temp | st_b | st_r ); - pc = READ_PROG16( 0xFFFE ); - status |= st_i; - goto i_flag_changed; - } + case 0x00: // BRK + goto handle_brk; // Flags case 0x38: // SEC - c = ~0; + c = (unsigned) ~0; goto loop; case 0x18: // CLC @@ -841,110 +918,165 @@ if ( !(status & st_i) ) goto loop; status &= ~st_i; - handle_cli: - //dprintf( "%6d CLI\n", time() ); + handle_cli: { + //dprintf( "CLI at %d\n", TIME ); this->r.status = status; // update externally-visible I flag - if ( clock_count < end_time_ ) + blargg_long delta = s.base - irq_time_; + if ( delta <= 0 ) { - assert( clock_limit == end_time_ ); - if ( end_time_ <= irq_time_ ) - goto loop; // irq is later - if ( clock_count >= irq_time_ ) - irq_time_ = clock_count + 1; // delay IRQ until after next instruction - clock_limit = irq_time_; + if ( TIME < irq_time_ ) + goto loop; + goto delayed_cli; + } + s.base = irq_time_; + s_time += delta; + if ( s_time < 0 ) + goto loop; + + if ( delta >= s_time + 1 ) + { + s.base += s_time + 1; + s_time = -1; goto loop; } - // execution is stopping now, so delayed CLI must be handled by caller - result = result_cli; - goto end; + // TODO: implement + delayed_cli: + dprintf( "Delayed CLI not emulated\n" ); + goto loop; + } + case 0x78: // SEI if ( status & st_i ) goto loop; status |= st_i; - handle_sei: - //dprintf( "%6d SEI\n", time() ); + handle_sei: { this->r.status = status; // update externally-visible I flag - clock_limit = end_time_; - if ( clock_count < irq_time_ ) + blargg_long delta = s.base - end_time_; + s.base = end_time_; + s_time += delta; + if ( s_time < 0 ) goto loop; - result = result_sei; // IRQ will occur now, even though I flag is set - goto end; - -// Undocumented - - case 0x0C: case 0x1C: case 0x3C: case 0x5C: case 0x7C: case 0xDC: case 0xFC: // SKW + + dprintf( "Delayed SEI not emulated\n" ); + goto loop; + } + +// Unofficial + + // SKW - Skip word + case 0x1C: case 0x3C: case 0x5C: case 0x7C: case 0xDC: case 0xFC: + HANDLE_PAGE_CROSSING( data + x ); + case 0x0C: pc++; - case 0x74: case 0x04: case 0x14: case 0x34: case 0x44: case 0x54: case 0x64: // SKB + // SKB - Skip byte + case 0x74: case 0x04: case 0x14: case 0x34: case 0x44: case 0x54: case 0x64: case 0x80: case 0x82: case 0x89: case 0xC2: case 0xD4: case 0xE2: case 0xF4: pc++; - case 0xEA: case 0x1A: case 0x3A: case 0x5A: case 0x7A: case 0xDA: case 0xFA: // NOP + goto loop; + + // NOP + case 0xEA: case 0x1A: case 0x3A: case 0x5A: case 0x7A: case 0xDA: case 0xFA: goto loop; -// Unimplemented - - case page_wrap_opcode: // HLT - if ( pc > 0x10000 ) + case bad_opcode: // HLT + pc--; + if ( pc > 0xFFFF ) { // handle wrap-around (assumes caller has put page of HLT at 0x10000) - pc = (pc - 1) & 0xffff; - clock_count -= 2; + pc &= 0xFFFF; goto loop; } - // fall through - case 0x02: case 0x12: case 0x22: case 0x32: // HLT - case 0x42: case 0x52: case 0x62: case 0x72: - case 0x92: case 0xB2: case 0xD2: - case 0x9B: // TAS - case 0x9C: // SAY - case 0x9E: // XAS - case 0x93: // AXA - case 0x9F: // AXA - case 0x0B: // ANC - case 0x2B: // ANC - case 0xBB: // LAS - case 0x4B: // ALR - case 0x6B: // AAR - case 0x8B: // XAA - case 0xAB: // OAL - case 0xCB: // SAX - case 0x83: case 0x87: case 0x8F: case 0x97: // AXS - case 0xA3: case 0xA7: case 0xAF: case 0xB3: case 0xB7: case 0xBF: // LAX - case 0xE3: case 0xE7: case 0xEF: case 0xF3: case 0xF7: case 0xFB: case 0xFF: // INS - case 0xC3: case 0xC7: case 0xCF: case 0xD3: case 0xD7: case 0xDB: case 0xDF: // DCM - case 0x63: case 0x67: case 0x6F: case 0x73: case 0x77: case 0x7B: case 0x7F: // RRA - case 0x43: case 0x47: case 0x4F: case 0x53: case 0x57: case 0x5B: case 0x5F: // LSE - case 0x23: case 0x27: case 0x2F: case 0x33: case 0x37: case 0x3B: case 0x3F: // RLA - case 0x03: case 0x07: case 0x0F: case 0x13: case 0x17: case 0x1B: case 0x1F: // ASO - result = result_badop; + case 0x02: case 0x12: case 0x22: case 0x32: case 0x42: case 0x52: + case 0x62: case 0x72: case 0x92: case 0xB2: case 0xD2: goto stop; + +// Unimplemented + + case 0xFF: // force 256-entry jump table for optimization purposes + c |= 1; + default: + check( (unsigned) opcode <= 0xFF ); + // skip over proper number of bytes + static unsigned char const illop_lens [8] = { + 0x40, 0x40, 0x40, 0x80, 0x40, 0x40, 0x80, 0xA0 + }; + fuint8 opcode = instr [-1]; + fint16 len = illop_lens [opcode >> 2 & 7] >> (opcode << 1 & 6) & 3; + if ( opcode == 0x9C ) + len = 2; + pc += len; + error_count_++; + + if ( (opcode >> 4) == 0x0B ) + { + if ( opcode == 0xB3 ) + data = READ_LOW( data ); + if ( opcode != 0xB7 ) + HANDLE_PAGE_CROSSING( data + y ); + } + goto loop; + } + assert( false ); + + int result_; +handle_brk: + pc++; + result_ = 4; + +interrupt: + { + s_time += 7; + + WRITE_LOW( 0x100 | (sp - 1), pc >> 8 ); + WRITE_LOW( 0x100 | (sp - 2), pc ); + pc = GET_LE16( &READ_PROG( 0xFFFA ) + result_ ); + + sp = (sp - 3) | 0x100; + fuint8 temp; + CALC_STATUS( temp ); + temp |= st_r; + if ( result_ ) + temp |= st_b; // TODO: incorrectly sets B flag for IRQ + WRITE_LOW( sp, temp ); + + this->r.status = status |= st_i; + blargg_long delta = s.base - end_time_; + if ( delta >= 0 ) goto loop; + s_time += delta; + s.base = end_time_; + goto loop; } - // If this fails then the case above is missing an opcode - assert( false ); +out_of_time: + pc--; + SYNC_TIME(); + CPU_DONE( this, TIME, result_ ); + RELOAD_TIME(); + if ( result_ >= 0 ) + goto interrupt; + if ( s_time < 0 ) + goto loop; stop: - pc--; -end: - { - int temp; - CALC_STATUS( temp ); - r.status = temp; - } + s.time = s_time; - base_time += clock_count; - clock_limit -= clock_count; - this->clock_count = 0; r.pc = pc; r.sp = GET_SP(); r.a = a; r.x = x; r.y = y; - irq_time_ = LONG_MAX / 2 + 1; - return result; + { + fuint8 temp; + CALC_STATUS( temp ); + r.status = temp; + } + + this->state_ = s; + this->state = &this->state_; + + return s_time < 0; } -#endif -
--- a/src/console/Nes_Cpu.h Wed Nov 29 14:42:11 2006 -0800 +++ b/src/console/Nes_Cpu.h Thu Nov 30 19:54:33 2006 -0800 @@ -1,61 +1,38 @@ - -// Nintendo Entertainment System (NES) 6502 CPU emulator +// NES 6502 CPU emulator -// Game_Music_Emu 0.3.0 - +// Game_Music_Emu 0.5.1 #ifndef NES_CPU_H #define NES_CPU_H #include "blargg_common.h" -typedef long nes_time_t; // clock cycle count +typedef blargg_long nes_time_t; // clock cycle count typedef unsigned nes_addr_t; // 16-bit address - -class Nes_Emu; +enum { future_nes_time = LONG_MAX / 2 + 1 }; class Nes_Cpu { +public: typedef BOOST::uint8_t uint8_t; - enum { page_bits = 11 }; - enum { page_count = 0x10000 >> page_bits }; - uint8_t const* code_map [page_count + 1]; -public: - Nes_Cpu(); - // Memory read/write function types. Reader must return value from 0 to 255. - typedef int (*reader_t)( Nes_Emu*, nes_addr_t ); - typedef void (*writer_t)( Nes_Emu*, nes_addr_t, int data ); - void set_emu( Nes_Emu* emu ) { callback_data = emu; } - - // Clear registers, unmap memory, and map code pages to unmapped_page. - void reset( const void* unmapped_page = NULL, reader_t read = NULL, writer_t write = NULL ); - - // Memory mapping functions take a block of memory of specified 'start' address - // and 'size' in bytes. Both start address and size must be a multiple of page_size. - enum { page_size = 1L << page_bits }; + // Clear registers, map low memory and its three mirrors to address 0, + // and mirror unmapped_page in remaining memory + void reset( void const* unmapped_page = 0 ); - // Map code memory (memory accessed via the program counter) - void map_code( nes_addr_t start, unsigned long size, const void* code ); - - // Set read function for address range - void set_reader( nes_addr_t start, unsigned long size, reader_t ); - - // Set write function for address range - void set_writer( nes_addr_t start, unsigned long size, writer_t ); - - // Set read and write functions for address range - void map_memory( nes_addr_t start, unsigned long size, reader_t, writer_t ); + // Map code memory (memory accessed via the program counter). Start and size + // must be multiple of page_size. If mirror is true, repeats code page + // throughout address range. + enum { page_size = 0x800 }; + void map_code( nes_addr_t start, unsigned size, void const* code, bool mirror = false ); - // Access memory as the emulated CPU does. - int read( nes_addr_t ); - void write( nes_addr_t, int data ); - uint8_t* get_code( nes_addr_t ); // non-const to allow debugger to modify code + // Access emulated memory as CPU does + uint8_t const* get_code( nes_addr_t ); - // Push a byte on the stack - void push_byte( int ); + // 2KB of RAM at address 0 + uint8_t low_mem [0x800]; - // NES 6502 registers. *Not* kept updated during a call to run(). + // NES 6502 registers. Not kept updated during a call to run(). struct registers_t { - nes_addr_t pc; // more than 16 bits to allow overflow detection + BOOST::uint16_t pc; BOOST::uint8_t a; BOOST::uint8_t x; BOOST::uint8_t y; @@ -64,111 +41,74 @@ }; registers_t r; - // Reasons that run() returns - enum result_t { - result_cycles, // Requested number of cycles (or more) were executed - result_sei, // I flag just set and IRQ time would generate IRQ now - result_cli, // I flag just cleared but IRQ should occur *after* next instr - result_badop // unimplemented/illegal instruction - }; + // Set end_time and run CPU from current time. Returns true if execution + // stopped due to encountering bad_opcode. + bool run( nes_time_t end_time ); - result_t run( nes_time_t end_time_ ); + // Time of beginning of next instruction to be executed + nes_time_t time() const { return state->time + state->base; } + void set_time( nes_time_t t ) { state->time = t - state->base; } + void adjust_time( int delta ) { state->time += delta; } + + nes_time_t irq_time() const { return irq_time_; } + void set_irq_time( nes_time_t ); + + nes_time_t end_time() const { return end_time_; } + void set_end_time( nes_time_t ); - nes_time_t time() const { return base_time + clock_count; } - void set_time( nes_time_t t ); - void end_frame( nes_time_t ); - nes_time_t end_time() const { return base_time + end_time_; } - nes_time_t irq_time() const { return base_time + irq_time_; } - void set_end_time( nes_time_t t ); - void set_irq_time( nes_time_t t ); + // Number of undefined instructions encountered and skipped + void clear_error_count() { error_count_ = 0; } + unsigned long error_count() const { return error_count_; } - // If PC exceeds 0xFFFF and encounters page_wrap_opcode, it will be silently wrapped. - enum { page_wrap_opcode = 0xF2 }; + // CPU invokes bad opcode handler if it encounters this + enum { bad_opcode = 0xF2 }; - // One of the many opcodes that are undefined and stop CPU emulation. - enum { bad_opcode = 0xD2 }; - - // End of public interface +public: + Nes_Cpu() { state = &state_; } + enum { page_bits = 11 }; + enum { page_count = 0x10000 >> page_bits }; + enum { irq_inhibit = 0x04 }; private: - // noncopyable - Nes_Cpu( const Nes_Cpu& ); - Nes_Cpu& operator = ( const Nes_Cpu& ); - - nes_time_t clock_limit; - nes_time_t base_time; - nes_time_t clock_count; + struct state_t { + uint8_t const* code_map [page_count + 1]; + nes_time_t base; + int time; + }; + state_t* state; // points to state_ or a local copy within run() + state_t state_; nes_time_t irq_time_; nes_time_t end_time_; - - Nes_Emu* callback_data; + unsigned long error_count_; - enum { irq_inhibit = 0x04 }; - reader_t data_reader [page_count + 1]; // extra entry catches address overflow - writer_t data_writer [page_count + 1]; - void set_code_page( int, uint8_t const* ); - void update_clock_limit(); - -public: - // low_mem is a full page size so it can be mapped with code_map - uint8_t low_mem [page_size > 0x800 ? page_size : 0x800]; + void set_code_page( int, void const* ); + inline int update_end_time( nes_time_t end, nes_time_t irq ); }; -inline BOOST::uint8_t* Nes_Cpu::get_code( nes_addr_t addr ) +inline BOOST::uint8_t const* Nes_Cpu::get_code( nes_addr_t addr ) { - #if BLARGG_NONPORTABLE - return (uint8_t*) code_map [addr >> page_bits] + addr; - #else - return (uint8_t*) code_map [addr >> page_bits] + (addr & (page_size - 1)); + return state->code_map [addr >> page_bits] + addr + #if !BLARGG_NONPORTABLE + % (unsigned) page_size #endif + ; } - -inline void Nes_Cpu::update_clock_limit() + +inline int Nes_Cpu::update_end_time( nes_time_t t, nes_time_t irq ) { - nes_time_t t = end_time_; - if ( t > irq_time_ && !(r.status & irq_inhibit) ) - t = irq_time_; - clock_limit = t; + if ( irq < t && !(r.status & irq_inhibit) ) t = irq; + int delta = state->base - t; + state->base = t; + return delta; +} + +inline void Nes_Cpu::set_irq_time( nes_time_t t ) +{ + state->time += update_end_time( end_time_, (irq_time_ = t) ); } inline void Nes_Cpu::set_end_time( nes_time_t t ) { - end_time_ = t - base_time; - update_clock_limit(); -} - -inline void Nes_Cpu::set_irq_time( nes_time_t t ) -{ - irq_time_ = t - base_time; - update_clock_limit(); -} - -inline void Nes_Cpu::end_frame( nes_time_t end_time_ ) -{ - base_time -= end_time_; - assert( time() >= 0 ); -} - -inline void Nes_Cpu::set_time( nes_time_t t ) -{ - t -= time(); - clock_limit -= t; - end_time_ -= t; - irq_time_ -= t; - base_time += t; -} - -inline void Nes_Cpu::push_byte( int data ) -{ - int sp = r.sp; - r.sp = (sp - 1) & 0xff; - low_mem [0x100 + sp] = data; -} - -inline void Nes_Cpu::map_memory( nes_addr_t addr, unsigned long s, reader_t r, writer_t w ) -{ - set_reader( addr, s, r ); - set_writer( addr, s, w ); + state->time += update_end_time( (end_time_ = t), irq_time_ ); } #endif -
--- a/src/console/Nes_Fme7_Apu.cxx Wed Nov 29 14:42:11 2006 -0800 +++ b/src/console/Nes_Fme7_Apu.cxx Thu Nov 30 19:54:33 2006 -0800 @@ -1,5 +1,4 @@ - -// Game_Music_Emu 0.3.0. http://www.slack.net/~ant/ +// Game_Music_Emu 0.5.1. http://www.slack.net/~ant/ #include "Nes_Fme7_Apu.h" @@ -11,12 +10,12 @@ 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 */ +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_BEGIN +#include "blargg_source.h" void Nes_Fme7_Apu::reset() { @@ -25,13 +24,11 @@ for ( int i = 0; i < osc_count; i++ ) oscs [i].last_amp = 0; - fme7_snapshot_t* state = this; + fme7_apu_state_t* state = this; memset( state, 0, sizeof *state ); } -#include BLARGG_ENABLE_OPTIMIZER - -unsigned char Nes_Fme7_Apu::amp_table [16] = +unsigned char const Nes_Fme7_Apu::amp_table [16] = { #define ENTRY( n ) (unsigned char) (n * amp_range + 0.5) ENTRY(0.0000), ENTRY(0.0078), ENTRY(0.0110), ENTRY(0.0156), @@ -51,8 +48,10 @@ int vol_mode = regs [010 + index]; int volume = amp_table [vol_mode & 0x0f]; - if ( !oscs [index].output ) + Blip_Buffer* const osc_output = oscs [index].output; + if ( !osc_output ) continue; + osc_output->set_modified(); // check for unsupported mode #ifndef NDEBUG @@ -83,15 +82,13 @@ if ( delta ) { oscs [index].last_amp = amp; - synth.offset( last_time, delta, oscs [index].output ); + synth.offset( last_time, delta, osc_output ); } blip_time_t time = last_time + delays [index]; if ( time < end_time ) { - Blip_Buffer* const osc_output = oscs [index].output; int delta = amp * 2 - volume; - if ( volume ) { do @@ -110,7 +107,7 @@ // maintain phase when silent int count = (end_time - time + period - 1) / period; phases [index] ^= count & 1; - time += (long) count * period; + time += (blargg_long) count * period; } }
--- a/src/console/Nes_Fme7_Apu.h Wed Nov 29 14:42:11 2006 -0800 +++ b/src/console/Nes_Fme7_Apu.h Thu Nov 30 19:54:33 2006 -0800 @@ -1,15 +1,13 @@ - // Sunsoft FME-7 sound emulator -// Game_Music_Emu 0.3.0 - +// Game_Music_Emu 0.5.1 #ifndef NES_FME7_APU_H #define NES_FME7_APU_H #include "blargg_common.h" #include "Blip_Buffer.h" -struct fme7_snapshot_t +struct fme7_apu_state_t { enum { reg_count = 14 }; BOOST::uint8_t regs [reg_count]; @@ -17,12 +15,10 @@ BOOST::uint8_t latch; BOOST::uint16_t delays [3]; // a, b, c }; -BOOST_STATIC_ASSERT( sizeof (fme7_snapshot_t) == 24 ); +BOOST_STATIC_ASSERT( sizeof (fme7_apu_state_t) == 24 ); -class Nes_Fme7_Apu : private fme7_snapshot_t { +class Nes_Fme7_Apu : private fme7_apu_state_t { public: - Nes_Fme7_Apu(); - // See Nes_Apu.h for reference void reset(); void volume( double ); @@ -31,8 +27,8 @@ enum { osc_count = 3 }; void osc_output( int index, Blip_Buffer* ); void end_frame( blip_time_t ); - void save_snapshot( fme7_snapshot_t* ) const; - void load_snapshot( fme7_snapshot_t const& ); + void save_state( fme7_apu_state_t* ) const; + void load_state( fme7_apu_state_t const& ); // Mask and addresses of registers enum { addr_mask = 0xe000 }; @@ -45,13 +41,15 @@ // (addr & addr_mask) == data_addr void write_data( blip_time_t, int data ); - // End of public interface +public: + Nes_Fme7_Apu(); + BLARGG_DISABLE_NOTHROW private: // noncopyable Nes_Fme7_Apu( const Nes_Fme7_Apu& ); Nes_Fme7_Apu& operator = ( const Nes_Fme7_Apu& ); - static unsigned char amp_table [16]; + static unsigned char const amp_table [16]; struct { Blip_Buffer* output; @@ -119,17 +117,16 @@ last_time -= time; } -inline void Nes_Fme7_Apu::save_snapshot( fme7_snapshot_t* out ) const +inline void Nes_Fme7_Apu::save_state( fme7_apu_state_t* out ) const { *out = *this; } -inline void Nes_Fme7_Apu::load_snapshot( fme7_snapshot_t const& in ) +inline void Nes_Fme7_Apu::load_state( fme7_apu_state_t const& in ) { reset(); - fme7_snapshot_t* state = this; + fme7_apu_state_t* state = this; *state = in; } #endif -
--- a/src/console/Nes_Namco_Apu.cxx Wed Nov 29 14:42:11 2006 -0800 +++ b/src/console/Nes_Namco_Apu.cxx Thu Nov 30 19:54:33 2006 -0800 @@ -1,5 +1,4 @@ - -// Nes_Snd_Emu 0.1.7. http://www.slack.net/~ant/ +// Nes_Snd_Emu 0.1.8. http://www.slack.net/~ant/ #include "Nes_Namco_Apu.h" @@ -9,12 +8,12 @@ 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 */ +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_BEGIN +#include "blargg_source.h" Nes_Namco_Apu::Nes_Namco_Apu() { @@ -23,10 +22,6 @@ reset(); } -Nes_Namco_Apu::~Nes_Namco_Apu() -{ -} - void Nes_Namco_Apu::reset() { last_time = 0; @@ -54,7 +49,7 @@ /* void Nes_Namco_Apu::reflect_state( Tagged_Data& data ) { - reflect_int16( data, 'ADDR', &addr_reg ); + reflect_int16( data, BLARGG_4CHAR('A','D','D','R'), &addr_reg ); static const char hex [17] = "0123456789ABCDEF"; int i; @@ -63,13 +58,13 @@ for ( i = 0; i < osc_count; i++ ) { - reflect_int32( data, 'DLY0' + i, &oscs [i].delay ); - reflect_int16( data, 'POS0' + i, &oscs [i].wave_pos ); + reflect_int32( data, BLARGG_4CHAR('D','L','Y','0') + i, &oscs [i].delay ); + reflect_int16( data, BLARGG_4CHAR('P','O','S','0') + i, &oscs [i].wave_pos ); } } */ -void Nes_Namco_Apu::end_frame( nes_time_t time ) +void Nes_Namco_Apu::end_frame( blip_time_t time ) { if ( time > last_time ) run_until( time ); @@ -78,9 +73,7 @@ last_time -= time; } -#include BLARGG_ENABLE_OPTIMIZER - -void Nes_Namco_Apu::run_until( nes_time_t nes_end_time ) +void Nes_Namco_Apu::run_until( blip_time_t nes_end_time ) { int active_oscs = (reg [0x7F] >> 4 & 7) + 1; for ( int i = osc_count - active_oscs; i < osc_count; i++ ) @@ -89,6 +82,7 @@ Blip_Buffer* output = osc.output; if ( !output ) continue; + output->set_modified(); blip_resampled_time_t time = output->resampled_time( last_time ) + osc.delay; @@ -104,7 +98,7 @@ if ( !volume ) continue; - long freq = (osc_reg [4] & 3) * 0x10000 + osc_reg [2] * 0x100L + osc_reg [0]; + blargg_long 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 blip_resampled_time_t period =
--- a/src/console/Nes_Namco_Apu.h Wed Nov 29 14:42:11 2006 -0800 +++ b/src/console/Nes_Namco_Apu.h Thu Nov 30 19:54:33 2006 -0800 @@ -1,20 +1,16 @@ - // Namco 106 sound chip emulator -// Nes_Snd_Emu 0.1.7 - +// Nes_Snd_Emu 0.1.8 #ifndef NES_NAMCO_APU_H #define NES_NAMCO_APU_H -#include "Nes_Apu.h" +#include "blargg_common.h" +#include "Blip_Buffer.h" -struct namco_snapshot_t; +struct namco_state_t; class Nes_Namco_Apu { public: - Nes_Namco_Apu(); - ~Nes_Namco_Apu(); - // See Nes_Apu.h for reference. void volume( double ); void treble_eq( const blip_eq_t& ); @@ -22,11 +18,11 @@ enum { osc_count = 8 }; void osc_output( int index, Blip_Buffer* ); void reset(); - void end_frame( nes_time_t ); + void end_frame( blip_time_t ); // Read/write data register is at 0x4800 enum { data_reg_addr = 0x4800 }; - void write_data( nes_time_t, int ); + void write_data( blip_time_t, int ); int read_data(); // Write-only address register is at 0xF800 @@ -34,16 +30,19 @@ void write_addr( int ); // to do: implement save/restore - void save_snapshot( namco_snapshot_t* out ) const; - void load_snapshot( namco_snapshot_t const& ); + void save_state( namco_state_t* out ) const; + void load_state( namco_state_t const& ); +public: + Nes_Namco_Apu(); + BLARGG_DISABLE_NOTHROW private: // noncopyable Nes_Namco_Apu( const Nes_Namco_Apu& ); Nes_Namco_Apu& operator = ( const Nes_Namco_Apu& ); struct Namco_Osc { - long delay; + blargg_long delay; Blip_Buffer* output; short last_amp; short wave_pos; @@ -51,7 +50,7 @@ Namco_Osc oscs [osc_count]; - nes_time_t last_time; + blip_time_t last_time; int addr_reg; enum { reg_count = 0x80 }; @@ -59,10 +58,10 @@ Blip_Synth<blip_good_quality,15> synth; BOOST::uint8_t& access(); - void run_until( nes_time_t ); + void run_until( blip_time_t ); }; /* -struct namco_snapshot_t +struct namco_state_t { BOOST::uint8_t regs [0x80]; BOOST::uint8_t addr; @@ -74,7 +73,7 @@ inline BOOST::uint8_t& Nes_Namco_Apu::access() { - int addr = addr_reg & 0x7f; + int addr = addr_reg & 0x7F; if ( addr_reg & 0x80 ) addr_reg = (addr + 1) | 0x80; return reg [addr]; @@ -94,11 +93,10 @@ oscs [i].output = buf; } -inline void Nes_Namco_Apu::write_data( nes_time_t time, int data ) +inline void Nes_Namco_Apu::write_data( blip_time_t time, int data ) { run_until( time ); access() = data; } #endif -
--- a/src/console/Nes_Oscs.cxx Wed Nov 29 14:42:11 2006 -0800 +++ b/src/console/Nes_Oscs.cxx Thu Nov 30 19:54:33 2006 -0800 @@ -1,5 +1,4 @@ - -// Nes_Snd_Emu 0.1.7. http://www.slack.net/~ant/ +// Nes_Snd_Emu 0.1.8. http://www.slack.net/~ant/ #include "Nes_Apu.h" @@ -9,12 +8,12 @@ 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 */ +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_BEGIN +#include "blargg_source.h" // Nes_Osc @@ -67,7 +66,7 @@ { period += offset; // rewrite period - regs [2] = period & 0xff; + regs [2] = period & 0xFF; regs [3] = (regs [3] & ~7) | ((period >> 8) & 7); } } @@ -79,18 +78,38 @@ } } +// TODO: clean up +inline nes_time_t Nes_Square::maintain_phase( nes_time_t time, nes_time_t end_time, + nes_time_t timer_period ) +{ + nes_time_t remain = end_time - time; + if ( remain > 0 ) + { + int count = (remain + timer_period - 1) / timer_period; + phase = (phase + count) & (phase_range - 1); + time += (blargg_long) count * timer_period; + } + return time; +} + void Nes_Square::run( nes_time_t time, nes_time_t end_time ) { - if ( !output ) - return; + const int period = this->period(); + const int timer_period = (period + 1) * 2; - const int volume = this->volume(); - const int period = this->period(); + if ( !output ) + { + delay = maintain_phase( time + delay, end_time, timer_period ) - end_time; + return; + } + + output->set_modified(); + int offset = period >> (regs [1] & shift_mask); if ( regs [1] & negate_flag ) offset = 0; - const int timer_period = (period + 1) * 2; + const int volume = this->volume(); if ( volume == 0 || period < 8 || (period + offset) >= 0x800 ) { if ( last_amp ) { @@ -99,13 +118,7 @@ } time += delay; - if ( time < end_time ) - { - // maintain proper phase - int count = (end_time - time + timer_period - 1) / timer_period; - phase = (phase + count) & (phase_range - 1); - time += (long) count * timer_period; - } + time = maintain_phase( time, end_time, timer_period ); } else { @@ -155,7 +168,7 @@ void Nes_Triangle::clock_linear_counter() { if ( reg_written [3] ) - linear_counter = regs [0] & 0x7f; + linear_counter = regs [0] & 0x7F; else if ( linear_counter ) linear_counter--; @@ -171,10 +184,34 @@ return amp; } +// TODO: clean up +inline nes_time_t Nes_Triangle::maintain_phase( nes_time_t time, nes_time_t end_time, + nes_time_t timer_period ) +{ + nes_time_t remain = end_time - time; + if ( remain > 0 ) + { + int count = (remain + timer_period - 1) / timer_period; + phase = ((unsigned) phase + 1 - count) & (phase_range * 2 - 1); + phase++; + time += (blargg_long) count * timer_period; + } + return time; +} + void Nes_Triangle::run( nes_time_t time, nes_time_t end_time ) { + const int timer_period = period() + 1; if ( !output ) + { + time += delay; + delay = 0; + if ( length_counter && linear_counter && timer_period >= 3 ) + delay = maintain_phase( time, end_time, timer_period ) - end_time; return; + } + + output->set_modified(); // to do: track phase when period < 3 // to do: Output 7.5 on dac when period < 2? More accurate, but results in more clicks. @@ -184,7 +221,6 @@ synth.offset( time, delta, output ); time += delay; - const int timer_period = period() + 1; if ( length_counter == 0 || linear_counter == 0 || timer_period < 3 ) { time = end_time; @@ -237,7 +273,7 @@ irq_enabled = false; Nes_Osc::reset(); - period = 0x1ac; + period = 0x1AC; } void Nes_Dmc::recalc_irq() @@ -260,8 +296,8 @@ if ( length_counter == 0 ) return 0; // not reading - long first_read = next_read_time(); - long avail = time - first_read; + nes_time_t first_read = next_read_time(); + nes_time_t avail = time - first_read; if ( avail <= 0 ) return 0; @@ -269,22 +305,23 @@ if ( !(regs [0] & loop_flag) && count > length_counter ) count = length_counter; - if ( last_read ) { + if ( last_read ) + { *last_read = first_read + (count - 1) * (period * 8) + 1; - assert( *last_read <= time ); - assert( count == count_reads( *last_read, NULL ) ); - assert( count - 1 == count_reads( *last_read - 1, NULL ) ); + check( *last_read <= time ); + check( count == count_reads( *last_read, NULL ) ); + check( count - 1 == count_reads( *last_read - 1, NULL ) ); } return count; } -static const short dmc_period_table [2] [16] = { - {0x1ac, 0x17c, 0x154, 0x140, 0x11e, 0x0fe, 0x0e2, 0x0d6, // NTSC - 0x0be, 0x0a0, 0x08e, 0x080, 0x06a, 0x054, 0x048, 0x036}, - - {0x18e, 0x161, 0x13c, 0x129, 0x10a, 0x0ec, 0x0d2, 0x0c7, // PAL (totally untested) - 0x0b1, 0x095, 0x084, 0x077, 0x062, 0x04e, 0x043, 0x032} // to do: verify PAL periods +static short const dmc_period_table [2] [16] = { + {428, 380, 340, 320, 286, 254, 226, 214, // NTSC + 190, 160, 142, 128, 106, 84, 72, 54}, + + {398, 354, 316, 298, 276, 236, 210, 198, // PAL + 176, 148, 132, 118, 98, 78, 66, 50} }; inline void Nes_Dmc::reload_sample() @@ -293,7 +330,7 @@ length_counter = regs [3] * 0x10 + 1; } -static const unsigned char dac_table [128] = +static byte const dac_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, @@ -310,7 +347,7 @@ if ( addr == 0 ) { period = dmc_period_table [pal_mode] [data & 15]; - irq_enabled = (data & 0xc0) == 0x80; // enabled only if loop disabled + irq_enabled = (data & 0xC0) == 0x80; // enabled only if loop disabled irq_flag &= irq_enabled; recalc_irq(); } @@ -338,8 +375,8 @@ { if ( !buf_full && length_counter ) { - require( rom_reader ); // rom_reader must be set - buf = rom_reader( rom_reader_data, 0x8000u + address ); + require( prg_reader ); // prg_reader must be set + buf = prg_reader( prg_reader_data, 0x8000u + address ); address = (address + 1) & 0x7FFF; buf_full = true; if ( --length_counter == 0 ) @@ -361,9 +398,15 @@ { int delta = update_amp( dac ); if ( !output ) + { silence = true; - else if ( delta ) - synth.offset( time, delta, output ); + } + else + { + output->set_modified(); + if ( delta ) + synth.offset( time, delta, output ); + } time += delay; if ( time < end_time ) @@ -425,17 +468,24 @@ // Nes_Noise -#include BLARGG_ENABLE_OPTIMIZER - -static const short noise_period_table [16] = { +static short const noise_period_table [16] = { 0x004, 0x008, 0x010, 0x020, 0x040, 0x060, 0x080, 0x0A0, 0x0CA, 0x0FE, 0x17C, 0x1FC, 0x2FA, 0x3F8, 0x7F2, 0xFE4 }; void Nes_Noise::run( nes_time_t time, nes_time_t end_time ) { + int period = noise_period_table [regs [2] & 15]; + if ( !output ) + { + // TODO: clean up + time += delay; + delay = time + (end_time - time + period - 1) / period * period - end_time; return; + } + + output->set_modified(); const int volume = this->volume(); int amp = (noise & 1) ? volume : 0; @@ -448,7 +498,6 @@ { const int mode_flag = 0x80; - int period = noise_period_table [regs [2] & 15]; if ( !volume ) { // round to next multiple of period
--- a/src/console/Nes_Oscs.h Wed Nov 29 14:42:11 2006 -0800 +++ b/src/console/Nes_Oscs.h Thu Nov 30 19:54:33 2006 -0800 @@ -1,8 +1,6 @@ - // Private oscillators used by Nes_Apu -// Nes_Snd_Emu 0.1.7 - +// Nes_Snd_Emu 0.1.8 #ifndef NES_OSCS_H #define NES_OSCS_H @@ -22,7 +20,7 @@ void clock_length( int halt_mask ); int period() const { - return (regs [3] & 7) * 0x100 + (regs [2] & 0xff); + return (regs [3] & 7) * 0x100 + (regs [2] & 0xFF); } void reset() { delay = 0; @@ -69,6 +67,8 @@ sweep_delay = 0; Nes_Envelope::reset(); } + nes_time_t maintain_phase( nes_time_t time, nes_time_t end_time, + nes_time_t timer_period ); }; // Nes_Triangle @@ -84,9 +84,11 @@ void clock_linear_counter(); void reset() { linear_counter = 0; - phase = phase_range; + phase = 1; Nes_Osc::reset(); } + nes_time_t maintain_phase( nes_time_t time, nes_time_t end_time, + nes_time_t timer_period ); }; // Nes_Noise @@ -124,8 +126,8 @@ bool pal_mode; bool nonlinear; - int (*rom_reader)( void*, nes_addr_t ); // needs to be initialized to rom read function - void* rom_reader_data; + int (*prg_reader)( void*, nes_addr_t ); // needs to be initialized to prg read function + void* prg_reader_data; Nes_Apu* apu; @@ -143,4 +145,3 @@ }; #endif -
--- a/src/console/Nes_Vrc6_Apu.cxx Wed Nov 29 14:42:11 2006 -0800 +++ b/src/console/Nes_Vrc6_Apu.cxx Thu Nov 30 19:54:33 2006 -0800 @@ -1,5 +1,4 @@ - -// Nes_Snd_Emu 0.1.7. http://www.slack.net/~ant/ +// Nes_Snd_Emu 0.1.8. http://www.slack.net/~ant/ #include "Nes_Vrc6_Apu.h" @@ -9,12 +8,12 @@ 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 */ +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_BEGIN +#include "blargg_source.h" Nes_Vrc6_Apu::Nes_Vrc6_Apu() { @@ -23,10 +22,6 @@ reset(); } -Nes_Vrc6_Apu::~Nes_Vrc6_Apu() -{ -} - void Nes_Vrc6_Apu::reset() { last_time = 0; @@ -48,7 +43,7 @@ osc_output( i, buf ); } -void Nes_Vrc6_Apu::run_until( nes_time_t time ) +void Nes_Vrc6_Apu::run_until( blip_time_t time ) { require( time >= last_time ); run_square( oscs [0], time ); @@ -57,7 +52,7 @@ last_time = time; } -void Nes_Vrc6_Apu::write_osc( nes_time_t time, int osc_index, int reg, int data ) +void Nes_Vrc6_Apu::write_osc( blip_time_t time, int osc_index, int reg, int data ) { require( (unsigned) osc_index < osc_count ); require( (unsigned) reg < reg_count ); @@ -66,7 +61,7 @@ oscs [osc_index].regs [reg] = data; } -void Nes_Vrc6_Apu::end_frame( nes_time_t time ) +void Nes_Vrc6_Apu::end_frame( blip_time_t time ) { if ( time > last_time ) run_until( time ); @@ -75,7 +70,7 @@ last_time -= time; } -void Nes_Vrc6_Apu::save_snapshot( vrc6_snapshot_t* out ) const +void Nes_Vrc6_Apu::save_state( vrc6_apu_state_t* out ) const { out->saw_amp = oscs [2].amp; for ( int i = 0; i < osc_count; i++ ) @@ -89,7 +84,7 @@ } } -void Nes_Vrc6_Apu::load_snapshot( vrc6_snapshot_t const& in ) +void Nes_Vrc6_Apu::load_state( vrc6_apu_state_t const& in ) { reset(); oscs [2].amp = in.saw_amp; @@ -106,13 +101,12 @@ oscs [2].phase = 1; } -#include BLARGG_ENABLE_OPTIMIZER - -void Nes_Vrc6_Apu::run_square( Vrc6_Osc& osc, nes_time_t end_time ) +void Nes_Vrc6_Apu::run_square( Vrc6_Osc& osc, blip_time_t end_time ) { Blip_Buffer* output = osc.output; if ( !output ) return; + output->set_modified(); int volume = osc.regs [0] & 15; if ( !(osc.regs [2] & 0x80) ) @@ -121,7 +115,7 @@ int gate = osc.regs [0] & 0x80; int duty = ((osc.regs [0] >> 4) & 7) + 1; int delta = ((gate || osc.phase < duty) ? volume : 0) - osc.last_amp; - nes_time_t time = last_time; + blip_time_t time = last_time; if ( delta ) { osc.last_amp += delta; @@ -161,16 +155,17 @@ } } -void Nes_Vrc6_Apu::run_saw( nes_time_t end_time ) +void Nes_Vrc6_Apu::run_saw( blip_time_t end_time ) { Vrc6_Osc& osc = oscs [2]; Blip_Buffer* output = osc.output; if ( !output ) return; + output->set_modified(); int amp = osc.amp; int amp_step = osc.regs [0] & 0x3F; - nes_time_t time = last_time; + blip_time_t time = last_time; int last_amp = osc.last_amp; if ( !(osc.regs [2] & 0x80) || !(amp_step | amp) ) {
--- a/src/console/Nes_Vrc6_Apu.h Wed Nov 29 14:42:11 2006 -0800 +++ b/src/console/Nes_Vrc6_Apu.h Thu Nov 30 19:54:33 2006 -0800 @@ -1,21 +1,16 @@ - // Konami VRC6 sound chip emulator -// Nes_Snd_Emu 0.1.7 - +// Nes_Snd_Emu 0.1.8 #ifndef NES_VRC6_APU_H #define NES_VRC6_APU_H -#include "Nes_Apu.h" +#include "blargg_common.h" #include "Blip_Buffer.h" -struct vrc6_snapshot_t; +struct vrc6_apu_state_t; class Nes_Vrc6_Apu { public: - Nes_Vrc6_Apu(); - ~Nes_Vrc6_Apu(); - // See Nes_Apu.h for reference void reset(); void volume( double ); @@ -23,9 +18,9 @@ void output( Blip_Buffer* ); enum { osc_count = 3 }; void osc_output( int index, Blip_Buffer* ); - void end_frame( nes_time_t ); - void save_snapshot( vrc6_snapshot_t* ) const; - void load_snapshot( vrc6_snapshot_t const& ); + void end_frame( blip_time_t ); + void save_state( vrc6_apu_state_t* ) const; + void load_state( vrc6_apu_state_t const& ); // Oscillator 0 write-only registers are at $9000-$9002 // Oscillator 1 write-only registers are at $A000-$A002 @@ -33,8 +28,11 @@ enum { reg_count = 3 }; enum { base_addr = 0x9000 }; enum { addr_step = 0x1000 }; - void write_osc( nes_time_t, int osc, int reg, int data ); + void write_osc( blip_time_t, int osc, int reg, int data ); +public: + Nes_Vrc6_Apu(); + BLARGG_DISABLE_NOTHROW private: // noncopyable Nes_Vrc6_Apu( const Nes_Vrc6_Apu& ); @@ -51,22 +49,22 @@ int period() const { - return (regs [2] & 0x0f) * 0x100L + regs [1] + 1; + return (regs [2] & 0x0F) * 0x100L + regs [1] + 1; } }; Vrc6_Osc oscs [osc_count]; - nes_time_t last_time; + blip_time_t last_time; Blip_Synth<blip_med_quality,1> saw_synth; Blip_Synth<blip_good_quality,1> square_synth; - void run_until( nes_time_t ); - void run_square( Vrc6_Osc& osc, nes_time_t ); - void run_saw( nes_time_t ); + void run_until( blip_time_t ); + void run_square( Vrc6_Osc& osc, blip_time_t ); + void run_saw( blip_time_t ); }; -struct vrc6_snapshot_t +struct vrc6_apu_state_t { BOOST::uint8_t regs [3] [3]; BOOST::uint8_t saw_amp; @@ -74,7 +72,7 @@ BOOST::uint8_t phases [3]; BOOST::uint8_t unused; }; -BOOST_STATIC_ASSERT( sizeof (vrc6_snapshot_t) == 20 ); +BOOST_STATIC_ASSERT( sizeof (vrc6_apu_state_t) == 20 ); inline void Nes_Vrc6_Apu::osc_output( int i, Blip_Buffer* buf ) { @@ -96,4 +94,3 @@ } #endif -
--- a/src/console/Nsf_Emu.cxx Wed Nov 29 14:42:11 2006 -0800 +++ b/src/console/Nsf_Emu.cxx Thu Nov 30 19:54:33 2006 -0800 @@ -1,392 +1,279 @@ - -// Game_Music_Emu 0.3.0. http://www.slack.net/~ant/ +// Game_Music_Emu 0.5.1. http://www.slack.net/~ant/ #include "Nsf_Emu.h" +#include "blargg_endian.h" #include <string.h> #include <stdio.h> #if !NSF_EMU_APU_ONLY + #include "Nes_Namco_Apu.h" #include "Nes_Vrc6_Apu.h" - #include "Nes_Namco_Apu.h" #include "Nes_Fme7_Apu.h" #endif -#include "blargg_endian.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 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_BEGIN - -#ifndef RUN_NES_CPU - #define RUN_NES_CPU( cpu, count ) cpu.run( count ) -#endif - -#ifndef NSF_BEGIN_FRAME - #define NSF_BEGIN_FRAME() -#endif +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 */ -const unsigned low_mem_size = 0x800; -const unsigned page_size = 0x1000; -const long ram_size = 0x10000; -const nes_addr_t rom_begin = 0x8000; -const nes_addr_t bank_select_addr = 0x5ff8; -const nes_addr_t exram_addr = bank_select_addr - (bank_select_addr % Nes_Cpu::page_size); -const int master_clock_divisor = 12; - -const int vrc6_flag = 0x01; -const int fds_flag = 0x04; -const int namco_flag = 0x10; -const int fme7_flag = 0x20; - -static BOOST::uint8_t unmapped_code [Nes_Cpu::page_size]; - -Nes_Emu::equalizer_t const Nes_Emu::nes_eq = { -1.0, 80 }; -Nes_Emu::equalizer_t const Nes_Emu::famicom_eq = { -15.0, 80 }; - -// ROM +#include "blargg_source.h" -int Nsf_Emu::read_code( Nsf_Emu* emu, nes_addr_t addr ) -{ - return *emu->cpu.get_code( addr ); -} +int const vrc6_flag = 0x01; +int const namco_flag = 0x10; +int const fme7_flag = 0x20; -void Nsf_Emu::write_exram( Nsf_Emu* emu, nes_addr_t addr, int data ) -{ - unsigned bank = addr - bank_select_addr; - if ( bank < bank_count ) - { - if ( data < emu->total_banks ) - { - emu->cpu.map_code( (bank + 8) * page_size, page_size, - &emu->rom [data * page_size] ); - } - else - { - dprintf( "Bank %d out of range (%d banks total)\n", - data, (int) emu->total_banks ); - } - } -} +long const clock_divisor = 12; -// APU - -int Nsf_Emu::read_snd( Nsf_Emu* emu, nes_addr_t addr ) -{ - if ( addr == Nes_Apu::status_addr ) - return emu->apu.read_status( emu->cpu.time() ); - return addr >> 8; // high byte of address stays on bus -} - -void Nsf_Emu::write_snd( Nsf_Emu* emu, nes_addr_t addr, int data ) -{ - if ( unsigned (addr - Nes_Apu::start_addr) <= Nes_Apu::end_addr - Nes_Apu::start_addr ) - emu->apu.write_register( emu->cpu.time(), addr, data ); -} +Nsf_Emu::equalizer_t const Nsf_Emu::nes_eq = { -1.0, 80 }; +Nsf_Emu::equalizer_t const Nsf_Emu::famicom_eq = { -15.0, 80 }; int Nsf_Emu::pcm_read( void* emu, nes_addr_t addr ) { - return ((Nsf_Emu*) emu)->cpu.read( addr ); -} - -// Low Mem - -int Nsf_Emu::read_low_mem( Nsf_Emu* emu, nes_addr_t addr ) -{ - return emu->cpu.low_mem [addr]; -} - -void Nsf_Emu::write_low_mem( Nsf_Emu* emu, nes_addr_t addr, int data ) -{ - emu->cpu.low_mem [addr] = data; -} - -// SRAM - -int Nsf_Emu::read_sram( Nsf_Emu* emu, nes_addr_t addr ) -{ - return emu->sram [addr & (sram_size - 1)]; -} - -void Nsf_Emu::write_sram( Nsf_Emu* emu, nes_addr_t addr, int data ) -{ - emu->sram [addr & (sram_size - 1)] = data; -} - -#if !NSF_EMU_APU_ONLY - -// Namco -int Nsf_Emu::read_namco( Nsf_Emu* emu, nes_addr_t addr ) -{ - if ( addr == Nes_Namco_Apu::data_reg_addr ) - return emu->namco->read_data(); - return addr >> 8; -} - -void Nsf_Emu::write_namco( Nsf_Emu* emu, nes_addr_t addr, int data ) -{ - if ( addr == Nes_Namco_Apu::data_reg_addr ) - emu->namco->write_data( emu->cpu.time(), data ); -} - -void Nsf_Emu::write_namco_addr( Nsf_Emu* emu, nes_addr_t addr, int data ) -{ - if ( addr == Nes_Namco_Apu::addr_reg_addr ) - emu->namco->write_addr( data ); -} - -// VRC6 -void Nsf_Emu::write_vrc6( Nsf_Emu* emu, nes_addr_t addr, int data ) -{ - unsigned reg = addr & (Nes_Vrc6_Apu::addr_step - 1); - unsigned osc = unsigned (addr - Nes_Vrc6_Apu::base_addr) / Nes_Vrc6_Apu::addr_step; - if ( osc < Nes_Vrc6_Apu::osc_count && reg < Nes_Vrc6_Apu::reg_count ) - emu->vrc6->write_osc( emu->cpu.time(), osc, reg, data ); + return *((Nsf_Emu*) emu)->cpu::get_code( addr ); } -// FME-7 -void Nsf_Emu::write_fme7( Nsf_Emu* emu, nes_addr_t addr, int data ) +Nsf_Emu::Nsf_Emu() { - switch ( addr & Nes_Fme7_Apu::addr_mask ) - { - case Nes_Fme7_Apu::latch_addr: - emu->fme7->write_latch( data ); - break; - - case Nes_Fme7_Apu::data_addr: - emu->fme7->write_data( emu->cpu.time(), data ); - break; - } -} - -#endif - -// Unmapped -int Nsf_Emu::read_unmapped( Nsf_Emu*, nes_addr_t addr ) -{ - dprintf( "Read unmapped $%.4X\n", (unsigned) addr ); - return (addr >> 8) & 0xff; // high byte of address stays on bus + vrc6 = 0; + namco = 0; + fme7 = 0; + + set_type( gme_nsf_type ); + set_silence_lookahead( 6 ); + apu.dmc_reader( pcm_read, this ); + Music_Emu::set_equalizer( nes_eq ); + set_gain( 1.4 ); + memset( unmapped_code, Nes_Cpu::bad_opcode, sizeof unmapped_code ); } -void Nsf_Emu::write_unmapped( Nsf_Emu*, nes_addr_t addr, int data ) -{ - #ifdef NDEBUG - return; - #endif - - // some games write to $8000 and $8001 repeatedly - if ( addr == 0x8000 || addr == 0x8001 ) - return; - - // probably namco sound mistakenly turned on in mck - if ( addr == 0x4800 || addr == 0xF800 ) - return; - - // memory mapper? - if ( addr == 0xFFF8 ) - return; - - dprintf( "write_unmapped( 0x%04X, 0x%02X )\n", (unsigned) addr, (unsigned) data ); -} - -Nes_Emu::Nes_Emu( double gain_ ) -{ - cpu.set_emu( this ); - play_addr = 0; - gain = gain_; - apu.dmc_reader( pcm_read, this ); - vrc6 = NULL; - namco = NULL; - fme7 = NULL; - Music_Emu::set_equalizer( nes_eq ); - - // set unmapped code to illegal instruction - memset( unmapped_code, 0x32, sizeof unmapped_code ); -} - -Nes_Emu::~Nes_Emu() -{ - unload(); -} +Nsf_Emu::~Nsf_Emu() { unload(); } void Nsf_Emu::unload() { #if !NSF_EMU_APU_ONLY + { delete vrc6; - vrc6 = NULL; + vrc6 = 0; delete namco; - namco = NULL; + namco = 0; delete fme7; - fme7 = NULL; - + fme7 = 0; + } #endif rom.clear(); + Music_Emu::unload(); +} + +// Track info + +static void copy_nsf_fields( Nsf_Emu::header_t const& h, track_info_t* out ) +{ + GME_COPY_FIELD( h, out, game ); + GME_COPY_FIELD( h, out, author ); + GME_COPY_FIELD( h, out, copyright ); + if ( h.chip_flags ) + Gme_File::copy_field_( out->system, "Famicom" ); +} + +blargg_err_t Nsf_Emu::track_info_( track_info_t* out, int ) const +{ + copy_nsf_fields( header_, out ); + return 0; +} + +static blargg_err_t check_nsf_header( void const* header ) +{ + if ( memcmp( header, "NESM\x1A", 5 ) ) + return gme_wrong_file_type; + return 0; } -const char** Nsf_Emu::voice_names() const +struct Nsf_File : Gme_Info_ { - static const char* base_names [] = { - "Square 1", "Square 2", "Triangle", "Noise", "DMC" - }; - static const char* namco_names [] = { - "Square 1", "Square 2", "Triangle", "Noise", "DMC", - "Namco 5&7", "Namco 4&6", "Namco 1-3" - }; - static const char* vrc6_names [] = { - "Square 1", "Square 2", "Triangle", "Noise", "DMC", - "VRC6 Square 1", "VRC6 Square 2", "VRC6 Saw" - }; - static const char* dual_names [] = { - "Square 1", "Square 2", "Triangle", "Noise", "DMC", - "VRC6.1,N106.5&7", "VRC6.2,N106.4&6", "VRC6.3,N106.1-3" - }; + Nsf_Emu::header_t h; + + Nsf_File() { set_type( gme_nsf_type ); } + + blargg_err_t load_( Data_Reader& in ) + { + blargg_err_t err = in.read( &h, sizeof h ); + if ( err ) + return (err == in.eof_error ? gme_wrong_file_type : err); + + if ( h.chip_flags & ~(namco_flag | vrc6_flag | fme7_flag) ) + set_warning( "Uses unsupported audio expansion hardware" ); + + set_track_count( h.track_count ); + return check_nsf_header( &h ); + } - static const char* fme7_names [] = { - "Square 1", "Square 2", "Triangle", "Noise", "DMC", - "Square 3", "Square 4", "Square 5" - }; - - if ( namco ) - return vrc6 ? dual_names : namco_names; + blargg_err_t track_info_( track_info_t* out, int ) const + { + copy_nsf_fields( h, out ); + return 0; + } +}; + +static Music_Emu* new_nsf_emu () { return BLARGG_NEW Nsf_Emu ; } +static Music_Emu* new_nsf_file() { return BLARGG_NEW Nsf_File; } + +gme_type_t_ const gme_nsf_type [1] = { "Nintendo NES", 0, &new_nsf_emu, &new_nsf_file, "NSF", 1 }; + +// Setup + +void Nsf_Emu::set_tempo_( double t ) +{ + unsigned playback_rate = get_le16( header_.ntsc_speed ); + unsigned standard_rate = 0x411A; + clock_rate_ = 1789772.72727; + play_period = 262 * 341L * 4 - 2; // two fewer PPU clocks every four frames - if ( vrc6 ) - return vrc6_names; + if ( pal_only ) + { + play_period = 33247 * clock_divisor; + clock_rate_ = 1662607.125; + standard_rate = 0x4E20; + playback_rate = get_le16( header_.pal_speed ); + } - if ( fme7 ) - return fme7_names; + if ( !playback_rate ) + playback_rate = standard_rate; - return base_names; + if ( playback_rate != standard_rate || t != 1.0 ) + play_period = long (playback_rate * clock_rate_ / (1000000.0 / clock_divisor * t)); + + apu.set_tempo( t ); } blargg_err_t Nsf_Emu::init_sound() { - if ( exp_flags & ~(namco_flag | vrc6_flag | fme7_flag | fds_flag) ) - return "NSF requires unsupported expansion audio hardware"; + if ( header_.chip_flags & ~(namco_flag | vrc6_flag | fme7_flag) ) + set_warning( "Uses unsupported audio expansion hardware" ); - // map memory - cpu.reset( unmapped_code, read_unmapped, write_unmapped ); - cpu.map_memory( 0, low_mem_size, read_low_mem, write_low_mem ); - cpu.map_code( 0, low_mem_size, cpu.low_mem ); - cpu.map_memory( 0x4000, Nes_Cpu::page_size, read_snd, write_snd ); - cpu.map_memory( exram_addr, Nes_Cpu::page_size, read_unmapped, write_exram ); - cpu.map_memory( 0x6000, sram_size, read_sram, write_sram ); - cpu.map_code ( 0x6000, sram_size, sram ); - cpu.map_memory( rom_begin, ram_size - rom_begin, read_code, write_unmapped ); + { + #define APU_NAMES "Square 1", "Square 2", "Triangle", "Noise", "DMC" + + int const count = Nes_Apu::osc_count; + static const char* const apu_names [count] = { APU_NAMES }; + set_voice_count( count ); + set_voice_names( apu_names ); + + } - set_voice_count( Nes_Apu::osc_count ); + static int const types [] = { + wave_type | 1, wave_type | 2, wave_type | 0, + noise_type | 0, mixed_type | 1, + wave_type | 3, wave_type | 4, wave_type | 5, + wave_type | 6, wave_type | 7, wave_type | 8, wave_type | 9, + wave_type |10, wave_type |11, wave_type |12, wave_type |13 + }; + set_voice_types( types ); // common to all sound chip configurations - double adjusted_gain = gain; + double adjusted_gain = gain(); #if NSF_EMU_APU_ONLY - if ( exp_flags ) - return "NSF requires expansion audio hardware"; + { + if ( header_.chip_flags ) + set_warning( "Uses unsupported audio expansion hardware" ); + } #else - - if ( exp_flags ) - set_voice_count( Nes_Apu::osc_count + 3 ); - - // namco - if ( exp_flags & namco_flag ) { - namco = BLARGG_NEW Nes_Namco_Apu; - BLARGG_CHECK_ALLOC( namco ); + if ( header_.chip_flags & (namco_flag | vrc6_flag | fme7_flag) ) + set_voice_count( Nes_Apu::osc_count + 3 ); - adjusted_gain *= 0.75; - cpu.map_memory( Nes_Namco_Apu::data_reg_addr, Nes_Cpu::page_size, - read_namco, write_namco ); - cpu.map_memory( Nes_Namco_Apu::addr_reg_addr, Nes_Cpu::page_size, - read_code, write_namco_addr ); - } - - // vrc6 - if ( exp_flags & vrc6_flag ) - { - vrc6 = BLARGG_NEW Nes_Vrc6_Apu; - BLARGG_CHECK_ALLOC( vrc6 ); + if ( header_.chip_flags & namco_flag ) + { + namco = BLARGG_NEW Nes_Namco_Apu; + CHECK_ALLOC( namco ); + adjusted_gain *= 0.75; + + int const count = Nes_Apu::osc_count + Nes_Namco_Apu::osc_count; + static const char* const names [count] = { + APU_NAMES, + "Wave 1", "Wave 2", "Wave 3", "Wave 4", + "Wave 5", "Wave 6", "Wave 7", "Wave 8" + }; + set_voice_count( count ); + set_voice_names( names ); + } - adjusted_gain *= 0.75; - for ( int i = 0; i < Nes_Vrc6_Apu::osc_count; i++ ) - cpu.map_memory( Nes_Vrc6_Apu::base_addr + i * Nes_Vrc6_Apu::addr_step, - Nes_Cpu::page_size, read_code, write_vrc6 ); - } - - // fme7 - if ( exp_flags & fme7_flag ) - { - fme7 = BLARGG_NEW Nes_Fme7_Apu; - BLARGG_CHECK_ALLOC( fme7 ); + if ( header_.chip_flags & vrc6_flag ) + { + vrc6 = BLARGG_NEW Nes_Vrc6_Apu; + CHECK_ALLOC( vrc6 ); + adjusted_gain *= 0.75; + + int const count = Nes_Apu::osc_count + Nes_Vrc6_Apu::osc_count; + static const char* const names [count] = { + APU_NAMES, + "Saw Wave", "Square 3", "Square 4" + }; + set_voice_count( count ); + set_voice_names( names ); + + if ( header_.chip_flags & namco_flag ) + { + int const count = Nes_Apu::osc_count + Nes_Vrc6_Apu::osc_count + + Nes_Namco_Apu::osc_count; + static const char* const names [count] = { + APU_NAMES, + "Saw Wave", "Square 3", "Square 4", + "Wave 1", "Wave 2", "Wave 3", "Wave 4", + "Wave 5", "Wave 6", "Wave 7", "Wave 8" + }; + set_voice_count( count ); + set_voice_names( names ); + } + } - adjusted_gain *= 0.75; - cpu.map_memory( fme7->latch_addr, ram_size - fme7->latch_addr, - read_code, write_fme7 ); + if ( header_.chip_flags & fme7_flag ) + { + fme7 = BLARGG_NEW Nes_Fme7_Apu; + CHECK_ALLOC( fme7 ); + adjusted_gain *= 0.75; + + int const count = Nes_Apu::osc_count + Nes_Fme7_Apu::osc_count; + static const char* const names [count] = { + APU_NAMES, + "Square 3", "Square 4", "Square 5" + }; + set_voice_count( count ); + set_voice_names( names ); + } + + if ( namco ) namco->volume( adjusted_gain ); + if ( vrc6 ) vrc6 ->volume( adjusted_gain ); + if ( fme7 ) fme7 ->volume( adjusted_gain ); } - // to do: is gain adjustment even needed? other sound chip volumes should work - // naturally with the apu without change. - - if ( namco ) - namco->volume( adjusted_gain ); - - if ( vrc6 ) - vrc6->volume( adjusted_gain ); - - if ( fme7 ) - fme7->volume( adjusted_gain ); - -#endif + #endif apu.volume( adjusted_gain ); - return blargg_success; -} - -void Nsf_Emu::update_eq( blip_eq_t const& eq ) -{ - apu.treble_eq( eq ); - - #if !NSF_EMU_APU_ONLY - if ( vrc6 ) - vrc6->treble_eq( eq ); - - if ( namco ) - namco->treble_eq( eq ); - - if ( fme7 ) - fme7->treble_eq( eq ); - #endif + return 0; } -blargg_err_t Nsf_Emu::load( Data_Reader& in ) -{ - header_t h; - BLARGG_RETURN_ERR( in.read( &h, sizeof h ) ); - return load( h, in ); -} - -blargg_err_t Nsf_Emu::load( const header_t& h, Data_Reader& in ) +blargg_err_t Nsf_Emu::load_( Data_Reader& in ) { - header_ = h; unload(); + RETURN_ERR( rom.load( in, sizeof header_, &header_, 0 ) ); - // check compatibility - if ( 0 != memcmp( header_.tag, "NESM\x1A", 5 ) ) - return "Not an NSF file"; + set_track_count( header_.track_count ); + RETURN_ERR( check_nsf_header( &header_ ) ); + if ( header_.vers != 1 ) - return "Unsupported NSF format"; + set_warning( "Unknown file version" ); // sound and memory - exp_flags = header_.chip_flags; blargg_err_t err = init_sound(); if ( err ) return err; @@ -399,25 +286,24 @@ if ( !init_addr ) init_addr = rom_begin; if ( !play_addr ) play_addr = rom_begin; if ( load_addr < rom_begin || init_addr < rom_begin ) - return "Invalid address in NSF"; - - // set up rom - total_banks = (in.remain() + load_addr % page_size + page_size - 1) / page_size; - BLARGG_RETURN_ERR( rom.resize( total_banks * page_size ) ); - memset( rom.begin(), 0, rom.size() ); - err = in.read( &rom [load_addr % page_size], in.remain() ); - if ( err ) { - unload(); - return err; + const char* w = warning(); + if ( !w ) + w = "Corrupt file (invalid load/init/play address)"; + return w; } + rom.set_addr( load_addr % bank_size ); + int total_banks = rom.size() / bank_size; + // bank switching - int first_bank = (load_addr - rom_begin) / page_size; + int first_bank = (load_addr - rom_begin) / bank_size; for ( int i = 0; i < bank_count; i++ ) { unsigned bank = i - first_bank; - initial_banks [i] = (bank < (unsigned) total_banks) ? bank : 0; + if ( bank >= (unsigned) total_banks ) + bank = 0; + initial_banks [i] = bank; if ( header_.banks [i] ) { @@ -427,39 +313,28 @@ } } - // playback rate - unsigned playback_rate = get_le16( header_.ntsc_speed ); - unsigned standard_rate = 0x411A; - double clock_rate = 1789772.72727; - play_period = 262 * 341L * 4 + 2; - pal_only = false; + pal_only = (header_.speed_flags & 3) == 1; - // use pal speed if there is no ntsc speed - if ( (header_.speed_flags & 3) == 1 ) - { - pal_only = true; - play_period = 33247 * master_clock_divisor; - clock_rate = 1662607.125; - standard_rate = 0x4E20; - playback_rate = get_le16( header_.pal_speed ); - } + #if !NSF_EMU_EXTRA_FLAGS + header_.speed_flags = 0; + #endif + + set_tempo( tempo() ); - // use custom playback rate if not the standard rate - if ( playback_rate && playback_rate != standard_rate ) - play_period = long (clock_rate * playback_rate * master_clock_divisor / - 1000000.0); + return setup_buffer( (long) (clock_rate_ + 0.5) ); +} + +void Nsf_Emu::update_eq( blip_eq_t const& eq ) +{ + apu.treble_eq( eq ); - // extra flags - int extra_flags = header_.speed_flags; - #if !NSF_EMU_EXTRA_FLAGS - extra_flags = 0; + #if !NSF_EMU_APU_ONLY + { + if ( namco ) namco->treble_eq( eq ); + if ( vrc6 ) vrc6 ->treble_eq( eq ); + if ( fme7 ) fme7 ->treble_eq( eq ); + } #endif - needs_long_frames = (extra_flags & 0x10) != 0; - initial_pcm_dac = (extra_flags & 0x20) ? 0x3F : 0; - - set_track_count( header_.track_count ); - - return setup_buffer( (long) (clock_rate + 0.5) ); } void Nsf_Emu::set_voice( int i, Blip_Buffer* buf, Blip_Buffer*, Blip_Buffer* ) @@ -469,155 +344,211 @@ apu.osc_output( i, buf ); return; } + i -= Nes_Apu::osc_count; #if !NSF_EMU_APU_ONLY - if ( vrc6 ) - vrc6->osc_output( i - Nes_Apu::osc_count, buf ); + { + if ( fme7 && i < Nes_Fme7_Apu::osc_count ) + { + fme7->osc_output( i, buf ); + return; + } - if ( fme7 ) - fme7->osc_output( i - Nes_Apu::osc_count, buf ); - - if ( namco ) + if ( vrc6 ) { - if ( i < 7 ) + if ( i < Nes_Vrc6_Apu::osc_count ) { - i &= 1; - namco->osc_output( i + 4, buf ); - namco->osc_output( i + 6, buf ); + // put saw first + if ( --i < 0 ) + i = 2; + vrc6->osc_output( i, buf ); + return; } - else - { - for ( int n = 0; n < namco->osc_count / 2; n++ ) - namco->osc_output( n, buf ); - } + i -= Nes_Vrc6_Apu::osc_count; } + + if ( namco && i < Nes_Namco_Apu::osc_count ) + { + namco->osc_output( i, buf ); + return; + } + } #endif } -void Nsf_Emu::start_track( int track ) +// Emulation + +// see nes_cpu_io.h for read/write functions + +void Nsf_Emu::cpu_write_misc( nes_addr_t addr, int data ) { - require( rom.size() ); // file must be loaded - - Classic_Emu::start_track( track ); - - // clear memory - memset( cpu.low_mem, 0, sizeof cpu.low_mem ); - memset( sram, 0, sizeof sram ); - - // initial rom banks - for ( int i = 0; i < bank_count; ++i ) - cpu.write( bank_select_addr + i, initial_banks [i] ); - - // reset sound - apu.reset( pal_only, initial_pcm_dac ); - apu.write_register( 0, 0x4015, 0x0F ); - apu.write_register( 0, 0x4017, needs_long_frames ? 0x80 : 0 ); - #if !NSF_EMU_APU_ONLY + { if ( namco ) - namco->reset(); + { + switch ( addr ) + { + case Nes_Namco_Apu::data_reg_addr: + namco->write_data( time(), data ); + return; + + case Nes_Namco_Apu::addr_reg_addr: + namco->write_addr( data ); + return; + } + } + + if ( addr >= Nes_Fme7_Apu::latch_addr && fme7 ) + { + switch ( addr & Nes_Fme7_Apu::addr_mask ) + { + case Nes_Fme7_Apu::latch_addr: + fme7->write_latch( data ); + return; + + case Nes_Fme7_Apu::data_addr: + fme7->write_data( time(), data ); + return; + } + } if ( vrc6 ) - vrc6->reset(); - - if ( fme7 ) - fme7->reset(); + { + unsigned reg = addr & (Nes_Vrc6_Apu::addr_step - 1); + unsigned osc = unsigned (addr - Nes_Vrc6_Apu::base_addr) / Nes_Vrc6_Apu::addr_step; + if ( osc < Nes_Vrc6_Apu::osc_count && reg < Nes_Vrc6_Apu::reg_count ) + { + vrc6->write_osc( time(), osc, reg, data ); + return; + } + } + } #endif - // reset cpu - cpu.r.pc = exram_addr; - cpu.r.a = track; - cpu.r.x = pal_only; - cpu.r.y = 0; - cpu.r.sp = 0xFF; - cpu.r.status = 0x04; // i flag + // unmapped write - // first call - cpu_jsr( init_addr, -1 ); - next_play = 0; - play_extra = 0; + #ifndef NDEBUG + { + // some games write to $8000 and $8001 repeatedly + if ( addr == 0x8000 || addr == 0x8001 ) return; + + // probably namco sound mistakenly turned on in mck + if ( addr == 0x4800 || addr == 0xF800 ) return; + + // memory mapper? + if ( addr == 0xFFF8 ) return; + + dprintf( "write_unmapped( 0x%04X, 0x%02X )\n", (unsigned) addr, (unsigned) data ); + } + #endif } -void Nsf_Emu::cpu_jsr( nes_addr_t pc, int adj ) +blargg_err_t Nsf_Emu::start_track_( int track ) { - unsigned addr = cpu.r.pc + adj; - cpu.r.pc = pc; - cpu.push_byte( addr >> 8 ); - cpu.push_byte( addr ); -} - -void Nsf_Emu::call_play() -{ - cpu_jsr( play_addr, -1 ); + RETURN_ERR( Classic_Emu::start_track_( track ) ); + + memset( low_mem, 0, sizeof low_mem ); + memset( sram, 0, sizeof sram ); + + cpu::reset( unmapped_code ); // also maps low_mem + cpu::map_code( sram_addr, sizeof sram, sram ); + for ( int i = 0; i < bank_count; ++i ) + cpu_write( bank_select_addr + i, initial_banks [i] ); + + apu.reset( pal_only, (header_.speed_flags & 0x20) ? 0x3F : 0 ); + apu.write_register( 0, 0x4015, 0x0F ); + apu.write_register( 0, 0x4017, (header_.speed_flags & 0x10) ? 0x80 : 0 ); + #if !NSF_EMU_APU_ONLY + { + if ( namco ) namco->reset(); + if ( vrc6 ) vrc6 ->reset(); + if ( fme7 ) fme7 ->reset(); + } + #endif + + play_ready = 4; + play_extra = 0; + next_play = play_period / clock_divisor; + + saved_state.pc = badop_addr; + low_mem [0x1FF] = (badop_addr - 1) >> 8; + low_mem [0x1FE] = (badop_addr - 1); + r.sp = 0xFD; + r.pc = init_addr; + r.a = track; + r.x = pal_only; + + return 0; } -blip_time_t Nsf_Emu::run_clocks( blip_time_t duration, bool* ) +blargg_err_t Nsf_Emu::run_clocks( blip_time_t& duration, int ) { - // run cpu - cpu.set_time( 0 ); - bool first_illegal = true; // avoid swamping output with illegal instruction errors - while ( cpu.time() < duration ) + set_time( 0 ); + while ( time() < duration ) { - // check for idle cpu - if ( cpu.r.pc == exram_addr ) + nes_time_t end = min( next_play, duration ); + end = min( end, time() + 32767 ); // allows CPU to use 16-bit time delta + if ( cpu::run( end ) ) { - if ( next_play > duration ) + if ( r.pc != badop_addr ) { - cpu.set_time( duration ); - break; - } - - if ( next_play > cpu.time() ) - cpu.set_time( next_play ); - - nes_time_t period = (play_period + play_extra) / master_clock_divisor; - play_extra = play_period - period * master_clock_divisor; - next_play += period; - call_play(); - } - - Nes_Cpu::result_t result = RUN_NES_CPU( cpu, duration ); - if ( result == Nes_Cpu::result_badop && cpu.r.pc != exram_addr ) - { - if ( cpu.r.pc > 0xffff ) - { - cpu.r.pc &= 0xffff; - dprintf( "PC wrapped around\n" ); + set_warning( "Emulation error (illegal instruction)" ); + r.pc++; } else { - cpu.r.pc = (cpu.r.pc + 1) & 0xffff; - cpu.set_time( cpu.time() + 4 ); - log_error(); - if ( first_illegal ) + play_ready = 1; + if ( saved_state.pc != badop_addr ) + { + cpu::r = saved_state; + saved_state.pc = badop_addr; + } + else { - first_illegal = false; - dprintf( "Bad opcode $%.2x at $%.4x\n", - (int) cpu.read( cpu.r.pc ), (int) cpu.r.pc ); + set_time( end ); } } } + + if ( time() >= next_play ) + { + nes_time_t period = (play_period + play_extra) / clock_divisor; + play_extra = play_period - period * clock_divisor; + next_play += period; + if ( play_ready && !--play_ready ) + { + check( saved_state.pc == badop_addr ); + if ( r.pc != badop_addr ) + saved_state = cpu::r; + + r.pc = play_addr; + low_mem [0x100 + r.sp--] = (badop_addr - 1) >> 8; + low_mem [0x100 + r.sp--] = (badop_addr - 1); + } + } } - // end time frame - duration = cpu.time(); + if ( cpu::error_count() ) + { + cpu::clear_error_count(); + set_warning( "Emulation error (illegal instruction)" ); + } + + duration = time(); next_play -= duration; - if ( next_play < 0 ) // could go negative if routine is taking too long to return + check( next_play >= 0 ); + if ( next_play < 0 ) next_play = 0; + apu.end_frame( duration ); #if !NSF_EMU_APU_ONLY - if ( namco ) - namco->end_frame( duration ); - - if ( vrc6 ) - vrc6->end_frame( duration ); - - if ( fme7 ) - fme7->end_frame( duration ); - + { + if ( namco ) namco->end_frame( duration ); + if ( vrc6 ) vrc6 ->end_frame( duration ); + if ( fme7 ) fme7 ->end_frame( duration ); + } #endif - return duration; + return 0; } -
--- a/src/console/Nsf_Emu.h Wed Nov 29 14:42:11 2006 -0800 +++ b/src/console/Nsf_Emu.h Thu Nov 30 19:54:33 2006 -0800 @@ -1,8 +1,6 @@ - -// Nintendo Entertainment System (NES) NSF music file emulator +// Nintendo NES/Famicom NSF music file emulator -// Game_Music_Emu 0.3.0 - +// Game_Music_Emu 0.5.1 #ifndef NSF_EMU_H #define NSF_EMU_H @@ -10,14 +8,12 @@ #include "Nes_Apu.h" #include "Nes_Cpu.h" -typedef Nes_Emu Nsf_Emu; - -class Nes_Emu : public Classic_Emu { +class Nsf_Emu : private Nes_Cpu, public Classic_Emu { + typedef Nes_Cpu cpu; public: - - // Set internal gain, where 1.0 results in almost no clamping. Default gain - // roughly matches volume of other emulators. - Nes_Emu( double gain = 1.4 ); + // Equalizer profiles for US NES and Japanese Famicom + static equalizer_t const nes_eq; + static equalizer_t const famicom_eq; // NSF file header struct header_t @@ -38,100 +34,73 @@ byte speed_flags; byte chip_flags; byte unused [4]; - - enum { song = 0 }; // no song titles }; BOOST_STATIC_ASSERT( sizeof (header_t) == 0x80 ); - // Load NSF data - blargg_err_t load( Data_Reader& ); - - // Load NSF using already-loaded header and remaining data - blargg_err_t load( header_t const&, Data_Reader& ); - - // Header for currently loaded NSF + // Header for currently loaded file header_t const& header() const { return header_; } - // Equalizer profiles for US NES and Japanese Famicom - static equalizer_t const nes_eq; - static equalizer_t const famicom_eq; + static gme_type_t static_type() { return gme_nsf_type; } public: - ~Nes_Emu(); - void start_track( int ); + // deprecated + Music_Emu::load; + blargg_err_t load( header_t const& h, Data_Reader& in ) // use Remaining_Reader + { return load_remaining_( &h, sizeof h, in ); } + +public: + Nsf_Emu(); + ~Nsf_Emu(); Nes_Apu* apu_() { return &apu; } - const char** voice_names() const; protected: + blargg_err_t track_info_( track_info_t*, int track ) const; + blargg_err_t load_( Data_Reader& ); + blargg_err_t start_track_( int ); + blargg_err_t run_clocks( blip_time_t&, int ); + void set_tempo_( double ); void set_voice( int, Blip_Buffer*, Blip_Buffer*, Blip_Buffer* ); void update_eq( blip_eq_t const& ); - blip_time_t run_clocks( blip_time_t, bool* ); - virtual void call_play(); + void unload(); protected: - // initial state enum { bank_count = 8 }; byte initial_banks [bank_count]; - int initial_pcm_dac; - double gain; - bool needs_long_frames; + nes_addr_t init_addr; + nes_addr_t play_addr; + double clock_rate_; bool pal_only; - unsigned init_addr; - unsigned play_addr; - int exp_flags; // timing + Nes_Cpu::registers_t saved_state; nes_time_t next_play; - long play_period; + nes_time_t play_period; int play_extra; - nes_time_t clock() const; - nes_time_t next_irq( nes_time_t end_time ); - static void irq_changed( void* ); + int play_ready; - // rom - int total_banks; - blargg_vector<byte> rom; - static int read_code( Nsf_Emu*, nes_addr_t ); - void unload(); - - blargg_err_t init_sound(); + enum { rom_begin = 0x8000 }; + enum { bank_select_addr = 0x5FF8 }; + enum { bank_size = 0x1000 }; + Rom_Data<bank_size> rom; - // expansion sound - - class Nes_Namco_Apu* namco; - static int read_namco( Nsf_Emu*, nes_addr_t ); - static void write_namco( Nsf_Emu*, nes_addr_t, int ); - static void write_namco_addr( Nsf_Emu*, nes_addr_t, int ); +public: private: friend class Nes_Cpu; + void cpu_jsr( nes_addr_t ); + int cpu_read( nes_addr_t ); + void cpu_write( nes_addr_t, int ); + void cpu_write_misc( nes_addr_t, int ); + enum { badop_addr = bank_select_addr }; - class Nes_Vrc6_Apu* vrc6; - static void write_vrc6( Nsf_Emu*, nes_addr_t, int ); - - class Nes_Fme7_Apu* fme7; - static void write_fme7( Nsf_Emu*, nes_addr_t, int ); - - // large objects +private: + class Nes_Namco_Apu* namco; + class Nes_Vrc6_Apu* vrc6; + class Nes_Fme7_Apu* fme7; + Nes_Apu apu; + static int pcm_read( void*, nes_addr_t ); + blargg_err_t init_sound(); header_t header_; - // cpu - Nes_Cpu cpu; - void cpu_jsr( unsigned pc, int adj ); - static int read_low_mem( Nsf_Emu*, nes_addr_t ); - static void write_low_mem( Nsf_Emu*, nes_addr_t, int ); - static int read_unmapped( Nsf_Emu*, nes_addr_t ); - static void write_unmapped( Nsf_Emu*, nes_addr_t, int ); - static void write_exram( Nsf_Emu*, nes_addr_t, int ); - - // apu - Nes_Apu apu; - static int read_snd( Nsf_Emu*, nes_addr_t ); - static void write_snd( Nsf_Emu*, nes_addr_t, int ); - static int pcm_read( void*, nes_addr_t ); - - // sram - enum { sram_size = 0x2000 }; - byte sram [sram_size]; - static int read_sram( Nsf_Emu*, nes_addr_t ); - static void write_sram( Nsf_Emu*, nes_addr_t, int ); + enum { sram_addr = 0x6000 }; + byte sram [0x2000]; + byte unmapped_code [Nes_Cpu::page_size + 8]; }; #endif -
--- a/src/console/Nsfe_Emu.cxx Wed Nov 29 14:42:11 2006 -0800 +++ b/src/console/Nsfe_Emu.cxx Thu Nov 30 19:54:33 2006 -0800 @@ -1,10 +1,10 @@ - -// Game_Music_Emu 0.3.0. http://www.slack.net/~ant/ +// Game_Music_Emu 0.5.1. http://www.slack.net/~ant/ #include "Nsfe_Emu.h" #include "blargg_endian.h" #include <string.h> +#include <ctype.h> /* Copyright (C) 2005-2006 Shay Green. This module is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser @@ -12,84 +12,61 @@ 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_BEGIN +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 */ -#define NSFE_TAG( a, b, c, d ) (d*0x1000000L + c*0x10000L + b*0x100L + a) +#include "blargg_source.h" -Nsfe_Info::Nsfe_Info() -{ - playlist_enabled = false; -} +Nsfe_Info::Nsfe_Info() { playlist_disabled = false; } Nsfe_Info::~Nsfe_Info() { } -void Nsfe_Info::enable_playlist( bool b ) -{ - playlist_enabled = b; - info_.track_count = (b && playlist_size()) ? playlist_size() : track_count_; -} - -int Nsfe_Info::remap_track( int i ) const +inline void Nsfe_Info::unload() { - if ( !playlist_enabled || !playlist_size() ) - return i; - - return playlist_entry( i ); -} - -void Nsfe_Emu::start_track( int i ) -{ - Nsf_Emu::start_track( remap_track( i ) ); + track_name_data.clear(); + track_names.clear(); + playlist.clear(); + track_times.clear(); } -const char* Nsfe_Info::track_name( unsigned i ) const +// TODO: if no playlist, treat as if there is a playlist that is just 1,2,3,4,5... ? +void Nsfe_Info::disable_playlist( bool b ) { - i = remap_track( i ); - if ( i < track_names.size() ) - return track_names [i]; - - return ""; + playlist_disabled = b; + info.track_count = playlist.size(); + if ( !info.track_count || playlist_disabled ) + info.track_count = actual_track_count_; } -long Nsfe_Info::track_time( unsigned i ) const +int Nsfe_Info::remap_track( int track ) const { - i = remap_track( i ); - if ( i < track_times.size() ) - return track_times [i]; - - return 0; -} - -// Read little-endian 32-bit int -static blargg_err_t read_le32( Emu_Reader& in, long* out ) -{ - unsigned char buf [4]; - BLARGG_RETURN_ERR( in.read( buf, sizeof buf ) ); - *out = get_le32( buf ); - return blargg_success; + if ( !playlist_disabled && (unsigned) track < playlist.size() ) + track = playlist [track]; + return track; } // Read multiple strings and separate into individual strings -static blargg_err_t read_strs( Emu_Reader& in, long size, std::vector<char>& chars, - std::vector<const char*>& strs ) +static blargg_err_t read_strs( Data_Reader& in, long size, blargg_vector<char>& chars, + blargg_vector<const char*>& strs ) { - chars.resize( size + 1 ); + RETURN_ERR( chars.resize( size + 1 ) ); chars [size] = 0; // in case last string doesn't have terminator - BLARGG_RETURN_ERR( in.read( &chars [0], size ) ); + RETURN_ERR( in.read( &chars [0], size ) ); + RETURN_ERR( strs.resize( 128 ) ); + int count = 0; for ( int i = 0; i < size; i++ ) { - strs.push_back( &chars [i] ); + if ( (int) strs.size() <= count ) + RETURN_ERR( strs.resize( count * 2 ) ); + strs [count++] = &chars [i]; while ( i < size && chars [i] ) i++; } - return blargg_success; + return strs.resize( count ); } // Copy in to out, where out has out_max characters allocated. Truncate to @@ -100,21 +77,28 @@ strncpy( out, in, out_max - 1 ); } -struct nsfe_info_t { - unsigned char load_addr [2]; - unsigned char init_addr [2]; - unsigned char play_addr [2]; - unsigned char speed_flags; - unsigned char chip_flags; - unsigned char track_count; - unsigned char first_track; +struct nsfe_info_t +{ + byte load_addr [2]; + byte init_addr [2]; + byte play_addr [2]; + byte speed_flags; + byte chip_flags; + byte track_count; + byte first_track; + byte unused [6]; }; +BOOST_STATIC_ASSERT( sizeof (nsfe_info_t) == 16 ); -blargg_err_t Nsfe_Info::load( const header_t& nsfe_tag, Emu_Reader& in, Nsf_Emu* nsf_emu ) +blargg_err_t Nsfe_Info::load( Data_Reader& in, Nsf_Emu* nsf_emu ) { // check header - if ( memcmp( nsfe_tag.tag, "NSFE", 4 ) ) - return "Not an NSFE file"; + byte signature [4]; + blargg_err_t err = in.read( signature, sizeof signature ); + if ( err ) + return (err == in.eof_error ? gme_wrong_file_type : err); + if ( memcmp( signature, "NSFE", 4 ) ) + return gme_wrong_file_type; // free previous info track_name_data.clear(); @@ -136,7 +120,7 @@ 0, 0, // flags {0,0,0,0} // unused }; - Nsf_Emu::header_t& header = info_; + Nsf_Emu::header_t& header = info; header = base_header; // parse tags @@ -144,83 +128,82 @@ while ( phase != 3 ) { // read size and tag - long size = 0; - long tag = 0; - BLARGG_RETURN_ERR( read_le32( in, &size ) ); - BLARGG_RETURN_ERR( read_le32( in, &tag ) ); + byte block_header [2] [4]; + RETURN_ERR( in.read( block_header, sizeof block_header ) ); + blargg_long size = get_le32( block_header [0] ); + blargg_long tag = get_le32( block_header [1] ); + + //dprintf( "tag: %c%c%c%c\n", char(tag), char(tag>>8), char(tag>>16), char(tag>>24) ); switch ( tag ) { - case NSFE_TAG('I','N','F','O'): { + case BLARGG_4CHAR('O','F','N','I'): { check( phase == 0 ); if ( size < 8 ) - return "Bad NSFE file"; + return "Corrupt file"; - nsfe_info_t info; - info.track_count = 1; - info.first_track = 0; + nsfe_info_t finfo; + finfo.track_count = 1; + finfo.first_track = 0; - int s = size; - if ( s > (int) sizeof info ) - s = sizeof info; - BLARGG_RETURN_ERR( in.read( &info, s ) ); - BLARGG_RETURN_ERR( in.skip( size - s ) ); + RETURN_ERR( in.read( &finfo, min( size, (blargg_long) sizeof finfo ) ) ); + if ( size > (int) sizeof finfo ) + RETURN_ERR( in.skip( size - sizeof finfo ) ); phase = 1; - info_.speed_flags = info.speed_flags; - info_.chip_flags = info.chip_flags; - info_.track_count = info.track_count; - this->track_count_ = info.track_count; - info_.first_track = info.first_track; - std::memcpy( info_.load_addr, info.load_addr, 2 * 3 ); + info.speed_flags = finfo.speed_flags; + info.chip_flags = finfo.chip_flags; + info.track_count = finfo.track_count; + this->actual_track_count_ = finfo.track_count; + info.first_track = finfo.first_track; + memcpy( info.load_addr, finfo.load_addr, 2 * 3 ); break; } - case NSFE_TAG('B','A','N','K'): - if ( size > (int) sizeof info_.banks ) - return "Bad NSFE file"; - BLARGG_RETURN_ERR( in.read( info_.banks, size ) ); + case BLARGG_4CHAR('K','N','A','B'): + if ( size > (int) sizeof info.banks ) + return "Corrupt file"; + RETURN_ERR( in.read( info.banks, size ) ); break; - case NSFE_TAG('a','u','t','h'): { - std::vector<char> chars; - std::vector<const char*> strs; - BLARGG_RETURN_ERR( read_strs( in, size, chars, strs ) ); + case BLARGG_4CHAR('h','t','u','a'): { + blargg_vector<char> chars; + blargg_vector<const char*> strs; + RETURN_ERR( read_strs( in, size, chars, strs ) ); int n = strs.size(); if ( n > 3 ) - copy_str( strs [3], info_.ripper, sizeof info_.ripper ); + copy_str( strs [3], info.dumper, sizeof info.dumper ); if ( n > 2 ) - copy_str( strs [2], info_.copyright, sizeof info_.copyright ); + copy_str( strs [2], info.copyright, sizeof info.copyright ); if ( n > 1 ) - copy_str( strs [1], info_.author, sizeof info_.author ); + copy_str( strs [1], info.author, sizeof info.author ); if ( n > 0 ) - copy_str( strs [0], info_.game, sizeof info_.game ); + copy_str( strs [0], info.game, sizeof info.game ); break; } - case NSFE_TAG('t','i','m','e'): { - track_times.resize( size / 4 ); - for ( unsigned i = 0; i < track_times.size(); i++ ) - BLARGG_RETURN_ERR( read_le32( in, &track_times [i] ) ); + case BLARGG_4CHAR('e','m','i','t'): + RETURN_ERR( track_times.resize( size / 4 ) ); + RETURN_ERR( in.read( track_times.begin(), track_times.size() * 4 ) ); break; - } - case NSFE_TAG('t','l','b','l'): - BLARGG_RETURN_ERR( read_strs( in, size, track_name_data, track_names ) ); + case BLARGG_4CHAR('l','b','l','t'): + RETURN_ERR( read_strs( in, size, track_name_data, track_names ) ); break; - case NSFE_TAG('p','l','s','t'): - playlist.resize( size ); - BLARGG_RETURN_ERR( in.read( &playlist [0], size ) ); + case BLARGG_4CHAR('t','s','l','p'): + RETURN_ERR( playlist.resize( size ) ); + RETURN_ERR( in.read( &playlist [0], size ) ); break; - case NSFE_TAG('D','A','T','A'): { + case BLARGG_4CHAR('A','T','A','D'): { check( phase == 1 ); phase = 2; + disable_playlist( false ); if ( !nsf_emu ) { in.skip( size ); @@ -228,41 +211,118 @@ else { Subset_Reader sub( &in, size ); // limit emu to nsf data - BLARGG_RETURN_ERR( nsf_emu->load( info_, sub ) ); - check( sub.remain() == 0 ); + Remaining_Reader rem( &header, sizeof header, &sub ); + RETURN_ERR( nsf_emu->load( rem ) ); + check( rem.remain() == 0 ); } + disable_playlist( false ); // TODO: fix this crappy hack (unload() disables playlist) break; } - case NSFE_TAG('N','E','N','D'): + case BLARGG_4CHAR('D','N','E','N'): check( phase == 2 ); phase = 3; break; default: // tags that can be skipped start with a lowercase character - check( std::islower( (tag >> 24) & 0xff ) ); - BLARGG_RETURN_ERR( in.skip( size ) ); + check( islower( (tag >> 24) & 0xFF ) ); + RETURN_ERR( in.skip( size ) ); break; } } - enable_playlist( playlist_enabled ); + return 0; +} + +blargg_err_t Nsfe_Info::track_info_( track_info_t* out, int track ) const +{ + int remapped = remap_track( track ); + if ( (unsigned) remapped < track_times.size() ) + { + long length = (BOOST::int32_t) get_le32( track_times [remapped] ); + if ( length > 0 ) + out->length = length; + } + if ( (unsigned) remapped < track_names.size() ) + Gme_File::copy_field_( out->song, track_names [remapped] ); - return blargg_success; + GME_COPY_FIELD( info, out, game ); + GME_COPY_FIELD( info, out, author ); + GME_COPY_FIELD( info, out, copyright ); + GME_COPY_FIELD( info, out, dumper ); + return 0; +} + +Nsfe_Emu::Nsfe_Emu() +{ + loading = false; + set_type( gme_nsfe_type ); +} + +Nsfe_Emu::~Nsfe_Emu() { } + +void Nsfe_Emu::unload() +{ + if ( !loading ) + info.unload(); // TODO: extremely hacky! + Nsf_Emu::unload(); +} + +blargg_err_t Nsfe_Emu::track_info_( track_info_t* out, int track ) const +{ + return info.track_info_( out, track ); } -blargg_err_t Nsfe_Info::load( Emu_Reader& in, Nsf_Emu* nsf_emu ) +struct Nsfe_File : Gme_Info_ { - header_t h; - BLARGG_RETURN_ERR( in.read( &h, sizeof h ) ); - return load( h, in, nsf_emu ); + Nsfe_Info info; + + Nsfe_File() { set_type( gme_nsfe_type ); } + + blargg_err_t load_( Data_Reader& in ) + { + RETURN_ERR( info.load( in, 0 ) ); + set_track_count( info.info.track_count ); + return 0; + } + + blargg_err_t track_info_( track_info_t* out, int track ) const + { + return info.track_info_( out, track ); + } +}; + +static Music_Emu* new_nsfe_emu () { return BLARGG_NEW Nsfe_Emu ; } +static Music_Emu* new_nsfe_file() { return BLARGG_NEW Nsfe_File; } + +gme_type_t_ const gme_nsfe_type [1] = { "Nintendo NES", 0, &new_nsfe_emu, &new_nsfe_file, "NSFE", 1 }; + +blargg_err_t Nsfe_Emu::load_( Data_Reader& in ) +{ + if ( loading ) + return Nsf_Emu::load_( in ); + + // TODO: this hacky recursion-avoidance could have subtle problems + loading = true; + blargg_err_t err = info.load( in, this ); + loading = false; + return err; } -blargg_err_t Nsfe_Info::load_file( const char* path, Nsf_Emu* emu ) +void Nsfe_Emu::disable_playlist( bool b ) { - Std_File_Reader in; - BLARGG_RETURN_ERR( in.open( path ) ); - return load( in, emu ); + info.disable_playlist( b ); + set_track_count( info.info.track_count ); } +void Nsfe_Emu::clear_playlist_() +{ + disable_playlist(); + Nsf_Emu::clear_playlist_(); +} + +blargg_err_t Nsfe_Emu::start_track_( int track ) +{ + return Nsf_Emu::start_track_( info.remap_track( track ) ); +}
--- a/src/console/Nsfe_Emu.h Wed Nov 29 14:42:11 2006 -0800 +++ b/src/console/Nsfe_Emu.h Thu Nov 30 19:54:33 2006 -0800 @@ -1,103 +1,68 @@ - -// Nintendo Entertainment System (NES) NSFE-format game music file emulator +// Nintendo NES/Famicom NSFE music file emulator -// Game_Music_Emu 0.3.0 - +// Game_Music_Emu 0.5.1 #ifndef NSFE_EMU_H #define NSFE_EMU_H #include "blargg_common.h" #include "Nsf_Emu.h" -// to do: eliminate dependence on bloated std vector -#include <vector> - +// Allows reading info from NSFE file without creating emulator class Nsfe_Info { public: - struct header_t - { - char tag [4]; // 'N', 'S', 'F', 'E' - }; - BOOST_STATIC_ASSERT( sizeof (header_t) == 4 ); + blargg_err_t load( Data_Reader&, Nsf_Emu* ); - // Load NSFE info and optionally load file into Nsf_Emu - blargg_err_t load_file( const char* path, Nsf_Emu* = 0 ); - - // Load NSFE info and optionally load file into Nsf_Emu - blargg_err_t load( Data_Reader&, Nsf_Emu* = 0 ); - - // Load NSFE info and optionally load file into Nsf_Emu - blargg_err_t load( header_t const&, Data_Reader&, Nsf_Emu* = 0 ); - - // Information about current file struct info_t : Nsf_Emu::header_t { - // These (longer) fields hide those in Nsf_Emu::header_t - char game [256]; - char author [256]; + char game [256]; + char author [256]; char copyright [256]; - char ripper [256]; - }; - const info_t& info() const { return info_; } - - // All track indicies are 0-based + char dumper [256]; + } info; - // Name of track [i], or "" if none available - const char* track_name( unsigned i ) const; + void disable_playlist( bool = true ); - // Duration of track [i] in milliseconds, negative if endless, or 0 if none available - long track_time( unsigned i ) const; + blargg_err_t track_info_( track_info_t* out, int track ) const; - // Optional playlist consisting of track indicies - int playlist_size() const { return playlist.size(); } - int playlist_entry( int i ) const { return playlist [i]; } + int remap_track( int i ) const; - // If true and playlist is present in NSFE file, remap track numbers using it - void enable_playlist( bool = true ); + void unload(); -public: Nsfe_Info(); ~Nsfe_Info(); - int track_count() const { return info_.track_count; } private: - std::vector<char> track_name_data; - std::vector<const char*> track_names; - std::vector<unsigned char> playlist; - std::vector<long> track_times; - int track_count_; - info_t info_; - bool playlist_enabled; - - int remap_track( int i ) const; - friend class Nsfe_Emu; + blargg_vector<char> track_name_data; + blargg_vector<const char*> track_names; + blargg_vector<unsigned char> playlist; + blargg_vector<char [4]> track_times; + int actual_track_count_; + bool playlist_disabled; }; -class Nsfe_Emu : public Nsf_Emu, public Nsfe_Info { +class Nsfe_Emu : public Nsf_Emu { public: - // See Nsf_Emu.h for further information - - Nsfe_Emu( double gain = 1.4 ) : Nsf_Emu( gain ) { } - - typedef Nsfe_Info::header_t header_t; - - // Load NSFE data - blargg_err_t load( Emu_Reader& r ) { return Nsfe_Info::load( r, this ); } - - // Load NSFE using already-loaded header and remaining data - blargg_err_t load( header_t const& h, Emu_Reader& r ) { return Nsfe_Info::load( h, r, this ); } + static gme_type_t static_type() { return gme_nsfe_type; } public: - Nsf_Emu::track_count; - Nsf_Emu::load_file; - void start_track( int ); - void enable_playlist( bool = true ); + // deprecated + struct header_t { char tag [4]; }; + Music_Emu::load; + blargg_err_t load( header_t const& h, Data_Reader& in ) // use Remaining_Reader + { return load_remaining_( &h, sizeof h, in ); } + void disable_playlist( bool = true ); // use clear_playlist() + +public: + Nsfe_Emu(); + ~Nsfe_Emu(); +protected: + blargg_err_t load_( Data_Reader& ); + blargg_err_t track_info_( track_info_t*, int track ) const; + blargg_err_t start_track_( int ); + void unload(); + void clear_playlist_(); +private: + Nsfe_Info info; + bool loading; }; -inline void Nsfe_Emu::enable_playlist( bool b ) -{ - Nsfe_Info::enable_playlist( b ); - set_track_count( info().track_count ); -} - #endif -
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/console/Sap_Apu.cxx Thu Nov 30 19:54:33 2006 -0800 @@ -0,0 +1,334 @@ +// Game_Music_Emu 0.5.1. http://www.slack.net/~ant/ + +#include "Sap_Apu.h" + +#include <string.h> + +/* Copyright (C) 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" + +int const max_frequency = 12000; // pure waves above this frequency are silenced + +static void gen_poly( blargg_ulong mask, int count, byte* out ) +{ + blargg_ulong n = 1; + do + { + int bits = 0; + int b = 0; + do + { + // implemented using "Galios configuration" + bits |= (n & 1) << b; + n = (n >> 1) ^ (mask & -(n & 1)); + } + while ( b++ < 7 ); + *out++ = bits; + } + while ( --count ); +} + +// poly5 +int const poly5_len = (1 << 5) - 1; +blargg_ulong const poly5_mask = (1UL << poly5_len) - 1; +blargg_ulong const poly5 = 0x167C6EA1; + +inline blargg_ulong run_poly5( blargg_ulong in, int shift ) +{ + return (in << shift & poly5_mask) | (in >> (poly5_len - shift)); +} + +#define POLY_MASK( width, tap1, tap2 ) \ + ((1UL << (width - 1 - tap1)) | (1UL << (width - 1 - tap2))) + +Sap_Apu_Impl::Sap_Apu_Impl() +{ + gen_poly( POLY_MASK( 4, 1, 0 ), sizeof poly4, poly4 ); + gen_poly( POLY_MASK( 9, 5, 0 ), sizeof poly9, poly9 ); + gen_poly( POLY_MASK( 17, 5, 0 ), sizeof poly17, poly17 ); + + if ( 0 ) // comment out to recauculate poly5 constant + { + byte poly5 [4]; + gen_poly( POLY_MASK( 5, 2, 0 ), sizeof poly5, poly5 ); + blargg_ulong n = poly5 [3] * 0x1000000L + poly5 [2] * 0x10000L + + poly5 [1] * 0x100L + poly5 [0]; + blargg_ulong rev = n & 1; + for ( int i = 1; i < poly5_len; i++ ) + rev |= (n >> i & 1) << (poly5_len - i); + dprintf( "poly5: 0x%08lX\n", rev ); + } +} + +Sap_Apu::Sap_Apu() +{ + impl = 0; + for ( int i = 0; i < osc_count; i++ ) + osc_output( i, 0 ); +} + +void Sap_Apu::reset( Sap_Apu_Impl* new_impl ) +{ + impl = new_impl; + last_time = 0; + poly5_pos = 0; + poly4_pos = 0; + polym_pos = 0; + control = 0; + + for ( int i = 0; i < osc_count; i++ ) + memset( &oscs [i], 0, offsetof (osc_t,output) ); +} + +inline void Sap_Apu::calc_periods() +{ + // 15/64 kHz clock + int divider = 28; + if ( this->control & 1 ) + divider = 114; + + for ( int i = 0; i < osc_count; i++ ) + { + osc_t* const osc = &oscs [i]; + + int const osc_reload = osc->regs [0]; // cache + blargg_long period = (osc_reload + 1) * divider; + static byte const fast_bits [osc_count] = { 1 << 6, 1 << 4, 1 << 5, 1 << 3 }; + if ( this->control & fast_bits [i] ) + { + period = osc_reload + 4; + if ( i & 1 ) + { + period = osc_reload * 0x100L + osc [-1].regs [0] + 7; + if ( !(this->control & fast_bits [i - 1]) ) + period = (period - 6) * divider; + + if ( (osc [-1].regs [1] & 0x1F) > 0x10 ) + dprintf( "Use of slave channel in 16-bit mode not supported\n" ); + } + } + osc->period = period; + } +} + +void Sap_Apu::run_until( blip_time_t end_time ) +{ + calc_periods(); + Sap_Apu_Impl* const impl = this->impl; // cache + + // 17/9-bit poly selection + byte const* polym = impl->poly17; + int polym_len = poly17_len; + if ( this->control & 0x80 ) + { + polym_len = poly9_len; + polym = impl->poly9; + } + polym_pos %= polym_len; + + for ( int i = 0; i < osc_count; i++ ) + { + osc_t* const osc = &oscs [i]; + blip_time_t time = last_time + osc->delay; + blip_time_t const period = osc->period; + + // output + Blip_Buffer* output = osc->output; + if ( output ) + { + output->set_modified(); + + int const osc_control = osc->regs [1]; // cache + int volume = (osc_control & 0x0F) * 2; + if ( !volume || osc_control & 0x10 || // silent, DAC mode, or inaudible frequency + ((osc_control & 0xA0) == 0xA0 && period < 1789773 / 2 / max_frequency) ) + { + if ( !(osc_control & 0x10) ) + volume >>= 1; // inaudible frequency = half volume + + int delta = volume - osc->last_amp; + if ( delta ) + { + osc->last_amp = volume; + impl->synth.offset( last_time, delta, output ); + } + + // TODO: doesn't maintain high pass flip-flop (very minor issue) + } + else + { + // high pass + static byte const hipass_bits [osc_count] = { 1 << 2, 1 << 1, 0, 0 }; + blip_time_t period2 = 0; // unused if no high pass + blip_time_t time2 = end_time; + if ( this->control & hipass_bits [i] ) + { + period2 = osc [2].period; + time2 = last_time + osc [2].delay; + if ( osc->invert ) + { + // trick inner wave loop into inverting output + osc->last_amp -= volume; + volume = -volume; + } + } + + if ( time < end_time || time2 < end_time ) + { + // poly source + static byte const poly1 [] = { 0x55, 0x55 }; // square wave + byte const* poly = poly1; + int poly_len = 8 * sizeof poly1; // can be just 2 bits, but this is faster + int poly_pos = osc->phase & 1; + int poly_inc = 1; + if ( !(osc_control & 0x20) ) + { + poly = polym; + poly_len = polym_len; + poly_pos = polym_pos; + if ( osc_control & 0x40 ) + { + poly = impl->poly4; + poly_len = poly4_len; + poly_pos = poly4_pos; + } + poly_inc = period % poly_len; + poly_pos = (poly_pos + osc->delay) % poly_len; + } + poly_inc -= poly_len; // allows more optimized inner loop below + + // square/poly5 wave + blargg_ulong wave = poly5; + check( poly5 & 1 ); // low bit is set for pure wave + int poly5_inc = 0; + if ( !(osc_control & 0x80) ) + { + wave = run_poly5( wave, (osc->delay + poly5_pos) % poly5_len ); + poly5_inc = period % poly5_len; + } + + // Run wave and high pass interleved with each catching up to the other. + // Disabled high pass has no performance effect since inner wave loop + // makes no compromise for high pass, and only runs once in that case. + int osc_last_amp = osc->last_amp; + do + { + // run high pass + if ( time2 < time ) + { + int delta = -osc_last_amp; + if ( volume < 0 ) + delta += volume; + if ( delta ) + { + osc_last_amp += delta - volume; + volume = -volume; + impl->synth.offset( time2, delta, output ); + } + } + while ( time2 <= time ) // must advance *past* time to avoid hang + time2 += period2; + + // run wave + blip_time_t end = end_time; + if ( end > time2 ) + end = time2; + while ( time < end ) + { + if ( wave & 1 ) + { + int amp = volume & -(poly [poly_pos >> 3] >> (poly_pos & 7) & 1); + if ( (poly_pos += poly_inc) < 0 ) + poly_pos += poly_len; + int delta = amp - osc_last_amp; + if ( delta ) + { + osc_last_amp = amp; + impl->synth.offset( time, delta, output ); + } + } + wave = run_poly5( wave, poly5_inc ); + time += period; + } + } + while ( time < end_time || time2 < end_time ); + + osc->phase = poly_pos; + osc->last_amp = osc_last_amp; + } + + osc->invert = 0; + if ( volume < 0 ) + { + // undo inversion trickery + osc->last_amp -= volume; + osc->invert = 1; + } + } + } + + // maintain divider + blip_time_t remain = end_time - time; + if ( remain > 0 ) + { + blargg_long count = (remain + period - 1) / period; + osc->phase ^= count; + time += count * period; + } + osc->delay = time - end_time; + } + + // advance polies + blip_time_t duration = end_time - last_time; + last_time = end_time; + poly4_pos = (poly4_pos + duration) % poly4_len; + poly5_pos = (poly5_pos + duration) % poly5_len; + polym_pos += duration; // will get %'d on next call +} + +void Sap_Apu::write_data( blip_time_t time, unsigned addr, int data ) +{ + run_until( time ); + int i = (addr ^ 0xD200) >> 1; + if ( i < osc_count ) + { + oscs [i].regs [addr & 1] = data; + } + else if ( addr == 0xD208 ) + { + control = data; + } + else if ( addr == 0xD209 ) + { + oscs [0].delay = 0; + oscs [1].delay = 0; + oscs [2].delay = 0; + oscs [3].delay = 0; + } + /* + // TODO: are polynomials reset in this case? + else if ( addr == 0xD20F ) + { + if ( (data & 3) == 0 ) + polym_pos = 0; + } + */ +} + +void Sap_Apu::end_frame( blip_time_t end_time ) +{ + if ( end_time > last_time ) + run_until( end_time ); + + last_time -= end_time; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/console/Sap_Apu.h Thu Nov 30 19:54:33 2006 -0800 @@ -0,0 +1,77 @@ +// Atari POKEY sound chip emulator + +// Game_Music_Emu 0.5.1 +#ifndef SAP_APU_H +#define SAP_APU_H + +#include "blargg_common.h" +#include "Blip_Buffer.h" + +class Sap_Apu_Impl; + +class Sap_Apu { +public: + enum { osc_count = 4 }; + void osc_output( int index, Blip_Buffer* ); + + void reset( Sap_Apu_Impl* ); + + enum { start_addr = 0xD200 }; + enum { end_addr = 0xD209 }; + void write_data( blip_time_t, unsigned addr, int data ); + + void end_frame( blip_time_t ); + +public: + Sap_Apu(); +private: + struct osc_t + { + unsigned char regs [2]; + unsigned char phase; + unsigned char invert; + int last_amp; + blip_time_t delay; + blip_time_t period; // always recalculated before use; here for convenience + Blip_Buffer* output; + }; + osc_t oscs [osc_count]; + Sap_Apu_Impl* impl; + blip_time_t last_time; + int poly5_pos; + int poly4_pos; + int polym_pos; + int control; + + void calc_periods(); + void run_until( blip_time_t ); + + enum { poly4_len = (1L << 4) - 1 }; + enum { poly9_len = (1L << 9) - 1 }; + enum { poly17_len = (1L << 17) - 1 }; + friend class Sap_Apu_Impl; +}; + +// Common tables and Blip_Synth that can be shared among multiple Sap_Apu objects +class Sap_Apu_Impl { +public: + Blip_Synth<blip_good_quality,1> synth; + + Sap_Apu_Impl(); + void volume( double d ) { synth.volume( 1.0 / Sap_Apu::osc_count / 30 * d ); } + +private: + typedef unsigned char byte; + byte poly4 [Sap_Apu::poly4_len / 8 + 1]; + byte poly9 [Sap_Apu::poly9_len / 8 + 1]; + byte poly17 [Sap_Apu::poly17_len / 8 + 1]; + friend class Sap_Apu; +}; + +inline void Sap_Apu::osc_output( int i, Blip_Buffer* b ) +{ + assert( (unsigned) i < osc_count ); + oscs [i].output = b; +} + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/console/Sap_Cpu.cxx Thu Nov 30 19:54:33 2006 -0800 @@ -0,0 +1,1010 @@ +// Game_Music_Emu 0.5.1. http://www.slack.net/~ant/ + +#include "Sap_Cpu.h" + +#include <limits.h> +#include "blargg_endian.h" + +//#include "nes_cpu_log.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 +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 */ + +#define SYNC_TIME() (void) (s.time = s_time) +#define RELOAD_TIME() (void) (s_time = s.time) + +#include "sap_cpu_io.h" + +#ifndef CPU_DONE + #define CPU_DONE( cpu, time, result_out ) { result_out = -1; } +#endif + +#include "blargg_source.h" + +int const st_n = 0x80; +int const st_v = 0x40; +int const st_r = 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 Sap_Cpu::reset( void* new_mem ) +{ + check( state == &state_ ); + state = &state_; + mem = (uint8_t*) new_mem; + r.status = st_i; + r.sp = 0xFF; + r.pc = 0; + r.a = 0; + r.x = 0; + r.y = 0; + state_.time = 0; + state_.base = 0; + irq_time_ = future_sap_time; + end_time_ = future_sap_time; + + blargg_verify_byte_order(); +} + +#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 ) (mem [int (addr)]) +#define WRITE_LOW( addr, data ) (void) (READ_LOW( addr ) = (data)) +#define READ_PROG( addr ) (READ_LOW( 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 Sap_Cpu::run( sap_time_t end_time ) +{ + bool illegal_encountered = false; + set_end_time( end_time ); + state_t s = this->state_; + this->state = &s; + fint32 s_time = s.time; + uint8_t* const mem = this->mem; // cache + + // registers + fuint16 pc = r.pc; + fuint8 a = r.a; + fuint8 x = r.x; + fuint8 y = r.y; + fuint16 sp; + SET_SP( r.sp ); + + // status flags + #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; +dec_clock_loop: + s_time--; +loop: + + #ifndef NDEBUG + { + sap_time_t correct = end_time_; + if ( !(status & st_i) && correct > irq_time_ ) + correct = irq_time_; + check( s.base == correct ); + } + #endif + + check( (unsigned) GET_SP() < 0x100 ); + check( (unsigned) a < 0x100 ); + check( (unsigned) x < 0x100 ); + check( (unsigned) y < 0x100 ); + + fuint8 opcode = mem [pc]; + pc++; + uint8_t const* instr = mem + pc; + + static uint8_t const clock_table [256] = + {// 0 1 2 3 4 5 6 7 8 9 A B C D E F + 0,6,2,8,3,3,5,5,3,2,2,2,4,4,6,6,// 0 + 3,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7,// 1 + 6,6,2,8,3,3,5,5,4,2,2,2,4,4,6,6,// 2 + 3,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7,// 3 + 6,6,2,8,3,3,5,5,3,2,2,2,3,4,6,6,// 4 + 3,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7,// 5 + 6,6,2,8,3,3,5,5,4,2,2,2,5,4,6,6,// 6 + 3,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7,// 7 + 2,6,2,6,3,3,3,3,2,2,2,2,4,4,4,4,// 8 + 3,6,2,6,4,4,4,4,2,5,2,5,5,5,5,5,// 9 + 2,6,2,6,3,3,3,3,2,2,2,2,4,4,4,4,// A + 3,5,2,5,4,4,4,4,2,4,2,4,4,4,4,4,// B + 2,6,2,8,3,3,5,5,2,2,2,2,4,4,6,6,// C + 3,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7,// D + 2,6,2,8,3,3,5,5,2,2,2,2,4,4,6,6,// E + 3,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7 // F + }; // 0x00 was 7 + + fuint16 data; + data = clock_table [opcode]; + if ( (s_time += data) >= 0 ) + goto possibly_out_of_time; +almost_out_of_time: + + data = *instr; + + #ifdef NES_CPU_LOG_H + nes_cpu_log( "cpu_log", pc - 1, opcode, instr [0], instr [1] ); + #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 (pc++, data += 0x100 * GET_MSB()); +#define GET_ADDR() GET_LE16( instr ) + +#define NO_PAGE_CROSSING( lsb ) +#define HANDLE_PAGE_CROSSING( lsb ) s_time += (lsb) >> 8; + +#define INC_DEC_XY( reg, n ) reg = uint8_t (nz = reg + n); goto loop; + +#define IND_Y( cross, out ) { \ + fuint16 temp = READ_LOW( data ) + y; \ + out = temp + 0x100 * READ_LOW( uint8_t (data + 1) ); \ + cross( temp ); \ + } + +#define IND_X( out ) { \ + fuint16 temp = data + x; \ + out = 0x100 * READ_LOW( uint8_t (temp + 1) ) + READ_LOW( uint8_t (temp) ); \ + } + +#define ARITH_ADDR_MODES( op ) \ +case op - 0x04: /* (ind,x) */ \ + IND_X( data ) \ + goto ptr##op; \ +case op + 0x0C: /* (ind),y */ \ + IND_Y( HANDLE_PAGE_CROSSING, data ) \ + goto ptr##op; \ +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: \ + HANDLE_PAGE_CROSSING( data ); \ +case op + 0x08: /* abs */ \ + ADD_PAGE \ +ptr##op: \ + SYNC_TIME(); \ + data = READ( data ); \ + RELOAD_TIME(); \ +case op + 0x04: /* imm */ \ +imm##op: \ + +#define BRANCH( cond ) \ +{ \ + fint16 offset = (BOOST::int8_t) data; \ + fuint16 extra_clock = (++pc & 0xFF) + offset;\ + if ( !(cond) ) goto dec_clock_loop; \ + pc += offset; \ + s_time += extra_clock >> 8 & 1; \ + goto loop; \ +} + +// Often-Used + + case 0xB5: // LDA zp,x + a = nz = READ_LOW( uint8_t (data + x) ); + pc++; + goto loop; + + case 0xA5: // LDA zp + a = nz = READ_LOW( data ); + pc++; + goto loop; + + case 0xD0: // BNE + BRANCH( (uint8_t) nz ); + + 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 0x4C: // JMP abs + pc = GET_ADDR(); + goto loop; + + case 0xE8: // INX + INC_DEC_XY( x, 1 ) + + case 0x10: // BPL + BRANCH( !IS_NEG ) + + ARITH_ADDR_MODES( 0xC5 ) // CMP + nz = a - data; + pc++; + c = ~nz; + nz &= 0xFF; + goto loop; + + case 0x30: // BMI + BRANCH( IS_NEG ) + + case 0xF0: // BEQ + BRANCH( !(uint8_t) nz ); + + case 0x95: // STA zp,x + data = uint8_t (data + x); + case 0x85: // STA zp + pc++; + WRITE_LOW( data, a ); + goto loop; + + case 0xC8: // INY + INC_DEC_XY( y, 1 ) + + case 0xA8: // TAY + y = a; + nz = a; + goto loop; + + case 0x98: // TYA + a = y; + nz = y; + goto loop; + + case 0xAD:{// LDA abs + unsigned addr = GET_ADDR(); + pc += 2; + nz = READ( addr ); + a = nz; + goto loop; + } + + case 0x60: // RTS + pc = 1 + READ_LOW( sp ); + pc += 0x100 * READ_LOW( 0x100 | (sp - 0xFF) ); + sp = (sp - 0xFE) | 0x100; + goto loop; + + { + fuint16 addr; + + case 0x99: // STA abs,Y + addr = y + GET_ADDR(); + pc += 2; + if ( addr <= 0x7FF ) + { + WRITE_LOW( addr, a ); + goto loop; + } + goto sta_ptr; + + case 0x8D: // STA abs + addr = GET_ADDR(); + pc += 2; + if ( addr <= 0x7FF ) + { + WRITE_LOW( addr, a ); + goto loop; + } + goto sta_ptr; + + case 0x9D: // STA abs,X (slightly more common than STA abs) + addr = x + GET_ADDR(); + pc += 2; + if ( addr <= 0x7FF ) + { + WRITE_LOW( addr, a ); + goto loop; + } + sta_ptr: + SYNC_TIME(); + WRITE( addr, a ); + RELOAD_TIME(); + goto loop; + + case 0x91: // STA (ind),Y + IND_Y( NO_PAGE_CROSSING, addr ) + pc++; + goto sta_ptr; + + case 0x81: // STA (ind,X) + IND_X( addr ) + pc++; + goto sta_ptr; + + } + + case 0xA9: // LDA #imm + pc++; + a = data; + nz = data; + goto loop; + + // common read instructions + { + fuint16 addr; + + case 0xA1: // LDA (ind,X) + IND_X( addr ) + pc++; + goto a_nz_read_addr; + + case 0xB1:// LDA (ind),Y + addr = READ_LOW( data ) + y; + HANDLE_PAGE_CROSSING( addr ); + addr += 0x100 * READ_LOW( (uint8_t) (data + 1) ); + pc++; + a = nz = READ_PROG( addr ); + if ( (addr ^ 0x8000) <= 0x9FFF ) + goto loop; + goto a_nz_read_addr; + + case 0xB9: // LDA abs,Y + HANDLE_PAGE_CROSSING( data + y ); + addr = GET_ADDR() + y; + pc += 2; + a = nz = READ_PROG( addr ); + if ( (addr ^ 0x8000) <= 0x9FFF ) + goto loop; + goto a_nz_read_addr; + + case 0xBD: // LDA abs,X + HANDLE_PAGE_CROSSING( data + x ); + addr = GET_ADDR() + x; + pc += 2; + a = nz = READ_PROG( addr ); + if ( (addr ^ 0x8000) <= 0x9FFF ) + goto loop; + a_nz_read_addr: + SYNC_TIME(); + a = nz = READ( addr ); + RELOAD_TIME(); + goto loop; + + } + +// Branch + + case 0x50: // BVC + BRANCH( !(status & st_v) ) + + case 0x70: // BVS + BRANCH( status & st_v ) + + case 0xB0: // BCS + BRANCH( c & 0x100 ) + + case 0x90: // BCC + BRANCH( !(c & 0x100) ) + +// Load/store + + 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; + HANDLE_PAGE_CROSSING( data ); + case 0xAC:{// LDY abs + unsigned addr = data + 0x100 * GET_MSB(); + pc += 2; + SYNC_TIME(); + y = nz = READ( addr ); + RELOAD_TIME(); + goto loop; + } + + case 0xBE: // LDX abs,y + data += y; + HANDLE_PAGE_CROSSING( data ); + case 0xAE:{// LDX abs + unsigned addr = data + 0x100 * GET_MSB(); + pc += 2; + SYNC_TIME(); + x = nz = READ( addr ); + RELOAD_TIME(); + goto loop; + } + + { + fuint8 temp; + case 0x8C: // STY abs + temp = y; + goto store_abs; + + case 0x8E: // STX abs + temp = x; + store_abs: + unsigned addr = GET_ADDR(); + pc += 2; + if ( addr <= 0x7FF ) + { + WRITE_LOW( addr, temp ); + goto loop; + } + SYNC_TIME(); + WRITE( addr, temp ); + RELOAD_TIME(); + goto loop; + } + +// Compare + + case 0xEC:{// CPX abs + unsigned addr = GET_ADDR(); + pc++; + SYNC_TIME(); + data = READ( addr ); + RELOAD_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 + unsigned addr = GET_ADDR(); + pc++; + SYNC_TIME(); + data = READ( addr ); + RELOAD_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 + + 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; + + case 0x2C:{// BIT abs + unsigned addr = GET_ADDR(); + pc += 2; + status &= ~st_v; + nz = READ( addr ); + status |= nz & st_v; + if ( a & nz ) + goto loop; + nz <<= 8; // result must be zero, even if N bit is set + goto loop; + } + + case 0x24: // BIT zp + nz = READ_LOW( data ); + pc++; + status &= ~st_v; + status |= nz & st_v; + if ( a & nz ) + goto loop; + nz <<= 8; // result must be zero, even if N bit is set + goto loop; + +// Add/subtract + + ARITH_ADDR_MODES( 0xE5 ) // SBC + case 0xEB: // unofficial equivalent + data ^= 0xFF; + goto adc_imm; + + ARITH_ADDR_MODES( 0x65 ) // ADC + adc_imm: { + check( !(status & st_d) ); + fint16 carry = c >> 8 & 1; + fint16 ov = (a ^ 0x80) + carry + (BOOST::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 + SYNC_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 + nz = c >> 8 & 1; + SYNC_TIME(); + nz |= (c = READ( data ) << 1); + rotate_common: + pc++; + WRITE( data, (uint8_t) nz ); + RELOAD_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 + + case 0xCA: // DEX + INC_DEC_XY( x, -1 ) + + case 0x88: // DEY + INC_DEC_XY( 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: + SYNC_TIME(); + nz += READ( data ); + pc += 2; + WRITE( data, (uint8_t) nz ); + RELOAD_TIME(); + goto loop; + +// Transfer + + 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; + +// Stack + + case 0x48: // PHA + PUSH( a ); // verified + goto loop; + + case 0x68: // PLA + a = nz = READ_LOW( sp ); + sp = (sp - 0xFF) | 0x100; + 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 ); + this->r.status = status; // update externally-visible I flag + if ( (data ^ status) & st_i ) + { + sap_time_t new_time = end_time_; + if ( !(status & st_i) && new_time > irq_time_ ) + new_time = irq_time_; + blargg_long delta = s.base - new_time; + s.base = new_time; + s_time += delta; + } + goto loop; + } + + case 0x28:{// PLP + fuint8 temp = READ_LOW( sp ); + sp = (sp - 0xFF) | 0x100; + 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; + } + + case 0x08: { // PHP + fuint8 temp; + CALC_STATUS( temp ); + PUSH( temp | (st_b | st_r) ); + goto loop; + } + + case 0x6C:{// JMP (ind) + data = GET_ADDR(); + pc = READ_PROG( data ); + data = (data & 0xFF00) | ((data + 1) & 0xFF); + pc |= 0x100 * READ_PROG( data ); + goto loop; + } + + case 0x00: // BRK + goto handle_brk; + +// 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: { + this->r.status = status; // update externally-visible I flag + blargg_long delta = s.base - irq_time_; + if ( delta <= 0 ) + { + if ( TIME < irq_time_ ) + goto loop; + goto delayed_cli; + } + s.base = 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; + irq_time_ = s.base; // TODO: remove, as only to satisfy debug check in loop + goto loop; + } + delayed_cli: + dprintf( "Delayed CLI not emulated\n" ); + goto loop; + } + + case 0x78: // SEI + if ( status & st_i ) + goto loop; + status |= st_i; + handle_sei: { + this->r.status = status; // update externally-visible I flag + blargg_long delta = s.base - end_time_; + s.base = end_time_; + s_time += delta; + if ( s_time < 0 ) + goto loop; + dprintf( "Delayed SEI not emulated\n" ); + goto loop; + } + +// Unofficial + + // SKW - Skip word + case 0x1C: case 0x3C: case 0x5C: case 0x7C: case 0xDC: case 0xFC: + HANDLE_PAGE_CROSSING( data + x ); + case 0x0C: + pc++; + // SKB - Skip byte + case 0x74: case 0x04: case 0x14: case 0x34: case 0x44: case 0x54: case 0x64: + case 0x80: case 0x82: case 0x89: case 0xC2: case 0xD4: case 0xE2: case 0xF4: + pc++; + goto loop; + + // NOP + case 0xEA: case 0x1A: case 0x3A: case 0x5A: case 0x7A: case 0xDA: case 0xFA: + goto loop; + +// Unimplemented + + // halt + //case 0x02: case 0x12: case 0x22: case 0x32: case 0x42: case 0x52: + //case 0x62: case 0x72: case 0x92: case 0xB2: case 0xD2: case 0xF2: + + default: + assert( (unsigned) opcode <= 0xFF ); + illegal_encountered = true; + pc--; + goto stop; + } + assert( false ); + + int result_; +handle_brk: + if ( pc >= idle_addr + 1 ) + goto idle_done; + pc++; + result_ = 4; + dprintf( "BRK executed\n" ); + +interrupt: + { + s_time += 7; + + WRITE_LOW( 0x100 | (sp - 1), pc >> 8 ); + WRITE_LOW( 0x100 | (sp - 2), pc ); + pc = GET_LE16( &READ_PROG( 0xFFFA ) + result_ ); + + sp = (sp - 3) | 0x100; + fuint8 temp; + CALC_STATUS( temp ); + temp |= st_r; + if ( result_ ) + temp |= st_b; // TODO: incorrectly sets B flag for IRQ + WRITE_LOW( sp, temp ); + + status &= ~st_d; + status |= st_i; + this->r.status = status; // update externally-visible I flag + + blargg_long delta = s.base - end_time_; + s.base = end_time_; + s_time += delta; + goto loop; + } + +idle_done: + //s_time = 0; + pc--; + goto stop; +out_of_time: + pc--; + SYNC_TIME(); + CPU_DONE( this, TIME, result_ ); + RELOAD_TIME(); + if ( result_ >= 0 ) + goto interrupt; + if ( s_time < 0 ) + goto loop; + +stop: + + 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; + } + + this->state_ = s; + this->state = &this->state_; + + return illegal_encountered; +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/console/Sap_Cpu.h Thu Nov 30 19:54:33 2006 -0800 @@ -0,0 +1,83 @@ +// Atari 6502 CPU emulator + +// Game_Music_Emu 0.5.1 +#ifndef SAP_CPU_H +#define SAP_CPU_H + +#include "blargg_common.h" + +typedef blargg_long sap_time_t; // clock cycle count +typedef unsigned sap_addr_t; // 16-bit address +enum { future_sap_time = LONG_MAX / 2 + 1 }; + +class Sap_Cpu { +public: + typedef BOOST::uint8_t uint8_t; + + // Clear all registers and keep pointer to 64K memory passed in + void reset( void* mem_64k ); + + // Run until specified time is reached. Returns true if suspicious/unsupported + // instruction was encountered at any point during run. + bool run( sap_time_t end_time ); + + // Registers are not updated until run() returns (except I flag in status) + struct registers_t { + BOOST::uint16_t pc; + BOOST::uint8_t a; + BOOST::uint8_t x; + BOOST::uint8_t y; + BOOST::uint8_t status; + BOOST::uint8_t sp; + }; + registers_t r; + + enum { idle_addr = 0xFEFF }; + + // Time of beginning of next instruction to be executed + sap_time_t time() const { return state->time + state->base; } + void set_time( sap_time_t t ) { state->time = t - state->base; } + void adjust_time( int delta ) { state->time += delta; } + + sap_time_t irq_time() const { return irq_time_; } + void set_irq_time( sap_time_t ); + + sap_time_t end_time() const { return end_time_; } + void set_end_time( sap_time_t ); + +public: + Sap_Cpu() { state = &state_; } + enum { irq_inhibit = 0x04 }; +private: + struct state_t { + sap_time_t base; + sap_time_t time; + }; + state_t* state; // points to state_ or a local copy within run() + state_t state_; + sap_time_t irq_time_; + sap_time_t end_time_; + uint8_t* mem; + + inline sap_time_t update_end_time( sap_time_t end, sap_time_t irq ); +}; + +inline sap_time_t Sap_Cpu::update_end_time( sap_time_t t, sap_time_t irq ) +{ + if ( irq < t && !(r.status & irq_inhibit) ) t = irq; + sap_time_t delta = state->base - t; + state->base = t; + return delta; +} + +inline void Sap_Cpu::set_irq_time( sap_time_t t ) +{ + state->time += update_end_time( end_time_, (irq_time_ = t) ); +} + +inline void Sap_Cpu::set_end_time( sap_time_t t ) +{ + state->time += update_end_time( (end_time_ = t), irq_time_ ); +} + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/console/Sap_Emu.cxx Thu Nov 30 19:54:33 2006 -0800 @@ -0,0 +1,438 @@ +// Game_Music_Emu 0.5.1. http://www.slack.net/~ant/ + +#include "Sap_Emu.h" + +#include "blargg_endian.h" +#include <string.h> + +/* Copyright (C) 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" + +long const base_scanline_period = 114; + +Sap_Emu::Sap_Emu() +{ + set_type( gme_sap_type ); + + static const char* const names [Sap_Apu::osc_count * 2] = { + "Wave 1", "Wave 2", "Wave 3", "Wave 4", + "Wave 5", "Wave 6", "Wave 7", "Wave 8", + }; + set_voice_names( names ); + + static int const types [Sap_Apu::osc_count * 2] = { + wave_type | 1, wave_type | 2, wave_type | 3, wave_type | 0, + wave_type | 5, wave_type | 6, wave_type | 7, wave_type | 4, + }; + set_voice_types( types ); + set_silence_lookahead( 6 ); +} + +Sap_Emu::~Sap_Emu() { } + +// Track info + +// Returns 16 or greater if not hex +inline int from_hex_char( int h ) +{ + h -= 0x30; + if ( (unsigned) h > 9 ) + h = ((h - 0x11) & 0xDF) + 10; + return h; +} + +static long from_hex( byte const* in ) +{ + unsigned result = 0; + for ( int n = 4; n--; ) + { + int h = from_hex_char( *in++ ); + if ( h > 15 ) + return -1; + result = result * 0x10 + h; + } + return result; +} + +static int from_dec( byte const* in, byte const* end ) +{ + if ( in >= end ) + return -1; + + int n = 0; + while ( in < end ) + { + int dig = *in++ - '0'; + if ( (unsigned) dig > 9 ) + return -1; + n = n * 10 + dig; + } + return n; +} + +static void parse_string( byte const* in, byte const* end, int len, char* out ) +{ + byte const* start = in; + if ( *in++ == '\"' ) + { + start++; + while ( in < end && *in != '\"' ) + in++; + } + else + { + in = end; + } + len = min( len - 1, int (in - start) ); + out [len] = 0; + memcpy( out, start, len ); +} + +static blargg_err_t parse_info( byte const* in, long size, Sap_Emu::info_t* out ) +{ + out->track_count = 1; + out->author [0] = 0; + out->name [0] = 0; + out->copyright [0] = 0; + + if ( size < 16 || memcmp( in, "SAP\x0D\x0A", 5 ) ) + return gme_wrong_file_type; + + byte const* file_end = in + size - 5; + in += 5; + while ( in < file_end && (in [0] != 0xFF || in [1] != 0xFF) ) + { + byte const* line_end = in; + while ( line_end < file_end && *line_end != 0x0D ) + line_end++; + + char const* tag = (char const*) in; + while ( in < line_end && *in > ' ' ) + in++; + int tag_len = (char const*) in - tag; + + while ( in < line_end && *in <= ' ' ) in++; + + if ( tag_len <= 0 ) + { + // skip line + } + else if ( !strncmp( "INIT", tag, tag_len ) ) + { + out->init_addr = from_hex( in ); + if ( (unsigned long) out->init_addr > 0xFFFF ) + return "Invalid init address"; + } + else if ( !strncmp( "PLAYER", tag, tag_len ) ) + { + out->play_addr = from_hex( in ); + if ( (unsigned long) out->play_addr > 0xFFFF ) + return "Invalid play address"; + } + else if ( !strncmp( "MUSIC", tag, tag_len ) ) + { + out->music_addr = from_hex( in ); + if ( (unsigned long) out->music_addr > 0xFFFF ) + return "Invalid music address"; + } + else if ( !strncmp( "SONGS", tag, tag_len ) ) + { + out->track_count = from_dec( in, line_end ); + if ( out->track_count <= 0 ) + return "Invalid track count"; + } + else if ( !strncmp( "TYPE", tag, tag_len ) ) + { + switch ( out->type = *in ) + { + case 'C': + case 'B': + break; + + case 'D': + return "Digimusic not supported"; + + default: + return "Unsupported player type"; + } + } + else if ( !strncmp( "STEREO", tag, tag_len ) ) + { + out->stereo = true; + } + else if ( !strncmp( "FASTPLAY", tag, tag_len ) ) + { + out->fastplay = from_dec( in, line_end ); + if ( out->fastplay <= 0 ) + return "Invalid fastplay value"; + } + else if ( !strncmp( "AUTHOR", tag, tag_len ) ) + { + parse_string( in, line_end, sizeof out->author, out->author ); + } + else if ( !strncmp( "NAME", tag, tag_len ) ) + { + parse_string( in, line_end, sizeof out->name, out->name ); + } + else if ( !strncmp( "DATE", tag, tag_len ) ) + { + parse_string( in, line_end, sizeof out->copyright, out->copyright ); + } + + in = line_end + 2; + } + + if ( in [0] != 0xFF || in [1] != 0xFF ) + return "ROM data missing"; + out->rom_data = in + 2; + + return 0; +} + +static void copy_sap_fields( Sap_Emu::info_t const& in, track_info_t* out ) +{ + Gme_File::copy_field_( out->game, in.name ); + Gme_File::copy_field_( out->author, in.author ); + Gme_File::copy_field_( out->copyright, in.copyright ); +} + +blargg_err_t Sap_Emu::track_info_( track_info_t* out, int ) const +{ + copy_sap_fields( info, out ); + return 0; +} + +struct Sap_File : Gme_Info_ +{ + Sap_Emu::info_t info; + + Sap_File() { set_type( gme_sap_type ); } + + blargg_err_t load_mem_( byte const* begin, long size ) + { + RETURN_ERR( parse_info( begin, size, &info ) ); + set_track_count( info.track_count ); + return 0; + } + + blargg_err_t track_info_( track_info_t* out, int ) const + { + copy_sap_fields( info, out ); + return 0; + } +}; + +static Music_Emu* new_sap_emu () { return BLARGG_NEW Sap_Emu ; } +static Music_Emu* new_sap_file() { return BLARGG_NEW Sap_File; } + +gme_type_t_ const gme_sap_type [1] = { "Atari XL", 0, &new_sap_emu, &new_sap_file, "SAP", 1 }; + +// Setup + +blargg_err_t Sap_Emu::load_mem_( byte const* in, long size ) +{ + file_end = in + size; + + info.warning = 0; + info.type = 'B'; + info.stereo = false; + info.init_addr = -1; + info.play_addr = -1; + info.music_addr = -1; + info.fastplay = 312; + RETURN_ERR( parse_info( in, size, &info ) ); + + set_warning( info.warning ); + set_track_count( info.track_count ); + set_voice_count( Sap_Apu::osc_count << info.stereo ); + apu_impl.volume( gain() ); + + return setup_buffer( 1773447 ); +} + +void Sap_Emu::update_eq( blip_eq_t const& eq ) +{ + apu_impl.synth.treble_eq( eq ); +} + +void Sap_Emu::set_voice( int i, Blip_Buffer* center, Blip_Buffer* left, Blip_Buffer* right ) +{ + int i2 = i - Sap_Apu::osc_count; + if ( i2 >= 0 ) + apu2.osc_output( i2, right ); + else + apu.osc_output( i, (info.stereo ? left : center) ); +} + +// Emulation + +void Sap_Emu::set_tempo_( double t ) +{ + scanline_period = sap_time_t (base_scanline_period / t); +} + +inline sap_time_t Sap_Emu::play_period() const { return info.fastplay * scanline_period; } + +void Sap_Emu::cpu_jsr( sap_addr_t addr ) +{ + check( r.sp >= 0xFE ); // catch anything trying to leave data on stack + r.pc = addr; + int high_byte = (idle_addr - 1) >> 8; + if ( r.sp == 0xFE && mem [0x1FF] == high_byte ) + r.sp = 0xFF; // pop extra byte off + mem [0x100 + r.sp--] = high_byte; // some routines use RTI to return + mem [0x100 + r.sp--] = high_byte; + mem [0x100 + r.sp--] = idle_addr - 1; +} + +void Sap_Emu::run_routine( sap_addr_t addr ) +{ + cpu_jsr( addr ); + cpu::run( 312 * base_scanline_period * 60 ); + check( r.pc == idle_addr ); +} + +inline void Sap_Emu::call_init( int track ) +{ + switch ( info.type ) + { + case 'B': + r.a = track; + run_routine( info.init_addr ); + break; + + case 'C': + r.a = 0x70; + r.x = info.music_addr&0xFF; + r.y = info.music_addr >> 8; + run_routine( info.play_addr + 3 ); + r.a = 0; + r.x = track; + run_routine( info.play_addr + 3 ); + break; + } +} + +blargg_err_t Sap_Emu::start_track_( int track ) +{ + RETURN_ERR( Classic_Emu::start_track_( track ) ); + + memset( mem, 0, sizeof mem ); + byte const* in = info.rom_data; + while ( file_end - in >= 5 ) + { + unsigned start = get_le16( in ); + unsigned end = get_le16( in + 2 ); + //dprintf( "Block $%04X-$%04X\n", start, end ); + in += 4; + if ( end < start ) + { + set_warning( "Invalid file data block" ); + break; + } + long len = end - start + 1; + if ( len > file_end - in ) + { + set_warning( "Invalid file data block" ); + break; + } + + memcpy( mem + start, in, len ); + in += len; + if ( file_end - in >= 2 && in [0] == 0xFF && in [1] == 0xFF ) + in += 2; + } + + apu.reset( &apu_impl ); + apu2.reset( &apu_impl ); + cpu::reset( mem ); + time_mask = 0; // disables sound during init + call_init( track ); + time_mask = -1; + + next_play = play_period(); + + return 0; +} + +// Emulation + +// see sap_cpu_io.h for read/write functions + +void Sap_Emu::cpu_write_( sap_addr_t addr, int data ) +{ + if ( (addr ^ Sap_Apu::start_addr) <= (Sap_Apu::end_addr - Sap_Apu::start_addr) ) + { + apu.write_data( time() & time_mask, addr, data ); + return; + } + + if ( (addr ^ (Sap_Apu::start_addr + 0x10)) <= (Sap_Apu::end_addr - Sap_Apu::start_addr) && + info.stereo ) + { + apu2.write_data( time() & time_mask, addr ^ 0x10, data ); + return; + } + + if ( (addr & ~0x0010) != 0xD20F || data != 0x03 ) + dprintf( "Unmapped write $%04X <- $%02X\n", addr, data ); +} + +inline void Sap_Emu::call_play() +{ + switch ( info.type ) + { + case 'B': + cpu_jsr( info.play_addr ); + break; + + case 'C': + cpu_jsr( info.play_addr + 6 ); + break; + } +} + +blargg_err_t Sap_Emu::run_clocks( blip_time_t& duration, int ) +{ + set_time( 0 ); + while ( time() < duration ) + { + if ( cpu::run( duration ) || r.pc > idle_addr ) + return "Emulation error (illegal instruction)"; + + if ( r.pc == idle_addr ) + { + if ( next_play <= duration ) + { + set_time( next_play ); + next_play += play_period(); + call_play(); + } + else + { + set_time( duration ); + } + } + } + + duration = time(); + next_play -= duration; + check( next_play >= 0 ); + if ( next_play < 0 ) + next_play = 0; + apu.end_frame( duration ); + if ( info.stereo ) + apu2.end_frame( duration ); + + return 0; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/console/Sap_Emu.h Thu Nov 30 19:54:33 2006 -0800 @@ -0,0 +1,65 @@ +// Atari XL/XE SAP music file emulator + +// Game_Music_Emu 0.5.1 +#ifndef SAP_EMU_H +#define SAP_EMU_H + +#include "Classic_Emu.h" +#include "Sap_Apu.h" +#include "Sap_Cpu.h" + +class Sap_Emu : private Sap_Cpu, public Classic_Emu { + typedef Sap_Cpu cpu; +public: + static gme_type_t static_type() { return gme_sap_type; } +public: + Sap_Emu(); + ~Sap_Emu(); + struct info_t { + byte const* rom_data; + const char* warning; + long init_addr; + long play_addr; + long music_addr; + int type; + int track_count; + int fastplay; + bool stereo; + char author [256]; + char name [256]; + char copyright [ 32]; + }; +protected: + blargg_err_t track_info_( track_info_t*, int track ) const; + blargg_err_t load_mem_( byte const*, long ); + blargg_err_t start_track_( int ); + blargg_err_t run_clocks( blip_time_t&, int ); + void set_tempo_( double ); + void set_voice( int, Blip_Buffer*, Blip_Buffer*, Blip_Buffer* ); + void update_eq( blip_eq_t const& ); +public: private: friend class Sap_Cpu; + int cpu_read( sap_addr_t ); + void cpu_write( sap_addr_t, int ); + void cpu_write_( sap_addr_t, int ); +private: + info_t info; + + byte const* file_end; + sap_time_t scanline_period; + sap_time_t next_play; + sap_time_t time_mask; + Sap_Apu apu; + Sap_Apu apu2; + + // large items + byte mem [0x10000 + 0x100]; + Sap_Apu_Impl apu_impl; + + sap_time_t play_period() const; + void call_play(); + void cpu_jsr( sap_addr_t ); + void call_init( int track ); + void run_routine( sap_addr_t ); +}; + +#endif
--- a/src/console/Sms_Apu.cxx Wed Nov 29 14:42:11 2006 -0800 +++ b/src/console/Sms_Apu.cxx Thu Nov 30 19:54:33 2006 -0800 @@ -1,5 +1,4 @@ - -// Sms_Snd_Emu 0.1.3. http://www.slack.net/~ant/ +// Sms_Snd_Emu 0.1.4. http://www.slack.net/~ant/ #include "Sms_Apu.h" @@ -9,22 +8,22 @@ 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 */ +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_BEGIN +#include "blargg_source.h" // Sms_Osc Sms_Osc::Sms_Osc() { - output = NULL; - outputs [0] = NULL; // always stays NULL - outputs [1] = NULL; - outputs [2] = NULL; - outputs [3] = NULL; + output = 0; + outputs [0] = 0; // always stays NULL + outputs [1] = 0; + outputs [2] = 0; + outputs [3] = 0; } void Sms_Osc::reset() @@ -45,7 +44,7 @@ Sms_Osc::reset(); } -void Sms_Square::run( sms_time_t time, sms_time_t end_time ) +void Sms_Square::run( blip_time_t time, blip_time_t end_time ) { if ( !volume || period <= 128 ) { @@ -99,17 +98,17 @@ // Sms_Noise -static const int noise_periods [3] = { 0x100, 0x200, 0x400 }; +static int const noise_periods [3] = { 0x100, 0x200, 0x400 }; inline void Sms_Noise::reset() { period = &noise_periods [0]; shifter = 0x8000; - tap = 12; + feedback = 0x9000; Sms_Osc::reset(); } -void Sms_Noise::run( sms_time_t time, sms_time_t end_time ) +void Sms_Noise::run( blip_time_t time, blip_time_t end_time ) { int amp = volume; if ( shifter & 1 ) @@ -137,9 +136,9 @@ do { - int changed = (shifter + 1) & 2; // set if prev and next bits differ - shifter = (((shifter << 15) ^ (shifter << tap)) & 0x8000) | (shifter >> 1); - if ( changed ) + int changed = shifter + 1; + shifter = (feedback & -(shifter & 1)) ^ (shifter >> 1); + if ( changed & 2 ) // true if bits 0 and 1 differ { delta = -delta; synth.offset_inline( time, delta, output ); @@ -203,19 +202,32 @@ osc_output( i, center, left, right ); } -void Sms_Apu::reset() +void Sms_Apu::reset( unsigned feedback, int noise_width ) { - stereo_found = false; last_time = 0; latch = 0; + if ( !feedback || !noise_width ) + { + feedback = 0x0009; + noise_width = 16; + } + // convert to "Galios configuration" + looped_feedback = 1 << (noise_width - 1); + noise_feedback = 0; + while ( noise_width-- ) + { + noise_feedback = (noise_feedback << 1) | (feedback & 1); + feedback >>= 1; + } + squares [0].reset(); squares [1].reset(); squares [2].reset(); noise.reset(); } -void Sms_Apu::run_until( sms_time_t end_time ) +void Sms_Apu::run_until( blip_time_t end_time ) { require( end_time >= last_time ); // end_time must not be before previous time @@ -227,9 +239,7 @@ Sms_Osc& osc = *oscs [i]; if ( osc.output ) { - if ( osc.output != osc.outputs [3] ) - stereo_found = true; // playing on side output - + osc.output->set_modified(); if ( i < 3 ) squares [i].run( last_time, end_time ); else @@ -241,20 +251,16 @@ } } -bool Sms_Apu::end_frame( sms_time_t end_time ) +void Sms_Apu::end_frame( blip_time_t end_time ) { if ( end_time > last_time ) run_until( end_time ); assert( last_time >= end_time ); last_time -= end_time; - - bool result = stereo_found; - stereo_found = false; - return result; } -void Sms_Apu::write_ggstereo( sms_time_t time, int data ) +void Sms_Apu::write_ggstereo( blip_time_t time, int data ) { require( (unsigned) data <= 0xFF ); @@ -270,18 +276,21 @@ if ( osc.output != old_output && osc.last_amp ) { if ( old_output ) + { + old_output->set_modified(); square_synth.offset( time, -osc.last_amp, old_output ); + } osc.last_amp = 0; } } } -static const unsigned char volumes [16] = { - // volumes [i] = 64 * pow( 1.26, 15 - i ) / pow( 1.26, 15 ) +// volumes [i] = 64 * pow( 1.26, 15 - i ) / pow( 1.26, 15 ) +static unsigned char const volumes [16] = { 64, 50, 39, 31, 24, 19, 15, 12, 9, 7, 5, 4, 3, 2, 1, 0 }; -void Sms_Apu::write_data( sms_time_t time, int data ) +void Sms_Apu::write_data( blip_time_t time, int data ) { require( (unsigned) data <= 0xFF ); @@ -311,9 +320,7 @@ else noise.period = &squares [2].period; - int const tap_disabled = 16; - noise.tap = (data & 0x04) ? 12 : tap_disabled; + noise.feedback = (data & 0x04) ? noise_feedback : looped_feedback; noise.shifter = 0x8000; } } -
--- a/src/console/Sms_Apu.h Wed Nov 29 14:42:11 2006 -0800 +++ b/src/console/Sms_Apu.h Thu Nov 30 19:54:33 2006 -0800 @@ -1,13 +1,9 @@ - // Sega Master System SN76489 PSG sound chip emulator -// Sms_Snd_Emu 0.1.3 - +// Sms_Snd_Emu 0.1.4 #ifndef SMS_APU_H #define SMS_APU_H -typedef long sms_time_t; // clock cycle count - #include "Sms_Oscs.h" class Sms_Apu { @@ -34,19 +30,17 @@ void osc_output( int index, Blip_Buffer* center, Blip_Buffer* left, Blip_Buffer* right ); // Reset oscillators and internal state - void reset(); + void reset( unsigned noise_feedback = 0, int noise_width = 0 ); // Write GameGear left/right assignment byte - void write_ggstereo( sms_time_t, int ); + void write_ggstereo( blip_time_t, int ); // Write to data port - void write_data( sms_time_t, int ); + void write_data( blip_time_t, int ); // Run all oscillators up to specified time, end current frame, then - // start a new frame at time 0. Returns true if any oscillators added - // sound to one of the left/right buffers, false if they only added - // to the center buffer. - bool end_frame( sms_time_t ); + // start a new frame at time 0. + void end_frame( blip_time_t ); public: Sms_Apu(); @@ -59,12 +53,13 @@ Sms_Osc* oscs [osc_count]; Sms_Square squares [3]; Sms_Square::Synth square_synth; // used by squares - sms_time_t last_time; + blip_time_t last_time; int latch; - bool stereo_found; Sms_Noise noise; + unsigned noise_feedback; + unsigned looped_feedback; - void run_until( sms_time_t ); + void run_until( blip_time_t ); }; struct sms_apu_state_t @@ -78,4 +73,3 @@ inline void Sms_Apu::osc_output( int i, Blip_Buffer* b ) { osc_output( i, b, b, b ); } #endif -
--- a/src/console/Sms_Oscs.h Wed Nov 29 14:42:11 2006 -0800 +++ b/src/console/Sms_Oscs.h Thu Nov 30 19:54:33 2006 -0800 @@ -1,12 +1,9 @@ - // Private oscillators used by Sms_Apu -// Sms_Snd_Emu 0.1.3 - +// Sms_Snd_Emu 0.1.4 #ifndef SMS_OSCS_H #define SMS_OSCS_H -#include "blargg_common.h" #include "Blip_Buffer.h" struct Sms_Osc @@ -32,21 +29,20 @@ const Synth* synth; void reset(); - void run( sms_time_t, sms_time_t ); + void run( blip_time_t, blip_time_t ); }; struct Sms_Noise : Sms_Osc { const int* period; unsigned shifter; - unsigned tap; + unsigned feedback; typedef Blip_Synth<blip_med_quality,1> Synth; Synth synth; void reset(); - void run( sms_time_t, sms_time_t ); + void run( blip_time_t, blip_time_t ); }; #endif -
--- a/src/console/Snes_Spc.cxx Wed Nov 29 14:42:11 2006 -0800 +++ b/src/console/Snes_Spc.cxx Thu Nov 30 19:54:33 2006 -0800 @@ -1,9 +1,7 @@ - -// Game_Music_Emu 0.3.0. http://www.slack.net/~ant/ +// Game_Music_Emu 0.5.1. http://www.slack.net/~ant/ #include "Snes_Spc.h" -#include <assert.h> #include <string.h> /* Copyright (C) 2004-2006 Shay Green. This module is free software; you @@ -12,29 +10,46 @@ 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 */ +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_BEGIN +#include "blargg_source.h" // always in the future (CPU time can go over 0, but not by this much) int const timer_disabled_time = 127; Snes_Spc::Snes_Spc() : dsp( ram ), cpu( this, ram ) { - timer [0].shift = 7; // 8 kHz - timer [1].shift = 7; // 8 kHz - timer [2].shift = 4; // 64 kHz + set_tempo( 1.0 ); // Put STOP instruction past end of memory to catch PC overflow. - memset( ram + ram_size, 0xff, (sizeof ram) - ram_size ); + memset( ram + ram_size, 0xFF, (sizeof ram) - ram_size ); + + // A few tracks read from the last four bytes of IPL ROM + boot_rom [sizeof boot_rom - 2] = 0xC0; + boot_rom [sizeof boot_rom - 1] = 0xFF; + memset( boot_rom, 0, sizeof boot_rom - 2 ); +} + +void Snes_Spc::set_tempo( double t ) +{ + int unit = (int) (16.0 / t + 0.5); + + timer [0].divisor = unit * 8; // 8 kHz + timer [1].divisor = unit * 8; // 8 kHz + timer [2].divisor = unit; // 64 kHz } // Load -blargg_err_t Snes_Spc::load_spc( const void* data, long size, bool clear_echo_ ) +void Snes_Spc::set_ipl_rom( void const* in ) +{ + memcpy( boot_rom, in, sizeof boot_rom ); +} + +blargg_err_t Snes_Spc::load_spc( const void* data, long size ) { struct spc_file_t { char signature [27]; @@ -48,10 +63,11 @@ char unused2 [212]; uint8_t ram [0x10000]; uint8_t dsp [128]; + uint8_t ipl_rom [128]; }; - BOOST_STATIC_ASSERT( sizeof (spc_file_t) == spc_file_size ); + BOOST_STATIC_ASSERT( sizeof (spc_file_t) == spc_file_size + 128 ); - const spc_file_t* spc = (spc_file_t*) data; + const spc_file_t* spc = (spc_file_t const*) data; if ( size < spc_file_size ) return "Not an SPC file"; @@ -60,31 +76,30 @@ return "Not an SPC file"; registers_t regs; - regs.pc = spc->pc [1] * 0x100 + spc->pc [0]; - regs.a = spc->a; - regs.x = spc->x; - regs.y = spc->y; + regs.pc = spc->pc [1] * 0x100 + spc->pc [0]; + regs.a = spc->a; + regs.x = spc->x; + regs.y = spc->y; regs.status = spc->status; - regs.sp = spc->sp; + regs.sp = spc->sp; + + if ( (unsigned long) size >= sizeof *spc ) + set_ipl_rom( spc->ipl_rom ); const char* error = load_state( regs, spc->ram, spc->dsp ); echo_accessed = false; - if ( clear_echo_ ) - clear_echo(); - return error; } void Snes_Spc::clear_echo() { - if ( !(dsp.read( 0x6c ) & 0x20) ) + if ( !(dsp.read( 0x6C ) & 0x20) ) { - unsigned addr = 0x100 * dsp.read( 0x6d ); - unsigned size = 0x800 * dsp.read( 0x7d ); - unsigned limit = ram_size - addr; - memset( ram + addr, 0xff, (size < limit) ? size : limit ); + unsigned addr = 0x100 * dsp.read( 0x6D ); + unsigned size = 0x800 * dsp.read( 0x7D ); + memset( ram + addr, 0xFF, min( size, ram_size - addr ) ); } } @@ -108,14 +123,14 @@ memcpy( extra_ram, ram + rom_addr, sizeof extra_ram ); // boot rom (have to force enable_rom() to update it) - rom_enabled = !(ram [0xf1] & 0x80); + rom_enabled = !(ram [0xF1] & 0x80); enable_rom( !rom_enabled ); // dsp dsp.reset(); int i; for ( i = 0; i < Spc_Dsp::register_count; i++ ) - dsp.write( i, ((uint8_t*) dsp_state) [i] ); + dsp.write( i, ((uint8_t const*) dsp_state) [i] ); // timers for ( i = 0; i < timer_count; i++ ) @@ -123,29 +138,29 @@ Timer& t = timer [i]; t.next_tick = 0; - t.enabled = (ram [0xf1] >> i) & 1; + t.enabled = (ram [0xF1] >> i) & 1; if ( !t.enabled ) t.next_tick = timer_disabled_time; t.count = 0; - t.counter = ram [0xfd + i] & 15; + t.counter = ram [0xFD + i] & 15; - int p = ram [0xfa + i]; + int p = ram [0xFA + i]; t.period = p ? p : 0x100; } // Handle registers which already give 0 when read by setting RAM and not changing it. // Put STOP instruction in registers which can be read, to catch attempted CPU execution. - ram [0xf0] = 0; - ram [0xf1] = 0; - ram [0xf3] = 0xff; - ram [0xfa] = 0; - ram [0xfb] = 0; - ram [0xfc] = 0; - ram [0xfd] = 0xff; - ram [0xfe] = 0xff; - ram [0xff] = 0xff; + ram [0xF0] = 0; + ram [0xF1] = 0; + ram [0xF3] = 0xFF; + ram [0xFA] = 0; + ram [0xFB] = 0; + ram [0xFC] = 0; + ram [0xFD] = 0xFF; + ram [0xFE] = 0xFF; + ram [0xFF] = 0xFF; - return NULL; // success + return 0; // success } // Hardware @@ -166,10 +181,12 @@ dprintf( "next_tick: %ld, time: %ld", (long) next_tick, (long) time ); assert( enabled ); // when disabled, next_tick should always be in the future - int elapsed = ((time - next_tick) >> shift) + 1; - next_tick += elapsed << shift; + int elapsed = ((time - next_tick) / divisor) + 1; + next_tick += elapsed * divisor; + elapsed += count; - if ( elapsed >= period ) { // avoid costly divide + if ( elapsed >= period ) // avoid unnecessary division + { int n = elapsed / period; elapsed -= n * period; counter = (counter + n) & 15; @@ -203,13 +220,13 @@ // inaccurate emulation due to the DSP not being caught up to the present. inline void Snes_Spc::check_for_echo_access( spc_addr_t addr ) { - if ( !echo_accessed && !(dsp.read( 0x6c ) & 0x20) ) + if ( !echo_accessed && !(dsp.read( 0x6C ) & 0x20) ) { // ** If echo accesses are found that require running the DSP, cache // the start and end address on DSP writes to speed up checking. - unsigned start = 0x100 * dsp.read( 0x6d ); - unsigned end = start + 0x800 * dsp.read( 0x7d ); + unsigned start = 0x100 * dsp.read( 0x6D ); + unsigned end = start + 0x800 * dsp.read( 0x7D ); if ( start <= addr && addr < end ) { echo_accessed = true; dprintf( "Read/write at $%04X within echo buffer\n", (unsigned) addr ); @@ -221,69 +238,66 @@ int Snes_Spc::read( spc_addr_t addr ) { - // zero page ram is used most often - if ( addr < 0xf0 ) - return ram [addr]; + int result = ram [addr]; + + if ( (rom_addr <= addr && addr < 0xFFFC || addr >= 0xFFFE) && rom_enabled ) + dprintf( "Read from ROM: %04X -> %02X\n", addr, result ); - // dsp - if ( addr == 0xf3 ) { - run_dsp( time() ); - if ( ram [0xf2] >= Spc_Dsp::register_count ) - dprintf( "DSP read from $%02X\n", (int) ram [0xf2] ); - return dsp.read( ram [0xf2] & 0x7f ); + if ( unsigned (addr - 0xF0) < 0x10 ) + { + assert( 0xF0 <= addr && addr <= 0xFF ); + + // counters + int i = addr - 0xFD; + if ( i >= 0 ) + { + Timer& t = timer [i]; + t.run_until( time() ); + int result = t.counter; + t.counter = 0; + return result; + } + + // dsp + if ( addr == 0xF3 ) + { + run_dsp( time() ); + if ( ram [0xF2] >= Spc_Dsp::register_count ) + dprintf( "DSP read from $%02X\n", (int) ram [0xF2] ); + return dsp.read( ram [0xF2] & 0x7F ); + } + + if ( addr == 0xF0 || addr == 0xF1 || addr == 0xF8 || + addr == 0xF9 || addr == 0xFA ) + dprintf( "Read from register $%02X\n", (int) addr ); + + // Registers which always read as 0 are handled by setting ram [reg] to 0 + // at startup then never changing that value. + + check(( check_for_echo_access( addr ), true )); } - // counters - unsigned i = addr - 0xfd; // negative converts to large positive unsigned - if ( i < timer_count ) { - Timer& t = timer [i]; - t.run_until( time() ); - int result = t.counter; - t.counter = 0; - return result; - } - - if ( addr == 0xf0 || addr == 0xf1 || addr == 0xf8 || - addr == 0xf9 || addr == 0xfa ) - dprintf( "Read from register $%02X\n", (int) addr ); - - // Registers which always read as 0 are handled by setting ram [reg] to 0 - // at startup then never changing that value. - - check(( check_for_echo_access( addr ), true )); - - // ram - return ram [addr]; + return result; } // Write -const unsigned char Snes_Spc::boot_rom [rom_size] = { // verified - 0xCD, 0xEF, 0xBD, 0xE8, 0x00, 0xC6, 0x1D, 0xD0, - 0xFC, 0x8F, 0xAA, 0xF4, 0x8F, 0xBB, 0xF5, 0x78, - 0xCC, 0xF4, 0xD0, 0xFB, 0x2F, 0x19, 0xEB, 0xF4, - 0xD0, 0xFC, 0x7E, 0xF4, 0xD0, 0x0B, 0xE4, 0xF5, - 0xCB, 0xF4, 0xD7, 0x00, 0xFC, 0xD0, 0xF3, 0xAB, - 0x01, 0x10, 0xEF, 0x7E, 0xF4, 0x10, 0xEB, 0xBA, - 0xF6, 0xDA, 0x00, 0xBA, 0xF4, 0xC4, 0xF4, 0xDD, - 0x5D, 0xD0, 0xDB, 0x1F, 0x00, 0x00, 0xC0, 0xFF -}; - void Snes_Spc::enable_rom( bool enable ) { if ( rom_enabled != enable ) { rom_enabled = enable; memcpy( ram + rom_addr, (enable ? boot_rom : extra_ram), rom_size ); + // TODO: ROM can still get overwritten when DSP writes to echo buffer } } void Snes_Spc::write( spc_addr_t addr, int data ) { // first page is very common - if ( addr < 0xf0 ) { - ram [addr] = data; + if ( addr < 0xF0 ) { + ram [addr] = (uint8_t) data; } else switch ( addr ) { @@ -291,20 +305,20 @@ default: check(( check_for_echo_access( addr ), true )); if ( addr < rom_addr ) { - ram [addr] = data; + ram [addr] = (uint8_t) data; } else { - extra_ram [addr - rom_addr] = data; + extra_ram [addr - rom_addr] = (uint8_t) data; if ( !rom_enabled ) - ram [addr] = data; + ram [addr] = (uint8_t) data; } break; // DSP - //case 0xf2: // mapped to RAM - case 0xf3: { + //case 0xF2: // mapped to RAM + case 0xF3: { run_dsp( time() ); - int reg = ram [0xf2]; + int reg = ram [0xF2]; if ( next_dsp > 0 ) { // skip mode @@ -327,12 +341,12 @@ break; } - case 0xf0: // Test register + case 0xF0: // Test register dprintf( "Wrote $%02X to $F0\n", (int) data ); break; // Config - case 0xf1: + case 0xF1: { // timers for ( int i = 0; i < timer_count; i++ ) @@ -353,12 +367,12 @@ // port clears if ( data & 0x10 ) { - ram [0xf4] = 0; - ram [0xf5] = 0; + ram [0xF4] = 0; + ram [0xF5] = 0; } if ( data & 0x20 ) { - ram [0xf6] = 0; - ram [0xf7] = 0; + ram [0xF6] = 0; + ram [0xF7] = 0; } enable_rom( data & 0x80 ); @@ -367,22 +381,22 @@ } // Ports - case 0xf4: - case 0xf5: - case 0xf6: - case 0xf7: + case 0xF4: + case 0xF5: + case 0xF6: + case 0xF7: // to do: handle output ports break; - //case 0xf8: // verified on SNES that these are read/write (RAM) - //case 0xf9: + //case 0xF8: // verified on SNES that these are read/write (RAM) + //case 0xF9: // Timers - case 0xfa: - case 0xfb: - case 0xfc: { - Timer& t = timer [addr - 0xfa]; - if ( (t.period & 0xff) != data ) { + case 0xFA: + case 0xFB: + case 0xFC: { + Timer& t = timer [addr - 0xFA]; + if ( (t.period & 0xFF) != data ) { t.run_until( time() ); t.period = data ? data : 0x100; } @@ -390,11 +404,11 @@ } // Counters (cleared on write) - case 0xfd: - case 0xfe: - case 0xff: + case 0xFD: + case 0xFE: + case 0xFF: dprintf( "Wrote to counter $%02X\n", (int) addr ); - timer [addr - 0xfd].counter = 0; + timer [addr - 0xFD].counter = 0; break; } } @@ -413,7 +427,7 @@ keys_pressed = 0; keys_released = 0; // sentinel tells play to ignore DSP - BLARGG_RETURN_ERR( play( count - sync_count, skip_sentinel ) ); + RETURN_ERR( play( count - sync_count, skip_sentinel ) ); // press/release keys now dsp.write( 0x5C, keys_released & ~keys_pressed ); @@ -458,7 +472,7 @@ { dprintf( "Unhandled instruction $%02X, pc = $%04X\n", (int) cpu.read( cpu.r.pc ), (unsigned) cpu.r.pc ); - return "Emulation error"; + return "Emulation error (illegal/unsupported instruction)"; } extra_cycles = -elapsed; @@ -468,8 +482,7 @@ assert( next_dsp == clocks_per_sample ); assert( out == skip_sentinel || sample_buf - out == count ); } - buf_end = NULL; + buf_end = 0; - return blargg_success; + return 0; } -
--- a/src/console/Snes_Spc.h Wed Nov 29 14:42:11 2006 -0800 +++ b/src/console/Snes_Spc.h Thu Nov 30 19:54:33 2006 -0800 @@ -1,8 +1,6 @@ - // Super Nintendo (SNES) SPC-700 APU Emulator -// Game_Music_Emu 0.3.0 - +// Game_Music_Emu 0.5.1 #ifndef SNES_SPC_H #define SNES_SPC_H @@ -12,31 +10,30 @@ class Snes_Spc { public: - typedef BOOST::uint8_t uint8_t; - - Snes_Spc(); // Load copy of SPC data into emulator. Clear echo buffer if 'clear_echo' is true. enum { spc_file_size = 0x10180 }; - blargg_err_t load_spc( const void* spc, long spc_size, bool clear_echo = 1 ); + blargg_err_t load_spc( const void* spc, long spc_size ); + + // Generate 'count' samples and optionally write to 'buf'. Count must be even. + // Sample output is 16-bit 32kHz, signed stereo pairs with the left channel first. + typedef short sample_t; + blargg_err_t play( long count, sample_t* buf = NULL ); + +// Optional functionality // Load copy of state into emulator. typedef Spc_Cpu::registers_t registers_t; blargg_err_t load_state( const registers_t& cpu_state, const void* ram_64k, const void* dsp_regs_128 ); - // Clear echo buffer + // Clear echo buffer, useful because many tracks have junk in the buffer. void clear_echo(); // Mute voice n if bit n (1 << n) of mask is set enum { voice_count = Spc_Dsp::voice_count }; void mute_voices( int mask ); - // Generate 'count' samples and optionally write to 'buf'. Count must be even. - // Sample output is 16-bit 32kHz, signed stereo pairs with the left channel first. - typedef short sample_t; - blargg_err_t play( long count, sample_t* buf = NULL ); - // Skip forward by the specified number of samples (64000 samples = 1 second) blargg_err_t skip( long count ); @@ -45,9 +42,17 @@ void set_gain( double ); // If true, prevent channels and global volumes from being phase-negated - void disable_surround( bool disable ); + void disable_surround( bool disable = true ); + + // Set 128 bytes to use for IPL boot ROM. Makes copy. Default is zero filled, + // to avoid including copyrighted code from the SPC-700. + void set_ipl_rom( const void* ); -// End of public interface + void set_tempo( double ); + +public: + Snes_Spc(); + typedef BOOST::uint8_t uint8_t; private: // timers struct Timer @@ -55,9 +60,9 @@ spc_time_t next_tick; int period; int count; - int shift; + int divisor; + int enabled; int counter; - int enabled; void run_until_( spc_time_t ); void run_until( spc_time_t time ) @@ -91,16 +96,16 @@ // boot rom enum { rom_size = 64 }; - enum { rom_addr = 0xffc0 }; + enum { rom_addr = 0xFFC0 }; bool rom_enabled; - uint8_t extra_ram [rom_size]; - static const uint8_t boot_rom [rom_size]; void enable_rom( bool ); // CPU and RAM (at end because it's large) Spc_Cpu cpu; enum { ram_size = 0x10000 }; + uint8_t extra_ram [rom_size]; uint8_t ram [ram_size + 0x100]; // padding for catching jumps past end + uint8_t boot_rom [rom_size]; }; inline void Snes_Spc::disable_surround( bool disable ) { dsp.disable_surround( disable ); } @@ -110,4 +115,3 @@ inline void Snes_Spc::set_gain( double v ) { dsp.set_gain( v ); } #endif -
--- a/src/console/Spc_Cpu.cxx Wed Nov 29 14:42:11 2006 -0800 +++ b/src/console/Spc_Cpu.cxx Thu Nov 30 19:54:33 2006 -0800 @@ -1,10 +1,7 @@ - -// Game_Music_Emu 0.3.0. http://www.slack.net/~ant/ +// Game_Music_Emu 0.5.1. http://www.slack.net/~ant/ #include "Spc_Cpu.h" -#include <limits.h> - #include "blargg_endian.h" #include "Snes_Spc.h" @@ -14,12 +11,12 @@ 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 */ +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_BEGIN +#include "blargg_source.h" // Several instructions are commented out (or not even implemented). These aren't // used by the SPC files tested. @@ -43,6 +40,7 @@ { remain_ = 0; BOOST_STATIC_ASSERT( sizeof (int) >= 4 ); + blargg_verify_byte_order(); } #define READ( addr ) (emu.read( addr )) @@ -65,7 +63,7 @@ } // Cycle table derived from text copy of SPC-700 manual (using regular expressions) -static const unsigned char cycle_table [0x100] = { +static unsigned char const cycle_table [0x100] = { // 0 1 2 3 4 5 6 7 8 9 A B C D E F 2,8,4,5,3,4,3,6,2,6,5,4,5,4,6,8, // 0 2,8,4,5,4,5,5,6,5,5,6,5,2,2,4,6, // 1 @@ -90,12 +88,10 @@ unsigned Spc_Cpu::mem_bit( spc_addr_t pc ) { unsigned addr = READ_PROG16( pc ); - unsigned t = READ( addr & 0x1fff ) >> (addr >> 13); + unsigned t = READ( addr & 0x1FFF ) >> (addr >> 13); return (t << 8) & 0x100; } -#include BLARGG_ENABLE_OPTIMIZER - spc_time_t Spc_Cpu::run( spc_time_t cycle_count ) { remain_ = cycle_count; @@ -105,7 +101,7 @@ // Stack pointer is kept one greater than usual SPC stack pointer to allow // common pre-decrement and post-increment memory instructions that some // processors have. Address wrap-around isn't supported. - #define PUSH( v ) (*--sp = (v)) + #define PUSH( v ) (*--sp = uint8_t (v)) #define PUSH16( v ) (sp -= 2, SET_LE16( sp, v )) #define POP() (*sp++) #define SET_SP( v ) (sp = ram + 0x101 + (v)) @@ -115,7 +111,7 @@ SET_SP( r.sp ); // registers - unsigned pc = r.pc; + unsigned pc = (unsigned) r.pc; int a = r.a; int x = r.x; int y = r.y; @@ -149,9 +145,9 @@ dp = (in << 3) & 0x100; \ } while ( 0 ) - uint8_t status; + int status; int c; // store C as 'c' & 0x100. - int nz; // Z set if (nz & 0xff) == 0, N set if (nz & 0x880) != 0 + int nz; // Z set if (nz & 0xFF) == 0, N set if (nz & 0x880) != 0 unsigned dp; // direct page base { int temp = r.status; @@ -173,9 +169,9 @@ check( (unsigned) pc < 0x10000 ); check( (unsigned) GET_SP() < 0x100 ); - assert( (unsigned) a < 0x100 ); - assert( (unsigned) x < 0x100 ); - assert( (unsigned) y < 0x100 ); + check( (unsigned) a < 0x100 ); + check( (unsigned) x < 0x100 ); + check( (unsigned) y < 0x100 ); unsigned opcode = READ_PROG( pc ); pc++; @@ -412,14 +408,14 @@ case 0x68: // CMP imm nz = a - data; c = ~nz; - nz &= 0xff; + nz &= 0xFF; goto inc_pc_loop; case 0x79: // CMP (X),(Y) data = READ_DP( x ); nz = data - READ_DP( y ); c = ~nz; - nz &= 0xff; + nz &= 0xFF; goto loop; case 0x69: // CMP (dp),(dp) @@ -428,7 +424,7 @@ pc++; nz = READ_DP( READ_PROG( pc ) ) - data; c = ~nz; - nz &= 0xff; + nz &= 0xFF; goto inc_pc_loop; case 0x3E: // CMP X,dp @@ -442,7 +438,7 @@ case 0xC8: // CMP X,imm nz = x - data; c = ~nz; - nz &= 0xff; + nz &= 0xFF; goto inc_pc_loop; case 0x7E: // CMP Y,dp @@ -456,7 +452,7 @@ case 0xAD: // CMP Y,imm nz = y - data; c = ~nz; - nz &= 0xff; + nz &= 0xFF; goto inc_pc_loop; { @@ -489,7 +485,7 @@ nz = a; adc_data: { if ( opcode & 0x20 ) - data ^= 0xff; // SBC + data ^= 0xFF; // SBC int carry = (c >> 8) & 1; int ov = (nz ^ 0x80) + carry + (BOOST::int8_t) data; // sign-extend int hc = (nz & 15) + carry; @@ -613,7 +609,7 @@ case 0xBA: // MOVW YA,dp a = READ_DP( data ); - nz = (a & 0x7f) | (a >> 1); + nz = (a & 0x7F) | (a >> 1); y = READ_DP( uint8_t (data + 1) ); nz |= y; goto inc_pc_loop; @@ -632,7 +628,7 @@ // low byte int temp = READ( data ); temp += ((opcode >> 4) & 2) - 1; // +1 for INCW, -1 for DECW - nz = ((temp >> 1) | temp) & 0x7f; + nz = ((temp >> 1) | temp) & 0x7F; WRITE( data, (uint8_t) temp ); // high byte @@ -665,12 +661,12 @@ // add low byte (A) temp += a; a = (uint8_t) temp; - nz = (temp | (temp >> 1)) & 0x7f; + nz = (temp | (temp >> 1)) & 0x7F; // add high byte (Y) temp >>= 8; c = y + temp; - nz = (nz | c) & 0xff; + nz = (nz | c) & 0xFF; // half-carry (temporary avoids CodeWarrior optimizer bug) unsigned hc = (c & 15) - (y & 15); @@ -686,12 +682,12 @@ case 0x5A: { // CMPW YA,dp int temp = a - READ_DP( data ); - nz = ((temp >> 1) | temp) & 0x7f; + nz = ((temp >> 1) | temp) & 0x7F; temp = y + (temp >> 8); temp -= READ_DP( uint8_t (data + 1) ); nz |= temp; c = ~temp; - nz &= 0xff; + nz &= 0xFF; goto inc_pc_loop; } @@ -700,7 +696,7 @@ case 0xCF: { // MUL YA unsigned temp = y * a; a = (uint8_t) temp; - nz = ((temp >> 1) | temp) & 0x7f; + nz = ((temp >> 1) | temp) & 0x7F; y = temp >> 8; nz |= y; goto loop; @@ -826,7 +822,7 @@ case 0x0F: // BRK check( false ); // untested PUSH16( pc + 1 ); - pc = READ_PROG16( 0xffde ); // vector address verified + pc = READ_PROG16( 0xFFDE ); // vector address verified int temp; CALC_STATUS( temp ); PUSH( temp ); @@ -836,7 +832,7 @@ case 0x4F: // PCALL offset pc++; PUSH16( pc ); - pc = 0xff00 + data; + pc = 0xFF00 + data; goto loop; case 0x01: // TCALL n @@ -856,7 +852,7 @@ case 0xE1: case 0xF1: PUSH16( pc ); - pc = READ_PROG16( 0xffde - (opcode >> 3) ); + pc = READ_PROG16( 0xFFDE - (opcode >> 3) ); goto loop; // 14. STACK OPERATION COMMANDS @@ -977,19 +973,19 @@ case 0xEA: { // NOT1 mem.bit data = READ_PROG16( pc ); pc += 2; - unsigned temp = READ( data & 0x1fff ); + unsigned temp = READ( data & 0x1FFF ); temp ^= 1 << (data >> 13); - WRITE( data & 0x1fff, temp ); + WRITE( data & 0x1FFF, temp ); goto loop; } case 0xCA: { // MOV1 mem.bit,C data = READ_PROG16( pc ); pc += 2; - unsigned temp = READ( data & 0x1fff ); + unsigned temp = READ( data & 0x1FFF ); unsigned bit = data >> 13; temp = (temp & ~(1 << bit)) | (((c >> 8) & 1) << bit); - WRITE( data & 0x1fff, temp ); + WRITE( data & 0x1FFF, temp ); goto loop; } @@ -1052,15 +1048,14 @@ { int temp; CALC_STATUS( temp ); - r.status = temp; + r.status = (uint8_t) temp; } r.pc = pc; - r.sp = GET_SP(); - r.a = a; - r.x = x; - r.y = y; + r.sp = (uint8_t) GET_SP(); + r.a = (uint8_t) a; + r.x = (uint8_t) x; + r.y = (uint8_t) y; return remain_; } -
--- a/src/console/Spc_Cpu.h Wed Nov 29 14:42:11 2006 -0800 +++ b/src/console/Spc_Cpu.h Thu Nov 30 19:54:33 2006 -0800 @@ -1,15 +1,13 @@ - // Super Nintendo (SNES) SPC-700 CPU emulator -// Game_Music_Emu 0.3.0 - +// Game_Music_Emu 0.5.1 #ifndef SPC_CPU_H #define SPC_CPU_H #include "blargg_common.h" typedef unsigned spc_addr_t; -typedef long spc_time_t; +typedef blargg_long spc_time_t; class Snes_Spc; @@ -57,4 +55,3 @@ inline spc_time_t Spc_Cpu::remain() const { return remain_; } #endif -
--- a/src/console/Spc_Dsp.cxx Wed Nov 29 14:42:11 2006 -0800 +++ b/src/console/Spc_Dsp.cxx Thu Nov 30 19:54:33 2006 -0800 @@ -1,14 +1,12 @@ - -// Game_Music_Emu 0.3.0. http://www.slack.net/~ant/ +// Game_Music_Emu 0.5.1. http://www.slack.net/~ant/ // Based on Brad Martin's OpenSPC DSP emulator #include "Spc_Dsp.h" +#include "blargg_endian.h" #include <string.h> -#include "blargg_endian.h" - /* Copyright (C) 2002 Brad Martin */ /* Copyright (C) 2004-2006 Shay Green. This module is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser @@ -16,12 +14,16 @@ 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 */ +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_BEGIN +#include "blargg_source.h" + +#ifdef BLARGG_ENABLE_OPTIMIZER + #include BLARGG_ENABLE_OPTIMIZER +#endif Spc_Dsp::Spc_Dsp( uint8_t* ram_ ) : ram( ram_ ) { @@ -30,6 +32,7 @@ disable_surround( false ); BOOST_STATIC_ASSERT( sizeof (g) == register_count && sizeof (voice) == register_count ); + blargg_verify_byte_order(); } void Spc_Dsp::mute_voices( int mask ) @@ -67,17 +70,17 @@ reg [i] = data; int high = i >> 4; - switch ( i & 0x0f ) + switch ( i & 0x0F ) { // voice volume case 0: case 1: { short* volume = voice_state [high].volume; int left = (int8_t) reg [i & ~1]; - int right = (int8_t) reg [i | 1]; + int right = (int8_t) reg [i | 1]; volume [0] = left; volume [1] = right; - // kill surround only if signs of volumes differ + // kill surround only if enabled and signs of volumes differ if ( left * right < surround_threshold ) { if ( left < 0 ) @@ -89,7 +92,7 @@ } // fir coefficients - case 0x0f: + case 0x0F: fir_coeff [high] = (int8_t) data; // sign-extend break; } @@ -100,7 +103,7 @@ // The counter starts at 30720 (0x7800). Each count divides exactly into // 0x7800 without remainder. const int env_rate_init = 0x7800; -static const short env_rates [0x20] = +static short const env_rates [0x20] = { 0x0000, 0x000F, 0x0014, 0x0018, 0x001E, 0x0028, 0x0030, 0x003C, 0x0050, 0x0060, 0x0078, 0x00A0, 0x00C0, 0x00F0, 0x0140, 0x0180, @@ -192,7 +195,7 @@ // Docs: "SR [is multiplied] by the fixed value 1-1/256." // Multiplying ENVX by 255/256 every time SUSTAIN is // updated. - cnt -= env_rates [raw_voice.adsr [1] & 0x1f]; + cnt -= env_rates [raw_voice.adsr [1] & 0x1F]; if ( cnt <= 0 ) { cnt = env_rate_init; @@ -340,6 +343,7 @@ noise_amp = BOOST::int16_t (noise * 2); + // TODO: switch to Galios style int feedback = (noise << 13) ^ (noise << 14); noise = (feedback & 0x4000) | (noise >> 1); } @@ -347,7 +351,7 @@ // What is the expected behavior when pitch modulation is enabled on // voice 0? Jurassic Park 2 does this. Assume 0 for now. - long prev_outx = 0; + blargg_long prev_outx = 0; int echol = 0; int echor = 0; @@ -367,7 +371,7 @@ voice.block_remain = 1; voice.envx = 0; voice.block_header = 0; - voice.fraction = 0x3fff; // decode three samples immediately + voice.fraction = 0x3FFF; // decode three samples immediately voice.interp0 = 0; // BRR decoder filter uses previous two samples voice.interp1 = 0; @@ -474,29 +478,25 @@ // One, two and three point IIR filters int smp1 = voice.interp0; int smp2 = voice.interp1; - switch ( (voice.block_header >> 2) & 3 ) + if ( voice.block_header & 8 ) { - case 0: - break; - - case 1: - delta += smp1 >> 1; - delta += (-smp1) >> 5; - break; - - case 2: - delta += smp1; - delta += (-(smp1 + (smp1 >> 1))) >> 5; - delta -= smp2 >> 1; + delta += smp1; + delta -= smp2 >> 1; + if ( !(voice.block_header & 4) ) + { + delta += (-smp1 - (smp1 >> 1)) >> 5; delta += smp2 >> 5; - break; - - case 3: - delta += smp1; - delta += (-(smp1 + (smp1 << 2) + (smp1 << 3))) >> 7; - delta -= smp2 >> 1; + } + else + { + delta += (-smp1 * 13) >> 7; delta += (smp2 + (smp2 >> 1)) >> 4; - break; + } + } + else if ( voice.block_header & 4 ) + { + delta += smp1 >> 1; + delta += (-smp1) >> 5; } voice.interp3 = voice.interp2; @@ -513,16 +513,16 @@ // Gaussian interpolation using most recent 4 samples int index = voice.fraction >> 2 & 0x3FC; voice.fraction = (voice.fraction & 0x0FFF) + rate; - const BOOST::int16_t* table = (BOOST::int16_t*) ((char*) gauss + index); - const BOOST::int16_t* table2 = (BOOST::int16_t*) ((char*) gauss + (255*4 - index)); + const BOOST::int16_t* table = (BOOST::int16_t const*) ((char const*) gauss + index); + const BOOST::int16_t* table2 = (BOOST::int16_t const*) ((char const*) gauss + (255*4 - index)); int s = ((table [0] * voice.interp3) >> 12) + - ((table [1] * voice.interp2) >> 12); - s += ((table2 [1] * voice.interp1) >> 12) + - // to do: should clamp here - ((table2 [0] * voice.interp0) >> 12); - int output = noise_amp; // noise is rarely used - if ( !(g.noise_enables & vbit) ) - output = clamp_16( s * 2 ); + ((table [1] * voice.interp2) >> 12) + + ((table2 [1] * voice.interp1) >> 12); + s = (BOOST::int16_t) (s * 2); + s += (table2 [0] * voice.interp0) >> 11 & ~1; + int output = clamp_16( s ); + if ( g.noise_enables & vbit ) + output = noise_amp; // scale output and set outx values output = (output * envx) >> 11 & ~1; @@ -532,13 +532,13 @@ int l = (voice.volume [0] * output) >> voice.enabled; int r = (voice.volume [1] * output) >> voice.enabled; prev_outx = output; - raw_voice.outx = output >> 8; + raw_voice.outx = int8_t (output >> 8); if ( g.echo_ons & vbit ) { echol += l; echor += r; } - left += l; + left += l; right += r; } // end of channel loop @@ -563,10 +563,10 @@ const int fir_offset = this->fir_offset; short (*fir_pos) [2] = &fir_buf [fir_offset]; this->fir_offset = (fir_offset + 7) & 7; // move backwards one step - fir_pos [0] [0] = fb_left; - fir_pos [0] [1] = fb_right; - fir_pos [8] [0] = fb_left; // duplicate at +8 eliminates wrap checking below - fir_pos [8] [1] = fb_right; + fir_pos [0] [0] = (short) fb_left; + fir_pos [0] [1] = (short) fb_right; + fir_pos [8] [0] = (short) fb_left; // duplicate at +8 eliminates wrap checking below + fir_pos [8] [1] = (short) fb_right; // FIR fb_left = fb_left * fir_coeff [7] + @@ -608,8 +608,8 @@ int mute = g.flags & 0x40; - out_buf [0] = left; - out_buf [1] = right; + out_buf [0] = (short) left; + out_buf [1] = (short) right; out_buf += 2; // muting @@ -663,4 +663,3 @@ 0, 434, 0, 430, 0, 426, 0, 422, 0, 418, 0, 414, 0, 410, 0, 405, 0, 401, 0, 397, 0, 393, 0, 389, 0, 385, 0, 381, 0, 378, 0, 374, }; -
--- a/src/console/Spc_Dsp.h Wed Nov 29 14:42:11 2006 -0800 +++ b/src/console/Spc_Dsp.h Thu Nov 30 19:54:33 2006 -0800 @@ -1,8 +1,6 @@ - // Super Nintendo (SNES) SPC DSP emulator -// Game_Music_Emu 0.3.0 - +// Game_Music_Emu 0.5.1 #ifndef SPC_DSP_H #define SPC_DSP_H @@ -109,7 +107,7 @@ int surround_threshold; - static const BOOST::int16_t gauss []; + static BOOST::int16_t const gauss []; enum state_t { state_attack, @@ -152,4 +150,3 @@ } #endif -
--- a/src/console/Spc_Emu.cxx Wed Nov 29 14:42:11 2006 -0800 +++ b/src/console/Spc_Emu.cxx Thu Nov 30 19:54:33 2006 -0800 @@ -1,11 +1,10 @@ - -// Game_Music_Emu 0.3.0. http://www.slack.net/~ant/ +// Game_Music_Emu 0.5.1. http://www.slack.net/~ant/ #include "Spc_Emu.h" +#include "blargg_endian.h" #include <stdlib.h> #include <string.h> -#include "blargg_endian.h" /* Copyright (C) 2004-2006 Shay Green. This module is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser @@ -13,107 +12,297 @@ 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 */ +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" -#include BLARGG_SOURCE_BEGIN - -Spc_Emu::Spc_Emu( double gain ) +Spc_Emu::Spc_Emu() { - apu.set_gain( gain ); + set_type( gme_spc_type ); + + static const char* const names [Snes_Spc::voice_count] = { + "DSP 1", "DSP 2", "DSP 3", "DSP 4", "DSP 5", "DSP 6", "DSP 7", "DSP 8" + }; + set_voice_names( names ); + + set_gain( 1.4 ); } -Spc_Emu::~Spc_Emu() +Spc_Emu::~Spc_Emu() { } + +// Track info + +long const trailer_offset = 0x10200; + +byte const* Spc_Emu::trailer() const { return &file_data [min( file_size, trailer_offset )]; } + +long Spc_Emu::trailer_size() const { return max( 0L, file_size - trailer_offset ); } + +static void get_spc_xid6( byte const* begin, long size, track_info_t* out ) { + // header + byte const* end = begin + size; + if ( size < 8 || memcmp( begin, "xid6", 4 ) ) + { + check( false ); + return; + } + long info_size = get_le32( begin + 4 ); + byte const* in = begin + 8; + if ( end - in > info_size ) + { + dprintf( "Extra data after SPC xid6 info\n" ); + end = in + info_size; + } + + int year = 0; + char copyright [256 + 5]; + int copyright_len = 0; + int const year_len = 5; + + while ( end - in >= 4 ) + { + // header + int id = in [0]; + int data = in [3] * 0x100 + in [2]; + int type = in [1]; + int len = type ? data : 0; + in += 4; + if ( len > end - in ) + { + check( false ); + break; // block goes past end of data + } + + // handle specific block types + char* field = 0; + switch ( id ) + { + case 0x01: field = out->song; break; + case 0x02: field = out->game; break; + case 0x03: field = out->author; break; + case 0x04: field = out->dumper; break; + case 0x07: field = out->comment; break; + case 0x14: year = data; break; + + //case 0x30: // intro length + // Many SPCs have intro length set wrong for looped tracks, making it useless + /* + case 0x30: + check( len == 4 ); + if ( len >= 4 ) + { + out->intro_length = get_le32( in ) / 64; + if ( out->length > 0 ) + { + long loop = out->length - out->intro_length; + if ( loop >= 2000 ) + out->loop_length = loop; + } + } + break; + */ + + case 0x13: + copyright_len = min( len, (int) sizeof copyright - year_len ); + memcpy( ©right [year_len], in, copyright_len ); + break; + + default: + if ( id < 0x01 || (id > 0x07 && id < 0x10) || + (id > 0x14 && id < 0x30) || id > 0x36 ) + dprintf( "Unknown SPC xid6 block: %X\n", (int) id ); + break; + } + if ( field ) + { + check( type == 1 ); + Gme_File::copy_field_( field, (char const*) in, len ); + } + + // skip to next block + in += len; + + // blocks are supposed to be 4-byte aligned with zero-padding... + byte const* unaligned = in; + while ( (in - begin) & 3 && in < end ) + { + if ( *in++ != 0 ) + { + // ...but some files have no padding + in = unaligned; + dprintf( "SPC info tag wasn't properly padded to align\n" ); + break; + } + } + } + + char* p = ©right [year_len]; + if ( year ) + { + *--p = ' '; + for ( int n = 4; n--; ) + { + *--p = char (year % 10 + '0'); + year /= 10; + } + copyright_len += year_len; + } + if ( copyright_len ) + Gme_File::copy_field_( out->copyright, p, copyright_len ); + + check( in == end ); } -const char** Spc_Emu::voice_names() const +static void get_spc_info( Spc_Emu::header_t const& h, byte const* xid6, long xid6_size, + track_info_t* out ) { - static const char* names [] = { - "DSP 1", "DSP 2", "DSP 3", "DSP 4", "DSP 5", "DSP 6", "DSP 7", "DSP 8" - }; - return names; + // decode length (can be in text or binary format, sometimes ambiguous ugh) + long len_secs = 0; + for ( int i = 0; i < 3; i++ ) + { + unsigned n = h.len_secs [i] - '0'; + if ( n > 9 ) + { + // ignore single-digit text lengths + // (except if author field is present and begins at offset 1, ugh) + if ( i == 1 && (h.author [0] || !h.author [1]) ) + len_secs = 0; + break; + } + len_secs *= 10; + len_secs += n; + } + if ( !len_secs || len_secs > 0x1FFF ) + len_secs = get_le16( h.len_secs ); + if ( len_secs < 0x1FFF ) + out->length = len_secs * 1000; + + int offset = (h.author [0] < ' ' || unsigned (h.author [0] - '0') <= 9); + Gme_File::copy_field_( out->author, &h.author [offset], sizeof h.author - offset ); + + GME_COPY_FIELD( h, out, song ); + GME_COPY_FIELD( h, out, game ); + GME_COPY_FIELD( h, out, dumper ); + GME_COPY_FIELD( h, out, comment ); + + if ( xid6_size ) + get_spc_xid6( xid6, xid6_size, out ); +} + +blargg_err_t Spc_Emu::track_info_( track_info_t* out, int ) const +{ + get_spc_info( header(), trailer(), trailer_size(), out ); + return 0; +} + +static blargg_err_t check_spc_header( void const* header ) +{ + if ( memcmp( header, "SNES-SPC700 Sound File Data", 27 ) ) + return gme_wrong_file_type; + return 0; } -void Spc_Emu::mute_voices( int m ) +struct Spc_File : Gme_Info_ { - Music_Emu::mute_voices( m ); + Spc_Emu::header_t header; + blargg_vector<byte> xid6; + + Spc_File() { set_type( gme_spc_type ); } + + blargg_err_t load_( Data_Reader& in ) + { + long file_size = in.remain(); + if ( file_size < Snes_Spc::spc_file_size ) + return gme_wrong_file_type; + RETURN_ERR( in.read( &header, sizeof header ) ); + RETURN_ERR( check_spc_header( header.tag ) ); + long const xid6_offset = 0x10200; + long xid6_size = file_size - xid6_offset; + if ( xid6_size > 0 ) + { + RETURN_ERR( xid6.resize( xid6_size ) ); + RETURN_ERR( in.skip( xid6_offset - sizeof header ) ); + RETURN_ERR( in.read( xid6.begin(), xid6.size() ) ); + } + return 0; + } + + blargg_err_t track_info_( track_info_t* out, int ) const + { + get_spc_info( header, xid6.begin(), xid6.size(), out ); + return 0; + } +}; + +static Music_Emu* new_spc_emu () { return BLARGG_NEW Spc_Emu ; } +static Music_Emu* new_spc_file() { return BLARGG_NEW Spc_File; } + +gme_type_t_ const gme_spc_type [1] = { "Super Nintendo", 1, &new_spc_emu, &new_spc_file, "SPC", 0 }; + +// Setup + +blargg_err_t Spc_Emu::set_sample_rate_( long sample_rate ) +{ + apu.set_gain( gain() ); + if ( sample_rate != native_sample_rate ) + { + RETURN_ERR( resampler.buffer_size( native_sample_rate / 20 * 2 ) ); + resampler.time_ratio( (double) native_sample_rate / sample_rate, 0.9965 ); + } + return 0; +} + +void Spc_Emu::mute_voices_( int m ) +{ + Music_Emu::mute_voices_( m ); apu.mute_voices( m ); } -blargg_err_t Spc_Emu::set_sample_rate( long sample_rate ) +blargg_err_t Spc_Emu::load_mem_( byte const* in, long size ) { - if ( sample_rate != native_sample_rate ) - { - BLARGG_RETURN_ERR( resampler.buffer_size( native_sample_rate / 20 * 2 ) ); - resampler.time_ratio( (double) native_sample_rate / sample_rate, 0.9965 ); - } - return Music_Emu::set_sample_rate( sample_rate ); -} - -blargg_err_t Spc_Emu::load( Data_Reader& in ) -{ - header_t h; - BLARGG_RETURN_ERR( in.read( &h, sizeof h ) ); - return load( h, in ); + file_data = in; + file_size = size; + set_voice_count( Snes_Spc::voice_count ); + if ( size < Snes_Spc::spc_file_size ) + return gme_wrong_file_type; + return check_spc_header( in ); } -blargg_err_t Spc_Emu::load( const header_t& h, Data_Reader& in ) +// Emulation + +void Spc_Emu::set_tempo_( double t ) { apu.set_tempo( t ); } + +blargg_err_t Spc_Emu::start_track_( int track ) { - if ( in.remain() < Snes_Spc::spc_file_size - (int) sizeof h ) - return "Not an SPC file"; - - if ( strncmp( h.tag, "SNES-SPC700 Sound File Data", 27 ) != 0 ) - return "Not an SPC file"; - - long remain = in.remain(); - long size = remain + sizeof h; - if ( size < trailer_offset ) - size = trailer_offset; - BLARGG_RETURN_ERR( spc_data.resize( size ) ); - - set_track_count( 1 ); - set_voice_count( Snes_Spc::voice_count ); - - memcpy( spc_data.begin(), &h, sizeof h ); - return in.read( &spc_data [sizeof h], remain ); + RETURN_ERR( Music_Emu::start_track_( track ) ); + resampler.clear(); + RETURN_ERR( apu.load_spc( file_data, file_size ) ); + apu.clear_echo(); + return 0; } -void Spc_Emu::start_track( int track ) -{ - Music_Emu::start_track( track ); - - resampler.clear(); - if ( apu.load_spc( spc_data.begin(), spc_data.size() ) ) - check( false ); -} - -void Spc_Emu::skip( long count ) +blargg_err_t Spc_Emu::skip_( long count ) { count = long (count * resampler.ratio()) & ~1; count -= resampler.skip_input( count ); if ( count > 0 ) - apu.skip( count ); + RETURN_ERR( apu.skip( count ) ); // eliminate pop due to resampler const int resampler_latency = 64; sample_t buf [resampler_latency]; - play( resampler_latency, buf ); + return play_( resampler_latency, buf ); } -void Spc_Emu::play( long count, sample_t* out ) +blargg_err_t Spc_Emu::play_( long count, sample_t* out ) { - require( track_count() ); // file must be loaded - if ( sample_rate() == native_sample_rate ) - { - if ( apu.play( count, out ) ) - log_error(); - return; - } + return apu.play( count, out ); long remain = count; while ( remain > 0 ) @@ -122,12 +311,10 @@ if ( remain > 0 ) { long n = resampler.max_write(); - if ( apu.play( n, resampler.buffer() ) ) - log_error(); + RETURN_ERR( apu.play( n, resampler.buffer() ) ); resampler.write( n ); } } - - assert( remain == 0 ); + check( remain == 0 ); + return 0; } -
--- a/src/console/Spc_Emu.h Wed Nov 29 14:42:11 2006 -0800 +++ b/src/console/Spc_Emu.h Thu Nov 30 19:54:33 2006 -0800 @@ -1,8 +1,6 @@ - -// Super Nintendo (SNES) SPC music file emulator +// Super Nintendo SPC music file emulator -// Game_Music_Emu 0.3.0 - +// Game_Music_Emu 0.5.1 #ifndef SPC_EMU_H #define SPC_EMU_H @@ -11,12 +9,7 @@ #include "Snes_Spc.h" class Spc_Emu : public Music_Emu { - enum { trailer_offset = 0x10200 }; public: - // A gain of 1.0 results in almost no clamping. Default gain roughly - // matches volume of other emulators. - Spc_Emu( double gain = 1.4 ); - // The Super Nintendo hardware samples at 32kHz. Other sample rates are // handled by resampling the 32kHz output; emulation accuracy is not affected. enum { native_sample_rate = 32000 }; @@ -35,47 +28,46 @@ char dumper [16]; char comment [32]; byte date [11]; - char len_secs [3]; - byte fade_msec [5]; + byte len_secs [3]; + byte fade_msec [4]; char author [32]; byte mute_mask; byte emulator; - byte unused2 [45]; - - enum { track_count = 1 }; - enum { copyright = 0 }; // no copyright field + byte unused2 [46]; }; BOOST_STATIC_ASSERT( sizeof (header_t) == 0x100 ); - // Load SPC data - blargg_err_t load( Data_Reader& ); - - // Load SPC using already-loaded header and remaining data - blargg_err_t load( header_t const&, Data_Reader& ); + // Header for currently loaded file + header_t const& header() const { return *(header_t const*) file_data; } - // Header for currently loaded SPC - header_t const& header() const { return *(header_t*) spc_data.begin(); } - - // Pointer and size for trailer data - byte const* trailer() const { return &spc_data [trailer_offset]; } - long trailer_size() const { return spc_data.size() - trailer_offset; } - - // If true, prevents channels and global volumes from being phase-negated + // Prevents channels and global volumes from being phase-negated void disable_surround( bool disable = true ); -public: - ~Spc_Emu(); - blargg_err_t set_sample_rate( long ); - void mute_voices( int ); - void start_track( int ); - void play( long, sample_t* ); - void skip( long ); - const char** voice_names() const; + static gme_type_t static_type() { return gme_spc_type; } + public: // deprecated - blargg_err_t init( long r, double gain = 1.4 ) { return set_sample_rate( r ); } + Music_Emu::load; + blargg_err_t load( header_t const& h, Data_Reader& in ) // use Remaining_Reader + { return load_remaining_( &h, sizeof h, in ); } + byte const* trailer() const; // use track_info() + long trailer_size() const; + +public: + Spc_Emu(); + ~Spc_Emu(); +protected: + blargg_err_t load_mem_( byte const*, long ); + blargg_err_t track_info_( track_info_t*, int track ) const; + blargg_err_t set_sample_rate_( long ); + blargg_err_t start_track_( int ); + blargg_err_t play_( long, sample_t* ); + blargg_err_t skip_( long ); + void mute_voices_( int ); + void set_tempo_( double ); private: - blargg_vector<byte> spc_data; + byte const* file_data; + long file_size; Fir_Resampler<24> resampler; Snes_Spc apu; }; @@ -83,4 +75,3 @@ inline void Spc_Emu::disable_surround( bool b ) { apu.disable_surround( b ); } #endif -
--- a/src/console/Vfs_File.cxx Wed Nov 29 14:42:11 2006 -0800 +++ b/src/console/Vfs_File.cxx Thu Nov 30 19:54:33 2006 -0800 @@ -1,15 +1,19 @@ - #include "Vfs_File.h" -#include "audacious/vfs.h" - -Vfs_File_Reader::Vfs_File_Reader() : file_( NULL ) { } +Vfs_File_Reader::Vfs_File_Reader() : file_( 0 ), owned_file_( 0 ) { } Vfs_File_Reader::~Vfs_File_Reader() { close(); } +void Vfs_File_Reader::reset( VFSFile* f ) +{ + close(); + file_ = f; +} + Vfs_File_Reader::error_t Vfs_File_Reader::open( const char* path ) { - file_ = vfs_fopen( path, "rb" ); + close(); + file_ = owned_file_ = vfs_fopen( path, "rb" ); if ( !file_ ) return "Couldn't open file"; return 0; @@ -18,37 +22,37 @@ long Vfs_File_Reader::size() const { long pos = tell(); - vfs_fseek( (VFSFile*) file_, 0, SEEK_END ); + vfs_fseek( file_, 0, SEEK_END ); long result = tell(); - vfs_fseek( (VFSFile*) file_, pos, SEEK_SET ); + vfs_fseek( file_, pos, SEEK_SET ); return result; } long Vfs_File_Reader::read_avail( void* p, long s ) { - return (long) vfs_fread( p, 1, s, (VFSFile*) file_ ); + return (long) vfs_fread( p, 1, s, file_ ); } long Vfs_File_Reader::tell() const { - return vfs_ftell( (VFSFile*) file_ ); + return vfs_ftell( file_ ); } Vfs_File_Reader::error_t Vfs_File_Reader::seek( long n ) { if ( n == 0 ) // optimization - vfs_rewind( (VFSFile*) file_ ); - else if ( vfs_fseek( (VFSFile*) file_, n, SEEK_SET ) != 0 ) + vfs_rewind( file_ ); + else if ( vfs_fseek( file_, n, SEEK_SET ) != 0 ) return "Error seeking in file"; return 0; } void Vfs_File_Reader::close() { - if ( file_ ) + file_ = 0; + if ( owned_file_ ) { - vfs_fclose( (VFSFile*) file_ ); - file_ = 0; + vfs_fclose( owned_file_ ); + owned_file_ = 0; } } -
--- a/src/console/Vfs_File.h Wed Nov 29 14:42:11 2006 -0800 +++ b/src/console/Vfs_File.h Thu Nov 30 19:54:33 2006 -0800 @@ -1,23 +1,29 @@ - // File_Reader based on a VFSFile #ifndef VFS_FILE_H #define VFS_FILE_H -#include "abstract_file.h" +#include "Data_Reader.h" + +#include "audacious/vfs.h" class Vfs_File_Reader : public File_Reader { - void* file_; +public: + void reset( VFSFile* ); // use already-open file and doesn't close it in close() + error_t open( const char* path ); + VFSFile* file() const { return file_; } + void close(); + public: Vfs_File_Reader(); ~Vfs_File_Reader(); - error_t open( const char* ); long size() const; long read_avail( void*, long ); long tell() const; error_t seek( long ); - void close(); +private: + VFSFile* file_; + VFSFile* owned_file_; }; #endif -
--- a/src/console/Vgm_Emu.cxx Wed Nov 29 14:42:11 2006 -0800 +++ b/src/console/Vgm_Emu.cxx Thu Nov 30 19:54:33 2006 -0800 @@ -1,11 +1,10 @@ - -// Game_Music_Emu 0.3.0. http://www.slack.net/~ant/ +// Game_Music_Emu 0.5.1. http://www.slack.net/~ant/ #include "Vgm_Emu.h" -#include <math.h> +#include "blargg_endian.h" #include <string.h> -#include "blargg_endian.h" +#include <math.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 @@ -13,75 +12,224 @@ 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 */ +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_BEGIN +#include "blargg_source.h" -double const gain = 3.0; // FM emulators are internally quieter to avoid 16-bit overflow +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( bool os, double tempo ) +Vgm_Emu::Vgm_Emu() { - oversample = os; - pos = NULL; - data = NULL; - uses_fm = false; - vgm_rate = (long) (header_t::time_rate * tempo + 0.5); + 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 ); - psg.volume( 1.0 ); } -Vgm_Emu::~Vgm_Emu() +Vgm_Emu::~Vgm_Emu() { } + +// Track info + +static byte const* skip_gd3_str( byte const* in, byte const* end ) { - unload(); + 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; } -void Vgm_Emu::unload() +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 ) { - data = NULL; - pos = NULL; - set_track_ended( false ); - mem.clear(); + 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 ); } -blargg_err_t Vgm_Emu::set_sample_rate( long sample_rate ) +int const gd3_header_size = 12; + +static long check_gd3_header( byte const* h, long remain ) { - BLARGG_RETURN_ERR( blip_buf.set_sample_rate( sample_rate, 1000 / 30 ) ); - return Classic_Emu::set_sample_rate( sample_rate ); + 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; } -BOOST::uint8_t const* Vgm_Emu::gd3_data( int* size ) const +byte const* Vgm_Emu::gd3_data( int* size ) const { if ( size ) *size = 0; - long gd3_offset = get_le32( header_.gd3_offset ); - if ( !gd3_offset ) - return NULL; - - gd3_offset -= 0x40 - offsetof (header_t,gd3_offset); + long gd3_offset = get_le32( header().gd3_offset ) - 0x2C; if ( gd3_offset < 0 ) - return NULL; + return 0; - byte const* gd3 = data + gd3_offset; - if ( data_end - gd3 < 16 || 0 != memcmp( gd3, "Gd3 ", 4 ) || get_le32( gd3 + 4 ) >= 0x200 ) - return NULL; - - long gd3_size = get_le32( gd3 + 8 ); - if ( data_end - gd3 < gd3_size - 12 ) - return NULL; + byte const* gd3 = data + sizeof (header_t) + gd3_offset; + long gd3_size = check_gd3_header( gd3, data_end - gd3 ); + if ( !gd3_size ) + return 0; if ( size ) - *size = data_end - gd3; + *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<byte> gd3; + + Vgm_File() { set_type( gme_vgm_type ); } + + blargg_err_t load_( Data_Reader& in ) + { + long file_size = in.remain(); + if ( file_size <= (long) sizeof h ) + return gme_wrong_file_type; + + RETURN_ERR( in.read( &h, sizeof h ) ); + RETURN_ERR( check_vgm_header( h ) ); + + long gd3_offset = get_le32( h.gd3_offset ) - 0x2C; + long remain = file_size - sizeof h - 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 ); @@ -94,96 +242,88 @@ psg.osc_output( i, c, l, r ); } -const char** Vgm_Emu::voice_names() const +void Vgm_Emu::mute_voices_( int mask ) { - static const char* fm_names [] = { - "FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "PCM", "PSG" - }; - if ( uses_fm ) - return fm_names; - - static const char* psg_names [] = { "Square 1", "Square 2", "Square 3", "Noise" }; - return psg_names; -} - -void Vgm_Emu::mute_voices( int mask ) -{ - Classic_Emu::mute_voices( 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 * gain ); + dac_synth.volume( (mask & 0x40) ? 0.0 : 0.1115 / 256 * fm_gain * gain() ); ym2612.mute_voices( mask ); } if ( ym2413.enabled() ) { - int m = mask & 0x3f; + int m = mask & 0x3F; if ( mask & 0x20 ) - m |= 0x01e0; // channels 5-8 + m |= 0x01E0; // channels 5-8 if ( mask & 0x40 ) - m |= 0x3e00; + m |= 0x3E00; ym2413.mute_voices( m ); } } } -blargg_err_t Vgm_Emu::load_( const header_t& h, void const* new_data, long new_size ) +blargg_err_t Vgm_Emu::load_mem_( byte const* new_data, long new_size ) { - header_ = h; + if ( new_size <= (long) sizeof (header_t) ) + return gme_wrong_file_type; - // compatibility - if ( 0 != memcmp( header_.tag, "Vgm ", 4 ) ) - return "Not a VGM file"; - check( get_le32( header_.version ) <= 0x150 ); + header_t const& h = *(header_t const*) new_data; + + RETURN_ERR( check_vgm_header( h ) ); + + check( get_le32( h.version ) <= 0x150 ); // psg rate - long psg_rate = get_le32( header_.psg_rate ); + psg_rate = get_le32( h.psg_rate ); if ( !psg_rate ) psg_rate = 3579545; - blip_time_factor = (long) floor( (double) (1L << blip_time_bits) / vgm_rate * psg_rate + 0.5 ); blip_buf.clock_rate( psg_rate ); - data = (byte*) new_data; - data_end = data + new_size; + data = new_data; + data_end = new_data + new_size; // get loop loop_begin = data_end; - if ( get_le32( header_.loop_offset ) ) - loop_begin = &data [get_le32( header_.loop_offset ) + offsetof (header_t,loop_offset) - 0x40]; + 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 ); - set_track_count( 1 ); + + RETURN_ERR( setup_fm() ); - BLARGG_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 - BLARGG_RETURN_ERR( Classic_Emu::setup_buffer( psg_rate ) ); - - return blargg_success; + 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 ) + 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; - double fm_rate = blip_buf.sample_rate() * oversample_factor; + fm_rate = blip_buf.sample_rate() * oversample_factor; if ( ym2612_rate ) { uses_fm = true; - if ( !oversample ) + if ( disable_oversampling_ ) fm_rate = ym2612_rate / 144.0; - Dual_Resampler::setup( fm_rate / blip_buf.sample_rate(), rolloff, gain ); - BLARGG_RETURN_ERR( ym2612.set_rate( fm_rate, ym2612_rate ) ); + 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 ); } @@ -191,75 +331,48 @@ if ( !uses_fm && ym2413_rate ) { uses_fm = true; - if ( !oversample ) + if ( disable_oversampling_ ) fm_rate = ym2413_rate / 72.0; - Dual_Resampler::setup( fm_rate / blip_buf.sample_rate(), rolloff, gain ); + 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"; - BLARGG_CHECK_ALLOC( !result ); + CHECK_ALLOC( !result ); ym2413.enable( true ); set_voice_count( 8 ); } if ( uses_fm ) { - //dprintf( "fm_rate: %f\n", fm_rate ); - fm_time_factor = 2 + (long) floor( fm_rate * (1L << fm_time_bits) / vgm_rate + 0.5 ); - BLARGG_RETURN_ERR( Dual_Resampler::resize( blip_buf.length() * blip_buf.sample_rate() / 1000 ) ); - psg.volume( 0.135 * gain ); + 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( 1.0 ); + psg.volume( gain() ); } - return blargg_success; -} - -blargg_err_t Vgm_Emu::load( Data_Reader& reader ) -{ - header_t h; - BLARGG_RETURN_ERR( reader.read( &h, sizeof h ) ); - return load( h, reader ); + return 0; } -blargg_err_t Vgm_Emu::load( const header_t& h, Data_Reader& reader ) +// Emulation + +blargg_err_t Vgm_Emu::start_track_( int track ) { - unload(); - - // allocate and read data - long data_size = reader.remain(); - int const padding = 8; - BLARGG_RETURN_ERR( mem.resize( data_size + padding ) ); - blargg_err_t err = reader.read( mem.begin(), data_size ); - if ( err ) { - unload(); - return err; - } - memset( &mem [data_size], 0x66, padding ); // pad with end command - - return load_( h, mem.begin(), data_size ); -} - -void Vgm_Emu::start_track( int track ) -{ - require( data ); // file must have been loaded - - Classic_Emu::start_track( track ); - psg.reset(); + RETURN_ERR( Classic_Emu::start_track_( track ) ); + psg.reset( get_le16( header().noise_feedback ), header().noise_width ); dac_disabled = -1; - pcm_data = data; - pcm_pos = data; - dac_amp = -1; - vgm_time = 0; - pos = data; - if ( get_le32( header_.version ) >= 0x150 ) + pos = data + sizeof (header_t); + 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 ); + long data_offset = get_le32( header().data_offset ); check( data_offset ); if ( data_offset ) pos += data_offset + offsetof (header_t,data_offset) - 0x40; @@ -277,22 +390,21 @@ blip_buf.clear(); Dual_Resampler::clear(); } -} - -long Vgm_Emu::run( int msec, bool* added_stereo ) -{ - blip_time_t psg_end = run_commands( msec * vgm_rate / 1000 ); - *added_stereo = psg.end_frame( psg_end ); - return psg_end; + return 0; } -void Vgm_Emu::play( long count, sample_t* out ) +blargg_err_t Vgm_Emu::run_clocks( blip_time_t& time_io, int msec ) { - require( pos ); // track must have been started - - if ( uses_fm ) - Dual_Resampler::play( count, out, blip_buf ); - else - Classic_Emu::play( count, out ); + 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; +}
--- a/src/console/Vgm_Emu.h Wed Nov 29 14:42:11 2006 -0800 +++ b/src/console/Vgm_Emu.h Thu Nov 30 19:54:33 2006 -0800 @@ -1,12 +1,9 @@ - -// Multi-format VGM music emulator with support for SMS PSG and Mega Drive FM +// Sega Master System/Mark III, Sega Genesis/Mega Drive, BBC Micro VGM music file emulator -// Game_Music_Emu 0.3.0 - +// Game_Music_Emu 0.5.1 #ifndef VGM_EMU_H #define VGM_EMU_H -#include "abstract_file.h" #include "Vgm_Emu_Impl.h" // Emulates VGM music using SN76489/SN76496 PSG, YM2612, and YM2413 FM sound chips. @@ -16,10 +13,13 @@ // YM2413 sound chip emulator. I can provide one I've modified to work with the library. class Vgm_Emu : public Vgm_Emu_Impl { public: + // True if custom buffer and custom equalization are supported + // TODO: move into Music_Emu and rename to something like supports_custom_buffer() + bool is_classic_emu() const { return !uses_fm; } - // Oversample runs FM chips at higher than normal rate. Tempo adjusts speed of - // music, but not pitch. - Vgm_Emu( bool oversample = true, double tempo = 1.0 ); + // Disable running FM chips at higher than normal rate. Will result in slightly + // more aliasing of high notes. + void disable_oversampling( bool disable = true ) { disable_oversampling_ = disable; } // VGM header format struct header_t @@ -41,78 +41,44 @@ byte ym2151_rate [4]; byte data_offset [4]; byte unused2 [8]; - - enum { track_count = 1 }; // one track per file - enum { time_rate = 44100 }; // all times specified at this rate - - // track information is in gd3 data - enum { game = 0 }; - enum { song = 0 }; - enum { author = 0 }; - enum { copyright = 0 }; }; - BOOST_STATIC_ASSERT( sizeof (header_t) == 64 ); - - // Load VGM data - blargg_err_t load( Data_Reader& ); + BOOST_STATIC_ASSERT( sizeof (header_t) == 0x40 ); - // Load VGM using already-loaded header and remaining data - blargg_err_t load( header_t const&, Data_Reader& ); - - // Load VGM using pointer to file data. Keeps pointer to data. - blargg_err_t load( void const* data, long size ); + // Header for currently loaded file + header_t const& header() const { return *(header_t const*) data; } - // Header for currently loaded VGM - header_t const& header() const { return header_; } - - // Pointer to gd3 data, or NULL if none. Optionally returns size of data. - // Checks for GD3 header and that version is less than 2.0. - byte const* gd3_data( int* size_out = NULL ) const; - - // to do: find better name for this - // True if Classic_Emu operations are supported - bool is_classic_emu() const { return !uses_fm; } + static gme_type_t static_type() { return gme_vgm_type; } public: - ~Vgm_Emu(); - blargg_err_t set_sample_rate( long sample_rate ); - void start_track( int ); - void mute_voices( int mask ); - const char** voice_names() const; - void play( long count, sample_t* ); + // deprecated + Music_Emu::load; + blargg_err_t load( header_t const& h, Data_Reader& in ) // use Remaining_Reader + { return load_remaining_( &h, sizeof h, in ); } + byte const* gd3_data( int* size_out = 0 ) const; // use track_info() + public: - // deprecated - int track_length( const byte** end_out = NULL, int* remain_out = NULL ) const - { - return (header().track_duration [3]*0x1000000L + - header().track_duration [2]*0x0010000L + - header().track_duration [1]*0x0000100L + - header().track_duration [0]) / header_t::time_rate; - } + Vgm_Emu(); + ~Vgm_Emu(); protected: - // Classic_Emu + blargg_err_t track_info_( track_info_t*, int track ) const; + blargg_err_t load_mem_( byte const*, long ); + blargg_err_t set_sample_rate_( long sample_rate ); + blargg_err_t start_track_( int ); + blargg_err_t play_( long count, sample_t* ); + blargg_err_t run_clocks( blip_time_t&, int ); + void set_tempo_( double ); + void mute_voices_( int mask ); void set_voice( int, Blip_Buffer*, Blip_Buffer*, Blip_Buffer* ); void update_eq( blip_eq_t const& ); - blip_time_t run( int, bool* ); private: - header_t header_; - blargg_vector<byte> mem; + // removed; use disable_oversampling() and set_tempo() instead + Vgm_Emu( bool oversample, double tempo = 1.0 ); + double fm_rate; + long psg_rate; long vgm_rate; - bool oversample; + bool disable_oversampling_; bool uses_fm; - - blargg_err_t init_( long sample_rate ); - blargg_err_t load_( const header_t&, void const* data, long size ); blargg_err_t setup_fm(); - void unload(); }; -inline blargg_err_t Vgm_Emu::load( void const* data, long size ) -{ - unload(); - return load_( *(header_t*) data, (char*) data + sizeof (header_t), - size - sizeof (header_t) ); -} - #endif -
--- a/src/console/Vgm_Emu_Impl.cxx Wed Nov 29 14:42:11 2006 -0800 +++ b/src/console/Vgm_Emu_Impl.cxx Thu Nov 30 19:54:33 2006 -0800 @@ -1,5 +1,4 @@ - -// Game_Music_Emu 0.3.0. http://www.slack.net/~ant/ +// Game_Music_Emu 0.5.1. http://www.slack.net/~ant/ #include "Vgm_Emu.h" @@ -13,12 +12,12 @@ 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 */ +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_BEGIN +#include "blargg_source.h" enum { cmd_gg_stereo = 0x4F, @@ -63,7 +62,8 @@ return 5; } - return 0; + check( false ); + return 1; } template<class Emu> @@ -120,11 +120,13 @@ { set_track_ended(); if ( pos > data_end ) - log_error(); + set_warning( "Stream lacked end event" ); } while ( vgm_time < end_time && pos < data_end ) { + // TODO: be sure there are enough bytes left in stream for particular command + // so we don't read past end switch ( *pos++ ) { case cmd_end: @@ -204,15 +206,15 @@ default: int cmd = pos [-1]; - switch ( cmd & 0xf0 ) + switch ( cmd & 0xF0 ) { case cmd_pcm_delay: - vgm_time += cmd & 0x0f; write_pcm( vgm_time, *pcm_pos++ ); + vgm_time += cmd & 0x0F; break; case cmd_short_delay: - vgm_time += (cmd & 0x0f) + 1; + vgm_time += (cmd & 0x0F) + 1; break; case 0x50: @@ -221,7 +223,7 @@ default: pos += command_len( cmd ) - 1; - log_error(); + set_warning( "Unknown stream event" ); } } } @@ -242,6 +244,7 @@ int pairs = min_pairs; while ( (pairs = to_fm_time( vgm_time )) < min_pairs ) vgm_time++; + //dprintf( "pairs: %d, min_pairs: %d\n", pairs, min_pairs ); if ( ym2612.enabled() ) { @@ -268,7 +271,7 @@ // Update pre-1.10 header FM rates by scanning commands void Vgm_Emu_Impl::update_fm_rates( long* ym2413_rate, long* ym2612_rate ) const { - byte const* p = data; + byte const* p = data + 0x40; while ( p < data_end ) { switch ( *p ) @@ -309,4 +312,3 @@ } } } -
--- a/src/console/Vgm_Emu_Impl.h Wed Nov 29 14:42:11 2006 -0800 +++ b/src/console/Vgm_Emu_Impl.h Thu Nov 30 19:54:33 2006 -0800 @@ -1,6 +1,6 @@ +// Low-level parts of Vgm_Emu -// Game_Music_Emu 0.3.0 - +// Game_Music_Emu 0.5.1 #ifndef VGM_EMU_IMPL_H #define VGM_EMU_IMPL_H @@ -27,8 +27,6 @@ class Vgm_Emu_Impl : public Classic_Emu, private Dual_Resampler { public: typedef Classic_Emu::sample_t sample_t; - typedef BOOST::uint8_t byte; - protected: enum { stereo = 2 }; @@ -71,4 +69,3 @@ }; #endif -
--- a/src/console/Ym2413_Emu.cxx Wed Nov 29 14:42:11 2006 -0800 +++ b/src/console/Ym2413_Emu.cxx Thu Nov 30 19:54:33 2006 -0800 @@ -1,7 +1,7 @@ // Use in place of Ym2413_Emu.cpp and ym2413.c to disable support for this chip -// Game_Music_Emu 0.3.0. http://www.slack.net/~ant/ +// Game_Music_Emu 0.5.1. http://www.slack.net/~ant/ #include "Ym2413_Emu.h"
--- a/src/console/Ym2413_Emu.h Wed Nov 29 14:42:11 2006 -0800 +++ b/src/console/Ym2413_Emu.h Thu Nov 30 19:54:33 2006 -0800 @@ -1,8 +1,6 @@ - // YM2413 FM sound chip emulator interface -// Game_Music_Emu 0.3.0 - +// Game_Music_Emu 0.5.1 #ifndef YM2413_EMU_H #define YM2413_EMU_H @@ -33,4 +31,3 @@ }; #endif -
--- a/src/console/Ym2612_Emu.cxx Wed Nov 29 14:42:11 2006 -0800 +++ b/src/console/Ym2612_Emu.cxx Thu Nov 30 19:54:33 2006 -0800 @@ -1,5 +1,4 @@ - -// Game_Music_Emu 0.3.0. http://www.slack.net/~ant/ +// Game_Music_Emu 0.5.1. http://www.slack.net/~ant/ // Based on Gens 2.10 ym2612.c @@ -19,10 +18,10 @@ 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 */ +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 */ // This is mostly the original source in its C style and all. // @@ -33,6 +32,10 @@ // high sample accuracy (the Genesis sounds like it has only 8 bit samples). // - Shay +#ifdef BLARGG_ENABLE_OPTIMIZER + #include BLARGG_ENABLE_OPTIMIZER +#endif + const int output_bits = 14; struct slot_t @@ -547,7 +550,7 @@ break; case 0x25: - YM2612.TimerA = (YM2612.TimerA & 0x3fc) | (data & 3); + YM2612.TimerA = (YM2612.TimerA & 0x3FC) | (data & 3); if (YM2612.TimerAL != (1024 - YM2612.TimerA) << 12) { @@ -636,7 +639,6 @@ // prescale set to 6 by default double Frequence = clock_rate / sample_rate / 144.0; - //dprintf( "Frequence: %.40f\n", Frequence ); if ( fabs( Frequence - 1.0 ) < 0.0000001 ) Frequence = 1.0; YM2612.TimerBase = int (Frequence * 4096.0); @@ -1318,4 +1320,3 @@ } void Ym2612_Emu::run( int pair_count, sample_t* out ) { impl->run( pair_count, out ); } -
--- a/src/console/Ym2612_Emu.h Wed Nov 29 14:42:11 2006 -0800 +++ b/src/console/Ym2612_Emu.h Thu Nov 30 19:54:33 2006 -0800 @@ -1,8 +1,6 @@ - // YM2612 FM sound chip emulator interface -// Game_Music_Emu 0.3.0 - +// Game_Music_Emu 0.5.1 #ifndef YM2612_EMU_H #define YM2612_EMU_H @@ -38,4 +36,3 @@ }; #endif -
--- a/src/console/blargg_common.h Wed Nov 29 14:42:11 2006 -0800 +++ b/src/console/blargg_common.h Thu Nov 30 19:54:33 2006 -0800 @@ -1,100 +1,122 @@ - // Sets up common environment for Shay Green's libraries. -// // To change configuration options, modify blargg_config.h, not this file. #ifndef BLARGG_COMMON_H #define BLARGG_COMMON_H -// HAVE_CONFIG_H: If defined, include user's "config.h" first (which *can* -// re-include blargg_common.h if it needs to) -#ifdef HAVE_CONFIG_H - #undef BLARGG_COMMON_H - #include "config.h" - #define BLARGG_COMMON_H +#include <stddef.h> +#include <stdlib.h> +#include <assert.h> +#include <limits.h> + +#undef BLARGG_COMMON_H +// allow blargg_config.h to #include blargg_common.h +#include "blargg_config.h" +#ifndef BLARGG_COMMON_H +#define BLARGG_COMMON_H + +// STATIC_CAST(T,expr): Used in place of static_cast<T> (expr) +#ifndef STATIC_CAST + #define STATIC_CAST(T,expr) ((T) (expr)) +#endif + +// blargg_err_t (0 on success, otherwise error string) +#ifndef blargg_err_t + typedef const char* blargg_err_t; #endif -// BLARGG_NONPORTABLE: If defined to 1, platform-specific (and possibly non-portable) -// optimizations are used. Defaults to off. Report any problems that occur only when -// this is enabled. -#ifndef BLARGG_NONPORTABLE - #define BLARGG_NONPORTABLE 0 +// blargg_vector - very lightweight vector of POD types (no constructor/destructor) +template<class T> +class blargg_vector { + T* begin_; + size_t size_; +public: + blargg_vector() : begin_( 0 ), size_( 0 ) { } + ~blargg_vector() { free( begin_ ); } + size_t size() const { return size_; } + T* begin() const { return begin_; } + T* end() const { return begin_ + size_; } + blargg_err_t resize( size_t n ) + { + void* p = realloc( begin_, n * sizeof (T) ); + if ( !p && n ) + return "Out of memory"; + begin_ = (T*) p; + size_ = n; + return 0; + } + void clear() { void* p = begin_; begin_ = 0; size_ = 0; free( p ); } + T& operator [] ( size_t n ) const + { + assert( n <= size_ ); // <= to allow past-the-end value + return begin_ [n]; + } +}; + +#ifndef BLARGG_DISABLE_NOTHROW + #define BLARGG_DISABLE_NOTHROW \ + void* operator new ( size_t s ) { return malloc( s ); }\ + void operator delete ( void* p ) { free( p ); } + #define BLARGG_NEW new +#else + #include <new> + #define BLARGG_NEW new (std::nothrow) #endif -// BLARGG_BIG_ENDIAN, BLARGG_LITTLE_ENDIAN: Determined automatically, otherwise only -// one must be #defined to 1. Only needed if something actually depends on byte order. -#if !defined (BLARGG_BIG_ENDIAN) && !defined (BLARGG_LITTLE_ENDIAN) - #if defined (MSB_FIRST) || defined (__powerc) || defined (macintosh) || \ - defined (WORDS_BIGENDIAN) || defined (__BIG_ENDIAN__) - #define BLARGG_BIG_ENDIAN 1 +#define BLARGG_4CHAR( a, b, c, d ) \ + ((a&0xFF)*0x1000000L + (b&0xFF)*0x10000L + (c&0xFF)*0x100L + (d&0xFF)) + +// BOOST_STATIC_ASSERT( expr ): Generates compile error if expr is 0. +#ifndef BOOST_STATIC_ASSERT + #ifdef _MSC_VER + // MSVC6 (_MSC_VER < 1300) fails for use of __LINE__ when /Zl is specified + #define BOOST_STATIC_ASSERT( expr ) \ + void blargg_failed_( int (*arg) [2 / !!(expr) - 1] ) #else - #define BLARGG_LITTLE_ENDIAN 1 + // Some other compilers fail when declaring same function multiple times in class, + // so differentiate them by line + #define BOOST_STATIC_ASSERT( expr ) \ + void blargg_failed_( int (*arg) [2 / !!(expr) - 1] [__LINE__] ) #endif #endif -// Determine compiler's language support - -// Metrowerks CodeWarrior -#if defined (__MWERKS__) - #define BLARGG_COMPILER_HAS_NAMESPACE 1 - #if !__option(bool) - #define BLARGG_COMPILER_HAS_BOOL 0 - #endif - #define STATIC_CAST(T,expr) static_cast< T > (expr) - -// Microsoft Visual C++ -#elif defined (_MSC_VER) - #if _MSC_VER < 1100 +// BLARGG_COMPILER_HAS_BOOL: If 0, provides bool support for old compiler. If 1, +// compiler is assumed to support bool. If undefined, availability is determined. +#ifndef BLARGG_COMPILER_HAS_BOOL + #if defined (__MWERKS__) + #if !__option(bool) + #define BLARGG_COMPILER_HAS_BOOL 0 + #endif + #elif defined (_MSC_VER) + #if _MSC_VER < 1100 + #define BLARGG_COMPILER_HAS_BOOL 0 + #endif + #elif defined (__GNUC__) + // supports bool + #elif __cplusplus < 199711 #define BLARGG_COMPILER_HAS_BOOL 0 #endif - -// GNU C++ -#elif defined (__GNUC__) - #if __GNUC__ > 2 - #define BLARGG_COMPILER_HAS_NAMESPACE 1 - #endif - -// Mingw -#elif defined (__MINGW32__) - // empty - -// Pre-ISO C++ compiler -#elif __cplusplus < 199711 - #ifndef BLARGG_COMPILER_HAS_BOOL - #define BLARGG_COMPILER_HAS_BOOL 0 - #endif - #endif - -/* BLARGG_COMPILER_HAS_BOOL: If 0, provides bool support for old compilers. - If errors occur here, add the following line to your config.h file: - #define BLARGG_COMPILER_HAS_BOOL 0 -*/ #if defined (BLARGG_COMPILER_HAS_BOOL) && !BLARGG_COMPILER_HAS_BOOL + // If you get errors here, modify your blargg_config.h file typedef int bool; const bool true = 1; const bool false = 0; #endif -// BLARGG_USE_NAMESPACE: If 1, use <cxxx> headers rather than <xxxx.h> -#if BLARGG_USE_NAMESPACE || (!defined (BLARGG_USE_NAMESPACE) && BLARGG_COMPILER_HAS_NAMESPACE) - #include <cstddef> - #include <cstdlib> - #include <cassert> - #include <climits> - #define STD std +// blargg_long/blargg_ulong = at least 32 bits, int if it's big enough +#include <limits.h> + +#if INT_MAX >= 0x7FFFFFFF + typedef int blargg_long; #else - #include <stddef.h> - #include <stdlib.h> - #include <assert.h> - #include <limits.h> - #define STD + typedef long blargg_long; #endif -// BLARGG_NEW is used in place of 'new' to create objects. By default, plain new is used. -// To prevent an exception if out of memory, #define BLARGG_NEW new (std::nothrow) -#ifndef BLARGG_NEW - #define BLARGG_NEW new +#if UINT_MAX >= 0xFFFFFFFF + typedef unsigned blargg_ulong; +#else + typedef unsigned long blargg_ulong; #endif // BOOST::int8_t etc. @@ -144,99 +166,5 @@ }; #endif -// BLARGG_SOURCE_BEGIN: Library sources #include this after other #includes. -#ifndef BLARGG_SOURCE_BEGIN - #define BLARGG_SOURCE_BEGIN "blargg_source.h" -#endif - -// BLARGG_ENABLE_OPTIMIZER: Library sources #include this for speed-critical code -#ifndef BLARGG_ENABLE_OPTIMIZER - #define BLARGG_ENABLE_OPTIMIZER "blargg_common.h" -#endif - -// BLARGG_CPU_*: Used to select between some optimizations -#if !defined (BLARGG_CPU_POWERPC) && !defined (BLARGG_CPU_X86) - #if defined (__powerc) - #define BLARGG_CPU_POWERPC 1 - #elif defined (_MSC_VER) && defined (_M_IX86) - #define BLARGG_CPU_X86 1 - #endif -#endif - -// BOOST_STATIC_ASSERT( expr ): Generates compile error if expr is 0. -#ifndef BOOST_STATIC_ASSERT - #ifdef _MSC_VER - // MSVC6 (_MSC_VER < 1300) fails for use of __LINE__ when /Zl is specified - #define BOOST_STATIC_ASSERT( expr ) \ - void blargg_failed_( int (*arg) [2 / ((expr) ? 1 : 0) - 1] ) - #else - // Some other compilers fail when declaring same function multiple times in class, - // so differentiate them by line - #define BOOST_STATIC_ASSERT( expr ) \ - void blargg_failed_( int (*arg) [2 / ((expr) ? 1 : 0) - 1] [__LINE__] ) - #endif -#endif - -// STATIC_CAST(T,expr): Used in place of static_cast<T> (expr) -#ifndef STATIC_CAST - #define STATIC_CAST(T,expr) ((T) (expr)) -#endif - -// blargg_err_t (NULL on success, otherwise error string) -#ifndef blargg_err_t - typedef const char* blargg_err_t; #endif -const char* const blargg_success = 0; - -// blargg_vector: Simple array that does *not* work for types with a constructor (non-POD). -template<class T> -class blargg_vector { - T* begin_; - STD::size_t size_; -public: - blargg_vector() : begin_( 0 ), size_( 0 ) { } - ~blargg_vector() { STD::free( begin_ ); } - - typedef STD::size_t size_type; - - blargg_err_t resize( size_type n ) - { - void* p = STD::realloc( begin_, n * sizeof (T) ); - if ( !p && n ) - return "Out of memory"; - begin_ = (T*) p; - size_ = n; - return 0; - } - - void clear() - { - void* p = begin_; - begin_ = 0; - size_ = 0; - STD::free( p ); - } - - size_type size() const { return size_; } - - T* begin() { return begin_; } - T* end() { return begin_ + size_; } - - const T* begin() const { return begin_; } - const T* end() const { return begin_ + size_; } - - T& operator [] ( size_type n ) - { - assert( n <= size_ ); // allow for past-the-end value - return begin_ [n]; - } - - const T& operator [] ( size_type n ) const - { - assert( n <= size_ ); // allow for past-the-end value - return begin_ [n]; - } -}; - #endif -
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/console/blargg_config.h Thu Nov 30 19:54:33 2006 -0800 @@ -0,0 +1,26 @@ +// Game_Music_Emu 0.5.1 user configuration file. Don't replace when updating library. + +#ifndef BLARGG_CONFIG_H +#define BLARGG_CONFIG_H + +// Uncomment to use zlib for transparent decompression of gzipped files +#define HAVE_ZLIB_H + +// Uncomment to enable platform-specific (and possibly non-portable) optimizations. +//#define BLARGG_NONPORTABLE 1 + +// Uncomment if automatic byte-order determination doesn't work +//#define BLARGG_BIG_ENDIAN 1 + +// Uncomment if you get errors in the bool section of blargg_common.h +//#define BLARGG_COMPILER_HAS_BOOL 1 + +// Uncomment to disable out-of-memory exceptions +#define BLARGG_DISABLE_NOTHROW + +// You can have the library use your own custom Data_Reader +//#define GME_FILE_READER Gzip_File_Reader +#include "Vfs_File.h" +#define GME_FILE_READER Vfs_File_Reader + +#endif
--- a/src/console/blargg_endian.h Wed Nov 29 14:42:11 2006 -0800 +++ b/src/console/blargg_endian.h Thu Nov 30 19:54:33 2006 -0800 @@ -1,156 +1,158 @@ - // CPU Byte Order Utilities -// Game_Music_Emu 0.3.0 - +// Game_Music_Emu 0.5.1 #ifndef BLARGG_ENDIAN #define BLARGG_ENDIAN #include "blargg_common.h" -#if 0 - // Read 16/32-bit little-endian integer from memory - unsigned GET_LE16( void const* ); - unsigned long GET_LE32( void const* ); +// BLARGG_CPU_CISC: Defined if CPU has very few general-purpose registers (< 16) +#if defined (_M_IX86) || defined (_M_IA64) || defined (__i486__) || \ + defined (__x86_64__) || defined (__ia64__) || defined (__i386__) + #define BLARGG_CPU_X86 1 + #define BLARGG_CPU_CISC 1 +#endif - // Read 16/32-bit big-endian integer from memory - unsigned GET_BE16( void const* ); - unsigned long GET_BE32( void const* ); - - // Write 16/32-bit integer to memory in little-endian format - void SET_LE16( void*, unsigned ); - void SET_LE32( void*, unsigned ); - - // Write 16/32-bit integer to memory in big-endian format - void SET_BE16( void*, unsigned long ); - void SET_BE32( void*, unsigned long ); +#if defined (__powerpc__) || defined (__ppc__) || defined (__POWERPC__) || defined (__powerc) + #define BLARGG_CPU_POWERPC 1 #endif -inline unsigned get_le16( void const* p ) +// BLARGG_BIG_ENDIAN, BLARGG_LITTLE_ENDIAN: Determined automatically, otherwise only +// one may be #defined to 1. Only needed if something actually depends on byte order. +#if !defined (BLARGG_BIG_ENDIAN) && !defined (BLARGG_LITTLE_ENDIAN) +#ifdef __GLIBC__ + // GCC handles this for us + #include <endian.h> + #if __BYTE_ORDER == __LITTLE_ENDIAN + #define BLARGG_LITTLE_ENDIAN 1 + #elif __BYTE_ORDER == __BIG_ENDIAN + #define BLARGG_BIG_ENDIAN 1 + #endif +#else + +#if defined (LSB_FIRST) || defined (__LITTLE_ENDIAN__) || BLARGG_CPU_X86 || \ + (defined (LITTLE_ENDIAN) && LITTLE_ENDIAN+0 != 1234) + #define BLARGG_LITTLE_ENDIAN 1 +#endif + +#if defined (MSB_FIRST) || defined (__BIG_ENDIAN__) || defined (WORDS_BIGENDIAN) || \ + defined (__mips__) || defined (__sparc__) || BLARGG_CPU_POWERPC || \ + (defined (BIG_ENDIAN) && BIG_ENDIAN+0 != 4321) + #define BLARGG_BIG_ENDIAN 1 +#else + // No endian specified; assume little-endian, since it's most common + #define BLARGG_LITTLE_ENDIAN 1 +#endif +#endif +#endif + +#if BLARGG_LITTLE_ENDIAN && BLARGG_BIG_ENDIAN + #undef BLARGG_LITTLE_ENDIAN + #undef BLARGG_BIG_ENDIAN +#endif + +inline void blargg_verify_byte_order() { - return ((unsigned char*) p) [1] * 0x100 + - ((unsigned char*) p) [0]; + #ifndef NDEBUG + #if BLARGG_BIG_ENDIAN + volatile int i = 1; + assert( *(volatile char*) &i == 0 ); + #elif BLARGG_LITTLE_ENDIAN + volatile int i = 1; + assert( *(volatile char*) &i != 0 ); + #endif + #endif } -inline unsigned get_be16( void const* p ) -{ - return ((unsigned char*) p) [0] * 0x100 + - ((unsigned char*) p) [1]; +inline unsigned get_le16( void const* p ) { + return ((unsigned char const*) p) [1] * 0x100u + + ((unsigned char const*) p) [0]; +} +inline unsigned get_be16( void const* p ) { + return ((unsigned char const*) p) [0] * 0x100u + + ((unsigned char const*) p) [1]; } - -inline unsigned long get_le32( void const* p ) -{ - return ((unsigned char*) p) [3] * 0x01000000 + - ((unsigned char*) p) [2] * 0x00010000 + - ((unsigned char*) p) [1] * 0x00000100 + - ((unsigned char*) p) [0]; +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]; } - -inline unsigned long get_be32( void const* p ) -{ - return ((unsigned char*) p) [0] * 0x01000000 + - ((unsigned char*) p) [1] * 0x00010000 + - ((unsigned char*) p) [2] * 0x00000100 + - ((unsigned char*) p) [3]; +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]; } - -inline void set_le16( void* p, unsigned n ) -{ +inline void set_le16( void* p, unsigned n ) { ((unsigned char*) p) [1] = (unsigned char) (n >> 8); ((unsigned char*) p) [0] = (unsigned char) n; } - -inline void set_be16( void* p, unsigned n ) -{ +inline void set_be16( void* p, unsigned n ) { ((unsigned char*) p) [0] = (unsigned char) (n >> 8); ((unsigned char*) p) [1] = (unsigned char) n; } - -inline void set_le32( void* p, unsigned long n ) -{ +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); ((unsigned char*) p) [0] = (unsigned char) n; } - -inline void set_be32( void* p, unsigned long n ) -{ +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); ((unsigned char*) p) [3] = (unsigned char) n; } -#ifndef GET_LE16 +#if BLARGG_NONPORTABLE // Optimized implementation if byte order is known - #if BLARGG_NONPORTABLE && BLARGG_LITTLE_ENDIAN + #if BLARGG_LITTLE_ENDIAN #define GET_LE16( addr ) (*(BOOST::uint16_t*) (addr)) #define GET_LE32( addr ) (*(BOOST::uint32_t*) (addr)) - #define SET_LE16( addr, data ) (void (*(BOOST::uint16_t*) (addr) = (data))) - #define SET_LE32( addr, data ) (void (*(BOOST::uint32_t*) (addr) = (data))) - - #elif BLARGG_NONPORTABLE && BLARGG_CPU_POWERPC + #define SET_LE16( addr, data ) (void) (*(BOOST::uint16_t*) (addr) = (data)) + #define SET_LE32( addr, data ) (void) (*(BOOST::uint32_t*) (addr) = (data)) + #elif BLARGG_BIG_ENDIAN + #define GET_BE16( addr ) (*(BOOST::uint16_t*) (addr)) + #define GET_BE32( addr ) (*(BOOST::uint32_t*) (addr)) + #define SET_BE16( addr, data ) (void) (*(BOOST::uint16_t*) (addr) = (data)) + #define SET_BE32( addr, data ) (void) (*(BOOST::uint32_t*) (addr) = (data)) + #endif + + #if BLARGG_CPU_POWERPC && defined (__MWERKS__) // PowerPC has special byte-reversed instructions // to do: assumes that PowerPC is running in big-endian mode + // to do: implement for other compilers which don't support these macros #define GET_LE16( addr ) (__lhbrx( (addr), 0 )) #define GET_LE32( addr ) (__lwbrx( (addr), 0 )) #define SET_LE16( addr, data ) (__sthbrx( (data), (addr), 0 )) #define SET_LE32( addr, data ) (__stwbrx( (data), (addr), 0 )) - - #define GET_BE16( addr ) (*(BOOST::uint16_t*) (addr)) - #define GET_BE32( addr ) (*(BOOST::uint32_t*) (addr)) - #define SET_BE16( addr, data ) (void (*(BOOST::uint16_t*) (addr) = (data))) - #define SET_BE32( addr, data ) (void (*(BOOST::uint32_t*) (addr) = (data))) - #endif #endif #ifndef GET_LE16 #define GET_LE16( addr ) get_le16( addr ) -#endif - -#ifndef GET_LE32 #define GET_LE32( addr ) get_le32( addr ) -#endif - -#ifndef SET_LE16 #define SET_LE16( addr, data ) set_le16( addr, data ) -#endif - -#ifndef SET_LE32 #define SET_LE32( addr, data ) set_le32( addr, data ) #endif #ifndef GET_BE16 #define GET_BE16( addr ) get_be16( addr ) -#endif - -#ifndef GET_BE32 #define GET_BE32( addr ) get_be32( addr ) -#endif - -#ifndef SET_BE16 #define SET_BE16( addr, data ) set_be16( addr, data ) -#endif - -#ifndef SET_BE32 #define SET_BE32( addr, data ) set_be32( addr, data ) #endif // auto-selecting versions -inline void set_le( BOOST::uint16_t* p, unsigned n ) { SET_LE16( p, n ); } -inline void set_le( BOOST::uint32_t* p, unsigned long n ) { SET_LE32( p, n ); } - -inline void set_be( BOOST::uint16_t* p, unsigned n ) { SET_BE16( p, n ); } -inline void set_be( BOOST::uint32_t* p, unsigned long n ) { SET_BE32( p, n ); } - -inline unsigned get_le( BOOST::uint16_t* p ) { return GET_LE16( p ); } -inline unsigned long get_le( BOOST::uint32_t* p ) { return GET_LE32( p ); } - -inline unsigned get_be( BOOST::uint16_t* p ) { return GET_BE16( p ); } -inline unsigned long get_be( BOOST::uint32_t* p ) { return GET_BE32( p ); } +inline void set_le( BOOST::uint16_t* p, unsigned n ) { SET_LE16( p, n ); } +inline void set_le( BOOST::uint32_t* p, blargg_ulong n ) { SET_LE32( p, n ); } +inline void set_be( BOOST::uint16_t* p, unsigned n ) { SET_BE16( p, n ); } +inline void set_be( BOOST::uint32_t* p, blargg_ulong n ) { SET_BE32( p, n ); } +inline unsigned get_le( BOOST::uint16_t* p ) { return GET_LE16( p ); } +inline blargg_ulong get_le( BOOST::uint32_t* p ) { return GET_LE32( p ); } +inline unsigned get_be( BOOST::uint16_t* p ) { return GET_BE16( p ); } +inline blargg_ulong get_be( BOOST::uint32_t* p ) { return GET_BE32( p ); } #endif -
--- a/src/console/blargg_source.h Wed Nov 29 14:42:11 2006 -0800 +++ b/src/console/blargg_source.h Thu Nov 30 19:54:33 2006 -0800 @@ -1,9 +1,4 @@ - -// By default, #included at beginning of library source files. -// Can be overridden by #defining BLARGG_SOURCE_BEGIN to path of alternate file. - -// Copyright (C) 2005 Shay Green. - +// Included at the beginning of library source files, after all other #include lines #ifndef BLARGG_SOURCE_H #define BLARGG_SOURCE_H @@ -22,32 +17,26 @@ // Like printf() except output goes to debug log file. Might be defined to do // nothing (not even evaluate its arguments). // void dprintf( const char* format, ... ); +inline void blargg_dprintf_( const char*, ... ) { } #undef dprintf -#ifdef BLARGG_DPRINTF - #define dprintf BLARGG_DPRINTF -#else - inline void blargg_dprintf_( const char*, ... ) { } - #define dprintf (1) ? (void) 0 : blargg_dprintf_ -#endif +#define dprintf (1) ? (void) 0 : blargg_dprintf_ // If enabled, evaluate expr and if false, make debug log entry with source file // and line. Meant for finding situations that should be examined further, but that // don't indicate a problem. In all cases, execution continues normally. #undef check -#ifdef BLARGG_CHECK - #define check( expr ) BLARGG_CHECK( expr ) -#else - #define check( expr ) ((void) 0) -#endif +#define check( expr ) ((void) 0) -// If expr returns non-NULL error string, return it from current function, otherwise continue. -#define BLARGG_RETURN_ERR( expr ) do { \ +// 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_; \ } while ( 0 ) -// If ptr is NULL, return out of memory error string. -#define BLARGG_CHECK_ALLOC( ptr ) do { if ( (ptr) == 0 ) return "Out of memory"; } 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 ) // Avoid any macros which evaluate their arguments multiple times #undef min @@ -72,5 +61,18 @@ return x; } +// TODO: good idea? bad idea? +#undef byte +#define byte byte_ +typedef unsigned char byte; + +// deprecated +#define BLARGG_CHECK_ALLOC CHECK_ALLOC +#define BLARGG_RETURN_ERR RETURN_ERR + +// BLARGG_SOURCE_BEGIN: If defined, #included, allowing redefition of dprintf and check +#ifdef BLARGG_SOURCE_BEGIN + #include BLARGG_SOURCE_BEGIN #endif +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/console/gb_cpu_io.h Thu Nov 30 19:54:33 2006 -0800 @@ -0,0 +1,69 @@ + +#include "Gbs_Emu.h" + +#include "blargg_source.h" + +int Gbs_Emu::cpu_read( gb_addr_t addr ) +{ + int result = *cpu::get_code( addr ); + if ( unsigned (addr - Gb_Apu::start_addr) < Gb_Apu::register_count ) + result = apu.read_register( clock(), addr ); +#ifndef NDEBUG + else if ( unsigned (addr - 0x8000) < 0x2000 || unsigned (addr - 0xE000) < 0x1F00 ) + dprintf( "Read from unmapped memory $%.4x\n", (unsigned) addr ); + else if ( unsigned (addr - 0xFF01) < 0xFF80 - 0xFF01 ) + dprintf( "Unhandled I/O read 0x%4X\n", (unsigned) addr ); +#endif + return result; +} + +void Gbs_Emu::cpu_write( gb_addr_t addr, int data ) +{ + unsigned offset = addr - ram_addr; + if ( offset <= 0xFFFF - ram_addr ) + { + ram [offset] = data; + if ( (addr ^ 0xE000) <= 0x1F80 - 1 ) + { + if ( unsigned (addr - Gb_Apu::start_addr) < Gb_Apu::register_count ) + apu.write_register( clock(), addr, data ); + else if ( (addr ^ 0xFF06) < 2 ) + update_timer(); + else if ( addr == joypad_addr ) + ram [offset] = 0; // keep joypad return value 0 + else + ram [offset] = 0xFF; + + //if ( addr == 0xFFFF ) + // dprintf( "Wrote interrupt mask\n" ); + } + } + else if ( (addr ^ 0x2000) <= 0x2000 - 1 ) + { + set_bank( data ); + } +#ifndef NDEBUG + else if ( unsigned (addr - 0x8000) < 0x2000 || unsigned (addr - 0xE000) < 0x1F00 ) + { + dprintf( "Wrote to unmapped memory $%.4x\n", (unsigned) addr ); + } +#endif +} + +#define CPU_READ_FAST( cpu, addr, time, out ) \ + CPU_READ_FAST_( STATIC_CAST(Gbs_Emu*,cpu), addr, time, out ) + +#define CPU_READ_FAST_( emu, addr, time, out ) \ +{\ + out = READ_PROG( addr );\ + if ( unsigned (addr - Gb_Apu::start_addr) <= Gb_Apu::register_count )\ + out = emu->apu.read_register( emu->cpu_time - time * clocks_per_instr, addr );\ + else\ + check( out == emu->cpu_read( addr ) );\ +} + +#define CPU_READ( cpu, addr, time ) \ + STATIC_CAST(Gbs_Emu*,cpu)->cpu_read( addr ) + +#define CPU_WRITE( cpu, addr, data, time ) \ + STATIC_CAST(Gbs_Emu*,cpu)->cpu_write( addr, data )
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/console/gme.cxx Thu Nov 30 19:54:33 2006 -0800 @@ -0,0 +1,176 @@ +// Game_Music_Emu 0.5.1. http://www.slack.net/~ant/ + +#include "Music_Emu.h" + +#include "Effects_Buffer.h" +#include "blargg_endian.h" +#include <string.h> +#include <ctype.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 +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" + +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','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_, gme_type_t const* types ) +{ + char const* end = strrchr( extension_, '.' ); + if ( end ) + extension_ = end + 1; + + char extension [6]; + to_uppercase( extension_, sizeof extension, extension ); + + for ( ; *types; types++ ) + if ( !strcmp( extension, (*types)->extension_ ) ) + return *types; + return 0; +} + +gme_err_t gme_identify_file( const char* path, gme_type_t const* types, gme_type_t* type_out ) +{ + *type_out = gme_identify_extension( path, types ); + 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 ), types ); + } + return 0; +} + +Music_Emu* gme_new_emu( gme_type_t type, long rate ) +{ + if ( type ) + { + Music_Emu* me = type->new_emu(); + if ( me ) + { + if ( type->flags_ & 1 ) + { + me->effects_buffer = BLARGG_NEW Effects_Buffer; + if ( me->effects_buffer ) + me->set_buffer( me->effects_buffer ); + } + + if ( !(type->flags_ & 1) || me->effects_buffer ) + { + if ( !me->set_sample_rate( rate ) ) + { + check( me->type() == type ); + return me; + } + } + delete me; + } + } + return 0; +} + +Music_Emu* gme_new_info( gme_type_t type ) +{ + if ( !type ) + return 0; + + return type->new_info(); +} + +const char* gme_load_file( Music_Emu* me, const char* path ) { return me->load_file( path ); } + +const char* gme_load_data( Music_Emu* me, const char* data, long size ) +{ + Mem_File_Reader in( data, size ); + return me->load( in ); +} + +gme_err_t gme_load_custom( Music_Emu* me, gme_reader_t func, long size, void* data ) +{ + Callback_Reader in( func, size, data ); + return me->load( in ); +} + +void gme_delete( Music_Emu* me ) { delete me; } + +gme_type_t gme_type( Music_Emu const* me ) { return me->type(); } + +const char* gme_warning( Music_Emu* me ) { return me->warning(); } + +int gme_track_count( Music_Emu const* me ) { return me->track_count(); } + +const char* gme_track_info( Music_Emu const* me, track_info_t* out, int track ) +{ + return me->track_info( out, track ); +} + +void gme_set_stereo_depth( Music_Emu* me, double depth ) +{ + if ( me->effects_buffer ) + STATIC_CAST(Effects_Buffer*,me->effects_buffer)->set_depth( depth ); +} + +gme_err_t gme_start_track( Music_Emu* me, int index ) { return me->start_track( index ); } + +gme_err_t gme_play( Music_Emu* me, long n, short* p ) { return me->play( n, p ); } + +void gme_set_fade( Music_Emu* me, long start_msec ) { me->set_fade( start_msec ); } + +int gme_track_ended( Music_Emu const* me ) { return me->track_ended(); } + +long gme_tell( Music_Emu const* me ) { return me->tell(); } + +gme_err_t gme_seek( Music_Emu* me, long msec ) { return me->seek( msec ); } + +int gme_voice_count( Music_Emu const* me ) { return me->voice_count(); } + +const char** gme_voice_names( Music_Emu const* me ) { return me->voice_names(); } + +void gme_ignore_silence( Music_Emu* me, int disable ) { me->ignore_silence( disable != 0 ); } + +void gme_set_tempo( Music_Emu* me, double t ) { me->set_tempo( t ); } + +void gme_mute_voice( Music_Emu* me, int index, int mute ) { me->mute_voice( index, mute != 0 ); } + +void gme_mute_voices( Music_Emu* me, int mask ) { me->mute_voices( mask ); } + +gme_equalizer_t gme_equalizer( Music_Emu const* me ) { return me->equalizer(); } + +void gme_set_equalizer( Music_Emu* me, gme_equalizer_t const* eq ) { me->set_equalizer( *eq ); }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/console/gme.h Thu Nov 30 19:54:33 2006 -0800 @@ -0,0 +1,201 @@ +/* Game music emulator library C interface (also usable from C++) */ + +/* Game_Music_Emu 0.5.1 */ +#ifndef GME_H +#define GME_H + +#ifdef __cplusplus + extern "C" { +#endif + +/* Error string returned by library functions, or NULL if no error (success) */ +typedef const char* gme_err_t; + +/* First parameter of most gme_ functions is a pointer to the Music_Emu */ +typedef struct Music_Emu Music_Emu; + + +/******** Game music types ********/ + +/* Emulator type constants for each supported file type */ +extern struct gme_type_t_ const 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_spc_type [], gme_vgm_type [], gme_vgz_type []; +typedef struct gme_type_t_ const* gme_type_t; + +/* Determine likely game music type based on first four bytes of file. Returns +string containing proper file suffix (i.e. "NSF", "SPC", etc.) or "" if +file header is not recognized. */ +const char* gme_identify_header( void const* header ); + +/* Pointer to array of music types, with NULL entry at end. Allows a player linked +to this library to support new music types without having to be updated. */ +gme_type_t const* gme_type_list(); + +/* Get corresponding music type for file extension passed in. Types points to +an array of gme_type_t elements with a NULL terminator at the end. */ +gme_type_t gme_identify_extension( const char* extension, gme_type_t const* types ); + +/* Determine file type based on file's extension or header (if extension isn't recognized) */ +gme_err_t gme_identify_file( const char* path, gme_type_t const* types, gme_type_t* type_out ); + +/* gme_type_t is a pointer to this structure. For example, gme_nsf_type->system is +"Nintendo NES" and gme_nsf_type->new_emu() is equilvant to new Nsf_Emu (in C++). */ +struct gme_type_t_ +{ + const char* system; /* name of system this music file type is generally for */ + int track_count; /* non-zero for formats with a fixed number of tracks */ + Music_Emu* (*new_emu)(); /* Create new emulator for this type (useful in C++ only) */ + Music_Emu* (*new_info)(); /* Create new info reader for this type */ + + /* internal */ + const char* extension_; + int flags_; +}; + + +/******** Emulator creation/cleanup ********/ + +/* Create new emulator and set sample rate. Returns NULL if out of memory. */ +Music_Emu* gme_new_emu( gme_type_t, long sample_rate ); + +/* Create new music file info reader. Same as gme_new_emu() except it does not +support playback functions. It *does* support an m3u playlist. */ +Music_Emu* gme_new_info( gme_type_t ); + +/* Error returned if file is wrong type */ +extern const char gme_wrong_file_type []; + +/* Type of this emulator */ +gme_type_t gme_type( Music_Emu const* ); + +/* Finish using emulator and free memory */ +void gme_delete( Music_Emu* ); + + +/******** File loading ********/ + +/* Load music file */ +gme_err_t gme_load_file( Music_Emu*, const char* path ); + +/* Load music file from memory. Makes a copy of data passed. */ +gme_err_t gme_load_data( Music_Emu*, const void* data, long size ); + +/* Load music file using custom data reader function that will be called to +read file data. Most emulators load the entire file in one call. */ +typedef gme_err_t (*gme_reader_t)( void* your_data, void* out, long count ); +gme_err_t gme_load_custom( Music_Emu*, gme_reader_t, long file_size, void* your_data ); + +/* Load m3u playlist file (must be done after loading file) */ +gme_err_t gme_load_m3u( Music_Emu*, const char* path ); + +/* Load m3u playlist file from memory (must be done after loading file) */ +gme_err_t gme_load_m3u_data( Music_Emu*, const void* data, long size ); + +/* Clears any loaded m3u playlist and any internal playlist that the music format +supports (NSFE for example). */ +void gme_clear_playlist( Music_Emu* ); + +/* Most recent warning string, or NULL if none. Clears current warning after returning. */ +const char* gme_warning( Music_Emu* ); + + +/******** Track information ********/ + +/* Number of tracks in file/playlist */ +int gme_track_count( Music_Emu const* ); + +/* Get information for a particular track (length, name, author, etc.) */ +typedef struct track_info_t track_info_t; +gme_err_t gme_track_info( Music_Emu const*, track_info_t* out, int track ); + +enum { gme_max_field = 255 }; +struct track_info_t +{ + long track_count; + + /* times in milliseconds; -1 if unknown */ + long length; + long intro_length; + long loop_length; + + /* empty string if not available */ + char system [256]; + char game [256]; + char song [256]; + char author [256]; + char copyright [256]; + char comment [256]; + char dumper [256]; +}; + + +/******** Basic playback ********/ + +/* Start a track, where 0 is the first track. Also clears warning string. */ +gme_err_t gme_start_track( Music_Emu*, int index ); + +/* 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 gme_set_fade( Music_Emu*, long start_msec ); + +/* Generate 'count' samples info 'buf'. Output is in stereo. Any emulation +errors set warning string, and major errors also end track. */ +gme_err_t gme_play( Music_Emu*, long sample_count, short* out ); + +/* True if a track has reached its end */ +int gme_track_ended( Music_Emu const* ); + +/* Number of milliseconds (1000 msec = 1 second) played since beginning of track */ +long gme_tell( Music_Emu const* ); + +/* Seek to new time in track. Seeking backwards or far forward can take a while. */ +gme_err_t gme_seek( Music_Emu*, long msec ); + + +/******** Advanced playback ********/ + +/* Adjust stereo echo depth, where 0.0 = off and 1.0 = maximum. Has no effect for +GYM, SPC, and Sega Genesis VGM music */ +void gme_set_stereo_depth( Music_Emu*, double depth ); + +/* Disable automatic end-of-track detection and skipping of silence at beginning +if ignore is true */ +void gme_ignore_silence( Music_Emu*, int ignore ); + +/* 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 gme_set_tempo( Music_Emu*, double tempo ); + +/* Number of voices used by currently loaded file */ +int gme_voice_count( Music_Emu const* ); + +/* Names of voices */ +const char** gme_voice_names( Music_Emu const* ); + +/* Mute/unmute voice i, where voice 0 is first voice */ +void gme_mute_voice( Music_Emu*, int index, int mute ); + +/* Set muting state of all voices at once using a bit mask, where -1 mutes all +voices, 0 unmutes them all, 0x01 mutes just the first voice, etc. */ +void gme_mute_voices( Music_Emu*, int muting_mask ); + +/* Frequency equalizer parameters (see gme.txt) */ +typedef struct gme_equalizer_t +{ + double treble; /* -50.0 = muffled, 0 = flat, +5.0 = extra-crisp */ + long bass; /* 1 = full bass, 90 = average, 16000 = almost no bass */ +} gme_equalizer_t; + +/* Get current frequency equalizater parameters */ +gme_equalizer_t gme_equalizer( Music_Emu const* ); + +/* Change frequency equalizer parameters */ +void gme_set_equalizer( Music_Emu*, gme_equalizer_t const* eq ); + + +#ifdef __cplusplus + } +#endif + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/console/gme_design.txt Thu Nov 30 19:54:33 2006 -0800 @@ -0,0 +1,171 @@ +Game_Music_Emu 0.5.1 Design +--------------------------- + +Architecture +------------ +Gme_File and Music_Emu provide a common interface to the emulators. The +virtual functions are protected rather than public to allow pre- and +post-processing of arguments and data in one place. This allows the +emulator classes to assume that everything is set up properly when +starting a track and playing samples. + +Since silence checking and fading are relatively complex, basic file +loading and track information are handled in the base class Gme_File. My +orignal intent was to use Gme_File as the common base class for full +emulators and track information-only readers, but implementing the C +interface was much simpler if both derived from Music_Emu. User C++ code +can still benefit from static checking by using Gme_File where only +track information will be accessed. + +Each emulator generally has three components: main emulator, CPU +emulator, and sound chip emulator(s). Each component has minimial +coupling, so use in a full emulator or standalone is fairly easy. This +modularity really helps reduce complexity. Blip_Buffer helps a lot with +simplifying the APU interfaces and implementation. + +The "classic" emulators derive from Classic_Emu, which handles +Blip_Buffer filling and multiple channels. It uses Multi_Buffer for +output, allowing you to derive a custom buffer that could output each +voice to a separate sound channel and do different processing on each. +At some point I'm going to implement a better Effects_Buffer that allows +individual control of every channel. + +In implementing the C interface, I wanted a way to specify an emulator +type that didn't require linking in all the emulators. This rules out +any functions which automatically select it based on a file's extension +or header contents. For each emulator type there is a global object with +pointers to functions to create the emulator or a track information +reader. The emulator type is thus a pointer to this, which conveniently +allows for a NULL value. The user referencing this emulator type object +is what ultimately links the emulator in (unless new Foo_Emu is used in +C++, of course). + + +Interface conventions +---------------------- +If a function retains a pointer to or replaces the value of an object +passed, it takes a pointer so that it will be clear in the caller's +source code that care is required. + +Multi-word names have an underscore '_' separator between individual +words. + +Functions are named with lowercase words. Functions which perform an +action with side-effects are named with a verb phrase (i.e. load, move, +run). Functions which return the value of a piece of state are named +using a noun phrase (i.e. loaded, moved, running). + +Classes are named with capitalized words. Only the first letter of an +acronym is capitalized. Class names are nouns, sometimes suggestive of +what they do (i.e. File_Scanner). + +Structure, enumeration, and typedefs to these and built-in types are +named using lowercase words with a _t suffix. + +Macros are named with all-uppercase words. + +Internal names which can't be hidden due to technical reasons have an +underscore '_' suffix. + + +Managing Complexity +------------------- +Complexity has been a factor in most library decisions. Many features +have been passed by due to the complexity they would add. Once +complexity goes past a certain level, it mentally grasping the library +in its entirety, at which point more defects will occur and be hard to +find. + +I chose 16-bit signed samples because it seems to be the most common +format. Supporting multiple formats would add too much complexity to be +worth it. Other formats can be obtained via conversion. + +I've kept interfaces fairly lean, leaving many possible features +untapped but easy to add if necessary. For example the classic emulators +could have volume and frequency equalization adjusted separately for +each channel, since they each have an associated Blip_Synth. + +Source files of 400 lines or less seem to be the best size to limit +complexity. In a few cases there is no reasonable way to split longer +files, or there is benefit from having the source together in one file. + + +Preventing Bugs +--------------- +I've done many things to reduce the opportunity for defects. A general +principle is to write code so that defects will be as visible as +possible. I've used several techniques to achieve this. + +I put assertions at key points where defects seem likely or where +corruption due to a defect is likely to be visible. I've also put +assertions where violations of the interface are likely. In emulators +where I am unsure of exact hardware operation in a particular case, I +output a debug-only message noting that this has occurred; many times I +haven't implemented a hardware feature because nothing uses it. I've +made code brittle where there is no clear reason flexibility; code +written to handle every possibility sacrifices quality and reliability +to handle vaguely defined situations. + + +Flexibility through indirection +------------------------------- +I've tried to allow the most flexibility of modules by using indirection +to allow extension by the user. This keeps each module simpler and more +focused on its unique task. + +The classic emulators use Multi_Buffer, which potentially allows a +separate Blip_Buffer for each channel. This keeps emulators free of +typical code to allow output in mono, stereo, panning, etc. + +All emulators use a reader object to access file data, allowing it to be +stored in a regular file, compressed archive, memory, or generated +on-the-fly. Again, the library can be kept free of the particulars of +file access and changes required to support new formats. + + +Emulators in general +-------------------- +When I wrote the first NES sound emulator, I stored most of the state in +an emulator-specific format, with significant redundancy. In the +register write function I decoded everything into named variables. I +became tired of the verbosity and wanted to more closely model the +hardware, so I moved to a style of storing the last written value to +each register, along with as little other state as possible, mostly the +internal hardware registers. While this involves slightly more +recalculation, in most cases the emulation code is of comparable size. +It also makes state save/restore (for use in a full emulator) much +simpler. Finally, it makes debugging easier since the hardware registers +used in emulation are obvious. + + +CPU Cores +--------- +I've spent lots of time coming up with techniques to optimize the CPU +cores. Some of the most important: execute multiple instructions during +an emulation call, keep state in local variables to allow register +assignment, optimize state representation for most common instructions, +defer status flag calculation until actually needed, read program code +directly without a call to the memory read function, always pre-fetch +the operand byte before decoding instruction, and emulate instructions +using common blocks of code. + +I've successfully used Nes_Cpu in a fairly complete NES emulator, and +I'd like to make all the CPU emulators suitable for use in emulators. It +seems a waste for them to be used only for the small amount of emulation +necessary for game music files. + +I debugged the CPU cores by writing a test shell that ran them in +parallel with other CPU cores and compared all memory accesses and +processor states at each step. This provided good value at little cost. + +The CPU mapping page size is adjustable to allow the best tradeoff +between memory/cache usage and handler granularity. The interface allows +code to be somewhat independent of the page size. + +I optimize program memory accesses to to direct reads rather than calls +to the memory read function. My assumption is that it would be difficult +to get useful code out of hardware I/O addresses, so no software will +intentionally execute out of I/O space. Since the page size can be +changed easily, most program memory mapping schemes can be accommodated. +This greatly reduces memory access function calls. +
--- a/src/console/gme_notes.txt Wed Nov 29 14:42:11 2006 -0800 +++ b/src/console/gme_notes.txt Thu Nov 30 19:54:33 2006 -0800 @@ -1,43 +1,341 @@ -Game_Music_Emu 0.3.0 Notes --------------------------- -Author : Shay Green <hotpop.com@blargg> -Website: http://www.slack.net/~ant/ +Game_Music_Emu 0.5.1 +-------------------- +Author : Shay Green <gblargg@gmail.com> +Website: http://www.slack.net/~ant/libs/ Forum : http://groups.google.com/group/blargg-sound-libs +License: GNU Lesser General Public License (LGPL) + +Contents +-------- +* Overview +* C and C++ interfaces +* Function reference +* Error handling +* Emulator types +* M3U playlist support +* Information fields +* Track length +* Loading file data +* Sound parameters +* VGM/GYM YM2413 & YM2612 FM sound +* Modular construction +* Obscure features +* Solving problems +* Deprecated features +* Thanks Overview -------- -This library is composed of several independent game music emulators -derived from the common Music_Emu interface. Each emulator can load a -game music file and play from any track. To play a game music file, do -the following: +This library contains several game music file emulators with a common +interface. Each can load a game music file and play from any track. To +play a track from a game music file, do the following: + +* Determine file's type (i.e. gme_nsf_emu, gme_spc_emu, etc.) +* Create appropriate emulator and set sample rate with gme_new_emu() +* Load file into emulator with gme_load() +* Start a track with gme_start_track() +* Generate samples as needed with gme_play() +* Play samples through speaker using your operating system +* Delete emulator when done with gme_delete() + +Your code must arrange for the generated samples to be played through +the computer's speaker using whatever method your operating system +requires. + + +C and C++ interfaces +-------------------- +While the library is written in C++, a fairly complete C interface is +provided in gme.h. This C interface will be referred to throughout this +documentation unless a feature is only available in the full C++ +interface. All C interface functions and other names have the gme_ +prefix, so you can recognize a C++-only feature by the lack of gme_ in +the names used (contact me if you'd like a feature added to the C +interface). If you're building a shared library, I highly recommend +sticking to the C interface only, because it will be much more stable +between releases of the library than the C++ interface. Finally, the C +and C++ interfaces can be freely mixed without problems. Compare +demo/basics.c with demo/cpp_basics.cpp to see how the C and C++ +interfaces translate between each other. + + +Function reference +------------------ +Read the following header files for a complete reference to functions +and features. The second group of header files can only be used in C++. + +blargg_config.h Library configuration +gme.h C interface + +Gme_File.h File loading and track information +Music_Emu.h Track playback and adjustments +Data_Reader.h Custom data readers +Effects_Buffer.h Sound buffer with adjustable stereo echo and panning +M3u_Playlist.h M3U playlist support +Gbs_Emu.h GBS equalizer settings +Nsf_Emu.h NSF equalizer settings +Spc_Emu.h SPC surround disable +Vgm_Emu.h VGM oversampling disable and custom buffer query + + +Error handling +-------------- +Functions which can fail have a return type of gme_err_t, which is a +pointer to an error string (const char*). If a function is successful it +returns NULL. Errors that you can easily avoid are checked with debug +assertions; gme_err_t return values are only used for genuine run-time +errors that can't be easily predicted in advance (out of memory, I/O +errors, incompatible file data). Your code should check all error +values. -- Determine file's type -- Create appropriate emulator -- Set sample rate -- Load file into emulator -- Start desired track -- When samples are needed, call play() -- When done, delete emulator +To improve usability for C programmers, C++ programmers unfamiliar with +exceptions, and compatibility with older C++ compilers, the library does +*not* throw any C++ exceptions and uses malloc() instead of the standard +operator new. This means that you *must* check for NULL when creating a +library object with the new operator. + +When loading a music file in the wrong emulator or trying to load a +non-music file, gme_wrong_file_type is returned. You can check for this +error in C++ like this: + + gme_err_t err = gme_load_file( music_emu, path, 0 ); + if ( err == gme_wrong_file_type ) + ... + +To check for minor problems, call gme_warning() to get a string +describing the last warning. Your player should allow the user some way +of knowing when this is the case, since these minor errors could affect +playback. Without this information the user can't solve problems as +well. When playing a track, gme_warning() returns minor playback-related +problems (major playback problems end the track immediately and set the +warning string). + + +Emulator types +-------------- +The library includes several game music emulators that each support a +different file type. Each is identified by a gme_type_t constant defined +in gme.h, for example gme_nsf_emu is for the NSF emulator. Ultimately +you will use one of these to select which emulator to use for a file. -See Music_Emu.h for reference. +There are two basic ways to identify a game music file's type: look at +its file extension, or read the header data. The library includes +functions to help with both methods. The first is preferable because it +is fast and the most common way to identify files. Sometimes the +extension is lost or wrong, so the header must be read. + +Use gme_identify_extension() to find the correct game music type based +on a filename. It takes an array of gme_type_t elements, allowing you to +customize what game music types you accept. If you want to accept all +types supported by the library, use gme_type_list(), otherwise pass an +array of the types you support. For example, to support just NSF and +GBS, and avoid having to compile/link the other emulator files, do this +(be sure to end your array with 0): + + static gme_type_t types [] = { gme_nsf_type, gme_gbs_type, 0 }; + file_type = gme_identify_extension( path, types ); + +To identify a file based on its extension and header contents, use +gme_identify_file(). If you read the header data yourself, use +gme_identify_header(). + + +M3U playlist support +-------------------- +The library supports playlists in the .m3u format with gme_load_m3u() to +give track names and times to multi-song formats: AY, GBS, HES, KSS, +NSF, NSFE, and SAP. Some aspects of the file format itself is not +well-defined so some .m3u files won't work properly (particularly those +provided with KSS files). Only .m3u files referencing a single file are +supported; your code must handle .m3u files covering more than one game +music file, though it can use the built-in .m3u parsing provided by the +library. -Information Fields +Information fields ------------------ -Game music files include text fields with information about the game and -track. These are stored in the file's header or in an embedded block. -Text fields in most game music formats do *not* have a nul terminator if -the string completely fills the field. The simplest way to handle this -is to copy the string out and manually add a nul terminator at the end. +Support is provided for the various text fields and length information +in a file with gme_track_info(). If you just need track information for +a file (for example, building a playlist), use gme_new_info() in place +of gme_new_emu(), load the file normally, then you can access the track +count and info, but nothing else. + + M3U VGM GYM SPC SAP NSFE NSF AY GBS HES KSS + ------------------------------------------------------- +Track Count | * * * * * * * * * + | +System | * * * * * * * * * * + | +Game | * * * * * * * + | +Song | * * * * * * * + | +Author | * * * * * * * * + | +Copyright | * * * * * * * * + | +Comment | * * * * + | +Dumper | * * * * + | +Length | * * * * * * + | +Intro Length| * * * + | +Loop Length | * * * -This library is currently focused only on actual emulation, so it -doesn't provide a common interface to the different schemes each game -music file format uses. Refer to the file's official specification for -further information. +As listed above, the HES and KSS file formats don't include a track +count, and tracks are often scattered over the 0-255 range, so an m3u +playlist for these is a must. + +Unavailable text fields are set to an empty string and times to -1. Your +code should be prepared for any combination of available and unavailable +fields, as a particular music file might not use all of the supported +fields listed above. + +Currently text fields are truncated to 255 characters. Obscure fields of +some formats are not currently decoded; contact me if you want one +added. + + +Track length +------------ +The library leaves it up to you as to when to stop playing a track. You +can ask for available length information and then tell the library what +time it should start fading the track with gme_set_fade(). By default it +also continually checks for 6 or more seconds of silence to mark the end +of a track. Here is a reasonable algorithm you can use to decide how +long to play a track: + +* If the track length is > 0, use it +* If the loop length > 0, play for intro + loop * 2 +* Otherwise, default to 2.5 minutes (150000 msec) + +If you want to play a track longer than normal, be sure the loop length +isn't zero. See Music_Player.cpp around line 145 for example code. + +By default, the library skips silence at the beginning of a track. It +also continually checks for the end of a non-looping track by watching +for 6 seconds of unbroken silence. When doing this is scans *ahead* by +several seconds so it can report the end of the track after only one +second of silence has actually played. This feature can be disabled with +gme_ignore_silence(). -Modular Construction +Loading file data +----------------- +The library allows file data to be loaded in many different ways. All +load functions return an error which you should check (not shown here +for clarity). The most basic is always available, simple calling +load_file() with the path of a file: + + gme_load_file( music_emu, file_path ); + +* From a block of memory: + + gme_load_data( music_emu, pointer, size ); + +* Have library call your function to read data: + + gme_err_t my_read( void* my_data, void* out, long count ) + { + // code that reads 'count' bytes into 'out' buffer + // and return 0 if no error + } + + gme_load_custom( music_emu, my_read, file_size, my_data ); + +* If you must load the file data into memory yourself, you can have the +library use your data directly *without* making a copy. If you do this, +you must not free the data until you're done playing the file. + + music_emu->load_mem( pointer, size ); + +* If you've already read the first bytes of a file (perhaps to determine +the file type) and want to avoid seeking back to the beginning for +performance reasons, use Remaining_Reader: + + Std_File_Reader in; + in.open( file_path ); + + char header [4]; + in.read( &header, sizeof header ); + ... + + Remaining_Reader rem( &header, sizeof header, &in ); + music_emu->load( rem ); + +If you merely need access to a file's header after loading, use the +emulator-specific header() functions, after casting the Music_Emu +pointer to the specific emulator's type. This example examines the +chip_flags field of the header if it's an NSF file: + + if ( music_emu->type() == gme_nsf_type ) + { + Nsf_Emu* nsf_emu = (Nsf_Emu*) music_emu; + if ( nsf_emu->header().chip_flags & 0x01 ) + ... + } + +Contact me if you want more information about loading files. + + +Sound parameters +---------------- +All emulators support an arbitrary output sampling rate. A rate of 44100 +Hz should work well on most systems. Since band-limited synthesis is +used, a sampling rate above 48000 Hz is not necessary and will actually +reduce sound quality and performance. + +All emulators also support adjustable gain, mainly for the purpose of +getting consistent volume between different music formats and avoiding +excessive modulation. The gain can only be set *before* setting the +emulator's sampling rate, so it's not useful as a general volume +control. The default gains of emulators are set so that they give +generally similar volumes, though some soundtracks are significantly +louder or quieter than normal. + +Some emulators support adjustable treble and bass frequency equalization +(AY, GBS, HES, KSS, NSF, NSFE, SAP, VGM) using set_equalizer(). +Parameters are specified using gme_equalizer_t eq = { treble_dB, +bass_freq }. Treble_dB sets the treble level (in dB), where 0.0 dB gives +normal treble; -200.0 dB is quite muffled, and 5.0 dB emphasizes treble +for an extra crisp sound. Bass_freq sets the frequency where bass +response starts to diminish; 15 Hz is normal, 0 Hz gives maximum bass, +and 15000 Hz removes all bass. For example, the following makes the +sound extra-crisp but lacking bass: + + gme_equalizer_t eq = { 5.0, 1000 }; + gme_set_equalizer( music_emu, &eq ); + +Each emulator's equalization defaults to approximate the particular +console's sound quality; this default can be determined by calling +equalizer() just after creating the emulator. The Music_Emu::tv_eq +profile gives sound as if coming from a TV speaker, and some emulators +include other profiles for different versions of the system. For +example, to use Famicom sound equalization with the NSF emulator, do the +following: + + music_emu->set_equalizer( Nsf_Emu::famicom_eq ); + + +VGM/GYM YM2413 & YM2612 FM sound +-------------------------------- +The library plays Sega Genesis/Mega Drive music using a YM2612 FM sound +chip emulator based on the Gens project. Because this has some +inaccuracies, other YM2612 emulators can be used in its place by +re-implementing the interface in YM2612_Emu.h. Available on my website +is a modified version of MAME's YM2612 emulator, which sounds better in +some ways and whose author is still making improvements. + +VGM music files using the YM2413 FM sound chip are also supported, but a +YM2413 emulator isn't included with the library due to technical +reasons. I have put one of the available YM2413 emulators on my website +that can be used directly. + + +Modular construction -------------------- The library is made of many fairly independent modules. If you're using only one music file emulator, you can eliminate many of the library @@ -46,74 +344,86 @@ me to put together a smaller version for a particular use, as this only takes me a few minutes to do. -If you want to use one of the individual sound chip emulators in your -console emulator, first check the libraries page on my website since I -have released several of them as standalone libraries with included -documentation and examples on their use. +If you want to use one of the individual sound chip emulators (or CPU +cores) in your own console emulator, first check the libraries page on +my website since I have released several of them as stand alone +libraries with included documentation and examples on their use. If you +don't find it as a standalone library, contact me and I'll consider +separating it. The "classic" sound chips use my Blip_Buffer library, which greatly simplifies their implementation and efficiently handles band-limited -synthesis. It is also available as a standalone library with +synthesis. It is also available as a stand alone library with documentation and many examples. -Sound Parameters +Obscure features ---------------- -All emulators support adjustable output sampling rate, set with -Music_Emu::set_sample_rate(). A rate of 44100 should work well on most -systems. Since band-limited synthesis is used, a sampling rate above -48000 Hz is not necessary. +The library's flexibility allows many possibilities. Contact me if you +want help implementing ideas or removing limitations. + +* Uses no global/static variables, allowing multiple instances of any +emulator. This is useful in a music player if you want to allow +simultaneous recording or scanning of other tracks while one is already +playing. This will also be useful if your platform disallows global +data. -Some emulators support adjustable treble and bass frequency equalization -(NSF, GBS, VGM) using Music_Emu::set_equalizer(). Parameters are -specified using Music_Emu::equalizer_t eq = { treble_dB, bass_freq }. -Treble_dB sets the treble level (in dB), where 0.0 dB gives normal -treble; -200.0 dB is quite muffled, and 5.0 dB emphasizes treble for an -extra crisp sound. Bass_freq sets the frequency where bass response -starts to diminish; 15 Hz is normal, 0 Hz gives maximum bass, and 15000 -Hz removes all bass. For example, the following makes the sound -extra-crisp but lacking bass: +* Emulators that support a custom sound buffer can have *every* voice +routed to a different Blip_Buffer, allowing custom processing on each +voice. For example you could record a Game Boy track as a 4-channel +sound file. - Music_Emu::equalizer_t eq = { 5.0, 1000 }; - music_emu->set_equalizer( eq ); - -Each emulator's equalization defaults to a profile that approximates its -particular console's sound quality; this default can be determined by -calling Music_Emu::equalizer() just after creating the emulator. Some -emulators include other profiles for different versions of the system. -The Music_Emu::tv_eq profile gives sound as if coming from a TV speaker. -For example, to use Famicom sound equalization with the NSF emulator, do -the following: - - nsf_emu->set_equalizer( Nsf_Emu::famicom_eq ); +* Defining BLIP_BUFFER_FAST uses lower quality, less-multiply-intensive +synthesis on "classic" emulators, which might help on some really old +processors. This significantly lowers sound quality and prevents treble +equalization. Try this if your platform's processor isn't fast enough +for normal quality. Even on my ten-year-old 400 MHz Mac, this reduces +processor usage at most by about 0.6% (from 4% to 3.4%), hardly worth +the quality loss. -VGM/GYM YM2413 & YM2612 FM Sound --------------------------------- -The library plays Sega Genesis/Mega Drive music using a YM2612 FM sound -chip emulator based on Gens. Because this has some inaccuracies, other -YM2612 emulators can be used in its place by reimplementing the -interface in YM2612_Emu.h. Available on my website is a modified version -of MAME's YM2612 emulator, which sounds better in some ways and whose -author is still making improvements. +Solving problems +---------------- +If you're having problems, try the following: + +* If you're getting garbled sound, try this simple siren generator in +place of your call to play(). This will quickly tell whether the problem +is in the library or in your code. -VGM music files using the YM2413 FM sound chip are also supported, but a -YM2413 emulator isn't included. Similar to above, I have put one of the -available YM2413 emulators on my website that can be used directly. + static void play_siren( long count, short* out ) + { + static double a, a2; + while ( count-- ) + *out++ = 0x2000 * sin( a += .1 + .05*sin( a2+=.00005 ) ); + } + +* Enable debugging support in your environment. This enables assertions +and other run-time checks. + +* Turn the compiler's optimizer is off. Sometimes an optimizer generates +bad code. + +* If multiple threads are being used, ensure that only one at a time is +accessing a given set of objects from the library. This library is not +in general thread-safe, though independent objects can be used in +separate threads. + +* If all else fails, see if the demos work. -Misc ----- -Some emulators have constructor parameters which can be specified when -creating the object. For example, this creates a Vgm_Emu with -oversampling off and a tempo of 83%: +Deprecated features +------------------- +The following functions and other features have been deprecated and will +be removed in a future release of the library. Alternatives to the +deprecated features are listed to the right. - Vgm_Emu* emu = new Vgm_Emu( false, 0.83 ); - -For a full example of using Game_Music_Emu see the source code for Game -Music Box, a full-featured game music player for Mac OS: - - http://www.slack.net/~ant/game-music-box/dev.html +Music_Emu::error_count() warning() +load( header, reader ) see "Loading file data" above +Spc_Emu::trailer() track_info() +Spc_Emu::trailer_size() +Gym_Emu::track_length() track_info() +Vgm_Emu::gd3_data() track_info() +Nsfe_Emu::disable_playlist() clear_playlist() Thanks @@ -122,60 +432,7 @@ feedback, for maintaining the Foobar2000 plugin foo_gep based on it, and for original work on openspc++ that was used when developing Spc_Emu. Brad Martin's excellent OpenSPC SNES DSP emulator worked well from the -start. Also thanks to Richard Bannister, Mahendra Tallur, Shazz, and the -Audacious team for testing and using the library in their game music +start. Also thanks to Richard Bannister, Mahendra Tallur, Shazz, +nenolod, theHobbit, Johan Samuelsson, and nes6502 for testing, using, +and giving feedback for the library in their respective game music players. - - -Solving Problems ----------------- -If you're having problems, try the following: - -- Enable debugging support in your environment. This enables assertions -and other run-time checks. - -- Turn the compiler's optimizer is off. Sometimes an optimizer generates -bad code. - -- If multiple threads are being used, ensure that only one at a time is -accessing a given set of objects from the library. This library is not -in general thread-safe, though independent objects can be used in -separate threads. - -- If all else fails, see if the demos work. - - -Error handling --------------- -Functions which can fail have a return type of blargg_err_t, which is a -pointer to an error string (const char*). If the function is successful -it returns blargg_success (NULL), otherwise it returns a pointer to an -error string. Errors which the caller can easily detect are only checked -with debug assertions; blargg_err_t returns values are only used for -genuine run-time errors that can't be easily predicted in advance (out -of memory, I/O errors, incompatible file data). - -To allow compatibility with older C++ compilers, no exceptions are -thrown by any of the modules and code is generally exception-safe. Any -exceptions thrown by the standard library or caller-supplied functions -are allowed to propagate normally. - - -Configuration -------------- -The header "blargg_common.h" is used to establish a common environment, -and is #included at the beginning of all library headers and sources. It -attempts to automatically determine the features of the environment, but -might need help. Refer to "blargg_common.h" for descriptions of -features. - -If defined HAVE_CONFIG_H in the compiler command-line, the user-provided -"config.h" is included at the beginning of each library header file, -allowing configuration options for the library to be set. I have -attempted to design the library so that configuration can be done -*without* modifying any of the library sources and header files. This -makes it easy to upgrade to a new version without losing any -customizations to its configuration. - -Post to the forum if you have problems or suggestions. -
--- a/src/console/gme_readme.txt Wed Nov 29 14:42:11 2006 -0800 +++ b/src/console/gme_readme.txt Thu Nov 30 19:54:33 2006 -0800 @@ -1,19 +1,38 @@ -Game_Music_Emu 0.3.0: Game Music Emulators +Game_Music_Emu 0.5.1: Game Music Emulators ------------------------------------------ -Game_Music_Emu is a collection of portable video game music emulators. Its -modular design allows elimination of any unneeded emulators and features. -Modules are included supporting the following file formats: +Game_Music_Emu is a collection of video game music file emulators that +support the following formats and systems: + +AY Sinclair Spectrum +GBS Nintendo Game Boy +GYM Sega Genesis/Mega Drive +HES NEC TurboGrafx-16/PC Engine +KSS MSX Home Computer/other Z80 systems (doesn't support FM sound) +NSF/NSFE Nintendo NES/Famicom (with VRC 6, Namco 106, and FME-7 sound) +SAP Atari systems using POKEY sound chip +SPC Super Nintendo/Super Famicom +VGM/VGZ Sega Master System/Mark III, Sega Genesis/Mega Drive,BBC Micro -GBS Nintendo Game Boy -VGM/VGZ Sega Master System/Genesis/Mega Drive/Mark III/BBC Micro -GYM Sega Genesis -SPC Super Nintendo -NSF Nintendo NES (with VRC6, N106, and FME-7 sound) +Features: +* Every emulator works the same way +* High emphasis on being easy to use +* Several examples, including music player using SDL +* Includes C interface that supports the main features +* Portable code for use on any system with modern or older C++ compilers +* Adjustable output sample rate using quality band-limited resampling +* Uniform access to text information fields and track timing information +* End-of-track fading and automatic look ahead silence detection +* Treble/bass and stereo echo for AY/GBS/HES/KSS/NSF/NSFE/SAP/VGM +* Tempo can be adjusted and individual voices can be muted while playing +* Can read music data from file, memory, or custom reader function/class +* Access to track information without having to load into full emulator +* M3U track listing support for multi-track formats +* Modular design allows elimination of unneeded emulators/features -This library has been used in game music players for Win32, Linux x86-32/64, -Mac OS X, Mac OS Classic, MorphOS (Amiga), PlayStation Portable, and GP2X. +This library has been used in game music players for Windows, Linux, Mac +OS, MorphOS, Xbox, PlayStation Portable, GP2X, and Nintendo DS. -Author : Shay Green <hotpop.com@blargg> +Author : Shay Green <gblargg@gmail.com> Website: http://www.slack.net/~ant/ Forum : http://groups.google.com/group/blargg-sound-libs License: GNU Lesser General Public License (LGPL) @@ -21,53 +40,101 @@ Getting Started --------------- -Build a program consisting of demo/basics.cpp, demo/Wave_Writer.cpp, and all -source files in gme/ except Gzip_File.cpp. Be sure "test.nsf" is in the same -directory. Running the program should generate a WAVE sound file "out.wav" of -music. +Build a program consisting of demo/c_basics.c, demo/Wave_Writer.cpp, and +all source files in gme/. Be sure "test.nsf" is in the same directory. +Running the program should generate the recording "out.wav". -See notes.txt for more information, and respective header (.h) files for -reference. Post to the discussion forum for assistance. +To enable transparent support for gzipped files, see blargg_config.h. + +Read gme.txt for more information. Post to the discussion forum for +assistance. Files ----- -notes.txt General notes about the library -changes.txt Changes made since previous releases -design.txt Library design notes -LGPL.txt GNU Lesser General Public License +gme.txt General notes about the library +changes.txt Changes made since previous releases +design.txt Library design notes +license.txt GNU Lesser General Public License -test.nsf Test file for NSF emulator +test.nsf Test file for NSF emulator +test.m3u Test m3u playlist for playlist.c demo demo/ - basics.cpp Loads game music file and records to wave sound file - info_fields.cpp Reads information tags from files - multi_format.cpp Handles multiple game music types - custom_reader.cpp Loads music data from gzip file and memory block - stereo_effects.cpp Uses Effects_Buffer to add stereo echo - - simple_player.cpp Uses Music_Player to make simple player - Music_Player.cpp Simple game music player module using SDL sound - Music_Player.h - - Wave_Writer.h WAVE sound file writer used for demo output + basics.c Records NSF file to wave sound file + cpp_basics.cpp C++ version of basics.c + features.c Demonstrates the main library features + Wave_Writer.h WAVE sound file writer used for demo output Wave_Writer.cpp +player/ Player using the SDL multimedia library + player.cpp Simple music player with waveform display + Music_Player.cpp Stand alone player for background music + Music_Player.h + Audio_Scope.cpp Audio waveform scope + Audio_Scope.h + gme/ - Effects_Buffer.h Sound buffer with adjustable stereo echo and panning + blargg_config.h Library configuration (modify this file as needed) + + gme.h C interface + gme.cpp + gme_type_list.cpp gme_type_list() support + + Gme_File.h File loading and track information + Music_Emu.h Track playback and adjustments + Data_Reader.h Custom data readers + + Effects_Buffer.h Sound buffer with stereo echo and panning Effects_Buffer.cpp + + M3u_Playlist.h M3U playlist support + M3u_Playlist.cpp + + Ay_Emu.h Sinclair Spectrum AY emulator + Ay_Emu.cpp + Ay_Apu.cpp + Ay_Apu.h + Ay_Cpu.cpp + Ay_Cpu.h - Gzip_File.h Gzip reader for transparent access to gzipped files - Gzip_File.cpp + Gbs_Emu.h Nintendo Game Boy GBS emulator + Gbs_Emu.cpp + Gb_Apu.cpp + Gb_Apu.h + Gb_Cpu.cpp + Gb_Cpu.h + gb_cpu_io.h + Gb_Oscs.cpp + Gb_Oscs.h - Music_Emu.h Game music emulator interface + Hes_Emu.h TurboGrafx-16/PC Engine HES emulator + Hes_Apu.cpp + Hes_Apu.h + Hes_Cpu.cpp + Hes_Cpu.h + hes_cpu_io.h + Hes_Emu.cpp + + Kss_Emu.h MSX Home Computer/other Z80 systems KSS emulator + Kss_Emu.cpp + Kss_Cpu.cpp + Kss_Cpu.h + Kss_Scc_Apu.cpp + Kss_Scc_Apu.h + Ay_Apu.h + Ay_Apu.cpp + Sms_Apu.h + Sms_Apu.cpp + Sms_Oscs.h - Nsf_Emu.h Nintendo NES NSF emulator + Nsf_Emu.h Nintendo NES NSF/NSFE emulator Nsf_Emu.cpp Nes_Apu.cpp Nes_Apu.h Nes_Cpu.cpp Nes_Cpu.h + nes_cpu_io.h Nes_Oscs.cpp Nes_Oscs.h Nes_Fme7_Apu.cpp @@ -76,17 +143,10 @@ Nes_Namco_Apu.h Nes_Vrc6_Apu.cpp Nes_Vrc6_Apu.h + Nsfe_Emu.h NSFE support + Nsfe_Emu.cpp - Gbs_Emu.h Nintendo Game Boy GBS emulator - Gbs_Emu.cpp - Gb_Apu.cpp - Gb_Apu.h - Gb_Cpu.cpp - Gb_Cpu.h - Gb_Oscs.cpp - Gb_Oscs.h - - Spc_Emu.h Super Nintendo SPC emulator + Spc_Emu.h Super Nintendo SPC emulator Spc_Emu.cpp Snes_Spc.cpp Snes_Spc.h @@ -97,15 +157,23 @@ Fir_Resampler.cpp Fir_Resampler.h - Gym_Emu.h Sega Genesis GYM emulator - Gym_Emu.cpp - Vgm_Emu.h Sega VGM emulator + Sap_Emu.h Atari SAP emulator + Sap_Emu.cpp + Sap_Apu.cpp + Sap_Apu.h + Sap_Cpu.cpp + Sap_Cpu.h + sap_cpu_io.h + + Vgm_Emu.h Sega VGM emulator Vgm_Emu_Impl.cpp Vgm_Emu_Impl.h Vgm_Emu.cpp Ym2413_Emu.cpp Ym2413_Emu.h - Sms_Apu.cpp Common Sega emulator files + Gym_Emu.h Sega Genesis GYM emulator + Gym_Emu.cpp + Sms_Apu.cpp Common Sega emulator files Sms_Apu.h Sms_Oscs.h Ym2612_Emu.cpp @@ -115,18 +183,18 @@ Fir_Resampler.cpp Fir_Resampler.h - blargg_common.h Common files + blargg_common.h Common files needed by all emulators blargg_endian.h blargg_source.h Blip_Buffer.cpp Blip_Buffer.h + Gme_File.cpp Music_Emu.cpp Classic_Emu.h Classic_Emu.cpp Multi_Buffer.h Multi_Buffer.cpp - abstract_file.cpp - abstract_file.h + Data_Reader.cpp Legal @@ -134,3 +202,6 @@ Game_Music_Emu library copyright (C) 2003-2006 Shay Green. SNES SPC DSP emulator based on OpenSPC, copyright (C) 2002 Brad Martin. Sega Genesis YM2612 emulator copyright (C) 2002 Stephane Dallongeville. + +-- +Shay Green <gblargg@gmail.com>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/console/gme_type_list.cxx Thu Nov 30 19:54:33 2006 -0800 @@ -0,0 +1,38 @@ +// Game_Music_Emu 0.5.1. http://www.slack.net/~ant/ + +// separate file to avoid linking all emulators if this is not used + +#include "gme.h" + +/* Copyright (C) 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" + +gme_type_t const* gme_type_list() +{ + static gme_type_t const 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_spc_type, + gme_vgm_type, + gme_vgz_type, + 0 + }; + return gme_type_list_; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/console/hes_cpu_io.h Thu Nov 30 19:54:33 2006 -0800 @@ -0,0 +1,101 @@ + +#include "Hes_Emu.h" + +#include "blargg_source.h" + +int Hes_Emu::cpu_read( hes_addr_t addr ) +{ + check( addr <= 0xFFFF ); + int result = *cpu::get_code( addr ); + if ( mmr [addr >> page_shift] == 0xFF ) + result = cpu_read_( addr ); + return result; +} + +void Hes_Emu::cpu_write( hes_addr_t addr, int data ) +{ + check( addr <= 0xFFFF ); + byte* out = write_pages [addr >> page_shift]; + addr &= page_size - 1; + if ( out ) + out [addr] = data; + else if ( mmr [addr >> page_shift] == 0xFF ) + cpu_write_( addr, data ); +} + +inline byte const* Hes_Emu::cpu_set_mmr( int page, int bank ) +{ + write_pages [page] = 0; + if ( bank < 0x80 ) + return rom.at_addr( bank * (blargg_long) page_size ); + + byte* data = 0; + switch ( bank ) + { + case 0xF8: + data = cpu::ram; + break; + + case 0xF9: + case 0xFA: + case 0xFB: + data = &sgx [(bank - 0xF9) * page_size]; + break; + + default: + if ( bank != 0xFF ) + dprintf( "Unmapped bank $%02X\n", bank ); + return rom.unmapped(); + } + + write_pages [page] = data; + return data; +} + +#define CPU_READ_FAST( cpu, addr, time, out ) \ + CPU_READ_FAST_( STATIC_CAST(Hes_Emu*,cpu), addr, time, out ) + +#define CPU_READ_FAST_( cpu, addr, time, out ) \ +{\ + out = READ_PROG( addr );\ + if ( mmr [addr >> page_shift] == 0xFF )\ + {\ + FLUSH_TIME();\ + out = cpu->cpu_read_( addr );\ + CACHE_TIME();\ + }\ +} + +#define CPU_WRITE_FAST( cpu, addr, data, time ) \ + CPU_WRITE_FAST_( STATIC_CAST(Hes_Emu*,cpu), addr, data, time ) + +#define CPU_WRITE_FAST_( cpu, addr, data, time ) \ +{\ + byte* out = cpu->write_pages [addr >> page_shift];\ + addr &= page_size - 1;\ + if ( out )\ + {\ + out [addr] = data;\ + }\ + else if ( mmr [addr >> page_shift] == 0xFF )\ + {\ + FLUSH_TIME();\ + cpu->cpu_write_( addr, data );\ + CACHE_TIME();\ + }\ +} + +#define CPU_READ( cpu, addr, time ) \ + STATIC_CAST(Hes_Emu*,cpu)->cpu_read( addr ) + +#define CPU_WRITE( cpu, addr, data, time ) \ + STATIC_CAST(Hes_Emu*,cpu)->cpu_write( addr, data ) + +#define CPU_WRITE_VDP( cpu, addr, data, time ) \ + STATIC_CAST(Hes_Emu*,cpu)->cpu_write_vdp( addr, data ) + +#define CPU_SET_MMR( cpu, page, bank ) \ + STATIC_CAST(Hes_Emu*,cpu)->cpu_set_mmr( page, bank ) + +#define CPU_DONE( cpu, time, result_out ) \ + result_out = STATIC_CAST(Hes_Emu*,cpu)->cpu_done()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/console/nes_cpu_io.h Thu Nov 30 19:54:33 2006 -0800 @@ -0,0 +1,82 @@ + +#include "Nsf_Emu.h" + +#if !NSF_EMU_APU_ONLY + #include "Nes_Namco_Apu.h" +#endif + +#include "blargg_source.h" + +int Nsf_Emu::cpu_read( nes_addr_t addr ) +{ + int result; + + result = cpu::low_mem [addr & 0x7FF]; + if ( !(addr & 0xE000) ) + goto exit; + + result = *cpu::get_code( addr ); + if ( addr > 0x7FFF ) + goto exit; + + result = sram [addr & (sizeof sram - 1)]; + if ( addr > 0x5FFF ) + goto exit; + + if ( addr == Nes_Apu::status_addr ) + return apu.read_status( cpu::time() ); + + #if !NSF_EMU_APU_ONLY + if ( addr == Nes_Namco_Apu::data_reg_addr && namco ) + return namco->read_data(); + #endif + + result = addr >> 8; // simulate open bus + + if ( addr != 0x2002 ) + dprintf( "Read unmapped $%.4X\n", (unsigned) addr ); + +exit: + return result; +} + +void Nsf_Emu::cpu_write( nes_addr_t addr, int data ) +{ + { + nes_addr_t offset = addr ^ sram_addr; + if ( offset < sizeof sram ) + { + sram [offset] = data; + return; + } + } + { + int temp = addr & 0x7FF; + if ( !(addr & 0xE000) ) + { + cpu::low_mem [temp] = data; + return; + } + } + + if ( unsigned (addr - Nes_Apu::start_addr) <= Nes_Apu::end_addr - Nes_Apu::start_addr ) + { + apu.write_register( cpu::time(), addr, data ); + return; + } + + unsigned bank = addr - bank_select_addr; + if ( bank < bank_count ) + { + blargg_long addr = rom.mask_addr( data * (blargg_long) bank_size ); + if ( addr >= rom.size() ) + set_warning( "Invalid bank" ); + cpu::map_code( (bank + 8) * bank_size, bank_size, rom.at_addr( addr ) ); + return; + } + + cpu_write_misc( addr, data ); +} + +#define CPU_READ( cpu, addr, time ) STATIC_CAST(Nsf_Emu&,*cpu).cpu_read( addr ) +#define CPU_WRITE( cpu, addr, data, time ) STATIC_CAST(Nsf_Emu&,*cpu).cpu_write( addr, data )
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/console/readme.txt Thu Nov 30 19:54:33 2006 -0800 @@ -0,0 +1,85 @@ +Audacious Console Video Game Music Plugin +----------------------------------------- +This plugin plays music from video game consoles of the 1980s and early +1990s. It uses the Game_Music_Emu sound engine and supports the +following file formats: + +AY Sinclair Spectrum +GBS Nintendo Game Boy +GYM Sega Genesis/Mega Drive +HES NEC TurboGrafx-16/PC Engine +KSS MSX Home Computer/other Z80 systems (doesn't support FM sound) +NSF/NSFE Nintendo NES/Famicom (with VRC 6, Namco 106, and FME-7 sound) +SAP Atari systems using POKEY sound chip +SPC Super Nintendo/Super Famicom +VGM/VGZ Sega Master System/Mark III, Sega Genesis/Mega Drive,BBC Micro + +Text information tags and track length information is supported in all +formats, though some files might not make use of it. + +Most formats include the actual music player code, "ripped" by hand from +the original game to allow it to run on its own. This plugin then +emulates the original processor and sound chip(s) in order to reproduce +the original sound, glitches and all. Band-limited synthesis is used to +give crystal-clear sound quality. + +Contact: Shay Green <gblargg@gmail.com> + + +Notes +----- +* Errors and warnings are logged to stdout, which might help if you want +to figure out why a music file isn't playing properly. + +* The HES and KSS music formats lack proper track numbering, presenting +the game's music sometimes randomly scattered among the 256 possible +track numbers. Because of this, most HES and KSS music files are +distributed with an .m3u file in a non-standard format which specifies +which tracks actually have music, among other things. When a game music +file is opened, a corresponding m3u file is opened if in the same +directory. There's not much that we can do about the situation, +unfortunately. + + +Things not supported +-------------------- +* Gzipped files (Audacious needs to support these in its VFS layer) + +* KSS: FM sound + +* GYM: files without a header + +* HES: ADPCM samples (used in only a few soundtracks, if that) + +* SAP: "digimusic samples" and more obscure tracker formats + +* VGM/VGZ: original Sega Master System FM sound chip (Sega Genesis/Mega +Drive music plays fine) + + +Source code notes +----------------- +Game_Music_Emu library configuration is in blargg_config.h. See +gme_readme.txt and gme.txt for library documentation. + +* See TODO comments in Audacious_Config.cxx and Audacious_Driver.cxx for +things which would be good to address. + +* The library does not use C++ exceptions, so you could disable them in +the makefile to reduce code size a bit. + +* The cause of errors and also warnings about possible problems are +logged in log_err() and log_warning() using printf(). + +* The vfs_* functions are used for file access, so gzipped game music +files must currently be decompressed before playback (in particular, +.vgz files). + +* File types are determined based on the first four bytes of a file; the +file extension is never examined. Some .gym files don't have any header, +so these won't play. + +* Some music formats have more than one track in a file. This track is +specified to the console plugin by appending ?i to the end of the file +path, where i is the track index, starting at 0. For example, foo.nsf?2 +specifies the third track.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/console/sap_cpu_io.h Thu Nov 30 19:54:33 2006 -0800 @@ -0,0 +1,26 @@ + +#include "Sap_Emu.h" + +#include "blargg_source.h" + +#define CPU_WRITE( cpu, addr, data, time ) STATIC_CAST(Sap_Emu&,*cpu).cpu_write( addr, data ) + +void Sap_Emu::cpu_write( sap_addr_t addr, int data ) +{ + mem [addr] = data; + if ( (addr >> 8) == 0xD2 ) + cpu_write_( addr, data ); +} + +#ifdef NDEBUG + #define CPU_READ( cpu, addr, time ) READ_LOW( addr ) +#else + #define CPU_READ( cpu, addr, time ) STATIC_CAST(Sap_Emu&,*cpu).cpu_read( addr ) + + int Sap_Emu::cpu_read( sap_addr_t addr ) + { + if ( (addr & 0xF900) == 0xD000 ) + dprintf( "Unmapped read $%04X\n", addr ); + return mem [addr]; + } +#endif