Mesen/Core/SquareChannel.h

204 lines
4.5 KiB
C++

#pragma once
#include "stdafx.h"
#include "APU.h"
#include "IMemoryHandler.h"
#include "ApuEnvelope.h"
class SquareChannel : public ApuEnvelope
{
protected:
const uint8_t _dutySequences[4][8] = {
{ 0, 0, 0, 0, 0, 0, 0, 1 },
{ 0, 0, 0, 0, 0, 0, 1, 1 },
{ 0, 0, 0, 0, 1, 1, 1, 1 },
{ 1, 1, 1, 1, 1, 1, 0, 0 }
};
bool _isChannel1 = false;
bool _isMmc5Square = false;
uint8_t _duty = 0;
uint8_t _dutyPos = 0;
bool _sweepEnabled = false;
uint8_t _sweepPeriod = 0;
bool _sweepNegate = false;
uint8_t _sweepShift = 0;
bool _reloadSweep = false;
uint8_t _sweepDivider = 0;
uint32_t _sweepTargetPeriod = 0;
uint16_t _realPeriod = 0;
bool IsMuted()
{
//A period of t < 8, either set explicitly or via a sweep period update, silences the corresponding pulse channel.
return _realPeriod < 8 || (!_sweepNegate && _sweepTargetPeriod > 0x7FF);
}
virtual void InitializeSweep(uint8_t regValue)
{
_sweepEnabled = (regValue & 0x80) == 0x80;
_sweepNegate = (regValue & 0x08) == 0x08;
//The divider's period is set to P + 1
_sweepPeriod = ((regValue & 0x70) >> 4) + 1;
_sweepShift = (regValue & 0x07);
UpdateTargetPeriod();
//Side effects: Sets the reload flag
_reloadSweep = true;
}
void UpdateTargetPeriod()
{
uint16_t shiftResult = (_realPeriod >> _sweepShift);
if(_sweepNegate) {
_sweepTargetPeriod = _realPeriod - shiftResult;
if(_isChannel1) {
// As a result, a negative sweep on pulse channel 1 will subtract the shifted period value minus 1
_sweepTargetPeriod--;
}
} else {
_sweepTargetPeriod = _realPeriod + shiftResult;
}
}
void SetPeriod(uint16_t newPeriod)
{
_realPeriod = newPeriod;
_period = (_realPeriod * 2) + 1;
UpdateTargetPeriod();
}
void UpdateOutput()
{
if(IsMuted()) {
AddOutput(0);
} else {
AddOutput(_dutySequences[_duty][_dutyPos] * GetVolume());
}
}
protected:
void Clock() override
{
_dutyPos = (_dutyPos - 1) & 0x07;
UpdateOutput();
}
public:
SquareChannel(AudioChannel channel, SoundMixer *mixer, bool isChannel1) : ApuEnvelope(channel, mixer)
{
_isChannel1 = isChannel1;
}
virtual void Reset(bool softReset) override
{
ApuEnvelope::Reset(softReset);
_duty = 0;
_dutyPos = 0;
_realPeriod = 0;
_sweepEnabled = false;
_sweepPeriod = 0;
_sweepNegate = false;
_sweepShift = 0;
_reloadSweep = false;
_sweepDivider = 0;
_sweepTargetPeriod = 0;
UpdateTargetPeriod();
}
virtual void StreamState(bool saving) override
{
ApuEnvelope::StreamState(saving);
Stream(_realPeriod, _duty, _dutyPos, _sweepEnabled, _sweepPeriod, _sweepNegate, _sweepShift, _reloadSweep, _sweepDivider, _sweepTargetPeriod);
}
void GetMemoryRanges(MemoryRanges &ranges) override
{
if(_isChannel1) {
ranges.AddHandler(MemoryOperation::Write, 0x4000, 0x4003);
} else {
ranges.AddHandler(MemoryOperation::Write, 0x4004, 0x4007);
}
}
void WriteRAM(uint16_t addr, uint8_t value) override
{
APU::StaticRun();
switch(addr & 0x03) {
case 0: //4000 & 4004
InitializeLengthCounter((value & 0x20) == 0x20);
InitializeEnvelope(value);
_duty = (value & 0xC0) >> 6;
if(EmulationSettings::CheckFlag(EmulationFlags::SwapDutyCycles)) {
_duty = ((_duty & 0x02) >> 1) | ((_duty & 0x01) << 1);
}
break;
case 1: //4001 & 4005
InitializeSweep(value);
break;
case 2: //4002 & 4006
SetPeriod((_realPeriod & 0x0700) | value);
break;
case 3: //4003 & 4007
LoadLengthCounter(value >> 3);
SetPeriod((_realPeriod & 0xFF) | ((value & 0x07) << 8));
//The sequencer is restarted at the first value of the current sequence.
_dutyPos = 0;
//The envelope is also restarted.
ResetEnvelope();
break;
}
if(!_isMmc5Square) {
UpdateOutput();
}
}
void TickSweep()
{
_sweepDivider--;
if(_sweepDivider == 0) {
if(_sweepShift > 0 && _sweepEnabled && _realPeriod >= 8 && _sweepTargetPeriod <= 0x7FF) {
SetPeriod(_sweepTargetPeriod);
}
_sweepDivider = _sweepPeriod;
}
if(_reloadSweep) {
_sweepDivider = _sweepPeriod;
_reloadSweep = false;
}
}
ApuSquareState GetState()
{
ApuSquareState state;
state.Duty = _duty;
state.DutyPosition = _dutyPos;
state.Enabled = _enabled;
state.Envelope = ApuEnvelope::GetState();
state.Frequency = CPU::GetClockRate(GetNesModel()) / 16.0 / (_realPeriod + 1);
state.LengthCounter = ApuLengthCounter::GetState();
state.OutputVolume = _lastOutput;
state.Period = _realPeriod;
state.SweepEnabled = _sweepEnabled;
state.SweepNegate = _sweepNegate;
state.SweepPeriod = _sweepPeriod;
state.SweepShift = _sweepShift;
return state;
}
};