mirror of
https://github.com/0ldsk00l/nestopia.git
synced 2025-04-02 10:31:51 -04:00
556 lines
14 KiB
C++
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 );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|