503 lines
11 KiB
C#
503 lines
11 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Text;
|
|
|
|
namespace BizHawk.Emulation.Computers.Commodore64
|
|
{
|
|
public enum SidEnvelopeState
|
|
{
|
|
Disabled,
|
|
Attack,
|
|
Decay,
|
|
Release
|
|
}
|
|
|
|
public enum SidMode
|
|
{
|
|
Sid6581,
|
|
Sid8580
|
|
}
|
|
|
|
public class SidRegs
|
|
{
|
|
public int[] ATK = new int[3];
|
|
public bool BP;
|
|
public bool D3;
|
|
public int[] DCY = new int[3];
|
|
public int[] ENV = new int[3];
|
|
public int[] F = new int[3];
|
|
public int FC;
|
|
public bool[] FILT = new bool[3];
|
|
public bool FILTEX;
|
|
public bool[] GATE = new bool[3];
|
|
public bool HP;
|
|
public bool LP;
|
|
public bool[] NOISE = new bool[3];
|
|
public int[] OSC = new int[3];
|
|
public int POTX;
|
|
public int POTY;
|
|
public int[] PW = new int[3];
|
|
public int RES;
|
|
public int[] RLS = new int[3];
|
|
public bool[] RMOD = new bool[3];
|
|
public bool[] SAW = new bool[3];
|
|
public int[] SR = new int[3];
|
|
public bool[] SQU = new bool[3];
|
|
public int[] STN = new int[3];
|
|
public bool[] SYNC = new bool[3];
|
|
public bool[] TEST = new bool[3];
|
|
public bool[] TRI = new bool[3];
|
|
public int VOL;
|
|
|
|
public SidRegs()
|
|
{
|
|
// power on state
|
|
SR[0] = 0x7FFFFF;
|
|
SR[1] = 0x7FFFFF;
|
|
SR[2] = 0x7FFFFF;
|
|
}
|
|
|
|
public byte this[int addr]
|
|
{
|
|
|
|
get
|
|
{
|
|
int result;
|
|
int index;
|
|
|
|
addr &= 0x1F;
|
|
switch (addr)
|
|
{
|
|
case 0x00:
|
|
case 0x07:
|
|
case 0x0E:
|
|
result = F[addr / 7] & 0xFF;
|
|
break;
|
|
case 0x01:
|
|
case 0x08:
|
|
case 0x0F:
|
|
result = (F[addr / 7] & 0xFF00) >> 8;
|
|
break;
|
|
case 0x02:
|
|
case 0x09:
|
|
case 0x10:
|
|
result = PW[addr / 7] & 0xFF;
|
|
break;
|
|
case 0x03:
|
|
case 0x0A:
|
|
case 0x11:
|
|
result = (PW[addr / 7] & 0x0F00) >> 8;
|
|
break;
|
|
case 0x04:
|
|
case 0x0B:
|
|
case 0x12:
|
|
index = addr / 7;
|
|
result = GATE[index] ? 0x01 : 0x00;
|
|
result |= SYNC[index] ? 0x02 : 0x00;
|
|
result |= RMOD[index] ? 0x04 : 0x00;
|
|
result |= TEST[index] ? 0x08 : 0x00;
|
|
result |= TRI[index] ? 0x10 : 0x00;
|
|
result |= SAW[index] ? 0x20 : 0x00;
|
|
result |= SQU[index] ? 0x40 : 0x00;
|
|
result |= NOISE[index] ? 0x80 : 0x00;
|
|
break;
|
|
case 0x05:
|
|
case 0x0C:
|
|
case 0x13:
|
|
index = addr / 7;
|
|
result = (ATK[index] & 0xF) << 4;
|
|
result |= DCY[index] & 0xF;
|
|
break;
|
|
case 0x06:
|
|
case 0x0D:
|
|
case 0x14:
|
|
index = addr / 7;
|
|
result = (STN[index] & 0xF) << 4;
|
|
result |= RLS[index] & 0xF;
|
|
break;
|
|
case 0x15:
|
|
result = FC & 0x7;
|
|
break;
|
|
case 0x16:
|
|
result = (FC & 0x7F8) >> 3;
|
|
break;
|
|
case 0x17:
|
|
result = FILT[0] ? 0x01 : 0x00;
|
|
result |= FILT[1] ? 0x02 : 0x00;
|
|
result |= FILT[2] ? 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 ;
|
|
break;
|
|
case 0x1A:
|
|
result = POTY;
|
|
break;
|
|
case 0x1B:
|
|
result = OSC[2] >> 4;
|
|
break;
|
|
case 0x1C:
|
|
result = ENV[2];
|
|
break;
|
|
default:
|
|
result = 0;
|
|
break;
|
|
}
|
|
return (byte)(result & 0xFF);
|
|
}
|
|
set
|
|
{
|
|
int val = value;
|
|
int index;
|
|
|
|
addr &= 0x1F;
|
|
switch (addr)
|
|
{
|
|
case 0x00:
|
|
case 0x07:
|
|
case 0x0E:
|
|
index = addr / 7;
|
|
F[index] &= 0xFF00;
|
|
F[index] |= val;
|
|
break;
|
|
case 0x01:
|
|
case 0x08:
|
|
case 0x0F:
|
|
index = addr / 7;
|
|
F[index] &= 0xFF;
|
|
F[index] |= val << 8;
|
|
break;
|
|
case 0x02:
|
|
case 0x09:
|
|
case 0x10:
|
|
index = addr / 7;
|
|
PW[index] &= 0x0700;
|
|
PW[index] |= val;
|
|
break;
|
|
case 0x03:
|
|
case 0x0A:
|
|
case 0x11:
|
|
index = addr / 7;
|
|
F[index] &= 0xFF;
|
|
F[index] |= (val & 0x07) << 8;
|
|
break;
|
|
case 0x04:
|
|
case 0x0B:
|
|
case 0x12:
|
|
index = addr / 7;
|
|
GATE[index] = ((val & 0x01) != 0x00);
|
|
SYNC[index] = ((val & 0x02) != 0x00);
|
|
RMOD[index] = ((val & 0x04) != 0x00);
|
|
TEST[index] = ((val & 0x08) != 0x00);
|
|
TRI[index] = ((val & 0x10) != 0x00);
|
|
SAW[index] = ((val & 0x20) != 0x00);
|
|
SQU[index] = ((val & 0x40) != 0x00);
|
|
NOISE[index] = ((val & 0x80) != 0x00);
|
|
break;
|
|
case 0x05:
|
|
case 0x0C:
|
|
case 0x13:
|
|
index = addr / 7;
|
|
ATK[index] = (val >> 4) & 0xF;
|
|
DCY[index] = val & 0xF;
|
|
break;
|
|
case 0x06:
|
|
case 0x0D:
|
|
case 0x14:
|
|
index = addr / 7;
|
|
STN[index] = (val >> 4) & 0xF;
|
|
RLS[index] = val & 0xF;
|
|
break;
|
|
case 0x15:
|
|
FC &= 0x7F8;
|
|
FC |= val & 0x7;
|
|
break;
|
|
case 0x16:
|
|
FC &= 0x7;
|
|
FC |= val << 3;
|
|
break;
|
|
case 0x17:
|
|
FILT[0] = ((val & 0x01) != 0x00);
|
|
FILT[1] = ((val & 0x02) != 0x00);
|
|
FILT[2] = ((val & 0x04) != 0x00);
|
|
FILTEX = ((val & 0x08) != 0x00);
|
|
RES = (val >> 4);
|
|
break;
|
|
case 0x18:
|
|
VOL = (val & 0xF);
|
|
LP = ((val & 0x10) != 0x00);
|
|
BP = ((val & 0x20) != 0x00);
|
|
HP = ((val & 0x40) != 0x00);
|
|
D3 = ((val & 0x80) != 0x00);
|
|
break;
|
|
case 0x19:
|
|
POTX = val;
|
|
break;
|
|
case 0x1A:
|
|
POTY = val;
|
|
break;
|
|
case 0x1B:
|
|
OSC[2] = val << 4;
|
|
break;
|
|
case 0x1C:
|
|
ENV[2] = val;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
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[] syncIndex = { 2, 0, 1 };
|
|
|
|
public Func<int> ReadPotX;
|
|
public Func<int> ReadPotY;
|
|
|
|
public int clock;
|
|
public bool[] envEnable = new bool[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[] gateLastCycle = new bool[3];
|
|
public int output;
|
|
public SidRegs regs;
|
|
public int[] waveClock = new int[3];
|
|
|
|
public Sid()
|
|
{
|
|
ReadPotX = DummyReadPot;
|
|
ReadPotY = DummyReadPot;
|
|
HardReset();
|
|
}
|
|
|
|
private int DummyReadPot()
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
public void HardReset()
|
|
{
|
|
regs = new SidRegs();
|
|
}
|
|
|
|
public byte Peek(int addr)
|
|
{
|
|
return regs[addr & 0x1F];
|
|
}
|
|
|
|
public void PerformCycle()
|
|
{
|
|
// accumulator is 24 bits
|
|
clock = (clock + 1) & 0xFFFFFF;
|
|
|
|
ProcessVoice(0);
|
|
ProcessVoice(1);
|
|
ProcessVoice(2);
|
|
|
|
// query pots every 512 cycles
|
|
if ((clock & 0x1FF) == 0x000)
|
|
{
|
|
regs.POTX = ReadPotX() & 0xFF;
|
|
regs.POTY = ReadPotY() & 0xFF;
|
|
}
|
|
}
|
|
|
|
public void Poke(int addr, byte val)
|
|
{
|
|
regs[addr & 0x1F] = val;
|
|
}
|
|
|
|
private void ProcessEnvelope(int index)
|
|
{
|
|
// envelope counter is 15 bits
|
|
envRateCounter[index] &= 0x7FFF;
|
|
|
|
if (!gateLastCycle[index] && regs.GATE[index])
|
|
{
|
|
envState[index] = SidEnvelopeState.Attack;
|
|
envEnable[index] = true;
|
|
}
|
|
else if (gateLastCycle[index] && !regs.GATE[index])
|
|
{
|
|
envState[index] = SidEnvelopeState.Release;
|
|
}
|
|
|
|
if (envRateCounter[index] == envRate[index])
|
|
{
|
|
envExpCounter[index] = 0;
|
|
if (envEnable[index])
|
|
{
|
|
|
|
}
|
|
}
|
|
|
|
gateLastCycle[index] = regs.GATE[index];
|
|
}
|
|
|
|
private void ProcessShiftRegister(int index)
|
|
{
|
|
int newBit = ((regs.SR[index] >> 22) ^ (regs.SR[index] >> 17)) & 0x1;
|
|
regs.SR[index] = ((regs.SR[index] << 1) | newBit) & 0x7FFFFF;
|
|
}
|
|
|
|
private void ProcessVoice(int index)
|
|
{
|
|
int triOutput;
|
|
int sawOutput;
|
|
int squOutput;
|
|
int noiseOutput;
|
|
int finalOutput = 0xFFFFFF;
|
|
bool outputEnabled = false;
|
|
|
|
// triangle waveform
|
|
if (regs.TRI[index])
|
|
{
|
|
triOutput = waveClock[index] >> 12;
|
|
if (regs.SYNC[index])
|
|
triOutput ^= regs.OSC[syncIndex[index]] & 0x800;
|
|
|
|
if ((triOutput & 0x800) != 0x000)
|
|
triOutput &= 0xFFF;
|
|
|
|
triOutput <<= 1;
|
|
finalOutput &= triOutput;
|
|
outputEnabled = true;
|
|
}
|
|
|
|
// saw waveform
|
|
if (regs.SAW[index])
|
|
{
|
|
sawOutput = waveClock[index] >> 12;
|
|
finalOutput &= sawOutput;
|
|
outputEnabled = true;
|
|
}
|
|
|
|
// square waveform
|
|
if (regs.SQU[index])
|
|
{
|
|
if (regs.TEST[index])
|
|
{
|
|
squOutput = 0xFFF;
|
|
}
|
|
else
|
|
{
|
|
if (regs.PW[index] >= (waveClock[index] >> 12))
|
|
squOutput = 0xFFF;
|
|
else
|
|
squOutput = 0x000;
|
|
}
|
|
finalOutput &= squOutput;
|
|
outputEnabled = true;
|
|
}
|
|
|
|
// noise waveform
|
|
if (regs.NOISE[index])
|
|
{
|
|
int sr = regs.SR[index];
|
|
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;
|
|
}
|
|
|
|
// test bit resets the oscillator and silences output
|
|
if (regs.TEST[index])
|
|
{
|
|
waveClock[index] = 0x000000;
|
|
outputEnabled = false;
|
|
}
|
|
else
|
|
{
|
|
// shift register for generating noise
|
|
if ((waveClock[index] & 0x7FFFF) == 0x00000)
|
|
ProcessShiftRegister(index);
|
|
|
|
// increment wave clock
|
|
waveClock[index] = (waveClock[index] + regs.F[index]) & 0xFFFFFF;
|
|
}
|
|
|
|
// process the envelope generator
|
|
//ProcessEnvelope(index);
|
|
|
|
// write to internal reg
|
|
if (outputEnabled)
|
|
regs.OSC[index] = finalOutput;
|
|
else
|
|
regs.OSC[index] = 0x000000;
|
|
}
|
|
|
|
public byte Read(ushort addr)
|
|
{
|
|
addr &= 0x1F;
|
|
switch (addr)
|
|
{
|
|
case 0x19:
|
|
case 0x1A:
|
|
case 0x1B:
|
|
case 0x1C:
|
|
// can only read these regs
|
|
return regs[addr];
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
private void UpdateEnvelopeRateCounter(int index)
|
|
{
|
|
switch (envState[index])
|
|
{
|
|
case SidEnvelopeState.Attack:
|
|
envRate[index] = envRateIndex[regs.ATK[index]];
|
|
break;
|
|
case SidEnvelopeState.Decay:
|
|
envRate[index] = envRateIndex[regs.DCY[index]];
|
|
break;
|
|
case SidEnvelopeState.Release:
|
|
envRate[index] = envRateIndex[regs.RLS[index]];
|
|
break;
|
|
}
|
|
}
|
|
|
|
public void Write(ushort addr, byte val)
|
|
{
|
|
addr &= 0x1F;
|
|
switch (addr)
|
|
{
|
|
case 0x19:
|
|
case 0x1A:
|
|
case 0x1B:
|
|
case 0x1C:
|
|
// can't write these regs
|
|
break;
|
|
default:
|
|
regs[addr] = val;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|