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