//////////////////////////////////////////////////////////////////////////////////////// // // Nestopia - NES/Famicom emulator written in C++ // // Copyright (C) 2003-2008 Martin Freij // // This file is part of Nestopia. // // Nestopia is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // Nestopia 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 General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Nestopia; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA // //////////////////////////////////////////////////////////////////////////////////////// #include "NstBoard.hpp" #include "../NstTimer.hpp" #include "NstBoardKonamiVrc4.hpp" #include "NstBoardKonamiVrc6.hpp" namespace Nes { namespace Core { namespace Boards { namespace Konami { #ifdef NST_MSVC_OPTIMIZE #pragma optimize("s", on) #endif Vrc6::Sound::Sound(Apu& a,bool connect) : Channel(a) { Reset(); bool audible = UpdateSettings(); if (connect) Connect( audible ); } uint Vrc6::GetPrgLineShift(const Context& c,const uint pin,const uint def) { if (const Chips::Type* const vrc6 = c.chips.Find(L"Konami VRC VI")) { const uint line = *vrc6->Pin(pin).C(L"PRG").A(); NST_VERIFY( line < 8 ); if (line < 8) return line; } return def; } Vrc6::Vrc6(const Context& c) : Board (c), irq (*c.cpu), sound (*c.apu), prgLineA (GetPrgLineShift(c, 9,1)), prgLineB (GetPrgLineShift(c,10,0)) {} void Vrc6::Sound::BaseChannel::Reset() { enabled = false; waveLength = 1; active = false; timer = 0; frequency = 0; step = 0; } void Vrc6::Sound::Square::Reset() { BaseChannel::Reset(); duty = 1; volume = 0; digitized = false; } void Vrc6::Sound::Saw::Reset() { BaseChannel::Reset(); phase = 0; amp = 0; frequency = 0; } void Vrc6::Sound::Reset() { for (uint i=0; i < 2; ++i) square[i].Reset(); saw.Reset(); dcBlocker.Reset(); } void Vrc6::SubReset(const bool hard) { irq.Reset( hard, hard ? false : irq.Connected() ); Map( 0x8000U, 0x8FFFU, PRG_SWAP_16K_0 ); Map( 0xC000U, 0xCFFFU, PRG_SWAP_8K_2 ); for (dword i=0x9000, a=9-prgLineA, b=8-prgLineB; i <= 0xFFFF; ++i) { switch ((i & 0xF000) | (i << a & 0x0200) | (i << b & 0x0100)) { case 0x9000: Map( i, &Vrc6::Poke_9000 ); break; case 0x9100: Map( i, &Vrc6::Poke_9001 ); break; case 0x9200: Map( i, &Vrc6::Poke_9002 ); break; case 0xA000: Map( i, &Vrc6::Poke_A000 ); break; case 0xA100: Map( i, &Vrc6::Poke_A001 ); break; case 0xA200: Map( i, &Vrc6::Poke_A002 ); break; case 0xB000: Map( i, &Vrc6::Poke_B000 ); break; case 0xB100: Map( i, &Vrc6::Poke_B001 ); break; case 0xB200: Map( i, &Vrc6::Poke_B002 ); break; case 0xB300: Map( i, &Vrc6::Poke_B003 ); break; case 0xD000: Map( i, CHR_SWAP_1K_0 ); break; case 0xD100: Map( i, CHR_SWAP_1K_1 ); break; case 0xD200: Map( i, CHR_SWAP_1K_2 ); break; case 0xD300: Map( i, CHR_SWAP_1K_3 ); break; case 0xE000: Map( i, CHR_SWAP_1K_4 ); break; case 0xE100: Map( i, CHR_SWAP_1K_5 ); break; case 0xE200: Map( i, CHR_SWAP_1K_6 ); break; case 0xE300: Map( i, CHR_SWAP_1K_7 ); break; case 0xF000: Map( i, &Vrc6::Poke_F000 ); break; case 0xF100: Map( i, &Vrc6::Poke_F001 ); break; case 0xF200: Map( i, &Vrc6::Poke_F002 ); break; } } } bool Vrc6::Sound::Square::CanOutput() const { return volume && enabled && !digitized && waveLength >= MIN_FRQ; } bool Vrc6::Sound::Saw::CanOutput() const { return enabled && phase && waveLength >= MIN_FRQ; } void Vrc6::Sound::Square::UpdateSettings(const uint fixed) { active = CanOutput(); frequency = (waveLength + 1U) * fixed; } void Vrc6::Sound::Saw::UpdateSettings(const uint fixed) { active = CanOutput(); frequency = ((waveLength + 1UL) << FRQ_SHIFT) * fixed; } bool Vrc6::Sound::UpdateSettings() { uint volume = GetVolume(EXT_VRC6); output = IsMuted() ? 0 : volume; GetOscillatorClock( rate, fixed ); for (uint i=0; i < 2; ++i) square[i].UpdateSettings( fixed ); saw.UpdateSettings( fixed ); dcBlocker.Reset(); return volume; } void Vrc6::SubLoad(State::Loader& state,const dword baseChunk) { NST_VERIFY( baseChunk == (AsciiId<'K','V','6'>::V) ); if (baseChunk == AsciiId<'K','V','6'>::V) { while (const dword chunk = state.Begin()) { switch (chunk) { case AsciiId<'I','R','Q'>::V: irq.LoadState( state ); break; case AsciiId<'S','N','D'>::V: sound.LoadState( state ); break; } state.End(); } } } void Vrc6::SubSave(State::Saver& state) const { state.Begin( AsciiId<'K','V','6'>::V ); irq.SaveState( state, AsciiId<'I','R','Q'>::V ); sound.SaveState( state, AsciiId<'S','N','D'>::V ); state.End(); } void Vrc6::Sound::SaveState(State::Saver& state,const dword baseChunk) const { state.Begin( baseChunk ); square[0].SaveState( state, AsciiId<'S','Q','0'>::V ); square[1].SaveState( state, AsciiId<'S','Q','1'>::V ); saw.SaveState( state, AsciiId<'S','A','W'>::V ); state.End(); } void Vrc6::Sound::LoadState(State::Loader& state) { while (const dword chunk = state.Begin()) { switch (chunk) { case AsciiId<'S','Q','0'>::V: square[0].LoadState( state, fixed ); break; case AsciiId<'S','Q','1'>::V: square[1].LoadState( state, fixed ); break; case AsciiId<'S','A','W'>::V: saw.LoadState( state, fixed ); break; } state.End(); } } void Vrc6::Sound::Square::SaveState(State::Saver& state,const dword chunk) const { const byte data[4] = { (enabled ? 0x1U : 0x0U) | (digitized ? 0x2U : 0x0U), waveLength & 0xFF, waveLength >> 8, (duty - 1) | ((volume / VOLUME) << 3) }; state.Begin( chunk ).Begin( AsciiId<'R','E','G'>::V ).Write( data ).End().End(); } void Vrc6::Sound::Square::LoadState(State::Loader& state,const uint fixed) { while (const dword chunk = state.Begin()) { if (chunk == AsciiId<'R','E','G'>::V) { State::Loader::Data<4> data( state ); enabled = data[0] & 0x1; digitized = data[0] & 0x2; waveLength = data[1] | (data[2] << 8 & 0xF00); duty = (data[3] & 0x7) + 1; volume = (data[3] >> 3 & 0xF) * VOLUME; timer = 0; step = 0; UpdateSettings( fixed ); } state.End(); } } void Vrc6::Sound::Saw::SaveState(State::Saver& state,const dword chunk) const { const byte data[3] = { (enabled != 0) | (phase << 1), waveLength & 0xFF, waveLength >> 8 }; state.Begin( chunk ).Begin( AsciiId<'R','E','G'>::V ).Write( data ).End().End(); } void Vrc6::Sound::Saw::LoadState(State::Loader& state,const uint fixed) { while (const dword chunk = state.Begin()) { if (chunk == AsciiId<'R','E','G'>::V) { State::Loader::Data<3> data( state ); enabled = data[0] & 0x1; phase = data[0] >> 1 & 0x3F; waveLength = data[1] | (data[2] << 8 & 0xF00); timer = 0; step = 0; amp = 0; UpdateSettings( fixed ); } state.End(); } } #ifdef NST_MSVC_OPTIMIZE #pragma optimize("", on) #endif NST_SINGLE_CALL void Vrc6::Sound::Square::WriteReg0(const uint data) { volume = (data & REG0_VOLUME) * VOLUME; duty = ((data & REG0_DUTY) >> REG0_DUTY_SHIFT) + 1; digitized = data & REG0_DIGITIZED; NST_VERIFY( !digitized ); active = CanOutput(); } NST_SINGLE_CALL void Vrc6::Sound::Square::WriteReg1(const uint data,const dword fixed) { waveLength &= uint(REG2_WAVELENGTH_HIGH) << 8; waveLength |= data; frequency = (waveLength + 1U) * fixed; active = CanOutput(); } NST_SINGLE_CALL void Vrc6::Sound::Square::WriteReg2(const uint data,const dword fixed) { waveLength &= REG1_WAVELENGTH_LOW; waveLength |= (data & REG2_WAVELENGTH_HIGH) << 8; frequency = (waveLength + 1U) * fixed; enabled = data & REG2_ENABLE; active = CanOutput(); } NST_SINGLE_CALL void Vrc6::Sound::Saw::WriteReg0(const uint data) { phase = data & REG0_PHASE; active = CanOutput(); } NST_SINGLE_CALL void Vrc6::Sound::Saw::WriteReg1(const uint data,const dword fixed) { waveLength &= uint(REG2_WAVELENGTH_HIGH) << 8; waveLength |= data; frequency = ((waveLength + 1UL) << FRQ_SHIFT) * fixed; active = CanOutput(); } NST_SINGLE_CALL void Vrc6::Sound::Saw::WriteReg2(const uint data,const dword fixed) { waveLength &= REG1_WAVELENGTH_LOW; waveLength |= (data & REG2_WAVELENGTH_HIGH) << 8; frequency = ((waveLength + 1UL) << FRQ_SHIFT) * fixed; enabled = data & REG2_ENABLE; active = CanOutput(); } void Vrc6::Sound::WriteSquareReg0(uint i,uint data) { Update(); square[i].WriteReg0( data ); } void Vrc6::Sound::WriteSquareReg1(uint i,uint data) { Update(); square[i].WriteReg1( data, fixed ); } void Vrc6::Sound::WriteSquareReg2(uint i,uint data) { Update(); square[i].WriteReg2( data, fixed ); } void Vrc6::Sound::WriteSawReg0(uint data) { Update(); saw.WriteReg0( data ); } void Vrc6::Sound::WriteSawReg1(uint data) { Update(); saw.WriteReg1( data, fixed ); } void Vrc6::Sound::WriteSawReg2(uint data) { Update(); saw.WriteReg2( data, fixed ); } NES_POKE_D(Vrc6,9000) { sound.WriteSquareReg0 ( 0, data ); } NES_POKE_D(Vrc6,9001) { sound.WriteSquareReg1 ( 0, data ); } NES_POKE_D(Vrc6,9002) { sound.WriteSquareReg2 ( 0, data ); } NES_POKE_D(Vrc6,A000) { sound.WriteSquareReg0 ( 1, data ); } NES_POKE_D(Vrc6,A001) { sound.WriteSquareReg1 ( 1, data ); } NES_POKE_D(Vrc6,A002) { sound.WriteSquareReg2 ( 1, data ); } NES_POKE_D(Vrc6,B000) { sound.WriteSawReg0 ( data ); } NES_POKE_D(Vrc6,B001) { sound.WriteSawReg1 ( data ); } NES_POKE_D(Vrc6,B002) { sound.WriteSawReg2 ( data ); } NST_SINGLE_CALL dword Vrc6::Sound::Square::GetSample(const Cycle rate) { NST_VERIFY( bool(active) == CanOutput() && timer >= 0 ); if (active) { dword sum = timer; timer -= idword(rate); if (timer >= 0) { return step < duty ? volume : 0; } else { if (step >= duty) sum = 0; do { step = (step + 1) & 0xF; if (step < duty) sum += NST_MIN(dword(-timer),frequency); timer += idword(frequency); } while (timer < 0); return (sum * volume + (rate/2)) / rate; } } return 0; } NST_SINGLE_CALL dword Vrc6::Sound::Saw::GetSample(const Cycle rate) { NST_VERIFY( bool(active) == CanOutput() && timer >= 0 ); if (active) { dword sum = timer; timer -= idword(rate); if (timer >= 0) { return (amp >> 3) * VOLUME; } else { sum *= amp; do { if (++step >= 0x7) { step = 0; amp = 0; } amp = (amp + phase) & 0xFF; sum += NST_MIN(dword(-timer),frequency) * amp; timer += idword(frequency); } while (timer < 0); return ((sum >> 3) * VOLUME + (rate / 2)) / rate; } } return 0; } Vrc6::Sound::Sample Vrc6::Sound::GetSample() { if (output) { dword sample = 0; for (uint i=0; i < 2; ++i) sample += square[i].GetSample( rate ); sample += saw.GetSample( rate ); return dcBlocker.Apply( sample * output / DEFAULT_VOLUME ); } else { return 0; } } NES_POKE_D(Vrc6,B003) { SetMirroringVH01( data >> 2 ); } NES_POKE_D(Vrc6,F000) { irq.Update(); irq.unit.latch = data; } NES_POKE_D(Vrc6,F001) { irq.Toggle( data ); } NES_POKE(Vrc6,F002) { irq.Toggle(); } void Vrc6::Sync(Event event,Input::Controllers* controllers) { if (event == EVENT_END_FRAME) irq.VSync(); Board::Sync( event, controllers ); } } } } }