Mercurial > audlegacy-plugins
diff src/console/Nsf_Emu.cxx @ 12:3da1b8942b8b trunk
[svn] - remove src/Input src/Output src/Effect src/General src/Visualization src/Container
author | nenolod |
---|---|
date | Mon, 18 Sep 2006 03:14:20 -0700 |
parents | src/Input/console/Nsf_Emu.cxx@13389e613d67 |
children | fb513e10174e |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/console/Nsf_Emu.cxx Mon Sep 18 03:14:20 2006 -0700 @@ -0,0 +1,623 @@ + +// Game_Music_Emu 0.3.0. http://www.slack.net/~ant/ + +#include "Nsf_Emu.h" + +#include <string.h> +#include <stdio.h> + +#if !NSF_EMU_APU_ONLY + #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 + +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 + +int Nsf_Emu::read_code( Nsf_Emu* emu, nes_addr_t addr ) +{ + return *emu->cpu.get_code( addr ); +} + +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 ); + } + } +} + +// 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 ); +} + +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 ); +} + +// FME-7 +void Nsf_Emu::write_fme7( Nsf_Emu* emu, nes_addr_t addr, int data ) +{ + 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 +} + +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(); +} + +void Nsf_Emu::unload() +{ + #if !NSF_EMU_APU_ONLY + delete vrc6; + vrc6 = NULL; + + delete namco; + namco = NULL; + + delete fme7; + fme7 = NULL; + + #endif + + rom.clear(); +} + +const char** Nsf_Emu::voice_names() const +{ + 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" + }; + + 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; + + if ( vrc6 ) + return vrc6_names; + + if ( fme7 ) + return fme7_names; + + return base_names; +} + +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"; + + // 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 ); + + set_voice_count( Nes_Apu::osc_count ); + + double adjusted_gain = gain; + + #if NSF_EMU_APU_ONLY + if ( exp_flags ) + return "NSF requires expansion audio 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 ); + + 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 ); + + 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 ); + + adjusted_gain *= 0.75; + cpu.map_memory( fme7->latch_addr, ram_size - fme7->latch_addr, + read_code, write_fme7 ); + } + // 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 + + 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 +} + +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 ) +{ + header_ = h; + unload(); + + // check compatibility + if ( 0 != memcmp( header_.tag, "NESM\x1A", 5 ) ) + return "Not an NSF file"; + if ( header_.vers != 1 ) + return "Unsupported NSF format"; + + // sound and memory + exp_flags = header_.chip_flags; + blargg_err_t err = init_sound(); + if ( err ) + return err; + + // set up data + nes_addr_t load_addr = get_le16( header_.load_addr ); + init_addr = get_le16( header_.init_addr ); + play_addr = get_le16( header_.play_addr ); + if ( !load_addr ) load_addr = rom_begin; + 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; + } + + // bank switching + int first_bank = (load_addr - rom_begin) / page_size; + for ( int i = 0; i < bank_count; i++ ) + { + unsigned bank = i - first_bank; + initial_banks [i] = (bank < (unsigned) total_banks) ? bank : 0; + + if ( header_.banks [i] ) + { + // bank-switched + memcpy( initial_banks, header_.banks, sizeof initial_banks ); + break; + } + } + + // 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; + + // 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 ); + } + + // 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); + + // extra flags + int extra_flags = header_.speed_flags; + #if !NSF_EMU_EXTRA_FLAGS + extra_flags = 0; + #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* ) +{ + if ( i < Nes_Apu::osc_count ) + { + apu.osc_output( i, buf ); + return; + } + + #if !NSF_EMU_APU_ONLY + if ( vrc6 ) + vrc6->osc_output( i - Nes_Apu::osc_count, buf ); + + if ( fme7 ) + fme7->osc_output( i - Nes_Apu::osc_count, buf ); + + if ( namco ) + { + if ( i < 7 ) + { + i &= 1; + namco->osc_output( i + 4, buf ); + namco->osc_output( i + 6, buf ); + } + else + { + for ( int n = 0; n < namco->osc_count / 2; n++ ) + namco->osc_output( n, buf ); + } + } + #endif +} + +void Nsf_Emu::start_track( int track ) +{ + 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(); + + if ( vrc6 ) + vrc6->reset(); + + if ( fme7 ) + fme7->reset(); + #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 + + // first call + cpu_jsr( init_addr, -1 ); + next_play = 0; + play_extra = 0; +} + +void Nsf_Emu::cpu_jsr( nes_addr_t pc, int adj ) +{ + 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 ); +} + +blip_time_t Nsf_Emu::run_clocks( blip_time_t duration, bool* ) +{ + // run cpu + cpu.set_time( 0 ); + bool first_illegal = true; // avoid swamping output with illegal instruction errors + while ( cpu.time() < duration ) + { + // check for idle cpu + if ( cpu.r.pc == exram_addr ) + { + if ( next_play > duration ) + { + 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" ); + } + else + { + cpu.r.pc = (cpu.r.pc + 1) & 0xffff; + cpu.set_time( cpu.time() + 4 ); + log_error(); + if ( first_illegal ) + { + first_illegal = false; + dprintf( "Bad opcode $%.2x at $%.4x\n", + (int) cpu.read( cpu.r.pc ), (int) cpu.r.pc ); + } + } + } + } + + // end time frame + duration = cpu.time(); + next_play -= duration; + if ( next_play < 0 ) // could go negative if routine is taking too long to return + 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 ); + + #endif + + return duration; +} +