changeset 316:fb513e10174e trunk

[svn] - merge libconsole-blargg into mainline libconsole: + obsoletes plugins-ugly:sapplug
author nenolod
date Thu, 30 Nov 2006 19:54:33 -0800
parents 2294f3a6f136
children 3b3887985a5c
files ChangeLog src/console/Audacious_Config.cxx src/console/Audacious_Config.h src/console/Audacious_Driver.cxx src/console/Ay_Apu.cxx src/console/Ay_Apu.h src/console/Ay_Cpu.cxx src/console/Ay_Cpu.h src/console/Ay_Emu.cxx src/console/Ay_Emu.h src/console/Blip_Buffer.cxx src/console/Blip_Buffer.h src/console/Classic_Emu.cxx src/console/Classic_Emu.h src/console/Data_Reader.cxx src/console/Data_Reader.h src/console/Dual_Resampler.cxx src/console/Dual_Resampler.h src/console/Effects_Buffer.cxx src/console/Effects_Buffer.h src/console/Fir_Resampler.cxx src/console/Fir_Resampler.h src/console/Gb_Apu.cxx src/console/Gb_Apu.h src/console/Gb_Cpu.cxx src/console/Gb_Cpu.h src/console/Gb_Oscs.cxx src/console/Gb_Oscs.h src/console/Gbs_Emu.cxx src/console/Gbs_Emu.h src/console/Gme_File.cxx src/console/Gme_File.h src/console/Gym_Emu.cxx src/console/Gym_Emu.h src/console/Hes_Apu.cxx src/console/Hes_Apu.h src/console/Hes_Cpu.cxx src/console/Hes_Cpu.h src/console/Hes_Emu.cxx src/console/Hes_Emu.h src/console/Kss_Cpu.cxx src/console/Kss_Cpu.h src/console/Kss_Emu.cxx src/console/Kss_Emu.h src/console/Kss_Scc_Apu.cxx src/console/Kss_Scc_Apu.h src/console/M3u_Playlist.cxx src/console/M3u_Playlist.h src/console/Makefile src/console/Multi_Buffer.cxx src/console/Multi_Buffer.h src/console/Music_Emu.cxx src/console/Music_Emu.h src/console/Nes_Apu.cxx src/console/Nes_Apu.h src/console/Nes_Cpu.cxx src/console/Nes_Cpu.h src/console/Nes_Fme7_Apu.cxx src/console/Nes_Fme7_Apu.h src/console/Nes_Namco_Apu.cxx src/console/Nes_Namco_Apu.h src/console/Nes_Oscs.cxx src/console/Nes_Oscs.h src/console/Nes_Vrc6_Apu.cxx src/console/Nes_Vrc6_Apu.h src/console/Nsf_Emu.cxx src/console/Nsf_Emu.h src/console/Nsfe_Emu.cxx src/console/Nsfe_Emu.h src/console/Sap_Apu.cxx src/console/Sap_Apu.h src/console/Sap_Cpu.cxx src/console/Sap_Cpu.h src/console/Sap_Emu.cxx src/console/Sap_Emu.h src/console/Sms_Apu.cxx src/console/Sms_Apu.h src/console/Sms_Oscs.h src/console/Snes_Spc.cxx src/console/Snes_Spc.h src/console/Spc_Cpu.cxx src/console/Spc_Cpu.h src/console/Spc_Dsp.cxx src/console/Spc_Dsp.h src/console/Spc_Emu.cxx src/console/Spc_Emu.h src/console/Vfs_File.cxx src/console/Vfs_File.h src/console/Vgm_Emu.cxx src/console/Vgm_Emu.h src/console/Vgm_Emu_Impl.cxx src/console/Vgm_Emu_Impl.h src/console/Ym2413_Emu.cxx src/console/Ym2413_Emu.h src/console/Ym2612_Emu.cxx src/console/Ym2612_Emu.h src/console/blargg_common.h src/console/blargg_config.h src/console/blargg_endian.h src/console/blargg_source.h src/console/gb_cpu_io.h src/console/gme.cxx src/console/gme.h src/console/gme_design.txt src/console/gme_notes.txt src/console/gme_readme.txt src/console/gme_type_list.cxx src/console/hes_cpu_io.h src/console/nes_cpu_io.h src/console/readme.txt src/console/sap_cpu_io.h
diffstat 111 files changed, 17993 insertions(+), 4834 deletions(-) [+]
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 = &regs [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( &copyright [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 = &copyright [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