/// <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(); SaveToTapeProvider?.SetName(name); } SaveToTapeProvider?.SaveTapeBlock(dataBlock); } break; } _savePhase = nextPhase; }
/// <summary> /// Leaves the save mode. Stops recording MIC pulses /// </summary> private void LeaveSaveMode() { _currentMode = TapeOperationMode.Passive; SaveToTapeProvider?.FinalizeTapeFile(); }