ZXHawk: Overhaul datacorder and cassette loading code. This should now be more accurate and fixes a number of loading isues with particular games and loading schemes. There be desync dragons here!! Fixes #1446

This commit is contained in:
Matt Burgess 2021-06-11 23:41:14 +01:00
parent 160217ef74
commit 51a67a947a
9 changed files with 1525 additions and 1123 deletions

View File

@ -122,14 +122,6 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
MonitorFrame(); MonitorFrame();
} }
/// <summary>
/// No longer in use
/// </summary>
public void StartFrame()
{
//_buzzer.ProcessPulseValue(currentState);
}
/// <summary> /// <summary>
/// Starts the tape playing from the beginning of the current block /// Starts the tape playing from the beginning of the current block
/// </summary> /// </summary>
@ -466,27 +458,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
cycles -= _waitEdge; cycles -= _waitEdge;
if (_position == 0 && _tapeIsPlaying) if (_position == 0 && _tapeIsPlaying)
{ {
// start of block - take care of initial pulse level for PZX
switch (_dataBlocks[_currentDataBlockIndex].BlockDescription)
{
case BlockType.PULS:
// initial pulse level is always low
if (currentState)
FlipTapeState();
break;
case BlockType.DATA:
// initial pulse level is stored in block
if (currentState != _dataBlocks[_currentDataBlockIndex].InitialPulseLevel)
FlipTapeState();
break;
case BlockType.PAUS:
// initial pulse level is stored in block
if (currentState != _dataBlocks[_currentDataBlockIndex].InitialPulseLevel)
FlipTapeState();
break;
}
// notify about the current block // notify about the current block
var bl = _dataBlocks[_currentDataBlockIndex]; var bl = _dataBlocks[_currentDataBlockIndex];
@ -595,7 +567,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
_waitEdge = _dataBlocks[_currentDataBlockIndex].DataPeriods.Count > 0 ? _dataBlocks[_currentDataBlockIndex].DataPeriods[_position] : 0; _waitEdge = _dataBlocks[_currentDataBlockIndex].DataPeriods.Count > 0 ? _dataBlocks[_currentDataBlockIndex].DataPeriods[_position] : 0;
// flip the current state // flip the current state
FlipTapeState(); //FlipTapeState();
currentState = _dataBlocks[_currentDataBlockIndex].DataLevels[_position];
} }
// update lastCycle and return currentstate // update lastCycle and return currentstate

View File

@ -208,17 +208,17 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
CPU.RegPC = 0; CPU.RegPC = 0;
Spectrum.SetCpuRegister("SP", 0xFFFF); Spectrum.SetCpuRegister("SP", 0xFFFF);
Spectrum.SetCpuRegister("IY", 0xFFFF); Spectrum.SetCpuRegister("IY", 0);
Spectrum.SetCpuRegister("IX", 0xFFFF); Spectrum.SetCpuRegister("IX", 0);
Spectrum.SetCpuRegister("AF", 0xFFFF); Spectrum.SetCpuRegister("AF", 0xFFFF);
Spectrum.SetCpuRegister("BC", 0xFFFF); Spectrum.SetCpuRegister("BC", 0);
Spectrum.SetCpuRegister("DE", 0xFFFF); Spectrum.SetCpuRegister("DE", 0);
Spectrum.SetCpuRegister("HL", 0xFFFF); Spectrum.SetCpuRegister("HL", 0);
Spectrum.SetCpuRegister("SP", 0xFFFF); Spectrum.SetCpuRegister("SP", 0xFFFF);
Spectrum.SetCpuRegister("Shadow AF", 0xFFFF); Spectrum.SetCpuRegister("Shadow AF", 0xFFFF);
Spectrum.SetCpuRegister("Shadow BC", 0xFFFF); Spectrum.SetCpuRegister("Shadow BC", 0);
Spectrum.SetCpuRegister("Shadow DE", 0xFFFF); Spectrum.SetCpuRegister("Shadow DE", 0);
Spectrum.SetCpuRegister("Shadow HL", 0xFFFF); Spectrum.SetCpuRegister("Shadow HL", 0);
CPU.Regs[CPU.I] = 0; CPU.Regs[CPU.I] = 0;
CPU.Regs[CPU.R] = 0; CPU.Regs[CPU.R] = 0;
@ -260,17 +260,16 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
CPU.RegPC = 0; CPU.RegPC = 0;
Spectrum.SetCpuRegister("SP", 0xFFFF); Spectrum.SetCpuRegister("SP", 0xFFFF);
Spectrum.SetCpuRegister("IY", 0xFFFF); //Spectrum.SetCpuRegister("IY", 0xFFFF);
Spectrum.SetCpuRegister("IX", 0xFFFF); //Spectrum.SetCpuRegister("IX", 0xFFFF);
Spectrum.SetCpuRegister("AF", 0xFFFF); Spectrum.SetCpuRegister("AF", 0xFFFF);
Spectrum.SetCpuRegister("BC", 0xFFFF); //Spectrum.SetCpuRegister("BC", 0xFFFF);
Spectrum.SetCpuRegister("DE", 0xFFFF); //Spectrum.SetCpuRegister("DE", 0xFFFF);
Spectrum.SetCpuRegister("HL", 0xFFFF); //Spectrum.SetCpuRegister("HL", 0xFFFF);
Spectrum.SetCpuRegister("SP", 0xFFFF);
Spectrum.SetCpuRegister("Shadow AF", 0xFFFF); Spectrum.SetCpuRegister("Shadow AF", 0xFFFF);
Spectrum.SetCpuRegister("Shadow BC", 0xFFFF); //Spectrum.SetCpuRegister("Shadow BC", 0xFFFF);
Spectrum.SetCpuRegister("Shadow DE", 0xFFFF); //Spectrum.SetCpuRegister("Shadow DE", 0xFFFF);
Spectrum.SetCpuRegister("Shadow HL", 0xFFFF); //Spectrum.SetCpuRegister("Shadow HL", 0xFFFF);
CPU.Regs[CPU.I] = 0; CPU.Regs[CPU.I] = 0;
CPU.Regs[CPU.R] = 0; CPU.Regs[CPU.R] = 0;

View File

@ -145,15 +145,35 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
/// Takes a PauseInMilliseconds value and returns the value in T-States /// Takes a PauseInMilliseconds value and returns the value in T-States
/// </summary> /// </summary>
public static int TranslatePause(int pauseInMS) public static int TranslatePause(int pauseInMS)
{ {
// t-states per millisecond var tspms = (double)(69888 * 50) / (double)1000;
var tspms = (69888 * 50) / 1000; int res = (int)(pauseInMS * tspms);
// get value
int res = pauseInMS * tspms;
return res; return res;
} }
/// <summary>
/// Caluclate a data block XOR checksum
/// </summary>
/// <param name="buf"></param>
/// <param name="len"></param>
/// <returns></returns>
public static bool CheckChecksum(byte[] buf, int len)
{
byte c = 0;
for (int n = 0; n < len - 1; n++)
{
c ^= buf[n];
}
if (c == buf[len - 1])
{
return true;
}
return false;
}
/// <summary> /// <summary>
/// Decompresses a byte array that is Z-RLE compressed /// Decompresses a byte array that is Z-RLE compressed
/// </summary> /// </summary>

View File

@ -197,12 +197,15 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
t.BlockDescription = BlockType.CSW_Recording; t.BlockDescription = BlockType.CSW_Recording;
t.BlockID = 0x18; t.BlockID = 0x18;
t.DataPeriods = new List<int>(); t.DataPeriods = new List<int>();
t.DataLevels = new List<bool>();
if (flags.Bit(0)) if (flags.Bit(0))
t.InitialPulseLevel = true; t.InitialPulseLevel = true;
else else
t.InitialPulseLevel = false; t.InitialPulseLevel = false;
bool currLevel = !t.InitialPulseLevel;
var rate = (69888 * 50) / sampleRate; var rate = (69888 * 50) / sampleRate;
for (int i = 0; i < cswDataUncompressed.Length;) for (int i = 0; i < cswDataUncompressed.Length;)
@ -215,10 +218,15 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
} }
t.DataPeriods.Add(length); t.DataPeriods.Add(length);
currLevel = !currLevel;
t.DataLevels.Add(currLevel);
} }
// add closing period // add closing period
t.DataPeriods.Add((69888 * 50) / 10); t.DataPeriods.Add((69888 * 50) / 10);
currLevel = !currLevel;
t.DataLevels.Add(currLevel);
// add to datacorder // add to datacorder
_datacorder.DataBlocks.Add(t); _datacorder.DataBlocks.Add(t);

View File

@ -158,8 +158,10 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
t.BlockID = GetInt32(b, 0); t.BlockID = GetInt32(b, 0);
t.DataPeriods = new List<int>(); t.DataPeriods = new List<int>();
t.DataLevels = new List<bool>();
t.InitialPulseLevel = false; t.InitialPulseLevel = false;
bool pLevel = !t.InitialPulseLevel;
List<ushort[]> pulses = new List<ushort[]>(); List<ushort[]> pulses = new List<ushort[]>();
@ -197,6 +199,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
for (int i = 0; i < x[0]; i++) for (int i = 0; i < x[0]; i++)
{ {
t.DataPeriods.Add(x[1]); t.DataPeriods.Add(x[1]);
pLevel = !pLevel;
t.DataLevels.Add(pLevel);
} }
} }
@ -220,6 +224,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
t.BlockID = GetInt32(b, 0); t.BlockID = GetInt32(b, 0);
t.DataPeriods = new List<int>(); t.DataPeriods = new List<int>();
t.DataLevels = new List<bool>();
List<ushort> s0 = new List<ushort>(); List<ushort> s0 = new List<ushort>();
List<ushort> s1 = new List<ushort>(); List<ushort> s1 = new List<ushort>();
@ -235,6 +240,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
initPulseLevel = (uint)((dCount & 0x80000000) == 0 ? 0 : 1); initPulseLevel = (uint)((dCount & 0x80000000) == 0 ? 0 : 1);
t.InitialPulseLevel = initPulseLevel == 1; t.InitialPulseLevel = initPulseLevel == 1;
bool bLevel = !t.InitialPulseLevel;
dCount = (int)(dCount & 0x7FFFFFFF); dCount = (int)(dCount & 0x7FFFFFFF);
pos += 4; pos += 4;
@ -274,6 +280,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
foreach (var pu in s1) foreach (var pu in s1)
{ {
t.DataPeriods.Add((int)pu); t.DataPeriods.Add((int)pu);
bLevel = !bLevel;
t.DataLevels.Add(bLevel);
} }
} }
@ -282,13 +290,20 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
foreach (var pu in s0) foreach (var pu in s0)
{ {
t.DataPeriods.Add((int)pu); t.DataPeriods.Add((int)pu);
bLevel = !bLevel;
t.DataLevels.Add(bLevel);
} }
} }
} }
} }
if (tail > 0) if (tail > 0)
{
t.DataPeriods.Add(tail); t.DataPeriods.Add(tail);
bLevel = !bLevel;
t.DataLevels.Add(bLevel);
}
dData.Clear(); dData.Clear();
} }
@ -320,12 +335,21 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
var d = GetInt32(b, pos); var d = GetInt32(b, pos);
iniPulseLevel = ((d & 0x80000000) == 0 ? 0 : 1); iniPulseLevel = ((d & 0x80000000) == 0 ? 0 : 1);
t.InitialPulseLevel = iniPulseLevel == 1; t.InitialPulseLevel = iniPulseLevel == 1;
bool paLevel = !t.InitialPulseLevel;
pCount = (d & 0x7FFFFFFF); pCount = (d & 0x7FFFFFFF);
// convert to tape block // convert to tape block
t.BlockDescription = BlockType.PAUS; t.BlockDescription = BlockType.PAUS;
paLevel = !paLevel;
t.DataLevels.Add(paLevel);
t.DataPeriods.Add(0); t.DataPeriods.Add(0);
paLevel = !paLevel;
t.DataLevels.Add(paLevel);
t.DataPeriods.Add(pCount); t.DataPeriods.Add(pCount);
paLevel = !paLevel;
t.DataLevels.Add(paLevel);
t.DataPeriods.Add(0); t.DataPeriods.Add(0);
_datacorder.DataBlocks.Add(t); _datacorder.DataBlocks.Add(t);

View File

@ -277,16 +277,25 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
// create a list to hold the data periods // create a list to hold the data periods
List<int> dataPeriods = new List<int>(); List<int> dataPeriods = new List<int>();
List<bool> dataLevels = new List<bool>();
bool currLevel = false;
// generate pilot pulses // generate pilot pulses
for (int i = 0; i < pilotLength; i++) for (int i = 0; i < pilotLength; i++)
{ {
dataPeriods.Add(PILOT_PL); dataPeriods.Add(PILOT_PL);
currLevel = !currLevel;
dataLevels.Add(currLevel);
} }
// add syncro pulses // add syncro pulses
dataPeriods.Add(SYNC_1_PL); dataPeriods.Add(SYNC_1_PL);
currLevel = !currLevel;
dataLevels.Add(currLevel);
dataPeriods.Add(SYNC_2_PL); dataPeriods.Add(SYNC_2_PL);
currLevel = !currLevel;
dataLevels.Add(currLevel);
int pos = 0; int pos = 0;
@ -296,13 +305,33 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
for (byte b = 0x80; b != 0; b >>= 1) for (byte b = 0x80; b != 0; b >>= 1)
{ {
if ((blockdata[i] & b) != 0) if ((blockdata[i] & b) != 0)
{
dataPeriods.Add(BIT_1_PL); dataPeriods.Add(BIT_1_PL);
currLevel = !currLevel;
dataLevels.Add(currLevel);
}
else else
{
dataPeriods.Add(BIT_0_PL); dataPeriods.Add(BIT_0_PL);
currLevel = !currLevel;
dataLevels.Add(currLevel);
}
if ((blockdata[i] & b) != 0) if ((blockdata[i] & b) != 0)
{
dataPeriods.Add(BIT_1_PL); dataPeriods.Add(BIT_1_PL);
currLevel = !currLevel;
dataLevels.Add(currLevel);
}
else else
{
dataPeriods.Add(BIT_0_PL); dataPeriods.Add(BIT_0_PL);
currLevel = !currLevel;
dataLevels.Add(currLevel);
}
} }
} }
@ -310,13 +339,29 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
for (byte c = 0x80; c != (byte)(0x80 >> BIT_COUNT_IN_LAST); c >>= 1) for (byte c = 0x80; c != (byte)(0x80 >> BIT_COUNT_IN_LAST); c >>= 1)
{ {
if ((blockdata[pos] & c) != 0) if ((blockdata[pos] & c) != 0)
{
dataPeriods.Add(BIT_1_PL); dataPeriods.Add(BIT_1_PL);
currLevel = !currLevel;
dataLevels.Add(currLevel);
}
else else
{
dataPeriods.Add(BIT_0_PL); dataPeriods.Add(BIT_0_PL);
currLevel = !currLevel;
dataLevels.Add(currLevel);
}
if ((blockdata[pos] & c) != 0) if ((blockdata[pos] & c) != 0)
{
dataPeriods.Add(BIT_1_PL); dataPeriods.Add(BIT_1_PL);
currLevel = !currLevel;
dataLevels.Add(currLevel);
}
else else
{
dataPeriods.Add(BIT_0_PL); dataPeriods.Add(BIT_0_PL);
currLevel = !currLevel;
dataLevels.Add(currLevel);
}
} }
// add block pause // add block pause
@ -325,14 +370,27 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
// default pause for tap files // default pause for tap files
tdb.PauseInMS = 1000; tdb.PauseInMS = 1000;
tdb.PauseInTStates = TranslatePause(tdb.PauseInMS);
// small inversion
dataPeriods.Add(3476);
currLevel = !currLevel;
dataLevels.Add(currLevel);
// actual pause
dataPeriods.Add(tdb.PauseInTStates - 3476);
currLevel = !currLevel;
dataLevels.Add(currLevel);
// add to the tapedatablock object // add to the tapedatablock object
tdb.DataPeriods = dataPeriods; tdb.DataPeriods = dataPeriods;
tdb.DataLevels = dataLevels;
// add the raw data // add the raw data
tdb.BlockData = blockdata; tdb.BlockData = blockdata;
// generate separate PAUS block // generate separate PAUS block
/*
TapeDataBlock tdbPause = new TapeDataBlock(); TapeDataBlock tdbPause = new TapeDataBlock();
tdbPause.DataPeriods = new List<int>(); tdbPause.DataPeriods = new List<int>();
tdbPause.BlockDescription = BlockType.PAUSE_BLOCK; tdbPause.BlockDescription = BlockType.PAUSE_BLOCK;
@ -341,10 +399,13 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
//if (pauseInTStates > 0) //if (pauseInTStates > 0)
//tdbPause.DataPeriods.Add(pauseInTStates); //tdbPause.DataPeriods.Add(pauseInTStates);
tdb.PauseInMS = 0; tdb.PauseInMS = 0;
tdb.PauseInTStates = pauseInTStates;
*/
// add block to the tape // add block to the tape
_datacorder.DataBlocks.Add(tdb); _datacorder.DataBlocks.Add(tdb);
/*
// PAUS block if neccessary // PAUS block if neccessary
if (pauseInTStates > 0) if (pauseInTStates > 0)
{ {
@ -366,6 +427,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
_datacorder.DataBlocks.Add(tdbPause); _datacorder.DataBlocks.Add(tdbPause);
} }
*/
} }
} }
} }

View File

@ -9,6 +9,12 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
/// </summary> /// </summary>
public class TapeDataBlock public class TapeDataBlock
{ {
public TapeDataBlock()
{
DataPeriods = new List<int>();
DataLevels = new List<bool>();
}
/// <summary> /// <summary>
/// Either the TZX block ID, or -1 in the case of non-tzx blocks /// Either the TZX block ID, or -1 in the case of non-tzx blocks
/// </summary> /// </summary>
@ -77,6 +83,13 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
/// </summary> /// </summary>
public List<int> DataPeriods = new List<int>(); public List<int> DataPeriods = new List<int>();
/// <summary>
/// List containing the pulse levels (in relation to the pulse timing values)
/// </summary>
public List<bool> DataLevels = new List<bool>();
public List<string> PulseDescription = new List<string>();
public bool InitialPulseLevel; public bool InitialPulseLevel;
/// <summary> /// <summary>
@ -91,7 +104,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
} }
/// <summary> /// <summary>
/// The defined post-block pause /// The defined post-block pause in MS
/// </summary> /// </summary>
private int _pauseInMS; private int _pauseInMS;
public int PauseInMS public int PauseInMS
@ -100,6 +113,15 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
set => _pauseInMS = value; set => _pauseInMS = value;
} }
/// <summary>
/// The defined post-block pause in T-States
/// </summary>
private int _pauseInTStates;
public int PauseInTStates
{
get { return _pauseInTStates; }
set { _pauseInTStates = value; }
}
/// <summary> /// <summary>
/// Returns the data periods as an array /// Returns the data periods as an array
@ -226,6 +248,10 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
Pulse_Length, Pulse_Length,
Pulse_Count, Pulse_Count,
TStatesPerSample,
Archive_Info,
Custom_Info,
Text_Description, Text_Description,
Title, Title,
Publisher, Publisher,

View File

@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq;
using System.Text; using System.Text;
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
@ -85,7 +86,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
WavStreamReader reader = new WavStreamReader(stream); WavStreamReader reader = new WavStreamReader(stream);
int rate = (69888 * 50) / reader.Header.sampleRate; //int rate = (int)(((double)69888 * (double)50) / (double)reader.Header.sampleRate);
int rate = (int)((double)(3500000) / (double)reader.Header.sampleRate);
int smpCounter = 0; int smpCounter = 0;
int state = reader.ReadNext(); int state = reader.ReadNext();
@ -94,6 +96,9 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
t.BlockDescription = BlockType.WAV_Recording; t.BlockDescription = BlockType.WAV_Recording;
t.BlockID = 0; t.BlockID = 0;
t.DataPeriods = new List<int>(); t.DataPeriods = new List<int>();
t.DataLevels = new List<bool>();
bool currLevel = false;
for (int i = 0; i < reader.Count; i++) for (int i = 0; i < reader.Count; i++)
{ {
@ -101,16 +106,33 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
smpCounter++; smpCounter++;
if ((state < 0 && sample < 0) || (state >= 0 && sample >= 0)) if ((state < 0 && sample < 0) || (state >= 0 && sample >= 0))
continue; continue;
t.DataPeriods.Add(smpCounter * rate); t.DataPeriods.Add((int)(((double)smpCounter * (double)rate) / (double)0.9838560885608856));
currLevel = !currLevel;
t.DataLevels.Add(currLevel);
smpCounter = 0; smpCounter = 0;
state = sample; state = sample;
} }
// add closing period // add closing period
t.DataPeriods.Add((69888 * 50) / 10); t.DataPeriods.Add((69888 * 50) / 10);
currLevel = false;
t.DataLevels.Add(currLevel);
// add to datacorder // add to datacorder
_datacorder.DataBlocks.Add(t); _datacorder.DataBlocks.Add(t);
StringBuilder export = new StringBuilder();
foreach (var b in _datacorder.DataBlocks)
{
for (int i = 0; i < b.DataPeriods.Count(); i++)
{
export.Append(b.DataPeriods[i].ToString());
export.Append("\t\t");
export.AppendLine(b.DataLevels[i].ToString());
}
}
string o = export.ToString();
} }
} }
} }