nestopia/source/core/board/NstBoardSunsoft5b.cpp
2021-02-15 08:01:46 -05:00

605 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 "NstBoardSunsoftFme7.hpp"
#include "NstBoardSunsoft5b.hpp"
namespace Nes
{
namespace Core
{
namespace Boards
{
namespace Sunsoft
{
#ifdef NST_MSVC_OPTIMIZE
#pragma optimize("s", on)
#endif
const word S5b::Sound::levels[32] =
{
// 32 levels, 1.5dB per step
0,89,106,127,152,181,216,257,306,364,433,515,613,729,867,1031,1226,1458,
1733,2060,2449,2911,3460,4113,4889,5811,6907,8209,9757,11597,13784,16383
};
S5b::Sound::Sound(Apu& a,bool connect)
: Channel(a), fixed(1)
{
Reset();
bool audible = UpdateSettings();
if (connect)
Connect( audible );
}
S5b::S5b(const Context& c)
:
Fme7 (c),
sound (*c.apu)
{}
void S5b::Sound::Envelope::Reset(const uint fixed)
{
holding = false;
hold = 0;
alternate = 0;
attack = 0;
timer = 0;
frequency = 1 * 8 * fixed;
length = 0;
count = 0;
volume = 0;
}
void S5b::Sound::Noise::Reset(const uint fixed)
{
timer = 0;
frequency = 1 * 16 * fixed;
length = 0;
rng = 1;
dc = 0;
}
void S5b::Sound::Square::Reset(const uint fixed)
{
timer = 0;
frequency = 1 * 16 * fixed;
status = 0;
ctrl = 0;
volume = 0;
dc = 0;
length = 0;
}
void S5b::Sound::Reset()
{
active = false;
regSelect = 0x0;
envelope.Reset( fixed );
for (uint i=0; i < NUM_SQUARES; ++i)
squares[i].Reset( fixed );
noise.Reset( fixed );
dcBlocker.Reset();
}
void S5b::SubReset(const bool hard)
{
Fme7::SubReset( hard );
Map( 0xC000U, 0xDFFFU, &S5b::Poke_C000 );
Map( 0xE000U, 0xFFFFU, &S5b::Poke_E000 );
}
void S5b::Sound::Envelope::UpdateSettings(const uint fixed)
{
timer = 0;
UpdateFrequency( fixed );
}
void S5b::Sound::Noise::UpdateSettings(const uint fixed)
{
timer = 0;
UpdateFrequency( fixed );
}
void S5b::Sound::Square::UpdateSettings(const uint fixed)
{
timer = 0;
UpdateFrequency( fixed );
}
bool S5b::Sound::UpdateSettings()
{
uint volume = GetVolume(EXT_S5B) * 94U / DEFAULT_VOLUME;
output = IsMuted() ? 0 : volume;
GetOscillatorClock( rate, fixed );
envelope.UpdateSettings( fixed );
for (uint i=0; i < NUM_SQUARES; ++i)
squares[i].UpdateSettings( fixed );
noise.UpdateSettings( fixed );
dcBlocker.Reset();
return volume;
}
void S5b::SubLoad(State::Loader& state,const dword baseChunk)
{
if (baseChunk == AsciiId<'S','5','B'>::V)
{
while (const dword chunk = state.Begin())
{
if (chunk == AsciiId<'S','N','D'>::V)
sound.LoadState( state );
state.End();
}
}
else
{
Fme7::SubLoad( state, baseChunk );
}
}
void S5b::SubSave(State::Saver& state) const
{
Fme7::SubSave( state );
state.Begin( AsciiId<'S','5','B'>::V );
sound.SaveState( state, AsciiId<'S','N','D'>::V );
state.End();
}
void S5b::Sound::SaveState(State::Saver& state,const dword baseChunk) const
{
state.Begin( baseChunk );
state.Begin( AsciiId<'R','E','G'>::V ).Write8( regSelect ).End();
envelope.SaveState ( state, AsciiId<'E','N','V'>::V );
noise.SaveState ( state, AsciiId<'N','O','I'>::V );
squares[0].SaveState ( state, AsciiId<'S','Q','0'>::V );
squares[1].SaveState ( state, AsciiId<'S','Q','1'>::V );
squares[2].SaveState ( state, AsciiId<'S','Q','2'>::V );
state.End();
}
void S5b::Sound::LoadState(State::Loader& state)
{
while (const dword chunk = state.Begin())
{
switch (chunk)
{
case AsciiId<'R','E','G'>::V:
regSelect = state.Read8();
break;
case AsciiId<'E','N','V'>::V:
envelope.LoadState( state, fixed );
break;
case AsciiId<'N','O','I'>::V:
noise.LoadState( state, fixed );
break;
case AsciiId<'S','Q','0'>::V:
squares[0].LoadState( state, fixed );
break;
case AsciiId<'S','Q','1'>::V:
squares[1].LoadState( state, fixed );
break;
case AsciiId<'S','Q','2'>::V:
squares[2].LoadState( state, fixed );
break;
}
state.End();
}
}
void S5b::Sound::Envelope::SaveState(State::Saver& state,const dword chunk) const
{
const byte data[4] =
{
(holding ? 0x1U : 0x0U) |
(hold ? 0x2U : 0x1U) |
(alternate ? 0x4U : 0x0U) |
(attack ? 0x8U : 0x0U),
count,
length & 0xFF,
length >> 8
};
state.Begin( chunk ).Begin( AsciiId<'R','E','G'>::V ).Write( data ).End().End();
}
void S5b::Sound::Noise::SaveState(State::Saver& state,const dword chunk) const
{
state.Begin( chunk ).Begin( AsciiId<'R','E','G'>::V ).Write8( length ).End().End();
}
void S5b::Sound::Square::SaveState(State::Saver& state,const dword chunk) const
{
const byte data[3] =
{
(~status & 0x1) | (ctrl << 1),
length & 0xFF,
(length >> 8) | ((status & 0x8) << 1),
};
state.Begin( chunk ).Begin( AsciiId<'R','E','G'>::V ).Write( data ).End().End();
}
void S5b::Sound::Envelope::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 );
holding = data[0] & 0x1;
hold = data[0] & 0x2;
alternate = data[0] & 0x4;
attack = (data[0] & 0x8) ? 0x1F : 0x00;
count = data[1] & 0x1F;
length = data[2] | (data[3] << 8 & 0xF00);
volume = levels[count ^ attack];
UpdateSettings( fixed );
}
state.End();
}
}
void S5b::Sound::Noise::LoadState(State::Loader& state,const uint fixed)
{
while (const dword chunk = state.Begin())
{
if (chunk == AsciiId<'R','E','G'>::V)
{
length = state.Read8() & 0x1F;
dc = 0;
rng = 1;
UpdateSettings( fixed );
}
state.End();
}
}
void S5b::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<3> data( state );
status = (~data[0] & 0x1) | (data[2] >> 1 & 0x8);
ctrl = data[0] >> 1 & 0x1F;
length = data[1] | (data[2] << 8 & 0xF00);
volume = levels[(ctrl & 0xF) ? (ctrl & 0xF) * 2 + 1 : 0];
dc = (status & 0x1) ? ~dword(0) : dword(0);
UpdateSettings( fixed );
}
state.End();
}
}
#ifdef NST_MSVC_OPTIMIZE
#pragma optimize("", on)
#endif
NES_POKE_D(S5b,C000)
{
sound.SelectReg( data );
}
void S5b::Sound::Square::UpdateFrequency(const uint fixed)
{
const idword prev = frequency;
frequency = NST_MAX(length,1) * 16 * fixed;
timer = NST_MAX(timer + idword(frequency) - prev,0);
}
void S5b::Sound::Square::WriteReg0(const uint data,const uint fixed)
{
length = (length & 0x0F00) | data;
UpdateFrequency( fixed );
}
void S5b::Sound::Square::WriteReg1(const uint data,const uint fixed)
{
length = (length & 0x00FF) | (data & 0xF) << 8;
UpdateFrequency( fixed );
}
void S5b::Sound::Square::WriteReg2(const uint data)
{
status = data & (0x1|0x8);
if (status & 0x1)
dc = (unsigned int) ~0UL;
}
void S5b::Sound::Square::WriteReg3(const uint data)
{
ctrl = data & 0x1F;
volume = levels[(ctrl & 0xF) ? (ctrl & 0xF) * 2 + 1 : 0];
}
void S5b::Sound::Envelope::UpdateFrequency(const uint fixed)
{
const idword prev = frequency;
frequency = NST_MAX(length*16,1*8) * fixed;
timer = NST_MAX(timer + idword(frequency) - prev,0);
}
void S5b::Sound::Envelope::WriteReg0(const uint data,const uint fixed)
{
length = (length & 0xFF00) | data << 0;
UpdateFrequency( fixed );
}
void S5b::Sound::Envelope::WriteReg1(const uint data,const uint fixed)
{
length = (length & 0x00FF) | data << 8;
UpdateFrequency( fixed );
}
void S5b::Sound::Envelope::WriteReg2(const uint data)
{
holding = false;
attack = (data & 0x04) ? 0x1F : 0x00;
if (data & 0x8)
{
hold = data & 0x1;
alternate = data & 0x2;
}
else
{
hold = 1;
alternate = attack;
}
timer = frequency;
count = 0x1F;
volume = levels[count ^ attack];
}
void S5b::Sound::Noise::UpdateFrequency(const uint fixed)
{
const idword prev = frequency;
frequency = NST_MAX(length,1) * 16 * fixed;
timer = NST_MAX(timer + idword(frequency) - prev,0);
}
void S5b::Sound::Noise::WriteReg(const uint data,const uint fixed)
{
length = data & 0x1F;
UpdateFrequency( fixed );
}
void S5b::Sound::WriteReg(const uint data)
{
Update();
active = true;
switch (regSelect & 0xF)
{
case 0x0:
case 0x2:
case 0x4:
squares[regSelect >> 1].WriteReg0( data, fixed );
break;
case 0x1:
case 0x3:
case 0x5:
squares[regSelect >> 1].WriteReg1( data, fixed );
break;
case 0x6:
noise.WriteReg( data, fixed );
break;
case 0x7:
for (uint i=0; i < NUM_SQUARES; ++i)
squares[i].WriteReg2( data >> i );
break;
case 0x8:
case 0x9:
case 0xA:
squares[regSelect - 0x8].WriteReg3( data );
break;
case 0xB:
envelope.WriteReg0( data, fixed );
break;
case 0xC:
envelope.WriteReg1( data, fixed );
break;
case 0xD:
envelope.WriteReg2( data );
break;
}
}
NES_POKE_D(S5b,E000)
{
sound.WriteReg( data );
}
NST_SINGLE_CALL dword S5b::Sound::Envelope::Clock(const Cycle rate)
{
if (!holding)
{
timer -= idword(rate);
if (timer < 0)
{
do
{
--count;
timer += idword(frequency);
}
while (timer < 0);
if (count > 0x1F)
{
if (hold)
{
if (alternate)
attack ^= 0x1FU;
holding = true;
count = 0x00;
}
else
{
if (alternate && count & 0x20)
attack ^= 0x1FU;
count = 0x1F;
}
}
volume = levels[count ^ attack];
}
}
return volume;
}
NST_SINGLE_CALL dword S5b::Sound::Noise::Clock(const Cycle rate)
{
for (timer -= idword(rate); timer < 0; timer += idword(frequency))
{
if ((rng + 1) & 0x2) dc = ~dc;
if ((rng + 0) & 0x1) rng ^= 0x24000;
rng >>= 1;
}
return dc;
}
NST_SINGLE_CALL dword S5b::Sound::Square::GetSample(const Cycle rate,const uint envelope,const uint noise)
{
dword sum = timer;
timer -= idword(rate);
const uint out = (ctrl & 0x10) ? envelope : volume;
if ((noise|status) & 0x8 && out)
{
if (timer >= 0)
{
return out & dc;
}
else
{
sum &= dc;
do
{
dc ^= (status & 0x1) - 1UL;
sum += NST_MIN(dword(-timer),frequency) & dc;
timer += idword(frequency);
}
while (timer < 0);
NST_VERIFY( sum <= 0xFFFFFFFF / out + rate/2 );
return (sum * out + rate/2) / rate;
}
}
else
{
while (timer < 0)
{
dc ^= (status & 0x1) - 1UL;
timer += idword(frequency);
}
return 0;
}
}
S5b::Sound::Sample S5b::Sound::GetSample()
{
if (active && output)
{
dword sample = 0;
for (dword i=0, e=envelope.Clock( rate ), n=noise.Clock( rate ); i < NUM_SQUARES; ++i)
sample += squares[i].GetSample( rate, e, n );
return dcBlocker.Apply( sample * output / DEFAULT_VOLUME );
}
else
{
return 0;
}
}
}
}
}
}