BizHawk/BizHawk.Emulation.Cores/Consoles/Atari/2600/Tia/TIA.cs

1335 lines
31 KiB
C#

using System;
using System.Numerics;
using BizHawk.Emulation.Common;
namespace BizHawk.Emulation.Cores.Atari.Atari2600
{
// Emulates the TIA
public partial class TIA : IVideoProvider, ISoundProvider
{
static TIA()
{
// add alpha to palette entries
for (int i = 0; i < PALPalette.Length; i++)
{
PALPalette[i] |= unchecked((int)0xff000000);
}
for (int i = 0; i < NTSCPalette.Length; i++)
{
NTSCPalette[i] |= unchecked((int)0xff000000);
}
}
public TIA(Atari2600 core, bool pal, bool secam)
{
_core = core;
_player0.ScanCnt = 8;
_player1.ScanCnt = 8;
_pal = pal;
SetSecam(secam);
CalcFrameRate();
_spf = _vsyncNum / (double)_vsyncDen > 55.0 ? 735 : 882;
}
// indicates to the core where a new frame is starting
public bool New_Frame = false;
private const int BackColor = unchecked((int)0xff000000);
private const int ScreenWidth = 160;
private const int MaxScreenHeight = 312;
private const byte CXP0 = 0x01;
private const byte CXP1 = 0x02;
private const byte CXM0 = 0x04;
private const byte CXM1 = 0x08;
private const byte CXPF = 0x10;
private const byte CXBL = 0x20;
private readonly Atari2600 _core;
// in all cases, the TIA has 228 clocks per scanline
// the NTSC TIA has a clock rate of 3579575hz
// the PAL/SECAM TIA has a clock rate of 3546894hz
private readonly bool _pal;
public int NominalNumScanlines => _pal ? 312 : 262;
private int[] _scanlinebuffer = new int[ScreenWidth * MaxScreenHeight];
private int[] _palette;
internal int BusState;
private byte _pf0Update;
private byte _pf1Update;
private byte _pf2Update;
private bool _pf0Updater;
private bool _pf1Updater;
private bool _pf2Updater;
private byte _pf0DelayClock;
private byte _pf1DelayClock;
private byte _pf2DelayClock;
private byte _pf0MaxDelay;
private byte _pf1MaxDelay;
private byte _pf2MaxDelay;
private int _enam0Delay;
private int _enam1Delay;
private int _enambDelay;
private bool _enam0Val;
private bool _enam1Val;
private bool _enambVal;
private int _vblankDelay;
private byte _vblankValue;
private bool _p0Stuff;
private bool _p1Stuff;
private bool _m0Stuff;
private bool _m1Stuff;
private bool _bStuff;
private int _hmp0Delay;
private byte _hmp0Val;
private int _hmp1Delay;
private byte _hmp1Val;
private int _prg0Delay;
private int _prg1Delay;
private byte _prg0Val;
private byte _prg1Val;
private bool _doTicks;
private byte _hsyncCnt;
private int _capChargeStart;
private bool _capCharging;
private bool _vblankEnabled;
private bool _vsyncEnabled;
private int _currentScanLine;
public int AudioClocks; // not savestated
private PlayerData _player0;
private PlayerData _player1;
private PlayfieldData _playField;
private HMoveData _hmove;
private BallData _ball;
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];
private static int ReverseBits(int value, int bits)
{
int result = 0;
for (int i = 0; i < bits; i++)
{
result = (result << 1) | ((value >> i) & 0x01);
}
return result;
}
private void CalcFrameRate()
{
// TODO when sound timing is made exact:
// NTSC refclock is actually 315 / 88 mhz
// 3546895
int clockrate = _pal ? 3546895 : 3579545;
int clocksperframe = 228 * NominalNumScanlines;
int gcd = (int)BigInteger.GreatestCommonDivisor(clockrate, clocksperframe);
_vsyncNum = clockrate / gcd;
_vsyncDen = clocksperframe / gcd;
}
public void SetSecam(bool secam)
{
_palette = _pal ? secam ? SecamPalette : PALPalette : NTSCPalette;
}
public int CurrentScanLine => _currentScanLine;
public bool IsVBlank => _vblankEnabled;
public bool IsVSync => _vsyncEnabled;
/// <summary>
/// Gets or sets a count of lines emulated; incremented by the TIA but not used by it
/// </summary>
public int LineCount { get; set; }
public void Reset()
{
_hsyncCnt = 0;
_capChargeStart = 0;
_capCharging = false;
_vblankEnabled = false;
_vblankDelay = 0;
_vblankValue = 0;
_vsyncEnabled = false;
_currentScanLine = 0;
AudioClocks = 0;
BusState = 0;
_pf0Update = 0;
_pf1Update = 0;
_pf2Update = 0;
_pf0Updater = false;
_pf1Updater = false;
_pf2Updater = false;
_pf0DelayClock = 0;
_pf1DelayClock = 0;
_pf2DelayClock = 0;
_pf0MaxDelay = 0;
_pf1MaxDelay = 0;
_pf2MaxDelay = 0;
_enam0Delay = 0;
_enam1Delay = 0;
_enambDelay = 0;
_enam0Val = false;
_enam1Val = false;
_enambVal = false;
_p0Stuff = false;
_p1Stuff = false;
_m0Stuff = false;
_m1Stuff = false;
_bStuff = false;
_hmp0Delay = 0;
_hmp0Val = 0;
_hmp1Delay = 0;
_hmp1Val = 0;
_prg0Delay = 0;
_prg1Delay = 0;
_prg0Val = 0;
_prg1Val = 0;
_doTicks = false;
_player0 = new PlayerData();
_player1 = new PlayerData();
_playField = new PlayfieldData();
_hmove = new HMoveData();
_ball = new BallData();
_player0.ScanCnt = 8;
_player1.ScanCnt = 8;
}
// Execute TIA cycles
public void Execute()
{
// Handle all of the Latch delays that occur in the TIA
if (_vblankDelay > 0)
{
_vblankDelay++;
if (_vblankDelay == 3)
{
_vblankEnabled = (_vblankValue & 0x02) != 0;
_vblankDelay = 0;
}
}
if (_pf0Updater)
{
_pf0DelayClock++;
if (_pf0DelayClock > _pf0MaxDelay)
{
_playField.Grp = (uint)((_playField.Grp & 0x0FFFF) + ((ReverseBits(_pf0Update, 8) & 0x0F) << 16));
_pf0Updater = false;
}
}
if (_pf1Updater)
{
_pf1DelayClock++;
if (_pf1DelayClock > _pf1MaxDelay)
{
_playField.Grp = (uint)((_playField.Grp & 0xF00FF) + (_pf1Update << 8));
_pf1Updater = false;
}
}
if (_pf2Updater)
{
_pf2DelayClock++;
if (_pf2DelayClock > _pf2MaxDelay)
{
_playField.Grp = (uint)((_playField.Grp & 0xFFF00) + ReverseBits(_pf2Update, 8));
_pf2Updater = false;
}
}
if (_enam0Delay > 0)
{
_enam0Delay++;
if (_enam0Delay == 3)
{
_enam0Delay = 0;
_player0.Missile.Enabled = _enam0Val;
}
}
if (_enam1Delay > 0)
{
_enam1Delay++;
if (_enam1Delay == 3)
{
_enam1Delay = 0;
_player1.Missile.Enabled = _enam1Val;
}
}
if (_enambDelay > 0)
{
_enambDelay++;
if (_enambDelay == 3)
{
_enambDelay = 0;
_ball.Enabled = _enambVal;
}
}
if (_prg0Delay > 0)
{
_prg0Delay++;
if (_prg0Delay == 3)
{
_prg0Delay = 0;
_player0.Grp = _prg0Val;
_player1.Dgrp = _player1.Grp;
}
}
if (_prg1Delay > 0)
{
_prg1Delay++;
if (_prg1Delay == 3)
{
_prg1Delay = 0;
_player1.Grp = _prg1Val;
_player0.Dgrp = _player0.Grp;
// TODO: Find a game that uses this functionality and test it
_ball.Denabled = _ball.Enabled;
}
}
if (_hmp0Delay > 0)
{
_hmp0Delay++;
if (_hmp0Delay == 4)
{
_hmp0Delay = 0;
_player0.HM = _hmp0Val;
}
}
if (_hmp1Delay > 0)
{
_hmp1Delay++;
if (_hmp1Delay == 3)
{
_hmp1Delay = 0;
_player1.HM = _hmp1Val;
}
}
// Reset the RDY flag when we reach hblank
if (_hsyncCnt <= 0)
{
_core.Cpu.RDY = true;
}
// Assume we're on the left side of the screen for now
var rightSide = false;
// ---- Things that happen only in the drawing section ----
// TODO: Remove this magic number (17). It depends on the HMOVE
if (_hsyncCnt >= (_hmove.LateHBlankReset ? 76 : 68))
{
_doTicks = false;
// TODO: Remove this magic number
if ((_hsyncCnt / 4) >= 37)
{
rightSide = true;
}
// The bit number of the PF data which we want
int pfBit = ((_hsyncCnt / 4) - 17) % 20;
// Create the mask for the bit we want
// Note that bits are arranged 0 1 2 3 4 .. 19
int pfMask = 1 << (20 - 1 - pfBit);
// Reverse the mask if on the right and playfield is reflected
if (rightSide && _playField.Reflect)
{
pfMask = ReverseBits(pfMask, 20);
}
// Calculate collisions
byte collisions = 0x00;
if ((_playField.Grp & pfMask) != 0)
{
collisions |= CXPF;
}
// ---- Player 0 ----
collisions |= _player0.Tick() ? CXP0 : (byte)0x00;
// ---- Missile 0 ----
collisions |= _player0.Missile.Tick() ? CXM0 : (byte)0x00;
// ---- Player 1 ----
collisions |= _player1.Tick() ? CXP1 : (byte)0x00;
// ---- Missile 0 ----
collisions |= _player1.Missile.Tick() ? CXM1 : (byte)0x00;
// ---- Ball ----
collisions |= _ball.Tick() ? CXBL : (byte)0x00;
// Pick the pixel color from collisions
int pixelColor = BackColor;
if (_core.Settings.ShowBG)
{
pixelColor = _palette[_playField.BkColor];
}
if ((collisions & CXPF) != 0 && _core.Settings.ShowPlayfield)
{
if (_playField.Score)
{
pixelColor = !rightSide
? _palette[_player0.Color]
: _palette[_player1.Color];
}
else
{
pixelColor = _palette[_playField.PfColor];
}
}
if ((collisions & CXBL) != 0)
{
_ball.Collisions |= collisions;
if (_core.Settings.ShowBall)
{
pixelColor = _palette[_playField.PfColor];
}
}
if ((collisions & CXM1) != 0)
{
_player1.Missile.Collisions |= collisions;
if (_core.Settings.ShowMissle2)
{
pixelColor = _palette[_player1.Color];
}
}
if ((collisions & CXP1) != 0)
{
_player1.Collisions |= collisions;
if (_core.Settings.ShowPlayer2)
{
pixelColor = _palette[_player1.Color];
}
}
if ((collisions & CXM0) != 0)
{
_player0.Missile.Collisions |= collisions;
if (_core.Settings.ShowMissle1)
{
pixelColor = _palette[_player0.Color];
}
}
if ((collisions & CXP0) != 0)
{
_player0.Collisions |= collisions;
if (_core.Settings.ShowPlayer1)
{
pixelColor = _palette[_player0.Color];
}
}
if (_playField.Score && !_playField.Priority && ((collisions & CXPF) != 0) && _core.Settings.ShowPlayfield)
{
pixelColor = !rightSide ? _palette[_player0.Color] : _palette[_player1.Color];
}
if (_playField.Priority && (collisions & CXPF) != 0 && _core.Settings.ShowPlayfield)
{
pixelColor = _palette[_playField.PfColor];
}
// Handle vblank
if (_vblankEnabled)
{
pixelColor = BackColor;
}
// Add the pixel to the scanline
// TODO: Remove this magic number (68)
int y = _currentScanLine;
// y >= max screen height means lag frame or game crashed, but is a legal situation.
// either way, there's nothing to display
if (y < MaxScreenHeight)
{
int x = _hsyncCnt - 68;
if (x < 0 || x > 159) // this can't happen, right?
{
throw new Exception(); // TODO
}
_scanlinebuffer[(_currentScanLine * ScreenWidth) + x] = pixelColor;
}
}
else
{
_doTicks = true;
}
// if extended HBLank is active, the screen area still needs a color
if (_hsyncCnt >= 68 && _hsyncCnt < 76 && _hmove.LateHBlankReset)
{
int pixelColor = 0;
// Add the pixel to the scanline
// TODO: Remove this magic number (68)
int y = _currentScanLine;
// y >= max screen height means lag frame or game crashed, but is a legal situation.
// either way, there's nothing to display
if (y < MaxScreenHeight)
{
int x = _hsyncCnt - 68;
if (x < 0 || x > 159) // this can't happen, right?
{
throw new Exception(); // TODO
}
_scanlinebuffer[(_currentScanLine * ScreenWidth) + x] = pixelColor;
}
}
// Handle HMOVE
if (_hmove.HMoveEnabled)
{
if (_hmove.DecCntEnabled)
{
// Actually do stuff only evey 4 pulses
if (_hmove.HMoveCnt == 0)
{
// If the latch is still set
if (_hmove.Player0Latch)
{
// If the move counter still has a bit in common with the HM register
if (((15 - _hmove.Player0Cnt) ^ ((_player0.HM & 0x07) | ((~(_player0.HM & 0x08)) & 0x08))) != 0x0F)
{
_p0Stuff = true;
}
else
{
_hmove.Player0Latch = false;
}
}
if (_hmove.Missile0Latch)
{
// If the move counter still has a bit in common with the HM register
if (((15 - _hmove.Missile0Cnt) ^ ((_player0.Missile.Hm & 0x07) | ((~(_player0.Missile.Hm & 0x08)) & 0x08))) != 0x0F)
{
_m0Stuff = true;
}
else
{
_hmove.Missile0Latch = false;
}
}
if (_hmove.Player1Latch)
{
// If the move counter still has a bit in common with the HM register
if (((15 - _hmove.Player1Cnt) ^ ((_player1.HM & 0x07) | ((~(_player1.HM & 0x08)) & 0x08))) != 0x0F)
{
_p1Stuff = true;
}
else
{
_hmove.Player1Latch = false;
}
}
if (_hmove.Missile1Latch)
{
// If the move counter still has a bit in common with the HM register
if (((15 - _hmove.Missile1Cnt) ^ ((_player1.Missile.Hm & 0x07) | ((~(_player1.Missile.Hm & 0x08)) & 0x08))) != 0x0F)
{
_m1Stuff = true;
}
else
{
_hmove.Missile1Latch = false;
}
}
if (_hmove.BallLatch)
{
// If the move counter still has a bit in common with the HM register
if (((15 - _hmove.BallCnt) ^ ((_ball.HM & 0x07) | ((~(_ball.HM & 0x08)) & 0x08))) != 0x0F)
{
_bStuff = true;
}
else
{
_hmove.BallLatch = false;
}
}
if (!_hmove.Player0Latch && !_hmove.Player1Latch && !_hmove.BallLatch && !_hmove.Missile0Latch && !_hmove.Missile1Latch)
{
_hmove.HMoveEnabled = false;
_hmove.DecCntEnabled = false;
_hmove.HMoveDelayCnt = 0;
}
}
_hmove.HMoveCnt++;
_hmove.HMoveCnt %= 4;
if (_p0Stuff && _hsyncCnt % 4 == 0)
{
_p0Stuff = false;
// "Clock-Stuffing"
if (_doTicks)
{
_player0.Tick();
}
// Increase by 1, max of 15
_hmove.test_count_p0++;
if (_hmove.test_count_p0 < 16)
{
_hmove.Player0Cnt++;
}
else
{
_hmove.Player0Cnt = 0;
}
}
if (_p1Stuff && _hsyncCnt % 4 == 0)
{
_p1Stuff = false;
// "Clock-Stuffing"
if (_doTicks)
{
_player1.Tick();
}
// Increase by 1, max of 15
_hmove.test_count_p1++;
if (_hmove.test_count_p1 < 16)
{
_hmove.Player1Cnt++;
}
else
{
_hmove.Player1Cnt = 0;
}
}
if (_m0Stuff && _hsyncCnt % 4 == 0)
{
_m0Stuff = false;
// "Clock-Stuffing"
if (_doTicks)
{
_player0.Missile.Tick();
}
// Increase by 1, max of 15
_hmove.test_count_m0++;
if (_hmove.test_count_m0 < 16)
{
_hmove.Missile0Cnt++;
}
else
{
_hmove.Missile0Cnt = 0;
}
}
if (_m1Stuff && _hsyncCnt % 4 == 0)
{
_m1Stuff = false;
// "Clock-Stuffing"
if (_doTicks)
{
_player1.Missile.Tick();
}
// Increase by 1, max of 15
_hmove.test_count_m1++;
if (_hmove.test_count_m1 < 16)
{
_hmove.Missile1Cnt++;
}
else
{
_hmove.Missile1Cnt = 0;
}
}
if (_bStuff && _hsyncCnt % 4 == 0)
{
_bStuff = false;
// "Clock-Stuffing"
if (_doTicks)
{
_ball.Tick();
}
// Increase by 1, max of 15
_hmove.test_count_b++;
if (_hmove.test_count_b < 16)
{
_hmove.BallCnt++;
}
else
{
_hmove.BallCnt = 0;
}
}
}
if (_hmove.HMoveDelayCnt < 5)
{
_hmove.HMoveDelayCnt++;
}
if (_hmove.HMoveDelayCnt == 5)
{
_hmove.HMoveDelayCnt++;
_hmove.HMoveCnt = 0;
_hmove.DecCntEnabled = true;
_hmove.test_count_p0 = 0;
_hmove.test_count_p1 = 0;
_hmove.test_count_m0 = 0;
_hmove.test_count_m1 = 0;
_hmove.test_count_b = 0;
_hmove.Player0Latch = true;
_hmove.Player0Cnt = 0;
_hmove.Missile0Latch = true;
_hmove.Missile0Cnt = 0;
_hmove.Player1Latch = true;
_hmove.Player1Cnt = 0;
_hmove.Missile1Latch = true;
_hmove.Missile1Cnt = 0;
_hmove.BallLatch = true;
_hmove.BallCnt = 0;
_hmove.LateHBlankReset = true;
}
}
// do the audio sampling
if (_hsyncCnt == 36 || _hsyncCnt == 148)
{
if (AudioClocks < 2000)
{
LocalAudioCycles[AudioClocks] += (short)(AUD[0].Cycle() / 2);
LocalAudioCycles[AudioClocks] += (short)(AUD[1].Cycle() / 2);
AudioClocks++;
}
}
// Increment the hsync counter
_hsyncCnt++;
_hsyncCnt %= 228;
// End of the line? Add it to the buffer!
if (_hsyncCnt == 0)
{
_hmove.LateHBlankReset = false;
_currentScanLine++;
LineCount++;
}
}
private void OutputFrame(int validlines)
{
int topLine = _pal ? _core.Settings.PALTopLine : _core.Settings.NTSCTopLine;
int bottomLine = _pal ? _core.Settings.PALBottomLine : _core.Settings.NTSCBottomLine;
// if vsync occured unexpectedly early, black out the remainder
for (; validlines < bottomLine; validlines++)
{
for (int i = 0; i < 160; i++)
{
_scanlinebuffer[(validlines * 160) + i] = BackColor;
}
}
int srcbytes = sizeof(int) * ScreenWidth * topLine;
int count = bottomLine - topLine; // no +1, as the bottom line number is not inclusive
count *= sizeof(int) * ScreenWidth;
Buffer.BlockCopy(_scanlinebuffer, srcbytes, _frameBuffer, 0, count);
}
public byte ReadMemory(ushort addr, bool peek)
{
var maskedAddr = (ushort)(addr & 0x000F);
byte coll = 0;
int mask = 0;
if (maskedAddr == 0x00) // CXM0P
{
coll = (byte)((((_player0.Missile.Collisions & CXP1) != 0) ? 0x80 : 0x00) | (((_player0.Missile.Collisions & CXP0) != 0) ? 0x40 : 0x00));
mask = 0x3f;
}
if (maskedAddr == 0x01) // CXM1P
{
coll = (byte)((((_player1.Missile.Collisions & CXP0) != 0) ? 0x80 : 0x00) | (((_player1.Missile.Collisions & CXP1) != 0) ? 0x40 : 0x00));
mask = 0x3f;
}
if (maskedAddr == 0x02) // CXP0FB
{
coll = (byte)((((_player0.Collisions & CXPF) != 0) ? 0x80 : 0x00) | (((_player0.Collisions & CXBL) != 0) ? 0x40 : 0x00));
mask = 0x3f;
}
if (maskedAddr == 0x03) // CXP1FB
{
coll = (byte)((((_player1.Collisions & CXPF) != 0) ? 0x80 : 0x00) | (((_player1.Collisions & CXBL) != 0) ? 0x40 : 0x00));
mask = 0x3f;
}
if (maskedAddr == 0x04) // CXM0FB
{
coll = (byte)((((_player0.Missile.Collisions & CXPF) != 0) ? 0x80 : 0x00) | (((_player0.Missile.Collisions & CXBL) != 0) ? 0x40 : 0x00));
mask = 0x3f;
}
if (maskedAddr == 0x05) // CXM1FB
{
coll = (byte)((((_player1.Missile.Collisions & CXPF) != 0) ? 0x80 : 0x00) | (((_player1.Missile.Collisions & CXBL) != 0) ? 0x40 : 0x00));
mask = 0x3f;
}
if (maskedAddr == 0x06) // CXBLPF
{
coll = (byte)(((_ball.Collisions & CXPF) != 0) ? 0x80 : 0x00);
mask = 0x7f;
}
if (maskedAddr == 0x07) // CXPPMM
{
coll = (byte)((((_player0.Collisions & CXP1) != 0) ? 0x80 : 0x00) | (((_player0.Missile.Collisions & CXM1) != 0) ? 0x40 : 0x00));
mask = 0x3f;
}
// 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 (_core.ReadPot1(0)>0 && _capCharging && _core.Cpu.TotalExecutedCycles - _capChargeStart >= _core.ReadPot1(0))
{
coll = 0x80;
}
else
{
coll = 0x00;
}
mask = 0x7f;
}
if (maskedAddr == 0x09) // INPT1
{
if (_core.ReadPot1(1) > 0 && _capCharging && _core.Cpu.TotalExecutedCycles - _capChargeStart >= _core.ReadPot1(1))
{
coll = 0x80;
}
else
{
coll = 0x00;
}
mask = 0x7f;
}
if (maskedAddr == 0x0A) // INPT2
{
if (_core.ReadPot2(0) > 0 && _capCharging && _core.Cpu.TotalExecutedCycles - _capChargeStart >= _core.ReadPot2(0))
{
coll = 0x80;
}
else
{
coll = 0x00;
}
mask = 0x7f;
}
if (maskedAddr == 0x0B) // INPT3
{
if (_core.ReadPot2(1) > 0 && _capCharging && _core.Cpu.TotalExecutedCycles - _capChargeStart >= _core.ReadPot2(1))
{
coll = 0x80;
}
else
{
coll = 0x00;
}
mask = 0x7f;
}
if (maskedAddr == 0x0C) // INPT4
{
coll = (byte)((_core.ReadControls1(peek) & 0x08) != 0 ? 0x80 : 0x00);
mask = 0x7f;
}
if (maskedAddr == 0x0D) // INPT5
{
coll = (byte)((_core.ReadControls2(peek) & 0x08) != 0 ? 0x80 : 0x00);
mask = 0x7f;
}
// 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
{
if ((value & 0x02) != 0)
{
// Frame is complete, output to buffer
_vsyncEnabled = true;
}
else if (_vsyncEnabled)
{
// When VSYNC is disabled, this will be the first line of the new frame
// write to frame buffer
OutputFrame(_currentScanLine);
New_Frame = true;
// Clear all from last frame
_currentScanLine = 0;
// Frame is done
_vsyncEnabled = false;
// Do not reset hsync, since we're on the first line of the new frame
// hsyncCnt = 0;
}
}
else if (maskedAddr == 0x01) // VBLANK
{
_vblankDelay = 1;
_vblankValue = value;
_capCharging = (value & 0x80) == 0;
if ((value & 0x80) == 0)
{
_capChargeStart = _core.Cpu.TotalExecutedCycles;
}
}
else if (maskedAddr == 0x02) // WSYNC
{
// Halt the CPU until we reach hblank
_core.Cpu.RDY = false;
}
else if (maskedAddr == 0x04) // NUSIZ0
{
_player0.Nusiz = (byte)(value & 0x37);
_player0.Missile.Size = (byte)((value & 0x30) >> 4);
_player0.Missile.Number = (byte)(value & 0x07);
}
else if (maskedAddr == 0x05) // NUSIZ1
{
_player1.Nusiz = (byte)(value & 0x37);
_player1.Missile.Size = (byte)((value & 0x30) >> 4);
_player1.Missile.Number = (byte)(value & 0x07);
}
else if (maskedAddr == 0x06) // COLUP0
{
_player0.Color = (byte)(value & 0xFE);
}
else if (maskedAddr == 0x07) // COLUP1
{
_player1.Color = (byte)(value & 0xFE);
}
else if (maskedAddr == 0x08) // COLUPF
{
_playField.PfColor = (byte)(value & 0xFE);
}
else if (maskedAddr == 0x09) // COLUBK
{
_playField.BkColor = (byte)(value & 0xFE);
}
else if (maskedAddr == 0x0A) // CTRLPF
{
_playField.Reflect = (value & 0x01) != 0;
_playField.Score = (value & 0x02) != 0;
_playField.Priority = (value & 0x04) != 0;
_ball.Size = (byte)((value & 0x30) >> 4);
}
else if (maskedAddr == 0x0B) // REFP0
{
_player0.Reflect = (value & 0x08) != 0;
}
else if (maskedAddr == 0x0C) // REFP1
{
_player1.Reflect = (value & 0x08) != 0;
}
else if (maskedAddr == 0x0D) // PF0
{
_pf0Update = value;
_pf0Updater = true;
_pf0DelayClock = 0;
if (((_hsyncCnt / 3) & 3) == 0)
{
_pf0MaxDelay = 4;
}
if (((_hsyncCnt / 3) & 3) == 1)
{
_pf0MaxDelay = 5;
}
if (((_hsyncCnt / 3) & 3) == 2)
{
_pf0MaxDelay = 2;
}
if (((_hsyncCnt / 3) & 3) == 3)
{
_pf0MaxDelay = 3;
}
////_playField.Grp = (uint)((_playField.Grp & 0x0FFFF) + ((ReverseBits(value, 8) & 0x0F) << 16));
}
else if (maskedAddr == 0x0E) // PF1
{
_pf1Update = value;
_pf1Updater = true;
_pf1DelayClock = 0;
if (((_hsyncCnt / 3) & 3) == 0)
{
_pf1MaxDelay = 4;
}
if (((_hsyncCnt / 3) & 3) == 1)
{
_pf1MaxDelay = 5;
}
if (((_hsyncCnt / 3) & 3) == 2)
{
_pf1MaxDelay = 2;
}
if (((_hsyncCnt / 3) & 3) == 3)
{
_pf1MaxDelay = 3;
}
////_playField.Grp = (uint)((_playField.Grp & 0xF00FF) + (value << 8));
}
else if (maskedAddr == 0x0F) // PF2
{
_pf2Update = value;
_pf2Updater = true;
_pf2DelayClock = 0;
if (((_hsyncCnt / 3) & 3) == 0)
{
_pf2MaxDelay = 4;
}
if (((_hsyncCnt / 3) & 3) == 1)
{
_pf2MaxDelay = 5;
}
if (((_hsyncCnt / 3) & 3) == 2)
{
_pf2MaxDelay = 2;
}
if (((_hsyncCnt / 3) & 3) == 3)
{
_pf2MaxDelay = 3;
}
////_playField.Grp = (uint)((_playField.Grp & 0xFFF00) + ReverseBits(value, 8));
}
else if (maskedAddr == 0x10) // RESP0
{
// Resp depends on HMOVE
if (!_hmove.LateHBlankReset)
{
_player0.HPosCnt = (byte)(_hsyncCnt < 68 ? 160 - 2 : 160 - 4);
if (_hsyncCnt == 67 || _hsyncCnt==0)
{
_player0.HPosCnt = 160 - 3;
}
}
else
{
_player0.HPosCnt = (byte)(_hsyncCnt < 76 ? 160 - 2 : 160 - 4);
if (_hsyncCnt == 75 || _hsyncCnt == 0)
{
_player0.HPosCnt = 160 - 3;
}
}
}
else if (maskedAddr == 0x11) // RESP1
{
// RESP depends on HMOVE
if (!_hmove.LateHBlankReset)
{
_player1.HPosCnt = (byte)(_hsyncCnt < 68 ? 160 - 2 : 160 - 4);
if (_hsyncCnt == 67 || _hsyncCnt == 0)
{
_player1.HPosCnt = 160 - 3;
}
}
else
{
_player1.HPosCnt = (byte)(_hsyncCnt < 76 ? 160 - 2 : 160 - 4);
if (_hsyncCnt == 75 || _hsyncCnt == 0)
{
_player1.HPosCnt = 160 - 3;
}
}
}
else if (maskedAddr == 0x12) // RESM0
{
if (!_hmove.LateHBlankReset)
{
_player0.Missile.HPosCnt = (byte)(_hsyncCnt < 68 ? 160 - 2 : 160 - 4);
if (_hsyncCnt == 67 || _hsyncCnt == 0)
{
_player0.Missile.HPosCnt = 160 - 3;
}
}
else
{
_player0.Missile.HPosCnt = (byte)(_hsyncCnt < 76 ? 160 - 2 : 160 - 4);
if (_hsyncCnt == 75 || _hsyncCnt == 0)
{
_player0.Missile.HPosCnt = 160 - 3;
}
}
}
else if (maskedAddr == 0x13) // RESM1
{
if (!_hmove.LateHBlankReset)
{
_player1.Missile.HPosCnt = (byte)(_hsyncCnt < 68 ? 160 - 2 : 160 - 4);
if (_hsyncCnt == 67 || _hsyncCnt == 0)
{
_player1.Missile.HPosCnt = 160 - 3;
}
}
else
{
_player1.Missile.HPosCnt = (byte)(_hsyncCnt < 76 ? 160 - 2 : 160 - 4);
if (_hsyncCnt == 75 || _hsyncCnt == 0)
{
_player1.Missile.HPosCnt = 160 - 3;
}
}
}
else if (maskedAddr == 0x14) // RESBL
{
if (!_hmove.LateHBlankReset)
{
_ball.HPosCnt = (byte)(_hsyncCnt < 68 ? 160 - 2 : 160 - 4);
if (_hsyncCnt == 67 || _hsyncCnt == 0)
{
_ball.HPosCnt = 160 - 3;
}
}
else
{
_ball.HPosCnt = (byte)(_hsyncCnt < 76 ? 160 - 2 : 160 - 4);
if (_hsyncCnt == 75 || _hsyncCnt == 0)
{
_ball.HPosCnt = 160 - 3;
}
}
}
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
{
_prg0Val = value;
_prg0Delay = 1;
}
else if (maskedAddr == 0x1C) // GRP1
{
_prg1Val = value;
_prg1Delay = 1;
}
else if (maskedAddr == 0x1D) // ENAM0
{
_enam0Val = (value & 0x02) != 0;
_enam0Delay = 1;
}
else if (maskedAddr == 0x1E) // ENAM1
{
_enam1Val = (value & 0x02) != 0;
_enam1Delay = 1;
}
else if (maskedAddr == 0x1F) // ENABL
{
_enambVal = (value & 0x02) != 0;
_enambDelay = 1;
}
else if (maskedAddr == 0x20) // HMP0
{
_hmp0Val = (byte)((value & 0xF0) >> 4);
_hmp0Delay = 1;
}
else if (maskedAddr == 0x21) // HMP1
{
_hmp1Val = (byte)((value & 0xF0) >> 4);
_hmp1Delay = 1;
}
else if (maskedAddr == 0x22) // HMM0
{
_player0.Missile.Hm = (byte)((value & 0xF0) >> 4);
}
else if (maskedAddr == 0x23) // HMM1
{
_player1.Missile.Hm = (byte)((value & 0xF0) >> 4);
}
else if (maskedAddr == 0x24) // HMBL
{
_ball.HM = (byte)((value & 0xF0) >> 4);
}
else if (maskedAddr == 0x25) // VDELP0
{
_player0.Delay = (value & 0x01) != 0;
}
else if (maskedAddr == 0x26) // VDELP1
{
_player1.Delay = (value & 0x01) != 0;
}
else if (maskedAddr == 0x27) // VDELBL
{
_ball.Delay = (value & 0x01) != 0;
}
else if (maskedAddr == 0x28) // RESMP0
{
_player0.Missile.ResetToPlayer = (value & 0x02) != 0;
}
else if (maskedAddr == 0x29) // RESMP1
{
_player1.Missile.ResetToPlayer = (value & 0x02) != 0;
}
else if (maskedAddr == 0x2A) // HMOVE
{
_hmove.HMoveEnabled = true;
_hmove.HMoveDelayCnt = 0;
}
else if (maskedAddr == 0x2B) // HMCLR
{
_player0.HM = 0;
_player0.Missile.Hm = 0;
_player1.HM = 0;
_player1.Missile.Hm = 0;
_ball.HM = 0;
}
else if (maskedAddr == 0x2C) // CXCLR
{
_player0.Collisions = 0;
_player0.Missile.Collisions = 0;
_player1.Collisions = 0;
_player1.Missile.Collisions = 0;
_ball.Collisions = 0;
}
}
private enum AudioRegister : byte
{
AUDC, AUDF, AUDV
}
}
}