diff src/console/Gbs_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 986f098da058
line wrap: on
line diff
--- a/src/console/Gbs_Emu.cxx	Wed Nov 29 14:42:11 2006 -0800
+++ b/src/console/Gbs_Emu.cxx	Thu Nov 30 19:54:33 2006 -0800
@@ -1,178 +1,137 @@
-
-// Game_Music_Emu 0.3.0. http://www.slack.net/~ant/
+// Game_Music_Emu 0.5.1. http://www.slack.net/~ant/
 
 #include "Gbs_Emu.h"
 
+#include "blargg_endian.h"
 #include <string.h>
 
-#include "blargg_endian.h"
-
 /* Copyright (C) 2003-2006 Shay Green. This module is free software; you
 can redistribute it and/or modify it under the terms of the GNU Lesser
 General Public License as published by the Free Software Foundation; either
 version 2.1 of the License, or (at your option) any later version. This
 module is distributed in the hope that it will be useful, but WITHOUT ANY
 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
-FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
-more details. You should have received a copy of the GNU Lesser General
-Public License along with this module; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
-
-#include BLARGG_SOURCE_BEGIN
+FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
+details. You should have received a copy of the GNU Lesser General Public
+License along with this module; if not, write to the Free Software Foundation,
+Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
 
-#ifndef RUN_GB_CPU
-	#define RUN_GB_CPU( cpu, n ) cpu.run( n )
-#endif
-
-const long bank_size = 0x4000;
-const gb_addr_t ram_addr = 0xa000;
-const gb_addr_t halt_addr = 0x9EFE;
-static BOOST::uint8_t unmapped_code [Gb_Cpu::page_size];
+#include "blargg_source.h"
 
 Gbs_Emu::equalizer_t const Gbs_Emu::handheld_eq   = { -47.0, 2000 };
-Gbs_Emu::equalizer_t const Gbs_Emu::headphones_eq = {   0.0, 300 };
-
-// RAM
-
-int Gbs_Emu::read_ram( Gbs_Emu* emu, gb_addr_t addr )
-{
-	return emu->ram [addr - ram_addr];
-}
-
-void Gbs_Emu::write_ram( Gbs_Emu* emu, gb_addr_t addr, int data )
-{
-	emu->ram [addr - ram_addr] = data;
-}
-
-// Unmapped
-
-int Gbs_Emu::read_unmapped( Gbs_Emu*, gb_addr_t addr )
-{
-	dprintf( "Read from unmapped memory $%.4x\n", (unsigned) addr );
-	return 0xFF; // open bus value
-}
-
-void Gbs_Emu::write_unmapped( Gbs_Emu*, gb_addr_t addr, int )
-{
-	dprintf( "Wrote to unmapped memory $%.4x\n", (unsigned) addr );
-}
-
-// ROM
+Gbs_Emu::equalizer_t const Gbs_Emu::headphones_eq = {   0.0,  300 };
 
-int Gbs_Emu::read_rom( Gbs_Emu* emu, gb_addr_t addr )
-{
-	return emu->rom [addr];
-}
-
-int Gbs_Emu::read_bank( Gbs_Emu* emu, gb_addr_t addr )
-{
-	return emu->rom_bank [addr & (bank_size - 1)];
-}
-
-void Gbs_Emu::set_bank( int n )
-{
-	if ( n >= bank_count )
-	{
-		n = 0;
-		dprintf( "Set to non-existent bank %d\n", (int) n );
-	}
-	if ( n == 0 && bank_count > 1 )
-	{
-		// to do: what is the correct behavior? Current Wario Land 3 and
-		// Tetris DX GBS rips require that this have no effect or set to bank 1.
-		//return;
-		//dprintf( "Selected ROM bank 0\n" );
-	}
-	rom_bank = &rom [n * bank_size];
-	cpu.map_code( bank_size, bank_size, rom_bank );
-}
-
-void Gbs_Emu::write_rom( Gbs_Emu* emu, gb_addr_t addr, int data )
+Gbs_Emu::Gbs_Emu()
 {
-	if ( unsigned (addr - 0x2000) < 0x2000 )
-		emu->set_bank( data & 0x1F );
-}
-
-// I/O: Timer, APU
-
-void Gbs_Emu::set_timer( int modulo, int rate )
-{
-	if ( timer_mode )
-	{
-		static byte const rates [4] = { 10, 4, 6, 8 };
-		play_period = (gb_time_t) (256 - modulo) << (rates [rate & 3] - double_speed);
-	}
-}
-
-inline gb_time_t Gbs_Emu::clock() const
-{
-	return cpu_time - cpu.remain();
-}
+	set_type( gme_gbs_type );
 	
-int Gbs_Emu::read_io( Gbs_Emu* emu, gb_addr_t addr )
-{
-	// hi_page is accessed most
-	if ( addr >= 0xFF80 )
-		return emu->hi_page [addr & 0xFF];
-	
-	if ( unsigned (addr - Gb_Apu::start_addr) <= Gb_Apu::register_count )
-		return emu->apu.read_register( emu->clock(), addr );
+	static const char* const names [Gb_Apu::osc_count] = {
+		"Square 1", "Square 2", "Wave", "Noise"
+	};
+	set_voice_names( names );
 	
-	if ( addr == 0xFF00 )
-		return 0; // joypad
-	
-	dprintf( "Unhandled I/O read 0x%4X\n", (unsigned) addr );
+	static int const types [Gb_Apu::osc_count] = {
+		wave_type | 1, wave_type | 2, wave_type | 0, mixed_type | 0
+	};
+	set_voice_types( types );
 	
-	return 0xFF;
-}
-
-void Gbs_Emu::write_io( Gbs_Emu* emu, gb_addr_t addr, int data )
-{
-	// apu is accessed most
-	if ( unsigned (addr - Gb_Apu::start_addr) < Gb_Apu::register_count )
-	{
-		emu->apu.write_register( emu->clock(), addr, data );
-	}
-	else
-	{
-		emu->hi_page [addr & 0xFF] = data;
-		
-		if ( addr == 0xFF06 || addr == 0xFF07 )
-			emu->set_timer( emu->hi_page [6], emu->hi_page [7] );
-		
-		//if ( addr == 0xFFFF )
-		//  dprintf( "Wrote interrupt mask\n" );
-	}
-}
-
-Gbs_Emu::Gbs_Emu( double gain ) : cpu( this )
-{
-	apu.volume( gain );
+	set_silence_lookahead( 6 );
+	set_max_initial_silence( 21 );
+	set_gain( 1.2 );
 	
 	static equalizer_t const eq = { -1.0, 120 };
 	set_equalizer( eq );
-	
- 	// unmapped code is all HALT instructions
-	memset( unmapped_code, 0x76, sizeof unmapped_code );
-	
-	// cpu
-	cpu.reset( unmapped_code, read_unmapped, write_unmapped );
-	cpu.map_memory( 0x0000, 0x4000, read_rom, write_rom );
-	cpu.map_memory( 0x4000, 0x4000, read_bank, write_rom );
-	cpu.map_memory( ram_addr, 0x4000, read_ram, write_ram );
-	cpu.map_code(   ram_addr, 0x4000, ram );
-	cpu.map_code(   0xFF00, 0x0100, hi_page );
-	cpu.map_memory( 0xFF00, 0x0100, read_io, write_io );
 }
 
-Gbs_Emu::~Gbs_Emu()
-{
-}
+Gbs_Emu::~Gbs_Emu() { }
 
 void Gbs_Emu::unload()
 {
-	cpu.r.pc = halt_addr;
 	rom.clear();
+	Music_Emu::unload();
+}
+
+// Track info
+
+static void copy_gbs_fields( Gbs_Emu::header_t const& h, track_info_t* out )
+{
+	GME_COPY_FIELD( h, out, game );
+	GME_COPY_FIELD( h, out, author );
+	GME_COPY_FIELD( h, out, copyright );
+}
+
+blargg_err_t Gbs_Emu::track_info_( track_info_t* out, int ) const
+{
+	copy_gbs_fields( header_, out );
+	return 0;
+}
+
+static blargg_err_t check_gbs_header( void const* header )
+{
+	if ( memcmp( header, "GBS", 3 ) )
+		return gme_wrong_file_type;
+	return 0;
+}
+
+struct Gbs_File : Gme_Info_
+{
+	Gbs_Emu::header_t h;
+	
+	Gbs_File() { set_type( gme_gbs_type ); }
+	
+	blargg_err_t load_( Data_Reader& in )
+	{
+		blargg_err_t err = in.read( &h, sizeof h );
+		if ( err )
+			return (err == in.eof_error ? gme_wrong_file_type : err);
+		
+		set_track_count( h.track_count );
+		return check_gbs_header( &h );
+	}
+	
+	blargg_err_t track_info_( track_info_t* out, int ) const
+	{
+		copy_gbs_fields( h, out );
+		return 0;
+	}
+};
+
+static Music_Emu* new_gbs_emu () { return BLARGG_NEW Gbs_Emu ; }
+static Music_Emu* new_gbs_file() { return BLARGG_NEW Gbs_File; }
+
+gme_type_t_ const gme_gbs_type [1] = { "Game Boy", 0, &new_gbs_emu, &new_gbs_file, "GBS", 1 };
+
+// Setup
+
+blargg_err_t Gbs_Emu::load_( Data_Reader& in )
+{
+	unload();
+	RETURN_ERR( rom.load( in, sizeof header_, &header_, 0 ) );
+	
+	set_track_count( header_.track_count );
+	RETURN_ERR( check_gbs_header( &header_ ) );
+	
+	if ( header_.vers != 1 )
+		set_warning( "Unknown file version" );
+	
+	if ( header_.timer_mode & 0x78 )
+		set_warning( "Invalid timer mode" );
+	
+	unsigned load_addr = get_le16( header_.load_addr );
+	if ( (header_.load_addr [1] | header_.init_addr [1] | header_.play_addr [1]) > 0x7F ||
+			load_addr < 0x400 )
+		set_warning( "Invalid load/init/play address" );
+	
+	set_voice_count( Gb_Apu::osc_count );
+	
+	apu.volume( gain() );
+	
+	return setup_buffer( 4194304 );
+}
+
+void Gbs_Emu::update_eq( blip_eq_t const& eq )
+{
+	apu.treble_eq( eq );
 }
 
 void Gbs_Emu::set_voice( int i, Blip_Buffer* c, Blip_Buffer* l, Blip_Buffer* r )
@@ -180,82 +139,41 @@
 	apu.osc_output( i, c, l, r );
 }
 
-void Gbs_Emu::update_eq( blip_eq_t const& eq )
-{
-	apu.treble_eq( eq );
-}
+// Emulation
+
+// see gb_cpu_io.h for read/write functions
 
-blargg_err_t Gbs_Emu::load( Data_Reader& in )
+void Gbs_Emu::set_bank( int n )
 {
-	header_t h;
-	BLARGG_RETURN_ERR( in.read( &h, sizeof h ) );
-	return load( h, in );
+	blargg_long addr = rom.mask_addr( n * (blargg_long) bank_size );
+	if ( addr == 0 && rom.size() > bank_size )
+	{
+		// TODO: what is the correct behavior? Current Game & Watch Gallery
+		// rip requires that this have no effect or set to bank 1.
+		//dprintf( "Selected ROM bank 0\n" );
+		return;
+		//n = 1;
+	}
+	cpu::map_code( bank_size, bank_size, rom.at_addr( addr ) );
 }
 
-blargg_err_t Gbs_Emu::load( const header_t& h, Data_Reader& in )
+void Gbs_Emu::update_timer()
 {
-	header_ = h;
-	unload();
-	
-	// check compatibility
-	if ( 0 != memcmp( header_.tag, "GBS", 3 ) )
-		return "Not a GBS file";
-	if ( header_.vers != 1 )
-		return "Unsupported GBS format";
-	
-	// gather relevant fields
-	load_addr = get_le16( header_.load_addr );
-	init_addr = get_le16( header_.init_addr );
-	play_addr = get_le16( header_.play_addr );
-	stack_ptr = get_le16( header_.stack_ptr );
-	double_speed = (header_.timer_mode & 0x80) != 0;
-	timer_modulo_init = header_.timer_modulo;
-	timer_mode = header_.timer_mode;
-	if ( !(timer_mode & 0x04) )
-		timer_mode = 0; // using vbl
-	
-	#ifndef NDEBUG
+	if ( header_.timer_mode & 0x04 )
 	{
-		if ( header_.timer_mode & 0x78 )
-			dprintf( "TAC field has extra bits set: 0x%02x\n", (unsigned) header_.timer_mode );
-		
-		if ( load_addr < 0x400 || load_addr >= 0x8000 ||
-				init_addr < 0x400 || init_addr >= 0x8000 ||
-				play_addr < 0x400 || play_addr >= 0x8000 )
-			dprintf( "Load/init/play address violates GBS spec.\n" );
+		static byte const rates [4] = { 10, 4, 6, 8 };
+		int shift = rates [ram [hi_page + 7] & 3] - (header_.timer_mode >> 7);
+		play_period = (256L - ram [hi_page + 6]) << shift;
 	}
-	#endif
-	
-	// rom
-	bank_count = (load_addr + in.remain() + bank_size - 1) / bank_size;
-	BLARGG_RETURN_ERR( rom.resize( bank_count * bank_size ) );
-	memset( rom.begin(), 0, rom.size() );
-	blargg_err_t err = in.read( &rom [load_addr], in.remain() );
-	if ( err )
+	else
 	{
-		unload();
-		return err;
+		play_period = 70224; // 59.73 Hz
 	}
-	
-	// cpu
-	cpu.rst_base = load_addr;
-	cpu.map_code( 0x0000, 0x4000, rom.begin() );
-	
-	set_voice_count( Gb_Apu::osc_count );
-	set_track_count( header_.track_count );
-	
-	return setup_buffer( 4194304 );
+	if ( tempo() != 1.0 )
+		play_period = blip_time_t (play_period / tempo());
 }
 
-const char** Gbs_Emu::voice_names() const
-{
-	static const char* names [] = { "Square 1", "Square 2", "Wave", "Noise" };
-	return names;
-}
-
-// Emulation
-
-static const BOOST::uint8_t sound_data [Gb_Apu::register_count] = {
+static BOOST::uint8_t const sound_data [Gb_Apu::register_count] = {
 	0x80, 0xBF, 0x00, 0x00, 0xBF, // square 1
 	0x00, 0x3F, 0x00, 0x00, 0xBF, // square 2
 	0x7F, 0xFF, 0x9F, 0x00, 0xBF, // wave
@@ -268,99 +186,101 @@
 
 void Gbs_Emu::cpu_jsr( gb_addr_t addr )
 {
-	cpu.write( --cpu.r.sp, cpu.r.pc >> 8 );
-	cpu.write( --cpu.r.sp, cpu.r.pc&0xFF );
-	cpu.r.pc = addr;
+	check( cpu::r.sp == get_le16( header_.stack_ptr ) );
+	cpu::r.pc = addr;
+	cpu_write( --cpu::r.sp, idle_addr >> 8 );
+	cpu_write( --cpu::r.sp, idle_addr&0xFF );
 }
 
-void Gbs_Emu::start_track( int track_index )
+void Gbs_Emu::set_tempo_( double t )
 {
-	require( rom.size() ); // file must be loaded
+	apu.set_tempo( t );
+	update_timer();
+}
+
+blargg_err_t Gbs_Emu::start_track_( int track )
+{
+	RETURN_ERR( Classic_Emu::start_track_( track ) );
 	
-	Classic_Emu::start_track( track_index );
+	memset( ram, 0, 0x4000 );
+	memset( ram + 0x4000, 0xFF, 0x1F80 );
+	memset( ram + 0x5F80, 0, sizeof ram - 0x5F80 );
+	ram [hi_page] = 0; // joypad reads back as 0
 	
 	apu.reset();
-	
-	memset( ram, 0, sizeof ram );
-	memset( hi_page, 0, sizeof hi_page );
-	
-	// configure hardware
-	set_bank( bank_count > 1 );
 	for ( int i = 0; i < (int) sizeof sound_data; i++ )
 		apu.write_register( 0, i + apu.start_addr, sound_data [i] );
-	play_period = 70224; // 59.73 Hz
-	set_timer( timer_modulo_init, timer_mode ); // ignored if using vbl
+	
+	cpu::reset( rom.unmapped() );
+	
+	unsigned load_addr = get_le16( header_.load_addr );
+	cpu::rst_base = load_addr;
+	rom.set_addr( load_addr );
+	
+	cpu::map_code( ram_addr, 0x10000 - ram_addr, ram );
+	cpu::map_code( 0, bank_size, rom.at_addr( 0 ) );
+	set_bank( rom.size() > bank_size );
+	
+	ram [hi_page + 6] = header_.timer_modulo;
+	ram [hi_page + 7] = header_.timer_mode;
+	update_timer();
 	next_play = play_period;
 	
-	// set up init call
-	cpu.r.a = track_index;
-	cpu.r.b = 0;
-	cpu.r.c = 0;
-	cpu.r.d = 0;
-	cpu.r.e = 0;
-	cpu.r.h = 0;
-	cpu.r.l = 0;
-	cpu.r.flags = 0;
-	cpu.r.pc = halt_addr;
-	cpu.r.sp = stack_ptr;
-	cpu_jsr( init_addr );
+	cpu::r.a  = track;
+	cpu::r.pc = idle_addr;
+	cpu::r.sp = get_le16( header_.stack_ptr );
+	cpu_time  = 0;
+	cpu_jsr( get_le16( header_.init_addr ) );
+	
+	return 0;
 }
 
-blip_time_t Gbs_Emu::run_clocks( blip_time_t duration, bool* added_stereo )
+blargg_err_t Gbs_Emu::run_clocks( blip_time_t& duration, int )
 {
-	require( rom.size() ); // file must be loaded
-	
 	cpu_time = 0;
 	while ( cpu_time < duration )
 	{
-		// check for idle cpu
-		if ( cpu.r.pc == halt_addr )
-		{
-			if ( next_play > duration )
-			{
-				cpu_time = duration;
-				break;
-			}
-			
-			if ( cpu_time < next_play )
-				cpu_time = next_play;
-			next_play += play_period;
-			cpu_jsr( play_addr );
-		}
-		
 		long count = duration - cpu_time;
 		cpu_time = duration;
-		Gb_Cpu::result_t result = RUN_GB_CPU( cpu, count );
-		cpu_time -= cpu.remain();
+		bool result = cpu::run( count );
+		cpu_time -= cpu::remain();
 		
-		if ( (result == Gb_Cpu::result_halt && cpu.r.pc != halt_addr) ||
-				result == Gb_Cpu::result_badop )
+		if ( result )
 		{
-			if ( cpu.r.pc > 0xFFFF )
+			if ( cpu::r.pc == idle_addr )
+			{
+				if ( next_play > duration )
+				{
+					cpu_time = duration;
+					break;
+				}
+				
+				if ( cpu_time < next_play )
+					cpu_time = next_play;
+				next_play += play_period;
+				cpu_jsr( get_le16( header_.play_addr ) );
+			}
+			else if ( cpu::r.pc > 0xFFFF )
 			{
 				dprintf( "PC wrapped around\n" );
-				cpu.r.pc &= 0xFFFF;
+				cpu::r.pc &= 0xFFFF;
 			}
 			else
 			{
-				log_error();
+				set_warning( "Emulation error (illegal/unsupported instruction)" );
 				dprintf( "Bad opcode $%.2x at $%.4x\n",
-						(int) cpu.read( cpu.r.pc ), (int) cpu.r.pc );
-				cpu.r.pc = (cpu.r.pc + 1) & 0xFFFF;
+						(int) *cpu::get_code( cpu::r.pc ), (int) cpu::r.pc );
+				cpu::r.pc = (cpu::r.pc + 1) & 0xFFFF;
 				cpu_time += 6;
 			}
 		}
 	}
 	
-	// end time frame
-	
+	duration = cpu_time;
 	next_play -= cpu_time;
 	if ( next_play < 0 ) // could go negative if routine is taking too long to return
 		next_play = 0;
-	
-	if ( apu.end_frame( cpu_time ) && added_stereo )
-		*added_stereo = true;
+	apu.end_frame( cpu_time );
 	
-	return cpu_time;
+	return 0;
 }
-