/// <summary> /// Save data blocks /// </summary> /// <param name="vm">Export parameters</param> /// <param name="blocksToSave">Collection of data blocks to save</param> private static void SaveDataBlocks(ExportZ80ProgramViewModel vm, IEnumerable <byte[]> blocksToSave) { try { // --- Create directory var dirName = Path.GetDirectoryName(vm.Filename); if (dirName != null && !Directory.Exists(dirName)) { Directory.CreateDirectory(dirName); } // --- Save data blocks if (vm.Format == ExportFormat.Tzx) { using (var writer = new BinaryWriter(File.Create(vm.Filename))) { var header = new TzxHeader(); header.WriteTo(writer); foreach (var block in blocksToSave) { var tzxBlock = new TzxStandardSpeedDataBlock { Data = block, DataLength = (ushort)block.Length }; tzxBlock.WriteTo(writer); } } } else { using (var writer = new BinaryWriter(File.Create(vm.Filename))) { foreach (var block in blocksToSave) { writer.Write((ushort)block.Length); writer.Write(block); } } } } catch (Exception ex) { VsxDialogs.Show($"An error has been raised while exporting a program: {ex.Message}", "Errorr when exporting file"); } }
/// <summary> /// Playes back a standard speed data block entirely /// </summary> /// <param name="block">Data block to play back</param> public static void CompleteBlock(this TzxStandardSpeedDataBlock block) { const int PILOT_PL = TapeDataBlockPlayer.PILOT_PL; const int HEADER_PILOT_COUNT = TapeDataBlockPlayer.HEADER_PILOT_COUNT; const int SYNC_1_PL = TapeDataBlockPlayer.SYNC_1_PL; const int SYNC_2_PL = TapeDataBlockPlayer.SYNC_2_PL; const long PILOT_END = PILOT_PL * HEADER_PILOT_COUNT; const int TERM_SYNC = TapeDataBlockPlayer.TERM_SYNC; const int PAUSE_MS = TapeDataBlockPlayer.PAUSE_MS; var start = block.StartTact; // --- Skip all pilot pulses + the first sync pulse for (long pos = 0; pos < PILOT_END + SYNC_1_PL; pos += 50) { block.GetEarBit(start + pos); } // --- Skip the second sync pulse for (var pos = PILOT_END + SYNC_1_PL + 50; pos < PILOT_END + SYNC_1_PL + SYNC_2_PL; pos += 50) { block.GetEarBit(start + pos); } // --- Play back the data for (var i = 0; i < block.DataLength; i++) { block.ReadNextByte(); } // --- Play back the terminating sync var nextTact = block.LastTact; for (var pos = nextTact; pos < nextTact + TERM_SYNC + 50; pos += 50) { block.GetEarBit(pos); } // --- Play back the pause nextTact = block.LastTact; for (var pos = nextTact; pos < nextTact + PAUSE_MS * block.PauseAfter + 100; pos += 50) { block.GetEarBit(pos); } }
/// <summary> /// Save data blocks /// </summary> /// <param name="vm">Export parameters</param> /// <param name="blocksToSave">Collection of data blocks to save</param> private static void SaveDataBlocks(ExportZ80ProgramViewModel vm, IEnumerable <byte[]> blocksToSave) { // --- Create directory var dirName = Path.GetDirectoryName(vm.Filename); if (dirName != null && !Directory.Exists(dirName)) { Directory.CreateDirectory(dirName); } // --- Save data blocks if (vm.Format == ExportFormat.Tzx) { using (var writer = new BinaryWriter(File.Create(vm.Filename))) { var header = new TzxHeader(); header.WriteTo(writer); foreach (var block in blocksToSave) { var tzxBlock = new TzxStandardSpeedDataBlock { Data = block, DataLength = (ushort)block.Length }; tzxBlock.WriteTo(writer); } } } else { using (var writer = new BinaryWriter(File.Create(vm.Filename))) { foreach (var block in blocksToSave) { writer.Write((ushort)block.Length); writer.Write(block); } } } }
/// <summary> /// Reads the data byte from the current playback position /// </summary> /// <param name="block">Block to play back</param> /// <returns>Byte read</returns> public static byte ReadNextByte(this TzxStandardSpeedDataBlock block) { const int BIT_0_PL = TapeDataBlockPlayer.BIT_0_PL; const int BIT_1_PL = TapeDataBlockPlayer.BIT_1_PL; const int STEP = 50; var nextTact = block.LastTact + STEP; byte bits = 0x00; for (var i = 0; i < 8; i++) { // --- Wait for the high EAR bit var samplesLow = 0; while (!block.GetEarBit(nextTact)) { samplesLow++; nextTact += STEP; } // --- Wait for the low EAR bit var samplesHigh = 0; while (block.GetEarBit(nextTact) && samplesHigh < BIT_1_PL / STEP + 2) { samplesHigh++; nextTact += STEP; } bits <<= 1; if (samplesLow > (BIT_0_PL + BIT_1_PL) / 2 / STEP && samplesHigh > (BIT_0_PL + BIT_1_PL) / 2 / STEP) { bits++; } } return(bits); }
/// <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; }