2017-08-25 14:21:10 +00:00
|
|
|
|
using System;
|
|
|
|
|
using BizHawk.Emulation.Common;
|
|
|
|
|
using BizHawk.Common.NumberExtensions;
|
|
|
|
|
using BizHawk.Common;
|
|
|
|
|
|
|
|
|
|
namespace BizHawk.Emulation.Cores.Atari.A7800Hawk
|
|
|
|
|
{
|
|
|
|
|
// emualtes pokey sound chip
|
|
|
|
|
// note: A7800 implementation is used only for sound
|
|
|
|
|
// potentiometers, keyboard, and IRQs are not used in this context
|
|
|
|
|
/*
|
|
|
|
|
* Regs 0,2,4,6: Frequency control (divider = value + 1)
|
|
|
|
|
* Regs 1,3,5,7: Channel control (Bits 0-3 = volume) (bits 4 - 7 control clocking)
|
|
|
|
|
* Reg 8: Control register
|
|
|
|
|
*
|
|
|
|
|
* Reg A: Random number generator
|
|
|
|
|
*
|
|
|
|
|
* The registers are write only, except for the RNG none of the things that would return reads are connected
|
|
|
|
|
* for now return FF
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public class Pokey
|
|
|
|
|
{
|
|
|
|
|
public A7800Hawk Core { get; set; }
|
|
|
|
|
|
2017-08-26 16:46:59 +00:00
|
|
|
|
public readonly short[] LocalAudioCycles = new short[2000];
|
|
|
|
|
public int AudioClocks;
|
|
|
|
|
|
|
|
|
|
// state variables
|
2017-08-25 14:21:10 +00:00
|
|
|
|
public byte[] Regs = new byte[16];
|
2017-08-26 16:46:59 +00:00
|
|
|
|
public int poly4, poly5, poly9, poly17;
|
|
|
|
|
public int[] ch_div = new int[4];
|
|
|
|
|
public int[] inc_ch = new int[4];
|
|
|
|
|
public bool[] ch_out = new bool[4];
|
|
|
|
|
public bool[] ch_src = new bool[4];
|
|
|
|
|
public int[] ch_vol = new int[4];
|
|
|
|
|
public bool high_pass_1;
|
|
|
|
|
public bool high_pass_2;
|
2017-08-25 14:21:10 +00:00
|
|
|
|
|
2017-08-26 16:46:59 +00:00
|
|
|
|
// these are derived values and do not need to be save-stated
|
|
|
|
|
public bool[] clock_ch = new bool[4];
|
|
|
|
|
public int bit_xor;
|
2017-08-25 14:21:10 +00:00
|
|
|
|
|
|
|
|
|
public Pokey()
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-26 16:46:59 +00:00
|
|
|
|
public void sample()
|
|
|
|
|
{
|
|
|
|
|
LocalAudioCycles[AudioClocks] += (short)(ch_vol[0] + ch_vol[1] + ch_vol[2] + ch_vol[3]);
|
|
|
|
|
AudioClocks++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void GetSamples(short[] samples)
|
|
|
|
|
{
|
|
|
|
|
if (AudioClocks > 0)
|
|
|
|
|
{
|
|
|
|
|
var samples31Khz = new short[AudioClocks]; // mono
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < AudioClocks; i++)
|
|
|
|
|
{
|
|
|
|
|
samples31Khz[i] = LocalAudioCycles[i];
|
|
|
|
|
LocalAudioCycles[i] = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// convert from 31khz to 44khz
|
|
|
|
|
for (var i = 0; i < samples.Length / 2; i++)
|
|
|
|
|
{
|
|
|
|
|
samples[i * 2] = samples31Khz[(int)(((double)samples31Khz.Length / (double)(samples.Length / 2)) * i)];
|
|
|
|
|
samples[(i * 2) + 1] = samples[i * 2];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
AudioClocks = 0;
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-25 14:21:10 +00:00
|
|
|
|
public byte ReadReg(int reg)
|
|
|
|
|
{
|
|
|
|
|
byte ret = 0xFF;
|
|
|
|
|
|
2017-08-26 16:46:59 +00:00
|
|
|
|
if (reg==0xA)
|
|
|
|
|
{
|
|
|
|
|
ret = (byte)(poly17 >> 9);
|
|
|
|
|
}
|
2017-08-25 14:21:10 +00:00
|
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void WriteReg(int reg, byte value)
|
|
|
|
|
{
|
|
|
|
|
Regs[reg] = value;
|
2017-08-25 15:28:19 +00:00
|
|
|
|
|
2017-08-26 16:46:59 +00:00
|
|
|
|
// this condition resets poly counters and holds them in place
|
|
|
|
|
if ((Regs[0xF] & 3) == 0)
|
|
|
|
|
{
|
|
|
|
|
poly4 = 0xF;
|
|
|
|
|
poly5 = 0x1F;
|
|
|
|
|
poly17 = 0x1FFFF;
|
|
|
|
|
}
|
2017-08-25 14:21:10 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void Tick()
|
|
|
|
|
{
|
2017-08-26 16:46:59 +00:00
|
|
|
|
// clock the 4-5-(9 or 17) bit poly counters
|
|
|
|
|
// NOTE: These might not be the exact poly implementation, I just picked a maximal one from wikipedia
|
|
|
|
|
// poly 4 and 5 are known to result in:
|
|
|
|
|
// poly4 output: 000011101100101
|
|
|
|
|
// poly5 output: 1101001100000111001000101011110
|
|
|
|
|
if ((Regs[0xF] & 3) != 0)
|
|
|
|
|
{
|
|
|
|
|
bit_xor = ((poly4 >> 3) ^ (poly4 >> 2) ^ poly4) & 1;
|
|
|
|
|
poly4 = (poly4 >> 1) | (bit_xor << 3);
|
2017-08-25 14:21:10 +00:00
|
|
|
|
|
2017-08-26 16:46:59 +00:00
|
|
|
|
bit_xor = ((poly5 >> 4) ^ (poly5 >> 2) ^ poly5) & 1;
|
|
|
|
|
poly5 = (poly5 >> 1) | (bit_xor << 4);
|
|
|
|
|
|
|
|
|
|
if (Regs[8].Bit(7))
|
|
|
|
|
{
|
|
|
|
|
// clock only 9 bits of the 17 bit poly
|
|
|
|
|
poly9 = poly17 >> 8;
|
|
|
|
|
bit_xor = ((poly9 >> 8) ^ (poly9 >> 4) ^ poly9) & 1;
|
|
|
|
|
poly9 = (poly9 >> 1) | (bit_xor << 8);
|
|
|
|
|
poly17 = (poly17 & 0xFF) | (poly9 << 8);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// clock the whole 17 bit poly
|
|
|
|
|
bit_xor = ((poly17 >> 16) ^ (poly17 >> 13) ^ poly17) & 1;
|
|
|
|
|
poly17 = (poly17 >> 1) | (bit_xor << 16);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
clock_ch[0] = clock_ch[1] = clock_ch[2] = clock_ch[3] = false;
|
|
|
|
|
|
|
|
|
|
// now that we have the poly counters, check which channels to clock
|
|
|
|
|
if (Regs[8].Bit(6))
|
|
|
|
|
{
|
|
|
|
|
clock_ch[0] = true;
|
|
|
|
|
clock_ch[2] = true;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
inc_ch[0]++;
|
|
|
|
|
inc_ch[2]++;
|
|
|
|
|
if (Regs[8].Bit(0))
|
|
|
|
|
{
|
|
|
|
|
if (inc_ch[0] >= 114) { inc_ch[0] = 0; clock_ch[0] = true; }
|
|
|
|
|
if (inc_ch[2] >= 114) { inc_ch[2] = 0; clock_ch[2] = true; }
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
if (inc_ch[0] >= 28) { inc_ch[0] = 0; clock_ch[0] = true; }
|
|
|
|
|
if (inc_ch[2] >= 28) { inc_ch[2] = 0; clock_ch[2] = true; }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (Regs[8].Bit(4))
|
|
|
|
|
{
|
|
|
|
|
if (clock_ch[0]) { clock_ch[1] = true; }
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
inc_ch[1]++;
|
|
|
|
|
if (Regs[8].Bit(0))
|
|
|
|
|
{
|
|
|
|
|
if (inc_ch[1] >= 114) { inc_ch[1] = 0; clock_ch[1] = true; }
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
if (inc_ch[1] >= 28) { inc_ch[1] = 0; clock_ch[1] = true; }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (Regs[8].Bit(3))
|
|
|
|
|
{
|
|
|
|
|
if (clock_ch[2]) { clock_ch[3] = true; }
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
inc_ch[3]++;
|
|
|
|
|
if (Regs[8].Bit(0))
|
|
|
|
|
{
|
|
|
|
|
if (inc_ch[3] >= 114) { inc_ch[3] = 0; clock_ch[3] = true; }
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
if (inc_ch[3] >= 28) { inc_ch[3] = 0; clock_ch[3] = true; }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// first update the high pass filter latch
|
|
|
|
|
if (clock_ch[2] && Regs[8].Bit(2)) { high_pass_1 = ch_out[0]; }
|
|
|
|
|
if (clock_ch[3] && Regs[8].Bit(1)) { high_pass_2 = ch_out[1]; }
|
|
|
|
|
|
|
|
|
|
// now we know what channels to clock, execute the cycles
|
|
|
|
|
for (int i = 0; i < 4; i++) {
|
|
|
|
|
if (clock_ch[i])
|
|
|
|
|
{
|
|
|
|
|
ch_div[i]++;
|
|
|
|
|
if (ch_div[i] == (Regs[i * 2] + 1))
|
|
|
|
|
{
|
|
|
|
|
ch_div[i] = 0;
|
|
|
|
|
|
|
|
|
|
// select the next source based on the channel control register
|
|
|
|
|
if (Regs[i * 2 + 1].Bit(4))
|
|
|
|
|
{
|
|
|
|
|
// forced output always on (used with volume modulation)
|
|
|
|
|
ch_out[i] = true;
|
|
|
|
|
}
|
|
|
|
|
else if ((Regs[i * 2 + 1] & 0xF0) == 0)
|
|
|
|
|
{
|
|
|
|
|
// 17 bit poly then 5 bit poly
|
|
|
|
|
if (ch_src[i])
|
|
|
|
|
{
|
|
|
|
|
ch_out[i] = poly5.Bit(4);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
ch_out[i] = poly5.Bit(16);
|
|
|
|
|
}
|
|
|
|
|
ch_src[i] = !ch_src[i];
|
|
|
|
|
}
|
|
|
|
|
else if (((Regs[i * 2 + 1] & 0xF0) == 0x20) || ((Regs[i * 2 + 1] & 0xF0) == 0x60))
|
|
|
|
|
{
|
|
|
|
|
// 5 bit poly
|
|
|
|
|
ch_out[i] = poly5.Bit(4);
|
|
|
|
|
}
|
|
|
|
|
else if ((Regs[i * 2 + 1] & 0xF0) == 0x40)
|
|
|
|
|
{
|
|
|
|
|
// 4 bit poly then 5 bit poly
|
|
|
|
|
if (ch_src[i])
|
|
|
|
|
{
|
|
|
|
|
ch_out[i] = poly5.Bit(4);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
ch_out[i] = poly4.Bit(3);
|
|
|
|
|
}
|
|
|
|
|
ch_src[i] = !ch_src[i];
|
|
|
|
|
}
|
|
|
|
|
else if ((Regs[i * 2 + 1] & 0xF0) == 0x80)
|
|
|
|
|
{
|
|
|
|
|
// 17 bit poly
|
|
|
|
|
if (ch_src[i])
|
|
|
|
|
{
|
|
|
|
|
ch_out[i] = poly17.Bit(16);
|
|
|
|
|
}
|
|
|
|
|
ch_src[i] = !ch_src[i];
|
|
|
|
|
}
|
|
|
|
|
else if ((Regs[i * 2 + 1] & 0xF0) == 0xA0)
|
|
|
|
|
{
|
|
|
|
|
// tone
|
|
|
|
|
ch_out[i] = !ch_out[i];
|
|
|
|
|
}
|
|
|
|
|
else if ((Regs[i * 2 + 1] & 0xF0) == 0xC0)
|
|
|
|
|
{
|
|
|
|
|
// 4 bit poly
|
|
|
|
|
if (ch_src[i])
|
|
|
|
|
{
|
|
|
|
|
ch_out[i] = poly4.Bit(3);
|
|
|
|
|
}
|
|
|
|
|
ch_src[i] = !ch_src[i];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// for channels 1 and 2, an optional high pass filter exists
|
|
|
|
|
// the filter is just a flip flop and xor combo
|
|
|
|
|
if ((i == 0 && Regs[8].Bit(2)) || (i == 1 && Regs[8].Bit(1)))
|
|
|
|
|
{
|
|
|
|
|
if (i == 0) { ch_vol[0] = (ch_out[0] ^ high_pass_1) ? (Regs[1] & 0xF) : 0; }
|
|
|
|
|
if (i == 1) { ch_vol[1] = (ch_out[1] ^ high_pass_2) ? (Regs[3] & 0xF) : 0; }
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
ch_vol[i] = (ch_out[i] ? (Regs[i * 2 + 1] & 0xF) : 0) * 70;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-08-25 14:21:10 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void Reset()
|
|
|
|
|
{
|
|
|
|
|
Regs = new byte[16];
|
2017-08-26 16:46:59 +00:00
|
|
|
|
poly4 = 0xF;
|
|
|
|
|
poly5 = 0x1F;
|
|
|
|
|
poly17 = 0x1FFFF;
|
2017-08-25 14:21:10 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void SyncState(Serializer ser)
|
|
|
|
|
{
|
|
|
|
|
ser.BeginSection("Pokey");
|
|
|
|
|
|
|
|
|
|
ser.Sync("Regs", ref Regs, false);
|
|
|
|
|
|
2017-08-26 16:46:59 +00:00
|
|
|
|
ser.Sync("poly4", ref poly4);
|
|
|
|
|
ser.Sync("poly5", ref poly5);
|
|
|
|
|
ser.Sync("poly9", ref poly9);
|
|
|
|
|
ser.Sync("poly17", ref poly17);
|
|
|
|
|
ser.Sync("ch_div", ref ch_div, false);
|
|
|
|
|
ser.Sync("inc_ch", ref inc_ch, false);
|
|
|
|
|
ser.Sync("ch_out", ref ch_out, false);
|
|
|
|
|
ser.Sync("ch_src", ref ch_src, false);
|
|
|
|
|
ser.Sync("ch_vol", ref ch_vol, false);
|
|
|
|
|
ser.Sync("high_pass_1", ref high_pass_1);
|
|
|
|
|
ser.Sync("high_pass_2", ref high_pass_2);
|
|
|
|
|
|
|
|
|
|
ser.EndSection();
|
2017-08-25 14:21:10 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
}
|