diff src/console/Nsf_Emu.cxx @ 316:fb513e10174e trunk

[svn] - merge libconsole-blargg into mainline libconsole: + obsoletes plugins-ugly:sapplug
author nenolod
date Thu, 30 Nov 2006 19:54:33 -0800
parents 3da1b8942b8b
children 230decbfe9be
line wrap: on
line diff
--- a/src/console/Nsf_Emu.cxx	Wed Nov 29 14:42:11 2006 -0800
+++ b/src/console/Nsf_Emu.cxx	Thu Nov 30 19:54:33 2006 -0800
@@ -1,392 +1,279 @@
-
-// Game_Music_Emu 0.3.0. http://www.slack.net/~ant/
+// Game_Music_Emu 0.5.1. http://www.slack.net/~ant/
 
 #include "Nsf_Emu.h"
 
+#include "blargg_endian.h"
 #include <string.h>
 #include <stdio.h>
 
 #if !NSF_EMU_APU_ONLY
+	#include "Nes_Namco_Apu.h"
 	#include "Nes_Vrc6_Apu.h"
-	#include "Nes_Namco_Apu.h"
 	#include "Nes_Fme7_Apu.h"
 #endif
 
-#include "blargg_endian.h"
-
 /* Copyright (C) 2003-2006 Shay Green. This module is free software; you
 can redistribute it and/or modify it under the terms of the GNU Lesser
 General Public License as published by the Free Software Foundation; either
 version 2.1 of the License, or (at your option) any later version. This
 module is distributed in the hope that it will be useful, but WITHOUT ANY
 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
-FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
-more details. You should have received a copy of the GNU Lesser General
-Public License along with this module; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
-
-#include BLARGG_SOURCE_BEGIN
-
-#ifndef RUN_NES_CPU
-	#define RUN_NES_CPU( cpu, count ) cpu.run( count )
-#endif
-
-#ifndef NSF_BEGIN_FRAME
-	#define NSF_BEGIN_FRAME()
-#endif
+FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
+details. You should have received a copy of the GNU Lesser General Public
+License along with this module; if not, write to the Free Software Foundation,
+Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
 
-const unsigned low_mem_size = 0x800;
-const unsigned page_size = 0x1000;
-const long ram_size = 0x10000;
-const nes_addr_t rom_begin = 0x8000;
-const nes_addr_t bank_select_addr = 0x5ff8;
-const nes_addr_t exram_addr = bank_select_addr - (bank_select_addr % Nes_Cpu::page_size);
-const int master_clock_divisor = 12;
-
-const int vrc6_flag = 0x01;
-const int fds_flag = 0x04;
-const int namco_flag = 0x10;
-const int fme7_flag = 0x20;
-
-static BOOST::uint8_t unmapped_code [Nes_Cpu::page_size];
-
-Nes_Emu::equalizer_t const Nes_Emu::nes_eq     = {  -1.0, 80 };
-Nes_Emu::equalizer_t const Nes_Emu::famicom_eq = { -15.0, 80 };
-
-// ROM
+#include "blargg_source.h"
 
-int Nsf_Emu::read_code( Nsf_Emu* emu, nes_addr_t addr )
-{
-	return *emu->cpu.get_code( addr );
-}
+int const vrc6_flag  = 0x01;
+int const namco_flag = 0x10;
+int const fme7_flag  = 0x20;
 
-void Nsf_Emu::write_exram( Nsf_Emu* emu, nes_addr_t addr, int data )
-{
-	unsigned bank = addr - bank_select_addr;
-	if ( bank < bank_count )
-	{
-		if ( data < emu->total_banks )
-		{
-			emu->cpu.map_code( (bank + 8) * page_size, page_size,
-					&emu->rom [data * page_size] );
-		}
-		else
-		{
-			dprintf( "Bank %d out of range (%d banks total)\n",
-					data, (int) emu->total_banks );
-		}
-	}
-}
+long const clock_divisor = 12;
 
-// APU
-
-int Nsf_Emu::read_snd( Nsf_Emu* emu, nes_addr_t addr )
-{
-	if ( addr == Nes_Apu::status_addr )
-		return emu->apu.read_status( emu->cpu.time() );
-	return addr >> 8; // high byte of address stays on bus
-}
-
-void Nsf_Emu::write_snd( Nsf_Emu* emu, nes_addr_t addr, int data )
-{
-	if ( unsigned (addr - Nes_Apu::start_addr) <= Nes_Apu::end_addr - Nes_Apu::start_addr )
-		 emu->apu.write_register( emu->cpu.time(), addr, data );
-}
+Nsf_Emu::equalizer_t const Nsf_Emu::nes_eq     = {  -1.0, 80 };
+Nsf_Emu::equalizer_t const Nsf_Emu::famicom_eq = { -15.0, 80 };
 
 int Nsf_Emu::pcm_read( void* emu, nes_addr_t addr )
 {
-	return ((Nsf_Emu*) emu)->cpu.read( addr );
-}
-
-// Low Mem
-
-int Nsf_Emu::read_low_mem( Nsf_Emu* emu, nes_addr_t addr )
-{
-	return emu->cpu.low_mem [addr];
-}
-
-void Nsf_Emu::write_low_mem( Nsf_Emu* emu, nes_addr_t addr, int data )
-{
-	emu->cpu.low_mem [addr] = data;
-}
-
-// SRAM
-
-int Nsf_Emu::read_sram( Nsf_Emu* emu, nes_addr_t addr )
-{
-	return emu->sram [addr & (sram_size - 1)];
-}
-
-void Nsf_Emu::write_sram( Nsf_Emu* emu, nes_addr_t addr, int data )
-{
-	emu->sram [addr & (sram_size - 1)] = data;
-}
-
-#if !NSF_EMU_APU_ONLY
-
-// Namco
-int Nsf_Emu::read_namco( Nsf_Emu* emu, nes_addr_t addr )
-{
-	if ( addr == Nes_Namco_Apu::data_reg_addr )
-		return emu->namco->read_data();
-	return addr >> 8;
-}
-
-void Nsf_Emu::write_namco( Nsf_Emu* emu, nes_addr_t addr, int data )
-{
-	if ( addr == Nes_Namco_Apu::data_reg_addr )
-		emu->namco->write_data( emu->cpu.time(), data );
-}
-
-void Nsf_Emu::write_namco_addr( Nsf_Emu* emu, nes_addr_t addr, int data )
-{
-	if ( addr == Nes_Namco_Apu::addr_reg_addr )
-		emu->namco->write_addr( data );
-}
-
-// VRC6
-void Nsf_Emu::write_vrc6( Nsf_Emu* emu, nes_addr_t addr, int data )
-{
-	unsigned reg = addr & (Nes_Vrc6_Apu::addr_step - 1);
-	unsigned osc = unsigned (addr - Nes_Vrc6_Apu::base_addr) / Nes_Vrc6_Apu::addr_step;
-	if ( osc < Nes_Vrc6_Apu::osc_count && reg < Nes_Vrc6_Apu::reg_count )
-		emu->vrc6->write_osc( emu->cpu.time(), osc, reg, data );
+	return *((Nsf_Emu*) emu)->cpu::get_code( addr );
 }
 
-// FME-7
-void Nsf_Emu::write_fme7( Nsf_Emu* emu, nes_addr_t addr, int data )
+Nsf_Emu::Nsf_Emu()
 {
-	switch ( addr & Nes_Fme7_Apu::addr_mask )
-	{
-		case Nes_Fme7_Apu::latch_addr:
-			emu->fme7->write_latch( data );
-			break;
-		
-		case Nes_Fme7_Apu::data_addr:
-			emu->fme7->write_data( emu->cpu.time(), data );
-			break;
-	}
-}
-
-#endif
-
-// Unmapped
-int Nsf_Emu::read_unmapped( Nsf_Emu*, nes_addr_t addr )
-{
-	dprintf( "Read unmapped $%.4X\n", (unsigned) addr );
-	return (addr >> 8) & 0xff; // high byte of address stays on bus
+	vrc6  = 0;
+	namco = 0;
+	fme7  = 0;
+	
+	set_type( gme_nsf_type );
+	set_silence_lookahead( 6 );
+	apu.dmc_reader( pcm_read, this );
+	Music_Emu::set_equalizer( nes_eq );
+	set_gain( 1.4 );
+	memset( unmapped_code, Nes_Cpu::bad_opcode, sizeof unmapped_code );
 }
 
-void Nsf_Emu::write_unmapped( Nsf_Emu*, nes_addr_t addr, int data )
-{
-	#ifdef NDEBUG
-		return;
-	#endif
-	
-	// some games write to $8000 and $8001 repeatedly
-	if ( addr == 0x8000 || addr == 0x8001 )
-		return;
-	
-	// probably namco sound mistakenly turned on in mck
-	if ( addr == 0x4800 || addr == 0xF800 )
-		return;
-	
-	// memory mapper?
-	if ( addr == 0xFFF8 )
-		return;
-	
-	dprintf( "write_unmapped( 0x%04X, 0x%02X )\n", (unsigned) addr, (unsigned) data );
-}
-
-Nes_Emu::Nes_Emu( double gain_ )
-{
-	cpu.set_emu( this );
-	play_addr = 0;
-	gain = gain_;
-	apu.dmc_reader( pcm_read, this );
-	vrc6 = NULL;
-	namco = NULL;
-	fme7 = NULL;
-	Music_Emu::set_equalizer( nes_eq );
-	
-	// set unmapped code to illegal instruction
-	memset( unmapped_code, 0x32, sizeof unmapped_code );
-}
-
-Nes_Emu::~Nes_Emu()
-{
-	unload();
-}
+Nsf_Emu::~Nsf_Emu() { unload(); }
 
 void Nsf_Emu::unload()
 {
 	#if !NSF_EMU_APU_ONLY
+	{
 		delete vrc6;
-		vrc6 = NULL;
+		vrc6  = 0;
 		
 		delete namco;
-		namco = NULL;
+		namco = 0;
 		
 		delete fme7;
-		fme7 = NULL;
-		
+		fme7  = 0;
+	}
 	#endif
 	
 	rom.clear();
+	Music_Emu::unload();
+}
+
+// Track info
+
+static void copy_nsf_fields( Nsf_Emu::header_t const& h, track_info_t* out )
+{
+	GME_COPY_FIELD( h, out, game );
+	GME_COPY_FIELD( h, out, author );
+	GME_COPY_FIELD( h, out, copyright );
+	if ( h.chip_flags )
+		Gme_File::copy_field_( out->system, "Famicom" );
+}
+
+blargg_err_t Nsf_Emu::track_info_( track_info_t* out, int ) const
+{
+	copy_nsf_fields( header_, out );
+	return 0;
+}
+
+static blargg_err_t check_nsf_header( void const* header )
+{
+	if ( memcmp( header, "NESM\x1A", 5 ) )
+		return gme_wrong_file_type;
+	return 0;
 }
 
-const char** Nsf_Emu::voice_names() const
+struct Nsf_File : Gme_Info_
 {
-	static const char* base_names [] = {
-		"Square 1", "Square 2", "Triangle", "Noise", "DMC"
-	};
-	static const char* namco_names [] = {
-		"Square 1", "Square 2", "Triangle", "Noise", "DMC",
-		"Namco 5&7", "Namco 4&6", "Namco 1-3"
-	};
-	static const char* vrc6_names [] = {
-		"Square 1", "Square 2", "Triangle", "Noise", "DMC",
-		"VRC6 Square 1", "VRC6 Square 2", "VRC6 Saw"
-	};
-	static const char* dual_names [] = {
-		"Square 1", "Square 2", "Triangle", "Noise", "DMC",
-		"VRC6.1,N106.5&7", "VRC6.2,N106.4&6", "VRC6.3,N106.1-3"
-	};
+	Nsf_Emu::header_t h;
+	
+	Nsf_File() { set_type( gme_nsf_type ); }
+	
+	blargg_err_t load_( Data_Reader& in )
+	{
+		blargg_err_t err = in.read( &h, sizeof h );
+		if ( err )
+			return (err == in.eof_error ? gme_wrong_file_type : err);
+		
+		if ( h.chip_flags & ~(namco_flag | vrc6_flag | fme7_flag) )
+			set_warning( "Uses unsupported audio expansion hardware" );
+		
+		set_track_count( h.track_count );
+		return check_nsf_header( &h );
+	}
 	
-	static const char* fme7_names [] = {
-		"Square 1", "Square 2", "Triangle", "Noise", "DMC",
-		"Square 3", "Square 4", "Square 5"
-	};
-	
-	if ( namco )
-		return vrc6 ? dual_names : namco_names;
+	blargg_err_t track_info_( track_info_t* out, int ) const
+	{
+		copy_nsf_fields( h, out );
+		return 0;
+	}
+};
+
+static Music_Emu* new_nsf_emu () { return BLARGG_NEW Nsf_Emu ; }
+static Music_Emu* new_nsf_file() { return BLARGG_NEW Nsf_File; }
+
+gme_type_t_ const gme_nsf_type [1] = { "Nintendo NES", 0, &new_nsf_emu, &new_nsf_file, "NSF", 1 };
+
+// Setup
+
+void Nsf_Emu::set_tempo_( double t )
+{
+	unsigned playback_rate = get_le16( header_.ntsc_speed );
+	unsigned standard_rate = 0x411A;
+	clock_rate_ = 1789772.72727;
+	play_period = 262 * 341L * 4 - 2; // two fewer PPU clocks every four frames
 	
-	if ( vrc6 )
-		return vrc6_names;
+	if ( pal_only )
+	{
+		play_period   = 33247 * clock_divisor;
+		clock_rate_   = 1662607.125;
+		standard_rate = 0x4E20;
+		playback_rate = get_le16( header_.pal_speed );
+	}
 	
-	if ( fme7 )
-		return fme7_names;
+	if ( !playback_rate )
+		playback_rate = standard_rate;
 	
-	return base_names;
+	if ( playback_rate != standard_rate || t != 1.0 )
+		play_period = long (playback_rate * clock_rate_ / (1000000.0 / clock_divisor * t));
+
+	apu.set_tempo( t );
 }
 
 blargg_err_t Nsf_Emu::init_sound()
 {
-	if ( exp_flags & ~(namco_flag | vrc6_flag | fme7_flag | fds_flag) )
-		return "NSF requires unsupported expansion audio hardware";
+	if ( header_.chip_flags & ~(namco_flag | vrc6_flag | fme7_flag) )
+		set_warning( "Uses unsupported audio expansion hardware" );
 	
-	// map memory
-	cpu.reset( unmapped_code, read_unmapped, write_unmapped );
-	cpu.map_memory( 0, low_mem_size, read_low_mem, write_low_mem );
-	cpu.map_code( 0, low_mem_size, cpu.low_mem );
-	cpu.map_memory( 0x4000, Nes_Cpu::page_size, read_snd, write_snd );
-	cpu.map_memory( exram_addr, Nes_Cpu::page_size, read_unmapped, write_exram );
-	cpu.map_memory( 0x6000, sram_size, read_sram, write_sram );
-	cpu.map_code  ( 0x6000, sram_size, sram );
-	cpu.map_memory( rom_begin, ram_size - rom_begin, read_code, write_unmapped );
+	{
+		#define APU_NAMES "Square 1", "Square 2", "Triangle", "Noise", "DMC"
+		
+		int const count = Nes_Apu::osc_count;
+		static const char* const apu_names [count] = { APU_NAMES };
+		set_voice_count( count );
+		set_voice_names( apu_names );
+		
+	}
 	
-	set_voice_count( Nes_Apu::osc_count );
+	static int const types [] = {
+		wave_type  | 1, wave_type  | 2, wave_type | 0,
+		noise_type | 0, mixed_type | 1,
+		wave_type  | 3, wave_type  | 4, wave_type | 5,
+		wave_type  | 6, wave_type  | 7, wave_type | 8, wave_type | 9,
+		wave_type  |10, wave_type  |11, wave_type |12, wave_type |13
+	};
+	set_voice_types( types ); // common to all sound chip configurations
 	
-	double adjusted_gain = gain;
+	double adjusted_gain = gain();
 	
 	#if NSF_EMU_APU_ONLY
-		if ( exp_flags )
-			return "NSF requires expansion audio hardware";
+	{
+		if ( header_.chip_flags )
+			set_warning( "Uses unsupported audio expansion hardware" );
+	}
 	#else
-	
-	if ( exp_flags )
-		set_voice_count( Nes_Apu::osc_count + 3 );
-	
-	// namco
-	if ( exp_flags & namco_flag )
 	{
-		namco = BLARGG_NEW Nes_Namco_Apu;
-		BLARGG_CHECK_ALLOC( namco );
+		if ( header_.chip_flags & (namco_flag | vrc6_flag | fme7_flag) )
+			set_voice_count( Nes_Apu::osc_count + 3 );
 		
-		adjusted_gain *= 0.75;
-		cpu.map_memory( Nes_Namco_Apu::data_reg_addr, Nes_Cpu::page_size,
-				read_namco, write_namco );
-		cpu.map_memory( Nes_Namco_Apu::addr_reg_addr, Nes_Cpu::page_size,
-				 read_code, write_namco_addr );
-	}
-	
-	// vrc6
-	if ( exp_flags & vrc6_flag )
-	{
-		vrc6 = BLARGG_NEW Nes_Vrc6_Apu;
-		BLARGG_CHECK_ALLOC( vrc6 );
+		if ( header_.chip_flags & namco_flag )
+		{
+			namco = BLARGG_NEW Nes_Namco_Apu;
+			CHECK_ALLOC( namco );
+			adjusted_gain *= 0.75;
+			
+			int const count = Nes_Apu::osc_count + Nes_Namco_Apu::osc_count;
+			static const char* const names [count] = {
+				APU_NAMES,
+				"Wave 1", "Wave 2", "Wave 3", "Wave 4",
+				"Wave 5", "Wave 6", "Wave 7", "Wave 8"
+			};
+			set_voice_count( count );
+			set_voice_names( names );
+		}
 		
-		adjusted_gain *= 0.75;
-		for ( int i = 0; i < Nes_Vrc6_Apu::osc_count; i++ )
-			cpu.map_memory( Nes_Vrc6_Apu::base_addr + i * Nes_Vrc6_Apu::addr_step,
-					Nes_Cpu::page_size, read_code, write_vrc6 );
-	}
-	
-	// fme7
-	if ( exp_flags & fme7_flag )
-	{
-		fme7 = BLARGG_NEW Nes_Fme7_Apu;
-		BLARGG_CHECK_ALLOC( fme7 );
+		if ( header_.chip_flags & vrc6_flag )
+		{
+			vrc6 = BLARGG_NEW Nes_Vrc6_Apu;
+			CHECK_ALLOC( vrc6 );
+			adjusted_gain *= 0.75;
+			
+			int const count = Nes_Apu::osc_count + Nes_Vrc6_Apu::osc_count;
+			static const char* const names [count] = {
+				APU_NAMES,
+				"Saw Wave", "Square 3", "Square 4"
+			};
+			set_voice_count( count );
+			set_voice_names( names );
+			
+			if ( header_.chip_flags & namco_flag )
+			{
+				int const count = Nes_Apu::osc_count + Nes_Vrc6_Apu::osc_count +
+						Nes_Namco_Apu::osc_count;
+				static const char* const names [count] = {
+					APU_NAMES,
+					"Saw Wave", "Square 3", "Square 4",
+					"Wave 1", "Wave 2", "Wave 3", "Wave 4",
+					"Wave 5", "Wave 6", "Wave 7", "Wave 8"
+				};
+				set_voice_count( count );
+				set_voice_names( names );
+			}
+		}
 		
-		adjusted_gain *= 0.75;
-		cpu.map_memory( fme7->latch_addr, ram_size - fme7->latch_addr,
-				read_code, write_fme7 );
+		if ( header_.chip_flags & fme7_flag )
+		{
+			fme7 = BLARGG_NEW Nes_Fme7_Apu;
+			CHECK_ALLOC( fme7 );
+			adjusted_gain *= 0.75;
+			
+			int const count = Nes_Apu::osc_count + Nes_Fme7_Apu::osc_count;
+			static const char* const names [count] = {
+				APU_NAMES,
+				"Square 3", "Square 4", "Square 5"
+			};
+			set_voice_count( count );
+			set_voice_names( names );
+		}
+		
+		if ( namco ) namco->volume( adjusted_gain );
+		if ( vrc6  ) vrc6 ->volume( adjusted_gain );
+		if ( fme7  ) fme7 ->volume( adjusted_gain );
 	}
-	// to do: is gain adjustment even needed? other sound chip volumes should work
-	// naturally with the apu without change.
-	
-	if ( namco )
-		namco->volume( adjusted_gain );
-	
-	if ( vrc6 )
-		vrc6->volume( adjusted_gain );
-	
-	if ( fme7 )
-		fme7->volume( adjusted_gain );
-	
-#endif
+	#endif
 	
 	apu.volume( adjusted_gain );
 	
-	return blargg_success;
-}
-
-void Nsf_Emu::update_eq( blip_eq_t const& eq )
-{
-	apu.treble_eq( eq );
-	
-	#if !NSF_EMU_APU_ONLY
-		if ( vrc6 )
-			vrc6->treble_eq( eq );
-		
-		if ( namco )
-			namco->treble_eq( eq );
-		
-		if ( fme7 )
-			fme7->treble_eq( eq );
-	#endif
+	return 0;
 }
 
-blargg_err_t Nsf_Emu::load( Data_Reader& in )
-{
-	header_t h;
-	BLARGG_RETURN_ERR( in.read( &h, sizeof h ) );
-	return load( h, in );
-}
-
-blargg_err_t Nsf_Emu::load( const header_t& h, Data_Reader& in )
+blargg_err_t Nsf_Emu::load_( Data_Reader& in )
 {
-	header_ = h;
 	unload();
+	RETURN_ERR( rom.load( in, sizeof header_, &header_, 0 ) );
 	
-	// check compatibility
-	if ( 0 != memcmp( header_.tag, "NESM\x1A", 5 ) )
-		return "Not an NSF file";
+	set_track_count( header_.track_count );
+	RETURN_ERR( check_nsf_header( &header_ ) );
+	
 	if ( header_.vers != 1 )
-		return "Unsupported NSF format";
+		set_warning( "Unknown file version" );
 	
 	// sound and memory
-	exp_flags = header_.chip_flags;
 	blargg_err_t err = init_sound();
 	if ( err )
 		return err;
@@ -399,25 +286,24 @@
 	if ( !init_addr ) init_addr = rom_begin;
 	if ( !play_addr ) play_addr = rom_begin;
 	if ( load_addr < rom_begin || init_addr < rom_begin )
-		return "Invalid address in NSF";
-	
-	// set up rom
-	total_banks = (in.remain() + load_addr % page_size + page_size - 1) / page_size;
-	BLARGG_RETURN_ERR( rom.resize( total_banks * page_size ) );
-	memset( rom.begin(), 0, rom.size() );
-	err = in.read( &rom [load_addr % page_size], in.remain() );
-	if ( err )
 	{
-		unload();
-		return err;
+		const char* w = warning();
+		if ( !w )
+			w = "Corrupt file (invalid load/init/play address)";
+		return w;
 	}
 	
+	rom.set_addr( load_addr % bank_size );
+	int total_banks = rom.size() / bank_size;
+	
 	// bank switching
-	int first_bank = (load_addr - rom_begin) / page_size;
+	int first_bank = (load_addr - rom_begin) / bank_size;
 	for ( int i = 0; i < bank_count; i++ )
 	{
 		unsigned bank = i - first_bank;
-		initial_banks [i] = (bank < (unsigned) total_banks) ? bank : 0;
+		if ( bank >= (unsigned) total_banks )
+			bank = 0;
+		initial_banks [i] = bank;
 		
 		if ( header_.banks [i] )
 		{
@@ -427,39 +313,28 @@
 		}
 	}
 	
-	// playback rate
-	unsigned playback_rate = get_le16( header_.ntsc_speed );
-	unsigned standard_rate = 0x411A;
-	double clock_rate = 1789772.72727;
-	play_period = 262 * 341L * 4 + 2;
-	pal_only = false;
+	pal_only = (header_.speed_flags & 3) == 1;
 	
-	// use pal speed if there is no ntsc speed
-	if ( (header_.speed_flags & 3) == 1 )
-	{
-		pal_only = true;
-		play_period = 33247 * master_clock_divisor;
-		clock_rate = 1662607.125;
-		standard_rate = 0x4E20;
-		playback_rate = get_le16( header_.pal_speed );
-	}
+	#if !NSF_EMU_EXTRA_FLAGS
+		header_.speed_flags = 0;
+	#endif
+	
+	set_tempo( tempo() );
 	
-	// use custom playback rate if not the standard rate
-	if ( playback_rate && playback_rate != standard_rate )
-		play_period = long (clock_rate * playback_rate * master_clock_divisor /
-				1000000.0);
+	return setup_buffer( (long) (clock_rate_ + 0.5) );
+}
+
+void Nsf_Emu::update_eq( blip_eq_t const& eq )
+{
+	apu.treble_eq( eq );
 	
-	// extra flags
-	int extra_flags = header_.speed_flags;
-	#if !NSF_EMU_EXTRA_FLAGS
-		extra_flags = 0;
+	#if !NSF_EMU_APU_ONLY
+	{
+		if ( namco ) namco->treble_eq( eq );
+		if ( vrc6  ) vrc6 ->treble_eq( eq );
+		if ( fme7  ) fme7 ->treble_eq( eq );
+	}
 	#endif
-	needs_long_frames = (extra_flags & 0x10) != 0;
-	initial_pcm_dac = (extra_flags & 0x20) ? 0x3F : 0;
-
-	set_track_count( header_.track_count );
-	
-	return setup_buffer( (long) (clock_rate + 0.5) );
 }
 
 void Nsf_Emu::set_voice( int i, Blip_Buffer* buf, Blip_Buffer*, Blip_Buffer* )
@@ -469,155 +344,211 @@
 		apu.osc_output( i, buf );
 		return;
 	}
+	i -= Nes_Apu::osc_count;
 	
 	#if !NSF_EMU_APU_ONLY
-		if ( vrc6 )
-			vrc6->osc_output( i - Nes_Apu::osc_count, buf );
+	{
+		if ( fme7 && i < Nes_Fme7_Apu::osc_count )
+		{
+			fme7->osc_output( i, buf );
+			return;
+		}
 		
-		if ( fme7 )
-			fme7->osc_output( i - Nes_Apu::osc_count, buf );
-		
-		if ( namco )
+		if ( vrc6 )
 		{
-			if ( i < 7 )
+			if ( i < Nes_Vrc6_Apu::osc_count )
 			{
-				i &= 1;
-				namco->osc_output( i + 4, buf );
-				namco->osc_output( i + 6, buf );
+				// put saw first
+				if ( --i < 0 )
+					i = 2;
+				vrc6->osc_output( i, buf );
+				return;
 			}
-			else
-			{
-				for ( int n = 0; n < namco->osc_count / 2; n++ )
-					namco->osc_output( n, buf );
-			}
+			i -= Nes_Vrc6_Apu::osc_count;
 		}
+		
+		if ( namco && i < Nes_Namco_Apu::osc_count )
+		{
+			namco->osc_output( i, buf );
+			return;
+		}
+	}
 	#endif
 }
 
-void Nsf_Emu::start_track( int track )
+// Emulation
+
+// see nes_cpu_io.h for read/write functions
+
+void Nsf_Emu::cpu_write_misc( nes_addr_t addr, int data )
 {
-	require( rom.size() ); // file must be loaded
-	
-	Classic_Emu::start_track( track );
-	
-	// clear memory
-	memset( cpu.low_mem, 0, sizeof cpu.low_mem );
-	memset( sram, 0, sizeof sram );
-	
-	// initial rom banks
-	for ( int i = 0; i < bank_count; ++i )
-		cpu.write( bank_select_addr + i, initial_banks [i] );
-	
-	// reset sound
-	apu.reset( pal_only, initial_pcm_dac );
-	apu.write_register( 0, 0x4015, 0x0F );
-	apu.write_register( 0, 0x4017, needs_long_frames ? 0x80 : 0 );
-	
 	#if !NSF_EMU_APU_ONLY
+	{
 		if ( namco )
-			namco->reset();
+		{
+			switch ( addr )
+			{
+				case Nes_Namco_Apu::data_reg_addr:
+					namco->write_data( time(), data );
+					return;
+				
+				case Nes_Namco_Apu::addr_reg_addr:
+					namco->write_addr( data );
+					return;
+			}
+		}
+		
+		if ( addr >= Nes_Fme7_Apu::latch_addr && fme7 )
+		{
+			switch ( addr & Nes_Fme7_Apu::addr_mask )
+			{
+				case Nes_Fme7_Apu::latch_addr:
+					fme7->write_latch( data );
+					return;
+				
+				case Nes_Fme7_Apu::data_addr:
+					fme7->write_data( time(), data );
+					return;
+			}
+		}
 		
 		if ( vrc6 )
-			vrc6->reset();
-		
-		if ( fme7 )
-			fme7->reset();
+		{
+			unsigned reg = addr & (Nes_Vrc6_Apu::addr_step - 1);
+			unsigned osc = unsigned (addr - Nes_Vrc6_Apu::base_addr) / Nes_Vrc6_Apu::addr_step;
+			if ( osc < Nes_Vrc6_Apu::osc_count && reg < Nes_Vrc6_Apu::reg_count )
+			{
+				vrc6->write_osc( time(), osc, reg, data );
+				return;
+			}
+		}
+	}
 	#endif
 	
-	// reset cpu
-	cpu.r.pc = exram_addr;
-	cpu.r.a = track;
-	cpu.r.x = pal_only;
-	cpu.r.y = 0;
-	cpu.r.sp = 0xFF;
-	cpu.r.status = 0x04; // i flag
+	// unmapped write
 	
-	// first call
-	cpu_jsr( init_addr, -1 );
-	next_play = 0;
-	play_extra = 0;
+	#ifndef NDEBUG
+	{
+		// some games write to $8000 and $8001 repeatedly
+		if ( addr == 0x8000 || addr == 0x8001 ) return;
+		
+		// probably namco sound mistakenly turned on in mck
+		if ( addr == 0x4800 || addr == 0xF800 ) return;
+		
+		// memory mapper?
+		if ( addr == 0xFFF8 ) return;
+		
+		dprintf( "write_unmapped( 0x%04X, 0x%02X )\n", (unsigned) addr, (unsigned) data );
+	}
+	#endif
 }
 
-void Nsf_Emu::cpu_jsr( nes_addr_t pc, int adj )
+blargg_err_t Nsf_Emu::start_track_( int track )
 {
-	unsigned addr = cpu.r.pc + adj;
-	cpu.r.pc = pc;
-	cpu.push_byte( addr >> 8 );
-	cpu.push_byte( addr );
-}
-
-void Nsf_Emu::call_play()
-{
-	cpu_jsr( play_addr, -1 );
+	RETURN_ERR( Classic_Emu::start_track_( track ) );
+	
+	memset( low_mem, 0, sizeof low_mem );
+	memset( sram,    0, sizeof sram );
+	
+	cpu::reset( unmapped_code ); // also maps low_mem
+	cpu::map_code( sram_addr, sizeof sram, sram );
+	for ( int i = 0; i < bank_count; ++i )
+		cpu_write( bank_select_addr + i, initial_banks [i] );
+	
+	apu.reset( pal_only, (header_.speed_flags & 0x20) ? 0x3F : 0 );
+	apu.write_register( 0, 0x4015, 0x0F );
+	apu.write_register( 0, 0x4017, (header_.speed_flags & 0x10) ? 0x80 : 0 );
+	#if !NSF_EMU_APU_ONLY
+	{
+		if ( namco ) namco->reset();
+		if ( vrc6  ) vrc6 ->reset();
+		if ( fme7  ) fme7 ->reset();
+	}
+	#endif
+	
+	play_ready = 4;
+	play_extra = 0;
+	next_play = play_period / clock_divisor;
+	
+	saved_state.pc = badop_addr;
+	low_mem [0x1FF] = (badop_addr - 1) >> 8;
+	low_mem [0x1FE] = (badop_addr - 1);
+	r.sp = 0xFD;
+	r.pc = init_addr;
+	r.a  = track;
+	r.x  = pal_only;
+	
+	return 0;
 }
 
-blip_time_t Nsf_Emu::run_clocks( blip_time_t duration, bool* )
+blargg_err_t Nsf_Emu::run_clocks( blip_time_t& duration, int )
 {
-	// run cpu
-	cpu.set_time( 0 );
-	bool first_illegal = true; // avoid swamping output with illegal instruction errors
-	while ( cpu.time() < duration )
+	set_time( 0 );
+	while ( time() < duration )
 	{
-		// check for idle cpu
-		if ( cpu.r.pc == exram_addr )
+		nes_time_t end = min( next_play, duration );
+		end = min( end, time() + 32767 ); // allows CPU to use 16-bit time delta
+		if ( cpu::run( end ) )
 		{
-			if ( next_play > duration )
+			if ( r.pc != badop_addr )
 			{
-				cpu.set_time( duration );
-				break;
-			}
-			
-			if ( next_play > cpu.time() )
-				cpu.set_time( next_play );
-			
-			nes_time_t period = (play_period + play_extra) / master_clock_divisor;
-			play_extra = play_period - period * master_clock_divisor;
-			next_play += period;
-			call_play();
-		}
-		
-		Nes_Cpu::result_t result = RUN_NES_CPU( cpu, duration );
-		if ( result == Nes_Cpu::result_badop && cpu.r.pc != exram_addr )
-		{
-			if ( cpu.r.pc > 0xffff )
-			{
-				cpu.r.pc &= 0xffff;
-				dprintf( "PC wrapped around\n" );
+				set_warning( "Emulation error (illegal instruction)" );
+				r.pc++;
 			}
 			else
 			{
-				cpu.r.pc = (cpu.r.pc + 1) & 0xffff;
-				cpu.set_time( cpu.time() + 4 );
-				log_error();
-				if ( first_illegal )
+				play_ready = 1;
+				if ( saved_state.pc != badop_addr )
+				{
+					cpu::r = saved_state;
+					saved_state.pc = badop_addr;
+				}
+				else
 				{
-					first_illegal = false;
-					dprintf( "Bad opcode $%.2x at $%.4x\n",
-							(int) cpu.read( cpu.r.pc ), (int) cpu.r.pc );
+					set_time( end );
 				}
 			}
 		}
+		
+		if ( time() >= next_play )
+		{
+			nes_time_t period = (play_period + play_extra) / clock_divisor;
+			play_extra = play_period - period * clock_divisor;
+			next_play += period;
+			if ( play_ready && !--play_ready )
+			{
+				check( saved_state.pc == badop_addr );
+				if ( r.pc != badop_addr )
+					saved_state = cpu::r;
+				
+				r.pc = play_addr;
+				low_mem [0x100 + r.sp--] = (badop_addr - 1) >> 8;
+				low_mem [0x100 + r.sp--] = (badop_addr - 1);
+			}
+		}
 	}
 	
-	// end time frame
-	duration = cpu.time();
+	if ( cpu::error_count() )
+	{
+		cpu::clear_error_count();
+		set_warning( "Emulation error (illegal instruction)" );
+	}
+	
+	duration = time();
 	next_play -= duration;
-	if ( next_play < 0 ) // could go negative if routine is taking too long to return
+	check( next_play >= 0 );
+	if ( next_play < 0 )
 		next_play = 0;
+	
 	apu.end_frame( duration );
 	
 	#if !NSF_EMU_APU_ONLY
-		if ( namco )
-			namco->end_frame( duration );
-		
-		if ( vrc6 )
-			vrc6->end_frame( duration );
-		
-		if ( fme7 )
-			fme7->end_frame( duration );
-		
+	{
+		if ( namco ) namco->end_frame( duration );
+		if ( vrc6  ) vrc6 ->end_frame( duration );
+		if ( fme7  ) fme7 ->end_frame( duration );
+	}
 	#endif
 	
-	return duration;
+	return 0;
 }
-