changeset 341:986f098da058 trunk

[svn] - merge in blargg's changes
author nenolod
date Thu, 07 Dec 2006 15:20:41 -0800
parents 9e5a7158fa80
children 17311560f45f
files ChangeLog 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_Cpu.cxx src/console/Gb_Cpu.h src/console/Gb_Oscs.cxx 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/Gzip_Reader.cxx src/console/Gzip_Reader.h src/console/Hes_Apu.cxx 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/Music_Emu.cxx src/console/Music_Emu.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_Oscs.cxx 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/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/Zlib_Inflater.cxx src/console/Zlib_Inflater.h src/console/blargg_common.h src/console/blargg_config.h src/console/blargg_endian.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/nes_cpu_io.h src/console/readme.txt src/console/sap_cpu_io.h
diffstat 97 files changed, 1813 insertions(+), 1116 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog	Wed Dec 06 07:57:05 2006 -0800
+++ b/ChangeLog	Thu Dec 07 15:20:41 2006 -0800
@@ -1,3 +1,12 @@
+2006-12-06 15:57:05 +0000  William Pitcock <nenolod@nenolod.net>
+  revision [744]
+  - fix fprovide.[cxx,h] -- they were the wrong revision
+  
+  trunk/src/adplug/core/fprovide.cxx |    2 +-
+  trunk/src/adplug/core/fprovide.h   |    2 +-
+  2 files changed, 2 insertions(+), 2 deletions(-)
+
+
 2006-12-06 15:37:05 +0000  William Pitcock <nenolod@nenolod.net>
   revision [742]
   - implement virtual class to use VFS through binio
--- a/src/console/Audacious_Driver.cxx	Wed Dec 06 07:57:05 2006 -0800
+++ b/src/console/Audacious_Driver.cxx	Thu Dec 07 15:20:41 2006 -0800
@@ -23,10 +23,11 @@
 #include "Audacious_Config.h"
 
 #include "Music_Emu.h"
-#include "Vfs_File.h"
+#include "Gzip_Reader.h"
 
 int const fade_threshold = 10 * 1000;
 int const fade_length    = 8 * 1000;
+int const path_max = 4096;
 
 AudaciousConsoleConfig audcfg = { 180, FALSE, 32000, TRUE, 0, 0, FALSE, 0 };
 static GThread* decode_thread;
@@ -35,7 +36,6 @@
 static volatile long pending_seek;
 extern InputPlugin console_ip;
 static Music_Emu* emu = 0;
-static int track_ended;
 
 static blargg_err_t log_err( blargg_err_t err )
 {
@@ -59,84 +59,126 @@
 	emu = NULL;
 }
 
-// Extracts track number from file path, also frees memory at end of block
-
-struct Url_Parser
-{
-	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 ); }
+// Handles URL parsing, file opening and identification, and file loading.
+// Keeps file header around when loading rest of file to avoid seeking
+// and re-reading.
+class File_Handler {
+public:
+	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
+	Music_Emu* emu;         // set to 0 to take ownership
+	gme_type_t type;
+	
+	// Parses path and identifies file type
+	File_Handler( const char* path, VFSFile* fd = 0 );
+	
+	// Creates emulator and returns 0. If this wasn't a music file or
+	// emulator couldn't be created, returns 1.
+	int load( long sample_rate );
+	
+	// Deletes owned emu and closes file
+	~File_Handler();
+private:
+	char header [4];
+	Vfs_File_Reader vfs_in;
+	Gzip_Reader in;
 };
 
-Url_Parser::Url_Parser( gchar* path_in )
+File_Handler::File_Handler( const char* path_in, VFSFile* fd )
 {
+	emu   = 0;
+	type  = 0;
 	track = 0;
 	track_specified = false;
 	
 	path = g_strdup( path_in );
-	if ( path )
+	if ( !path )
+		return; // out of memory
+	
+	// extract track number
+	gchar* args = strchr( path, '?' ); // TODO: use strrchr()?
+	if ( args )
 	{
-		gchar* args = strchr( path, '?' );
-		if ( args )
+		*args = '\0';
+		// TODO: use func with better error reporting, and perhaps don't
+		// truncate path if there is no number after ?
+		track = atoi( args + 1 );
+		track_specified = true;
+	}
+	
+	// open vfs
+	if ( fd )
+		vfs_in.reset( fd );
+	else if ( log_err( vfs_in.open( path ) ) )
+		return;
+	
+	// now open gzip_reader on top of vfs
+	if ( log_err( in.open( &vfs_in ) ) )
+		return;
+	
+	// read and identify header
+	if ( !log_err( in.read( header, sizeof header ) ) )
+	{
+		type = gme_identify_extension( gme_identify_header( header ) );
+		if ( !type )
 		{
-			*args = '\0';
-			track = atoi( args + 1 );
-			track_specified = true;
+			type = gme_identify_extension( path );
+			if ( type != gme_gym_type ) // only trust file extension for headerless .gym files
+				type = 0;
 		}
 	}
 }
 
-// Determine file type based on header contents. Returns 0 if unrecognized or path is NULL.
-static gme_type_t identify_file( gchar* path )
+File_Handler::~File_Handler()
 {
-	if ( path )
-	{
-		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() );
-	}
-	return 0;
+	gme_delete( emu );
+	g_free( path );
 }
 
-// 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 )
+int File_Handler::load( long sample_rate )
 {
-	if ( !emu )
-		return "Out of memory";
-	
-	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 );
+	if ( !type )
+		return 1;
 	
-	if ( !err )
-		err = emu->load( in );
-	in.close();
-	
-	if ( !err )
+	emu = gme_new_emu( type, sample_rate );
+	if ( !emu )
 	{
-		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
+		log_err( "Out of memory" );
+		return 1;
+	}
+	
+	{
+		// combine header with remaining file data
+		Remaining_Reader reader( header, sizeof header, &in );
+		if ( log_err( emu->load( reader ) ) )
+			return 1;
 	}
 	
-	return err;
+	// files can be closed now
+	in.close();
+	vfs_in.close();
+	
+	log_warning( emu );
+	
+	// load .m3u from same directory( replace/add extension with ".m3u")
+	char m3u_path [path_max + 5];
+	strncpy( m3u_path, path, path_max );
+	m3u_path [path_max] = 0;
+	// TODO: use better path-building functions
+	char* p = strrchr( m3u_path, '.' );
+	if ( !p )
+		p = m3u_path + strlen( m3u_path );
+	strcpy( p, ".m3u" );
+	
+	Vfs_File_Reader m3u;
+	if ( !m3u.open( m3u_path ) )
+	{
+		if ( log_err( emu->load_m3u( m3u ) ) ) // TODO: fail if m3u can't be loaded?
+			log_warning( emu ); // this will log line number of first problem in m3u
+	}
+	
+	return 0;
 }
 
 // Get info
@@ -154,6 +196,7 @@
 		if ( info.track_count > 1 )
 			ti->track_number = track + 1;
 		ti->comment    = g_strdup( info.copyright );
+		ti->genre      = g_strconcat( "Console: ", info.system, NULL );
 		
 		int length = info.length;
 		if ( length <= 0 )
@@ -180,14 +223,13 @@
 static TitleInput *get_song_tuple( gchar *path )
 {
 	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;
+	File_Handler fh( path );
+	if ( !fh.load( gme_info_only ) )
+	{
+		track_info_t info;
+		if ( !log_err( fh.emu->track_info( &info, fh.track ) ) )
+			result = get_track_ti( fh.path, info, fh.track );
+	}
 	return result;
 }
 
@@ -207,11 +249,8 @@
 {
 	g_static_mutex_lock( &playback_mutex );
 	
-	while ( console_ip_is_going )
+	while ( console_ip_is_going && !emu->track_ended() )
 	{
-		int const buf_size = 1024;
-		Music_Emu::sample_t buf [buf_size];
-		
 		// handle pending seek
 		long s = pending_seek;
 		pending_seek = -1; // TODO: use atomic swap
@@ -220,21 +259,13 @@
 			console_ip.output->flush( s * 1000 );
 			emu->seek( s * 1000 );
 		}
-
-		// fill buffer
-		if ( track_ended )
-		{
-			// 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
-		{
-			emu->play( buf_size, buf );
-			track_ended = emu->track_ended();
-		}
+		
+		// fill and play buffer of audio
+		// TODO: see if larger buffer helps efficiency
+		int const buf_size = 1024;
+		Music_Emu::sample_t buf [buf_size];
+		emu->play( buf_size, buf );
+		
 		produce_audio( console_ip.output->written_time(), 
 			FMT_S16_NE, 1, sizeof buf, buf, 
 			&console_ip_is_going );
@@ -255,29 +286,25 @@
 	unload_file();
 	
 	// identify file
-	Url_Parser url( path );
-	gme_type_t type = identify_file( url.path );
-	if ( !type ) return;
+	File_Handler fh( path );
+	if ( !fh.type )
+		return;
 	
-	// sample rate
+	// select sample rate
 	long sample_rate = 0;
-	if ( type == gme_spc_type )
+	if ( fh.type == gme_spc_type )
 		sample_rate = 32000;
 	if ( audcfg.resample )
 		sample_rate = audcfg.resample_rate;
 	if ( !sample_rate )
 		sample_rate = 44100;
 	
-	// create emulator and load
-	emu = gme_new_emu( type, sample_rate );
-	if ( load_in_emu( emu, url.path ) )
-	{
-		unload_file();
+	// create emulator and load file
+	if ( fh.load( sample_rate ) )
 		return;
-	}
 	
 	// stereo echo depth
-	gme_set_stereo_depth( emu, 1.0 / 100 * audcfg.echo );
+	gme_set_stereo_depth( fh.emu, 1.0 / 100 * audcfg.echo );
 	
 	// set equalizer
 	if ( audcfg.treble || audcfg.bass )
@@ -292,53 +319,54 @@
 		double treble = audcfg.treble / 100.0;
 		eq.treble = treble * (treble < 0 ? 50.0 : 5.0);
 		
-		emu->set_equalizer(eq);
+		fh.emu->set_equalizer(eq);
 	}
 	
 	// get info
 	int length = -1;
 	track_info_t info;
-	if ( !log_err( emu->track_info( &info, url.track ) ) )
+	if ( !log_err( fh.emu->track_info( &info, fh.track ) ) )
 	{
-		if ( type == gme_spc_type && audcfg.ignore_spc_length )
+		if ( fh.type == gme_spc_type && audcfg.ignore_spc_length )
 			info.length = -1;
-		TitleInput* ti = get_track_ti( url.path, info, url.track );
+		TitleInput* ti = get_track_ti( fh.path, info, fh.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 );
+				console_ip.set_info( title, length, fh.emu->voice_count() * 1000, sample_rate, 2 );
 				g_free( title );
 			}
 		}
 	}
+	
+	// start track
+	if ( log_err( fh.emu->start_track( fh.track ) ) )
+		return;
+	log_warning( fh.emu );
+	if ( !console_ip.output->open_audio( FMT_S16_NE, sample_rate, 2 ) )
+		return;
+	
+	// set fade time
 	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;
 	if ( length >= fade_threshold + fade_length )
 		length -= fade_length;
-	emu->set_fade( length, fade_length );
+	fh.emu->set_fade( length, fade_length );
+	
+	// take ownership of emu
+	emu = fh.emu;
+	fh.emu = 0;
+	
+	pending_seek = -1;
 	console_ip_is_going = 1;
 	decode_thread = g_thread_create( play_loop_track, NULL, TRUE, NULL );
 }
 
 static void seek( gint time )
 {
-	// TODO: be sure seek works at all
-	// TODO: disallow seek on slow formats (SPC, GYM, VGM using FM)?
+	// TODO: use thread-safe atomic set
 	pending_seek = time;
 }
 
@@ -364,59 +392,37 @@
 	return console_ip_is_going ? console_ip.output->output_time() : -1;
 }
 
-static gint is_our_file_from_vfs( gchar* filename, VFSFile* fd )
+static gint is_our_file_from_vfs( gchar* path, VFSFile* fd )
 {
-	Url_Parser url( filename );
-	if ( !url.path ) return false;
-
-	// open file if not already open
-	Vfs_File_Reader in;
-	if ( !fd )
+	gint result = 0;
+	File_Handler fh( path, fd );
+	if ( fh.type )
 	{
-		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 )
-	{
-		if ( url.track_specified || type->track_count == 1 )
+		if ( fh.track_specified || fh.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
+		else if ( !fh.load( gme_info_only ) )
 		{
 			// 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 ( fh.emu->track_count() == 1 )
 			{
-				if ( emu->track_count() == 1 )
-				{
-					result = 1;
-				}
-				else
+				result = 1;
+			}
+			else
+			{
+				// for multi-track types, add each track to playlist
+				for (int i = 0; i < fh.emu->track_count(); i++)
 				{
-					// 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);
+					gchar _buf[path_max];
+					g_snprintf(_buf, path_max, "%s?%d", fh.path, i);
 
-						playlist_add_url(_buf);
-					}
-					result = -1;
+					playlist_add_url(_buf);
 				}
+				result = -1;
 			}
-			delete emu;
 		}
 	}
 	return result;
@@ -436,7 +442,7 @@
 	if (!aboutbox)
 	{
 		aboutbox = xmms_show_message(_("About the Console Music Decoder"),
-						_("Console music decoder engine based on Game_Music_Emu 0.5.1.\n"
+						_("Console music decoder engine based on Game_Music_Emu 0.5.2.\n"
 						"Audacious implementation by: William Pitcock <nenolod@nenolod.net>, \n"
 						"        Shay Green <gblargg@gmail.com>"),
 						_("Ok"),
--- a/src/console/Ay_Apu.cxx	Wed Dec 06 07:57:05 2006 -0800
+++ b/src/console/Ay_Apu.cxx	Thu Dec 07 15:20:41 2006 -0800
@@ -1,4 +1,4 @@
-// Game_Music_Emu 0.5.1. http://www.slack.net/~ant/
+// Game_Music_Emu 0.5.2. http://www.slack.net/~ant/
 
 #include "Ay_Apu.h"
 
@@ -74,13 +74,13 @@
 	{
 		byte* out = env.modes [m];
 		int flags = modes [m];
-		for ( int n = 3; --n >= 0; )
+		for ( int x = 3; --x >= 0; )
 		{
 			int amp = flags & 1;
 			int end = flags >> 1 & 1;
 			int step = end - amp;
 			amp *= 15;
-			for ( int n = 16; --n >= 0; )
+			for ( int y = 16; --y >= 0; )
 			{
 				*out++ = amp_table [amp];
 				amp += step;
@@ -119,6 +119,8 @@
 
 void Ay_Apu::write_data_( int addr, int data )
 {
+	assert( (unsigned) addr < reg_count );
+	
 	if ( (unsigned) addr >= 14 )
 	{
 		#ifdef dprintf
@@ -268,11 +270,13 @@
 			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 );
+				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.
--- a/src/console/Ay_Apu.h	Wed Dec 06 07:57:05 2006 -0800
+++ b/src/console/Ay_Apu.h	Thu Dec 07 15:20:41 2006 -0800
@@ -1,6 +1,6 @@
 // AY-3-8910 sound chip emulator
 
-// Game_Music_Emu 0.5.1
+// Game_Music_Emu 0.5.2
 #ifndef AY_APU_H
 #define AY_APU_H
 
@@ -16,6 +16,7 @@
 	void reset();
 	
 	// Write to register at specified time
+	enum { reg_count = 16 };
 	void write( blip_time_t time, int addr, int data );
 	
 	// Run sound to specified time, end current time frame, then start a new
@@ -50,7 +51,7 @@
 	} oscs [osc_count];
 	blip_time_t last_time;
 	byte latch;
-	byte regs [16];
+	byte regs [reg_count];
 	
 	struct {
 		blip_time_t delay;
--- a/src/console/Ay_Cpu.cxx	Wed Dec 06 07:57:05 2006 -0800
+++ b/src/console/Ay_Cpu.cxx	Thu Dec 07 15:20:41 2006 -0800
@@ -1,8 +1,10 @@
-// Game_Music_Emu 0.5.1. http://www.slack.net/~ant/
+// Game_Music_Emu 0.5.2. 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.
+/*
+Last validated with zexall 2006.11.21 5:26 PM
+* Doesn't implement the R register or immediate interrupt after EI.
+* Address wrap-around isn't completely correct, but is prevented from crashing emulator.
+*/
 
 #include "Ay_Cpu.h"
 
@@ -27,10 +29,10 @@
 
 // Callbacks to emulator
 
-#define CPU_OUT( cpu, addr, data, TIME ) \
+#define CPU_OUT( cpu, addr, data, TIME )\
 	ay_cpu_out( cpu, TIME, addr, data )
 
-#define CPU_IN( cpu, addr, TIME ) \
+#define CPU_IN( cpu, addr, TIME )\
 	ay_cpu_in( cpu, addr )
 
 #include "blargg_source.h"
@@ -105,7 +107,7 @@
 //#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 ) \
+#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
@@ -354,7 +356,14 @@
 		goto loop;
 	}
 	
-	CASE8( C7, CF, D7, DF, E7, EF, F7, FF ): // RST
+	case 0xFF: // RST
+		if ( (pc - 1) > 0xFFFF )
+		{
+			pc = uint16_t (pc - 1);
+			s_time -= 11;
+			goto loop;
+		}
+	CASE7( C7, CF, D7, DF, E7, EF, F7 ):
 		data = pc;
 		pc = opcode & 0x38;
 		goto push_data;
@@ -1297,6 +1306,9 @@
 		s_time += ed_dd_timing [data] & 0x0F;
 		switch ( data )
 		{
+	// TODO: more efficient way of avoid negative address
+	#define IXY_DISP( ixy, disp )   uint16_t ((ixy) + (disp))
+	
 	#define SET_IXY( in ) if ( opcode == 0xDD ) ix = in; else iy = in;
 	
 	// ADD/ADC/SUB/SBC
@@ -1308,7 +1320,7 @@
 		case 0x8E: // ADC (IXY+disp)
 			pc++;
 			opcode = data;
-			data = READ( ixy + (int8_t) data2 );
+			data = READ( IXY_DISP( ixy, (int8_t) data2 ) );
 			goto adc_data;
 		
 		case 0x94: // SUB HXY
@@ -1330,26 +1342,26 @@
 			goto adc_data;
 		
 		{
-			unsigned data2;
+			unsigned temp;
 		case 0x39: // ADD IXY,SP
-			data2 = sp;
+			temp = sp;
 			goto add_ixy_data;
 		
 		case 0x29: // ADD IXY,HL
-			data2 = ixy;
+			temp = ixy;
 			goto add_ixy_data;
 		
 		case 0x09: // ADD IXY,BC
 		case 0x19: // ADD IXY,DE
-			data2 = R16( data, 4, 0x09 );
+			temp = R16( data, 4, 0x09 );
 		add_ixy_data: {
-			blargg_ulong sum = ixy + data2;
-			data2 ^= ixy;
-			ixy = sum;
+			blargg_ulong sum = ixy + temp;
+			temp ^= ixy;
+			ixy = (uint16_t) sum;
 			flags = (flags & (S80 | Z40 | V04)) |
 					(sum >> 16) |
 					(sum >> 8 & (F20 | F08)) |
-					((data2 ^ sum) >> 8 & H10);
+					((temp ^ sum) >> 8 & H10);
 			goto set_ixy;
 		}
 		}
@@ -1357,7 +1369,7 @@
 	// AND
 		case 0xA6: // AND (IXY+disp)
 			pc++;
-			data = READ( ixy + (int8_t) data2 );
+			data = READ( IXY_DISP( ixy, (int8_t) data2 ) );
 			goto and_data;
 		
 		case 0xA4: // AND HXY
@@ -1371,7 +1383,7 @@
 	// OR
 		case 0xB6: // OR (IXY+disp)
 			pc++;
-			data = READ( ixy + (int8_t) data2 );
+			data = READ( IXY_DISP( ixy, (int8_t) data2 ) );
 			goto or_data;
 		
 		case 0xB4: // OR HXY
@@ -1385,7 +1397,7 @@
 	// XOR
 		case 0xAE: // XOR (IXY+disp)
 			pc++;
-			data = READ( ixy + (int8_t) data2 );
+			data = READ( IXY_DISP( ixy, (int8_t) data2 ) );
 			goto xor_data;
 		
 		case 0xAC: // XOR HXY
@@ -1399,7 +1411,7 @@
 	// CP
 		case 0xBE: // CP (IXY+disp)
 			pc++;
-			data = READ( ixy + (int8_t) data2  );
+			data = READ( IXY_DISP( ixy, (int8_t) data2 )  );
 			goto cp_data;
 		
 		case 0xBC: // CP HXY
@@ -1417,7 +1429,7 @@
 		case 0x36: // LD (IXY+disp),imm
 				pc++, data = READ_PROG( pc );
 			pc++;
-			WRITE( ixy + (int8_t) data2, data );
+			WRITE( IXY_DISP( ixy, (int8_t) data2 ), data );
 			goto loop;
 
 		CASE5( 44, 4C, 54, 5C, 7C ): // LD r,HXY
@@ -1434,7 +1446,7 @@
 		
 		CASE7( 46, 4E, 56, 5E, 66, 6E, 7E ): // LD r,(IXY+disp)
 			pc++;
-			R8( data >> 3, 8 ) = READ( ixy + (int8_t) data2 );
+			R8( data >> 3, 8 ) = READ( IXY_DISP( ixy, (int8_t) data2 ) );
 			goto loop;
 		
 		case 0x26: // LD HXY,imm
@@ -1497,7 +1509,7 @@
 		
 	// DD/FD CB prefix
 		case 0xCB: {
-			data = ixy + (int8_t) data2;
+			data = IXY_DISP( ixy, (int8_t) data2 );
 			pc++;
 			data2 = READ_PROG( pc );
 			pc++;
@@ -1550,14 +1562,14 @@
 			goto set_ixy;
 		
 		case 0x34: // INC (IXY+disp)
-			ixy += (int8_t) data2;
+			ixy = 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;
+			ixy = IXY_DISP( ixy, (int8_t) data2 );
 			pc++;
 			data = READ( ixy ) - 1;
 			WRITE( ixy, data );
@@ -1639,12 +1651,12 @@
 out_of_time:
 	pc--;
 	
-	s.time = s_time;
+	s.time   = s_time;
 	rg.flags = flags;
-	r.ix    = ix;
-	r.iy    = iy;
-	r.sp    = sp;
-	r.pc    = pc;
+	r.ix     = ix;
+	r.iy     = iy;
+	r.sp     = sp;
+	r.pc     = pc;
 	this->r.b = rg;
 	this->state_ = s;
 	this->state = &this->state_;
--- a/src/console/Ay_Cpu.h	Wed Dec 06 07:57:05 2006 -0800
+++ b/src/console/Ay_Cpu.h	Thu Dec 07 15:20:41 2006 -0800
@@ -1,6 +1,6 @@
 // Z80 CPU emulator
 
-// Game_Music_Emu 0.5.1
+// Game_Music_Emu 0.5.2
 #ifndef AY_CPU_H
 #define AY_CPU_H
 
--- a/src/console/Ay_Emu.cxx	Wed Dec 06 07:57:05 2006 -0800
+++ b/src/console/Ay_Emu.cxx	Thu Dec 07 15:20:41 2006 -0800
@@ -1,4 +1,4 @@
-// Game_Music_Emu 0.5.1. http://www.slack.net/~ant/
+// Game_Music_Emu 0.5.2. http://www.slack.net/~ant/
 
 #include "Ay_Emu.h"
 
@@ -18,6 +18,9 @@
 
 #include "blargg_source.h"
 
+long const spectrum_clock = 3546900;
+long const cpc_clock      = 2000000;
+
 unsigned const ram_start = 0x4000;
 int const osc_count = Ay_Apu::osc_count + 1;
 
@@ -59,7 +62,7 @@
 	out->header = (header_t const*) in;
 	out->end    = in + size;
 	
-	if ( size < (long) sizeof (header_t) )
+	if ( size < Ay_Emu::header_size )
 		return gme_wrong_file_type;
 	
 	header_t const& h = *(header_t const*) in;
@@ -113,12 +116,14 @@
 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 };
+gme_type_t_ const gme_ay_type [1] = { "ZX Spectrum", 0, &new_ay_emu, &new_ay_file, "AY", 1 };
 
 // Setup
 
 blargg_err_t Ay_Emu::load_mem_( byte const* in, long size )
 {
+	assert( offsetof (header_t,track_info [2]) == header_size );
+	
 	RETURN_ERR( parse_header( in, size, &file ) );
 	set_track_count( file.header->max_track + 1 );
 	
@@ -128,7 +133,7 @@
 	set_voice_count( osc_count );
 	apu.volume( gain() );
 	
-	return setup_buffer( 3546900 );
+	return setup_buffer( spectrum_clock );
 }
 	
 void Ay_Emu::update_eq( blip_eq_t const& eq )
@@ -155,9 +160,11 @@
 {
 	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 );
+	memset( mem.ram + 0x0000, 0xC9, 0x100 ); // fill RST vectors with RET
+	memset( mem.ram + 0x0100, 0xFF, 0x4000 - 0x100 );
+	memset( mem.ram + ram_start, 0x00, sizeof mem.ram - ram_start );
+	memset( mem.padding1, 0xFF, sizeof mem.padding1 );
+	memset( mem.ram + 0x10000, 0xFF, sizeof mem.ram - 0x10000 );
 	
 	// locate data blocks
 	byte const* const data = get_data( file, file.tracks + track * 4 + 2, 14 );
@@ -170,7 +177,7 @@
 	if ( !blocks ) return "File data missing";
 	
 	// initial addresses
-	cpu::reset( mem );
+	cpu::reset( mem.ram );
 	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];
@@ -204,7 +211,7 @@
 		//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 );
+		memcpy( mem.ram + addr, in, len );
 		
 		if ( file.end - blocks < 8 )
 		{
@@ -232,37 +239,98 @@
 		0xCD, 0, 0, // CALL play
 		0x18, 0xF7  // JR LOOP
 	};
-	memcpy( mem, passive, sizeof passive );
+	memcpy( mem.ram, 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;
+		memcpy( mem.ram, active, sizeof active );
+		mem.ram [ 9] = play_addr;
+		mem.ram [10] = play_addr >> 8;
 	}
-	mem [2] = init;
-	mem [3] = init >> 8;
+	mem.ram [2] = init;
+	mem.ram [3] = init >> 8;
 	
-	mem [0x38] = 0xFB; // Put EI at interrupt vector (followed by RET)
+	mem.ram [0x38] = 0xFB; // Put EI at interrupt vector (followed by RET)
 	
-	memcpy( mem + 0x10000, mem, sizeof mem - 0x10000 ); // some code wraps around (ugh)
+	memcpy( mem.ram + 0x10000, mem.ram, 0x80 ); // some code wraps around (ugh)
 	
 	beeper_delta = int (apu.amp_range * 0.65);
 	last_beeper = 0;
 	apu.reset();
 	next_play = play_period;
 	
+	// start at spectrum speed
+	change_clock_rate( spectrum_clock );
+	set_tempo( tempo() );
+	
+	spectrum_mode = false;
+	cpc_mode      = false;
+	cpc_latch     = 0;
+	
 	return 0;
 }
 
 // Emulation
 
+void Ay_Emu::cpu_out_misc( cpu_time_t time, unsigned addr, int data )
+{
+	if ( !cpc_mode )
+	{
+		switch ( addr & 0xFEFF )
+		{
+		case 0xFEFD:
+			spectrum_mode = true;
+			apu_addr = data & 0x0F;
+			return;
+		
+		case 0xBEFD:
+			spectrum_mode = true;
+			apu.write( time, apu_addr, data );
+			return;
+		}
+	}
+	
+	if ( !spectrum_mode )
+	{
+		switch ( addr >> 8 )
+		{
+		case 0xF6:
+			switch ( data & 0xC0 )
+			{
+			case 0xC0:
+				apu_addr = cpc_latch & 0x0F;
+				goto enable_cpc;
+			
+			case 0x80:
+				apu.write( time, apu_addr, cpc_latch );
+				goto enable_cpc;
+			}
+			break;
+		
+		case 0xF4:
+			cpc_latch = data;
+			goto enable_cpc;
+		}
+	}
+	
+	dprintf( "Unmapped OUT: $%04X <- $%02X\n", addr, data );
+	return;
+	
+enable_cpc:
+	if ( !cpc_mode )
+	{
+		cpc_mode = true;
+		change_clock_rate( cpc_clock );
+		set_tempo( tempo() );
+	}
+}
+
 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 )
+	if ( (addr & 0xFF) == 0xFE && !emu.cpc_mode )
 	{
 		int delta = emu.beeper_delta;
 		data &= 0x10;
@@ -270,31 +338,22 @@
 		{
 			emu.last_beeper = data;
 			emu.beeper_delta = -delta;
+			emu.spectrum_mode = true;
 			if ( emu.beeper_output )
 				emu.apu.synth_.offset( time, delta, emu.beeper_output );
 		}
-		return;
 	}
-	
-	switch ( addr & 0xFEFF )
+	else
 	{
-	case 0xFEFD:
-		emu.apu_addr = data & 0x0F;
-		return;
-	
-	case 0xBEFD:
-		emu.apu.write( time, emu.apu_addr, data );
-		//remote_write( apu_addr, data );
-		return;
+		emu.cpu_out_misc( time, addr, data );
 	}
-	
-	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
+	if ( (addr & 0xFF) == 0xFE )
+		return 0xFF; // other values break some beeper tunes
 	
 	dprintf( "Unmapped IN : $%04X\n", addr );
 	return 0xFF;
@@ -303,9 +362,11 @@
 blargg_err_t Ay_Emu::run_clocks( blip_time_t& duration, int )
 {
 	set_time( 0 );
+	if ( !(spectrum_mode | cpc_mode) )
+		duration /= 2; // until mode is set, leave room for halved clock rate
+	
 	while ( time() < duration )
 	{
-		//long start = time();
 		cpu::run( min( duration, next_play ) );
 		
 		if ( time() >= next_play )
@@ -314,26 +375,23 @@
 			
 			if ( r.iff1 )
 			{
-				// TODO: don't interrupt if not enabled
-				if ( mem [r.pc] == 0x76 )
+				if ( mem.ram [r.pc] == 0x76 )
 					r.pc++;
 				
 				r.iff1 = r.iff2 = 0;
 				
-				mem [--r.sp] = r.pc >> 8;
-				mem [--r.sp] = r.pc;
+				mem.ram [--r.sp] = r.pc >> 8;
+				mem.ram [--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];
+					r.pc = mem.ram [(addr + 1) & 0xFFFF] * 0x100u + mem.ram [addr];
 				}
 			}
 		}
-		//dprintf( "elapsed: %d\n", time() - start );
-		//remote_frame();
 	}
 	duration = time();
 	next_play -= duration;
--- a/src/console/Ay_Emu.h	Wed Dec 06 07:57:05 2006 -0800
+++ b/src/console/Ay_Emu.h	Thu Dec 07 15:20:41 2006 -0800
@@ -1,6 +1,6 @@
 // Sinclair Spectrum AY music file emulator
 
-// Game_Music_Emu 0.5.1
+// Game_Music_Emu 0.5.2
 #ifndef AY_EMU_H
 #define AY_EMU_H
 
@@ -12,6 +12,7 @@
 	typedef Ay_Cpu cpu;
 public:
 	// AY file header
+	enum { header_size = 0x14 };
 	struct header_t
 	{
 		byte tag [8];
@@ -24,7 +25,6 @@
 		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:
@@ -53,11 +53,18 @@
 	int beeper_delta;
 	int last_beeper;
 	int apu_addr;
+	int cpc_latch;
+	bool spectrum_mode;
+	bool cpc_mode;
 	
 	// large items
-	byte mem [0x10000 + cpu_padding];
+	struct {
+		byte padding1 [0x100];
+		byte ram [0x10000 + 0x100];
+	} mem;
 	Ay_Apu apu;
 	friend void ay_cpu_out( Ay_Cpu*, cpu_time_t, unsigned addr, int data );
+	void cpu_out_misc( cpu_time_t, unsigned addr, int data );
 };
 
 #endif
--- a/src/console/Blip_Buffer.cxx	Wed Dec 06 07:57:05 2006 -0800
+++ b/src/console/Blip_Buffer.cxx	Thu Dec 07 15:20:41 2006 -0800
@@ -120,9 +120,9 @@
 	return 0; // success
 }
 
-blip_resampled_time_t Blip_Buffer::clock_rate_factor( long clock_rate ) const
+blip_resampled_time_t Blip_Buffer::clock_rate_factor( long rate ) const
 {
-	double ratio = (double) sample_rate_ / clock_rate;
+	double ratio = (double) sample_rate_ / rate;
 	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;
@@ -189,19 +189,19 @@
 
 // Blip_Synth_
 
-#if BLIP_BUFFER_FAST
-	Blip_Synth_::Blip_Synth_()
-	{
-		buf = 0;
-		last_amp = 0;
-		delta_factor = 0;
-	}
+Blip_Synth_Fast_::Blip_Synth_Fast_()
+{
+	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
+void Blip_Synth_Fast_::volume_unit( double new_unit )
+{
+	delta_factor = int (new_unit * (1L << blip_sample_bits) + 0.5);
+}
+
+#if !BLIP_BUFFER_FAST
 
 Blip_Synth_::Blip_Synth_( short* p, int w ) :
 	impulses( p ),
--- a/src/console/Blip_Buffer.h	Wed Dec 06 07:57:05 2006 -0800
+++ b/src/console/Blip_Buffer.h	Thu Dec 07 15:20:41 2006 -0800
@@ -149,6 +149,17 @@
 	int const blip_res = 1 << BLIP_PHASE_BITS;
 	class blip_eq_t;
 	
+	class Blip_Synth_Fast_ {
+	public:
+		Blip_Buffer* buf;
+		int last_amp;
+		int delta_factor;
+		
+		void volume_unit( double );
+		Blip_Synth_Fast_();
+		void treble_eq( blip_eq_t const& ) { }
+	};
+	
 	class Blip_Synth_ {
 	public:
 		Blip_Buffer* buf;
@@ -156,10 +167,6 @@
 		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& );
 	private:
@@ -169,7 +176,6 @@
 		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.
@@ -217,8 +223,10 @@
 	}
 	
 private:
+#if BLIP_BUFFER_FAST
+	Blip_Synth_Fast_ impl;
+#else
 	Blip_Synth_ impl;
-#if !BLIP_BUFFER_FAST
 	typedef short imp_t;
 	imp_t impulses [blip_res * (quality / 2) + 1];
 public:
--- a/src/console/Classic_Emu.cxx	Wed Dec 06 07:57:05 2006 -0800
+++ b/src/console/Classic_Emu.cxx	Thu Dec 07 15:20:41 2006 -0800
@@ -1,4 +1,4 @@
-// Game_Music_Emu 0.5.1. http://www.slack.net/~ant/
+// Game_Music_Emu 0.5.2. http://www.slack.net/~ant/
 
 #include "Classic_Emu.h"
 
@@ -43,7 +43,7 @@
 		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 rate )
 {
 	if ( !buf )
 	{
@@ -51,7 +51,7 @@
 			CHECK_ALLOC( stereo_buffer = BLARGG_NEW Stereo_Buffer );
 		buf = stereo_buffer;
 	}
-	return buf->set_sample_rate( sample_rate, 1000 / 20 );
+	return buf->set_sample_rate( rate, 1000 / 20 );
 }
 
 void Classic_Emu::mute_voices_( int mask )
@@ -73,10 +73,15 @@
 	}
 }
 
-blargg_err_t Classic_Emu::setup_buffer( long rate )
+void Classic_Emu::change_clock_rate( long rate )
 {
 	clock_rate_ = rate;
 	buf->clock_rate( rate );
+}
+
+blargg_err_t Classic_Emu::setup_buffer( long rate )
+{
+	change_clock_rate( rate );
 	RETURN_ERR( buf->set_channel_count( voice_count() ) );
 	set_equalizer( equalizer() );
 	buf_changed_count = buf->channels_changed_count();
--- a/src/console/Classic_Emu.h	Wed Dec 06 07:57:05 2006 -0800
+++ b/src/console/Classic_Emu.h	Thu Dec 07 15:20:41 2006 -0800
@@ -1,6 +1,6 @@
 // Common aspects of emulators which use Blip_Buffer for sound output
 
-// Game_Music_Emu 0.5.1
+// Game_Music_Emu 0.5.2
 #ifndef CLASSIC_EMU_H
 #define CLASSIC_EMU_H
 
@@ -19,6 +19,7 @@
 	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_; }
+	void change_clock_rate( long ); // experimental
 	
 	// Overridable
 	virtual void set_voice( int index, Blip_Buffer* center,
@@ -113,4 +114,14 @@
 	}
 };
 
+#ifndef GME_APU_HOOK
+	#define GME_APU_HOOK( emu, addr, data ) ((void) 0)
 #endif
+
+#ifndef GME_FRAME_HOOK
+	#define GME_FRAME_HOOK( emu ) ((void) 0)
+#else
+	#define GME_FRAME_HOOK_DEFINED 1
+#endif
+
+#endif
--- a/src/console/Data_Reader.cxx	Wed Dec 06 07:57:05 2006 -0800
+++ b/src/console/Data_Reader.cxx	Thu Dec 07 15:20:41 2006 -0800
@@ -2,6 +2,7 @@
 
 #include "Data_Reader.h"
 
+#include "blargg_endian.h"
 #include <assert.h>
 #include <string.h>
 #include <stdio.h>
@@ -17,11 +18,11 @@
 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 Data_Reader::eof_error [] = "Unexpected end of file";
 
-typedef Data_Reader::error_t error_t;
-
-error_t Data_Reader::read( void* p, long s )
+blargg_err_t Data_Reader::read( void* p, long s )
 {
 	long result = read_avail( p, s );
 	if ( result != s )
@@ -35,7 +36,7 @@
 	return 0;
 }
 
-error_t Data_Reader::skip( long count )
+blargg_err_t Data_Reader::skip( long count )
 {
 	char buf [512];
 	while ( count )
@@ -44,19 +45,19 @@
 		if ( n > count )
 			n = count;
 		count -= n;
-		error_t err = read( buf, n );
-		if ( err )
-			return err;
+		RETURN_ERR( read( buf, n ) );
 	}
 	return 0;
 }
 
 long File_Reader::remain() const { return size() - tell(); }
 
-error_t File_Reader::skip( long n )
+blargg_err_t File_Reader::skip( long n )
 {
 	assert( n >= 0 );
-	return n ? seek( tell() + n ) : 0;
+	if ( !n )
+		return 0;
+	return seek( tell() + n );
 }
 
 // Subset_Reader
@@ -97,9 +98,9 @@
 	{
 		if ( first > count )
 			first = count;
-		void const* in = header;
+		void const* old = header;
 		header += first;
-		memcpy( out, in, first );
+		memcpy( out, old, first );
 	}
 	return first;
 }
@@ -107,23 +108,23 @@
 long Remaining_Reader::read_avail( void* out, long count )
 {
 	long first = read_first( out, count );
-	long remain = count - first;
-	if ( remain )
+	long second = count - first;
+	if ( second )
 	{
-		remain = in->read_avail( (char*) out + first, remain );
-		if ( remain <= 0 )
-			return remain;
+		second = in->read_avail( (char*) out + first, second );
+		if ( second <= 0 )
+			return second;
 	}
-	return first + remain;
+	return first + second;
 }
 
-error_t Remaining_Reader::read( void* out, long count )
+blargg_err_t Remaining_Reader::read( void* out, long count )
 {
 	long first = read_first( out, count );
-	long remain = count - first;
-	if ( !remain )
+	long second = count - first;
+	if ( !second )
 		return 0;
-	return in->read( (char*) out + first, remain );
+	return in->read( (char*) out + first, second );
 }
 
 // Mem_File_Reader
@@ -149,7 +150,7 @@
 
 long Mem_File_Reader::tell() const { return pos; }
 
-error_t Mem_File_Reader::seek( long n )
+blargg_err_t Mem_File_Reader::seek( long n )
 {
 	if ( n > size_ )
 		return eof_error;
@@ -157,63 +158,6 @@
 	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 ) :
@@ -234,13 +178,70 @@
 	return count;
 }
 
-Callback_Reader::error_t Callback_Reader::read( void* out, long count )
+blargg_err_t Callback_Reader::read( void* out, long count )
 {
 	if ( count > remain_ )
 		return eof_error;
 	return callback( data, out, count );
 }
 
+// Std_File_Reader
+
+Std_File_Reader::Std_File_Reader() : file_( 0 ) { }
+
+Std_File_Reader::~Std_File_Reader() { close(); }
+
+blargg_err_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_ );
+}
+
+blargg_err_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_ ); }
+
+blargg_err_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;
+	}
+}
+
 // Gzip_File_Reader
 
 #ifdef HAVE_ZLIB_H
@@ -254,11 +255,11 @@
 		return "Couldn't open file";
 	
 	unsigned char buf [4];
-	if ( fread( buf, 2, 1, file ) == 1 && buf [0] == 0x1F && buf [1] == 0x8B )
+	if ( fread( buf, 2, 1, file ) > 0 && 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];
+		*eof = get_le32( buf );
 	}
 	else
 	{
@@ -274,13 +275,11 @@
 
 Gzip_File_Reader::~Gzip_File_Reader() { close(); }
 
-error_t Gzip_File_Reader::open( const char* path )
+blargg_err_t Gzip_File_Reader::open( const char* path )
 {
 	close();
 	
-	error_t err = get_gzip_eof( path, &size_ );
-	if ( err )
-		return err;
+	RETURN_ERR( get_gzip_eof( path, &size_ ) );
 	
 	file_ = gzopen( path, "rb" );
 	if ( !file_ )
@@ -295,7 +294,7 @@
 
 long Gzip_File_Reader::tell() const { return gztell( file_ ); }
 
-error_t Gzip_File_Reader::seek( long n )
+blargg_err_t Gzip_File_Reader::seek( long n )
 {
 	if ( gzseek( file_, n, SEEK_SET ) >= 0 )
 		return 0;
--- a/src/console/Data_Reader.h	Wed Dec 06 07:57:05 2006 -0800
+++ b/src/console/Data_Reader.h	Thu Dec 07 15:20:41 2006 -0800
@@ -4,34 +4,30 @@
 #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
+#include "blargg_common.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 );
+	virtual blargg_err_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 );
+	virtual blargg_err_t skip( long count );
 	
+public:
+	Data_Reader() { }
+	typedef blargg_err_t error_t; // deprecated
 private:
 	// noncopyable
 	Data_Reader( const Data_Reader& );
@@ -48,26 +44,26 @@
 	virtual long tell() const = 0;
 	
 	// Go to new position
-	virtual error_t seek( long ) = 0;
+	virtual blargg_err_t seek( long ) = 0;
 	
 	long remain() const;
-	error_t skip( long n );
+	blargg_err_t skip( long n );
 };
 
 // Disk file reader
 class Std_File_Reader : public File_Reader {
 public:
-	error_t open( const char* path );
+	blargg_err_t open( const char* path );
 	void close();
 	
 public:
 	Std_File_Reader();
 	~Std_File_Reader();
 	long size() const;
-	error_t read( void*, long );
+	blargg_err_t read( void*, long );
 	long read_avail( void*, long );
 	long tell() const;
-	error_t seek( long );
+	blargg_err_t seek( long );
 private:
 	void* file_;
 };
@@ -81,7 +77,7 @@
 	long size() const;
 	long read_avail( void*, long );
 	long tell() const;
-	error_t seek( long );
+	blargg_err_t seek( long );
 private:
 	const char* const begin;
 	const long size_;
@@ -109,7 +105,7 @@
 public:
 	long remain() const;
 	long read_avail( void*, long );
-	error_t read( void*, long );
+	blargg_err_t read( void*, long );
 private:
 	char const* header;
 	char const* header_end;
@@ -120,11 +116,11 @@
 // 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 );
+	typedef const char* (*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 );
+	blargg_err_t read( void*, long );
 	long remain() const;
 private:
 	callback_t const callback;
@@ -136,7 +132,7 @@
 // Gzip compressed file reader
 class Gzip_File_Reader : public File_Reader {
 public:
-	error_t open( const char* path );
+	blargg_err_t open( const char* path );
 	void close();
 	
 public:
@@ -145,7 +141,7 @@
 	long size() const;
 	long read_avail( void*, long );
 	long tell() const;
-	error_t seek( long );
+	blargg_err_t seek( long );
 private:
 	void* file_;
 	long size_;
@@ -153,4 +149,3 @@
 #endif
 
 #endif
-#endif
--- a/src/console/Dual_Resampler.cxx	Wed Dec 06 07:57:05 2006 -0800
+++ b/src/console/Dual_Resampler.cxx	Thu Dec 07 15:20:41 2006 -0800
@@ -1,4 +1,4 @@
-// Game_Music_Emu 0.5.1. http://www.slack.net/~ant/
+// Game_Music_Emu 0.5.2. http://www.slack.net/~ant/
 
 #include "Dual_Resampler.h"
 
--- a/src/console/Dual_Resampler.h	Wed Dec 06 07:57:05 2006 -0800
+++ b/src/console/Dual_Resampler.h	Thu Dec 07 15:20:41 2006 -0800
@@ -1,6 +1,6 @@
 // Combination of Fir_Resampler and Blip_Buffer mixing. Used by Sega FM emulators.
 
-// Game_Music_Emu 0.5.1
+// Game_Music_Emu 0.5.2
 #ifndef DUAL_RESAMPLER_H
 #define DUAL_RESAMPLER_H
 
--- a/src/console/Effects_Buffer.cxx	Wed Dec 06 07:57:05 2006 -0800
+++ b/src/console/Effects_Buffer.cxx	Thu Dec 07 15:20:41 2006 -0800
@@ -1,4 +1,4 @@
-// Game_Music_Emu 0.5.1. http://www.slack.net/~ant/
+// Game_Music_Emu 0.5.2. http://www.slack.net/~ant/
 
 #include "Effects_Buffer.h"
 
--- a/src/console/Effects_Buffer.h	Wed Dec 06 07:57:05 2006 -0800
+++ b/src/console/Effects_Buffer.h	Thu Dec 07 15:20:41 2006 -0800
@@ -1,6 +1,6 @@
 // Multi-channel effects buffer with panning, echo and reverb
 
-// Game_Music_Emu 0.5.1
+// Game_Music_Emu 0.5.2
 #ifndef EFFECTS_BUFFER_H
 #define EFFECTS_BUFFER_H
 
--- a/src/console/Fir_Resampler.cxx	Wed Dec 06 07:57:05 2006 -0800
+++ b/src/console/Fir_Resampler.cxx	Thu Dec 07 15:20:41 2006 -0800
@@ -1,4 +1,4 @@
-// Game_Music_Emu 0.5.1. http://www.slack.net/~ant/
+// Game_Music_Emu 0.5.2. http://www.slack.net/~ant/
 
 #include "Fir_Resampler.h"
 
@@ -57,19 +57,19 @@
 	write_offset( width * stereo - stereo ),
 	impulses( impulses_ )
 {
-	write_pos = NULL;
-	res = 1;
-	imp = 0;
+	write_pos = 0;
+	res       = 1;
+	imp_phase = 0;
 	skip_bits = 0;
-	step = stereo;
-	ratio_ = 1.0;
+	step      = stereo;
+	ratio_    = 1.0;
 }
 
 Fir_Resampler_::~Fir_Resampler_() { }
 
 void Fir_Resampler_::clear()
 {
-	imp = 0;
+	imp_phase = 0;
 	if ( buf.size() )
 	{
 		write_pos = &buf [write_offset];
@@ -142,8 +142,8 @@
 {
 	blargg_long input_count = 0;
 	
-	unsigned long skip = skip_bits >> imp;
-	int remain = res - imp;
+	unsigned long skip = skip_bits >> imp_phase;
+	int remain = res - imp_phase;
 	while ( (output_count -= 2) > 0 )
 	{
 		input_count += step + (skip & 1) * stereo;
@@ -168,8 +168,8 @@
 	int output_count = cycle_count * res * stereo;
 	input_count -= cycle_count * input_per_cycle;
 	
-	blargg_ulong skip = skip_bits >> imp;
-	int remain = res - imp;
+	blargg_ulong skip = skip_bits >> imp_phase;
+	int remain = res - imp_phase;
 	while ( input_count >= 0 )
 	{
 		input_count -= step + (skip & 1) * stereo;
@@ -187,10 +187,10 @@
 int Fir_Resampler_::skip_input( long count )
 {
 	int remain = write_pos - buf.begin();
-	int avail = remain - width_ * stereo;
-	if ( count > avail )
-		count = avail;
-
+	int max_count = remain - width_ * stereo;
+	if ( count > max_count )
+		count = max_count;
+	
 	remain -= count;
 	write_pos = &buf [remain];
 	memmove( buf.begin(), &buf [count], remain * sizeof buf [0] );
--- a/src/console/Fir_Resampler.h	Wed Dec 06 07:57:05 2006 -0800
+++ b/src/console/Fir_Resampler.h	Thu Dec 07 15:20:41 2006 -0800
@@ -1,6 +1,6 @@
 // Finite impulse response (FIR) resampler with adjustable FIR size
 
-// Game_Music_Emu 0.5.1
+// Game_Music_Emu 0.5.2
 #ifndef FIR_RESAMPLER_H
 #define FIR_RESAMPLER_H
 
@@ -61,7 +61,7 @@
 	blargg_vector<sample_t> buf;
 	sample_t* write_pos;
 	int res;
-	int imp;
+	int imp_phase;
 	int const width_;
 	int const write_offset;
 	blargg_ulong skip_bits;
@@ -102,9 +102,9 @@
 	sample_t* out = out_begin;
 	const sample_t* in = buf.begin();
 	sample_t* end_pos = write_pos;
-	blargg_ulong skip = skip_bits >> this->imp;
-	sample_t const* imp = impulses [this->imp];
-	int remain = res - this->imp;
+	blargg_ulong skip = skip_bits >> imp_phase;
+	sample_t const* imp = impulses [imp_phase];
+	int remain = res - imp_phase;
 	int const step = this->step;
 	
 	count >>= 1;
@@ -159,7 +159,7 @@
 		while ( in <= end_pos );
 	}
 	
-	this->imp = res - remain;
+	imp_phase = res - remain;
 	
 	int left = write_pos - in;
 	write_pos = &buf [left];
--- a/src/console/Gb_Cpu.cxx	Wed Dec 06 07:57:05 2006 -0800
+++ b/src/console/Gb_Cpu.cxx	Thu Dec 07 15:20:41 2006 -0800
@@ -1,4 +1,4 @@
-// Game_Music_Emu 0.5.1. http://www.slack.net/~ant/
+// Game_Music_Emu 0.5.2. http://www.slack.net/~ant/
 
 #include "Gb_Cpu.h"
 
@@ -96,6 +96,8 @@
 	this->state = &s;
 	memcpy( &s, &this->state_, sizeof s );
 	
+	typedef BOOST::uint16_t uint16_t;
+	
 #if BLARGG_BIG_ENDIAN
 	#define R8( n ) (r8_ [n]) 
 #elif BLARGG_LITTLE_ENDIAN
@@ -156,13 +158,14 @@
 	switch ( op )
 	{
 
-#define BRANCH( cond )          \
-{                               \
-	pc++;                       \
-	int offset = (BOOST::int8_t) data;  \
-	if ( !(cond) ) goto loop;   \
-	pc += offset;               \
-	goto loop;                  \
+// TODO: more efficient way to handle negative branch that wraps PC around
+#define BRANCH( cond )\
+{\
+	pc++;\
+	int offset = (BOOST::int8_t) data;\
+	if ( !(cond) ) goto loop;\
+	pc = uint16_t (pc + offset);\
+	goto loop;\
 }
 
 // Most Common
--- a/src/console/Gb_Cpu.h	Wed Dec 06 07:57:05 2006 -0800
+++ b/src/console/Gb_Cpu.h	Thu Dec 07 15:20:41 2006 -0800
@@ -1,7 +1,7 @@
 // Nintendo Game Boy CPU emulator
 // Treats every instruction as taking 4 cycles
 
-// Game_Music_Emu 0.5.1
+// Game_Music_Emu 0.5.2
 #ifndef GB_CPU_H
 #define GB_CPU_H
 
--- a/src/console/Gb_Oscs.cxx	Wed Dec 06 07:57:05 2006 -0800
+++ b/src/console/Gb_Oscs.cxx	Thu Dec 07 15:20:41 2006 -0800
@@ -129,11 +129,13 @@
 		playing = false;
 	}
 	
-	int delta = amp - last_amp;
-	if ( delta )
 	{
-		last_amp = amp;
-		synth->offset( time, delta, output );
+		int delta = amp - last_amp;
+		if ( delta )
+		{
+			last_amp = amp;
+			synth->offset( time, delta, output );
+		}
 	}
 	
 	time += delay;
@@ -173,13 +175,15 @@
 	if ( bits >> tap & 2 )
 		amp = -amp;
 	
-	int delta = amp - last_amp;
-	if ( delta )
 	{
-		last_amp = amp;
-		synth->offset( time, delta, output );
+		int delta = amp - last_amp;
+		if ( delta )
+		{
+			last_amp = amp;
+			synth->offset( time, delta, output );
+		}
 	}
-		
+	
 	time += delay;
 	if ( !playing )
 		time = end_time;
@@ -251,20 +255,23 @@
 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;
-	
-	int frequency = this->frequency();
-	if ( unsigned (frequency - 1) > 2044 ) // frequency < 1 || frequency > 2045
+	int frequency;
 	{
-		amp = 30 >> volume_shift & playing;
-		playing = false;
-	}
-	
-	int delta = amp - last_amp;
-	if ( delta )
-	{
-		last_amp = amp;
-		synth->offset( time, delta, output );
+		int amp = (wave [wave_pos] >> volume_shift & playing) * 2;
+		
+		frequency = this->frequency();
+		if ( unsigned (frequency - 1) > 2044 ) // frequency < 1 || frequency > 2045
+		{
+			amp = 30 >> volume_shift & playing;
+			playing = false;
+		}
+		
+		int delta = amp - last_amp;
+		if ( delta )
+		{
+			last_amp = amp;
+			synth->offset( time, delta, output );
+		}
 	}
 	
 	time += delay;
--- a/src/console/Gbs_Emu.cxx	Wed Dec 06 07:57:05 2006 -0800
+++ b/src/console/Gbs_Emu.cxx	Thu Dec 07 15:20:41 2006 -0800
@@ -1,4 +1,4 @@
-// Game_Music_Emu 0.5.1. http://www.slack.net/~ant/
+// Game_Music_Emu 0.5.2. http://www.slack.net/~ant/
 
 #include "Gbs_Emu.h"
 
@@ -81,7 +81,7 @@
 	
 	blargg_err_t load_( Data_Reader& in )
 	{
-		blargg_err_t err = in.read( &h, sizeof h );
+		blargg_err_t err = in.read( &h, Gbs_Emu::header_size );
 		if ( err )
 			return (err == in.eof_error ? gme_wrong_file_type : err);
 		
@@ -105,8 +105,8 @@
 
 blargg_err_t Gbs_Emu::load_( Data_Reader& in )
 {
-	unload();
-	RETURN_ERR( rom.load( in, sizeof header_, &header_, 0 ) );
+	assert( offsetof (header_t,copyright [32]) == header_size );
+	RETURN_ERR( rom.load( in, header_size, &header_, 0 ) );
 	
 	set_track_count( header_.track_count );
 	RETURN_ERR( check_gbs_header( &header_ ) );
@@ -259,6 +259,8 @@
 					cpu_time = next_play;
 				next_play += play_period;
 				cpu_jsr( get_le16( header_.play_addr ) );
+				GME_FRAME_HOOK( this );
+				// TODO: handle timer rates different than 60 Hz
 			}
 			else if ( cpu::r.pc > 0xFFFF )
 			{
--- a/src/console/Gbs_Emu.h	Wed Dec 06 07:57:05 2006 -0800
+++ b/src/console/Gbs_Emu.h	Thu Dec 07 15:20:41 2006 -0800
@@ -1,6 +1,6 @@
 // Nintendo Game Boy GBS music file emulator
 
-// Game_Music_Emu 0.5.1
+// Game_Music_Emu 0.5.2
 #ifndef GBS_EMU_H
 #define GBS_EMU_H
 
@@ -16,6 +16,7 @@
 	static equalizer_t const headphones_eq;
 	
 	// GBS file header
+	enum { header_size = 112 };
 	struct header_t
 	{
 		char tag [3];
@@ -32,7 +33,6 @@
 		char author [32];
 		char copyright [32];
 	};
-	BOOST_STATIC_ASSERT( sizeof (header_t) == 112 );
 	
 	// Header for currently loaded file
 	header_t const& header() const { return header_; }
--- a/src/console/Gme_File.cxx	Wed Dec 06 07:57:05 2006 -0800
+++ b/src/console/Gme_File.cxx	Thu Dec 07 15:20:41 2006 -0800
@@ -1,4 +1,4 @@
-// Game_Music_Emu 0.5.1. http://www.slack.net/~ant/
+// Game_Music_Emu 0.5.2. http://www.slack.net/~ant/
 
 #include "Gme_File.h"
 
@@ -37,12 +37,18 @@
 
 Gme_File::Gme_File()
 {
-	type_ = 0;
+	type_         = 0;
+	user_data_    = 0;
+	user_cleanup_ = 0;
 	unload(); // clears fields
 	blargg_verify_byte_order(); // used by most emulator types, so save them the trouble
 }
 
-Gme_File::~Gme_File() { }
+Gme_File::~Gme_File()
+{
+	if ( user_cleanup_ )
+		user_cleanup_( user_data_ );
+}
 
 blargg_err_t Gme_File::load_mem_( byte const* data, long size )
 {
@@ -145,7 +151,7 @@
 	copy_field_( out, in, max_field_ );
 }
 
-blargg_err_t Gme_File::remap_track( int* track_io ) const
+blargg_err_t Gme_File::remap_track_( int* track_io ) const
 {
 	if ( (unsigned) *track_io >= (unsigned) track_count() )
 		return "Invalid track";
@@ -188,7 +194,7 @@
 	copy_field_( out->system, type()->system );
 	
 	int remapped = track;
-	RETURN_ERR( remap_track( &remapped ) );
+	RETURN_ERR( remap_track_( &remapped ) );
 	RETURN_ERR( track_info_( out, remapped ) );
 	
 	// override with m3u info
--- a/src/console/Gme_File.h	Wed Dec 06 07:57:05 2006 -0800
+++ b/src/console/Gme_File.h	Thu Dec 07 15:20:41 2006 -0800
@@ -1,6 +1,6 @@
 // Common interface to game music file loading and information
 
-// Game_Music_Emu 0.5.1
+// Game_Music_Emu 0.5.2
 #ifndef GME_FILE_H
 #define GME_FILE_H
 
@@ -55,6 +55,17 @@
 	// See gme.h for definition of struct track_info_t.
 	blargg_err_t track_info( track_info_t* out, int track ) const;
 	
+// User data/cleanup
+	
+	// Set/get pointer to data you want to associate with this emulator.
+	// You can use this for whatever you want.
+	void set_user_data( void* p )       { user_data_ = p; }
+	void* user_data() const             { return user_data_; }
+	
+	// Register cleanup function to be called when deleting emulator, or NULL to
+	// clear it. Passes user_data to cleanup function.
+	void set_user_cleanup( gme_user_cleanup_t func ) { user_cleanup_ = func; }
+	
 public:
 	// deprecated
 	int error_count() const; // use warning()
@@ -79,8 +90,8 @@
 	virtual void post_load_();
 	virtual void clear_playlist_() { }
 	
-protected:
-	blargg_err_t remap_track( int* track_io ) const; // need by Music_Emu
+public:
+	blargg_err_t remap_track_( int* track_io ) const; // need by Music_Emu
 private:
 	// noncopyable
 	Gme_File( const Gme_File& );
@@ -90,7 +101,10 @@
 	int track_count_;
 	int raw_track_count_;
 	const char* warning_;
+	void* user_data_;
+	gme_user_cleanup_t user_cleanup_;
 	M3u_Playlist playlist;
+	char playlist_warning [64];
 	blargg_vector<byte> file_data; // only if loaded into memory using default load
 	
 	blargg_err_t load_m3u_( blargg_err_t );
@@ -113,6 +127,8 @@
 	#else
 		#define GME_FILE_READER Std_File_Reader
 	#endif
+#elif defined (GME_FILE_READER_INCLUDE)
+	#include GME_FILE_READER_INCLUDE
 #endif
 
 inline gme_type_t Gme_File::type() const            { return type_; }
--- a/src/console/Gym_Emu.cxx	Wed Dec 06 07:57:05 2006 -0800
+++ b/src/console/Gym_Emu.cxx	Thu Dec 07 15:20:41 2006 -0800
@@ -1,4 +1,4 @@
-// Game_Music_Emu 0.5.1. http://www.slack.net/~ant/
+// Game_Music_Emu 0.5.2. http://www.slack.net/~ant/
 
 #include "Gym_Emu.h"
 
@@ -117,16 +117,16 @@
 	
 	if ( memcmp( in, "GYMX", 4 ) == 0 )
 	{
-		if ( size < (long) sizeof (Gym_Emu::header_t) + 1 )
+		if ( size < Gym_Emu::header_size + 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);
+			*data_offset = Gym_Emu::header_size;
 	}
-	else if ( *in != 0 && *in != 1 )
+	else if ( *in > 3 )
 	{
 		return gme_wrong_file_type;
 	}
@@ -209,6 +209,7 @@
 
 blargg_err_t Gym_Emu::load_mem_( byte const* in, long size )
 {
+	assert( offsetof (header_t,packed [4]) == header_size );
 	int offset = 0;
 	RETURN_ERR( check_header( in, size, &offset ) );
 	set_voice_count( 8 );
--- a/src/console/Gym_Emu.h	Wed Dec 06 07:57:05 2006 -0800
+++ b/src/console/Gym_Emu.h	Thu Dec 07 15:20:41 2006 -0800
@@ -1,7 +1,7 @@
 // Sega Genesis/Mega Drive GYM music file emulator
 // Includes with PCM timing recovery to improve sample quality.
 
-// Game_Music_Emu 0.5.1
+// Game_Music_Emu 0.5.2
 #ifndef GYM_EMU_H
 #define GYM_EMU_H
 
@@ -13,6 +13,7 @@
 class Gym_Emu : public Music_Emu, private Dual_Resampler {
 public:
 	// GYM file header
+	enum { header_size = 428 };
 	struct header_t
 	{
 	    char tag [4];
@@ -25,7 +26,6 @@
 	    byte loop_start [4]; // in 1/60 seconds, 0 if not looped
 	    byte packed [4];
 	};
-	BOOST_STATIC_ASSERT( sizeof (header_t) == 428 );
 	
 	// Header for currently loaded file
 	header_t const& header() const { return header_; }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/console/Gzip_Reader.cxx	Thu Dec 07 15:20:41 2006 -0800
@@ -0,0 +1,108 @@
+// File_Extractor 0.4.0. http://www.slack.net/~ant/
+
+#include "Gzip_Reader.h"
+
+#include "blargg_endian.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"
+
+void Gzip_Reader::close()
+{
+	in    = 0;
+	tell_ = 0;
+	size_ = 0;
+	inflater.end();
+}
+
+Gzip_Reader::Gzip_Reader() { close(); }
+
+Gzip_Reader::~Gzip_Reader() { }
+
+static blargg_err_t gzip_reader_read( void* file, void* out, long* count )
+{
+	*count = ((File_Reader*) file)->read_avail( out, *count );
+	return (*count < 0 ? "Read error" : 0);
+}
+
+blargg_err_t Gzip_Reader::open( File_Reader* new_in )
+{
+	close();
+	
+	RETURN_ERR( inflater.begin( inflater.mode_auto, gzip_reader_read, new_in ) );
+	
+	size_ = -1; // defer seeking to end of file until size is actually needed
+	in    = new_in;
+	return 0;
+}
+
+blargg_err_t Gzip_Reader::calc_size()
+{
+	long size = in->size();
+	if ( inflater.deflated() )
+	{
+		byte trailer [4];
+		long pos = in->tell();
+		RETURN_ERR( in->seek( size - sizeof trailer ) );
+		RETURN_ERR( in->read( trailer, sizeof trailer ) );
+		RETURN_ERR( in->seek( pos ) );
+		size = get_le32( trailer );
+	}
+	size_ = size;
+	return 0;
+}
+
+long Gzip_Reader::remain() const
+{
+	if ( size_ < 0 )
+	{
+		if ( !in )
+			return 0;
+		
+		// need to cast away constness to change cached value
+		if ( ((Gzip_Reader*) this)->calc_size() )
+			return -1;
+	}
+	return size_ - tell_;
+}
+
+blargg_err_t Gzip_Reader::read_( void* out, long* count )
+{
+	blargg_err_t err = inflater.read( out, count, gzip_reader_read, in );
+	tell_ += *count;
+	if ( size_ >= 0 && tell_ > size_ )
+	{
+		tell_ = size_;
+		return "Corrupt gzip file";
+	}
+	return err;
+}
+
+blargg_err_t Gzip_Reader::read( void* out, long count )
+{
+	if ( in )
+	{
+		long actual = count;
+		RETURN_ERR( read_( out, &actual ) );
+		if ( actual == count )
+			return 0;
+	}
+	return eof_error;
+}
+
+long Gzip_Reader::read_avail( void* out, long count )
+{
+	if ( !in || read_( out, &count ) )
+		count = -1;
+	return count;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/console/Gzip_Reader.h	Thu Dec 07 15:20:41 2006 -0800
@@ -0,0 +1,31 @@
+// Transparently decompresses gzip files, as well as uncompressed
+
+// File_Extractor 0.4.0
+#ifndef GZIP_READER_H
+#define GZIP_READER_H
+
+#include "Data_Reader.h"
+#include "Zlib_Inflater.h"
+
+class Gzip_Reader : public Data_Reader {
+public:
+	error_t open( File_Reader* );
+	void close();
+	
+public:
+	Gzip_Reader();
+	~Gzip_Reader();
+	long remain() const;
+	error_t read( void*, long );
+	long read_avail( void*, long );
+private:
+	File_Reader* in;
+	long tell_;
+	long size_;
+	Zlib_Inflater inflater;
+	
+	error_t calc_size();
+	blargg_err_t read_( void* out, long* count );
+};
+
+#endif
--- a/src/console/Hes_Apu.cxx	Wed Dec 06 07:57:05 2006 -0800
+++ b/src/console/Hes_Apu.cxx	Thu Dec 07 15:20:41 2006 -0800
@@ -1,4 +1,4 @@
-// Game_Music_Emu 0.5.1. http://www.slack.net/~ant/
+// Game_Music_Emu 0.5.2. http://www.slack.net/~ant/
 
 #include "Hes_Apu.h"
 
--- a/src/console/Hes_Cpu.cxx	Wed Dec 06 07:57:05 2006 -0800
+++ b/src/console/Hes_Cpu.cxx	Thu Dec 07 15:20:41 2006 -0800
@@ -1,4 +1,4 @@
-// Game_Music_Emu 0.5.1. http://www.slack.net/~ant/
+// Game_Music_Emu 0.5.2. http://www.slack.net/~ant/
 
 #include "Hes_Cpu.h"
 
@@ -227,12 +227,13 @@
 
 // Branch
 
+// TODO: more efficient way to handle negative branch that wraps PC around
 #define BRANCH( cond )\
 {\
 	fint16 offset = (BOOST::int8_t) data;\
 	pc++;\
 	if ( !(cond) ) goto branch_not_taken;\
-	pc += offset;\
+	pc = BOOST::uint16_t (pc + offset);\
 	goto loop;\
 }
 
--- a/src/console/Hes_Cpu.h	Wed Dec 06 07:57:05 2006 -0800
+++ b/src/console/Hes_Cpu.h	Thu Dec 07 15:20:41 2006 -0800
@@ -1,6 +1,6 @@
 // PC Engine CPU emulator for use with HES music files
 
-// Game_Music_Emu 0.5.1
+// Game_Music_Emu 0.5.2
 #ifndef HES_CPU_H
 #define HES_CPU_H
 
--- a/src/console/Hes_Emu.cxx	Wed Dec 06 07:57:05 2006 -0800
+++ b/src/console/Hes_Emu.cxx	Thu Dec 07 15:20:41 2006 -0800
@@ -1,4 +1,4 @@
-// Game_Music_Emu 0.5.1. http://www.slack.net/~ant/
+// Game_Music_Emu 0.5.2. http://www.slack.net/~ant/
 
 #include "Hes_Emu.h"
 
@@ -105,8 +105,8 @@
 
 struct Hes_File : Gme_Info_
 {
-	struct {
-		Hes_Emu::header_t h;
+	struct header_t {
+		char header [Hes_Emu::header_size];
 		char unused [0x20];
 		byte fields [0x30 * 3];
 	} h;
@@ -115,6 +115,7 @@
 	
 	blargg_err_t load_( Data_Reader& in )
 	{
+		assert( offsetof (header_t,fields) == Hes_Emu::header_size + 0x20 );
 		blargg_err_t err = in.read( &h, sizeof h );
 		if ( err )
 			return (err == in.eof_error ? gme_wrong_file_type : err);
@@ -137,7 +138,8 @@
 
 blargg_err_t Hes_Emu::load_( Data_Reader& in )
 {
-	RETURN_ERR( rom.load( in, sizeof header_, &header_, unmapped ) );
+	assert( offsetof (header_t,unused [4]) == header_size );
+	RETURN_ERR( rom.load( in, header_size, &header_, unmapped ) );
 	
 	RETURN_ERR( check_hes_header( header_.tag ) );
 	
@@ -243,6 +245,7 @@
 	r.a  = track;
 	
 	recalc_timer_load();
+	last_frame_hook = 0;
 	
 	return 0;
 }
@@ -282,6 +285,7 @@
 {
 	if ( unsigned (addr - apu.start_addr) <= apu.end_addr - apu.start_addr )
 	{
+		GME_APU_HOOK( this, addr - apu.start_addr, data );
 		// 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 );
@@ -458,6 +462,17 @@
 			timer.fired = true;
 			irq.timer = future_hes_time;
 			irq_changed(); // overkill, but not worth writing custom code
+			#if GME_FRAME_HOOK_DEFINED
+			{
+				unsigned const threshold = period_60hz / 30;
+				unsigned long elapsed = present - last_frame_hook;
+				if ( elapsed - period_60hz + threshold / 2 < threshold )
+				{
+					last_frame_hook = present;
+					GME_FRAME_HOOK( this );
+				}
+			}
+			#endif
 			return 0x0A;
 		}
 		
@@ -467,6 +482,10 @@
 			//run_until( present );
 			//irq.vdp = future_hes_time;
 			//irq_changed();
+			#if GME_FRAME_HOOK_DEFINED
+				last_frame_hook = present;
+				GME_FRAME_HOOK( this );
+			#endif
 			return 0x08;
 		}
 	}
@@ -498,6 +517,9 @@
 	// end time frame
 	timer.last_time -= duration;
 	vdp.next_vbl    -= duration;
+	#if GME_FRAME_HOOK_DEFINED
+		last_frame_hook -= duration;
+	#endif
 	cpu::end_frame( duration );
 	::adjust_time( irq.timer, duration );
 	::adjust_time( irq.vdp,   duration );
--- a/src/console/Hes_Emu.h	Wed Dec 06 07:57:05 2006 -0800
+++ b/src/console/Hes_Emu.h	Thu Dec 07 15:20:41 2006 -0800
@@ -1,6 +1,6 @@
 // TurboGrafx-16/PC Engine HES music file emulator
 
-// Game_Music_Emu 0.5.1
+// Game_Music_Emu 0.5.2
 #ifndef HES_EMU_H
 #define HES_EMU_H
 
@@ -12,6 +12,7 @@
 	typedef Hes_Cpu cpu;
 public:
 	// HES file header
+	enum { header_size = 0x20 };
 	struct header_t
 	{
 		byte tag [4];
@@ -24,7 +25,6 @@
 		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_; }
@@ -57,6 +57,7 @@
 	Rom_Data<page_size> rom;
 	header_t header_;
 	hes_time_t play_period;
+	hes_time_t last_frame_hook;
 	int timer_base;
 	
 	struct {
--- a/src/console/Kss_Cpu.cxx	Wed Dec 06 07:57:05 2006 -0800
+++ b/src/console/Kss_Cpu.cxx	Thu Dec 07 15:20:41 2006 -0800
@@ -1,8 +1,10 @@
-// Game_Music_Emu 0.5.1. http://www.slack.net/~ant/
+// Game_Music_Emu 0.5.2. 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.
+/*
+Last validated with zexall 2006.11.14 2:19 PM
+* Doesn't implement the R register or immediate interrupt after EI.
+* Address wrap-around isn't completely correct, but is prevented from crashing emulator.
+*/
 
 #include "Kss_Cpu.h"
 
@@ -27,13 +29,13 @@
 
 // Callbacks to emulator
 
-#define CPU_OUT( cpu, addr, data, time ) \
+#define CPU_OUT( cpu, addr, data, time )\
 	kss_cpu_out( this, time, addr, data )
 
-#define CPU_IN( cpu, addr, time ) \
+#define CPU_IN( cpu, addr, time )\
 	kss_cpu_in( this, time, addr )
 
-#define CPU_WRITE( cpu, addr, data, time ) \
+#define CPU_WRITE( cpu, addr, data, time )\
 	(SYNC_TIME(), kss_cpu_write( this, addr, data ))
 
 #include "blargg_source.h"
@@ -59,7 +61,7 @@
 
 Kss_Cpu::Kss_Cpu()
 {
-	state        = &state_;
+	state = &state_;
 	
 	for ( int i = 0x100; --i >= 0; )
 	{
@@ -130,8 +132,8 @@
 
 //#define R16( n, shift, offset )   (r16_ [((n) >> shift) - (offset >> shift)])
 
-// help compiler see that it can adjust stack offset
-#define R16( n, shift, offset ) \
+// 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
@@ -311,12 +313,13 @@
 #define MINUS   (flags & S80)
 
 // JR
+// TODO: more efficient way to handle negative branch that wraps PC around
 #define JR( cond ) {\
-	int disp = (BOOST::int8_t) data;\
+	int offset = (BOOST::int8_t) data;\
 	pc++;\
 	if ( !(cond) )\
 		goto jr_not_taken;\
-	pc += disp;\
+	pc = uint16_t (pc + offset);\
 	goto loop;\
 }
 	
@@ -1340,6 +1343,10 @@
 		s_time += ed_dd_timing [data] & 0x0F;
 		switch ( data )
 		{
+	// TODO: more efficient way of avoid negative address
+	// TODO: avoid using this as argument to READ() since it is evaluated twice
+	#define IXY_DISP( ixy, disp )   uint16_t ((ixy) + (disp))
+	
 	#define SET_IXY( in ) if ( opcode == 0xDD ) ix = in; else iy = in;
 	
 	// ADD/ADC/SUB/SBC
@@ -1351,7 +1358,7 @@
 		case 0x8E: // ADC (IXY+disp)
 			pc++;
 			opcode = data;
-			data = READ( ixy + (int8_t) data2 );
+			data = READ( IXY_DISP( ixy, (int8_t) data2 ) );
 			goto adc_data;
 		
 		case 0x94: // SUB HXY
@@ -1373,26 +1380,26 @@
 			goto adc_data;
 		
 		{
-			unsigned data2;
+			unsigned temp;
 		case 0x39: // ADD IXY,SP
-			data2 = sp;
+			temp = sp;
 			goto add_ixy_data;
 		
 		case 0x29: // ADD IXY,HL
-			data2 = ixy;
+			temp = ixy;
 			goto add_ixy_data;
 		
 		case 0x09: // ADD IXY,BC
 		case 0x19: // ADD IXY,DE
-			data2 = R16( data, 4, 0x09 );
+			temp = R16( data, 4, 0x09 );
 		add_ixy_data: {
-			blargg_ulong sum = ixy + data2;
-			data2 ^= ixy;
+			blargg_ulong sum = ixy + temp;
+			temp ^= ixy;
 			ixy = (uint16_t) sum;
 			flags = (flags & (S80 | Z40 | V04)) |
 					(sum >> 16) |
 					(sum >> 8 & (F20 | F08)) |
-					((data2 ^ sum) >> 8 & H10);
+					((temp ^ sum) >> 8 & H10);
 			goto set_ixy;
 		}
 		}
@@ -1400,7 +1407,7 @@
 	// AND
 		case 0xA6: // AND (IXY+disp)
 			pc++;
-			data = READ( ixy + (int8_t) data2 );
+			data = READ( IXY_DISP( ixy, (int8_t) data2 ) );
 			goto and_data;
 		
 		case 0xA4: // AND HXY
@@ -1414,7 +1421,7 @@
 	// OR
 		case 0xB6: // OR (IXY+disp)
 			pc++;
-			data = READ( ixy + (int8_t) data2 );
+			data = READ( IXY_DISP( ixy, (int8_t) data2 ) );
 			goto or_data;
 		
 		case 0xB4: // OR HXY
@@ -1428,7 +1435,7 @@
 	// XOR
 		case 0xAE: // XOR (IXY+disp)
 			pc++;
-			data = READ( ixy + (int8_t) data2 );
+			data = READ( IXY_DISP( ixy, (int8_t) data2 ) );
 			goto xor_data;
 		
 		case 0xAC: // XOR HXY
@@ -1442,7 +1449,7 @@
 	// CP
 		case 0xBE: // CP (IXY+disp)
 			pc++;
-			data = READ( ixy + (int8_t) data2  );
+			data = READ( IXY_DISP( ixy, (int8_t) data2 )  );
 			goto cp_data;
 		
 		case 0xBC: // CP HXY
@@ -1460,7 +1467,7 @@
 		case 0x36: // LD (IXY+disp),imm
 				pc++, data = READ_PROG( pc );
 			pc++;
-			WRITE( ixy + (int8_t) data2, data );
+			WRITE( IXY_DISP( ixy, (int8_t) data2 ), data );
 			goto loop;
 
 		CASE5( 44, 4C, 54, 5C, 7C ): // LD r,HXY
@@ -1477,7 +1484,7 @@
 		
 		CASE7( 46, 4E, 56, 5E, 66, 6E, 7E ): // LD r,(IXY+disp)
 			pc++;
-			R8( data >> 3, 8 ) = READ( ixy + (int8_t) data2 );
+			R8( data >> 3, 8 ) = READ( IXY_DISP( ixy, (int8_t) data2 ) );
 			goto loop;
 		
 		case 0x26: // LD HXY,imm
@@ -1540,7 +1547,7 @@
 		
 	// DD/FD CB prefix
 		case 0xCB: {
-			data = ixy + (int8_t) data2;
+			data = IXY_DISP( ixy, (int8_t) data2 );
 			pc++;
 			data2 = READ_PROG( pc );
 			pc++;
@@ -1593,14 +1600,14 @@
 			goto set_ixy;
 		
 		case 0x34: // INC (IXY+disp)
-			ixy += (int8_t) data2;
+			ixy = 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;
+			ixy = IXY_DISP( ixy, (int8_t) data2 );
 			pc++;
 			data = READ( ixy ) - 1;
 			WRITE( ixy, data );
--- a/src/console/Kss_Cpu.h	Wed Dec 06 07:57:05 2006 -0800
+++ b/src/console/Kss_Cpu.h	Thu Dec 07 15:20:41 2006 -0800
@@ -1,6 +1,6 @@
 // Z80 CPU emulator
 
-// Game_Music_Emu 0.5.1
+// Game_Music_Emu 0.5.2
 #ifndef KSS_CPU_H
 #define KSS_CPU_H
 
--- a/src/console/Kss_Emu.cxx	Wed Dec 06 07:57:05 2006 -0800
+++ b/src/console/Kss_Emu.cxx	Thu Dec 07 15:20:41 2006 -0800
@@ -1,4 +1,4 @@
-// Game_Music_Emu 0.5.1. http://www.slack.net/~ant/
+// Game_Music_Emu 0.5.2. http://www.slack.net/~ant/
 
 #include "Kss_Emu.h"
 
@@ -37,6 +37,8 @@
 		wave_type | 3, wave_type | 4, wave_type | 5, wave_type | 6, wave_type | 7
 	};
 	set_voice_types( types );
+	
+	memset( unmapped_read, 0xFF, sizeof unmapped_read );
 }
 
 Kss_Emu::~Kss_Emu() { unload(); }
@@ -54,7 +56,11 @@
 {
 	const char* system = "MSX";
 	if ( h.device_flags & 0x02 )
+	{
 		system = "Sega Master System";
+		if ( h.device_flags & 0x04 )
+			system = "Game Gear";
+	}
 	Gme_File::copy_field_( out->system, system );
 }
 
@@ -79,10 +85,9 @@
 	
 	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_ ) );
+		blargg_err_t err = in.read( &header_, Kss_Emu::header_size );
+		if ( err )
+			return (err == in.eof_error ? gme_wrong_file_type : err);
 		return check_kss_header( &header_ );
 	}
 	
@@ -114,7 +119,9 @@
 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 ) );
+	assert( offsetof (header_t,device_flags) == header_size - 1 );
+	assert( offsetof (ext_header_t,msx_audio_vol) == ext_header_size - 1 );
+	RETURN_ERR( rom.load( in, header_size, STATIC_CAST(header_t*,&header_), 0 ) );
 	
 	RETURN_ERR( check_kss_header( header_.tag ) );
 	
@@ -134,7 +141,7 @@
 	else
 	{
 		ext_header_t& ext = header_;
-		memcpy( &ext, rom.begin(), min( (int) sizeof ext, (int) header_.extra_header ) );
+		memcpy( &ext, rom.begin(), min( (int) ext_header_size, (int) header_.extra_header ) );
 		if ( header_.extra_header > 0x10 )
 			set_warning( "Unknown data in header" );
 	}
@@ -226,7 +233,7 @@
 	//dprintf( "bank_count: %d (%d claimed)\n", bank_count, header_.bank_mode & 0x7F );
 	
 	ram [idle_addr] = 0xFF;
-	cpu::reset( ram, ram );
+	cpu::reset( unmapped_write, unmapped_read );
 	cpu::map_mem( 0, mem_size, ram, ram );
 	
 	ay.reset();
@@ -242,6 +249,7 @@
 	scc_accessed = false;
 	gain_updated = false;
 	update_gain();
+	ay_latch = 0;
 	
 	return 0;
 }
@@ -265,7 +273,7 @@
 		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 ) );
+					unmapped_write, rom.at_addr( phys + offset ) );
 	}
 }
 
@@ -312,6 +320,7 @@
 		return;
 	
 	case 0xA1:
+		GME_APU_HOOK( &emu, emu.ay_latch, data );
 		emu.ay.write( time, emu.ay_latch, data );
 		return;
 	
@@ -327,6 +336,7 @@
 	case 0x7F:
 		if ( emu.sn )
 		{
+			GME_APU_HOOK( &emu, 16, data );
 			emu.sn->write_data( time, data );
 			return;
 		}
@@ -386,6 +396,7 @@
 				ram [--r.sp] = idle_addr >> 8;
 				ram [--r.sp] = idle_addr & 0xFF;
 				r.pc = get_le16( header_.play_addr );
+				GME_FRAME_HOOK( this );
 			}
 		}
 	}
--- a/src/console/Kss_Emu.h	Wed Dec 06 07:57:05 2006 -0800
+++ b/src/console/Kss_Emu.h	Thu Dec 07 15:20:41 2006 -0800
@@ -1,6 +1,6 @@
 // MSX computer KSS music file emulator
 
-// Game_Music_Emu 0.5.1
+// Game_Music_Emu 0.5.2
 #ifndef KSS_EMU_H
 #define KSS_EMU_H
 
@@ -14,6 +14,7 @@
 	typedef Kss_Cpu cpu;
 public:
 	// KSS file header
+	enum { header_size = 0x10 };
 	struct header_t
 	{
 		byte tag [4];
@@ -26,8 +27,8 @@
 		byte extra_header;
 		byte device_flags;
 	};
-	BOOST_STATIC_ASSERT( sizeof (header_t) == 0x10 );
 	
+	enum { ext_header_size = 0x10 };
 	struct ext_header_t
 	{
 		byte data_size [4];
@@ -39,7 +40,6 @@
 		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 { };
 	
@@ -62,7 +62,6 @@
 private:
 	Rom_Data<page_size> rom;
 	composite_header_t header_;
-	byte* unmapped_write() { return rom.unmapped(); }
 	
 	bool scc_accessed;
 	bool gain_updated;
@@ -90,6 +89,8 @@
 	Ay_Apu ay;
 	Scc_Apu scc;
 	Sms_Apu* sn;
+	byte unmapped_read  [0x100];
+	byte unmapped_write [page_size];
 };
 
 #endif
--- a/src/console/Kss_Scc_Apu.cxx	Wed Dec 06 07:57:05 2006 -0800
+++ b/src/console/Kss_Scc_Apu.cxx	Thu Dec 07 15:20:41 2006 -0800
@@ -1,4 +1,4 @@
-// Game_Music_Emu 0.5.1. http://www.slack.net/~ant/
+// Game_Music_Emu 0.5.2. http://www.slack.net/~ant/
 
 #include "Kss_Scc_Apu.h"
 
@@ -46,12 +46,14 @@
 		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 );
+			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;
--- a/src/console/Kss_Scc_Apu.h	Wed Dec 06 07:57:05 2006 -0800
+++ b/src/console/Kss_Scc_Apu.h	Thu Dec 07 15:20:41 2006 -0800
@@ -1,6 +1,6 @@
 // Konami SCC sound chip emulator
 
-// Game_Music_Emu 0.5.1
+// Game_Music_Emu 0.5.2
 #ifndef KSS_SCC_APU_H
 #define KSS_SCC_APU_H
 
@@ -97,13 +97,8 @@
 {
 	last_time = 0;
 	
-	osc_t* osc = &oscs [osc_count];
-	do
-	{
-		osc--;
-		memset( osc, 0, offsetof (osc_t,output) );
-	}
-	while ( osc != oscs );
+	for ( int i = 0; i < osc_count; i++ )
+		memset( &oscs [i], 0, offsetof (osc_t,output) );
 	
 	memset( regs, 0, sizeof regs );
 }
--- a/src/console/M3u_Playlist.cxx	Wed Dec 06 07:57:05 2006 -0800
+++ b/src/console/M3u_Playlist.cxx	Thu Dec 07 15:20:41 2006 -0800
@@ -1,4 +1,4 @@
-// Game_Music_Emu 0.5.1. http://www.slack.net/~ant/
+// Game_Music_Emu 0.5.2. http://www.slack.net/~ant/
 
 #include "M3u_Playlist.h"
 #include "Music_Emu.h"
@@ -22,9 +22,29 @@
 
 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
+	
+	if ( !err )
+	{
+		if ( playlist.size() )
+			track_count_ = playlist.size();
+		
+		int line = playlist.first_error();
+		if ( line )
+		{
+			// avoid using bloated printf()
+			char* out = &playlist_warning [sizeof playlist_warning];
+			*--out = 0;
+			do {
+				*--out = line % 10 + '0';
+			} while ( (line /= 10) > 0 );
+			
+			static const char str [] = "Problem in m3u at line ";
+			out -= sizeof str - 1;
+			memcpy( out, str, sizeof str - 1 );
+			set_warning( out );
+		}
+	}
 	return err;
 }
 
@@ -32,9 +52,9 @@
 
 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 ); }
+gme_err_t 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 )
+gme_err_t gme_load_m3u_data( Music_Emu* me, const void* data, long size )
 {
 	Mem_File_Reader in( data, size );
 	return me->load_m3u( in );
--- a/src/console/M3u_Playlist.h	Wed Dec 06 07:57:05 2006 -0800
+++ b/src/console/M3u_Playlist.h	Thu Dec 07 15:20:41 2006 -0800
@@ -1,6 +1,6 @@
 // M3U playlist file parser, with support for subtrack information
 
-// Game_Music_Emu 0.5.1
+// Game_Music_Emu 0.5.2
 #ifndef M3U_PLAYLIST_H
 #define M3U_PLAYLIST_H
 
--- a/src/console/Makefile	Wed Dec 06 07:57:05 2006 -0800
+++ b/src/console/Makefile	Thu Dec 07 15:20:41 2006 -0800
@@ -23,8 +23,8 @@
 	Gb_Oscs.cxx             \
 	gme.cxx                 \
 	Gme_File.cxx            \
-	gme_type_list.cxx       \
 	Gym_Emu.cxx             \
+	Gzip_Reader.cxx         \
 	Hes_Apu.cxx             \
 	Hes_Cpu.cxx             \
 	Hes_Emu.cxx             \
@@ -54,7 +54,8 @@
 	Vgm_Emu.cxx             \
 	Vgm_Emu_Impl.cxx        \
 	Ym2413_Emu.cxx          \
-	Ym2612_Emu.cxx
+	Ym2612_Emu.cxx          \
+	Zlib_Inflater.cxx
 
 OBJECTS = ${SOURCES:.cxx=.o}
 
--- a/src/console/Music_Emu.cxx	Wed Dec 06 07:57:05 2006 -0800
+++ b/src/console/Music_Emu.cxx	Thu Dec 07 15:20:41 2006 -0800
@@ -1,4 +1,4 @@
-// Game_Music_Emu 0.5.1. http://www.slack.net/~ant/
+// Game_Music_Emu 0.5.2. http://www.slack.net/~ant/
 
 #include "Music_Emu.h"
 
@@ -134,7 +134,7 @@
 	clear_track_vars();
 	
 	int remapped = track;
-	RETURN_ERR( remap_track( &remapped ) );
+	RETURN_ERR( remap_track_( &remapped ) );
 	current_track_ = track;
 	RETURN_ERR( start_track_( remapped ) );
 	
--- a/src/console/Music_Emu.h	Wed Dec 06 07:57:05 2006 -0800
+++ b/src/console/Music_Emu.h	Thu Dec 07 15:20:41 2006 -0800
@@ -1,10 +1,9 @@
 // Common interface to game music file emulators
 
-// Game_Music_Emu 0.5.1
+// Game_Music_Emu 0.5.2
 #ifndef MUSIC_EMU_H
 #define MUSIC_EMU_H
 
-#include "blargg_common.h"
 #include "Gme_File.h"
 class Multi_Buffer;
 
--- a/src/console/Nes_Cpu.cxx	Wed Dec 06 07:57:05 2006 -0800
+++ b/src/console/Nes_Cpu.cxx	Thu Dec 07 15:20:41 2006 -0800
@@ -1,4 +1,4 @@
-// Game_Music_Emu 0.5.1. http://www.slack.net/~ant/
+// Game_Music_Emu 0.5.2. http://www.slack.net/~ant/
 
 #include "Nes_Cpu.h"
 
@@ -22,8 +22,8 @@
 	#include BLARGG_ENABLE_OPTIMIZER
 #endif
 
-#define SYNC_TIME()     (void) (s.time = s_time)
-#define RELOAD_TIME()   (void) (s_time = s.time)
+#define FLUSH_TIME()    (void) (s.time = s_time)
+#define CACHE_TIME()    (void) (s_time = s.time)
 
 #include "nes_cpu_io.h"
 
@@ -36,9 +36,9 @@
 #ifndef CPU_READ_PPU
 	#define CPU_READ_PPU( cpu, addr, out, time )\
 	{\
-		SYNC_TIME();\
+		FLUSH_TIME();\
 		out = CPU_READ( cpu, addr, time );\
-		RELOAD_TIME();\
+		CACHE_TIME();\
 	}
 #endif
 
@@ -138,18 +138,18 @@
 	// 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;        \
+	#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;                       \
+	#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;
@@ -166,6 +166,7 @@
 loop:
 	
 	check( (unsigned) GET_SP() < 0x100 );
+	check( (unsigned) pc < 0x10000 );
 	check( (unsigned) a < 0x100 );
 	check( (unsigned) x < 0x100 );
 	check( (unsigned) y < 0x100 );
@@ -237,7 +238,7 @@
 // Macros
 
 #define GET_MSB()   (instr [1])
-#define ADD_PAGE    (pc++, data += 0x100 * GET_MSB());
+#define ADD_PAGE()  (pc++, data += 0x100 * GET_MSB())
 #define GET_ADDR()  GET_LE16( instr )
 
 #define NO_PAGE_CROSSING( lsb )
@@ -245,53 +246,54 @@
 
 #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_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 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 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:\
+	FLUSH_TIME();\
+	data = READ( data );\
+	CACHE_TIME();\
+case op + 0x04: /* imm */\
+imm##op:
 
-#define BRANCH( cond )                          \
-{                                               \
-	fint16 offset = (BOOST::int8_t) data;       \
+// TODO: more efficient way to handle negative branch that wraps PC around
+#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;                                  \
+	if ( !(cond) ) goto dec_clock_loop;\
+	pc = BOOST::uint16_t (pc + offset);\
+	s_time += extra_clock >> 8 & 1;\
+	goto loop;\
 }
 
 // Often-Used
@@ -407,9 +409,9 @@
 			goto loop;
 		}
 	sta_ptr:
-		SYNC_TIME();
+		FLUSH_TIME();
 		WRITE( addr, a );
-		RELOAD_TIME();
+		CACHE_TIME();
 		goto loop;
 		
 	case 0x91: // STA (ind),Y
@@ -466,9 +468,9 @@
 		if ( (addr ^ 0x8000) <= 0x9FFF )
 			goto loop;
 	a_nz_read_addr:
-		SYNC_TIME();
+		FLUSH_TIME();
 		a = nz = READ( addr );
-		RELOAD_TIME();
+		CACHE_TIME();
 		goto loop;
 	
 	}
@@ -529,9 +531,9 @@
 	case 0xAC:{// LDY abs
 		unsigned addr = data + 0x100 * GET_MSB();
 		pc += 2;
-		SYNC_TIME();
+		FLUSH_TIME();
 		y = nz = READ( addr );
-		RELOAD_TIME();
+		CACHE_TIME();
 		goto loop;
 	}
 	
@@ -541,9 +543,9 @@
 	case 0xAE:{// LDX abs
 		unsigned addr = data + 0x100 * GET_MSB();
 		pc += 2;
-		SYNC_TIME();
+		FLUSH_TIME();
 		x = nz = READ( addr );
-		RELOAD_TIME();
+		CACHE_TIME();
 		goto loop;
 	}
 	
@@ -563,9 +565,9 @@
 			WRITE_LOW( addr, temp );
 			goto loop;
 		}
-		SYNC_TIME();
+		FLUSH_TIME();
 		WRITE( addr, temp );
-		RELOAD_TIME();
+		CACHE_TIME();
 		goto loop;
 	}
 
@@ -574,9 +576,9 @@
 	case 0xEC:{// CPX abs
 		unsigned addr = GET_ADDR();
 		pc++;
-		SYNC_TIME();
+		FLUSH_TIME();
 		data = READ( addr );
-		RELOAD_TIME();
+		CACHE_TIME();
 		goto cpx_data;
 	}
 	
@@ -593,9 +595,9 @@
 	case 0xCC:{// CPY abs
 		unsigned addr = GET_ADDR();
 		pc++;
-		SYNC_TIME();
+		FLUSH_TIME();
 		data = READ( addr );
-		RELOAD_TIME();
+		CACHE_TIME();
 		goto cpy_data;
 	}
 	
@@ -699,8 +701,8 @@
 		c = 0;
 	case 0x6E: // ROR abs
 	ror_abs: {
-		ADD_PAGE
-		SYNC_TIME();
+		ADD_PAGE();
+		FLUSH_TIME();
 		int temp = READ( data );
 		nz = (c >> 1 & 0x80) | (temp >> 1);
 		c = temp << 8;
@@ -717,14 +719,14 @@
 		c = 0;
 	case 0x2E: // ROL abs
 	rol_abs:
-		ADD_PAGE
+		ADD_PAGE();
 		nz = c >> 8 & 1;
-		SYNC_TIME();
+		FLUSH_TIME();
 		nz |= (c = READ( data ) << 1);
 	rotate_common:
 		pc++;
 		WRITE( data, (uint8_t) nz );
-		RELOAD_TIME();
+		CACHE_TIME();
 		goto loop;
 	
 	case 0x7E: // ROR abs,X
@@ -805,11 +807,11 @@
 	dec_ptr:
 		nz = (unsigned) -1;
 	inc_common:
-		SYNC_TIME();
+		FLUSH_TIME();
 		nz += READ( data );
 		pc += 2;
 		WRITE( data, (uint8_t) nz );
-		RELOAD_TIME();
+		CACHE_TIME();
 		goto loop;
 		
 // Transfer
@@ -1050,9 +1052,9 @@
 	
 out_of_time:
 	pc--;
-	SYNC_TIME();
+	FLUSH_TIME();
 	CPU_DONE( this, TIME, result_ );
-	RELOAD_TIME();
+	CACHE_TIME();
 	if ( result_ >= 0 )
 		goto interrupt;
 	if ( s_time < 0 )
--- a/src/console/Nes_Cpu.h	Wed Dec 06 07:57:05 2006 -0800
+++ b/src/console/Nes_Cpu.h	Thu Dec 07 15:20:41 2006 -0800
@@ -1,6 +1,6 @@
 // NES 6502 CPU emulator
 
-// Game_Music_Emu 0.5.1
+// Game_Music_Emu 0.5.2
 #ifndef NES_CPU_H
 #define NES_CPU_H
 
--- a/src/console/Nes_Fme7_Apu.cxx	Wed Dec 06 07:57:05 2006 -0800
+++ b/src/console/Nes_Fme7_Apu.cxx	Thu Dec 07 15:20:41 2006 -0800
@@ -1,4 +1,4 @@
-// Game_Music_Emu 0.5.1. http://www.slack.net/~ant/
+// Game_Music_Emu 0.5.2. http://www.slack.net/~ant/
 
 #include "Nes_Fme7_Apu.h"
 
@@ -78,11 +78,13 @@
 		int amp = volume;
 		if ( !phases [index] )
 			amp = 0;
-		int delta = amp - oscs [index].last_amp;
-		if ( delta )
 		{
-			oscs [index].last_amp = amp;
-			synth.offset( last_time, delta, osc_output );
+			int delta = amp - oscs [index].last_amp;
+			if ( delta )
+			{
+				oscs [index].last_amp = amp;
+				synth.offset( last_time, delta, osc_output );
+			}
 		}
 		
 		blip_time_t time = last_time + delays [index];
--- a/src/console/Nes_Fme7_Apu.h	Wed Dec 06 07:57:05 2006 -0800
+++ b/src/console/Nes_Fme7_Apu.h	Thu Dec 07 15:20:41 2006 -0800
@@ -1,6 +1,6 @@
 // Sunsoft FME-7 sound emulator
 
-// Game_Music_Emu 0.5.1
+// Game_Music_Emu 0.5.2
 #ifndef NES_FME7_APU_H
 #define NES_FME7_APU_H
 
@@ -15,7 +15,6 @@
 	BOOST::uint8_t latch;
 	BOOST::uint16_t delays [3]; // a, b, c
 };
-BOOST_STATIC_ASSERT( sizeof (fme7_apu_state_t) == 24 );
 
 class Nes_Fme7_Apu : private fme7_apu_state_t {
 public:
--- a/src/console/Nes_Oscs.cxx	Wed Dec 06 07:57:05 2006 -0800
+++ b/src/console/Nes_Oscs.cxx	Thu Dec 07 15:20:41 2006 -0800
@@ -133,9 +133,11 @@
 		if ( phase < duty )
 			amp ^= volume;
 		
-		int delta = update_amp( amp );
-		if ( delta )
-			synth.offset( time, delta, output );
+		{
+			int delta = update_amp( amp );
+			if ( delta )
+				synth.offset( time, delta, output );
+		}
 		
 		time += delay;
 		if ( time < end_time )
@@ -489,9 +491,11 @@
 	
 	const int volume = this->volume();
 	int amp = (noise & 1) ? volume : 0;
-	int delta = update_amp( amp );
-	if ( delta )
-		synth.offset( time, delta, output );
+	{
+		int delta = update_amp( amp );
+		if ( delta )
+			synth.offset( time, delta, output );
+	}
 	
 	time += delay;
 	if ( time < end_time )
--- a/src/console/Nes_Vrc6_Apu.cxx	Wed Dec 06 07:57:05 2006 -0800
+++ b/src/console/Nes_Vrc6_Apu.cxx	Thu Dec 07 15:20:41 2006 -0800
@@ -72,6 +72,7 @@
 
 void Nes_Vrc6_Apu::save_state( vrc6_apu_state_t* out ) const
 {
+	assert( sizeof (vrc6_apu_state_t) == 20 );
 	out->saw_amp = oscs [2].amp;
 	for ( int i = 0; i < osc_count; i++ )
 	{
--- a/src/console/Nes_Vrc6_Apu.h	Wed Dec 06 07:57:05 2006 -0800
+++ b/src/console/Nes_Vrc6_Apu.h	Thu Dec 07 15:20:41 2006 -0800
@@ -72,7 +72,6 @@
 	BOOST::uint8_t phases [3];
 	BOOST::uint8_t unused;
 };
-BOOST_STATIC_ASSERT( sizeof (vrc6_apu_state_t) == 20 );
 
 inline void Nes_Vrc6_Apu::osc_output( int i, Blip_Buffer* buf )
 {
--- a/src/console/Nsf_Emu.cxx	Wed Dec 06 07:57:05 2006 -0800
+++ b/src/console/Nsf_Emu.cxx	Thu Dec 07 15:20:41 2006 -0800
@@ -1,4 +1,4 @@
-// Game_Music_Emu 0.5.1. http://www.slack.net/~ant/
+// Game_Music_Emu 0.5.2. http://www.slack.net/~ant/
 
 #include "Nsf_Emu.h"
 
@@ -106,7 +106,7 @@
 	
 	blargg_err_t load_( Data_Reader& in )
 	{
-		blargg_err_t err = in.read( &h, sizeof h );
+		blargg_err_t err = in.read( &h, Nsf_Emu::header_size );
 		if ( err )
 			return (err == in.eof_error ? gme_wrong_file_type : err);
 		
@@ -213,13 +213,15 @@
 			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 );
+			{
+				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 )
 			{
@@ -264,8 +266,8 @@
 
 blargg_err_t Nsf_Emu::load_( Data_Reader& in )
 {
-	unload();
-	RETURN_ERR( rom.load( in, sizeof header_, &header_, 0 ) );
+	assert( offsetof (header_t,unused [4]) == header_size );
+	RETURN_ERR( rom.load( in, header_size, &header_, 0 ) );
 	
 	set_track_count( header_.track_count );
 	RETURN_ERR( check_nsf_header( &header_ ) );
@@ -388,13 +390,13 @@
 		{
 			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;
+			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;
 			}
 		}
 		
@@ -402,13 +404,13 @@
 		{
 			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;
+			case Nes_Fme7_Apu::latch_addr:
+				fme7->write_latch( data );
+				return;
+			
+			case Nes_Fme7_Apu::data_addr:
+				fme7->write_data( time(), data );
+				return;
 			}
 		}
 		
@@ -524,6 +526,7 @@
 				r.pc = play_addr;
 				low_mem [0x100 + r.sp--] = (badop_addr - 1) >> 8;
 				low_mem [0x100 + r.sp--] = (badop_addr - 1) & 0xFF;
+				GME_FRAME_HOOK( this );
 			}
 		}
 	}
--- a/src/console/Nsf_Emu.h	Wed Dec 06 07:57:05 2006 -0800
+++ b/src/console/Nsf_Emu.h	Thu Dec 07 15:20:41 2006 -0800
@@ -1,6 +1,6 @@
 // Nintendo NES/Famicom NSF music file emulator
 
-// Game_Music_Emu 0.5.1
+// Game_Music_Emu 0.5.2
 #ifndef NSF_EMU_H
 #define NSF_EMU_H
 
@@ -16,6 +16,7 @@
 	static equalizer_t const famicom_eq;
 	
 	// NSF file header
+	enum { header_size = 0x80 };
 	struct header_t
 	{
 		char tag [5];
@@ -35,7 +36,6 @@
 		byte chip_flags;
 		byte unused [4];
 	};
-	BOOST_STATIC_ASSERT( sizeof (header_t) == 0x80 );
 	
 	// Header for currently loaded file
 	header_t const& header() const { return header_; }
--- a/src/console/Nsfe_Emu.cxx	Wed Dec 06 07:57:05 2006 -0800
+++ b/src/console/Nsfe_Emu.cxx	Thu Dec 07 15:20:41 2006 -0800
@@ -1,4 +1,4 @@
-// Game_Music_Emu 0.5.1. http://www.slack.net/~ant/
+// Game_Music_Emu 0.5.2. http://www.slack.net/~ant/
 
 #include "Nsfe_Emu.h"
 
@@ -88,10 +88,12 @@
 	byte first_track;
 	byte unused [6];
 };
-BOOST_STATIC_ASSERT( sizeof (nsfe_info_t) == 16 );
 
 blargg_err_t Nsfe_Info::load( Data_Reader& in, Nsf_Emu* nsf_emu )
 {
+	int const nsfe_info_size = 16;
+	assert( offsetof (nsfe_info_t,unused [6]) == nsfe_info_size );
+	
 	// check header
 	byte signature [4];
 	blargg_err_t err = in.read( signature, sizeof signature );
@@ -146,9 +148,9 @@
 				finfo.track_count = 1;
 				finfo.first_track = 0;
 				
-				RETURN_ERR( in.read( &finfo, min( size, (blargg_long) sizeof finfo ) ) );
-				if ( size > (int) sizeof finfo )
-					RETURN_ERR( in.skip( size - sizeof finfo ) );
+				RETURN_ERR( in.read( &finfo, min( size, (blargg_long) nsfe_info_size ) ) );
+				if ( size > nsfe_info_size )
+					RETURN_ERR( in.skip( size - nsfe_info_size ) );
 				phase = 1;
 				info.speed_flags = finfo.speed_flags;
 				info.chip_flags  = finfo.chip_flags;
@@ -203,19 +205,17 @@
 			case BLARGG_4CHAR('A','T','A','D'): {
 				check( phase == 1 );
 				phase = 2;
-				disable_playlist( false );
 				if ( !nsf_emu )
 				{
-					in.skip( size );
+					RETURN_ERR( in.skip( size ) );
 				}
 				else
 				{
 					Subset_Reader sub( &in, size ); // limit emu to nsf data
-					Remaining_Reader rem( &header, sizeof header, &sub );
+					Remaining_Reader rem( &header, Nsf_Emu::header_size, &sub );
 					RETURN_ERR( nsf_emu->load( rem ) );
 					check( rem.remain() == 0 );
 				}
-				disable_playlist( false ); // TODO: fix this crappy hack (unload() disables playlist)
 				break;
 			}
 			
@@ -283,6 +283,7 @@
 	blargg_err_t load_( Data_Reader& in )
 	{
 		RETURN_ERR( info.load( in, 0 ) );
+		info.disable_playlist( false );
 		set_track_count( info.info.track_count );
 		return 0;
 	}
--- a/src/console/Nsfe_Emu.h	Wed Dec 06 07:57:05 2006 -0800
+++ b/src/console/Nsfe_Emu.h	Thu Dec 07 15:20:41 2006 -0800
@@ -1,6 +1,6 @@
 // Nintendo NES/Famicom NSFE music file emulator
 
-// Game_Music_Emu 0.5.1
+// Game_Music_Emu 0.5.2
 #ifndef NSFE_EMU_H
 #define NSFE_EMU_H
 
--- a/src/console/Sap_Apu.cxx	Wed Dec 06 07:57:05 2006 -0800
+++ b/src/console/Sap_Apu.cxx	Thu Dec 07 15:20:41 2006 -0800
@@ -1,4 +1,4 @@
-// Game_Music_Emu 0.5.1. http://www.slack.net/~ant/
+// Game_Music_Emu 0.5.2. http://www.slack.net/~ant/
 
 #include "Sap_Apu.h"
 
--- a/src/console/Sap_Apu.h	Wed Dec 06 07:57:05 2006 -0800
+++ b/src/console/Sap_Apu.h	Thu Dec 07 15:20:41 2006 -0800
@@ -1,6 +1,6 @@
 // Atari POKEY sound chip emulator
 
-// Game_Music_Emu 0.5.1
+// Game_Music_Emu 0.5.2
 #ifndef SAP_APU_H
 #define SAP_APU_H
 
--- a/src/console/Sap_Cpu.cxx	Wed Dec 06 07:57:05 2006 -0800
+++ b/src/console/Sap_Cpu.cxx	Thu Dec 07 15:20:41 2006 -0800
@@ -1,4 +1,4 @@
-// Game_Music_Emu 0.5.1. http://www.slack.net/~ant/
+// Game_Music_Emu 0.5.2. http://www.slack.net/~ant/
 
 #include "Sap_Cpu.h"
 
@@ -18,8 +18,8 @@
 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)
+#define FLUSH_TIME()    (void) (s.time = s_time)
+#define CACHE_TIME()    (void) (s_time = s.time)
 
 #include "sap_cpu_io.h"
 
@@ -94,18 +94,18 @@
 	// 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;        \
+	#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;                       \
+	#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;
@@ -182,7 +182,7 @@
 // Macros
 
 #define GET_MSB()   (instr [1])
-#define ADD_PAGE    (pc++, data += 0x100 * GET_MSB());
+#define ADD_PAGE()  (pc++, data += 0x100 * GET_MSB())
 #define GET_ADDR()  GET_LE16( instr )
 
 #define NO_PAGE_CROSSING( lsb )
@@ -190,53 +190,54 @@
 
 #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_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 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 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:\
+	FLUSH_TIME();\
+	data = READ( data );\
+	CACHE_TIME();\
+case op + 0x04: /* imm */\
+imm##op:
 
-#define BRANCH( cond )                          \
-{                                               \
-	fint16 offset = (BOOST::int8_t) data;       \
+// TODO: more efficient way to handle negative branch that wraps PC around
+#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;                                  \
+	if ( !(cond) ) goto dec_clock_loop;\
+	pc += offset;\
+	s_time += extra_clock >> 8 & 1;\
+	goto loop;\
 }
 
 // Often-Used
@@ -352,9 +353,9 @@
 			goto loop;
 		}
 	sta_ptr:
-		SYNC_TIME();
+		FLUSH_TIME();
 		WRITE( addr, a );
-		RELOAD_TIME();
+		CACHE_TIME();
 		goto loop;
 		
 	case 0x91: // STA (ind),Y
@@ -411,9 +412,9 @@
 		if ( (addr ^ 0x8000) <= 0x9FFF )
 			goto loop;
 	a_nz_read_addr:
-		SYNC_TIME();
+		FLUSH_TIME();
 		a = nz = READ( addr );
-		RELOAD_TIME();
+		CACHE_TIME();
 		goto loop;
 	
 	}
@@ -474,9 +475,9 @@
 	case 0xAC:{// LDY abs
 		unsigned addr = data + 0x100 * GET_MSB();
 		pc += 2;
-		SYNC_TIME();
+		FLUSH_TIME();
 		y = nz = READ( addr );
-		RELOAD_TIME();
+		CACHE_TIME();
 		goto loop;
 	}
 	
@@ -486,9 +487,9 @@
 	case 0xAE:{// LDX abs
 		unsigned addr = data + 0x100 * GET_MSB();
 		pc += 2;
-		SYNC_TIME();
+		FLUSH_TIME();
 		x = nz = READ( addr );
-		RELOAD_TIME();
+		CACHE_TIME();
 		goto loop;
 	}
 	
@@ -508,9 +509,9 @@
 			WRITE_LOW( addr, temp );
 			goto loop;
 		}
-		SYNC_TIME();
+		FLUSH_TIME();
 		WRITE( addr, temp );
-		RELOAD_TIME();
+		CACHE_TIME();
 		goto loop;
 	}
 
@@ -519,9 +520,9 @@
 	case 0xEC:{// CPX abs
 		unsigned addr = GET_ADDR();
 		pc++;
-		SYNC_TIME();
+		FLUSH_TIME();
 		data = READ( addr );
-		RELOAD_TIME();
+		CACHE_TIME();
 		goto cpx_data;
 	}
 	
@@ -538,9 +539,9 @@
 	case 0xCC:{// CPY abs
 		unsigned addr = GET_ADDR();
 		pc++;
-		SYNC_TIME();
+		FLUSH_TIME();
 		data = READ( addr );
-		RELOAD_TIME();
+		CACHE_TIME();
 		goto cpy_data;
 	}
 	
@@ -645,8 +646,8 @@
 		c = 0;
 	case 0x6E: // ROR abs
 	ror_abs: {
-		ADD_PAGE
-		SYNC_TIME();
+		ADD_PAGE();
+		FLUSH_TIME();
 		int temp = READ( data );
 		nz = (c >> 1 & 0x80) | (temp >> 1);
 		c = temp << 8;
@@ -663,14 +664,14 @@
 		c = 0;
 	case 0x2E: // ROL abs
 	rol_abs:
-		ADD_PAGE
+		ADD_PAGE();
 		nz = c >> 8 & 1;
-		SYNC_TIME();
+		FLUSH_TIME();
 		nz |= (c = READ( data ) << 1);
 	rotate_common:
 		pc++;
 		WRITE( data, (uint8_t) nz );
-		RELOAD_TIME();
+		CACHE_TIME();
 		goto loop;
 	
 	case 0x7E: // ROR abs,X
@@ -751,11 +752,11 @@
 	dec_ptr:
 		nz = (unsigned) -1;
 	inc_common:
-		SYNC_TIME();
+		FLUSH_TIME();
 		nz += READ( data );
 		pc += 2;
 		WRITE( data, (uint8_t) nz );
-		RELOAD_TIME();
+		CACHE_TIME();
 		goto loop;
 		
 // Transfer
@@ -940,7 +941,7 @@
 	
 	int result_;
 handle_brk:
-	if ( pc >= idle_addr + 1 )
+	if ( (pc - 1) >= idle_addr )
 		goto idle_done;
 	pc++;
 	result_ = 4;
@@ -978,9 +979,9 @@
 	goto stop;
 out_of_time:
 	pc--;
-	SYNC_TIME();
+	FLUSH_TIME();
 	CPU_DONE( this, TIME, result_ );
-	RELOAD_TIME();
+	CACHE_TIME();
 	if ( result_ >= 0 )
 		goto interrupt;
 	if ( s_time < 0 )
--- a/src/console/Sap_Cpu.h	Wed Dec 06 07:57:05 2006 -0800
+++ b/src/console/Sap_Cpu.h	Thu Dec 07 15:20:41 2006 -0800
@@ -1,6 +1,6 @@
 // Atari 6502 CPU emulator
 
-// Game_Music_Emu 0.5.1
+// Game_Music_Emu 0.5.2
 #ifndef SAP_CPU_H
 #define SAP_CPU_H
 
--- a/src/console/Sap_Emu.cxx	Wed Dec 06 07:57:05 2006 -0800
+++ b/src/console/Sap_Emu.cxx	Thu Dec 07 15:20:41 2006 -0800
@@ -1,4 +1,4 @@
-// Game_Music_Emu 0.5.1. http://www.slack.net/~ant/
+// Game_Music_Emu 0.5.2. http://www.slack.net/~ant/
 
 #include "Sap_Emu.h"
 
@@ -288,11 +288,11 @@
 	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 )
+	if ( r.sp == 0xFE && mem.ram [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) & 0xFF;
+	mem.ram [0x100 + r.sp--] = high_byte; // some routines use RTI to return
+	mem.ram [0x100 + r.sp--] = high_byte;
+	mem.ram [0x100 + r.sp--] = (idle_addr - 1) & 0xFF;
 }
 
 void Sap_Emu::run_routine( sap_addr_t addr )
@@ -327,7 +327,8 @@
 {
 	RETURN_ERR( Classic_Emu::start_track_( track ) );
 	
-	memset( mem, 0, sizeof mem );
+	memset( &mem, 0, sizeof mem );
+
 	byte const* in = info.rom_data;
 	while ( file_end - in >= 5 )
 	{
@@ -347,7 +348,7 @@
 			break;
 		}
 		
-		memcpy( mem + start, in, len );
+		memcpy( mem.ram + start, in, len );
 		in += len;
 		if ( file_end - in >= 2 && in [0] == 0xFF && in [1] == 0xFF )
 			in += 2;
@@ -355,7 +356,7 @@
 	
 	apu.reset( &apu_impl );
 	apu2.reset( &apu_impl );
-	cpu::reset( mem );
+	cpu::reset( mem.ram );
 	time_mask = 0; // disables sound during init
 	call_init( track );
 	time_mask = -1;
@@ -373,6 +374,7 @@
 {
 	if ( (addr ^ Sap_Apu::start_addr) <= (Sap_Apu::end_addr - Sap_Apu::start_addr) )
 	{
+		GME_APU_HOOK( this, addr - Sap_Apu::start_addr, data );
 		apu.write_data( time() & time_mask, addr, data );
 		return;
 	}
@@ -380,6 +382,7 @@
 	if ( (addr ^ (Sap_Apu::start_addr + 0x10)) <= (Sap_Apu::end_addr - Sap_Apu::start_addr) &&
 			info.stereo )
 	{
+		GME_APU_HOOK( this, addr - 0x10 - Sap_Apu::start_addr + 10, data );
 		apu2.write_data( time() & time_mask, addr ^ 0x10, data );
 		return;
 	}
@@ -417,6 +420,7 @@
 				set_time( next_play );
 				next_play += play_period();
 				call_play();
+				GME_FRAME_HOOK( this );
 			}
 			else
 			{
--- a/src/console/Sap_Emu.h	Wed Dec 06 07:57:05 2006 -0800
+++ b/src/console/Sap_Emu.h	Thu Dec 07 15:20:41 2006 -0800
@@ -1,6 +1,6 @@
 // Atari XL/XE SAP music file emulator
 
-// Game_Music_Emu 0.5.1
+// Game_Music_Emu 0.5.2
 #ifndef SAP_EMU_H
 #define SAP_EMU_H
 
@@ -52,7 +52,11 @@
 	Sap_Apu apu2;
 	
 	// large items
-	byte mem [0x10000 + 0x100];
+	struct {
+		byte padding1 [0x100];
+		byte ram [0x10000];
+		byte padding2 [0x100];
+	} mem;
 	Sap_Apu_Impl apu_impl;
 	
 	sap_time_t play_period() const;
--- a/src/console/Sms_Apu.cxx	Wed Dec 06 07:57:05 2006 -0800
+++ b/src/console/Sms_Apu.cxx	Thu Dec 07 15:20:41 2006 -0800
@@ -70,11 +70,13 @@
 	else
 	{
 		int amp = phase ? volume : -volume;
-		int delta = amp - last_amp;
-		if ( delta )
 		{
-			last_amp = amp;
-			synth->offset( time, delta, output );
+			int delta = amp - last_amp;
+			if ( delta )
+			{
+				last_amp = amp;
+				synth->offset( time, delta, output );
+			}
 		}
 		
 		time += delay;
@@ -114,11 +116,13 @@
 	if ( shifter & 1 )
 		amp = -amp;
 	
-	int delta = amp - last_amp;
-	if ( delta )
 	{
-		last_amp = amp;
-		synth.offset( time, delta, output );
+		int delta = amp - last_amp;
+		if ( delta )
+		{
+			last_amp = amp;
+			synth.offset( time, delta, output );
+		}
 	}
 	
 	time += delay;
--- a/src/console/Snes_Spc.cxx	Wed Dec 06 07:57:05 2006 -0800
+++ b/src/console/Snes_Spc.cxx	Thu Dec 07 15:20:41 2006 -0800
@@ -1,4 +1,4 @@
-// Game_Music_Emu 0.5.1. http://www.slack.net/~ant/
+// Game_Music_Emu 0.5.2. http://www.slack.net/~ant/
 
 #include "Snes_Spc.h"
 
@@ -20,12 +20,13 @@
 // 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 )
+Snes_Spc::Snes_Spc() : dsp( mem.ram ), cpu( this, mem.ram )
 {
 	set_tempo( 1.0 );
 	
-	// Put STOP instruction past end of memory to catch PC overflow.
-	memset( ram + ram_size, 0xFF, (sizeof ram) - ram_size );
+	// Put STOP instruction around memory to catch PC underflow/overflow.
+	memset( mem.padding1, 0xFF, sizeof mem.padding1 );
+	memset( mem.padding2, 0xFF, sizeof mem.padding2 );
 	
 	// A few tracks read from the last four bytes of IPL ROM
 	boot_rom [sizeof boot_rom - 2] = 0xC0;
@@ -65,7 +66,7 @@
 		uint8_t dsp [128];
 		uint8_t ipl_rom [128];
 	};
-	BOOST_STATIC_ASSERT( sizeof (spc_file_t) == spc_file_size + 128 );
+	assert( offsetof (spc_file_t,ipl_rom) == spc_file_size );
 	
 	const spc_file_t* spc = (spc_file_t const*) data;
 	
@@ -98,8 +99,8 @@
 	if ( !(dsp.read( 0x6C ) & 0x20) )
 	{
 		unsigned addr = 0x100 * dsp.read( 0x6D );
-		unsigned size = 0x800 * dsp.read( 0x7D );
-		memset( ram + addr, 0xFF, min( size, ram_size - addr ) );
+		size_t   size = 0x800 * dsp.read( 0x7D );
+		memset( mem.ram + addr, 0xFF, min( size, sizeof mem.ram - addr ) );
 	}
 }
 
@@ -119,11 +120,11 @@
 	extra_cycles = 32; 
 	
 	// ram
-	memcpy( ram, new_ram, ram_size );
-	memcpy( extra_ram, ram + rom_addr, sizeof extra_ram );
+	memcpy( mem.ram, new_ram, sizeof mem.ram );
+	memcpy( extra_ram, mem.ram + rom_addr, sizeof extra_ram );
 	
 	// boot rom (have to force enable_rom() to update it)
-	rom_enabled = !(ram [0xF1] & 0x80);
+	rom_enabled = !(mem.ram [0xF1] & 0x80);
 	enable_rom( !rom_enabled );
 	
 	// dsp
@@ -138,27 +139,27 @@
 		Timer& t = timer [i];
 		
 		t.next_tick = 0;
-		t.enabled = (ram [0xF1] >> i) & 1;
+		t.enabled = (mem.ram [0xF1] >> i) & 1;
 		if ( !t.enabled )
 			t.next_tick = timer_disabled_time;
 		t.count = 0;
-		t.counter = ram [0xFD + i] & 15;
+		t.counter = mem.ram [0xFD + i] & 15;
 		
-		int p = ram [0xFA + i];
+		int p = mem.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;
+	mem.ram [0xF0] = 0;
+	mem.ram [0xF1] = 0;
+	mem.ram [0xF3] = 0xFF;
+	mem.ram [0xFA] = 0;
+	mem.ram [0xFB] = 0;
+	mem.ram [0xFC] = 0;
+	mem.ram [0xFD] = 0xFF;
+	mem.ram [0xFE] = 0xFF;
+	mem.ram [0xFF] = 0xFF;
 	
 	return 0; // success
 }
@@ -238,7 +239,7 @@
 
 int Snes_Spc::read( spc_addr_t addr )
 {
-	int result = ram [addr];
+	int result = mem.ram [addr];
 	
 	if ( (rom_addr <= addr && addr < 0xFFFC || addr >= 0xFFFE) && rom_enabled )
 		dprintf( "Read from ROM: %04X -> %02X\n", addr, result );
@@ -253,25 +254,25 @@
 		{
 			Timer& t = timer [i];
 			t.run_until( time() );
-			int result = t.counter;
+			int old = t.counter;
 			t.counter = 0;
-			return result;
+			return old;
 		}
 		
 		// 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 ( mem.ram [0xF2] >= Spc_Dsp::register_count )
+				dprintf( "DSP read from $%02X\n", (int) mem.ram [0xF2] );
+			return dsp.read( mem.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
+		// Registers which always read as 0 are handled by setting mem.ram [reg] to 0
 		// at startup then never changing that value.
 		
 		check(( check_for_echo_access( addr ), true ));
@@ -288,7 +289,7 @@
 	if ( rom_enabled != enable )
 	{
 		rom_enabled = enable;
-		memcpy( ram + rom_addr, (enable ? boot_rom : extra_ram), rom_size );
+		memcpy( mem.ram + rom_addr, (enable ? boot_rom : extra_ram), rom_size );
 		// TODO: ROM can still get overwritten when DSP writes to echo buffer
 	}
 }
@@ -297,7 +298,7 @@
 {
 	// first page is very common
 	if ( addr < 0xF0 ) {
-		ram [addr] = (uint8_t) data;
+		mem.ram [addr] = (uint8_t) data;
 	}
 	else switch ( addr )
 	{
@@ -305,12 +306,12 @@
 		default:
 			check(( check_for_echo_access( addr ), true ));
 			if ( addr < rom_addr ) {
-				ram [addr] = (uint8_t) data;
+				mem.ram [addr] = (uint8_t) data;
 			}
 			else {
 				extra_ram [addr - rom_addr] = (uint8_t) data;
 				if ( !rom_enabled )
-					ram [addr] = (uint8_t) data;
+					mem.ram [addr] = (uint8_t) data;
 			}
 			break;
 		
@@ -318,7 +319,7 @@
 		//case 0xF2: // mapped to RAM
 		case 0xF3: {
 			run_dsp( time() );
-			int reg = ram [0xF2];
+			int reg = mem.ram [0xF2];
 			if ( next_dsp > 0 ) {
 				// skip mode
 				
@@ -367,12 +368,12 @@
 			
 			// port clears
 			if ( data & 0x10 ) {
-				ram [0xF4] = 0;
-				ram [0xF5] = 0;
+				mem.ram [0xF4] = 0;
+				mem.ram [0xF5] = 0;
 			}
 			if ( data & 0x20 ) {
-				ram [0xF6] = 0;
-				ram [0xF7] = 0;
+				mem.ram [0xF6] = 0;
+				mem.ram [0xF7] = 0;
 			}
 			
 			enable_rom( data & 0x80 );
--- a/src/console/Snes_Spc.h	Wed Dec 06 07:57:05 2006 -0800
+++ b/src/console/Snes_Spc.h	Thu Dec 07 15:20:41 2006 -0800
@@ -1,6 +1,6 @@
 // Super Nintendo (SNES) SPC-700 APU Emulator
 
-// Game_Music_Emu 0.5.1
+// Game_Music_Emu 0.5.2
 #ifndef SNES_SPC_H
 #define SNES_SPC_H
 
@@ -102,9 +102,13 @@
 	
 	// 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
+	struct {
+		// padding to catch jumps before beginning or past end
+		uint8_t padding1 [0x100];
+		uint8_t ram [0x10000];
+		uint8_t padding2 [0x100];
+	} mem;
 	uint8_t boot_rom [rom_size];
 };
 
--- a/src/console/Spc_Cpu.cxx	Wed Dec 06 07:57:05 2006 -0800
+++ b/src/console/Spc_Cpu.cxx	Thu Dec 07 15:20:41 2006 -0800
@@ -1,4 +1,4 @@
-// Game_Music_Emu 0.5.1. http://www.slack.net/~ant/
+// Game_Music_Emu 0.5.2. http://www.slack.net/~ant/
 
 #include "Spc_Cpu.h"
 
@@ -39,7 +39,7 @@
 Spc_Cpu::Spc_Cpu( Snes_Spc* e, uint8_t* ram_in ) : ram( ram_in ), emu( *e )
 {
 	remain_ = 0;
-	BOOST_STATIC_ASSERT( sizeof (int) >= 4 );
+	assert( INT_MAX >= 0x7FFFFFFF ); // requires 32-bit int
 	blargg_verify_byte_order();
 }
 
@@ -129,20 +129,20 @@
 	
 	#define IS_NEG (nz & 0x880)
 	
-	#define CALC_STATUS( out ) do {                 \
-		out = status & ~(st_n | st_z | st_c);       \
-		out |= (c >> 8) & st_c;                     \
-		out |= (dp >> 3) & st_p;                    \
-		if ( IS_NEG ) out |= st_n;                  \
-		if ( !(nz & 0xFF) ) out |= st_z;            \
+	#define CALC_STATUS( out ) do {\
+		out = status & ~(st_n | st_z | st_c);\
+		out |= (c >> 8) & st_c;\
+		out |= (dp >> 3) & st_p;\
+		if ( IS_NEG ) out |= st_n;\
+		if ( !(nz & 0xFF) ) out |= st_z;\
 	} while ( 0 )       
 
-	#define SET_STATUS( in ) do {                   \
-		status = in & ~(st_n | st_z | st_c | st_p); \
-		c = in << 8;                                \
-		nz = (in << 4) & 0x800;                     \
-		nz |= ~in & st_z;                           \
-		dp = (in << 3) & 0x100;                     \
+	#define SET_STATUS( in ) do {\
+		status = in & ~(st_n | st_z | st_c | st_p);\
+		c = in << 8;\
+		nz = (in << 4) & 0x800;\
+		nz |= ~in & st_z;\
+		dp = (in << 3) & 0x100;\
 	} while ( 0 )
 	
 	int status;
@@ -188,14 +188,14 @@
 	switch ( opcode )
 	{
 	
-	#define BRANCH( cond ) {        \
-		pc++;                       \
-		int offset = (BOOST::int8_t) data; \
-		if ( cond ) {               \
-			pc += offset;           \
-			remain_ -= 2;           \
-		}                           \
-		goto loop;                  \
+	#define BRANCH( cond ) {\
+		pc++;\
+		int offset = (BOOST::int8_t) data;\
+		if ( cond ) {\
+			pc += offset;\
+			remain_ -= 2;\
+		}\
+		goto loop;\
 	}
 	
 // Most-Common
@@ -220,31 +220,31 @@
 
 // Define common address modes based on opcode for immediate mode. Execution
 // ends with data set to the address of the operand.
-#define ADDR_MODES( op )                \
-	CASE( op - 0x02 ) /* (X) */         \
-		data = x + dp;                  \
-		pc--;                           \
-		goto end_##op;                  \
-	CASE( op + 0x0F ) /* (dp)+Y */      \
+#define ADDR_MODES( op )\
+	CASE( op - 0x02 ) /* (X) */\
+		data = x + dp;\
+		pc--;\
+		goto end_##op;\
+	CASE( op + 0x0F ) /* (dp)+Y */\
 		data = READ_PROG16( data + dp ) + y;\
-		goto end_##op;                  \
-	CASE( op - 0x01 ) /* (dp+X) */      \
+		goto end_##op;\
+	CASE( op - 0x01 ) /* (dp+X) */\
 		data = READ_PROG16( uint8_t (data + x) + dp );\
-		goto end_##op;                  \
-	CASE( op + 0x0E ) /* abs+Y */       \
-		data += y;                      \
-		goto abs_##op;                  \
-	CASE( op + 0x0D ) /* abs+X */       \
-		data += x;                      \
-	CASE( op - 0x03 ) /* abs */         \
-	abs_##op:                           \
-		pc++;                           \
+		goto end_##op;\
+	CASE( op + 0x0E ) /* abs+Y */\
+		data += y;\
+		goto abs_##op;\
+	CASE( op + 0x0D ) /* abs+X */\
+		data += x;\
+	CASE( op - 0x03 ) /* abs */\
+	abs_##op:\
+		pc++;\
 		data += 0x100 * READ_PROG( pc );\
-		goto end_##op;                  \
-	CASE( op + 0x0C ) /* dp+X */        \
-		data = uint8_t (data + x);      \
-	CASE( op - 0x04 ) /* dp */          \
-		data += dp;                     \
+		goto end_##op;\
+	CASE( op + 0x0C ) /* dp+X */\
+		data = uint8_t (data + x);\
+	CASE( op - 0x04 ) /* dp */\
+		data += dp;\
 	end_##op:
 
 // 1. 8-bit Data Transmission Commands. Group I
@@ -372,27 +372,27 @@
 	
 // 5. 8-BIT LOGIC OPERATION COMMANDS
 	
-#define LOGICAL_OP( op, func )  \
-	ADDR_MODES( op ) /* addr */ \
-		data = READ( data );    \
-	case op: /* imm */          \
-		nz = a func##= data;    \
-		goto inc_pc_loop;       \
-	{   unsigned addr;          \
-	case op + 0x11: /* X,Y */   \
-		data = READ_DP( y );    \
-		addr = x + dp;          \
-		pc--;                   \
-		goto addr_##op;         \
-	case op + 0x01: /* dp,dp */ \
-		data = READ_DP( data ); \
+#define LOGICAL_OP( op, func )\
+	ADDR_MODES( op ) /* addr */\
+		data = READ( data );\
+	case op: /* imm */\
+		nz = a func##= data;\
+		goto inc_pc_loop;\
+	{   unsigned addr;\
+	case op + 0x11: /* X,Y */\
+		data = READ_DP( y );\
+		addr = x + dp;\
+		pc--;\
+		goto addr_##op;\
+	case op + 0x01: /* dp,dp */\
+		data = READ_DP( data );\
 	case op + 0x10: /*dp,imm*/\
-		pc++;                   \
+		pc++;\
 		addr = READ_PROG( pc ) + dp;\
-	addr_##op:                  \
+	addr_##op:\
 		nz = data func READ( addr );\
-		WRITE( addr, nz );      \
-		goto inc_pc_loop;       \
+		WRITE( addr, nz );\
+		goto inc_pc_loop;\
 	}
 	
 	LOGICAL_OP( 0x28, & ); // AND
@@ -504,9 +504,9 @@
 	
 // 6. ADDITION & SUBTRACTION COMMANDS
 
-#define INC_DEC_REG( reg, n )   \
-		nz = reg + n;           \
-		reg = (uint8_t) nz;     \
+#define INC_DEC_REG( reg, n )\
+		nz = reg + n;\
+		reg = (uint8_t) nz;\
 		goto loop;
 
 	case 0xBC: INC_DEC_REG( a, 1 )  // INC A
@@ -819,7 +819,7 @@
 	
 // 13. SUB-ROUTINE CALL RETURN COMMANDS
 	
-	case 0x0F: // BRK
+	case 0x0F:{// BRK
 		check( false ); // untested
 		PUSH16( pc + 1 );
 		pc = READ_PROG16( 0xFFDE ); // vector address verified
@@ -828,6 +828,7 @@
 		PUSH( temp );
 		status = (status | st_b) & ~st_i;
 		goto loop;
+	}
 	
 	case 0x4F: // PCALL offset
 		pc++;
--- a/src/console/Spc_Cpu.h	Wed Dec 06 07:57:05 2006 -0800
+++ b/src/console/Spc_Cpu.h	Thu Dec 07 15:20:41 2006 -0800
@@ -1,6 +1,6 @@
 // Super Nintendo (SNES) SPC-700 CPU emulator
 
-// Game_Music_Emu 0.5.1
+// Game_Music_Emu 0.5.2
 #ifndef SPC_CPU_H
 #define SPC_CPU_H
 
--- a/src/console/Spc_Dsp.cxx	Wed Dec 06 07:57:05 2006 -0800
+++ b/src/console/Spc_Dsp.cxx	Thu Dec 07 15:20:41 2006 -0800
@@ -1,4 +1,4 @@
-// Game_Music_Emu 0.5.1. http://www.slack.net/~ant/
+// Game_Music_Emu 0.5.2. http://www.slack.net/~ant/
 
 // Based on Brad Martin's OpenSPC DSP emulator
 
@@ -31,7 +31,8 @@
 	mute_voices( 0 );
 	disable_surround( false );
 	
-	BOOST_STATIC_ASSERT( sizeof (g) == register_count && sizeof (voice) == register_count );
+	assert( offsetof (globals_t,unused9 [2]) == register_count );
+	assert( sizeof (voice) == register_count );
 	blargg_verify_byte_order();
 }
 
--- a/src/console/Spc_Dsp.h	Wed Dec 06 07:57:05 2006 -0800
+++ b/src/console/Spc_Dsp.h	Thu Dec 07 15:20:41 2006 -0800
@@ -1,6 +1,6 @@
 // Super Nintendo (SNES) SPC DSP emulator
 
-// Game_Music_Emu 0.5.1
+// Game_Music_Emu 0.5.2
 #ifndef SPC_DSP_H
 #define SPC_DSP_H
 
@@ -52,37 +52,37 @@
 		int8_t  unused [6];
 	};
 	
+	struct globals_t {
+		int8_t  unused1 [12];
+		int8_t  left_volume;        // 0C   Main Volume Left (-.7)
+		int8_t  echo_feedback;      // 0D   Echo Feedback (-.7)
+		int8_t  unused2 [14];
+		int8_t  right_volume;       // 1C   Main Volume Right (-.7)
+		int8_t  unused3 [15];
+		int8_t  left_echo_volume;   // 2C   Echo Volume Left (-.7)
+		uint8_t pitch_mods;         // 2D   Pitch Modulation on/off for each voice
+		int8_t  unused4 [14];
+		int8_t  right_echo_volume;  // 3C   Echo Volume Right (-.7)
+		uint8_t noise_enables;      // 3D   Noise output on/off for each voice
+		int8_t  unused5 [14];
+		uint8_t key_ons;            // 4C   Key On for each voice
+		uint8_t echo_ons;           // 4D   Echo on/off for each voice
+		int8_t  unused6 [14];
+		uint8_t key_offs;           // 5C   key off for each voice (instantiates release mode)
+		uint8_t wave_page;          // 5D   source directory (wave table offsets)
+		int8_t  unused7 [14];
+		uint8_t flags;              // 6C   flags and noise freq
+		uint8_t echo_page;          // 6D
+		int8_t  unused8 [14];
+		uint8_t wave_ended;         // 7C
+		uint8_t echo_delay;         // 7D   ms >> 4
+		char    unused9 [2];
+	};
+	
 	union {
 		raw_voice_t voice [voice_count];
-		
 		uint8_t reg [register_count];
-		
-		struct {
-			int8_t  unused1 [12];
-			int8_t  left_volume;        // 0C   Main Volume Left (-.7)
-			int8_t  echo_feedback;      // 0D   Echo Feedback (-.7)
-			int8_t  unused2 [14];
-			int8_t  right_volume;       // 1C   Main Volume Right (-.7)
-			int8_t  unused3 [15];
-			int8_t  left_echo_volume;   // 2C   Echo Volume Left (-.7)
-			uint8_t pitch_mods;         // 2D   Pitch Modulation on/off for each voice
-			int8_t  unused4 [14];
-			int8_t  right_echo_volume;  // 3C   Echo Volume Right (-.7)
-			uint8_t noise_enables;      // 3D   Noise output on/off for each voice
-			int8_t  unused5 [14];
-			uint8_t key_ons;            // 4C   Key On for each voice
-			uint8_t echo_ons;           // 4D   Echo on/off for each voice
-			int8_t  unused6 [14];
-			uint8_t key_offs;           // 5C   key off for each voice (instantiates release mode)
-			uint8_t wave_page;          // 5D   source directory (wave table offsets)
-			int8_t  unused7 [14];
-			uint8_t flags;              // 6C   flags and noise freq
-			uint8_t echo_page;          // 6D
-			int8_t  unused8 [14];
-			uint8_t wave_ended;         // 7C
-			uint8_t echo_delay;         // 7D   ms >> 4
-			char    unused9 [2];
-		} g;
+		globals_t g;
 	};
 	
 	uint8_t* const ram;
--- a/src/console/Spc_Emu.cxx	Wed Dec 06 07:57:05 2006 -0800
+++ b/src/console/Spc_Emu.cxx	Thu Dec 07 15:20:41 2006 -0800
@@ -1,4 +1,4 @@
-// Game_Music_Emu 0.5.1. http://www.slack.net/~ant/
+// Game_Music_Emu 0.5.2. http://www.slack.net/~ant/
 
 #include "Spc_Emu.h"
 
@@ -218,14 +218,14 @@
 		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( in.read( &header, Spc_Emu::header_size ) );
 		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.skip( xid6_offset - Spc_Emu::header_size ) );
 			RETURN_ERR( in.read( xid6.begin(), xid6.size() ) );
 		}
 		return 0;
@@ -264,6 +264,7 @@
 
 blargg_err_t Spc_Emu::load_mem_( byte const* in, long size )
 {
+	assert( offsetof (header_t,unused2 [46]) == header_size );
 	file_data = in;
 	file_size = size;
 	set_voice_count( Snes_Spc::voice_count );
@@ -291,8 +292,7 @@
 	{
 		count = long (count * resampler.ratio()) & ~1;
 		count -= resampler.skip_input( count );
-	} 
-
+	}
 	if ( count > 0 )
 		RETURN_ERR( apu.skip( count ) );
 	
--- a/src/console/Spc_Emu.h	Wed Dec 06 07:57:05 2006 -0800
+++ b/src/console/Spc_Emu.h	Thu Dec 07 15:20:41 2006 -0800
@@ -1,6 +1,6 @@
 // Super Nintendo SPC music file emulator
 
-// Game_Music_Emu 0.5.1
+// Game_Music_Emu 0.5.2
 #ifndef SPC_EMU_H
 #define SPC_EMU_H
 
@@ -15,6 +15,7 @@
 	enum { native_sample_rate = 32000 };
 	
 	// SPC file header
+	enum { header_size = 0x100 };
 	struct header_t
 	{
 		char tag [35];
@@ -35,7 +36,6 @@
 		byte emulator;
 		byte unused2 [46];
 	};
-	BOOST_STATIC_ASSERT( sizeof (header_t) == 0x100 );
 	
 	// Header for currently loaded file
 	header_t const& header() const { return *(header_t const*) file_data; }
--- a/src/console/Vfs_File.cxx	Wed Dec 06 07:57:05 2006 -0800
+++ b/src/console/Vfs_File.cxx	Thu Dec 07 15:20:41 2006 -0800
@@ -43,7 +43,7 @@
 	if ( n == 0 ) // optimization
 		vfs_rewind( file_ );
 	else if ( vfs_fseek( file_, n, SEEK_SET ) != 0 )
-		return "Error seeking in file";
+		return eof_error;
 	return 0;
 }
 
--- a/src/console/Vfs_File.h	Wed Dec 06 07:57:05 2006 -0800
+++ b/src/console/Vfs_File.h	Thu Dec 07 15:20:41 2006 -0800
@@ -1,4 +1,4 @@
-// File_Reader based on a VFSFile
+// File_Reader wrapper over a VFSFile
 
 #ifndef VFS_FILE_H
 #define VFS_FILE_H
--- a/src/console/Vgm_Emu.cxx	Wed Dec 06 07:57:05 2006 -0800
+++ b/src/console/Vgm_Emu.cxx	Thu Dec 07 15:20:41 2006 -0800
@@ -1,4 +1,4 @@
-// Game_Music_Emu 0.5.1. http://www.slack.net/~ant/
+// Game_Music_Emu 0.5.2. http://www.slack.net/~ant/
 
 #include "Vgm_Emu.h"
 
@@ -108,7 +108,7 @@
 	if ( gd3_offset < 0 )
 		return 0;
 	
-	byte const* gd3 = data + sizeof (header_t) + gd3_offset;
+	byte const* gd3 = data + header_size + gd3_offset;
 	long gd3_size = check_gd3_header( gd3, data_end - gd3 );
 	if ( !gd3_size )
 		return 0;
@@ -168,14 +168,14 @@
 	blargg_err_t load_( Data_Reader& in )
 	{
 		long file_size = in.remain();
-		if ( file_size <= (long) sizeof h )
+		if ( file_size <= Vgm_Emu::header_size )
 			return gme_wrong_file_type;
 		
-		RETURN_ERR( in.read( &h, sizeof h ) );
+		RETURN_ERR( in.read( &h, Vgm_Emu::header_size ) );
 		RETURN_ERR( check_vgm_header( h ) );
 		
 		long gd3_offset = get_le32( h.gd3_offset ) - 0x2C;
-		long remain = file_size - sizeof h - gd3_offset;
+		long remain = file_size - Vgm_Emu::header_size - gd3_offset;
 		byte gd3_h [gd3_header_size];
 		if ( gd3_offset > 0 || remain >= gd3_header_size )
 		{
@@ -269,7 +269,9 @@
 
 blargg_err_t Vgm_Emu::load_mem_( byte const* new_data, long new_size )
 {
-	if ( new_size <= (long) sizeof (header_t) )
+	assert( offsetof (header_t,unused2 [8]) == header_size );
+	
+	if ( new_size <= header_size )
 		return gme_wrong_file_type;
 	
 	header_t const& h = *(header_t const*) new_data;
@@ -365,7 +367,7 @@
 	psg.reset( get_le16( header().noise_feedback ), header().noise_width );
 	
 	dac_disabled = -1;
-	pos          = data + sizeof (header_t);
+	pos          = data + header_size;
 	pcm_data     = pos;
 	pcm_pos      = pos;
 	dac_amp      = -1;
--- a/src/console/Vgm_Emu.h	Wed Dec 06 07:57:05 2006 -0800
+++ b/src/console/Vgm_Emu.h	Thu Dec 07 15:20:41 2006 -0800
@@ -1,6 +1,6 @@
 // Sega Master System/Mark III, Sega Genesis/Mega Drive, BBC Micro VGM music file emulator
 
-// Game_Music_Emu 0.5.1
+// Game_Music_Emu 0.5.2
 #ifndef VGM_EMU_H
 #define VGM_EMU_H
 
@@ -22,6 +22,7 @@
 	void disable_oversampling( bool disable = true ) { disable_oversampling_ = disable; }
 	
 	// VGM header format
+	enum { header_size = 0x40 };
 	struct header_t
 	{
 		char tag [4];
@@ -42,7 +43,6 @@
 		byte data_offset [4];
 		byte unused2 [8];
 	};
-	BOOST_STATIC_ASSERT( sizeof (header_t) == 0x40 );
 	
 	// Header for currently loaded file
 	header_t const& header() const { return *(header_t const*) data; }
--- a/src/console/Vgm_Emu_Impl.cxx	Wed Dec 06 07:57:05 2006 -0800
+++ b/src/console/Vgm_Emu_Impl.cxx	Thu Dec 07 15:20:41 2006 -0800
@@ -1,4 +1,4 @@
-// Game_Music_Emu 0.5.1. http://www.slack.net/~ant/
+// Game_Music_Emu 0.5.2. http://www.slack.net/~ant/
 
 #include "Vgm_Emu.h"
 
--- a/src/console/Vgm_Emu_Impl.h	Wed Dec 06 07:57:05 2006 -0800
+++ b/src/console/Vgm_Emu_Impl.h	Thu Dec 07 15:20:41 2006 -0800
@@ -1,6 +1,6 @@
 // Low-level parts of Vgm_Emu
 
-// Game_Music_Emu 0.5.1
+// Game_Music_Emu 0.5.2
 #ifndef VGM_EMU_IMPL_H
 #define VGM_EMU_IMPL_H
 
--- a/src/console/Ym2413_Emu.cxx	Wed Dec 06 07:57:05 2006 -0800
+++ b/src/console/Ym2413_Emu.cxx	Thu Dec 07 15:20:41 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.5.1. http://www.slack.net/~ant/
+// Game_Music_Emu 0.5.2. http://www.slack.net/~ant/
 
 #include "Ym2413_Emu.h"
 
--- a/src/console/Ym2413_Emu.h	Wed Dec 06 07:57:05 2006 -0800
+++ b/src/console/Ym2413_Emu.h	Thu Dec 07 15:20:41 2006 -0800
@@ -1,6 +1,6 @@
 // YM2413 FM sound chip emulator interface
 
-// Game_Music_Emu 0.5.1
+// Game_Music_Emu 0.5.2
 #ifndef YM2413_EMU_H
 #define YM2413_EMU_H
 
--- a/src/console/Ym2612_Emu.cxx	Wed Dec 06 07:57:05 2006 -0800
+++ b/src/console/Ym2612_Emu.cxx	Thu Dec 07 15:20:41 2006 -0800
@@ -1,4 +1,4 @@
-// Game_Music_Emu 0.5.1. http://www.slack.net/~ant/
+// Game_Music_Emu 0.5.2. http://www.slack.net/~ant/
 
 // Based on Gens 2.10 ym2612.c
 
@@ -741,10 +741,7 @@
 		double x = i * 3;           // 3 and not 6 (Mickey Mania first music for test)
 		x /= ENV_STEP;
 
-		int j = (int) x;
-		j <<= ENV_LBITS;
-
-		g.SL_TAB [i] = j + ENV_DECAY;
+		g.SL_TAB [i] = ((int) x << ENV_LBITS) + ENV_DECAY;
 	}
 
 	g.SL_TAB [15] = ((ENV_LENGHT - 1) << ENV_LBITS) + ENV_DECAY; // special case : volume off
@@ -804,13 +801,13 @@
 		for (int j = 0; j < 32; j++)
 		{
 #if ((SIN_LBITS + SIN_HBITS - 21) < 0)
-			double x = (double) DT_DEF_TAB [(i << 5) + j] * Frequence / (double) (1 << (21 - SIN_LBITS - SIN_HBITS));
+			double y = (double) DT_DEF_TAB [(i << 5) + j] * Frequence / (double) (1 << (21 - SIN_LBITS - SIN_HBITS));
 #else
-			double x = (double) DT_DEF_TAB [(i << 5) + j] * Frequence * (double) (1 << (SIN_LBITS + SIN_HBITS - 21));
+			double y = (double) DT_DEF_TAB [(i << 5) + j] * Frequence * (double) (1 << (SIN_LBITS + SIN_HBITS - 21));
 #endif
 
-			g.DT_TAB [i + 0] [j] = (int) x;
-			g.DT_TAB [i + 4] [j] = (int) -x;
+			g.DT_TAB [i + 0] [j] = (int)  y;
+			g.DT_TAB [i + 4] [j] = (int) -y;
 		}
 	}
 	
--- a/src/console/Ym2612_Emu.h	Wed Dec 06 07:57:05 2006 -0800
+++ b/src/console/Ym2612_Emu.h	Thu Dec 07 15:20:41 2006 -0800
@@ -1,6 +1,6 @@
 // YM2612 FM sound chip emulator interface
 
-// Game_Music_Emu 0.5.1
+// Game_Music_Emu 0.5.2
 #ifndef YM2612_EMU_H
 #define YM2612_EMU_H
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/console/Zlib_Inflater.cxx	Thu Dec 07 15:20:41 2006 -0800
@@ -0,0 +1,175 @@
+// File_Extractor 0.4.0. http://www.slack.net/~ant/
+
+#include "Zlib_Inflater.h"
+
+#include <assert.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"
+
+static const char* get_zlib_err( int code )
+{
+	assert( code != Z_OK );
+	if ( code == Z_MEM_ERROR )
+		return "Out of memory";
+	
+	const char* str = zError( code );
+	if ( code == Z_DATA_ERROR )
+		str = "Zip data is corrupt";
+	if ( !str )
+		str = "Zip error";
+	return str;
+}
+
+void Zlib_Inflater::end()
+{
+	if ( deflated_ )
+	{
+		deflated_ = false;
+		if ( inflateEnd( &zbuf ) )
+			check( false );
+	}
+	buf.clear();
+	
+	static z_stream const empty = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
+	memcpy( &zbuf, &empty, sizeof zbuf );
+}
+
+Zlib_Inflater::Zlib_Inflater()
+{
+	deflated_ = false;
+	end(); // initialize things
+}
+
+Zlib_Inflater::~Zlib_Inflater()
+{
+	end();
+}
+
+blargg_err_t Zlib_Inflater::begin( mode_t mode, callback_t callback, void* user_data,
+		long buf_size )
+{
+	end();
+	
+	if ( buf_size && buf.resize( buf_size ) )
+		buf_size = 0; // not enough memory for requested size, so use default
+	
+	if ( !buf_size )
+		RETURN_ERR( buf.resize( 16 * 1024L ) );
+	
+	// Fill buffer with some data, less than normal buffer size since caller might
+	// just be examining beginning of file. 4K is a common disk block size.
+	long count = (buf_size ? buf_size : 4096);
+	RETURN_ERR( callback( user_data, buf.begin(), &count ) );
+	zbuf.avail_in = count;
+	zbuf.next_in  = buf.begin();
+	
+	if ( mode == mode_auto )
+	{
+		// examine buffer for gzip header
+		mode = mode_copy;
+		int const min_gzip_size = 2 + 8 + 8;
+		if ( count >= min_gzip_size && buf [0] == 0x1F && buf [1] == 0x8B )
+			mode = mode_ungz;
+	}
+	
+	if ( mode != mode_copy )
+	{
+		int wb = MAX_WBITS + 16; // have zlib handle gzip header
+		if ( mode == mode_raw_deflate )
+			wb = -MAX_WBITS;
+		
+		int zerr = inflateInit2( &zbuf, wb );
+		if ( zerr )
+			return get_zlib_err( zerr );
+		
+		deflated_ = true;
+	}
+	return 0;
+}
+
+
+blargg_err_t Zlib_Inflater::read( void* out, long* count_io,
+		callback_t callback, void* user_data )
+{
+	if ( !*count_io )
+		return 0;
+	
+	if ( !deflated_ )
+	{
+		// copy buffered data
+		long first = zbuf.avail_in;
+		if ( first )
+		{
+			if ( first > *count_io )
+				first = *count_io;
+			memcpy( out, zbuf.next_in, first );
+			zbuf.next_in  += first;
+			zbuf.avail_in -= first;
+			if ( !zbuf.avail_in )
+				buf.clear(); // done with buffer
+		}
+		
+		// read remaining directly
+		long second = *count_io - first;
+		if ( second )
+		{
+			long actual = second;
+			RETURN_ERR( callback( user_data, (char*) out + first, &actual ) );
+			*count_io -= second - actual;
+		}
+	}
+	else
+	{
+		zbuf.next_out  = (Bytef*) out;
+		zbuf.avail_out = *count_io;
+		
+		while ( 1 )
+		{
+			uInt old_avail_in = zbuf.avail_in;
+			int err = inflate( &zbuf, Z_NO_FLUSH );
+			if ( err == Z_STREAM_END )
+			{
+				*count_io -= zbuf.avail_out;
+				end();
+				break; // all data deflated
+			}
+			
+			if ( err == Z_BUF_ERROR && !old_avail_in )
+				err = 0; // we just need to provide more input
+			
+			if ( err )
+				return get_zlib_err( err );
+			
+			if ( !zbuf.avail_out )
+				break; // requested number of bytes deflated
+			
+			if ( zbuf.avail_in )
+			{
+				// inflate() should never leave input if there's still space for output
+				assert( false );
+				return "Corrupt zip data";
+			}
+			
+			// refill buffer
+			long count = buf.size();
+			RETURN_ERR( callback( user_data, buf.begin(), &count ) );
+			zbuf.next_in  = buf.begin();
+			zbuf.avail_in = count;
+			if ( !zbuf.avail_in )
+				return "Corrupt zip data"; // stream didn't end but there's no more data
+		}
+	}
+	return 0;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/console/Zlib_Inflater.h	Thu Dec 07 15:20:41 2006 -0800
@@ -0,0 +1,48 @@
+// Simplifies use of zlib for deflating data
+
+// File_Extractor 0.4.0
+#ifndef ZLIB_INFLATER_H
+#define ZLIB_INFLATER_H
+
+#include "blargg_common.h"
+#include "zlib.h"
+
+class Zlib_Inflater {
+public:
+	
+	// Data reader callback
+	typedef blargg_err_t (*callback_t)( void* user_data, void* out, long* count );
+	
+	// Begin inflation using specified mode and fill buffer using read callback.
+	// Default buffer is 16K and filled to 4K, or specify buf_size for custom
+	// buffer size and to read that much file data. Using mode_auto selects
+	// between mode_copy and mode_ungz by examining first two bytes of buffer. 
+	enum mode_t { mode_copy, mode_ungz, mode_raw_deflate, mode_auto };
+	blargg_err_t begin( mode_t, callback_t, void* user_data, long buf_size = 0 );
+	
+	// True if begin() has been called with mode_ungz or mode_raw_deflate
+	bool deflated() const { return deflated_; }
+	
+	// Read/inflate at most *count_io bytes into out and set *count_io to actual
+	// number of bytes read (less than requested if end of deflated data is reached).
+	// Keeps buffer full with user-provided callback.
+	blargg_err_t read( void* out, long* count_io, callback_t, void* user_data );
+	
+	// End inflation and free memory
+	void end();
+	
+private:
+	// noncopyable
+	Zlib_Inflater( const Zlib_Inflater& );
+	Zlib_Inflater& operator = ( const Zlib_Inflater& );
+
+public:
+	Zlib_Inflater();
+	~Zlib_Inflater();
+private:
+	z_stream_s zbuf;
+	blargg_vector<unsigned char> buf;
+	bool deflated_;
+};
+
+#endif
--- a/src/console/blargg_common.h	Wed Dec 06 07:57:05 2006 -0800
+++ b/src/console/blargg_common.h	Thu Dec 07 15:20:41 2006 -0800
@@ -54,8 +54,13 @@
 };
 
 #ifndef BLARGG_DISABLE_NOTHROW
+	#if __cplusplus < 199711
+		#define BLARGG_THROWS( spec )
+	#else
+		#define BLARGG_THROWS( spec ) throw spec
+	#endif
 	#define BLARGG_DISABLE_NOTHROW \
-		void* operator new ( size_t s ) { return malloc( s ); }\
+		void* operator new ( size_t s ) BLARGG_THROWS(()) { return malloc( s ); }\
 		void operator delete ( void* p ) { free( p ); }
 	#define BLARGG_NEW new
 #else
--- a/src/console/blargg_config.h	Wed Dec 06 07:57:05 2006 -0800
+++ b/src/console/blargg_config.h	Thu Dec 07 15:20:41 2006 -0800
@@ -1,12 +1,9 @@
-// Game_Music_Emu 0.5.1 user configuration file. Don't replace when updating library.
+// Custom Audacious configuration
 
 #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.
+// Uncomment to enable platform-specific (and possibly non-portable) optimizations
 //#define BLARGG_NONPORTABLE 1
 
 // Uncomment if automatic byte-order determination doesn't work
@@ -15,12 +12,11 @@
 // 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
+// Use non-exception-throwing operator new
 #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"
+// Have GME use VFS file access
 #define GME_FILE_READER Vfs_File_Reader
+#define GME_FILE_READER_INCLUDE "Vfs_File.h"
 
 #endif
--- a/src/console/blargg_endian.h	Wed Dec 06 07:57:05 2006 -0800
+++ b/src/console/blargg_endian.h	Thu Dec 07 15:20:41 2006 -0800
@@ -1,6 +1,6 @@
 // CPU Byte Order Utilities
 
-// Game_Music_Emu 0.5.1
+// Game_Music_Emu 0.5.2
 #ifndef BLARGG_ENDIAN
 #define BLARGG_ENDIAN
 
--- a/src/console/gb_cpu_io.h	Wed Dec 06 07:57:05 2006 -0800
+++ b/src/console/gb_cpu_io.h	Thu Dec 07 15:20:41 2006 -0800
@@ -26,7 +26,10 @@
 		if ( (addr ^ 0xE000) <= 0x1F80 - 1 )
 		{
 			if ( unsigned (addr - Gb_Apu::start_addr) < Gb_Apu::register_count )
+			{
+				GME_APU_HOOK( this, addr - Gb_Apu::start_addr, data );
 				apu.write_register( clock(), addr, data );
+			}
 			else if ( (addr ^ 0xFF06) < 2 )
 				update_timer();
 			else if ( addr == joypad_addr )
--- a/src/console/gme.cxx	Wed Dec 06 07:57:05 2006 -0800
+++ b/src/console/gme.cxx	Thu Dec 07 15:20:41 2006 -0800
@@ -1,8 +1,10 @@
-// Game_Music_Emu 0.5.1. http://www.slack.net/~ant/
+// Game_Music_Emu 0.5.2. http://www.slack.net/~ant/
 
 #include "Music_Emu.h"
 
+#if !GME_DISABLE_STEREO_DEPTH
 #include "Effects_Buffer.h"
+#endif
 #include "blargg_endian.h"
 #include <string.h>
 #include <ctype.h>
@@ -20,6 +22,32 @@
 
 #include "blargg_source.h"
 
+#ifndef GME_TYPE_LIST
+
+// Default list of all supported game music types (copy this to blargg_config.h
+// if you want to modify it)
+#define GME_TYPE_LIST \
+	gme_ay_type,\
+	gme_gbs_type,\
+	gme_gym_type,\
+	gme_hes_type,\
+	gme_kss_type,\
+	gme_nsf_type,\
+	gme_nsfe_type,\
+	gme_sap_type,\
+	gme_spc_type,\
+	gme_vgm_type,\
+	gme_vgz_type
+
+#endif
+
+static gme_type_t const gme_type_list_ [] = { GME_TYPE_LIST, 0 };
+
+gme_type_t const* gme_type_list()
+{
+	return gme_type_list_;
+}
+
 const char* gme_identify_header( void const* header )
 {
 	switch ( get_be32( header ) )
@@ -49,7 +77,7 @@
 	*out = 0; // extension too long
 }
 
-gme_type_t gme_identify_extension( const char* extension_, gme_type_t const* types )
+gme_type_t gme_identify_extension( const char* extension_ )
 {
 	char const* end = strrchr( extension_, '.' );
 	if ( end )
@@ -58,33 +86,99 @@
 	char extension [6];
 	to_uppercase( extension_, sizeof extension, extension );
 	
-	for ( ; *types; types++ )
+	for ( gme_type_t const* types = gme_type_list_; *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 )
+gme_err_t gme_identify_file( const char* path, gme_type_t* type_out )
 {
-	*type_out = gme_identify_extension( path, types );
+	*type_out = gme_identify_extension( path );
+	// TODO: don't examine header if file has extension?
 	if ( !*type_out )
 	{
-		char header [4] = { };
+		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 );
+		*type_out = gme_identify_extension( gme_identify_header( header ) );
 	}
 	return 0;   
 }
 
+gme_err_t gme_open_data( void const* data, long size, Music_Emu** out, long sample_rate )
+{
+	require( (data || !size) && out );
+	*out = 0;
+	
+	gme_type_t file_type = 0;
+	if ( size >= 4 )
+		file_type = gme_identify_extension( gme_identify_header( data ) );
+	if ( !file_type )
+		return gme_wrong_file_type;
+	
+	Music_Emu* emu = gme_new_emu( file_type, sample_rate );
+	CHECK_ALLOC( emu );
+	
+	gme_err_t err = gme_load_data( emu, data, size );
+	
+	if ( err )
+		delete emu;
+	else
+		*out = emu;
+	
+	return err;
+}
+
+gme_err_t gme_open_file( const char* path, Music_Emu** out, long sample_rate )
+{
+	require( path && out );
+	*out = 0;
+	
+	GME_FILE_READER in;
+	RETURN_ERR( in.open( path ) );
+	
+	char header [4];
+	int header_size = 0;
+	
+	gme_type_t file_type = gme_identify_extension( path );
+	if ( !file_type )
+	{
+		header_size = sizeof header;
+		RETURN_ERR( in.read( header, sizeof header ) );
+		file_type = gme_identify_extension( gme_identify_header( header ) );
+	}
+	if ( !file_type )
+		return gme_wrong_file_type;
+	
+	Music_Emu* emu = gme_new_emu( file_type, sample_rate );
+	CHECK_ALLOC( emu );
+	
+	// optimization: avoids seeking/re-reading header
+	Remaining_Reader rem( header, header_size, &in );
+	gme_err_t err = emu->load( rem );
+	in.close();
+	
+	if ( err )
+		delete emu;
+	else
+		*out = emu;
+	
+	return err;
+}
+
 Music_Emu* gme_new_emu( gme_type_t type, long rate )
 {
 	if ( type )
 	{
+		if ( rate == gme_info_only )
+			return type->new_info();
+		
 		Music_Emu* me = type->new_emu();
 		if ( me )
 		{
+		#if !GME_DISABLE_STEREO_DEPTH
 			if ( type->flags_ & 1 )
 			{
 				me->effects_buffer = BLARGG_NEW Effects_Buffer;
@@ -93,6 +187,7 @@
 			}
 			
 			if ( !(type->flags_ & 1) || me->effects_buffer )
+		#endif
 			{
 				if ( !me->set_sample_rate( rate ) )
 				{
@@ -106,17 +201,9 @@
 	return 0;
 }
 
-Music_Emu* gme_new_info( gme_type_t type )
-{
-	if ( !type )
-		return 0;
-	
-	return type->new_info();
-}
+gme_err_t gme_load_file( Music_Emu* me, const char* path ) { return me->load_file( path ); }
 
-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 )
+gme_err_t gme_load_data( Music_Emu* me, void const* data, long size )
 {
 	Mem_File_Reader in( data, size );
 	return me->load( in );
@@ -143,34 +230,27 @@
 
 void gme_set_stereo_depth( Music_Emu* me, double depth )
 {
+#if !GME_DISABLE_STEREO_DEPTH
 	if ( me->effects_buffer )
 		STATIC_CAST(Effects_Buffer*,me->effects_buffer)->set_depth( depth );
+#endif
 }
 
-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(); }
+void*     gme_user_data      ( Music_Emu const* me )                { return me->user_data(); }
+void      gme_set_user_data  ( Music_Emu* me, void* new_user_data ) { me->set_user_data( new_user_data ); }
+void      gme_set_user_cleanup(Music_Emu* me, gme_user_cleanup_t func ) { me->set_user_cleanup( func ); }
 
-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 ); }
+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(); }
+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 ); }
+void      gme_set_equalizer  ( Music_Emu* me, gme_equalizer_t const* eq ) { me->set_equalizer( *eq ); }
+gme_equalizer_t gme_equalizer( Music_Emu const* me )                { return me->equalizer(); }
+const char** gme_voice_names ( Music_Emu const* me )                { return me->voice_names(); }
--- a/src/console/gme.h	Wed Dec 06 07:57:05 2006 -0800
+++ b/src/console/gme.h	Thu Dec 07 15:20:41 2006 -0800
@@ -1,6 +1,6 @@
 /* Game music emulator library C interface (also usable from C++) */
 
-/* Game_Music_Emu 0.5.1 */
+/* Game_Music_Emu 0.5.2 */
 #ifndef GME_H
 #define GME_H
 
@@ -15,101 +15,61 @@
 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;
+/******** Basic operations ********/
 
-/* 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 );
+/* Create emulator and load game music file/data into it. Sets *out to new emulator. */
+gme_err_t gme_open_file( const char* path, Music_Emu** out, long sample_rate );
 
-/* 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_;
-};
-
+/* Number of tracks available */
+int gme_track_count( Music_Emu const* );
 
-/******** 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 );
+/* Start a track, where 0 is the first track */
+gme_err_t gme_start_track( Music_Emu*, int index );
 
-/* 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* );
+/* Generate 'count' 16-bit signed samples info 'out'. Output is in stereo. */
+gme_err_t gme_play( Music_Emu*, long count, short* out );
 
 /* Finish using emulator and free memory */
 void gme_delete( Music_Emu* );
 
 
-/******** File loading ********/
+/******** Track position/length ********/
+
+/* Set time to start fading track 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 );
 
-/* Load music file */
-gme_err_t gme_load_file( Music_Emu*, const char* path );
+/* True if a track has reached its end */
+int gme_track_ended( Music_Emu const* );
+
+/* Number of milliseconds (1000 = one second) played since beginning of track */
+long gme_tell( Music_Emu const* );
 
-/* Load music file from memory. Makes a copy of data passed. */
-gme_err_t gme_load_data( Music_Emu*, const void* data, long size );
+/* Seek to new time in track. Seeking backwards or far forward can take a while. */
+gme_err_t gme_seek( Music_Emu*, long msec );
+
+
+/******** Informational ********/
 
-/* 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 );
+/* If you only need track information from a music file, pass gme_info_only for
+sample_rate to open/load. */
+enum { gme_info_only = -1 };
 
-/* Load m3u playlist file (must be done after loading file) */
+/* Most recent warning string, or NULL if none. Clears current warning after returning.
+Warning is also cleared when loading a file and starting a track. */
+const char* gme_warning( Music_Emu* );
+
+/* Load m3u playlist file (must be done after loading music) */
 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
+/* Clear 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;
@@ -128,29 +88,7 @@
 	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 );
+enum { gme_max_field = 255 };
 
 
 /******** Advanced playback ********/
@@ -194,6 +132,89 @@
 void gme_set_equalizer( Music_Emu*, gme_equalizer_t const* eq );
 
 
+
+/******** 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;
+
+/* Type of this emulator */
+gme_type_t gme_type( Music_Emu const* );
+
+/* 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_;
+};
+
+/* Pointer to array of all 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();
+
+
+/******** Advanced file loading ********/
+
+/* Error returned if file type is not supported */
+extern const char gme_wrong_file_type [];
+
+/* Same as gme_open_file(), but uses file data already in memory. Makes copy of data. */
+gme_err_t gme_open_data( void const* data, long size, Music_Emu** out, long sample_rate );
+
+/* 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 );
+
+/* Get corresponding music type for file path or extension passed in. */
+gme_type_t gme_identify_extension( const char* path_or_extension );
+
+/* Determine file type based on file's extension or header (if extension isn't recognized).
+Sets *type_out to type, or 0 if unrecognized or error. */
+gme_err_t gme_identify_file( const char* path, gme_type_t* type_out );
+
+/* Create new emulator and set sample rate. Returns NULL if out of memory. If you only need
+track information, pass gme_info_only for sample_rate. */
+Music_Emu* gme_new_emu( gme_type_t, long sample_rate );
+
+/* Load music file into emulator */
+gme_err_t gme_load_file( Music_Emu*, const char* path );
+
+/* Load music file from memory into emulator. Makes a copy of data passed. */
+gme_err_t gme_load_data( Music_Emu*, void const* 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 read 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 from memory (must be done after loading music) */
+gme_err_t gme_load_m3u_data( Music_Emu*, void const* data, long size );
+
+
+/******** User data ********/
+
+/* Set/get pointer to data you want to associate with this emulator.
+You can use this for whatever you want. */
+void  gme_set_user_data( Music_Emu*, void* new_user_data );
+void* gme_user_data( Music_Emu const* );
+
+/* Register cleanup function to be called when deleting emulator, or NULL to
+clear it. Passes user_data to cleanup function. */
+typedef void (*gme_user_cleanup_t)( void* user_data );
+void gme_set_user_cleanup( Music_Emu*, gme_user_cleanup_t func );
+
+
 #ifdef __cplusplus
 	}
 #endif
--- a/src/console/gme_design.txt	Wed Dec 06 07:57:05 2006 -0800
+++ b/src/console/gme_design.txt	Thu Dec 07 15:20:41 2006 -0800
@@ -1,4 +1,4 @@
-Game_Music_Emu 0.5.1 Design
+Game_Music_Emu 0.5.2 Design
 ---------------------------
 
 Architecture
--- a/src/console/gme_notes.txt	Wed Dec 06 07:57:05 2006 -0800
+++ b/src/console/gme_notes.txt	Thu Dec 07 15:20:41 2006 -0800
@@ -1,4 +1,4 @@
-Game_Music_Emu 0.5.1
+Game_Music_Emu 0.5.2
 --------------------
 Author : Shay Green <gblargg@gmail.com>
 Website: http://www.slack.net/~ant/libs/
@@ -31,6 +31,7 @@
 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:
 
+** TODO: things are simpler now
 * 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()
--- a/src/console/gme_readme.txt	Wed Dec 06 07:57:05 2006 -0800
+++ b/src/console/gme_readme.txt	Thu Dec 07 15:20:41 2006 -0800
@@ -1,9 +1,9 @@
-Game_Music_Emu 0.5.1: Game Music Emulators
+Game_Music_Emu 0.5.2: Game Music Emulators
 ------------------------------------------
 Game_Music_Emu is a collection of video game music file emulators that
 support the following formats and systems:
 
-AY        Sinclair Spectrum
+AY        ZX Spectrum/Amstrad CPC
 GBS       Nintendo Game Boy
 GYM       Sega Genesis/Mega Drive
 HES       NEC TurboGrafx-16/PC Engine
@@ -79,7 +79,6 @@
 
   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
@@ -91,7 +90,7 @@
   M3u_Playlist.h      M3U playlist support
   M3u_Playlist.cpp
 
-  Ay_Emu.h            Sinclair Spectrum AY emulator
+  Ay_Emu.h            ZX Spectrum AY emulator
   Ay_Emu.cpp
   Ay_Apu.cpp
   Ay_Apu.h
--- a/src/console/nes_cpu_io.h	Wed Dec 06 07:57:05 2006 -0800
+++ b/src/console/nes_cpu_io.h	Thu Dec 07 15:20:41 2006 -0800
@@ -61,6 +61,7 @@
 	
 	if ( unsigned (addr - Nes_Apu::start_addr) <= Nes_Apu::end_addr - Nes_Apu::start_addr )
 	{
+		GME_APU_HOOK( this, addr - Nes_Apu::start_addr, data );
 		apu.write_register( cpu::time(), addr, data );
 		return;
 	}
@@ -68,10 +69,10 @@
 	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() )
+		blargg_long offset = rom.mask_addr( data * (blargg_long) bank_size );
+		if ( offset >= rom.size() )
 			set_warning( "Invalid bank" );
-		cpu::map_code( (bank + 8) * bank_size, bank_size, rom.at_addr( addr ) );
+		cpu::map_code( (bank + 8) * bank_size, bank_size, rom.at_addr( offset ) );
 		return;
 	}
 	
--- a/src/console/readme.txt	Wed Dec 06 07:57:05 2006 -0800
+++ b/src/console/readme.txt	Thu Dec 07 15:20:41 2006 -0800
@@ -4,7 +4,7 @@
 1990s. It uses the Game_Music_Emu sound engine and supports the
 following file formats:
 
-AY        Sinclair Spectrum
+AY        ZX Spectrum/Amstrad CPC
 GBS       Nintendo Game Boy
 GYM       Sega Genesis/Mega Drive
 HES       NEC TurboGrafx-16/PC Engine
@@ -43,18 +43,15 @@
 
 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)
+Drive music plays fine). See the following discussion for a way to add
+support for this: http://www.smspower.org/forums/viewtopic.php?t=9321
 
 
 Source code notes
@@ -65,21 +62,13 @@
 * 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().
+* C++ exceptions and RTTI are not used or needed.
 
-* 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.
+* File types are determined based on the first four bytes of a file
+except for .gym files, some of which have no file header at all.
 
 * 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.
+specifies the third track. This is an internal thing, as the user should
+never see these modified paths.
--- a/src/console/sap_cpu_io.h	Wed Dec 06 07:57:05 2006 -0800
+++ b/src/console/sap_cpu_io.h	Thu Dec 07 15:20:41 2006 -0800
@@ -7,7 +7,7 @@
 
 void Sap_Emu::cpu_write( sap_addr_t addr, int data )
 {
-	mem [addr] = data;
+	mem.ram [addr] = data;
 	if ( (addr >> 8) == 0xD2 )
 		cpu_write_( addr, data );
 }
@@ -21,6 +21,6 @@
 	{
 		if ( (addr & 0xF900) == 0xD000 )
 			dprintf( "Unmapped read $%04X\n", addr );
-		return mem [addr];
+		return mem.ram [addr];
 	}
 #endif