namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum { /// /// Represents the standard speed data block in a tape file /// public class TapeDataBlockPlayer : ISupportsTapeBlockPlayback, ITapeData { /// /// Pause after this block (default: 1000ms) /// public ushort PauseAfter { get; } /// /// Block Data /// public byte[] Data { get; } /// /// Initializes a new instance /// public TapeDataBlockPlayer(byte[] data, ushort pauseAfter) { PauseAfter = pauseAfter; Data = data; } /// /// Pilot pulse length /// public const int PILOT_PL = 2168; /// /// Pilot pulses in the ROM header block /// public const int HEADER_PILOT_COUNT = 8063; /// /// Pilot pulses in the ROM data block /// public const int DATA_PILOT_COUNT = 3223; /// /// Sync 1 pulse length /// public const int SYNC_1_PL = 667; /// /// Sync 2 pulse lenth /// public const int SYNC_2_PL = 735; /// /// Bit 0 pulse length /// public const int BIT_0_PL = 855; /// /// Bit 1 pulse length /// public const int BIT_1_PL = 1710; /// /// End sync pulse length /// public const int TERM_SYNC = 947; /// /// 1 millisecond pause /// public const int PAUSE_MS = 3500; private int _pilotEnds; private int _sync1Ends; private int _sync2Ends; private int _bitStarts; private int _bitPulseLength; private bool _currentBit; private long _termSyncEnds; private long _pauseEnds; /// /// The index of the currently playing byte /// public int ByteIndex { get; private set; } /// /// The mask of the currently playing bit in the current byte /// public byte BitMask { get; private set; } /// /// The current playing phase /// public PlayPhase PlayPhase { get; private set; } /// /// The cycle count of the CPU when playing starts /// public long StartCycle { get; private set; } /// /// Last cycle queried /// public long LastCycle { get; private set; } /// /// Initializes the player /// public void InitPlay(long startTact) { PlayPhase = PlayPhase.Pilot; StartCycle = LastCycle = startTact; _pilotEnds = ((Data[0] & 0x80) == 0 ? HEADER_PILOT_COUNT : DATA_PILOT_COUNT) * PILOT_PL; _sync1Ends = _pilotEnds + SYNC_1_PL; _sync2Ends = _sync1Ends + SYNC_2_PL; ByteIndex = 0; BitMask = 0x80; } /// /// Gets the EAR bit value for the specified cycle /// /// Tacts to retrieve the EAR bit /// /// The EAR bit value to play back /// public bool GetEarBit(long currentCycle) { var pos = (int)(currentCycle - StartCycle); LastCycle = currentCycle; if (PlayPhase == PlayPhase.Pilot || PlayPhase == PlayPhase.Sync) { // --- Generate the appropriate pilot or sync EAR bit if (pos <= _pilotEnds) { // --- Alternating pilot pulses return (pos / PILOT_PL) % 2 == 0; } if (pos <= _sync1Ends) { // --- 1st sync pulse PlayPhase = PlayPhase.Sync; return false; } if (pos <= _sync2Ends) { // --- 2nd sync pulse PlayPhase = PlayPhase.Sync; return true; } PlayPhase = PlayPhase.Data; _bitStarts = _sync2Ends; _currentBit = (Data[ByteIndex] & BitMask) != 0; _bitPulseLength = _currentBit ? BIT_1_PL : BIT_0_PL; } if (PlayPhase == PlayPhase.Data) { // --- Data block playback // --- Generate current bit pulse var bitPos = pos - _bitStarts; if (bitPos < _bitPulseLength) { // --- First pulse of the bit return false; } if (bitPos < 2 * _bitPulseLength) { // --- Second pulse of the bit return true; } // --- Move to the next bit, or byte if ((BitMask >>= 1) == 0) { BitMask = 0x80; ByteIndex++; } // --- Prepare the next bit if (ByteIndex < Data.Length) { _bitStarts += 2 * _bitPulseLength; _currentBit = (Data[ByteIndex] & BitMask) != 0; _bitPulseLength = _currentBit ? BIT_1_PL : BIT_0_PL; // --- We're in the first pulse of the next bit return false; } // --- We've played back all data bytes, send terminating pulse PlayPhase = PlayPhase.TermSync; _termSyncEnds = currentCycle + TERM_SYNC; return false; } if (PlayPhase == PlayPhase.TermSync) { if (currentCycle < _termSyncEnds) { return false; } // --- We've played back all data, not, it's pause time PlayPhase = PlayPhase.Pause; _pauseEnds = currentCycle + PAUSE_MS * PauseAfter; return true; } // --- We need to produce pause signs if (currentCycle > _pauseEnds) { PlayPhase = PlayPhase.Completed; } return true; } } }