Mercurial > audlegacy-plugins
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; } -