nestopia/source/core/NstApu.hpp
2020-12-12 10:08:52 -05:00

670 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
//
////////////////////////////////////////////////////////////////////////////////////////
#ifndef NST_CPU_H
#error Do not include NstApu.h directly!
#endif
#include "NstSoundRenderer.hpp"
namespace Nes
{
namespace Core
{
namespace Sound
{
class Output;
}
namespace State
{
class Saver;
class Loader;
}
class Cpu;
class Apu
{
public:
explicit Apu(Cpu&);
void Reset(bool);
void PowerOff();
void ClearBuffers();
void BeginFrame(Sound::Output*);
void EndFrame();
void WriteFrameCtrl(uint);
Cycle Clock();
void ClockDMA(uint=0);
Result SetSampleRate(dword);
Result SetSampleBits(uint);
Result SetSpeed(uint);
Result SetVolume(uint,uint);
uint GetVolume(uint) const;
uint GetCtrl();
void Mute(bool);
void SetAutoTranspose(bool);
void SetGenie(bool);
void EnableStereo(bool);
void SaveState(State::Saver&,dword) const;
void LoadState(State::Loader&);
class NST_NO_VTABLE Channel
{
Apu& apu;
protected:
explicit Channel(Apu&);
~Channel();
void Update() const;
void Connect(bool);
dword GetSampleRate() const;
uint GetVolume(uint) const;
void GetOscillatorClock(Cycle&,uint&) const;
Cycle GetCpuClockBase() const;
uint GetCpuClockDivider() const;
Cycle GetCpuClock(uint=1) const;
bool IsMuted() const;
bool IsGenie() const;
public:
typedef Sound::Sample Sample;
enum
{
APU_SQUARE1,
APU_SQUARE2,
APU_TRIANGLE,
APU_NOISE,
APU_DPCM,
EXT_FDS,
EXT_MMC5,
EXT_VRC6,
EXT_VRC7,
EXT_N163,
EXT_S5B
};
enum
{
OUTPUT_MIN = -32767,
OUTPUT_MAX = +32767,
OUTPUT_MUL = 256,
OUTPUT_DECAY = OUTPUT_MUL / 4 - 1,
DEFAULT_VOLUME = 85
};
virtual void Reset() = 0;
virtual Sample GetSample() = 0;
virtual Cycle Clock(Cycle,Cycle,Cycle);
virtual bool UpdateSettings() = 0;
class LengthCounter
{
public:
LengthCounter();
void Reset();
void LoadState(State::Loader&);
void SaveState(State::Saver&,dword) const;
private:
uint enabled;
uint count;
static const byte lut[32];
public:
uint Disable(bool disable)
{
enabled = disable - 1U;
count &= enabled;
return enabled;
}
void Write(uint data)
{
NST_ASSERT( (data >> 3) < sizeof(array(lut)) );
count = lut[data >> 3] & enabled;
}
void Write(uint data,bool frameCounterDelta)
{
NST_VERIFY_MSG( frameCounterDelta, "APU $40xx/framecounter conflict" );
if (frameCounterDelta || !count)
Write( data );
}
uint GetCount() const
{
return count;
}
bool Clock()
{
return count && !--count;
}
};
class Envelope
{
public:
Envelope();
void Reset();
void SetOutputVolume(uint);
void LoadState(State::Loader&);
void SaveState(State::Saver&,dword) const;
void Clock();
void Write(uint);
private:
void UpdateOutput();
dword output;
uint outputVolume;
byte regs[2];
byte count;
bool reset;
public:
bool Looping() const
{
return regs[1] & 0x20U;
}
dword Volume() const
{
return output;
}
void ResetClock()
{
reset = true;
}
};
class DcBlocker
{
public:
DcBlocker();
void Reset();
Sample Apply(Sample);
void LoadState(State::Loader&);
void SaveState(State::Saver&,dword) const;
private:
enum
{
POLE = 3 // ~0.9999
};
idword prev;
idword next;
idword acc;
};
};
private:
typedef void (NST_FASTCALL Apu::*Updater)(Cycle);
inline void Update(Cycle);
void Update();
void UpdateLatency();
bool UpdateDelta();
void Reset(bool,bool);
void CalculateOscillatorClock(Cycle&,uint&) const;
void Resync(dword);
NST_NO_INLINE void ClearBuffers(bool);
enum
{
MAX_CHANNELS = 11,
STATUS_NO_FRAME_IRQ = 0x40,
STATUS_SEQUENCE_5_STEP = 0x80,
STATUS_FRAME_IRQ_ENABLE = 0,
STATUS_BITS = STATUS_NO_FRAME_IRQ|STATUS_SEQUENCE_5_STEP,
NLN_VOL = 192,
NLN_SQ_F = 900,
NLN_SQ_0 = 9552UL * Channel::OUTPUT_MUL * NLN_VOL * (NLN_SQ_F/100),
NLN_SQ_1 = 8128UL * Channel::OUTPUT_MUL * NLN_SQ_F,
NLN_SQ_2 = NLN_SQ_F * 100UL,
NLN_TND_F = 500,
NLN_TND_0 = 16367UL * Channel::OUTPUT_MUL * NLN_VOL * (NLN_TND_F/100),
NLN_TND_1 = 24329UL * Channel::OUTPUT_MUL * NLN_TND_F,
NLN_TND_2 = NLN_TND_F * 100UL
};
NES_DECL_POKE( 4000 );
NES_DECL_POKE( 4001 );
NES_DECL_POKE( 4002 );
NES_DECL_POKE( 4003 );
NES_DECL_POKE( 4004 );
NES_DECL_POKE( 4005 );
NES_DECL_POKE( 4006 );
NES_DECL_POKE( 4007 );
NES_DECL_POKE( 4008 );
NES_DECL_POKE( 400A );
NES_DECL_POKE( 400B );
NES_DECL_POKE( 400C );
NES_DECL_POKE( 400E );
NES_DECL_POKE( 400F );
NES_DECL_POKE( 4010 );
NES_DECL_POKE( 4011 );
NES_DECL_POKE( 4012 );
NES_DECL_POKE( 4013 );
NES_DECL_POKE( 4015 );
NES_DECL_PEEK( 4015 );
NES_DECL_PEEK( 40xx );
NST_NO_INLINE Channel::Sample GetSample();
void NST_FASTCALL SyncOn (Cycle);
void NST_FASTCALL SyncOnExt (Cycle);
void NST_FASTCALL SyncOff (Cycle);
NST_NO_INLINE void ClockFrameIRQ(Cycle);
NST_NO_INLINE void ClockFrameCounter();
NST_NO_INLINE void ClockDmc(Cycle,uint=0);
NST_NO_INLINE void ClockOscillators(bool);
template<typename T,bool STEREO>
void FlushSound();
void UpdateSettings();
void UpdateVolumes();
struct Cycles
{
Cycles();
void Update(dword,uint,const Cpu&);
void Reset(bool,CpuModel);
uint fixed;
Cycle rate;
Cycle rateCounter;
Cycle frameCounter;
Cycle extCounter;
word frameDivider;
word frameIrqRepeat;
Cycle frameIrqClock;
Cycle dmcClock;
static const dword frameClocks[3][4];
static const dword oscillatorClocks[3][2][4];
};
class Synchronizer
{
uint sync;
uint duty;
dword streamed;
dword rate;
public:
Synchronizer();
void Reset(uint,dword,const Cpu&);
void Resync(uint,const Cpu&);
NST_SINGLE_CALL dword Clock(dword,dword,const Cpu&);
};
class Oscillator
{
enum
{
RESET_CYCLES = 2048
};
protected:
Oscillator();
void Reset();
void UpdateSettings(dword,uint);
ibool active;
idword timer;
Cycle rate;
Cycle frequency;
dword amp;
uint fixed;
public:
inline void ClearAmp();
};
class Square : public Oscillator
{
public:
void Reset();
void UpdateSettings(uint,dword,uint);
void LoadState(State::Loader&);
void SaveState(State::Saver&,dword) const;
NST_SINGLE_CALL void WriteReg0(uint);
NST_SINGLE_CALL void WriteReg1(uint);
NST_SINGLE_CALL void WriteReg2(uint);
NST_SINGLE_CALL void WriteReg3(uint,Cycle);
NST_SINGLE_CALL void Disable(bool);
dword GetSample();
NST_SINGLE_CALL void ClockEnvelope();
NST_SINGLE_CALL void ClockSweep(uint);
inline uint GetLengthCounter() const;
private:
inline bool CanOutput() const;
void UpdateFrequency();
enum
{
MIN_FRQ = 0x008,
MAX_FRQ = 0x7FF,
REG0_DUTY_SHIFT = 6,
REG1_SWEEP_SHIFT = 0x07,
REG1_SWEEP_DECREASE = 0x08,
REG1_SWEEP_RATE = 0x70,
REG1_SWEEP_RATE_SHIFT = 4,
REG1_SWEEP_ENABLED = 0x80,
REG3_WAVELENGTH_LOW = 0x00FF,
REG3_WAVELENGTH_HIGH = 0x0700
};
uint step;
uint duty;
Channel::Envelope envelope;
Channel::LengthCounter lengthCounter;
bool validFrequency;
bool sweepReload;
byte sweepCount;
byte sweepRate;
uint sweepIncrease;
word sweepShift;
word waveLength;
};
class Triangle : public Oscillator
{
public:
Triangle();
void Reset();
void UpdateSettings(uint,dword,uint);
void LoadState(State::Loader&);
void SaveState(State::Saver&,dword) const;
NST_SINGLE_CALL void WriteReg0(uint);
NST_SINGLE_CALL void WriteReg2(uint);
NST_SINGLE_CALL void WriteReg3(uint,Cycle);
NST_SINGLE_CALL void Disable(bool);
NST_SINGLE_CALL dword GetSample();
NST_SINGLE_CALL void ClockLinearCounter();
NST_SINGLE_CALL void ClockLengthCounter();
inline uint GetLengthCounter() const;
private:
inline bool CanOutput() const;
enum
{
MIN_FRQ = 2 + 1,
STEP_CHECK = 0x1F,
REG0_LINEAR_COUNTER_LOAD = 0x7F,
REG0_LINEAR_COUNTER_START = 0x80,
REG2_WAVE_LENGTH_LOW = 0x00FF,
REG3_WAVE_LENGTH_HIGH = 0x0700
};
enum Status
{
STATUS_COUNTING,
STATUS_RELOAD
};
uint step;
uint outputVolume;
Status status;
word waveLength;
byte linearCtrl;
byte linearCounter;
Channel::LengthCounter lengthCounter;
};
class Noise : public Oscillator
{
public:
void Reset(CpuModel);
void UpdateSettings(uint,dword,uint);
void LoadState(State::Loader&,CpuModel);
void SaveState(State::Saver&,dword) const;
NST_SINGLE_CALL void WriteReg0(uint);
NST_SINGLE_CALL void WriteReg2(uint,CpuModel);
NST_SINGLE_CALL void WriteReg3(uint,Cycle);
NST_SINGLE_CALL void Disable(bool);
NST_SINGLE_CALL dword GetSample();
NST_SINGLE_CALL void ClockEnvelope();
NST_SINGLE_CALL void ClockLengthCounter();
inline uint GetLengthCounter() const;
private:
inline bool CanOutput() const;
uint GetFrequencyIndex() const;
enum
{
REG2_FREQUENCY = 0x0F,
REG2_93BIT_MODE = 0x80
};
uint bits;
uint shifter;
Channel::Envelope envelope;
Channel::LengthCounter lengthCounter;
static const word lut[3][16];
};
class Dmc
{
public:
Dmc();
void Reset(CpuModel);
void UpdateSettings(uint);
void LoadState(State::Loader&,const Cpu&,CpuModel,Cycle&);
void SaveState(State::Saver&,dword,const Cpu&,Cycle) const;
NST_SINGLE_CALL bool WriteReg0(uint,CpuModel);
NST_SINGLE_CALL void WriteReg1(uint);
NST_SINGLE_CALL void WriteReg2(uint);
NST_SINGLE_CALL void WriteReg3(uint);
NST_SINGLE_CALL void Disable(bool,Cpu&);
NST_SINGLE_CALL dword GetSample();
NST_SINGLE_CALL bool ClockDAC();
NST_SINGLE_CALL void Update();
NST_SINGLE_CALL void ClockDMA(Cpu&,Cycle&,uint=0);
inline void ClearAmp();
inline uint GetLengthCounter() const;
static Cycle GetResetFrequency(CpuModel);
private:
void DoDMA(Cpu&,Cycle,uint=0);
enum
{
REG0_FREQUENCY = 0x0F,
REG0_LOOP = 0x40,
REG0_IRQ_ENABLE = 0x80,
INP_STEP = 8
};
uint curSample;
uint linSample;
uint outputVolume;
Cycle frequency;
struct
{
uint ctrl;
word lengthCounter;
word address;
} regs;
struct
{
byte shifter;
byte dac;
byte buffer;
bool active;
} out;
struct
{
word lengthCounter;
word address;
word buffered;
word buffer;
} dma;
static const word lut[3][16];
};
struct Settings
{
Settings();
dword rate;
uint bits;
byte speed;
bool muted;
bool transpose;
bool genie;
bool stereo;
bool audible;
byte volumes[MAX_CHANNELS];
};
uint ctrl;
Updater updater;
Cpu& cpu;
Cycles cycles;
Synchronizer synchronizer;
Square square[2];
Triangle triangle;
Noise noise;
Dmc dmc;
Channel* extChannel;
Channel::DcBlocker dcBlocker;
Sound::Output* stream;
Sound::Buffer buffer;
Settings settings;
public:
dword GetSampleRate() const
{
return settings.rate;
}
uint GetSampleBits() const
{
return settings.bits;
}
uint GetSpeed() const
{
return settings.speed;
}
bool IsAutoTransposing() const
{
return settings.transpose;
}
bool IsGenie() const
{
return settings.genie;
}
bool InStereo() const
{
return settings.stereo;
}
bool IsMuted() const
{
return settings.muted;
}
bool IsAudible() const
{
return settings.audible && !settings.muted;
}
};
}
}