commodore64: rewrote SID emulation, should be a lot more accurate
This commit is contained in:
parent
5926918f8b
commit
0223225388
|
@ -4,14 +4,6 @@ using System.Text;
|
|||
|
||||
namespace BizHawk.Emulation.Computers.Commodore64
|
||||
{
|
||||
public enum SidEnvelopeState
|
||||
{
|
||||
Disabled,
|
||||
Attack,
|
||||
Decay,
|
||||
Release
|
||||
}
|
||||
|
||||
public enum SidMode
|
||||
{
|
||||
Sid6581,
|
||||
|
@ -20,24 +12,102 @@ namespace BizHawk.Emulation.Computers.Commodore64
|
|||
|
||||
public class VoiceRegs
|
||||
{
|
||||
public int ATK;
|
||||
public int DCY;
|
||||
public int ENV;
|
||||
public int F;
|
||||
public bool FILT;
|
||||
public bool GATE;
|
||||
public bool NOISE;
|
||||
public int OSC;
|
||||
public int PW;
|
||||
public int RLS;
|
||||
public bool RMOD;
|
||||
public bool SAW;
|
||||
public int SR;
|
||||
public bool SQU;
|
||||
public int STN;
|
||||
public bool SYNC;
|
||||
public bool TEST;
|
||||
public bool TRI;
|
||||
private EnvelopeGenerator envelope;
|
||||
private WaveformGenerator generator;
|
||||
private WaveformGenerator syncSource;
|
||||
|
||||
private short outputEnvLatch;
|
||||
private short outputWaveLatch;
|
||||
|
||||
public VoiceRegs()
|
||||
{
|
||||
envelope = new EnvelopeGenerator();
|
||||
generator = new WaveformGenerator(WaveformCalculator.BuildTable());
|
||||
}
|
||||
|
||||
public void Clock()
|
||||
{
|
||||
generator.Clock();
|
||||
envelope.Clock();
|
||||
|
||||
outputWaveLatch = generator.Output(syncSource);
|
||||
outputEnvLatch = envelope.Output();
|
||||
}
|
||||
|
||||
public short Output()
|
||||
{
|
||||
int sample = outputWaveLatch;
|
||||
int velocity = outputEnvLatch;
|
||||
int result = (sample * velocity) >> 4;
|
||||
result -= 32768;
|
||||
if (result > 32767)
|
||||
result = 32767;
|
||||
else if (result < -32768)
|
||||
result = -32768;
|
||||
return (short)result;
|
||||
}
|
||||
|
||||
public byte ReadEnv()
|
||||
{
|
||||
return (byte)(outputEnvLatch >> 4);
|
||||
}
|
||||
|
||||
public int ReadFreq()
|
||||
{
|
||||
return generator.ReadFreq();
|
||||
}
|
||||
|
||||
public byte ReadOsc()
|
||||
{
|
||||
return (byte)(outputWaveLatch >> 4);
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
generator.Reset();
|
||||
envelope.Reset();
|
||||
}
|
||||
|
||||
public void SetSyncSource(VoiceRegs source)
|
||||
{
|
||||
syncSource = source.generator;
|
||||
}
|
||||
|
||||
public void WriteAttackDecay(byte val)
|
||||
{
|
||||
envelope.WriteAttackDecay(val);
|
||||
}
|
||||
|
||||
public void WriteControl(byte val)
|
||||
{
|
||||
generator.WriteControl(val);
|
||||
envelope.WriteControl(val);
|
||||
}
|
||||
|
||||
public void WriteFreqLo(byte val)
|
||||
{
|
||||
generator.WriteFreqLo(val);
|
||||
}
|
||||
|
||||
public void WriteFreqHi(byte val)
|
||||
{
|
||||
generator.WriteFreqHi(val);
|
||||
}
|
||||
|
||||
public void WritePWLo(byte val)
|
||||
{
|
||||
generator.WritePWLo(val);
|
||||
}
|
||||
|
||||
public void WritePWHi(byte val)
|
||||
{
|
||||
generator.WritePWHi(val);
|
||||
}
|
||||
|
||||
public void WriteSustainRelease(byte val)
|
||||
{
|
||||
envelope.WriteSustainRelease(val);
|
||||
}
|
||||
}
|
||||
|
||||
public class SidRegs
|
||||
|
@ -45,6 +115,7 @@ namespace BizHawk.Emulation.Computers.Commodore64
|
|||
public bool BP;
|
||||
public bool D3;
|
||||
public int FC;
|
||||
public bool[] FILT = new bool[3];
|
||||
public bool FILTEX;
|
||||
public bool HP;
|
||||
public bool LP;
|
||||
|
@ -58,13 +129,13 @@ namespace BizHawk.Emulation.Computers.Commodore64
|
|||
public SidRegs()
|
||||
{
|
||||
Voices = new VoiceRegs[3];
|
||||
for (int i = 0; i < 3; i++)
|
||||
Voices[i] = new VoiceRegs();
|
||||
Voices[0] = new VoiceRegs();
|
||||
Voices[1] = new VoiceRegs();
|
||||
Voices[2] = new VoiceRegs();
|
||||
|
||||
// power on state
|
||||
Voices[0].SR = 0x7FFFFF;
|
||||
Voices[1].SR = 0x7FFFFF;
|
||||
Voices[2].SR = 0x7FFFFF;
|
||||
Voices[0].SetSyncSource(Voices[2]);
|
||||
Voices[1].SetSyncSource(Voices[0]);
|
||||
Voices[2].SetSyncSource(Voices[1]);
|
||||
}
|
||||
|
||||
public byte this[int addr]
|
||||
|
@ -78,84 +149,17 @@ namespace BizHawk.Emulation.Computers.Commodore64
|
|||
addr &= 0x1F;
|
||||
switch (addr)
|
||||
{
|
||||
case 0x00:
|
||||
case 0x07:
|
||||
case 0x0E:
|
||||
result = Voices[addr / 7].F & 0xFF;
|
||||
break;
|
||||
case 0x01:
|
||||
case 0x08:
|
||||
case 0x0F:
|
||||
result = (Voices[addr / 7].F & 0xFF00) >> 8;
|
||||
break;
|
||||
case 0x02:
|
||||
case 0x09:
|
||||
case 0x10:
|
||||
result = Voices[addr / 7].PW & 0xFF;
|
||||
break;
|
||||
case 0x03:
|
||||
case 0x0A:
|
||||
case 0x11:
|
||||
result = (Voices[addr / 7].PW & 0x0F00) >> 8;
|
||||
break;
|
||||
case 0x04:
|
||||
case 0x0B:
|
||||
case 0x12:
|
||||
index = addr / 7;
|
||||
result = Voices[index].GATE ? 0x01 : 0x00;
|
||||
result |= Voices[index].SYNC ? 0x02 : 0x00;
|
||||
result |= Voices[index].RMOD ? 0x04 : 0x00;
|
||||
result |= Voices[index].TEST ? 0x08 : 0x00;
|
||||
result |= Voices[index].TRI ? 0x10 : 0x00;
|
||||
result |= Voices[index].SAW ? 0x20 : 0x00;
|
||||
result |= Voices[index].SQU ? 0x40 : 0x00;
|
||||
result |= Voices[index].NOISE ? 0x80 : 0x00;
|
||||
break;
|
||||
case 0x05:
|
||||
case 0x0C:
|
||||
case 0x13:
|
||||
index = addr / 7;
|
||||
result = (Voices[index].ATK & 0xF) << 4;
|
||||
result |= Voices[index].DCY & 0xF;
|
||||
break;
|
||||
case 0x06:
|
||||
case 0x0D:
|
||||
case 0x14:
|
||||
index = addr / 7;
|
||||
result = (Voices[index].STN & 0xF) << 4;
|
||||
result |= Voices[index].RLS & 0xF;
|
||||
break;
|
||||
case 0x15:
|
||||
result = FC & 0x7;
|
||||
break;
|
||||
case 0x16:
|
||||
result = (FC & 0x7F8) >> 3;
|
||||
break;
|
||||
case 0x17:
|
||||
result = Voices[0].FILT ? 0x01 : 0x00;
|
||||
result |= Voices[1].FILT ? 0x02 : 0x00;
|
||||
result |= Voices[2].FILT ? 0x04 : 0x00;
|
||||
result |= FILTEX ? 0x08 : 0x00;
|
||||
result |= (RES & 0xF) << 4;
|
||||
break;
|
||||
case 0x18:
|
||||
result = (VOL & 0xF);
|
||||
result |= LP ? 0x10 : 0x00;
|
||||
result |= BP ? 0x20 : 0x00;
|
||||
result |= HP ? 0x40 : 0x00;
|
||||
result |= D3 ? 0x80 : 0x00;
|
||||
break;
|
||||
case 0x19:
|
||||
result = POTX ;
|
||||
result = POTX;
|
||||
break;
|
||||
case 0x1A:
|
||||
result = POTY;
|
||||
break;
|
||||
case 0x1B:
|
||||
result = Voices[2].OSC >> 4;
|
||||
result = Voices[2].ReadOsc();
|
||||
break;
|
||||
case 0x1C:
|
||||
result = Voices[2].ENV;
|
||||
result = Voices[2].ReadEnv();
|
||||
break;
|
||||
default:
|
||||
result = 0;
|
||||
|
@ -175,56 +179,43 @@ namespace BizHawk.Emulation.Computers.Commodore64
|
|||
case 0x07:
|
||||
case 0x0E:
|
||||
index = addr / 7;
|
||||
Voices[index].F &= 0xFF00;
|
||||
Voices[index].F |= val;
|
||||
Voices[index].WriteFreqLo(value);
|
||||
break;
|
||||
case 0x01:
|
||||
case 0x08:
|
||||
case 0x0F:
|
||||
index = addr / 7;
|
||||
Voices[index].F &= 0xFF;
|
||||
Voices[index].F |= val << 8;
|
||||
Voices[index].WriteFreqHi(value);
|
||||
break;
|
||||
case 0x02:
|
||||
case 0x09:
|
||||
case 0x10:
|
||||
index = addr / 7;
|
||||
Voices[index].PW &= 0x0F00;
|
||||
Voices[index].PW |= val;
|
||||
Voices[index].WritePWLo(value);
|
||||
break;
|
||||
case 0x03:
|
||||
case 0x0A:
|
||||
case 0x11:
|
||||
index = addr / 7;
|
||||
Voices[index].PW &= 0xFF;
|
||||
Voices[index].PW |= (val & 0x0F) << 8;
|
||||
Voices[index].WritePWHi(value);
|
||||
break;
|
||||
case 0x04:
|
||||
case 0x0B:
|
||||
case 0x12:
|
||||
index = addr / 7;
|
||||
Voices[index].GATE = ((val & 0x01) != 0x00);
|
||||
Voices[index].SYNC = ((val & 0x02) != 0x00);
|
||||
Voices[index].RMOD = ((val & 0x04) != 0x00);
|
||||
Voices[index].TEST = ((val & 0x08) != 0x00);
|
||||
Voices[index].TRI = ((val & 0x10) != 0x00);
|
||||
Voices[index].SAW = ((val & 0x20) != 0x00);
|
||||
Voices[index].SQU = ((val & 0x40) != 0x00);
|
||||
Voices[index].NOISE = ((val & 0x80) != 0x00);
|
||||
Voices[index].WriteControl(value);
|
||||
break;
|
||||
case 0x05:
|
||||
case 0x0C:
|
||||
case 0x13:
|
||||
index = addr / 7;
|
||||
Voices[index].ATK = (val >> 4) & 0xF;
|
||||
Voices[index].DCY = val & 0xF;
|
||||
Voices[index].WriteAttackDecay(value);
|
||||
break;
|
||||
case 0x06:
|
||||
case 0x0D:
|
||||
case 0x14:
|
||||
index = addr / 7;
|
||||
Voices[index].STN = (val >> 4) & 0xF;
|
||||
Voices[index].RLS = val & 0xF;
|
||||
Voices[index].WriteSustainRelease(value);
|
||||
break;
|
||||
case 0x15:
|
||||
FC &= 0x7F8;
|
||||
|
@ -235,9 +226,9 @@ namespace BizHawk.Emulation.Computers.Commodore64
|
|||
FC |= val << 3;
|
||||
break;
|
||||
case 0x17:
|
||||
Voices[0].FILT = ((val & 0x01) != 0x00);
|
||||
Voices[1].FILT = ((val & 0x02) != 0x00);
|
||||
Voices[2].FILT = ((val & 0x04) != 0x00);
|
||||
FILT[0] = ((val & 0x01) != 0x00);
|
||||
FILT[1] = ((val & 0x02) != 0x00);
|
||||
FILT[2] = ((val & 0x04) != 0x00);
|
||||
FILTEX = ((val & 0x08) != 0x00);
|
||||
RES = (val >> 4);
|
||||
break;
|
||||
|
@ -248,18 +239,6 @@ namespace BizHawk.Emulation.Computers.Commodore64
|
|||
HP = ((val & 0x40) != 0x00);
|
||||
D3 = ((val & 0x80) != 0x00);
|
||||
break;
|
||||
case 0x19:
|
||||
POTX = val;
|
||||
break;
|
||||
case 0x1A:
|
||||
POTY = val;
|
||||
break;
|
||||
case 0x1B:
|
||||
Voices[2].OSC = val << 4;
|
||||
break;
|
||||
case 0x1C:
|
||||
Voices[2].ENV = val;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -268,18 +247,6 @@ namespace BizHawk.Emulation.Computers.Commodore64
|
|||
|
||||
public partial class Sid : ISoundProvider
|
||||
{
|
||||
private int[] envRateIndex = {
|
||||
9, 32, 63, 95,
|
||||
149, 220, 267, 313,
|
||||
392, 977, 1954, 3126,
|
||||
3907, 11720, 19532, 31251
|
||||
};
|
||||
private int[] sustainLevels = {
|
||||
0x00, 0x11, 0x22, 0x33,
|
||||
0x44, 0x55, 0x66, 0x77,
|
||||
0x88, 0x99, 0xAA, 0xBB,
|
||||
0xCC, 0xDD, 0xEE, 0xFF
|
||||
};
|
||||
private int[] syncIndex = { 2, 0, 1 };
|
||||
|
||||
private VoiceRegs[] voices;
|
||||
|
@ -289,16 +256,8 @@ namespace BizHawk.Emulation.Computers.Commodore64
|
|||
|
||||
public int clock;
|
||||
public int cyclesPerSample;
|
||||
public bool[] envEnable = new bool[3];
|
||||
public int[] envExp = new int[3];
|
||||
public int[] envExpCounter = new int[3];
|
||||
public int[] envRate = new int[3];
|
||||
public int[] envRateCounter = new int[3];
|
||||
public SidEnvelopeState[] envState = new SidEnvelopeState[3];
|
||||
public bool[] lastGate = new bool[3];
|
||||
public int output;
|
||||
public SidRegs regs;
|
||||
public int[] waveClock = new int[3];
|
||||
|
||||
public Sid(Region newRegion, int sampleRate)
|
||||
{
|
||||
|
@ -328,27 +287,6 @@ namespace BizHawk.Emulation.Computers.Commodore64
|
|||
voices = regs.Voices;
|
||||
}
|
||||
|
||||
private short Mix(int input, int volume, short mixSource)
|
||||
{
|
||||
// logarithmic volume (probably inaccurate)
|
||||
int logVolume = volume * 256;
|
||||
logVolume = (int)Math.Sqrt(logVolume);
|
||||
|
||||
// combine the volumes together 1:1
|
||||
volume = (volume + logVolume) >> 1;
|
||||
|
||||
input &= 0xFFF;
|
||||
input -= 0x800;
|
||||
input *= volume;
|
||||
input /= 255;
|
||||
input += mixSource;
|
||||
if (input > 32767)
|
||||
input = 32767;
|
||||
else if (input < -32768)
|
||||
input = -32768;
|
||||
return (short)input;
|
||||
}
|
||||
|
||||
public byte Peek(int addr)
|
||||
{
|
||||
return regs[addr & 0x1F];
|
||||
|
@ -356,31 +294,10 @@ namespace BizHawk.Emulation.Computers.Commodore64
|
|||
|
||||
public void PerformCycle()
|
||||
{
|
||||
// accumulator is 24 bits
|
||||
clock = (clock + 1) & 0xFFFFFF;
|
||||
ProcessAccumulator(0);
|
||||
ProcessAccumulator(1);
|
||||
ProcessAccumulator(2);
|
||||
|
||||
// process each voice
|
||||
ProcessVoice(0);
|
||||
ProcessVoice(1);
|
||||
ProcessVoice(2);
|
||||
|
||||
// process voices again for best hard sync
|
||||
if (voices[1].SYNC)
|
||||
ProcessVoice(0);
|
||||
if (voices[2].SYNC)
|
||||
ProcessVoice(1);
|
||||
if (voices[0].SYNC)
|
||||
ProcessVoice(2);
|
||||
|
||||
// process each envelope
|
||||
ProcessEnvelope(0);
|
||||
ProcessEnvelope(1);
|
||||
ProcessEnvelope(2);
|
||||
|
||||
// submit sample to soundprovider
|
||||
voices[0].Clock();
|
||||
voices[1].Clock();
|
||||
voices[2].Clock();
|
||||
SubmitSample();
|
||||
|
||||
// query pots every 512 cycles
|
||||
|
@ -389,6 +306,8 @@ namespace BizHawk.Emulation.Computers.Commodore64
|
|||
regs.POTX = ReadPotX() & 0xFF;
|
||||
regs.POTY = ReadPotY() & 0xFF;
|
||||
}
|
||||
|
||||
clock = (clock + 1) & 0xFFFFFF;
|
||||
}
|
||||
|
||||
public void Poke(int addr, byte val)
|
||||
|
@ -396,188 +315,6 @@ namespace BizHawk.Emulation.Computers.Commodore64
|
|||
regs[addr & 0x1F] = val;
|
||||
}
|
||||
|
||||
private void ProcessAccumulator(int index)
|
||||
{
|
||||
// test bit resets the oscillator
|
||||
if (voices[index].TEST)
|
||||
{
|
||||
waveClock[index] = 0x000000;
|
||||
voices[index].SR = 0x7FFFFF;
|
||||
}
|
||||
else
|
||||
{
|
||||
int lastWaveClock = waveClock[index];
|
||||
|
||||
// increment wave clock
|
||||
waveClock[index] = (waveClock[index] + voices[index].F) & 0x00FFFFFF;
|
||||
|
||||
// process shift register if needed
|
||||
if ((lastWaveClock & 0x100000) != (waveClock[index] & 0x100000))
|
||||
ProcessShiftRegister(index);
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessEnvelope(int index)
|
||||
{
|
||||
envRateCounter[index] = (envRateCounter[index] + 1) & 0xFFFF;
|
||||
if (envRateCounter[index] == envRate[index])
|
||||
{
|
||||
envRateCounter[index] = 0;
|
||||
if (envState[index] != SidEnvelopeState.Disabled)
|
||||
{
|
||||
envExpCounter[index] = (envExpCounter[index] + 1) & 0xFF;
|
||||
if (envExpCounter[index] == 0)
|
||||
envState[index] = SidEnvelopeState.Disabled;
|
||||
|
||||
if (envExpCounter[index] == envExp[index])
|
||||
{
|
||||
switch (envState[index])
|
||||
{
|
||||
case SidEnvelopeState.Attack:
|
||||
if (voices[index].ENV < 0xFF)
|
||||
voices[index].ENV++;
|
||||
if (voices[index].ENV == 0xFF)
|
||||
{
|
||||
envState[index] = SidEnvelopeState.Decay;
|
||||
UpdateEnvelopeRateCounter(index);
|
||||
}
|
||||
break;
|
||||
case SidEnvelopeState.Decay:
|
||||
if (voices[index].ENV > sustainLevels[voices[index].STN] && voices[index].ENV > 0)
|
||||
voices[index].ENV--;
|
||||
break;
|
||||
case SidEnvelopeState.Release:
|
||||
if (voices[index].ENV > 0)
|
||||
voices[index].ENV--;
|
||||
if (voices[index].ENV == 0)
|
||||
{
|
||||
envState[index] = SidEnvelopeState.Disabled;
|
||||
UpdateEnvelopeRateCounter(index);
|
||||
}
|
||||
break;
|
||||
}
|
||||
envExpCounter[index] = 0;
|
||||
ProcessEnvelopeExpCounter(index);
|
||||
}
|
||||
else if (envState[index] == SidEnvelopeState.Attack)
|
||||
{
|
||||
ProcessEnvelopeExpCounter(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void ProcessEnvelopeExpCounter(int index)
|
||||
{
|
||||
switch (voices[index].ENV)
|
||||
{
|
||||
case 0xFF:
|
||||
envExp[index] = 1;
|
||||
break;
|
||||
case 0x5D:
|
||||
envExp[index] = 2;
|
||||
break;
|
||||
case 0x36:
|
||||
envExp[index] = 4;
|
||||
break;
|
||||
case 0x1A:
|
||||
envExp[index] = 8;
|
||||
break;
|
||||
case 0x0E:
|
||||
envExp[index] = 16;
|
||||
break;
|
||||
case 0x06:
|
||||
envExp[index] = 30;
|
||||
break;
|
||||
case 0x00:
|
||||
envExp[index] = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessShiftRegister(int index)
|
||||
{
|
||||
int newBit = ((voices[index].SR >> 22) ^ (voices[index].SR >> 17)) & 0x1;
|
||||
voices[index].SR = ((voices[index].SR << 1) | newBit) & 0x7FFFFF;
|
||||
}
|
||||
|
||||
private void ProcessVoice(int index)
|
||||
{
|
||||
int triOutput;
|
||||
int sawOutput;
|
||||
int squOutput;
|
||||
int noiseOutput;
|
||||
int finalOutput = 0x00000FFF;
|
||||
bool outputEnabled = false;
|
||||
|
||||
// triangle waveform
|
||||
if (voices[index].TRI)
|
||||
{
|
||||
triOutput = (waveClock[index] >> 12) & 0xFFF;
|
||||
if (voices[index].SYNC)
|
||||
triOutput ^= voices[syncIndex[index]].OSC & 0x800;
|
||||
|
||||
if ((triOutput & 0x800) != 0x000)
|
||||
triOutput ^= 0x7FF;
|
||||
|
||||
triOutput &= 0x7FF;
|
||||
triOutput <<= 1;
|
||||
finalOutput &= triOutput;
|
||||
outputEnabled = true;
|
||||
}
|
||||
|
||||
// saw waveform
|
||||
if (voices[index].SAW)
|
||||
{
|
||||
sawOutput = (waveClock[index] >> 12) & 0xFFF;
|
||||
finalOutput &= sawOutput;
|
||||
outputEnabled = true;
|
||||
}
|
||||
|
||||
// square waveform
|
||||
if (voices[index].SQU)
|
||||
{
|
||||
if (voices[index].TEST)
|
||||
{
|
||||
squOutput = 0xFFF;
|
||||
}
|
||||
else
|
||||
{
|
||||
squOutput = (waveClock[index] >> 12) >= voices[index].PW ? 0xFFF : 0x000;
|
||||
}
|
||||
finalOutput &= squOutput;
|
||||
outputEnabled = true;
|
||||
}
|
||||
|
||||
// noise waveform
|
||||
if (voices[index].NOISE)
|
||||
{
|
||||
// shift register information is from reSID
|
||||
int sr = voices[index].SR;
|
||||
noiseOutput = sr & 0x100000 >> 9;
|
||||
noiseOutput |= sr & 0x040000 >> 8;
|
||||
noiseOutput |= sr & 0x004000 >> 5;
|
||||
noiseOutput |= sr & 0x000800 >> 3;
|
||||
noiseOutput |= sr & 0x000200 >> 2;
|
||||
noiseOutput |= sr & 0x000020 << 1;
|
||||
noiseOutput |= sr & 0x000004 << 3;
|
||||
noiseOutput |= sr & 0x000001 << 4;
|
||||
finalOutput &= noiseOutput;
|
||||
outputEnabled = true;
|
||||
|
||||
// other waveforms write into the shift register
|
||||
if (voices[index].SQU || voices[index].TRI || voices[index].SAW)
|
||||
WriteShiftRegister(index, finalOutput);
|
||||
}
|
||||
|
||||
// write to internal reg
|
||||
if (outputEnabled)
|
||||
voices[index].OSC = finalOutput;
|
||||
else
|
||||
voices[index].OSC = 0x000;
|
||||
}
|
||||
|
||||
public byte Read(ushort addr)
|
||||
{
|
||||
addr &= 0x1F;
|
||||
|
@ -594,71 +331,11 @@ namespace BizHawk.Emulation.Computers.Commodore64
|
|||
}
|
||||
}
|
||||
|
||||
private void UpdateEnvelopeRateCounter(int index)
|
||||
{
|
||||
switch (envState[index])
|
||||
{
|
||||
case SidEnvelopeState.Attack:
|
||||
envRate[index] = envRateIndex[voices[index].ATK] / 3;
|
||||
break;
|
||||
case SidEnvelopeState.Decay:
|
||||
envRate[index] = envRateIndex[voices[index].DCY];
|
||||
break;
|
||||
case SidEnvelopeState.Release:
|
||||
envRate[index] = envRateIndex[voices[index].RLS];
|
||||
break;
|
||||
}
|
||||
ProcessEnvelopeExpCounter(index);
|
||||
}
|
||||
|
||||
public void Write(ushort addr, byte val)
|
||||
{
|
||||
int index;
|
||||
bool gate;
|
||||
|
||||
addr &= 0x1F;
|
||||
switch (addr)
|
||||
{
|
||||
case 0x04:
|
||||
case 0x0B:
|
||||
case 0x12:
|
||||
// set control
|
||||
index = addr / 7;
|
||||
gate = lastGate[index];
|
||||
regs[addr] = val;
|
||||
lastGate[index] = voices[index].GATE;
|
||||
if (!gate && lastGate[index])
|
||||
{
|
||||
envExpCounter[index] = 0;
|
||||
envState[index] = SidEnvelopeState.Attack;
|
||||
voices[index].ENV = 0;
|
||||
envRateCounter[index] = 0;
|
||||
UpdateEnvelopeRateCounter(index);
|
||||
ProcessEnvelopeExpCounter(index);
|
||||
}
|
||||
else if (gate && !lastGate[index])
|
||||
{
|
||||
envExpCounter[index] = 0;
|
||||
envState[index] = SidEnvelopeState.Release;
|
||||
UpdateEnvelopeRateCounter(index);
|
||||
}
|
||||
break;
|
||||
case 0x05:
|
||||
case 0x0C:
|
||||
case 0x13:
|
||||
// set attack/decay
|
||||
index = addr / 7;
|
||||
regs[addr] = val;
|
||||
UpdateEnvelopeRateCounter(index);
|
||||
break;
|
||||
case 0x06:
|
||||
case 0x0D:
|
||||
case 0x14:
|
||||
// set sustain/release
|
||||
index = addr / 7;
|
||||
regs[addr] = val;
|
||||
UpdateEnvelopeRateCounter(index);
|
||||
break;
|
||||
case 0x19:
|
||||
case 0x1A:
|
||||
case 0x1B:
|
||||
|
@ -670,19 +347,5 @@ namespace BizHawk.Emulation.Computers.Commodore64
|
|||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void WriteShiftRegister(int index, int sample)
|
||||
{
|
||||
voices[index].SR &=
|
||||
~((1 << 20) | (1 << 18) | (1 << 14) | (1 << 11) | (1 << 9) | (1 << 5) | (1 << 2) | (1 << 0)) |
|
||||
((sample & 0x800) << 9) |
|
||||
((sample & 0x400) << 8) |
|
||||
((sample & 0x200) << 5) |
|
||||
((sample & 0x100) << 3) |
|
||||
((sample & 0x080) << 2) |
|
||||
((sample & 0x040) >> 1) |
|
||||
((sample & 0x020) >> 3) |
|
||||
((sample & 0x010) >> 4);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,204 @@ using System.Text;
|
|||
|
||||
namespace BizHawk.Emulation.Computers.Commodore64
|
||||
{
|
||||
|
||||
// constants for the EnvelopeGenerator and calculation
|
||||
// methods come from the libsidplayfp residfp library.
|
||||
|
||||
class EnvelopeGenerator
|
||||
{
|
||||
enum State
|
||||
{
|
||||
Attack, DecaySustain, Release
|
||||
}
|
||||
|
||||
static int[] adsrTable = new int[]
|
||||
{
|
||||
0x7F00, 0x0006, 0x003C, 0x0330,
|
||||
0x20C0, 0x6755, 0x3800, 0x500E,
|
||||
0x1212, 0x0222, 0x1848, 0x59B8,
|
||||
0x3840, 0x77E2, 0x7625, 0x0A93
|
||||
};
|
||||
|
||||
int attack;
|
||||
int decay;
|
||||
byte envelopeCounter;
|
||||
bool envelopePipeline;
|
||||
int exponentialCounter;
|
||||
int exponentialCounterPeriod;
|
||||
bool gate;
|
||||
bool holdZero;
|
||||
int lfsr;
|
||||
int rate;
|
||||
int release;
|
||||
State state;
|
||||
int sustain;
|
||||
|
||||
public EnvelopeGenerator()
|
||||
{
|
||||
Reset();
|
||||
}
|
||||
|
||||
public void Clock()
|
||||
{
|
||||
if (envelopePipeline)
|
||||
{
|
||||
--envelopeCounter;
|
||||
envelopePipeline = false;
|
||||
SetExponentialCounter();
|
||||
}
|
||||
|
||||
if (lfsr != rate)
|
||||
{
|
||||
int feedback = ((lfsr >> 14) ^ (lfsr >> 13)) & 0x01;
|
||||
lfsr = ((lfsr << 1) & 0x7FFF) | feedback;
|
||||
return;
|
||||
}
|
||||
|
||||
lfsr = 0x7FFF;
|
||||
|
||||
if ((state == State.Attack) || (++exponentialCounter == exponentialCounterPeriod))
|
||||
{
|
||||
exponentialCounter = 0;
|
||||
if (holdZero)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
switch (state)
|
||||
{
|
||||
case State.Attack:
|
||||
++envelopeCounter;
|
||||
if (envelopeCounter == 0xFF)
|
||||
{
|
||||
state = State.DecaySustain;
|
||||
rate = adsrTable[decay];
|
||||
}
|
||||
break;
|
||||
case State.DecaySustain:
|
||||
if (envelopeCounter == ((sustain << 4) | sustain))
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (exponentialCounterPeriod != 1)
|
||||
{
|
||||
envelopePipeline = true;
|
||||
return;
|
||||
}
|
||||
--envelopeCounter;
|
||||
break;
|
||||
case State.Release:
|
||||
if (exponentialCounterPeriod != 1)
|
||||
{
|
||||
envelopePipeline = true;
|
||||
return;
|
||||
}
|
||||
--envelopeCounter;
|
||||
break;
|
||||
}
|
||||
|
||||
SetExponentialCounter();
|
||||
}
|
||||
}
|
||||
|
||||
public short Output()
|
||||
{
|
||||
return envelopeCounter;
|
||||
}
|
||||
|
||||
public byte ReadEnv()
|
||||
{
|
||||
return envelopeCounter;
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
envelopeCounter = 0;
|
||||
envelopePipeline = false;
|
||||
attack = 0;
|
||||
decay = 0;
|
||||
sustain = 0;
|
||||
release = 0;
|
||||
gate = false;
|
||||
lfsr = 0x7FFF;
|
||||
exponentialCounter = 0;
|
||||
exponentialCounterPeriod = 1;
|
||||
state = State.Release;
|
||||
rate = adsrTable[release];
|
||||
holdZero = true;
|
||||
}
|
||||
|
||||
private void SetExponentialCounter()
|
||||
{
|
||||
switch (envelopeCounter)
|
||||
{
|
||||
case 0xFF:
|
||||
exponentialCounterPeriod = 1;
|
||||
break;
|
||||
case 0x5D:
|
||||
exponentialCounterPeriod = 2;
|
||||
break;
|
||||
case 0x36:
|
||||
exponentialCounterPeriod = 4;
|
||||
break;
|
||||
case 0x1A:
|
||||
exponentialCounterPeriod = 8;
|
||||
break;
|
||||
case 0x0E:
|
||||
exponentialCounterPeriod = 16;
|
||||
break;
|
||||
case 0x06:
|
||||
exponentialCounterPeriod = 30;
|
||||
break;
|
||||
case 0x00:
|
||||
exponentialCounterPeriod = 1;
|
||||
holdZero = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void WriteAttackDecay(byte attackDecay)
|
||||
{
|
||||
attack = (attackDecay >> 4) & 0x0F;
|
||||
decay = attackDecay & 0x0F;
|
||||
if (state == State.Attack)
|
||||
{
|
||||
rate = adsrTable[attack];
|
||||
}
|
||||
else if (state == State.DecaySustain)
|
||||
{
|
||||
rate = adsrTable[decay];
|
||||
}
|
||||
}
|
||||
|
||||
public void WriteControl(byte control)
|
||||
{
|
||||
bool gateNext = ((control & 0x01) != 0);
|
||||
|
||||
if (!gate && gateNext)
|
||||
{
|
||||
state = State.Attack;
|
||||
rate = adsrTable[attack];
|
||||
holdZero = false;
|
||||
envelopePipeline = false;
|
||||
}
|
||||
else if (gate && !gateNext)
|
||||
{
|
||||
state = State.Release;
|
||||
rate = adsrTable[release];
|
||||
}
|
||||
|
||||
gate = gateNext;
|
||||
}
|
||||
|
||||
public void WriteSustainRelease(byte sustainRelease)
|
||||
{
|
||||
sustain = (sustainRelease >> 4) & 0x0F;
|
||||
release = sustainRelease & 0x0F;
|
||||
if (state == State.Release)
|
||||
{
|
||||
rate = adsrTable[release];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -78,24 +78,31 @@ namespace BizHawk.Emulation.Computers.Commodore64
|
|||
{
|
||||
if (sampleCounter == 0)
|
||||
{
|
||||
short output;
|
||||
output = Mix(voices[0].OSC, voices[0].ENV, 0);
|
||||
output = Mix(voices[1].OSC, voices[1].ENV, output);
|
||||
|
||||
// voice 3 can be disabled with a specific register, but
|
||||
// when the filter is enabled, it still plays
|
||||
if (!regs.D3 || voices[2].FILT)
|
||||
output = Mix(voices[2].OSC, voices[2].ENV, output);
|
||||
int mixer;
|
||||
|
||||
mixer = voices[0].Output();
|
||||
mixer += voices[1].Output();
|
||||
mixer += voices[2].Output();
|
||||
|
||||
// the mixer is very loud at this point, let's make it quieter
|
||||
mixer /= 6;
|
||||
|
||||
if (mixer > 32767)
|
||||
mixer = 326767;
|
||||
else if (mixer < -32768)
|
||||
mixer = -32768;
|
||||
|
||||
short output = (short)mixer;
|
||||
|
||||
// run twice since the buffer expects stereo sound (I THINK)
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
sampleCounter = cyclesPerSample;
|
||||
sampleBufferIndex++;
|
||||
if (sampleBufferIndex == sampleBufferCapacity)
|
||||
sampleBufferIndex = 0;
|
||||
sampleBuffer[sampleBufferIndex] = output;
|
||||
}
|
||||
sampleCounter = cyclesPerSample;
|
||||
}
|
||||
sampleCounter--;
|
||||
}
|
||||
|
|
|
@ -5,7 +5,10 @@ using System.Text;
|
|||
|
||||
namespace BizHawk.Emulation.Computers.Commodore64
|
||||
{
|
||||
class SidWaveformCalculator
|
||||
// constants for the WaveformCalculator and calculation
|
||||
// methods come from the libsidplayfp residfp library.
|
||||
|
||||
static class WaveformCalculator
|
||||
{
|
||||
struct CombinedWaveformConfig
|
||||
{
|
||||
|
@ -33,13 +36,13 @@ namespace BizHawk.Emulation.Computers.Commodore64
|
|||
new CombinedWaveformConfig(0.9527834f, 1.794777f, 0.0f, 0.09806272f, 0.7752482f)
|
||||
};
|
||||
|
||||
public short[][] BuildTable()
|
||||
public static short[][] BuildTable()
|
||||
{
|
||||
short[][] wftable = new short[8][];
|
||||
for (int i = 0; i < 8; i++)
|
||||
wftable[i] = new short[4096];
|
||||
|
||||
for (int accumulator = 0; accumulator < 1 << 24; accumulator += 1 << 12)
|
||||
for (int accumulator = 0; accumulator < (1 << 24); accumulator += (1 << 12))
|
||||
{
|
||||
int idx = (accumulator >> 12);
|
||||
wftable[0][idx] = 0xFFF;
|
||||
|
@ -55,7 +58,7 @@ namespace BizHawk.Emulation.Computers.Commodore64
|
|||
return wftable;
|
||||
}
|
||||
|
||||
private short CalculateCombinedWaveForm(CombinedWaveformConfig config, int waveform, int accumulator)
|
||||
private static short CalculateCombinedWaveForm(CombinedWaveformConfig config, int waveform, int accumulator)
|
||||
{
|
||||
float[] o = new float[12];
|
||||
|
||||
|
|
|
@ -5,10 +5,13 @@ using System.Text;
|
|||
|
||||
namespace BizHawk.Emulation.Computers.Commodore64
|
||||
{
|
||||
|
||||
// constants for the WaveformGenerator and calculation
|
||||
// methods come from the libsidplayfp residfp library.
|
||||
|
||||
class WaveformGenerator
|
||||
{
|
||||
private int accumulator;
|
||||
private short[] dac = new short[4096];
|
||||
private int floatingOutputTtl;
|
||||
private int freq;
|
||||
private short[][] modelWave;
|
||||
|
@ -29,8 +32,10 @@ namespace BizHawk.Emulation.Computers.Commodore64
|
|||
private int waveform;
|
||||
private int waveformOutput;
|
||||
|
||||
public WaveformGenerator()
|
||||
public WaveformGenerator(short[][] newModelWave)
|
||||
{
|
||||
modelWave = newModelWave;
|
||||
Reset();
|
||||
}
|
||||
|
||||
public void Clock()
|
||||
|
@ -88,7 +93,7 @@ namespace BizHawk.Emulation.Computers.Commodore64
|
|||
}
|
||||
}
|
||||
pulseOutput = ((accumulator >> 12) >= pw) ? 0xFFF : 0x000;
|
||||
return dac[waveformOutput];
|
||||
return (short)waveformOutput;
|
||||
}
|
||||
|
||||
public int ReadAccumulator()
|
||||
|
|
Loading…
Reference in New Issue