Files
Taylor Lineman d8e31d010d Fix many more castings
Signed-off-by: Taylor Lineman <git@actuallytaylor.com>
2025-10-13 13:24:56 -04:00

2698 lines
62 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 <cstring>
#include "NstCpu.hpp"
#include "NstState.hpp"
#include "api/NstApiSound.hpp"
#include "NstSoundRenderer.inl"
namespace Nes
{
namespace Core
{
const dword Apu::Cycles::frameClocks[3][4] =
{
{
CPU_RP2A03_CC * 29830UL,
CPU_RP2A03_CC,
CPU_RP2A03_CC,
CPU_RP2A03_CC * (29830UL - 2),
},
{
CPU_RP2A07_CC * 33254UL,
CPU_RP2A07_CC,
CPU_RP2A07_CC,
CPU_RP2A07_CC * (33254UL - 2)
},
{
CPU_DENDY_CC * 29830UL,
CPU_DENDY_CC,
CPU_DENDY_CC,
CPU_DENDY_CC * (29830UL - 2),
}
};
const dword Apu::Cycles::oscillatorClocks[3][2][4] =
{
{
{
CPU_RP2A03_CC * (7459UL - 1),
CPU_RP2A03_CC * 7456UL,
CPU_RP2A03_CC * 7458UL,
CPU_RP2A03_CC * 7458UL
},
{
CPU_RP2A03_CC * 7458UL,
CPU_RP2A03_CC * 7456UL,
CPU_RP2A03_CC * 7458UL,
CPU_RP2A03_CC * (7458UL + 7452)
}
},
{
{
CPU_RP2A07_CC * (8315UL - 1),
CPU_RP2A07_CC * 8314UL,
CPU_RP2A07_CC * 8312UL,
CPU_RP2A07_CC * 8314UL
},
{
CPU_RP2A07_CC * 8314UL,
CPU_RP2A07_CC * 8314UL,
CPU_RP2A07_CC * 8312UL,
CPU_RP2A07_CC * (8314UL + 8312)
}
},
{
{
CPU_DENDY_CC * (7459UL - 1),
CPU_DENDY_CC * 7456UL,
CPU_DENDY_CC * 7458UL,
CPU_DENDY_CC * 7458UL
},
{
CPU_DENDY_CC * 7458UL,
CPU_DENDY_CC * 7456UL,
CPU_DENDY_CC * 7458UL,
CPU_DENDY_CC * (7458UL + 7452)
}
}
};
const byte Apu::Channel::LengthCounter::lut[32] =
{
0x0A, 0xFE, 0x14, 0x02,
0x28, 0x04, 0x50, 0x06,
0xA0, 0x08, 0x3C, 0x0A,
0x0E, 0x0C, 0x1A, 0x0E,
0x0C, 0x10, 0x18, 0x12,
0x30, 0x14, 0x60, 0x16,
0xC0, 0x18, 0x48, 0x1A,
0x10, 0x1C, 0x20, 0x1E
};
const word Apu::Noise::lut[3][16] =
{
{
0x004, 0x008, 0x010, 0x020,
0x040, 0x060, 0x080, 0x0A0,
0x0CA, 0x0FE, 0x17C, 0x1FC,
0x2FA, 0x3F8, 0x7F2, 0xFE4
},
{
0x004, 0x007, 0x00E, 0x01E,
0x03C, 0x058, 0x076, 0x094,
0x0BC, 0x0EC, 0x162, 0x1D8,
0x2C4, 0x3B0, 0x762, 0xEC2
},
{
0x004, 0x008, 0x010, 0x020,
0x040, 0x060, 0x080, 0x0A0,
0x0CA, 0x0FE, 0x17C, 0x1FC,
0x2FA, 0x3F8, 0x7F2, 0xFE4
}
};
const word Apu::Dmc::lut[3][16] =
{
{
0x1AC * CPU_RP2A03_CC,
0x17C * CPU_RP2A03_CC,
0x154 * CPU_RP2A03_CC,
0x140 * CPU_RP2A03_CC,
0x11E * CPU_RP2A03_CC,
0x0FE * CPU_RP2A03_CC,
0x0E2 * CPU_RP2A03_CC,
0x0D6 * CPU_RP2A03_CC,
0x0BE * CPU_RP2A03_CC,
0x0A0 * CPU_RP2A03_CC,
0x08E * CPU_RP2A03_CC,
0x080 * CPU_RP2A03_CC,
0x06A * CPU_RP2A03_CC,
0x054 * CPU_RP2A03_CC,
0x048 * CPU_RP2A03_CC,
0x036 * CPU_RP2A03_CC
},
{
0x18E * CPU_RP2A07_CC,
0x162 * CPU_RP2A07_CC,
0x13C * CPU_RP2A07_CC,
0x12A * CPU_RP2A07_CC,
0x114 * CPU_RP2A07_CC,
0x0EC * CPU_RP2A07_CC,
0x0D2 * CPU_RP2A07_CC,
0x0C6 * CPU_RP2A07_CC,
0x0B0 * CPU_RP2A07_CC,
0x094 * CPU_RP2A07_CC,
0x084 * CPU_RP2A07_CC,
0x076 * CPU_RP2A07_CC,
0x062 * CPU_RP2A07_CC,
0x04E * CPU_RP2A07_CC,
0x042 * CPU_RP2A07_CC,
0x032 * CPU_RP2A07_CC
},
{
0x1AC * CPU_DENDY_CC,
0x17C * CPU_DENDY_CC,
0x154 * CPU_DENDY_CC,
0x140 * CPU_DENDY_CC,
0x11E * CPU_DENDY_CC,
0x0FE * CPU_DENDY_CC,
0x0E2 * CPU_DENDY_CC,
0x0D6 * CPU_DENDY_CC,
0x0BE * CPU_DENDY_CC,
0x0A0 * CPU_DENDY_CC,
0x08E * CPU_DENDY_CC,
0x080 * CPU_DENDY_CC,
0x06A * CPU_DENDY_CC,
0x054 * CPU_DENDY_CC,
0x048 * CPU_DENDY_CC,
0x036 * CPU_DENDY_CC
}
};
#ifdef NST_MSVC_OPTIMIZE
#pragma optimize("s", on)
#endif
Apu::Apu(Cpu& c)
:
cpu (c),
extChannel (NULL),
buffer (16)
{
NST_COMPILE_ASSERT( CPU_RP2A03 == 0 && CPU_RP2A07 == 1 && CPU_DENDY == 2 );
PowerOff();
}
void Apu::PowerOff()
{
Reset( false, true );
}
void Apu::Reset(bool hard)
{
Reset( true, hard );
}
void Apu::Reset(const bool on,const bool hard)
{
if (on)
UpdateSettings();
updater = &Apu::SyncOff;
cycles.Reset( extChannel, cpu.GetModel() );
synchronizer.Resync( settings.speed, cpu );
for (uint i=0; i < 2; ++i)
square[i].Reset();
triangle.Reset();
noise.Reset( cpu.GetModel() );
dmc.Reset( cpu.GetModel() );
dcBlocker.Reset();
stream = NULL;
buffer.Reset();
if (on)
{
cpu.Map( 0x4000 ).Set( this, &Apu::Peek_40xx, &Apu::Poke_4000 );
cpu.Map( 0x4001 ).Set( this, &Apu::Peek_40xx, &Apu::Poke_4001 );
cpu.Map( 0x4002 ).Set( this, &Apu::Peek_40xx, &Apu::Poke_4002 );
cpu.Map( 0x4003 ).Set( this, &Apu::Peek_40xx, &Apu::Poke_4003 );
cpu.Map( 0x4004 ).Set( this, &Apu::Peek_40xx, &Apu::Poke_4000 );
cpu.Map( 0x4005 ).Set( this, &Apu::Peek_40xx, &Apu::Poke_4001 );
cpu.Map( 0x4006 ).Set( this, &Apu::Peek_40xx, &Apu::Poke_4002 );
cpu.Map( 0x4007 ).Set( this, &Apu::Peek_40xx, &Apu::Poke_4003 );
cpu.Map( 0x4008 ).Set( this, &Apu::Peek_40xx, &Apu::Poke_4008 );
cpu.Map( 0x400A ).Set( this, &Apu::Peek_40xx, &Apu::Poke_400A );
cpu.Map( 0x400B ).Set( this, &Apu::Peek_40xx, &Apu::Poke_400B );
cpu.Map( 0x400C ).Set( this, &Apu::Peek_40xx, &Apu::Poke_400C );
cpu.Map( 0x400E ).Set( this, &Apu::Peek_40xx, &Apu::Poke_400E );
cpu.Map( 0x400F ).Set( this, &Apu::Peek_40xx, &Apu::Poke_400F );
cpu.Map( 0x4010 ).Set( this, &Apu::Peek_40xx, &Apu::Poke_4010 );
cpu.Map( 0x4011 ).Set( this, &Apu::Peek_40xx, &Apu::Poke_4011 );
cpu.Map( 0x4012 ).Set( this, &Apu::Peek_40xx, &Apu::Poke_4012 );
cpu.Map( 0x4013 ).Set( this, &Apu::Peek_40xx, &Apu::Poke_4013 );
cpu.Map( 0x4015 ).Set( this, &Apu::Peek_4015, &Apu::Poke_4015 );
if (cpu.GetApu().IsGenie())
{
NES_DO_POKE(4000,0x4000,0x30);
NES_DO_POKE(4001,0x4001,0xF9);
NES_DO_POKE(400C,0x400C,0x30);
NES_DO_POKE(400E,0x400E,0x0E);
NES_DO_POKE(400F,0x400F,0x04);
NES_DO_POKE(4015,0x4015,0x09);
}
if (hard)
{
ctrl = STATUS_FRAME_IRQ_ENABLE;
}
if (ctrl == STATUS_FRAME_IRQ_ENABLE)
cycles.frameIrqClock = (cycles.frameCounter / cycles.fixed) - cpu.GetClock();
if (extChannel)
extChannel->Reset();
}
else
{
ctrl = STATUS_FRAME_IRQ_ENABLE;
}
}
Result Apu::SetSampleRate(const dword rate)
{
if (settings.rate == rate)
return RESULT_NOP;
if (!rate)
return RESULT_ERR_INVALID_PARAM;
if (rate < 44100 || rate > 96000)
return RESULT_ERR_UNSUPPORTED;
settings.rate = rate;
UpdateSettings();
return RESULT_OK;
}
Result Apu::SetVolume(const uint channels,const uint volume)
{
if (volume > 100)
return RESULT_ERR_INVALID_PARAM;
bool updated = false;
for (uint i=0; i < MAX_CHANNELS; ++i)
{
if (channels & (1U << i))
{
if (settings.volumes[i] != volume)
{
settings.volumes[i] = volume;
updated = true;
}
}
}
if (!updated)
return RESULT_NOP;
UpdateSettings();
return RESULT_OK;
}
uint Apu::GetVolume(const uint channel) const
{
for (uint i=0; i < MAX_CHANNELS; ++i)
{
if (channel & (1U << i))
return settings.volumes[i];
}
return 0;
}
uint Apu::GetCtrl()
{
return ctrl;
}
Result Apu::SetSpeed(const uint speed)
{
if (settings.speed == speed)
return RESULT_NOP;
if ((speed > 0 && speed < 30) || speed > 240)
return RESULT_ERR_UNSUPPORTED;
settings.speed = speed;
UpdateSettings();
return RESULT_OK;
}
void Apu::Mute(const bool mute)
{
if (settings.muted != mute)
{
settings.muted = mute;
UpdateSettings();
}
}
void Apu::SetAutoTranspose(const bool transpose)
{
if (settings.transpose != transpose)
{
settings.transpose = transpose;
UpdateSettings();
}
}
void Apu::SetGenie(const bool genie)
{
if (settings.genie != genie)
{
settings.genie = genie;
UpdateSettings();
}
}
void Apu::EnableStereo(const bool enable)
{
if (settings.stereo != enable)
{
settings.stereo = enable;
UpdateSettings();
}
}
void Apu::UpdateSettings()
{
cycles.Update( settings.rate, settings.speed, cpu );
synchronizer.Reset( settings.speed, settings.rate, cpu );
dcBlocker.Reset();
buffer.Reset();
Cycle rate; uint fixed;
CalculateOscillatorClock( rate, fixed );
square[0].UpdateSettings ( settings.muted ? 0 : settings.volumes[ Channel::APU_SQUARE1 ], rate, fixed );
square[1].UpdateSettings ( settings.muted ? 0 : settings.volumes[ Channel::APU_SQUARE2 ], rate, fixed );
triangle.UpdateSettings ( settings.muted ? 0 : settings.volumes[ Channel::APU_TRIANGLE ], rate, fixed );
noise.UpdateSettings ( settings.muted ? 0 : settings.volumes[ Channel::APU_NOISE ], rate, fixed );
dmc.UpdateSettings ( settings.muted ? 0 : settings.volumes[ Channel::APU_DPCM ] );
UpdateVolumes();
}
void Apu::UpdateVolumes()
{
settings.audible = (extChannel && extChannel->UpdateSettings()) ||
(
uint(settings.volumes[ Channel::APU_SQUARE1 ]) |
uint(settings.volumes[ Channel::APU_SQUARE2 ]) |
uint(settings.volumes[ Channel::APU_TRIANGLE ]) |
uint(settings.volumes[ Channel::APU_NOISE ]) |
uint(settings.volumes[ Channel::APU_DPCM ])
);
}
void Apu::Resync(const dword rate)
{
cycles.Update( rate, settings.speed, cpu );
ClearBuffers( false );
}
void Apu::CalculateOscillatorClock(Cycle& rate,uint& fixed) const
{
dword sampleRate = settings.rate;
if (settings.transpose && settings.speed)
sampleRate = sampleRate * cpu.GetFps() / settings.speed;
uint multiplier = 0;
const qaword clockBase = cpu.GetClockBase();
while (++multiplier < 0x1000 && clockBase * (multiplier+1) / sampleRate <= 0x7FFFF && clockBase * multiplier % sampleRate);
rate = clockBase * multiplier / sampleRate;
fixed = cpu.GetClockDivider() * cpu.GetClock() * multiplier;
}
void Apu::SaveState(State::Saver& state,const dword baseChunk) const
{
state.Begin( baseChunk );
{
Cycle clock = cycles.frameCounter / cycles.fixed;
NST_VERIFY( clock >= cpu.GetCycles() );
if (clock > cpu.GetCycles())
clock = (clock - cpu.GetCycles()) / cpu.GetClock();
else
clock = 0;
NST_VERIFY( cycles.frameCounter == (cpu.GetCycles() + clock * cpu.GetClock()) * cycles.fixed );
const byte data[4] =
{
static_cast<byte>(ctrl),
static_cast<byte>(clock & 0xFF),
static_cast<byte>(clock >> 8),
static_cast<byte>(cycles.frameDivider)
};
state.Begin( AsciiId<'F','R','M'>::V ).Write( data ).End();
}
if (cycles.frameIrqClock != Cpu::CYCLE_MAX)
{
Cycle clock = cycles.frameIrqClock;
NST_VERIFY( clock >= cpu.GetCycles() );
if (clock > cpu.GetCycles())
clock = (clock - cpu.GetCycles()) / cpu.GetClock();
else
clock = 0;
NST_VERIFY( cycles.frameIrqClock == cpu.GetCycles() + clock * cpu.GetClock() );
const byte data[3] =
{
static_cast<byte>(clock & 0xFF),
static_cast<byte>(clock >> 8),
static_cast<byte>(cycles.frameIrqRepeat % 3)
};
state.Begin( AsciiId<'I','R','Q'>::V ).Write( data ).End();
}
if (cycles.extCounter != Cpu::CYCLE_MAX)
{
Cycle clock = cycles.extCounter / cycles.fixed;
NST_VERIFY( clock >= cpu.GetCycles() || clock == 0 );
if (clock > cpu.GetCycles())
{
clock = (clock - cpu.GetCycles()) / cpu.GetClock();
NST_VERIFY( cycles.extCounter == (cpu.GetCycles() + clock * cpu.GetClock()) * cycles.fixed );
}
else
{
clock = 0;
}
state.Begin( AsciiId<'E','X','T'>::V ).Write16( clock ).End();
}
square[0].SaveState( state, AsciiId<'S','Q','0'>::V );
square[1].SaveState( state, AsciiId<'S','Q','1'>::V );
triangle.SaveState( state, AsciiId<'T','R','I'>::V );
noise.SaveState( state, AsciiId<'N','O','I'>::V );
dmc.SaveState( state, AsciiId<'D','M','C'>::V, cpu, cycles.dmcClock );
dcBlocker.SaveState( state, AsciiId<'D','C','B'>::V );
{
const byte data[4] =
{
static_cast<byte>(cycles.rateCounter & 0xFFU),
static_cast<byte>(cycles.rateCounter >> 8),
static_cast<byte>(cycles.rateCounter >> 16),
static_cast<byte>(cycles.rateCounter >> 24),
};
state.Begin( AsciiId<'S','0','0'>::V ).Write( data ).End();
}
state.End();
}
void Apu::LoadState(State::Loader& state)
{
cycles.frameIrqClock = Cpu::CYCLE_MAX;
cycles.frameIrqRepeat = 0;
while (const dword chunk = state.Begin())
{
switch (chunk)
{
case AsciiId<'F','R','M'>::V:
{
State::Loader::Data<4> data( state );
ctrl = data[0] & STATUS_BITS;
cycles.rateCounter = cycles.fixed * cpu.GetCycles();
cycles.frameCounter = cycles.fixed *
(
cpu.GetCycles() + (data[1] | data[2] << 8) * cpu.GetClock()
);
cycles.frameDivider = data[3] & 0x3;
break;
}
case AsciiId<'I','R','Q'>::V:
{
State::Loader::Data<3> data( state );
cycles.frameIrqClock = cpu.GetCycles() +
(
(data[0] | data[1] << 8) * cpu.GetClock()
);
cycles.frameIrqRepeat = (data[2] & 0x3) % 3;
break;
}
case AsciiId<'E','X','T'>::V:
NST_VERIFY( cycles.extCounter != Cpu::CYCLE_MAX );
if (cycles.extCounter != Cpu::CYCLE_MAX)
{
cycles.extCounter = cycles.fixed *
(
cpu.GetCycles() + state.Read16() * cpu.GetClock()
);
}
break;
case AsciiId<'S','Q','0'>::V:
square[0].LoadState( state );
break;
case AsciiId<'S','Q','1'>::V:
square[1].LoadState( state );
break;
case AsciiId<'T','R','I'>::V:
triangle.LoadState( state );
break;
case AsciiId<'N','O','I'>::V:
noise.LoadState( state, cpu.GetModel() );
break;
case AsciiId<'D','M','C'>::V:
dmc.LoadState( state, cpu, cpu.GetModel(), cycles.dmcClock );
break;
case AsciiId<'D','C','B'>::V:
dcBlocker.LoadState( state );
break;
case AsciiId<'S','0','0'>::V:
{
State::Loader::Data<4> data( state );
cycles.rateCounter = data[0] | (data[1] << 8) | (data[2] << 16) | (data[3] << 24);
break;
}
}
state.End();
}
if (ctrl != STATUS_FRAME_IRQ_ENABLE)
{
cycles.frameIrqClock = Cpu::CYCLE_MAX;
cycles.frameIrqRepeat = 0;
}
else if (cycles.frameIrqClock == Cpu::CYCLE_MAX)
{
cycles.frameIrqClock = (cycles.frameCounter / cycles.fixed) + (3 - cycles.frameDivider) * (Cycles::frameClocks[cpu.GetModel()][0] / 4);
cycles.frameIrqRepeat = 0;
}
}
#ifdef NST_MSVC_OPTIMIZE
#pragma optimize("", on)
#endif
void NST_FASTCALL Apu::SyncOn(const Cycle target)
{
NST_ASSERT( (stream && settings.audible) && (cycles.rate && cycles.fixed) && (cycles.extCounter == Cpu::CYCLE_MAX) );
if (cycles.rateCounter < target)
{
Cycle rateCounter = cycles.rateCounter;
const Cycle rate = cycles.rate;
do
{
buffer << GetSample();
if (cycles.frameCounter <= rateCounter)
ClockFrameCounter();
rateCounter += rate;
}
while (rateCounter < target);
cycles.rateCounter = rateCounter;
}
if (cycles.frameCounter < target)
{
ClockFrameCounter();
NST_ASSERT( cycles.frameCounter >= target );
}
}
void NST_FASTCALL Apu::SyncOnExt(const Cycle target)
{
NST_ASSERT( (stream && settings.audible) && (cycles.rate && cycles.fixed) && extChannel );
Cycle extCounter = cycles.extCounter;
if (cycles.rateCounter < target)
{
Cycle rateCounter = cycles.rateCounter;
do
{
buffer << GetSample();
if (extCounter <= rateCounter)
extCounter = extChannel->Clock( extCounter, cycles.fixed, rateCounter );
if (cycles.frameCounter <= rateCounter)
ClockFrameCounter();
rateCounter += cycles.rate;
}
while (rateCounter < target);
cycles.rateCounter = rateCounter;
}
if (extCounter <= target)
{
cycles.extCounter = extChannel->Clock( extCounter, cycles.fixed, target );
NST_ASSERT( cycles.extCounter > target );
}
else
{
cycles.extCounter = extCounter;
}
if (cycles.frameCounter < target)
{
ClockFrameCounter();
NST_ASSERT( cycles.frameCounter >= target );
}
}
void NST_FASTCALL Apu::SyncOff(const Cycle target)
{
NST_ASSERT( !(stream && settings.audible) && cycles.fixed );
cycles.rateCounter = target;
while (cycles.frameCounter < target)
ClockFrameCounter();
NST_ASSERT( cycles.extCounter == Cpu::CYCLE_MAX || extChannel );
if (cycles.extCounter <= target)
{
cycles.extCounter = extChannel->Clock( cycles.extCounter, cycles.fixed, target );
NST_ASSERT( cycles.extCounter > target );
}
}
void Apu::BeginFrame(Sound::Output* output)
{
stream = output;
updater = (output && settings.audible ? (cycles.extCounter == Cpu::CYCLE_MAX ? &Apu::SyncOn : &Apu::SyncOnExt) : &Apu::SyncOff);
}
inline void Apu::Update(const Cycle target)
{
NST_ASSERT( cycles.fixed );
(*this.*updater)( target * cycles.fixed );
}
void Apu::Update()
{
Update( cpu.Update() );
}
void Apu::UpdateLatency()
{
Update( cpu.Update() + 1 );
}
bool Apu::UpdateDelta()
{
const Cycle elapsed = cpu.Update();
const bool delta = cycles.frameCounter != elapsed * cycles.fixed;
Update( elapsed + 1 );
return delta;
}
template<typename T,bool STEREO>
void Apu::FlushSound()
{
NST_ASSERT( (stream && settings.audible) && (cycles.rate && cycles.fixed) );
for (uint i=0; i < 2; ++i)
{
if (stream->length[i] && stream->samples[i])
{
Sound::Buffer::Block block( stream->length[i] );
buffer >> block;
Sound::Buffer::Renderer<T,STEREO> output( stream->samples[i], stream->length[i], buffer.history );
if (output << block)
{
const Cycle target = cpu.GetCycles() * cycles.fixed;
if (cycles.rateCounter < target)
{
Cycle rateCounter = cycles.rateCounter;
do
{
output << GetSample();
if (cycles.frameCounter <= rateCounter)
ClockFrameCounter();
if (cycles.extCounter <= rateCounter)
cycles.extCounter = extChannel->Clock( cycles.extCounter, cycles.fixed, rateCounter );
rateCounter += cycles.rate;
}
while (rateCounter < target && output);
cycles.rateCounter = rateCounter;
}
if (output)
{
if (cycles.frameCounter < target)
ClockFrameCounter();
if (cycles.extCounter <= target)
cycles.extCounter = extChannel->Clock( cycles.extCounter, cycles.fixed, target );
do
{
output << GetSample();
}
while (output);
}
}
}
}
}
void Apu::EndFrame()
{
NST_ASSERT( (stream && settings.audible) == (updater != &Apu::SyncOff) );
if (updater != &Apu::SyncOff)
{
dword streamed = 0;
if (Sound::Output::lockCallback( *stream ))
{
streamed = stream->length[0] + stream->length[1];
if (!settings.stereo)
FlushSound<iword,false>();
else
FlushSound<iword,true>();
Sound::Output::unlockCallback( *stream );
}
if (const dword rate = synchronizer.Clock( streamed, settings.rate, cpu ))
Resync( rate );
}
Update( cpu.GetCycles() );
Cycle frame = cpu.GetFrameCycles();
NST_ASSERT
(
cycles.dmcClock >= frame &&
cycles.frameIrqClock >= frame
);
cycles.dmcClock -= frame;
if (cycles.frameIrqClock != Cpu::CYCLE_MAX)
cycles.frameIrqClock -= frame;
frame *= cycles.fixed;
NST_ASSERT
(
cycles.rateCounter >= frame &&
cycles.frameCounter >= frame &&
cycles.extCounter >= frame
);
cycles.rateCounter -= frame;
cycles.frameCounter -= frame;
if (cycles.extCounter != Cpu::CYCLE_MAX)
cycles.extCounter -= frame;
}
#ifdef NST_MSVC_OPTIMIZE
#pragma optimize("s", on)
#endif
Apu::Settings::Settings()
: rate(44100), speed(0), muted(false), transpose(false), stereo(false), audible(true)
{
for (uint i=0; i < MAX_CHANNELS; ++i)
volumes[i] = Channel::DEFAULT_VOLUME;
}
Apu::Cycles::Cycles()
: fixed(1), rate(1) {}
void Apu::Cycles::Reset(const bool extChannel,const CpuModel model)
{
rateCounter = 0;
frameDivider = 0;
frameIrqClock = Cpu::CYCLE_MAX;
frameIrqRepeat = 0;
dmcClock = Dmc::GetResetFrequency( model );
frameCounter = frameClocks[model][0] * fixed;
extCounter = (extChannel ? 0UL : Cpu::CYCLE_MAX);
}
void Apu::Cycles::Update(dword sampleRate,const uint speed,const Cpu& cpu)
{
frameCounter /= fixed;
rateCounter /= fixed;
if (extCounter != Cpu::CYCLE_MAX)
extCounter /= fixed;
if (speed)
sampleRate = sampleRate * cpu.GetFps() / speed;
uint multiplier = 0;
const qaword clockBase = cpu.GetClockBase();
while (++multiplier < 512 && clockBase * multiplier % sampleRate);
rate = clockBase * multiplier / sampleRate;
fixed = cpu.GetClockDivider() * multiplier;
frameCounter *= fixed;
rateCounter *= fixed;
if (extCounter != Cpu::CYCLE_MAX)
extCounter *= fixed;
}
Apu::Synchronizer::Synchronizer()
: rate(0) {}
void Apu::Synchronizer::Resync(uint speed,const Cpu& cpu)
{
duty = 0;
streamed = 0;
if (speed == 0 || speed == cpu.GetFps())
sync = 4;
else
sync = 0;
}
void Apu::Synchronizer::Reset(uint speed,dword sampleRate,const Cpu& cpu)
{
rate = sampleRate;
Resync( speed, cpu );
}
#ifdef NST_MSVC_OPTIMIZE
#pragma optimize("", on)
#endif
NST_SINGLE_CALL dword Apu::Synchronizer::Clock(const dword output,const dword sampleRate,const Cpu& cpu)
{
/*if (sync)
{
if (duty >= 60*4)
streamed += output;
if (duty < 60*12)
{
duty++;
}
else
{
duty = 60*4;
dword actualRate = streamed / (60*8) * cpu.GetFps();
const dword limit = sampleRate / 21;
if (actualRate <= sampleRate-limit)
{
actualRate = sampleRate-limit;
sync--;
}
else if (actualRate >= sampleRate+limit)
{
actualRate = sampleRate+limit;
sync--;
}
else
{
sync = (sync > 2 ? sync - 2 : 0);
}
actualRate = actualRate * 9999 / 10000;
streamed = 0;
if (rate != actualRate)
{
rate = actualRate;
return actualRate;
}
}
}*/
return 0;
}
#ifdef NST_MSVC_OPTIMIZE
#pragma optimize("s", on)
#endif
Apu::Channel::LengthCounter::LengthCounter()
{
Reset();
}
void Apu::Channel::LengthCounter::Reset()
{
enabled = 0;
count = 0;
}
void Apu::Channel::LengthCounter::LoadState(State::Loader& state)
{
const uint data = state.Read8();
enabled = (data == 0xFF ? 0U : ~0U);
count = data & enabled;
}
void Apu::Channel::LengthCounter::SaveState(State::Saver& state,const dword chunk) const
{
NST_VERIFY( count < 0xFF );
state.Begin( chunk ).Write8( enabled ? count : 0xFF ).End();
}
Apu::Channel::Envelope::Envelope()
: outputVolume(OUTPUT_MUL)
{
Reset();
}
void Apu::Channel::Envelope::Reset()
{
output = 0;
regs[0] = 0x0;
regs[1] = 0x10;
count = 0;
reset = false;
}
void Apu::Channel::Envelope::SetOutputVolume(uint v)
{
outputVolume = v;
UpdateOutput();
}
void Apu::Channel::Envelope::SaveState(State::Saver& state,const dword chunk) const
{
const byte data[3] =
{
count,
static_cast<byte>(regs[0] | (reset ? 0x80U : 0x00U)),
regs[1]
};
state.Begin( chunk ).Write( data ).End();
}
void Apu::Channel::Envelope::LoadState(State::Loader& state)
{
State::Loader::Data<3> data( state );
count = data[0] & 0x0F;
reset = data[1] >> 7;
regs[0] = data[1] & 0x0F;
regs[1] = data[2];
UpdateOutput();
}
#ifdef NST_MSVC_OPTIMIZE
#pragma optimize("", on)
#endif
void Apu::Channel::Envelope::UpdateOutput()
{
output = (regs[regs[1] >> 4 & 1U] & 0xFUL) * outputVolume;
}
void Apu::Channel::Envelope::Clock()
{
if (!reset)
{
if (count)
{
count--;
return;
}
if (regs[0] | (regs[1] & 0x20U))
regs[0] = (regs[0] - 1U) & 0xF;
}
else
{
reset = false;
regs[0] = 0xF;
}
count = regs[1] & 0x0FU;
UpdateOutput();
}
void Apu::Channel::Envelope::Write(const uint data)
{
regs[1] = data;
UpdateOutput();
}
#ifdef NST_MSVC_OPTIMIZE
#pragma optimize("s", on)
#endif
Apu::Channel::DcBlocker::DcBlocker()
{
Reset();
}
void Apu::Channel::DcBlocker::Reset()
{
acc = 0;
prev = 0;
next = 0;
}
#ifdef NST_MSVC_OPTIMIZE
#pragma optimize("", on)
#endif
Apu::Channel::Sample Apu::Channel::DcBlocker::Apply(Sample sample)
{
acc -= prev;
prev = signed_shl(sample,15);
acc += prev - next * POLE;
next = signed_shr(acc,15);
return next;
}
void Apu::Channel::DcBlocker::SaveState(State::Saver& state,const dword chunk) const
{
state.Begin( chunk );
{
const byte data[12] =
{
static_cast<byte>(acc & 0xFFU),
static_cast<byte>(acc >> 8),
static_cast<byte>(acc >> 16),
static_cast<byte>(acc >> 24),
static_cast<byte>(prev & 0xFFU),
static_cast<byte>(prev >> 8),
static_cast<byte>(prev >> 16),
static_cast<byte>(prev >> 24),
static_cast<byte>(next & 0xFFU),
static_cast<byte>(next >> 8),
static_cast<byte>(next >> 16),
static_cast<byte>(next >> 24),
};
state.Begin( AsciiId<'S','0','0'>::V ).Write( data ).End();
}
state.End();
}
void Apu::Channel::DcBlocker::LoadState(State::Loader& state)
{
while (const dword chunk = state.Begin())
{
switch (chunk)
{
case AsciiId<'S','0','0'>::V:
{
State::Loader::Data<12> data( state );
acc = data[0] | (data[1] << 8) | (data[2] << 16) | (data[3] << 24);
prev = data[4] | (data[5] << 8) | (data[6] << 16) | (data[7] << 24);
next = data[8] | (data[9] << 8) | (data[10] << 16) | (data[11] << 24);
break;
}
}
state.End();
}
}
#ifdef NST_MSVC_OPTIMIZE
#pragma optimize("s", on)
#endif
Apu::Channel::Channel(Apu& a)
: apu(a) {}
Apu::Channel::~Channel()
{
if (apu.extChannel == this)
{
apu.extChannel = NULL;
apu.UpdateVolumes();
}
}
void Apu::Channel::Connect(bool audible)
{
NST_ASSERT( apu.extChannel == NULL );
if (audible)
apu.settings.audible = true;
else
apu.UpdateVolumes();
apu.extChannel = this;
}
void Apu::Channel::GetOscillatorClock(Cycle& rate,uint& fixed) const
{
apu.CalculateOscillatorClock( rate, fixed );
}
uint Apu::Channel::GetVolume(uint channel) const
{
NST_ASSERT( channel < MAX_CHANNELS );
return apu.settings.volumes[channel];
}
#ifdef NST_MSVC_OPTIMIZE
#pragma optimize("", on)
#endif
Cycle Apu::Channel::GetCpuClockBase() const
{
return apu.cpu.GetClockBase();
}
uint Apu::Channel::GetCpuClockDivider() const
{
return apu.cpu.GetClockDivider();
}
Cycle Apu::Channel::GetCpuClock(uint clock) const
{
return apu.cpu.GetClock(clock);
}
dword Apu::Channel::GetSampleRate() const
{
return apu.settings.rate;
}
bool Apu::Channel::IsMuted() const
{
return apu.settings.muted;
}
void Apu::Channel::Update() const
{
apu.Update();
}
Cycle Apu::Channel::Clock(Cycle,Cycle,Cycle)
{
return Cpu::CYCLE_MAX;
}
#ifdef NST_MSVC_OPTIMIZE
#pragma optimize("s", on)
#endif
Apu::Oscillator::Oscillator()
: rate(1), fixed(1) {}
void Apu::Oscillator::Reset()
{
active = false;
timer = RESET_CYCLES * fixed;
frequency = fixed;
amp = 0;
}
inline void Apu::Oscillator::ClearAmp()
{
amp = 0;
}
void Apu::Oscillator::UpdateSettings(dword r,uint f)
{
NST_ASSERT( r && f );
frequency = frequency / fixed * f;
timer = timer / fixed * f;
fixed = f;
rate = r;
}
void Apu::Square::Reset()
{
Oscillator::Reset();
frequency = fixed * 2;
step = 0;
duty = 0;
envelope.Reset();
lengthCounter.Reset();
validFrequency = false;
sweepRate = 0;
sweepCount = 1;
sweepReload = false;
sweepIncrease = ~0U;
sweepShift = 0;
waveLength = 0;
}
#ifdef NST_MSVC_OPTIMIZE
#pragma optimize("", on)
#endif
inline bool Apu::Square::CanOutput() const
{
return lengthCounter.GetCount() && envelope.Volume() && validFrequency;
}
#ifdef NST_MSVC_OPTIMIZE
#pragma optimize("s", on)
#endif
void Apu::Square::UpdateSettings(uint v,dword r,uint f)
{
Oscillator::UpdateSettings( r, f );
envelope.SetOutputVolume( (v * Channel::OUTPUT_MUL + Channel::DEFAULT_VOLUME/2) / Channel::DEFAULT_VOLUME );
active = CanOutput();
}
void Apu::Square::SaveState(State::Saver& state,const dword chunk) const
{
state.Begin( chunk );
{
byte data[4];
data[0] = waveLength & 0xFFU;
data[1] = (waveLength >> 8) | (duty ? duty << (2+3) : 2U << 3); // for version compatibility
data[2] = (sweepCount - 1U) << 4;
if (sweepRate)
data[2] |= 0x08U | (sweepRate - 1);
if (sweepReload)
data[2] |= 0x80U;
data[3] = sweepShift;
if (!sweepIncrease)
data[3] |= 0x08U;
state.Begin( AsciiId<'R','E','G'>::V ).Write( data ).End();
}
lengthCounter.SaveState( state, AsciiId<'L','E','N'>::V );
envelope.SaveState( state, AsciiId<'E','N','V'>::V );
{
const byte data[13] =
{
static_cast<byte>(step),
static_cast<byte>(timer & 0xFFU),
static_cast<byte>(timer >> 8),
static_cast<byte>(timer >> 16),
static_cast<byte>(timer >> 24),
static_cast<byte>(frequency & 0xFFU),
static_cast<byte>(frequency >> 8),
static_cast<byte>(frequency >> 16),
static_cast<byte>(frequency >> 24),
static_cast<byte>(amp & 0xFFU),
static_cast<byte>(amp >> 8),
static_cast<byte>(amp >> 16),
static_cast<byte>(amp >> 24),
};
state.Begin( AsciiId<'S','0','0'>::V ).Write( data ).End();
}
state.End();
}
void Apu::Square::LoadState(State::Loader& state)
{
while (const dword chunk = state.Begin())
{
switch (chunk)
{
case AsciiId<'R','E','G'>::V:
{
State::Loader::Data<4> data( state );
waveLength = data[0] | (data[1] << 8 & 0x0700);
// for version compatibility
switch (data[1] >> 3 & 0xF)
{
case 4: duty = 1; break;
case 8: duty = 2; break;
case 12: duty = 3; break;
default: duty = 0; break;
}
if (data[2] & 0x08)
sweepRate = (data[2] & 0x07) + 1;
else
sweepRate = 0;
sweepCount = (data[2] >> 4 & 0x07) + 1;
sweepReload = data[2] >> 7;
sweepShift = data[3] & 0x07;
sweepIncrease = (data[3] & 0x08) ? 0U : ~0U;
step = 0;
timer = 0;
break;
}
case AsciiId<'L','E','N'>::V:
lengthCounter.LoadState( state );
break;
case AsciiId<'E','N','V'>::V:
envelope.LoadState( state );
UpdateFrequency();
break;
case AsciiId<'S','0','0'>::V:
{
State::Loader::Data<13> data( state );
step = data[0];
timer = data[1] | (data[2] << 8) | (data[3] << 16) | (data[4] << 24);
frequency = data[5] | (data[6] << 8) | (data[7] << 16) | (data[8] << 24);
amp = data[9] | (data[10] << 8) | (data[11] << 16) | (data[12] << 24);
break;
}
}
state.End();
}
}
#ifdef NST_MSVC_OPTIMIZE
#pragma optimize("", on)
#endif
NST_SINGLE_CALL void Apu::Square::Disable(const bool disable)
{
active &= lengthCounter.Disable( disable );
}
void Apu::Square::UpdateFrequency()
{
if (waveLength >= MIN_FRQ && waveLength + (sweepIncrease & waveLength >> sweepShift) <= MAX_FRQ)
{
frequency = (waveLength + 1UL) * 2 * fixed;
validFrequency = true;
active = lengthCounter.GetCount() && envelope.Volume();
}
else
{
validFrequency = false;
active = false;
}
}
NST_SINGLE_CALL void Apu::Square::WriteReg0(const uint data)
{
envelope.Write( data );
duty = data >> REG0_DUTY_SHIFT;
active = CanOutput();
}
NST_SINGLE_CALL void Apu::Square::WriteReg1(const uint data)
{
sweepIncrease = (data & REG1_SWEEP_DECREASE) ? 0U : ~0U;
sweepShift = data & REG1_SWEEP_SHIFT;
sweepRate = 0;
if ((data & (REG1_SWEEP_ENABLED|REG1_SWEEP_SHIFT)) > REG1_SWEEP_ENABLED)
{
sweepRate = ((data & REG1_SWEEP_RATE) >> REG1_SWEEP_RATE_SHIFT) + 1;
sweepReload = true;
}
UpdateFrequency();
}
NST_SINGLE_CALL void Apu::Square::WriteReg2(const uint data)
{
waveLength = (waveLength & uint(REG3_WAVELENGTH_HIGH)) | (data & REG3_WAVELENGTH_LOW);
UpdateFrequency();
}
NST_SINGLE_CALL void Apu::Square::WriteReg3(const uint data,const Cycle frameCounterDelta)
{
step = 0;
envelope.ResetClock();
lengthCounter.Write( data, frameCounterDelta );
waveLength = (data << 8 & REG3_WAVELENGTH_HIGH) | (waveLength & uint(REG3_WAVELENGTH_LOW));
UpdateFrequency();
}
NST_SINGLE_CALL void Apu::Square::ClockEnvelope()
{
envelope.Clock();
active = CanOutput();
}
NST_SINGLE_CALL void Apu::Square::ClockSweep(const uint complement)
{
if (!envelope.Looping() && lengthCounter.Clock())
active = false;
if (sweepRate && !--sweepCount)
{
sweepCount = sweepRate;
if (waveLength >= MIN_FRQ)
{
const uint shifted = waveLength >> sweepShift;
if (!sweepIncrease)
{
waveLength += complement - shifted;
UpdateFrequency();
}
else if (waveLength + shifted <= MAX_FRQ)
{
waveLength += shifted;
UpdateFrequency();
}
}
}
if (sweepReload)
{
sweepReload = false;
sweepCount = sweepRate;
}
}
inline uint Apu::Square::GetLengthCounter() const
{
return lengthCounter.GetCount();
}
dword Apu::Square::GetSample()
{
NST_VERIFY( bool(active) == CanOutput() && timer >= 0 );
dword sum = timer;
timer -= idword(rate);
if (active)
{
static const byte forms[4][8] =
{
{0x1F,0x00,0x1F,0x1F,0x1F,0x1F,0x1F,0x1F},
{0x1F,0x00,0x00,0x1F,0x1F,0x1F,0x1F,0x1F},
{0x1F,0x00,0x00,0x00,0x00,0x1F,0x1F,0x1F},
{0x00,0x1F,0x1F,0x00,0x00,0x00,0x00,0x00}
};
const byte* const NST_RESTRICT form = forms[duty];
if (timer >= 0)
{
amp = envelope.Volume() >> form[step];
}
else
{
sum >>= form[step];
do
{
sum += NST_MIN(-timer,frequency) >> form[step = (step + 1) & 0x7];
timer += idword(frequency);
}
while (timer < 0);
NST_VERIFY( !envelope.Volume() || sum <= 0xFFFFFFFF / envelope.Volume() + rate/2 );
amp = (sum * envelope.Volume() + rate/2) / rate;
}
}
else
{
if (timer < 0)
{
const uint count = (-timer + frequency - 1) / frequency;
step = (step + count) & 0x7;
timer += idword(count * frequency);
}
if (amp < Channel::OUTPUT_DECAY)
{
return 0;
}
else
{
amp -= Channel::OUTPUT_DECAY;
}
}
return amp;
}
#ifdef NST_MSVC_OPTIMIZE
#pragma optimize("s", on)
#endif
Apu::Triangle::Triangle()
: outputVolume(0) {}
void Apu::Triangle::Reset()
{
Oscillator::Reset();
step = 0x7;
status = STATUS_COUNTING;
waveLength = 0;
//linearCtrl = 0;
linearCounter = 0;
lengthCounter.Reset();
}
#ifdef NST_MSVC_OPTIMIZE
#pragma optimize("", on)
#endif
inline bool Apu::Triangle::CanOutput() const
{
return lengthCounter.GetCount() && linearCounter && waveLength >= MIN_FRQ && outputVolume;
}
#ifdef NST_MSVC_OPTIMIZE
#pragma optimize("s", on)
#endif
void Apu::Triangle::UpdateSettings(uint v,dword r,uint f)
{
Oscillator::UpdateSettings( r, f );
outputVolume = (v * Channel::OUTPUT_MUL + Channel::DEFAULT_VOLUME/2) / Channel::DEFAULT_VOLUME;
active = CanOutput();
}
void Apu::Triangle::SaveState(State::Saver& state,const dword chunk) const
{
state.Begin( chunk );
{
const byte data[4] =
{
static_cast<byte>(waveLength & 0xFFU),
static_cast<byte>(waveLength >> 8),
static_cast<byte>(linearCounter | (uint(status) << 7)),
linearCtrl
};
state.Begin( AsciiId<'R','E','G'>::V ).Write( data ).End();
}
lengthCounter.SaveState( state, AsciiId<'L','E','N'>::V );
{
const byte data[9] =
{
static_cast<byte>(step),
static_cast<byte>(timer & 0xFFU),
static_cast<byte>(timer >> 8),
static_cast<byte>(timer >> 16),
static_cast<byte>(timer >> 24),
static_cast<byte>(amp & 0xFFU),
static_cast<byte>(amp >> 8),
static_cast<byte>(amp >> 16),
static_cast<byte>(amp >> 24),
};
state.Begin( AsciiId<'S','0','0'>::V ).Write( data ).End();
}
state.End();
}
void Apu::Triangle::LoadState(State::Loader& state)
{
while (const dword chunk = state.Begin())
{
switch (chunk)
{
case AsciiId<'R','E','G'>::V:
{
State::Loader::Data<4> data( state );
waveLength = data[0] | (data[1] << 8 & 0x0700);
linearCounter = data[2] & 0x7F;
status = static_cast<Status>(data[2] >> 7);
linearCtrl = data[3];
frequency = (waveLength + 1UL) * fixed;
timer = 0;
step = 0;
break;
}
case AsciiId<'L','E','N'>::V:
lengthCounter.LoadState( state );
break;
case AsciiId<'S','0','0'>::V:
{
State::Loader::Data<9> data( state );
step = data[0];
timer = data[1] | (data[2] << 8) | (data[3] << 16) | (data[4] << 24);
amp = data[5] | (data[6] << 8) | (data[7] << 16) | (data[8] << 24);
break;
}
}
state.End();
}
active = CanOutput();
}
#ifdef NST_MSVC_OPTIMIZE
#pragma optimize("", on)
#endif
NST_SINGLE_CALL void Apu::Triangle::Disable(const bool disable)
{
active &= lengthCounter.Disable( disable );
}
NST_SINGLE_CALL void Apu::Triangle::WriteReg0(const uint data)
{
linearCtrl = data;
}
NST_SINGLE_CALL void Apu::Triangle::WriteReg2(const uint data)
{
waveLength = (waveLength & uint(REG3_WAVE_LENGTH_HIGH)) | (data & REG2_WAVE_LENGTH_LOW);
frequency = (waveLength + 1UL) * fixed;
active = CanOutput();
}
NST_SINGLE_CALL void Apu::Triangle::WriteReg3(const uint data,const Cycle frameCounterDelta)
{
waveLength = (data << 8 & REG3_WAVE_LENGTH_HIGH) | (waveLength & uint(REG2_WAVE_LENGTH_LOW));
frequency = (waveLength + 1UL) * fixed;
status = STATUS_RELOAD;
lengthCounter.Write( data, frameCounterDelta );
active = CanOutput();
}
NST_SINGLE_CALL void Apu::Triangle::ClockLinearCounter()
{
if (status == STATUS_COUNTING)
{
if (linearCounter && !--linearCounter)
active = false;
}
else
{
if (!(linearCtrl & uint(REG0_LINEAR_COUNTER_START)))
status = STATUS_COUNTING;
linearCounter = linearCtrl & uint(REG0_LINEAR_COUNTER_LOAD);
active = CanOutput();
}
}
NST_SINGLE_CALL void Apu::Triangle::ClockLengthCounter()
{
if (!(linearCtrl & uint(REG0_LINEAR_COUNTER_START)) && lengthCounter.Clock())
active = false;
}
NST_SINGLE_CALL dword Apu::Triangle::GetSample()
{
NST_VERIFY( bool(active) == CanOutput() && timer >= 0 );
if (active)
{
static const byte pyramid[32] =
{
0x0,0x1,0x2,0x3,0x4,0x5,0x6,0x7,
0x8,0x9,0xA,0xB,0xC,0xD,0xE,0xF,
0xF,0xE,0xD,0xC,0xB,0xA,0x9,0x8,
0x7,0x6,0x5,0x4,0x3,0x2,0x1,0x0
};
dword sum = timer;
timer -= idword(rate);
if (timer >= 0)
{
amp = pyramid[step] * outputVolume * 3;
}
else
{
sum *= pyramid[step];
do
{
sum += NST_MIN(-timer,frequency) * pyramid[step = (step + 1) & 0x1F];
timer += idword(frequency);
}
while (timer < 0);
NST_VERIFY( !outputVolume || sum <= 0xFFFFFFFF / outputVolume + rate/2 );
amp = (sum * outputVolume + rate/2) / rate * 3;
}
}
/*else if (amp < Channel::OUTPUT_DECAY)
{
return 0;
}
else
{
amp -= Channel::OUTPUT_DECAY;
step &= STEP_CHECK;
}*/
return amp;
}
inline uint Apu::Triangle::GetLengthCounter() const
{
return lengthCounter.GetCount();
}
#ifdef NST_MSVC_OPTIMIZE
#pragma optimize("s", on)
#endif
void Apu::Noise::Reset(const CpuModel model)
{
Oscillator::Reset();
frequency = lut[model][0] * dword(fixed);
bits = 1;
shifter = 13;
envelope.Reset();
lengthCounter.Reset();
}
uint Apu::Noise::GetFrequencyIndex() const
{
for (uint v=frequency/fixed, i=0; i < 16; ++i)
{
if (v == lut[0][i] || v == lut[1][i])
return i;
}
return 0;
}
#ifdef NST_MSVC_OPTIMIZE
#pragma optimize("", on)
#endif
inline bool Apu::Noise::CanOutput() const
{
return lengthCounter.GetCount() && envelope.Volume();
}
#ifdef NST_MSVC_OPTIMIZE
#pragma optimize("s", on)
#endif
void Apu::Noise::UpdateSettings(uint v,dword r,uint f)
{
Oscillator::UpdateSettings( r, f );
envelope.SetOutputVolume( (v * Channel::OUTPUT_MUL + Channel::DEFAULT_VOLUME/2) / Channel::DEFAULT_VOLUME );
active = CanOutput();
}
void Apu::Noise::SaveState(State::Saver& state,const dword chunk) const
{
state.Begin( chunk );
state.Begin( AsciiId<'R','E','G'>::V ).Write8( (shifter == 8 ? 0x10 : 0x00) | GetFrequencyIndex() ).End();
lengthCounter.SaveState( state, AsciiId<'L','E','N'>::V );
envelope.SaveState( state, AsciiId<'E','N','V'>::V );
{
const byte data[6] =
{
static_cast<byte>(bits & 0xFFU),
static_cast<byte>(bits >> 8),
static_cast<byte>(timer & 0xFFU),
static_cast<byte>(timer >> 8),
static_cast<byte>(timer >> 16),
static_cast<byte>(timer >> 24)
};
state.Begin( AsciiId<'S','0','0'>::V ).Write( data ).End();
}
state.End();
}
void Apu::Noise::LoadState(State::Loader& state,const CpuModel model)
{
while (const dword chunk = state.Begin())
{
switch (chunk)
{
case AsciiId<'R','E','G'>::V:
{
const uint data = state.Read8();
frequency = lut[model][data & 0x0F] * dword(fixed);
shifter = (data & 0x10) ? 8 : 13;
timer = 0;
bits = 1;
break;
}
case AsciiId<'L','E','N'>::V:
lengthCounter.LoadState( state );
break;
case AsciiId<'E','N','V'>::V:
envelope.LoadState( state );
break;
case AsciiId<'S','0','0'>::V:
{
State::Loader::Data<6> data( state );
bits = data[0] | (data[1] << 8);
timer = data[2] | (data[3] << 8) | (data[4] << 16) | (data[5] << 24);
break;
}
}
state.End();
}
active = CanOutput();
}
#ifdef NST_MSVC_OPTIMIZE
#pragma optimize("", on)
#endif
NST_SINGLE_CALL void Apu::Noise::Disable(const bool disable)
{
active &= lengthCounter.Disable( disable );
}
NST_SINGLE_CALL void Apu::Noise::WriteReg0(const uint data)
{
envelope.Write( data );
active = CanOutput();
}
NST_SINGLE_CALL void Apu::Noise::WriteReg2(const uint data,const CpuModel model)
{
frequency = lut[model][data & REG2_FREQUENCY] * dword(fixed);
shifter = (data & REG2_93BIT_MODE) ? 8 : 13;
}
NST_SINGLE_CALL void Apu::Noise::WriteReg3(const uint data,const Cycle frameCounterDelta)
{
envelope.ResetClock();
lengthCounter.Write( data, frameCounterDelta );
active = CanOutput();
}
NST_SINGLE_CALL void Apu::Noise::ClockEnvelope()
{
envelope.Clock();
active = CanOutput();
}
NST_SINGLE_CALL void Apu::Noise::ClockLengthCounter()
{
if (!envelope.Looping() && lengthCounter.Clock())
active = false;
}
NST_SINGLE_CALL dword Apu::Noise::GetSample()
{
NST_VERIFY( bool(active) == CanOutput() && timer >= 0 );
dword sum = timer;
timer -= idword(rate);
if (active)
{
if (timer >= 0)
{
if (!(bits & 0x4000))
return envelope.Volume() * 2;
}
else
{
if (bits & 0x4000)
sum = 0;
do
{
bits = (bits << 1) | ((bits >> 14 ^ bits >> shifter) & 0x1);
if (!(bits & 0x4000))
sum += NST_MIN(-timer,frequency);
timer += idword(frequency);
}
while (timer < 0);
NST_VERIFY( !envelope.Volume() || sum <= 0xFFFFFFFF / envelope.Volume() + rate/2 );
return (sum * envelope.Volume() + rate/2) / rate * 2;
}
}
else while (timer < 0)
{
bits = (bits << 1) | ((bits >> 14 ^ bits >> shifter) & 0x1);
timer += idword(frequency);
}
return 0;
}
inline uint Apu::Noise::GetLengthCounter() const
{
return lengthCounter.GetCount();
}
#ifdef NST_MSVC_OPTIMIZE
#pragma optimize("s", on)
#endif
Apu::Dmc::Dmc()
: outputVolume(0)
{
frequency = GetResetFrequency( CPU_RP2A03 );
}
void Apu::Dmc::Reset(const CpuModel model)
{
curSample = 0;
linSample = 0;
frequency = GetResetFrequency( model );
regs.ctrl = 0;
regs.lengthCounter = 1;
regs.address = 0xC000;
out.active = false;
out.shifter = 0;
out.dac = 0;
out.buffer = 0x00;
dma.lengthCounter = 0;
dma.buffered = false;
dma.address = 0xC000;
dma.buffer = 0x00;
}
Cycle Apu::Dmc::GetResetFrequency(CpuModel model)
{
return lut[model][0];
}
void Apu::Dmc::UpdateSettings(uint v)
{
v = (v * Channel::OUTPUT_MUL + Channel::DEFAULT_VOLUME/2) / Channel::DEFAULT_VOLUME;
if (outputVolume)
linSample /= outputVolume;
if (outputVolume)
curSample /= outputVolume;
linSample *= v;
curSample *= v;
outputVolume = v;
if (!v)
out.active = false;
}
inline void Apu::Dmc::ClearAmp()
{
curSample = 0;
linSample = 0;
}
void Apu::Dmc::SaveState(State::Saver& state,const dword chunk,const Cpu& cpu,const Cycle dmcMcClock) const
{
NST_VERIFY( dmcMcClock >= cpu.GetCycles() );
dword dmcClock = dmcMcClock;
if (dmcClock > cpu.GetCycles())
dmcClock = (dmcClock - cpu.GetCycles()) / cpu.GetClock();
else
dmcClock = 0;
NST_VERIFY( dmcClock <= 0x1FFF && dmcMcClock == cpu.GetCycles() + dmcClock * cpu.GetClock() );
state.Begin( chunk );
{
const byte data[12] =
{
static_cast<byte>(dmcClock & 0xFF),
static_cast<byte>(dmcClock >> 8),
static_cast<byte>((
( ( regs.ctrl & REG0_FREQUENCY ) ) |
( ( regs.ctrl & REG0_LOOP ) ? 0x10U : 0U ) |
( ( regs.ctrl & REG0_IRQ_ENABLE ) ? 0x20U : 0U ) |
( ( dma.lengthCounter ) ? 0x40U : 0U )
)),
static_cast<byte>((regs.address - 0xC000U) >> 6),
static_cast<byte>((regs.lengthCounter - 1U) >> 4),
static_cast<byte>((dma.address >> 0 & 0xFFU)),
static_cast<byte>((dma.address >> 8 & 0x7FU) | (dma.buffered ? 0x80 : 0x00)),
static_cast<byte>(dma.lengthCounter ? (dma.lengthCounter - 1U) >> 4 : 0),
static_cast<byte>(dma.buffer),
static_cast<byte>(7 - out.shifter),
out.buffer,
out.dac,
};
state.Begin( AsciiId<'R','E','G'>::V ).Write( data ).End();
}
{
const byte data[4] =
{
static_cast<byte>(linSample & 0xFFU),
static_cast<byte>(linSample >> 8),
static_cast<byte>(dma.lengthCounter & 0xFFU),
static_cast<byte>(dma.lengthCounter >> 8),
};
state.Begin( AsciiId<'S','0','0'>::V ).Write( data ).End();
}
state.End();
}
void Apu::Dmc::LoadState(State::Loader& state,const Cpu& cpu,const CpuModel model,Cycle& dmcClock)
{
while (const dword chunk = state.Begin())
{
switch (chunk)
{
case AsciiId<'R','E','G'>::V:
{
State::Loader::Data<12> data( state );
dmcClock = cpu.GetCycles() + ((data[0] | data[1] << 8) * cpu.GetClock());
regs.ctrl =
(
( ( data[2] & 0x10 ) ? REG0_LOOP : 0U ) |
( ( data[2] & 0x20 ) ? REG0_IRQ_ENABLE : 0U ) |
( ( data[2] & REG0_FREQUENCY ) )
);
frequency = lut[model][regs.ctrl & REG0_FREQUENCY];
regs.address = 0xC000 | (data[3] << 6);
regs.lengthCounter = (data[4] << 4) + 1;
dma.address = 0x8000 | data[5] | (data[6] << 8 & 0x7F00);
dma.buffered = data[6] >> 7;
dma.lengthCounter = (data[2] & 0x40) ? (data[7] << 4) + 1 : 0;
dma.buffer = data[8];
out.shifter = 7 - (data[9] & 0x7);
out.buffer = data[10];
out.dac = data[11] & 0x7F;
curSample = out.dac * outputVolume;
linSample = curSample;
out.active = dma.buffered && outputVolume;
break;
}
case AsciiId<'S','0','0'>::V:
{
State::Loader::Data<4> data( state );
linSample = data[0] | (data[1] << 8);
dma.lengthCounter = data[2] | (data[3] << 8);
break;
}
}
state.End();
}
}
#ifdef NST_MSVC_OPTIMIZE
#pragma optimize("", on)
#endif
NST_SINGLE_CALL void Apu::Dmc::Disable(const bool disable,Cpu& cpu)
{
cpu.ClearIRQ( Cpu::IRQ_DMC );
if (disable)
{
dma.lengthCounter = 0;
}
else if (!dma.lengthCounter)
{
dma.lengthCounter = regs.lengthCounter;
dma.address = regs.address;
if (!dma.buffered)
DoDMA( cpu, cpu.GetCycles() );
}
}
NST_SINGLE_CALL dword Apu::Dmc::GetSample()
{
if (curSample != linSample)
{
const uint step = outputVolume * INP_STEP;
if (curSample + step - linSample <= step*2)
{
linSample = curSample;
}
else if (curSample > linSample)
{
linSample += step;
}
else
{
linSample -= step;
}
}
return linSample;
}
void Apu::Dmc::DoDMA(Cpu& cpu,const Cycle clock,const uint readAddress)
{
NST_VERIFY( !dma.buffered && (!readAddress || !cpu.IsWriteCycle(clock)) );
if (!readAddress)
{
cpu.StealCycles( cpu.GetClock(cpu.IsWriteCycle(clock) ? 2 : 3) );
}
else if (cpu.GetCycles() != clock)
{
cpu.StealCycles( cpu.GetClock(3) );
}
else
{
NST_DEBUG_MSG("DMA/Read conflict!");
cpu.StealCycles( cpu.GetClock(1) );
/* According to dmc_dma_during_read4/dma_2007_read, DMC DMA during read causes
* 2-3 extra $2007 reads before the real read. The nesdev wiki states that this
* also happens when polling $2002 for vblank.
*/
if ((readAddress & 0xF000) != 0x4000)
{
cpu.Peek( readAddress );
cpu.Peek( readAddress );
}
cpu.StealCycles( cpu.GetClock(1) );
cpu.Peek( readAddress );
cpu.StealCycles( cpu.GetClock(1) );
}
dma.buffer = cpu.Peek( dma.address );
cpu.StealCycles( cpu.GetClock() );
dma.address = 0x8000 | ((dma.address + 1U) & 0x7FFF);
dma.buffered = true;
NST_VERIFY( dma.lengthCounter );
if (!--dma.lengthCounter)
{
if (regs.ctrl & REG0_LOOP)
{
dma.address = regs.address;
dma.lengthCounter = regs.lengthCounter;
}
else if (regs.ctrl & REG0_IRQ_ENABLE)
{
cpu.DoIRQ( Cpu::IRQ_DMC );
}
}
}
NST_SINGLE_CALL bool Apu::Dmc::WriteReg0(const uint data,const CpuModel model)
{
regs.ctrl = data;
frequency = lut[model][data & REG0_FREQUENCY];
return data & REG0_IRQ_ENABLE;
}
NST_SINGLE_CALL void Apu::Dmc::WriteReg1(const uint data)
{
out.dac = data & 0x7F;
curSample = out.dac * outputVolume;
}
NST_SINGLE_CALL void Apu::Dmc::WriteReg2(const uint data)
{
regs.address = 0xC000 | (data << 6);
}
NST_SINGLE_CALL void Apu::Dmc::WriteReg3(const uint data)
{
regs.lengthCounter = (data << 4) + 1;
}
NST_SINGLE_CALL bool Apu::Dmc::ClockDAC()
{
if (out.active)
{
const uint next = out.dac + ((out.buffer & 0x1U) << 2) - 2;
out.buffer >>= 1;
if (next <= 0x7F && next != out.dac)
{
out.dac = next;
return true;
}
}
return false;
}
NST_SINGLE_CALL void Apu::Dmc::Update()
{
curSample = out.dac * outputVolume;
}
NST_SINGLE_CALL void Apu::Dmc::ClockDMA(Cpu& cpu,Cycle& clock,const uint readAddress)
{
const Cycle tmp = clock;
clock += frequency;
if (out.shifter)
{
out.shifter--;
}
else
{
out.shifter = 7;
out.active = dma.buffered;
if (out.active)
{
out.active = outputVolume;
dma.buffered = false;
out.buffer = dma.buffer;
if (dma.lengthCounter)
DoDMA( cpu, tmp, readAddress );
}
}
}
inline uint Apu::Dmc::GetLengthCounter() const
{
return dma.lengthCounter;
}
#ifdef NST_MSVC_OPTIMIZE
#pragma optimize("s", on)
#endif
void Apu::ClearBuffers()
{
ClearBuffers( true );
}
NST_NO_INLINE void Apu::ClearBuffers(bool resync)
{
if (resync)
synchronizer.Resync( settings.speed, cpu );
square[0].ClearAmp();
square[1].ClearAmp();
triangle.ClearAmp();
noise.ClearAmp();
dmc.ClearAmp();
dcBlocker.Reset();
buffer.Reset( false );
}
#ifdef NST_MSVC_OPTIMIZE
#pragma optimize("", on)
#endif
Cycle Apu::Clock()
{
if (cycles.dmcClock <= cpu.GetCycles())
ClockDmc( cpu.GetCycles() );
if (cycles.frameIrqClock <= cpu.GetCycles())
ClockFrameIRQ( cpu.GetCycles() );
return NST_MIN(cycles.dmcClock,cycles.frameIrqClock);
}
void Apu::ClockDMA(uint readAddress)
{
if (cycles.dmcClock <= cpu.GetCycles())
ClockDmc( cpu.GetCycles(), readAddress );
}
NST_NO_INLINE void Apu::ClockOscillators(const bool twoClocks)
{
for (uint i=0; i < 2; ++i)
square[i].ClockEnvelope();
triangle.ClockLinearCounter();
noise.ClockEnvelope();
if (twoClocks)
{
for (uint i=0; i < 2; ++i)
square[i].ClockSweep( i-1 );
triangle.ClockLengthCounter();
noise.ClockLengthCounter();
}
}
NST_NO_INLINE void Apu::ClockDmc(const Cycle target,const uint readAddress)
{
NST_ASSERT( cycles.dmcClock <= target );
do
{
if (dmc.ClockDAC())
{
Update( cycles.dmcClock );
dmc.Update();
}
dmc.ClockDMA( cpu, cycles.dmcClock, readAddress );
}
while (cycles.dmcClock <= target);
}
NST_NO_INLINE void Apu::ClockFrameCounter()
{
NST_COMPILE_ASSERT( STATUS_SEQUENCE_5_STEP == 0x80 );
NST_VERIFY( cycles.frameCounter <= cpu.GetCycles() * cycles.fixed );
ClockOscillators( cycles.frameDivider & 0x1U );
cycles.frameDivider = (cycles.frameDivider + 1) & 0x3U;
cycles.frameCounter += Cycles::oscillatorClocks[cpu.GetModel()][ctrl >> 7][cycles.frameDivider] * cycles.fixed;
}
NST_NO_INLINE void Apu::ClockFrameIRQ(const Cycle target)
{
NST_VERIFY( ctrl == STATUS_FRAME_IRQ_ENABLE );
cpu.DoIRQ( Cpu::IRQ_FRAME, cycles.frameIrqClock );
Cycle clock = cycles.frameIrqClock;
uint repeat = cycles.frameIrqRepeat;
do
{
clock += Cycles::frameClocks[cpu.GetModel()][1 + repeat++ % 3];
}
while (clock <= target);
cycles.frameIrqClock = clock;
cycles.frameIrqRepeat = repeat;
}
NST_NO_INLINE Apu::Channel::Sample Apu::GetSample()
{
dword dac[2];
return Clamp<Channel::OUTPUT_MIN,Channel::OUTPUT_MAX>
(
dcBlocker.Apply
(
(0 != (dac[0] = square[0].GetSample() + square[1].GetSample()) ? NLN_SQ_0 / (NLN_SQ_1 / dac[0] + NLN_SQ_2) : 0) +
(0 != (dac[1] = triangle.GetSample() + noise.GetSample() + dmc.GetSample()) ? NLN_TND_0 / (NLN_TND_1 / dac[1] + NLN_TND_2) : 0)
) + (extChannel ? extChannel->GetSample() : 0)
);
}
NES_POKE_AD(Apu,4000)
{
UpdateLatency();
square[address >> 2 & 0x1].WriteReg0( data );
}
NES_POKE_AD(Apu,4001)
{
Update();
square[address >> 2 & 0x1].WriteReg1( data );
}
NES_POKE_AD(Apu,4002)
{
Update();
square[address >> 2 & 0x1].WriteReg2( data );
}
NES_POKE_AD(Apu,4003)
{
square[address >> 2 & 0x1].WriteReg3( data, UpdateDelta() );
}
NES_POKE_D(Apu,4008)
{
Update();
triangle.WriteReg0( data );
}
NES_POKE_D(Apu,400A)
{
Update();
triangle.WriteReg2( data );
}
NES_POKE_D(Apu,400B)
{
triangle.WriteReg3( data, UpdateDelta() );
}
NES_POKE_D(Apu,400C)
{
UpdateLatency();
noise.WriteReg0( data );
}
NES_POKE_D(Apu,400E)
{
Update();
noise.WriteReg2( data, cpu.GetModel() );
}
NES_POKE_D(Apu,400F)
{
noise.WriteReg3( data, UpdateDelta() );
}
NES_POKE_D(Apu,4010)
{
if (!dmc.WriteReg0( data, cpu.GetModel() ))
cpu.ClearIRQ( Cpu::IRQ_DMC );
}
NES_POKE_D(Apu,4011)
{
Update();
dmc.WriteReg1( data );
}
NES_POKE_D(Apu,4012)
{
dmc.WriteReg2( data );
}
NES_POKE_D(Apu,4013)
{
dmc.WriteReg3( data );
}
NES_POKE_D(Apu,4015)
{
Update();
data = ~data;
square[0].Disable ( data >> 0 & 0x1 );
square[1].Disable ( data >> 1 & 0x1 );
triangle.Disable ( data >> 2 & 0x1 );
noise.Disable ( data >> 3 & 0x1 );
dmc.Disable ( data & 0x10, cpu );
}
NES_PEEK_A(Apu,4015)
{
NST_COMPILE_ASSERT( Cpu::IRQ_FRAME == 0x40 && Cpu::IRQ_DMC == 0x80 );
const Cycle elapsed = cpu.Update( address );
if (cycles.frameIrqClock <= elapsed)
ClockFrameIRQ( elapsed );
if (cycles.frameCounter < elapsed * cycles.fixed)
Update( elapsed );
const uint data = cpu.GetIRQ();
cpu.ClearIRQ( Cpu::IRQ_FRAME );
return (data & (Cpu::IRQ_FRAME|Cpu::IRQ_DMC)) |
(
( square[0].GetLengthCounter() ? 0x01U : 0x00U ) |
( square[1].GetLengthCounter() ? 0x02U : 0x00U ) |
( triangle.GetLengthCounter() ? 0x04U : 0x00U ) |
( noise.GetLengthCounter() ? 0x08U : 0x00U ) |
( dmc.GetLengthCounter() ? 0x10U : 0x00U )
);
}
void Apu::WriteFrameCtrl(uint data)
{
Cycle next = cpu.Update();
if (cpu.IsOddCycle())
next += cpu.GetClock();
Update( next );
if (cycles.frameIrqClock <= next)
ClockFrameIRQ( next );
next += cpu.GetClock();
data &= STATUS_BITS;
cycles.frameCounter = (next + Cycles::oscillatorClocks[cpu.GetModel()][data >> 7][0]) * cycles.fixed;
cycles.frameDivider = 0;
cycles.frameIrqRepeat = 0;
ctrl = data;
if (data)
{
cycles.frameIrqClock = Cpu::CYCLE_MAX;
if (data & STATUS_NO_FRAME_IRQ)
cpu.ClearIRQ( Cpu::IRQ_FRAME );
if (data & STATUS_SEQUENCE_5_STEP)
ClockOscillators( true );
}
else
{
cycles.frameIrqClock = next + Cycles::frameClocks[cpu.GetModel()][0];
}
}
NES_PEEK(Apu,40xx)
{
return 0x40;
}
}
}