/// <summary> /// Resets the tape device /// </summary> public void Reset() { TapeProvider?.Reset(); _tapePlayer = null; _currentMode = TapeOperationMode.Passive; _savePhase = SavePhase.None; _micBitState = true; }
/// <summary> /// Puts the device in save mode. From now on, every MIC pulse is recorded /// </summary> private void EnterSaveMode() { _currentMode = TapeOperationMode.Save; _savePhase = SavePhase.None; _micBitState = true; _lastMicBitActivityTact = _cpu.Tacts; _pilotPulseCount = 0; _prevDataPulse = MicPulseType.None; _dataBlockCount = 0; SaveToTapeProvider?.CreateTapeFile(); }
/// <summary> /// Processes the the change of the MIC bit /// </summary> /// <param name="micBit">MIC bit to process</param> public void ProcessMicBit(bool micBit) { if (_currentMode != TapeOperationMode.Save || _micBitState == micBit) { return; } var length = _cpu.Tacts - _lastMicBitActivityTact; // --- Classify the pulse by its width var pulse = MicPulseType.None; if (length >= TapeDataBlockPlayer.BIT_0_PL - SAVE_PULSE_TOLERANCE && length <= TapeDataBlockPlayer.BIT_0_PL + SAVE_PULSE_TOLERANCE) { pulse = MicPulseType.Bit0; } else if (length >= TapeDataBlockPlayer.BIT_1_PL - SAVE_PULSE_TOLERANCE && length <= TapeDataBlockPlayer.BIT_1_PL + SAVE_PULSE_TOLERANCE) { pulse = MicPulseType.Bit1; } if (length >= TapeDataBlockPlayer.PILOT_PL - SAVE_PULSE_TOLERANCE && length <= TapeDataBlockPlayer.PILOT_PL + SAVE_PULSE_TOLERANCE) { pulse = MicPulseType.Pilot; } else if (length >= TapeDataBlockPlayer.SYNC_1_PL - SAVE_PULSE_TOLERANCE && length <= TapeDataBlockPlayer.SYNC_1_PL + SAVE_PULSE_TOLERANCE) { pulse = MicPulseType.Sync1; } else if (length >= TapeDataBlockPlayer.SYNC_2_PL - SAVE_PULSE_TOLERANCE && length <= TapeDataBlockPlayer.SYNC_2_PL + SAVE_PULSE_TOLERANCE) { pulse = MicPulseType.Sync2; } else if (length >= TapeDataBlockPlayer.TERM_SYNC - SAVE_PULSE_TOLERANCE && length <= TapeDataBlockPlayer.TERM_SYNC + SAVE_PULSE_TOLERANCE) { pulse = MicPulseType.TermSync; } else if (length < TapeDataBlockPlayer.SYNC_1_PL - SAVE_PULSE_TOLERANCE) { pulse = MicPulseType.TooShort; } else if (length > TapeDataBlockPlayer.PILOT_PL + 2 * SAVE_PULSE_TOLERANCE) { pulse = MicPulseType.TooLong; } _micBitState = micBit; _lastMicBitActivityTact = _cpu.Tacts; // --- Lets process the pulse according to the current SAVE phase and pulse width var nextPhase = SavePhase.Error; switch (_savePhase) { case SavePhase.None: if (pulse == MicPulseType.TooShort || pulse == MicPulseType.TooLong) { nextPhase = SavePhase.None; } else if (pulse == MicPulseType.Pilot) { _pilotPulseCount = 1; nextPhase = SavePhase.Pilot; } break; case SavePhase.Pilot: if (pulse == MicPulseType.Pilot) { _pilotPulseCount++; nextPhase = SavePhase.Pilot; } else if (pulse == MicPulseType.Sync1 && _pilotPulseCount >= MIN_PILOT_PULSE_COUNT) { nextPhase = SavePhase.Sync1; } break; case SavePhase.Sync1: if (pulse == MicPulseType.Sync2) { nextPhase = SavePhase.Sync2; } break; case SavePhase.Sync2: if (pulse == MicPulseType.Bit0 || pulse == MicPulseType.Bit1) { // --- Next pulse starts data, prepare for receiving it _prevDataPulse = pulse; nextPhase = SavePhase.Data; _bitOffset = 0; _dataByte = 0; _dataLength = 0; _dataBuffer = new byte[DATA_BUFFER_LENGTH]; } break; case SavePhase.Data: if (pulse == MicPulseType.Bit0 || pulse == MicPulseType.Bit1) { if (_prevDataPulse == MicPulseType.None) { // --- We are waiting for the second half of the bit pulse _prevDataPulse = pulse; nextPhase = SavePhase.Data; } else if (_prevDataPulse == pulse) { // --- We received a full valid bit pulse nextPhase = SavePhase.Data; _prevDataPulse = MicPulseType.None; // --- Add this bit to the received data _bitOffset++; _dataByte = (byte)(_dataByte * 2 + (pulse == MicPulseType.Bit0 ? 0 : 1)); if (_bitOffset == 8) { // --- We received a full byte _dataBuffer[_dataLength++] = _dataByte; _dataByte = 0; _bitOffset = 0; } } } else if (pulse == MicPulseType.TermSync) { // --- We received the terminating pulse, the datablock has been completed nextPhase = SavePhase.None; _dataBlockCount++; // --- Create and save the data block var dataBlock = new TzxStandardSpeedDataBlock { Data = _dataBuffer, DataLength = (ushort)_dataLength }; // --- If this is the first data block, extract the name from the header if (_dataBlockCount == 1 && _dataLength == 0x13) { // --- It's a header! var sb = new StringBuilder(16); for (var i = 2; i <= 11; i++) { sb.Append((char)_dataBuffer[i]); } var name = sb.ToString().TrimEnd(); TapeProvider?.SetName(name); } TapeProvider?.SaveTapeBlock(dataBlock); } break; } _savePhase = nextPhase; }
public void Save(Stream stream) { _encoder.Clear(); _phase = SavePhase.Initial; foreach (var section in _sections) { foreach (var vertex in section._items) { vertex._offset = GetCurrentOffset(); vertex._iteration = _iteration; vertex.Save(this); #if NATIVEFORMAT_COMPRESSION // Ensure that the compressor state is fully flushed Debug.Assert(_TentativelyWritten.Count == 0); Debug.Assert(_compressionDepth == 0); #endif } } // Aggresive phase that only allows offsets to shrink. _phase = SavePhase.Shrinking; for (; ;) { _iteration++; _encoder.Clear(); _offsetAdjustment = 0; foreach (var section in _sections) { foreach (var vertex in section._items) { int currentOffset = GetCurrentOffset(); // Only allow the offsets to shrink. _offsetAdjustment = Math.Min(_offsetAdjustment, currentOffset - vertex._offset); vertex._offset += _offsetAdjustment; if (vertex._offset < currentOffset) { // It is possible for the encoding of relative offsets to grow during some iterations. // Ignore this growth because of it should disappear during next iteration. RollbackTo(vertex._offset); } Debug.Assert(vertex._offset == GetCurrentOffset()); vertex._iteration = _iteration; vertex.Save(this); #if NATIVEFORMAT_COMPRESSION // Ensure that the compressor state is fully flushed Debug.Assert(_tentativelyWritten.Count == 0); Debug.Assert(_compressionDepth == 0); #endif } } // We are not able to shrink anymore. We cannot just return here. It is possible that we have rolledback // above because of we shrinked too much. if (_offsetAdjustment == 0) { break; } // Limit number of shrinking interations. This limit is meant to be hit in corner cases only. if (_iteration > 10) { break; } } // Conservative phase that only allows the offsets to grow. It is guaranteed to converge. _phase = SavePhase.Growing; for (; ;) { _iteration++; _encoder.Clear(); _offsetAdjustment = 0; _paddingSize = 0; foreach (var section in _sections) { foreach (var vertex in section._items) { int currentOffset = GetCurrentOffset(); // Only allow the offsets to grow. _offsetAdjustment = Math.Max(_offsetAdjustment, currentOffset - vertex._offset); vertex._offset += _offsetAdjustment; if (vertex._offset > currentOffset) { // Padding int padding = vertex._offset - currentOffset; _paddingSize += padding; WritePad(padding); } Debug.Assert(vertex._offset == GetCurrentOffset()); vertex._iteration = _iteration; vertex.Save(this); #if NATIVEFORMAT_COMPRESSION // Ensure that the compressor state is fully flushed Debug.Assert(_tentativelyWritten.Count == 0); Debug.Assert(_compressionDepth == 0); #endif } } if (_offsetAdjustment == 0) { _encoder.Save(stream); return; } } }