diff --git a/BizHawk.Emulation.Cores/Consoles/Atari/A7800Hawk/TIA_Sound/TIA.cs b/BizHawk.Emulation.Cores/Consoles/Atari/A7800Hawk/TIA_Sound/TIA.cs new file mode 100644 index 0000000000..4c6ef5f20d --- /dev/null +++ b/BizHawk.Emulation.Cores/Consoles/Atari/A7800Hawk/TIA_Sound/TIA.cs @@ -0,0 +1,334 @@ +using System; +using System.Numerics; + +using BizHawk.Emulation.Common; + +namespace BizHawk.Emulation.Cores.Atari.A7800Hawk +{ + // Emulates the TIA + public partial class TIA : ISoundProvider + { + public byte BusState; + + private bool _doTicks; + + private byte _hsyncCnt; + private int _capChargeStart; + private bool _capCharging; + public int AudioClocks; // not savestated + + private readonly Audio[] AUD = { new Audio(), new Audio() }; + + // current audio register state used to sample correct positions in the scanline (clrclk 0 and 114) + ////public byte[] current_audio_register = new byte[6]; + public readonly short[] LocalAudioCycles = new short[2000]; + + public void Reset() + { + _hsyncCnt = 0; + _capChargeStart = 0; + _capCharging = false; + AudioClocks = 0; + + _doTicks = false; + } + + // Execute TIA cycles + public void Execute(int cycles) + { + // do the audio sampling + if (_hsyncCnt == 36 || _hsyncCnt == 148) + { + LocalAudioCycles[AudioClocks] += (short)(AUD[0].Cycle() / 2); + LocalAudioCycles[AudioClocks] += (short)(AUD[1].Cycle() / 2); + AudioClocks++; + } + + } + + public byte ReadMemory(ushort addr, bool peek) + { + var maskedAddr = (ushort)(addr & 0x000F); + byte coll = 0; + int mask = 0; + + if (maskedAddr == 0x00) // CXM0P + { + + } + + if (maskedAddr == 0x01) // CXM1P + { + + } + + if (maskedAddr == 0x02) // CXP0FB + { + + } + + if (maskedAddr == 0x03) // CXP1FB + { + + } + + if (maskedAddr == 0x04) // CXM0FB + { + + } + + if (maskedAddr == 0x05) // CXM1FB + { + + } + + if (maskedAddr == 0x06) // CXBLPF + { + + } + + if (maskedAddr == 0x07) // CXPPMM + { + + } + + // inputs 0-3 are measured by a charging capacitor, these inputs are used with the paddles and the keyboard + // Changing the hard coded value will change the paddle position. The range seems to be roughly 0-56000 according to values from stella + // 6105 roughly centers the paddle in Breakout + if (maskedAddr == 0x08) // INPT0 + { + + } + + if (maskedAddr == 0x09) // INPT1 + { + + } + + if (maskedAddr == 0x0A) // INPT2 + { + + } + + if (maskedAddr == 0x0B) // INPT3 + { + + } + + if (maskedAddr == 0x0C) // INPT4 + { + + } + + if (maskedAddr == 0x0D) // INPT5 + { + + } + + // some bits of the databus will be undriven when a read call is made. Our goal here is to sort out what + // happens to the undriven pins. Most of the time, they will be in whatever state they were when previously + // assigned in some other bus access, so let's go with that. + coll += (byte)(mask & BusState); + + if (!peek) + { + BusState = coll; + } + + return coll; + } + + public void WriteMemory(ushort addr, byte value, bool poke) + { + var maskedAddr = (ushort)(addr & 0x3f); + if (!poke) + { + BusState = value; + } + + if (maskedAddr == 0x00) // VSYNC + { + + } + else if (maskedAddr == 0x01) // VBLANK + { + + } + else if (maskedAddr == 0x02) // WSYNC + { + + } + else if (maskedAddr == 0x04) // NUSIZ0 + { + + } + else if (maskedAddr == 0x05) // NUSIZ1 + { + + } + else if (maskedAddr == 0x06) // COLUP0 + { + + } + else if (maskedAddr == 0x07) // COLUP1 + { + + } + else if (maskedAddr == 0x08) // COLUPF + { + + } + else if (maskedAddr == 0x09) // COLUBK + { + + } + else if (maskedAddr == 0x0A) // CTRLPF + { + + } + else if (maskedAddr == 0x0B) // REFP0 + { + + } + else if (maskedAddr == 0x0C) // REFP1 + { + + } + else if (maskedAddr == 0x0D) // PF0 + { + + } + else if (maskedAddr == 0x0E) // PF1 + { + + } + else if (maskedAddr == 0x0F) // PF2 + { + + } + else if (maskedAddr == 0x10) // RESP0 + { + + } + else if (maskedAddr == 0x11) // RESP1 + { + + } + else if (maskedAddr == 0x12) // RESM0 + { + + } + else if (maskedAddr == 0x13) // RESM1 + { + + } + else if (maskedAddr == 0x14) // RESBL + { + + } + else if (maskedAddr == 0x15) // AUDC0 + { + AUD[0].AUDC = (byte)(value & 15); + } + else if (maskedAddr == 0x16) // AUDC1 + { + AUD[1].AUDC = (byte)(value & 15); + } + else if (maskedAddr == 0x17) // AUDF0 + { + AUD[0].AUDF = (byte)((value & 31) + 1); + } + else if (maskedAddr == 0x18) // AUDF1 + { + AUD[1].AUDF = (byte)((value & 31) + 1); + } + else if (maskedAddr == 0x19) // AUDV0 + { + AUD[0].AUDV = (byte)(value & 15); + } + else if (maskedAddr == 0x1A) // AUDV1 + { + AUD[1].AUDV = (byte)(value & 15); + } + else if (maskedAddr == 0x1B) // GRP0 + { + + } + else if (maskedAddr == 0x1C) // GRP1 + { + + } + else if (maskedAddr == 0x1D) // ENAM0 + { + + } + else if (maskedAddr == 0x1E) // ENAM1 + { + + } + else if (maskedAddr == 0x1F) // ENABL + { + + } + else if (maskedAddr == 0x20) // HMP0 + { + + } + else if (maskedAddr == 0x21) // HMP1 + { + + } + else if (maskedAddr == 0x22) // HMM0 + { + + } + else if (maskedAddr == 0x23) // HMM1 + { + + } + else if (maskedAddr == 0x24) // HMBL + { + + } + else if (maskedAddr == 0x25) // VDELP0 + { + + } + else if (maskedAddr == 0x26) // VDELP1 + { + + } + else if (maskedAddr == 0x27) // VDELBL + { + + } + else if (maskedAddr == 0x28) // RESMP0 + { + + } + else if (maskedAddr == 0x29) // RESMP1 + { + + } + else if (maskedAddr == 0x2A) // HMOVE + { + + } + else if (maskedAddr == 0x2B) // HMCLR + { + + } + else if (maskedAddr == 0x2C) // CXCLR + { + + } + } + + private enum AudioRegister : byte + { + AUDC, AUDF, AUDV + } + + private int _frameStartCycles, _frameEndCycles; + } +} \ No newline at end of file diff --git a/BizHawk.Emulation.Cores/Consoles/Atari/A7800Hawk/TIA_Sound/Tia.Audio.cs b/BizHawk.Emulation.Cores/Consoles/Atari/A7800Hawk/TIA_Sound/Tia.Audio.cs new file mode 100644 index 0000000000..cf5aa3e0c7 --- /dev/null +++ b/BizHawk.Emulation.Cores/Consoles/Atari/A7800Hawk/TIA_Sound/Tia.Audio.cs @@ -0,0 +1,212 @@ +using System; +using BizHawk.Common; + +namespace BizHawk.Emulation.Cores.Atari.A7800Hawk +{ + public partial class TIA + { + public class Audio + { + // noise/division control + public byte AUDC = 0; + + // frequency divider + public byte AUDF = 1; + + // volume + public byte AUDV = 0; + + // 2 state counter + private bool sr1 = true; + + // 4 bit shift register + private int sr4 = 0x0f; + + // 5 bit shift register + private int sr5 = 0x1f; + + // 9 bit shift register + private int sr9 = 0x1ff; + + // 3 state counter + private int sr3 = 2; + + // counter based off AUDF + private byte freqcnt; + + // latched audio value + private bool on = true; + + private bool Run3() + { + sr3++; + if (sr3 == 3) + { + sr3 = 0; + return true; + } + + return false; + } + + private bool Run4() + { + bool ret = (sr4 & 1) != 0; + bool c = ((sr4 & 1) != 0) ^ ((sr4 & 2) != 0); + sr4 = (sr4 >> 1) | (c ? 8 : 0); + return ret; + } + + private bool Run5() + { + bool ret = (sr5 & 1) != 0; + bool c = ((sr5 & 1) != 0) ^ ((sr5 & 4) != 0); + sr5 = (sr5 >> 1) | (c ? 16 : 0); + return ret; + } + + private bool One4() + { + bool ret = (sr4 & 1) != 0; + sr4 = (sr4 >> 1) | 8; + return ret; + } + + private bool One5() + { + bool ret = (sr5 & 1) != 0; + sr5 = (sr5 >> 1) | 16; + return ret; + } + + private bool Run1() + { + sr1 = !sr1; + return !sr1; + } + + private bool Run9() + { + bool ret = (sr9 & 1) != 0; + bool c = ((sr9 & 1) != 0) ^ ((sr9 & 16) != 0); + sr9 = (sr9 >> 1) | (c ? 256 : 0); + return ret; + } + + /// + /// call me approx 31k times a second + /// + /// 16 bit audio sample + public short Cycle() + { + if (++freqcnt >= AUDF) + { + freqcnt = 0; + switch (AUDC) + { + case 0x00: + case 0x0b: + // Both have a 1 s + One5(); + on = One4(); + break; + + case 0x01: + // Both run, but the 5 bit is ignored + on = Run4(); + //Run5(); + break; + case 0x02: + if ((sr5 & 0x0f) == 0 || (sr5 & 0x0f) == 0x0f) + { + on = Run4(); + } + + Run5(); + break; + case 0x03: + if (Run5()) + { + on = Run4(); + } + + break; + + case 0x04: + Run5(); + One4(); + on = Run1(); + break; + + case 0x05: + One5(); + Run4(); + on = Run1(); + break; + + case 0x06: + case 0x0a: + Run5(); + if ((sr5 & 0x0f) == 0) + { + on = false; + } + else if ((sr5 & 0x0f) == 0x0f) + { + on = true; + } + + break; + + case 0x07: + case 0x09: + on = Run5(); + break; + + case 0x08: + on = Run9(); + break; + case 0x0c: + case 0x0d: + if (Run3()) + { + on = Run1(); + } + + break; + case 0x0e: + if (Run3()) + { + goto case 0x06; + } + + break; + case 0x0f: + if (Run3()) + { + goto case 0x07; + } + + break; + } + } + + return (short)(on ? AUDV * 1092 : 0); + } + + public void SyncState(Serializer ser) + { + ser.Sync("AUDC", ref AUDC); + ser.Sync("AUDF", ref AUDF); + ser.Sync("AUDV", ref AUDV); + ser.Sync("sr1", ref sr1); + ser.Sync("sr3", ref sr3); + ser.Sync("sr4", ref sr4); + ser.Sync("sr5", ref sr5); + ser.Sync("sr9", ref sr9); + ser.Sync("freqcnt", ref freqcnt); + ser.Sync("on", ref on); + } + } + } +} diff --git a/BizHawk.Emulation.Cores/Consoles/Atari/A7800Hawk/TIA_Sound/Tia.ISoundProvider.cs b/BizHawk.Emulation.Cores/Consoles/Atari/A7800Hawk/TIA_Sound/Tia.ISoundProvider.cs new file mode 100644 index 0000000000..3a85b7bbd0 --- /dev/null +++ b/BizHawk.Emulation.Cores/Consoles/Atari/A7800Hawk/TIA_Sound/Tia.ISoundProvider.cs @@ -0,0 +1,65 @@ +using System; +using BizHawk.Emulation.Common; + +namespace BizHawk.Emulation.Cores.Atari.A7800Hawk +{ + public partial class TIA : ISoundProvider + { + public bool CanProvideAsync => false; + + public void SetSyncMode(SyncSoundMode mode) + { + if (mode != SyncSoundMode.Sync) + { + throw new InvalidOperationException("Only Sync mode is supported."); + } + } + + public SyncSoundMode SyncMode => SyncSoundMode.Sync; + + public void GetSamplesSync(out short[] samples, out int nsamp) + { + short[] ret = new short[_spf * 2]; + GetSamples(ret); + samples = ret; + nsamp = _spf; + } + + public void GetSamplesAsync(short[] samples) + { + throw new NotSupportedException("Async is not available"); + } + + public void DiscardSamples() + { + AudioClocks = 0; + } + + private readonly int _spf; + + // Exposing this as GetSamplesAsync would allow this to provide async sound + // However, it does nothing special for async sound so I don't see a point + private 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; + } + } +} diff --git a/BizHawk.Emulation.Cores/Consoles/Atari/A7800Hawk/TIA_Sound/Tia.SyncState.cs b/BizHawk.Emulation.Cores/Consoles/Atari/A7800Hawk/TIA_Sound/Tia.SyncState.cs new file mode 100644 index 0000000000..100bdf5361 --- /dev/null +++ b/BizHawk.Emulation.Cores/Consoles/Atari/A7800Hawk/TIA_Sound/Tia.SyncState.cs @@ -0,0 +1,28 @@ +using BizHawk.Common; + +namespace BizHawk.Emulation.Cores.Atari.A7800Hawk +{ + public partial class TIA + { + public void SyncState(Serializer ser) + { + ser.BeginSection("TIA"); + + ser.Sync("hsyncCnt", ref _hsyncCnt); + + // add everything to the state + ser.Sync("Bus_State", ref BusState); + + ser.Sync("Ticks", ref _doTicks); + + // some of these things weren't in the state because they weren't needed if + // states were always taken at frame boundaries + ser.Sync("capChargeStart", ref _capChargeStart); + ser.Sync("capCharging", ref _capCharging); + ser.Sync("AudioClocks", ref AudioClocks); + ser.Sync("FrameStartCycles", ref _frameStartCycles); + ser.Sync("FrameEndCycles", ref _frameEndCycles); + + } + } +}