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