GBA.Unity/Assets/emulator/GbAudio.cs

1186 lines
44 KiB
C#

using System;
using static OptimeGBA.Bits;
namespace OptimeGBA
{
public sealed class GbAudio
{
public static byte[] SEVEN_BIT_NOISE = GenerateNoiseBuffer(true);
public static byte[] FIFTEEN_BIT_NOISE = GenerateNoiseBuffer(false);
public static float[] DAC_TABLE = GenerateDACTable();
public static int SAMPLE_RATE = 32768; // Digital mixing rate is 32768 Hz on GBA
public static int SAMPLE_TIME_MAX = 4194304 / SAMPLE_RATE;
readonly double CAPACITOR_FACTOR = Math.Pow(0.999958, (4194304 / SAMPLE_RATE)); // DMG
public static float[] GenerateDACTable()
{
float[] table = new float[32];
for (int i = 0; i < 16; i++)
{
table[i] = ((i / 15) * 2) - 1;
}
return table;
}
public static byte[] GenerateNoiseBuffer(bool putBitBack)
{
uint seed = 0xFF;
byte[] waveTable = new byte[32768];
for (int i = 0; i < 32768; i++)
{
waveTable[i] = (byte)((seed & 1) ^ 1);
int bit = (int)((seed) ^ (seed >> 1));
bit &= 1;
seed = (uint)(seed >> 1) | (uint)(bit << 14);
if (putBitBack == true)
{
seed &= ~BIT_7;
seed |= (uint)(bit << 6);
}
}
return waveTable;
}
public short Out1;
public short Out2;
readonly static uint[][] PULSE_DUTY = new uint[][] {
new uint[] {1, 1, 1, 1, 1, 1, 1, 0},
new uint[] {0, 1, 1, 1, 1, 1, 1, 0},
new uint[] {0, 1, 1, 1, 1, 0, 0, 0},
new uint[] {1, 0, 0, 0, 0, 0, 0, 1},
};
uint Pulse1_pos;
uint Pulse1_width = 2;
uint Pulse1_period = 8192;
int Pulse1_timer;
public bool enabled = false;
int ticksEnvelopePulse1 = 0;
int ticksEnvelopePulse2 = 0;
int ticksEnvelopeNoise = 0;
int clockPulse1FreqSweep = 0;
bool freqSweepEnabled = false;
int frameSequencerStep = 0;
byte[] soundRegisters = new byte[65536];
void advanceFrameSequencer()
{
if (this.enabled)
{
// 512Hz Frame Sequencer
switch (this.frameSequencerStep)
{
case 0:
case 4:
this.frameSequencerLength();
this.update();
break;
case 2:
case 6:
this.frameSequencerLength();
this.frameSequencerFrequencySweep();
this.update();
break;
case 7:
this.frameSequencerVolumeEnvelope();
this.update();
break;
default:
break;
}
this.frameSequencerStep++; this.frameSequencerStep &= 0b111;
}
}
// #region Channel params
public bool pulse1_enabled = false;
public int pulse1_width = 3;
public bool pulse1_dacEnabled = false;
public bool pulse1_lengthEnable = false;
public int pulse1_lengthCounter = 0;
public int pulse1_frequencyUpper = 0;
public int pulse1_frequencyLower = 0;
public int pulse1_volume = 0;
public bool pulse1_volumeEnvelopeUp = false;
public int pulse1_volumeEnvelopeSweep = 4;
public int pulse1_volumeEnvelopeStart = 0;
public bool pulse1_outputLeft = false;
public bool pulse1_outputRight = false;
public int pulse1_freqSweepPeriod = 0;
public bool pulse1_freqSweepUp = false;
public int pulse1_freqSweepShift = 0;
public bool pulse1_updated = true;
void pulse1_trigger()
{
if (this.pulse1_lengthCounter == 0 || this.pulse1_lengthEnable == false)
{
this.pulse1_lengthCounter = 64;
}
this.pulse1_volume = this.pulse1_volumeEnvelopeStart;
if (this.pulse1_dacEnabled)
{
this.pulse1_enabled = true;
}
this.clockPulse1FreqSweep = 0;
this.freqSweepEnabled = this.pulse1_freqSweepShift != 0 || this.pulse1_freqSweepPeriod != 0;
this.reloadPulse1Period();
this.updatePulse1Val();
}
public float pulse1_getFrequencyHz()
{
float frequency = (this.pulse1_frequencyUpper << 8) | this.pulse1_frequencyLower;
return 131072 / (2048 - frequency);
}
public bool pulse2_enabled = false;
public int pulse2_width = 3;
public bool pulse2_dacEnabled = false;
public bool pulse2_lengthEnable = false;
public int pulse2_lengthCounter = 0;
public int pulse2_frequencyUpper = 0;
public int pulse2_frequencyLower = 0;
public int pulse2_volume = 0;
public bool pulse2_volumeEnvelopeUp = false;
public int pulse2_volumeEnvelopeSweep = 4;
public int pulse2_volumeEnvelopeStart = 0;
public bool pulse2_outputLeft = false;
public bool pulse2_outputRight = false;
public bool pulse2_updated = true;
void pulse2_trigger()
{
if (this.pulse2_lengthCounter == 0 || this.pulse2_lengthEnable == false)
{
this.pulse2_lengthCounter = 64;
}
this.pulse2_volume = this.pulse2_volumeEnvelopeStart;
if (this.pulse2_dacEnabled)
{
this.pulse2_enabled = true;
}
this.reloadPulse2Period();
this.updatePulse2Val();
}
public float pulse2_getFrequencyHz()
{
float frequency = (this.pulse2_frequencyUpper << 8) | this.pulse2_frequencyLower;
return 131072 / (2048 - frequency);
}
public bool wave_bank = false;
public bool wave_dimension = true;
public bool wave_enabled = false;
public bool wave_dacEnabled = false;
public bool wave_lengthEnable = true;
public int wave_lengthCounter = 0;
public int wave_frequencyUpper = 0;
public int wave_frequencyLower = 0;
public int wave_volume = 0;
public int wave_oldVolume = 0;
public byte[] wave_waveTable0 = new byte[32];
public byte[] wave_waveTable1 = new byte[32];
public bool wave_outputLeft = false;
public bool wave_outputRight = false;
public float wave_getFrequencyHz()
{
float frequency = (this.wave_frequencyUpper << 8) | this.wave_frequencyLower;
return 65536 / (2048 - frequency);
}
public bool noise_enabled = false;
public int noise_divisorCode = 0;
public bool noise_lengthEnable = false;
public int noise_lengthCounter = 0;
public bool noise_dacEnabled = false;
public int noise_volume = 0;
public bool noise_volumeEnvelopeUp = false;
public int noise_volumeEnvelopeSweep = 4;
public int noise_volumeEnvelopeStart = 0;
public bool noise_outputLeft = false;
public bool noise_outputRight = false;
public int noise_shiftClockFrequency = 0;
public bool noise_counterStep = false;
public int noise_envelopeSweep = 0;
// #endregion
bool nightcoreMode = false;
bool vinLeftEnable = false;
bool vinRightEnable = false;
public int leftMasterVol = 0;
public int rightMasterVol = 0;
public int leftMasterVolMul = 0;
public int rightMasterVolMul = 0;
public byte pulse1Val = 0;
public byte pulse2Val = 0;
public byte waveVal = 0;
public byte noiseVal = 0;
int pulse1Pos = 0;
int pulse2Pos = 0;
int wavePos = 0;
int noisePos = 0;
int pulse1FreqTimer = 0;
int pulse2FreqTimer = 0;
int waveFreqTimer = 0;
int noiseFreqTimer = 0;
int pulse1Period = 0;
int pulse2Period = 0;
int wavePeriod = 0;
int noisePeriod = 0;
float capacitor1 = 0;
float capacitor2 = 0;
void calcPulse1Period()
{
this.pulse1Period = (2048 - ((this.pulse1_frequencyUpper << 8) | this.pulse1_frequencyLower)) * 4;
if (this.nightcoreMode) this.pulse1Period = (int)(this.pulse1Period * 0.5);
}
void calcPulse2Period()
{
this.pulse2Period = (2048 - ((this.pulse2_frequencyUpper << 8) | this.pulse2_frequencyLower)) * 4;
if (this.nightcoreMode) this.pulse2Period = (int)(this.pulse2Period * 0.5);
}
void calcWavePeriod()
{
this.wavePeriod = (2048 - ((this.wave_frequencyUpper << 8) | this.wave_frequencyLower)) * 2;
if (this.nightcoreMode) this.wavePeriod = (int)(this.wavePeriod * 0.5);
}
void calcNoisePeriod()
{
int[] noiseDivider = new int[] { 8, 16, 32, 48, 64, 80, 96, 112 };
this.noisePeriod = (noiseDivider[this.noise_divisorCode] << this.noise_shiftClockFrequency);
}
void reloadPulse1Period() { this.pulse1FreqTimer = this.pulse1Period; }
void reloadPulse2Period() { this.pulse2FreqTimer = this.pulse2Period; }
void reloadWavePeriod() { this.waveFreqTimer = this.wavePeriod; }
void reloadNoisePeriod() { this.noiseFreqTimer = this.noisePeriod; }
void noise_trigger()
{
if (this.noise_dacEnabled)
this.noise_enabled = true;
if (this.noise_lengthCounter == 0 || this.noise_lengthEnable == false)
{
this.noise_lengthCounter = 64;
}
this.noise_volume = this.noise_volumeEnvelopeStart;
this.updateNoiseVal();
}
void wave_trigger()
{
if (this.wave_lengthCounter == 0 || this.wave_lengthEnable == false)
{
this.wave_lengthCounter = 256;
}
if (this.wave_dacEnabled)
{
this.wave_enabled = true;
}
this.updateWaveVal();
}
public bool enable1Out = true;
public bool enable2Out = true;
public bool enable3Out = true;
public bool enable4Out = true;
void updatePulse1Val()
{
this.pulse1Val = (byte)(this.pulse1_enabled ? PULSE_DUTY[this.pulse1_width][this.pulse1Pos] * this.pulse1_volume : 0);
}
void updatePulse2Val()
{
this.pulse2Val = (byte)(this.pulse2_enabled ? PULSE_DUTY[this.pulse2_width][this.pulse2Pos] * this.pulse2_volume : 0);
}
void updateWaveVal()
{
int[] waveShift = new int[] { 4, 0, 1, 2 };
if (this.wave_dimension)
{
if (this.wavePos < 32)
{
int pos = this.wavePos - 0;
this.waveVal = (byte)(this.wave_enabled ? this.wave_waveTable0[this.wavePos] >> waveShift[this.wave_volume] : 0);
}
else
{
int pos = this.wavePos - 32;
this.waveVal = (byte)(this.wave_enabled ? this.wave_waveTable1[this.wavePos] >> waveShift[this.wave_volume] : 0);
}
}
else
{
if (this.wave_bank)
{
this.waveVal = (byte)(this.wave_enabled ? this.wave_waveTable1[this.wavePos] >> waveShift[this.wave_volume] : 0);
}
else
{
this.waveVal = (byte)(this.wave_enabled ? this.wave_waveTable0[this.wavePos] >> waveShift[this.wave_volume] : 0);
}
}
}
void updateNoiseVal()
{
this.noiseVal = (byte)(this.noise_enabled ? (this.noise_counterStep ? SEVEN_BIT_NOISE[this.noisePos] : FIFTEEN_BIT_NOISE[this.noisePos]) * this.noise_volume : 0);
}
uint pendingCycles = 0;
const uint FrameSequencerMax = 8192;
uint FrameSequencerTimer = 0;
public void Tick(uint cycles)
{
this.pendingCycles += cycles;
this.FrameSequencerTimer += cycles;
if (this.FrameSequencerTimer >= FrameSequencerMax)
{
this.FrameSequencerTimer -= FrameSequencerMax;
this.advanceFrameSequencer();
}
if (this.enabled)
{
if (this.pulse1_enabled) this.pulse1FreqTimer -= (int)cycles;
if (this.pulse2_enabled) this.pulse2FreqTimer -= (int)cycles;
if (this.wave_enabled) this.waveFreqTimer -= (int)cycles;
if (this.noise_enabled) this.noiseFreqTimer -= (int)cycles;
float in1 = 0;
float in2 = 0;
// Note: -1 value when disabled is the DAC DC offset
if (this.pulse1_dacEnabled)
{
if (this.pulse1Period > 0)
{
while (this.pulse1FreqTimer < 0)
{
this.pulse1FreqTimer += this.pulse1Period;
this.pulse1Pos++;
this.pulse1Pos &= 7;
this.updatePulse1Val();
}
}
if (this.enable1Out)
{
if (this.pulse1_outputLeft) in1 += this.pulse1Val;
if (this.pulse1_outputRight) in2 += this.pulse1Val;
}
}
if (this.pulse2_dacEnabled)
{
if (this.pulse2Period > 0)
{
while (this.pulse2FreqTimer < 0)
{
this.pulse2FreqTimer += this.pulse2Period;
this.pulse2Pos++;
this.pulse2Pos &= 7;
this.updatePulse2Val();
}
}
if (this.enable2Out)
{
if (this.pulse2_outputLeft) in1 += this.pulse2Val;
if (this.pulse2_outputRight) in2 += this.pulse2Val;
}
}
if (this.wave_dacEnabled)
{
if (this.wavePeriod > 0)
{
while (this.waveFreqTimer < 0)
{
this.waveFreqTimer += this.wavePeriod;
this.wavePos++;
if (this.wave_dimension)
{
this.wavePos &= 63;
}
else
{
this.wavePos &= 31;
}
this.updateWaveVal();
}
}
if (this.enable3Out)
{
if (this.wave_outputLeft) in1 += this.waveVal;
if (this.wave_outputRight) in2 += this.waveVal;
}
}
if (this.noise_dacEnabled)
{
if (this.noisePeriod > 0)
{
while (this.noiseFreqTimer < 0)
{
this.noiseFreqTimer += this.noisePeriod;
this.noisePos++;
this.noisePos &= 32767;
this.updateNoiseVal();
}
}
if (this.enable4Out)
{
if (this.noise_outputLeft) in1 += this.noiseVal;
if (this.noise_outputRight) in2 += this.noiseVal;
}
}
in1 *= this.leftMasterVolMul;
in2 *= this.rightMasterVolMul;
Out1 = (short)(in1 * PsgFactor);
Out2 = (short)(in2 * PsgFactor);
}
// The wave sample is visible to the CPU, so it's gotta be updated immediately
}
public uint PsgFactor = 8;
void update()
{
// this.fastForwardSound((int)this.pendingCycles);
this.pendingCycles = 0;
}
private void frameSequencerFrequencySweep()
{
// writeDebug("Frequency sweep")
int actualPeriod = this.pulse1_freqSweepPeriod;
if (actualPeriod == 0) actualPeriod = 8;
if (this.clockPulse1FreqSweep >= actualPeriod)
{
this.clockPulse1FreqSweep = 0;
if (this.freqSweepEnabled == true)
{
this.applyFrequencySweep();
}
// writeDebug("abs(Range): " + diff);
// writeDebug("Resulting frequency: " + this.pulse1_frequencyHz);
}
this.clockPulse1FreqSweep++;
}
private void applyFrequencySweep()
{
uint freq = (uint)((this.pulse1_frequencyUpper << 8) | this.pulse1_frequencyLower);
uint diff = freq >> this.pulse1_freqSweepShift;
uint newFreq = this.pulse1_freqSweepUp ? freq + diff : freq - diff;
freq = newFreq;
if (newFreq > 2047)
{
this.pulse1_enabled = false;
}
if (this.pulse1_freqSweepPeriod != 0 && this.pulse1_freqSweepShift != 0)
{
this.pulse1_frequencyLower = (int)(freq & 0xFF);
this.pulse1_frequencyUpper = (int)((freq >> 8) & 0xFF);
}
this.calcPulse1Period();
this.updatePulse1Val();
}
private void frameSequencerVolumeEnvelope()
{
this.ticksEnvelopePulse1--;
if (this.ticksEnvelopePulse1 <= 0)
{
if (this.pulse1_volumeEnvelopeSweep != 0)
{
if (this.pulse1_volumeEnvelopeUp == true)
{
if (this.pulse1_volume < 15)
{
this.pulse1_volume++;
}
}
else
{
if (this.pulse1_volume > 0)
{
this.pulse1_volume--;
}
}
this.updatePulse1Val();
}
this.ticksEnvelopePulse1 = this.pulse1_volumeEnvelopeSweep;
}
this.ticksEnvelopePulse2--;
if (this.ticksEnvelopePulse2 <= 0)
{
if (this.pulse2_volumeEnvelopeSweep != 0)
{
if (this.pulse2_volumeEnvelopeUp == true)
{
if (this.pulse2_volume < 15)
{
this.pulse2_volume++;
}
}
else
{
if (this.pulse2_volume > 0)
{
this.pulse2_volume--;
}
}
this.updatePulse2Val();
}
this.ticksEnvelopePulse2 = this.pulse2_volumeEnvelopeSweep;
}
this.ticksEnvelopeNoise--;
if (this.ticksEnvelopeNoise <= 0)
{
if (this.noise_volumeEnvelopeSweep != 0)
{
if (this.noise_volumeEnvelopeUp == true)
{
if (this.noise_volume < 15)
{
this.noise_volume++;
}
}
else
{
if (this.noise_volume > 0)
{
this.noise_volume--;
}
}
this.updateNoiseVal();
}
this.ticksEnvelopeNoise = this.noise_volumeEnvelopeSweep;
}
}
private void frameSequencerLength()
{
this.clockPulse1Length();
this.clockPulse2Length();
this.clockWaveLength();
this.clockNoiseLength();
}
private void clockPulse1Length()
{
if (this.pulse1_lengthEnable == true && this.pulse1_lengthCounter > 0)
{
this.pulse1_lengthCounter--;
if (this.pulse1_lengthCounter == 0)
{
this.pulse1_enabled = false;
this.updatePulse1Val();
}
}
}
private void clockPulse2Length()
{
if (this.pulse2_lengthEnable == true && this.pulse2_lengthCounter > 0)
{
this.pulse2_lengthCounter--;
if (this.pulse2_lengthCounter == 0)
{
this.pulse2_enabled = false;
this.updatePulse2Val();
}
}
}
private void clockWaveLength()
{
if (this.wave_lengthEnable == true && this.wave_lengthCounter > 0)
{
this.wave_lengthCounter--;
if (this.wave_lengthCounter == 0)
{
this.wave_enabled = false;
this.updateWaveVal();
}
}
}
private void clockNoiseLength()
{
if (this.noise_lengthEnable == true && this.noise_lengthCounter > 0)
{
this.noise_lengthCounter--;
if (this.noise_lengthCounter == 0)
{
this.noise_enabled = false;
this.updateNoiseVal();
}
}
}
public void WriteHwio8(uint addr, byte value)
{
if (this.enabled)
{
switch (addr)
{
// Pulse 1
case 0x60: // NR10
this.pulse1_freqSweepPeriod = (value & 0b01110000) >> 4; // in 128ths of a second (0-7)
this.pulse1_freqSweepUp = ((value >> 3) & 1) == 0; // 0 == Add, 1 = Sub
this.pulse1_freqSweepShift = (value & 0b111); // 0-7;
this.updatePulse1Val();
break;
case 0x62: // NR11
this.pulse1_width = value >> 6;
this.pulse1_lengthCounter = 64 - (value & 0b111111);
this.pulse1Val = (byte)PULSE_DUTY[this.pulse1_width][this.pulse1Pos];
this.updatePulse1Val();
break;
case 0x63: // NR12
{
bool newUp = ((value >> 3) & 1) == 1;
if (this.pulse1_enabled)
{
if (this.pulse1_volumeEnvelopeSweep == 0)
{
if (this.pulse1_volumeEnvelopeUp)
{
this.pulse1_volume += 1;
this.pulse1_volume &= 0xF;
}
else
{
this.pulse1_volume += 2;
this.pulse1_volume &= 0xF;
}
}
if (this.pulse1_volumeEnvelopeUp != newUp)
this.pulse1_volume = 0;
}
this.pulse1_volumeEnvelopeStart = (value >> 4) & 0xF;
this.pulse1_volumeEnvelopeUp = newUp;
this.pulse1_volumeEnvelopeSweep = value & 0b111;
this.pulse1_dacEnabled = (value & 0b11111000) != 0;
if (!this.pulse1_dacEnabled) this.pulse1_enabled = false;
this.updatePulse1Val();
}
break;
case 0x64: // NR13 Low bits
this.pulse1_frequencyLower = value;
this.calcPulse1Period();
this.updatePulse1Val();
break;
case 0x65: // NR14
this.pulse1_frequencyUpper = value & 0b111;
this.pulse1_lengthEnable = ((value >> 6) & 1) != 0;
// If the next step does not clock the length counter
if (
((this.frameSequencerStep + 1) & 7) == 1 ||
((this.frameSequencerStep + 1) & 7) == 3 ||
((this.frameSequencerStep + 1) & 7) == 5 ||
((this.frameSequencerStep + 1) & 7) == 7
)
{
this.clockPulse1Length();
}
if (((value >> 7) & 1) != 0)
{
this.pulse1_trigger();
}
this.calcPulse1Period();
this.updatePulse1Val();
break;
// Pulse 2
case 0x68: // NR21
this.pulse2_width = value >> 6;
this.pulse2_lengthCounter = 64 - (value & 0b111111);
this.pulse2Val = (byte)PULSE_DUTY[this.pulse2_width][this.pulse2Pos];
this.updatePulse2Val();
break;
case 0x69: // NR22
{
bool newUp = ((value >> 3) & 1) == 1;
if (this.pulse2_enabled)
{
if (this.pulse2_volumeEnvelopeSweep == 0)
{
if (this.pulse2_volumeEnvelopeUp)
{
this.pulse2_volume += 1;
this.pulse2_volume &= 0xF;
}
else
{
this.pulse2_volume += 2;
this.pulse2_volume &= 0xF;
}
}
if (this.pulse2_volumeEnvelopeUp != newUp)
this.pulse2_volume = 0;
}
this.pulse2_volumeEnvelopeStart = (value >> 4) & 0xF;
this.pulse2_volumeEnvelopeUp = newUp;
this.pulse2_volumeEnvelopeSweep = value & 0b111;
this.pulse2_dacEnabled = (value & 0b11111000) != 0;
if (!this.pulse2_dacEnabled) this.pulse2_enabled = false;
}
this.updatePulse2Val();
break;
case 0x6C: // NR23
this.pulse2_frequencyLower = value;
this.calcPulse2Period();
this.updatePulse2Val();
break;
case 0x6D: // NR24
this.pulse2_frequencyUpper = value & 0b111;
this.pulse2_lengthEnable = ((value >> 6) & 1) != 0;
// If the next step does not clock the length counter
if (
((this.frameSequencerStep + 1) & 7) == 1 ||
((this.frameSequencerStep + 1) & 7) == 3 ||
((this.frameSequencerStep + 1) & 7) == 5 ||
((this.frameSequencerStep + 1) & 7) == 7
)
{
this.clockPulse2Length();
}
if (((value >> 7) & 1) != 0)
{
this.pulse2_trigger();
}
this.calcPulse2Period();
this.updatePulse2Val();
break;
// Wave
case 0x70: // NR30
this.wave_dimension = BitTest(value, 5);
this.wave_bank = BitTest(value, 6);
this.wave_dacEnabled = BitTest(value, 7);
if (!this.wave_dacEnabled) this.wave_enabled = false;
this.updateWaveVal();
break;
case 0x72: // NR31
this.wave_lengthCounter = 256 - value;
this.updateWaveVal();
break;
case 0x73: // NR32
this.wave_volume = (value >> 5) & 0b11;
this.updateWaveVal();
break;
case 0x74: // NR33
this.wave_frequencyLower = value;
this.calcWavePeriod();
this.updateWaveVal();
break;
case 0x75: // NR34
this.wave_frequencyUpper = value & 0b111;
// If the next step does not clock the length counter
if (
((this.frameSequencerStep + 1) & 7) == 1 ||
((this.frameSequencerStep + 1) & 7) == 3 ||
((this.frameSequencerStep + 1) & 7) == 5 ||
((this.frameSequencerStep + 1) & 7) == 7
)
{
this.clockWaveLength();
}
if (((value >> 7) & 1) != 0)
{
this.wave_trigger();
this.wavePos = 0;
this.reloadWavePeriod();
}
this.wave_lengthEnable = ((value >> 6) & 1) != 0;
this.calcWavePeriod();
this.updateWaveVal();
break;
// Noise
case 0x78: // NR41
this.noise_lengthCounter = 64 - (value & 0b111111); // 6 bits
this.updateNoiseVal();
break;
case 0x79: // NR42
this.noise_volume = (value >> 4) & 0xF;
this.noise_volumeEnvelopeStart = (value >> 4) & 0xF;
this.noise_volumeEnvelopeUp = ((value >> 3) & 1) == 1;
this.noise_volumeEnvelopeSweep = value & 0b111;
this.noise_dacEnabled = (value & 0b11111000) != 0;
if (!this.noise_dacEnabled) this.noise_enabled = false;
this.updateNoiseVal();
break;
case 0x7C: // NR43
this.noise_shiftClockFrequency = (value >> 4) & 0xF;
this.noise_counterStep = ((value >> 3) & 1) != 0;
this.noise_divisorCode = (value & 0b111);
this.calcNoisePeriod();
this.updateNoiseVal();
break;
case 0x7D: // NR44
// If the next step does not clock the length counter
if (
((this.frameSequencerStep + 1) & 7) == 1 ||
((this.frameSequencerStep + 1) & 7) == 3 ||
((this.frameSequencerStep + 1) & 7) == 5 ||
((this.frameSequencerStep + 1) & 7) == 7
)
{
this.clockWaveLength();
}
if (((value >> 7) & 1) != 0)
{
this.noise_trigger();
this.noisePos = 0;
this.reloadNoisePeriod();
}
this.noise_lengthEnable = ((value >> 6) & 1) != 0;
this.updateNoiseVal();
break;
case 0x80: // NR50
this.vinLeftEnable = (value & BIT_7) != 0;
this.vinRightEnable = (value & BIT_3) != 0;
this.leftMasterVol = (value >> 4) & 0b111;
this.rightMasterVol = (value >> 0) & 0b111;
this.leftMasterVolMul = this.leftMasterVol / 7;
this.rightMasterVolMul = this.rightMasterVol / 7;
break;
// Panning
case 0x81: // NR51
this.noise_outputRight = (value & BIT_7) != 0;
this.wave_outputRight = (value & BIT_6) != 0;
this.pulse2_outputRight = (value & BIT_5) != 0;
this.pulse1_outputRight = (value & BIT_4) != 0;
this.noise_outputLeft = (value & BIT_3) != 0;
this.wave_outputLeft = (value & BIT_2) != 0;
this.pulse2_outputLeft = (value & BIT_1) != 0;
this.pulse1_outputLeft = (value & BIT_0) != 0;
break;
}
if (addr >= 0x90 && addr <= 0x9F)
{
addr -= 0x90;
if (!this.wave_bank) // Non-selected wave bank will be written to
{
this.wave_waveTable1[(addr * 2) + 0] = (byte)(value >> 4);
this.wave_waveTable1[(addr * 2) + 1] = (byte)(value & 0xF);
}
else
{
this.wave_waveTable0[(addr * 2) + 0] = (byte)(value >> 4);
this.wave_waveTable0[(addr * 2) + 1] = (byte)(value & 0xF);
}
}
else
{
this.soundRegisters[addr - 0x60] = value;
}
}
if (addr == 0x84)
{
// Control
if (((value >> 7) & 1) != 0)
{
// writeDebug("Enabled sound");
this.enabled = true;
this.frameSequencerStep = 0;
// Console.WriteLine("Enabled PSGs!");
}
else
{
// Console.WriteLine("Disabled PSGs...");
// Disable and write zeros on everything upon main disabling
this.noise_enabled = false;
this.wave_enabled = false;
this.pulse2_enabled = false;
this.pulse1_enabled = false;
this.noise_dacEnabled = false;
this.wave_dacEnabled = false;
this.pulse2_dacEnabled = false;
this.pulse1_dacEnabled = false;
this.WriteHwio8(0xFF10, 0);
this.WriteHwio8(0xFF11, 0);
this.WriteHwio8(0xFF12, 0);
this.WriteHwio8(0xFF13, 0);
this.WriteHwio8(0xFF14, 0);
this.WriteHwio8(0xFF16, 0);
this.WriteHwio8(0xFF17, 0);
this.WriteHwio8(0xFF18, 0);
this.WriteHwio8(0xFF19, 0);
this.WriteHwio8(0xFF1A, 0);
this.WriteHwio8(0xFF1B, 0);
this.WriteHwio8(0xFF1C, 0);
this.WriteHwio8(0xFF1D, 0);
this.WriteHwio8(0xFF1D, 0);
this.WriteHwio8(0xFF20, 0);
this.WriteHwio8(0xFF21, 0);
this.WriteHwio8(0xFF22, 0);
this.WriteHwio8(0xFF23, 0);
this.pulse1Pos = 0;
this.pulse2Pos = 0;
this.wavePos = 0;
this.noisePos = 0;
this.pulse1FreqTimer = 0;
this.pulse2FreqTimer = 0;
this.waveFreqTimer = 0;
this.noiseFreqTimer = 0;
for (int i = 0xFF10; i <= 0xFF25; i++)
{
this.soundRegisters[i - 0xFF10] = 0;
}
this.enabled = false;
}
}
this.update();
}
public byte ReadHwio8(uint addr)
{
if (addr >= 0x60 && addr <= 0x9F)
{
byte i = this.soundRegisters[addr - 0x60];
if (addr >= 0xFF27 && addr <= 0xFF2F) return 0xFF;
if (addr >= 0x90 && addr <= 0x9F)
{
uint tableAddr = addr - 0x90;
if (!this.wave_bank) // Non-selected wave bank will be written to
{
byte upper = this.wave_waveTable1[(tableAddr * 2) + 0];
byte lower = this.wave_waveTable1[(tableAddr * 2) + 1];
return (byte)((upper << 4) | lower);
}
else
{
byte upper = this.wave_waveTable0[(tableAddr * 2) + 0];
byte lower = this.wave_waveTable0[(tableAddr * 2) + 1];
return (byte)((upper << 4) | lower);
}
}
if (addr == 0x84)
{ // NR52
i = 0;
if (this.enabled) i |= (byte)BIT_7;
i |= 0b01110000;
if (this.noise_enabled && this.noise_dacEnabled) i |= (byte)BIT_3;
if (this.wave_enabled && this.wave_dacEnabled) i |= (byte)BIT_2;
if (this.pulse2_enabled && this.pulse2_dacEnabled) i |= (byte)BIT_1;
if (this.pulse1_enabled && this.pulse1_dacEnabled) i |= (byte)BIT_0;
return i;
}
switch (addr)
{
case 0x60: i |= 0x80; break; // NR10
case 0x62: i |= 0x3F; break; // NR11
case 0x63: i |= 0x00; break; // NR12
case 0x64: i |= 0xFF; break; // NR13
case 0x65: i |= 0xBF; break; // NR14
case 0x66: i |= 0xFF; break; // Unused
case 0x68: i |= 0x3F; break; // NR21
case 0x69: i |= 0x00; break; // NR22
case 0x6C: i |= 0xFF; break; // NR23
case 0x6D: i |= 0xBF; break; // NR24
case 0x70: i |= 0x7F; break; // NR30
case 0x72: i |= 0xFF; break; // NR31
case 0x73: i |= 0x9F; break; // NR32
case 0x74: i |= 0xFF; break; // NR33
case 0x75: i |= 0xBF; break; // NR34
case 0x76: i |= 0xFF; break; // Unused
case 0x78: i |= 0xFF; break; // NR41
case 0x79: i |= 0x00; break; // NR42
case 0x7C: i |= 0x00; break; // NR43
case 0x7E: i |= 0xBF; break; // NR44
case 0x80: i |= 0x00; break;
case 0x81: i |= 0x00; break;
case 0x82: i |= 0xFF; break;
}
return i;
}
// // PCM12
// if (addr == 0xFF76)
// {
// this.update();
// return (byte)(this.pulse1Val | (this.pulse2Val << 4));
// }
// // PCM34
// if (addr == 0xFF77)
// {
// this.update();
// return (byte)(this.waveVal | (this.noiseVal << 4));
// }
return 0xFF;
}
void reset()
{
this.enabled = false;
this.ticksEnvelopePulse1 = 0;
this.ticksEnvelopePulse2 = 0;
this.ticksEnvelopeNoise = 0;
this.clockPulse1FreqSweep = 0;
this.freqSweepEnabled = false;
this.frameSequencerStep = 0;
this.soundRegisters = new byte[64];
this.pulse1_enabled = false;
this.pulse1_width = 3;
this.pulse1_dacEnabled = false;
this.pulse1_lengthEnable = false;
this.pulse1_lengthCounter = 0;
this.pulse1_frequencyUpper = 0;
this.pulse1_frequencyLower = 0;
this.pulse1_volume = 0;
this.pulse1_volumeEnvelopeUp = false;
this.pulse1_volumeEnvelopeSweep = 4;
this.pulse1_volumeEnvelopeStart = 0;
this.pulse1_outputLeft = false;
this.pulse1_outputRight = false;
this.pulse1_freqSweepPeriod = 0;
this.pulse1_freqSweepUp = false;
this.pulse1_freqSweepShift = 0;
this.pulse1_updated = true;
this.pulse2_enabled = false;
this.pulse2_width = 3;
this.pulse2_dacEnabled = false;
this.pulse2_lengthEnable = false;
this.pulse2_lengthCounter = 0;
this.pulse2_frequencyUpper = 0;
this.pulse2_frequencyLower = 0;
this.pulse2_volume = 0;
this.pulse2_volumeEnvelopeUp = false;
this.pulse2_volumeEnvelopeSweep = 4;
this.pulse2_volumeEnvelopeStart = 0;
this.pulse2_outputLeft = false;
this.pulse2_outputRight = false;
this.pulse2_updated = true;
this.wave_enabled = false;
this.wave_dacEnabled = false;
this.wave_lengthEnable = true;
this.wave_lengthCounter = 0;
this.wave_frequencyUpper = 0;
this.wave_frequencyLower = 0;
this.wave_volume = 0;
this.wave_oldVolume = 0;
this.wave_waveTable0 = new byte[32];
this.wave_waveTable1 = new byte[32];
this.wave_outputLeft = false;
this.wave_outputRight = false;
this.noise_enabled = false;
this.noise_divisorCode = 0;
this.noise_lengthEnable = false;
this.noise_lengthCounter = 0;
this.noise_dacEnabled = false;
this.noise_volume = 0;
this.noise_volumeEnvelopeUp = false;
this.noise_volumeEnvelopeSweep = 4;
this.noise_volumeEnvelopeStart = 0;
this.noise_outputLeft = false;
this.noise_outputRight = false;
this.noise_shiftClockFrequency = 0;
this.noise_counterStep = false;
this.noise_envelopeSweep = 0;
this.pulse1Val = 0;
this.pulse2Val = 0;
this.waveVal = 0;
this.noiseVal = 0;
this.pulse1FreqTimer = 0;
this.pulse2FreqTimer = 0;
this.waveFreqTimer = 0;
this.noiseFreqTimer = 0;
this.updatePulse1Val();
this.updatePulse2Val();
this.updateWaveVal();
this.updateNoiseVal();
this.vinLeftEnable = false;
this.vinRightEnable = false;
this.leftMasterVol = 0;
this.rightMasterVol = 0;
this.leftMasterVolMul = 0;
this.rightMasterVolMul = 0;
}
}
}