nestopia/source/core/board/NstBoardKonamiVrc6.cpp
2012-09-02 13:13:55 -04:00

556 lines
14 KiB
C++

////////////////////////////////////////////////////////////////////////////////////////
//
// 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 );
}
}
}
}
}