/// <summary> /// DeSerialization method /// </summary> public override void Read(byte[] data) { // clear existing tape blocks _datacorder.DataBlocks.Clear(); // check whether this is a valid pzx format file by looking at the identifier in the header block string ident = Encoding.ASCII.GetString(data, 8, 4); if (ident.ToUpper() != "WAVE") { // this is not a valid TZX format file throw new Exception(this.GetType().ToString() + "This is not a valid WAV format file"); } //_position = 0; MemoryStream stream = new MemoryStream(); stream.Write(data, 0, data.Length); stream.Position = 0; WavStreamReader reader = new WavStreamReader(stream); int rate = (69888 * 50) / reader.Header.sampleRate; int smpCounter = 0; int state = reader.ReadNext(); // create the single tape block TapeDataBlock t = new TapeDataBlock(); t.BlockDescription = BlockType.WAV_Recording; t.BlockID = 0; t.DataPeriods = new List <int>(); for (int i = 0; i < reader.Count; i++) { int sample = reader.ReadNext(); smpCounter++; if ((state < 0 && sample < 0) || (state >= 0 && sample >= 0)) { continue; } t.DataPeriods.Add(smpCounter * rate); smpCounter = 0; state = sample; } // add closing period t.DataPeriods.Add((69888 * 50) / 10); // add to datacorder _datacorder.DataBlocks.Add(t); }
/// <summary> /// DeSerialization method /// </summary> /// <param name="data"></param> public override void Read(byte[] data) { // clear existing tape blocks _datacorder.DataBlocks.Clear(); /* * // PZX uniform block layout * offset type name meaning * ------ ---- ---- ------- * 0 u32 tag unique identifier for the block type. * 4 u32 size size of the block in bytes, excluding the tag and size fields themselves. * 8 u8[size] data arbitrary amount of block data. */ // check whether this is a valid pzx format file by looking at the identifier in the header block string ident = Encoding.ASCII.GetString(data, 0, 4); if (ident.ToUpper() != "PZXT") { // this is not a valid TZX format file throw new Exception(this.GetType().ToString() + "This is not a valid PZX format file"); } _position = 0; // parse all blocks out into seperate byte arrays first List <byte[]> bDatas = new List <byte[]>(); while (_position < data.Length) { int startPos = _position; // data size _position += 4; int blockSize = GetInt32(data, _position); _position += 4; // block data byte[] bd = new byte[8 + blockSize]; Array.Copy(data, startPos, bd, 0, bd.Length); bDatas.Add(bd); _position += blockSize; } // process the blocks foreach (var b in bDatas) { int pos = 8; string blockId = Encoding.ASCII.GetString(b, 0, 4); int blockSize = GetInt32(b, 4); TapeDataBlock t = new TapeDataBlock(); switch (blockId) { // PZXT - PZX header block /* * offset type name meaning * 0 u8 major major version number (currently 1). * 1 u8 minor minor version number (currently 0). * 2 u8[?] info tape info, see below. */ case "PZXT": break; // PULS - Pulse sequence /* * offset type name meaning * 0 u16 count bits 0-14 optional repeat count (see bit 15), always greater than zero * bit 15 repeat count present: 0 not present 1 present * 2 u16 duration1 bits 0-14 low/high (see bit 15) pulse duration bits * bit 15 duration encoding: 0 duration1 1 ((duration1<<16)+duration2) * 4 u16 duration2 optional low bits of pulse duration (see bit 15 of duration1) * 6 ... ... ditto repeated until the end of the block */ case "PULS": t.BlockID = GetInt32(b, 0); t.DataPeriods = new List <int>(); t.InitialPulseLevel = false; List <ushort[]> pulses = new List <ushort[]>(); while (pos < blockSize + 8) { ushort[] p = new ushort[2]; p[0] = 1; p[1] = GetWordValue(b, pos); pos += 2; if (p[1] > 0x8000) { p[0] = (ushort)(p[1] & 0x7fff); p[1] = GetWordValue(b, pos); pos += 2; } if (p[1] >= 0x8000) { p[1] &= 0x7fff; p[1] <<= 16; p[1] |= GetWordValue(b, pos); pos += 2; } pulses.Add(p); } // convert to tape block t.BlockDescription = BlockType.PULS; t.PauseInMS = 0; foreach (var x in pulses) { for (int i = 0; i < x[0]; i++) { t.DataPeriods.Add(x[1]); } } _datacorder.DataBlocks.Add(t); break; // DATA - Data block /* * offset type name meaning * 0 u32 count bits 0-30 number of bits in the data stream * bit 31 initial pulse level: 0 low 1 high * 4 u16 tail duration of extra pulse after last bit of the block * 6 u8 p0 number of pulses encoding bit equal to 0. * 7 u8 p1 number of pulses encoding bit equal to 1. * 8 u16[p0] s0 sequence of pulse durations encoding bit equal to 0. * 8+2*p0 u16[p1] s1 sequence of pulse durations encoding bit equal to 1. * 8+2*(p0+p1) u8[ceil(bits/8)] data data stream, see below. */ case "DATA": t.BlockID = GetInt32(b, 0); t.DataPeriods = new List <int>(); List <ushort> s0 = new List <ushort>(); List <ushort> s1 = new List <ushort>(); List <byte> dData = new List <byte>(); uint initPulseLevel = 1; int dCount = 1; ushort tail = 0; while (pos < blockSize + 8) { dCount = GetInt32(b, pos); initPulseLevel = (uint)((dCount & 0x80000000) == 0 ? 0 : 1); t.InitialPulseLevel = initPulseLevel == 1 ? true : false; dCount = (int)(dCount & 0x7FFFFFFF); pos += 4; tail = GetWordValue(b, pos); pos += 2; var p0 = b[pos++]; var p1 = b[pos++]; for (int i = 0; i < p1; i++) { var s = GetWordValue(b, pos); pos += 2; s0.Add(s); } for (int i = 0; i < p1; i++) { var s = GetWordValue(b, pos); pos += 2; s1.Add(s); } for (int i = 0; i < Math.Ceiling((decimal)dCount / 8); i++) { var buff = b[pos++]; dData.Add(buff); } foreach (var by in dData) { for (int i = 7; i >= 0; i--) { if (by.Bit(i) == true) { foreach (var pu in s1) { t.DataPeriods.Add((int)pu); } } else { foreach (var pu in s0) { t.DataPeriods.Add((int)pu); } } } } if (tail > 0) { t.DataPeriods.Add(tail); } dData.Clear(); } // convert to tape block t.BlockDescription = BlockType.DATA; t.PauseInMS = 0; // tail //t.DataPeriods.Add(tail); _datacorder.DataBlocks.Add(t); break; // PAUS - Pause /* * offset type name meaning * 0 u32 duration bits 0-30 duration of the pause * bit 31 initial pulse level: 0 low 1 high */ case "PAUS": t.BlockID = GetInt32(b, 0); t.DataPeriods = new List <int>(); int iniPulseLevel = 1; int pCount = 0; var d = GetInt32(b, pos); iniPulseLevel = ((d & 0x80000000) == 0 ? 0 : 1); t.InitialPulseLevel = iniPulseLevel == 1 ? true : false; pCount = (d & 0x7FFFFFFF); // convert to tape block t.BlockDescription = BlockType.PAUS; t.DataPeriods.Add(0); t.DataPeriods.Add(pCount); t.DataPeriods.Add(0); _datacorder.DataBlocks.Add(t); break; // BRWS - Browse point /* * offset type name meaning * 0 u8[?] text text describing this browse point */ case "BRWS": t.BlockID = GetInt32(b, 0); t.DataPeriods = new List <int>(); string info = Encoding.ASCII.GetString(b, 8, blockSize); // convert to tape block t.BlockDescription = BlockType.BRWS; t.MetaData.Add(BlockDescriptorTitle.Comments, info); t.PauseInMS = 0; _datacorder.DataBlocks.Add(t); break; // STOP - Stop tape command /* * offset type name meaning * 0 u16 flags when exactly to stop the tape (1 48k only, other always). */ case "STOP": t.BlockID = GetInt32(b, 0); t.DataPeriods = new List <int>(); var flags = GetWordValue(b, pos); if (flags == 1) { t.BlockDescription = BlockType.Stop_the_Tape_48K; t.Command = TapeCommand.STOP_THE_TAPE_48K; } else { t.BlockDescription = BlockType.Pause_or_Stop_the_Tape; t.Command = TapeCommand.STOP_THE_TAPE; } _datacorder.DataBlocks.Add(t); break; } } }
/// <summary> /// DeSerialization method /// </summary> public override void Read(byte[] data) { // clear existing tape blocks _datacorder.DataBlocks.Clear(); // check whether this is a valid pzx format file by looking at the identifier in the header block string ident = Encoding.ASCII.GetString(data, 8, 4); if (ident.ToUpper() != "WAVE") { // this is not a valid TZX format file throw new Exception(this.GetType().ToString() + "This is not a valid WAV format file"); } //_position = 0; MemoryStream stream = new MemoryStream(); stream.Write(data, 0, data.Length); stream.Position = 0; WavStreamReader reader = new WavStreamReader(stream); const double d = /*69888.0*/ 70000.0 * 50.0; int rate = (int)(d / reader.Header.sampleRate); int smpCounter = 0; int state = reader.ReadNext(); // create the single tape block TapeDataBlock t = new TapeDataBlock(); t.BlockDescription = BlockType.WAV_Recording; t.BlockID = 0; t.DataPeriods = new List <int>(); t.DataLevels = new List <bool>(); bool currLevel = false; for (int i = 0; i < reader.Count; i++) { int sample = reader.ReadNext(); smpCounter++; if ((state < 0 && sample < 0) || (state >= 0 && sample >= 0)) { continue; } t.DataPeriods.Add((int)(((double)smpCounter * (double)rate) / (double)0.9838560885608856)); currLevel = !currLevel; t.DataLevels.Add(currLevel); smpCounter = 0; state = sample; } // add closing period t.DataPeriods.Add((69888 * 50) / 10); currLevel = false; t.DataLevels.Add(currLevel); // add to datacorder _datacorder.DataBlocks.Add(t); /* debug stuff * * 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(); */ }
/// <summary> /// DeSerialization method /// </summary> public override void Read(byte[] data) { // clear existing tape blocks _datacorder.DataBlocks.Clear(); // CSW Header // check whether this is a valid csw format file by looking at the identifier in the header // (first 22 bytes of the file) string ident = Encoding.ASCII.GetString(data, 0, 22); if (ident.ToUpper() != "COMPRESSED SQUARE WAVE") { // this is not a valid CSW format file throw new Exception(this.GetType().ToString() + "This is not a valid CSW format file"); } if (data[0x16] != 0x1a) { // invalid terminator code throw new Exception(this.GetType().ToString() + "This image reports as a CSW but has an invalid terminator code"); } _position = 0; // version info int majorVer = data[0x17]; int minorVer = data[0x18]; int sampleRate; int totalPulses; byte compressionType; byte flags; byte headerExtensionLen; byte[] cswData; byte[] cswDataUncompressed; if (majorVer == 2) { /* * CSW-2 Header * CSW global file header - status: required * Offset Value Type Description * 0x00 (note) ASCII[22] "Compressed Square Wave" signature * 0x16 0x1A BYTE Terminator code * 0x17 0x02 BYTE CSW major revision number * 0x18 0x00 BYTE CSW minor revision number * 0x19 - DWORD Sample rate * 0x1D - DWORD Total number of pulses (after decompression) * 0x21 - BYTE Compression type (see notes below) * 0x01: RLE * 0x02: Z-RLE * 0x22 - BYTE Flags * b0: initial polarity; if set, the signal starts at logical high * 0x23 HDR BYTE Header extension length in bytes (0x00) * For future expansions only, see note below. * 0x24 - ASCIIZ[16] Encoding application description * Information about the tool which created the file (e.g. name and version) * 0x34 - BYTE[HDR] Header extension data (if present) * 0x34+HDR - - CSW data. */ _position = 0x19; sampleRate = GetInt32(data, _position); _position += 4; totalPulses = GetInt32(data, _position); cswDataUncompressed = new byte[totalPulses + 1]; _position += 4; compressionType = data[_position++]; flags = data[_position++]; headerExtensionLen = data[_position++]; _position = 0x34 + headerExtensionLen; cswData = new byte[data.Length - _position]; Array.Copy(data, _position, cswData, 0, cswData.Length); ProcessCSWV2(cswData, ref cswDataUncompressed, compressionType, totalPulses); } else if (majorVer == 1) { /* * CSW-1 Header * CSW global file header - status: required * Offset Value Type Description * 0x00 (note) ASCII[22] "Compressed Square Wave" signature * 0x16 0x1A BYTE Terminator code * 0x17 0x01 BYTE CSW major revision number * 0x18 0x01 BYTE CSW minor revision number * 0x19 - WORD Sample rate * 0x1B 0x01 BYTE Compression type * 0x01: RLE * 0x1C - BYTE Flags * b0: initial polarity; if set, the signal starts at logical high * 0x1D 0x00 BYTE[3] Reserved. * 0x20 - - CSW data. */ _position = 0x19; sampleRate = GetWordValue(data, _position); _position += 2; compressionType = data[_position++]; flags = data[_position++]; _position += 3; cswDataUncompressed = new byte[data.Length - _position]; if (compressionType == 1) { Array.Copy(data, _position, cswDataUncompressed, 0, cswDataUncompressed.Length); } else { throw new Exception(this.GetType().ToString() + "CSW Format unknown compression type"); } } else { throw new Exception(this.GetType().ToString() + "CSW Format Version " + majorVer + "." + minorVer + " is not currently supported"); } // create the single tape block // (use DATA block for now so initial signal level is handled correctly by the datacorder device) TapeDataBlock t = new TapeDataBlock(); t.BlockDescription = BlockType.CSW_Recording; t.BlockID = 0x18; t.DataPeriods = new List <int>(); t.DataLevels = new List <bool>(); if (flags.Bit(0)) { t.InitialPulseLevel = true; } else { t.InitialPulseLevel = false; } bool currLevel = !t.InitialPulseLevel; var rate = (69888 * 50) / sampleRate; for (int i = 0; i < cswDataUncompressed.Length;) { int length = cswDataUncompressed[i++] * rate; if (length == 0) { length = GetInt32(cswDataUncompressed, i) / rate; i += 4; } t.DataPeriods.Add(length); currLevel = !currLevel; t.DataLevels.Add(currLevel); } // add closing period t.DataPeriods.Add((69888 * 50) / 10); currLevel = !currLevel; t.DataLevels.Add(currLevel); // add to datacorder _datacorder.DataBlocks.Add(t); }
/// <summary> /// DeSerialization method /// </summary> /// <param name="data"></param> public override void DeSerialize(byte[] data) { /* * The .TAP files contain blocks of tape-saved data. All blocks start with two bytes specifying how many bytes will follow (not counting the two length bytes). Then raw tape data follows, including the flag and checksum bytes. The checksum is the bitwise XOR of all bytes including the flag byte. For example, when you execute the line SAVE "ROM" CODE 0,2 this will result: * |------ Spectrum-generated data -------| |---------| * * 13 00 00 03 52 4f 4d 7x20 02 00 00 00 00 80 f1 04 00 ff f3 af a3 * * ^^^^^...... first block is 19 bytes (17 bytes+flag+checksum) * ^^... flag byte (A reg, 00 for headers, ff for data blocks) * ^^ first byte of header, indicating a code block * * file name ..^^^^^^^^^^^^^ * header info ..............^^^^^^^^^^^^^^^^^ * checksum of header .........................^^ * length of second block ........................^^^^^ * flag byte ............................................^^ * first two bytes of rom .................................^^^^^ * checksum (checkbittoggle would be a better name!).............^^ */ // clear existing tape blocks _datacorder.DataBlocks.Clear(); // convert bytearray to memory stream MemoryStream stream = new MemoryStream(data); // the first 2 bytes of the TAP file designate the length of the first data block // this (I think) should always be 17 bytes (as this is the tape header) byte[] blockLengthData = new byte[2]; // we are now going to stream through the entire file processing a block at a time while (stream.Position < stream.Length) { // read and calculate the length of the block stream.Read(blockLengthData, 0, 2); int blockSize = BitConverter.ToUInt16(blockLengthData, 0); if (blockSize == 0) { // block size is 0 - this is probably invalid (but I guess could be EoF in some situations) break; } // copy the entire block into a new bytearray byte[] blockdata = new byte[blockSize]; stream.Read(blockdata, 0, blockSize); // create and populate a new tapedatablock object TapeDataBlock tdb = new TapeDataBlock(); // ascertain the block description string description = string.Empty; byte crc = 0; byte crcValue = 0; byte crcFile = 0; byte[] programData = new byte[10]; // calculate block checksum value for (int i = 0; i < blockSize; i++) { crc ^= blockdata[i]; if (i < blockSize - 1) { crcValue = crc; } else { crcFile = blockdata[i]; } } // process the type byte /* (The type is 0,1,2 or 3 for a Program, Number array, Character array or Code file. * A SCREEN$ file is regarded as a Code file with start address 16384 and length 6912 decimal. * If the file is a Program file, parameter 1 holds the autostart line number (or a number >=32768 if no LINE parameter was given) * and parameter 2 holds the start of the variable area relative to the start of the program. If it's a Code file, parameter 1 holds * the start of the code block when saved, and parameter 2 holds 32768. For data files finally, the byte at position 14 decimal holds the variable name.) */ if (blockdata[0] == 0x00 && blockSize == 19 && (blockdata[1] == 0x00) || blockdata[1] == 3) { // This is the PROGRAM header // take the 10 filename bytes (that start at offset 2) programData = blockdata.Skip(2).Take(10).ToArray(); // get the filename as a string (with padding removed) string fileName = Encoding.ASCII.GetString(programData).Trim(); // get the type string type = ""; if (blockdata[0] == 0x00) { type = "Program"; } else { type = "Bytes"; } // now build the description string StringBuilder sb = new StringBuilder(); sb.Append(type + ": "); sb.Append(fileName + " "); sb.Append(GetWordValue(blockdata, 14)); sb.Append(":"); sb.Append(GetWordValue(blockdata, 12)); description = sb.ToString(); } else if (blockdata[0] == 0xFF) { // this is a data block description = "Data Block " + (blockSize - 2) + "bytes"; } else { // other type description = string.Format("#{0} block, {1} bytes", blockdata[0].ToString("X2"), blockSize - 2); description += string.Format(", crc {0}", ((crc != 0) ? string.Format("bad (#{0:X2}!=#{1:X2})", crcFile, crcValue) : "ok")); } tdb.BlockDescription = BlockType.Standard_Speed_Data_Block; // calculate the data periods for this block int pilotLength = 0; // work out pilot length if (blockdata[0] < 4) { pilotLength = 8064; } else { pilotLength = 3220; } // create a list to hold the data periods List <int> dataPeriods = new List <int>(); // generate pilot pulses for (int i = 0; i < pilotLength; i++) { dataPeriods.Add(PILOT_PL); } // add syncro pulses dataPeriods.Add(SYNC_1_PL); dataPeriods.Add(SYNC_2_PL); int pos = 0; // add bit0 and bit1 periods for (int i = 0; i < blockSize - 1; i++, pos++) { for (byte b = 0x80; b != 0; b >>= 1) { if ((blockdata[i] & b) != 0) { dataPeriods.Add(BIT_1_PL); } else { dataPeriods.Add(BIT_0_PL); } if ((blockdata[i] & b) != 0) { dataPeriods.Add(BIT_1_PL); } else { dataPeriods.Add(BIT_0_PL); } } } // add the last byte for (byte c = 0x80; c != (byte)(0x80 >> BIT_COUNT_IN_LAST); c >>= 1) { if ((blockdata[pos] & c) != 0) { dataPeriods.Add(BIT_1_PL); } else { dataPeriods.Add(BIT_0_PL); } if ((blockdata[pos] & c) != 0) { dataPeriods.Add(BIT_1_PL); } else { dataPeriods.Add(BIT_0_PL); } } // add block pause int actualPause = PAUSE_MS * 1000; dataPeriods.Add(actualPause); // default pause for tap files tdb.PauseInMS = 1000; // add to the tapedatablock object tdb.DataPeriods = dataPeriods; // add the raw data tdb.BlockData = blockdata; // add block to the tape _datacorder.DataBlocks.Add(tdb); } }
/// <summary> /// DeSerialization method /// </summary> public override void Read(byte[] data) { /* * The .TAP files contain blocks of tape-saved data. All blocks start with two bytes specifying how many bytes will follow (not counting the two length bytes). Then raw tape data follows, including the flag and checksum bytes. The checksum is the bitwise XOR of all bytes including the flag byte. For example, when you execute the line SAVE "ROM" CODE 0,2 this will result: * |------ Spectrum-generated data -------| |---------| * * 13 00 00 03 52 4f 4d 7x20 02 00 00 00 00 80 f1 04 00 ff f3 af a3 * * ^^^^^...... first block is 19 bytes (17 bytes+flag+checksum) * ^^... flag byte (A reg, 00 for headers, ff for data blocks) * ^^ first byte of header, indicating a code block * * file name ..^^^^^^^^^^^^^ * header info ..............^^^^^^^^^^^^^^^^^ * checksum of header .........................^^ * length of second block ........................^^^^^ * flag byte ............................................^^ * first two bytes of rom .................................^^^^^ * checksum (checkbittoggle would be a better name!).............^^ */ // clear existing tape blocks _datacorder.DataBlocks.Clear(); // convert bytearray to memory stream MemoryStream stream = new MemoryStream(data); // the first 2 bytes of the TAP file designate the length of the first data block // this (I think) should always be 17 bytes (as this is the tape header) byte[] blockLengthData = new byte[2]; // we are now going to stream through the entire file processing a block at a time while (stream.Position < stream.Length) { // read and calculate the length of the block stream.Read(blockLengthData, 0, 2); int blockSize = BitConverter.ToUInt16(blockLengthData, 0); if (blockSize == 0) { // block size is 0 - this is probably invalid (but I guess could be EoF in some situations) break; } // copy the entire block into a new bytearray byte[] blockdata = new byte[blockSize]; stream.Read(blockdata, 0, blockSize); // create and populate a new tapedatablock object TapeDataBlock tdb = new TapeDataBlock(); // ascertain the block description string description = string.Empty; byte crc = 0; byte crcValue = 0; byte crcFile = 0; byte[] programData = new byte[10]; // calculate block checksum value for (int i = 0; i < blockSize; i++) { crc ^= blockdata[i]; if (i < blockSize - 1) { crcValue = crc; } else { crcFile = blockdata[i]; } } // process the type byte /* (The type is 0,1,2 or 3 for a Program, Number array, Character array or Code file. * A SCREEN$ file is regarded as a Code file with start address 16384 and length 6912 decimal. * If the file is a Program file, parameter 1 holds the autostart line number (or a number >=32768 if no LINE parameter was given) * and parameter 2 holds the start of the variable area relative to the start of the program. If it's a Code file, parameter 1 holds * the start of the code block when saved, and parameter 2 holds 32768. For data files finally, the byte at position 14 decimal holds the variable name.) */ tdb.MetaData = new Dictionary <BlockDescriptorTitle, string>(); if (blockdata[0] == 0x00 && blockSize == 19) { string fileName = Encoding.ASCII.GetString(blockdata.Skip(2).Take(10).ToArray()).Trim(); string type = "Unknown Type"; StringBuilder sb = new StringBuilder(); var param1 = GetWordValue(blockdata, 12); var param2 = GetWordValue(blockdata, 14); // header block - examine first byte of header if (blockdata[1] == 0) { type = "Program"; sb.Append(type + ": "); sb.Append(fileName + " "); } else if (blockdata[1] == 1) { type = "NumArray"; sb.Append(type + ": "); sb.Append(fileName + " "); } else if (blockdata[1] == 2) { type = "CharArray"; sb.Append(type + ": "); sb.Append(fileName + " "); } else if (blockdata[1] == 3) { type = "Code"; sb.Append(type + ": "); sb.Append(fileName + " "); } } else if (blockdata[0] == 0xff) { // data block description = "Data Block " + (blockSize - 2) + "bytes"; tdb.AddMetaData(BlockDescriptorTitle.Data_Bytes, (blockSize - 2) + " Bytes"); } else { // some other type (turbo data etc..) description = $"#{blockdata[0].ToString("X2")} block, {blockSize} bytes"; //description += (crc != 0) ? $", crc bad (#{crcFile:X2}!=#{crcValue:X2})" : ", crc ok"; tdb.AddMetaData(BlockDescriptorTitle.Undefined, description); } /* * if (blockdata[0] == 0x00 && blockSize == 19 && (blockdata[1] == 0x00) || blockdata[1] == 3) * { * // This is the PROGRAM header * // take the 10 filename bytes (that start at offset 2) * programData = blockdata.Skip(2).Take(10).ToArray(); * * // get the filename as a string (with padding removed) * string fileName = Encoding.ASCII.GetString(programData).Trim(); * * // get the type * string type = ""; * if (blockdata[0] == 0x00) * { * type = "Program"; * } * else * { * type = "Bytes"; * } * * // now build the description string * StringBuilder sb = new StringBuilder(); * sb.Append(type + ": "); * sb.Append(fileName + " "); * sb.Append(GetWordValue(blockdata, 14)); * sb.Append(':'); * sb.Append(GetWordValue(blockdata, 12)); * description = sb.ToString(); * } * else if (blockdata[0] == 0xFF) * { * // this is a data block * description = "Data Block " + (blockSize - 2) + "bytes"; * } * else * { * // other type * description = $"#{blockdata[0]:X2} block, {blockSize - 2} bytes"; * description += (crc != 0) ? $", crc bad (#{crcFile:X2}!=#{crcValue:X2})" : ", crc ok"; * } */ tdb.BlockDescription = BlockType.Standard_Speed_Data_Block; // calculate the data periods for this block int pilotLength = 0; // work out pilot length if (blockdata[0] < 4) { pilotLength = 8064; } else { pilotLength = 3220; } // create a list to hold the data periods List <int> dataPeriods = new List <int>(); List <bool> dataLevels = new List <bool>(); bool currLevel = false; // generate pilot pulses for (int i = 0; i < pilotLength; i++) { dataPeriods.Add(PILOT_PL); currLevel = !currLevel; dataLevels.Add(currLevel); } // add syncro pulses dataPeriods.Add(SYNC_1_PL); currLevel = !currLevel; dataLevels.Add(currLevel); dataPeriods.Add(SYNC_2_PL); currLevel = !currLevel; dataLevels.Add(currLevel); int pos = 0; // add bit0 and bit1 periods for (int i = 0; i < blockSize - 1; i++, pos++) { for (byte b = 0x80; b != 0; b >>= 1) { if ((blockdata[i] & b) != 0) { dataPeriods.Add(BIT_1_PL); currLevel = !currLevel; dataLevels.Add(currLevel); } else { dataPeriods.Add(BIT_0_PL); currLevel = !currLevel; dataLevels.Add(currLevel); } if ((blockdata[i] & b) != 0) { dataPeriods.Add(BIT_1_PL); currLevel = !currLevel; dataLevels.Add(currLevel); } else { dataPeriods.Add(BIT_0_PL); currLevel = !currLevel; dataLevels.Add(currLevel); } } } // add the last byte for (byte c = 0x80; c != (byte)(0x80 >> BIT_COUNT_IN_LAST); c >>= 1) { if ((blockdata[pos] & c) != 0) { dataPeriods.Add(BIT_1_PL); currLevel = !currLevel; dataLevels.Add(currLevel); } else { dataPeriods.Add(BIT_0_PL); currLevel = !currLevel; dataLevels.Add(currLevel); } if ((blockdata[pos] & c) != 0) { dataPeriods.Add(BIT_1_PL); currLevel = !currLevel; dataLevels.Add(currLevel); } else { dataPeriods.Add(BIT_0_PL); currLevel = !currLevel; dataLevels.Add(currLevel); } } // add block pause //int actualPause = PAUSE_MS * 1000; //dataPeriods.Add(actualPause); // default pause for tap files 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 tdb.DataPeriods = dataPeriods; tdb.DataLevels = dataLevels; // add the raw data tdb.BlockData = blockdata; // generate separate PAUS block /* * TapeDataBlock tdbPause = new TapeDataBlock(); * tdbPause.DataPeriods = new List<int>(); * tdbPause.BlockDescription = BlockType.PAUSE_BLOCK; * tdbPause.PauseInMS = 0; * var pauseInTStates = TranslatePause(tdb.PauseInMS); * //if (pauseInTStates > 0) * //tdbPause.DataPeriods.Add(pauseInTStates); * tdb.PauseInMS = 0; * tdb.PauseInTStates = pauseInTStates; */ // add block to the tape _datacorder.DataBlocks.Add(tdb); /* * // PAUS block if neccessary * if (pauseInTStates > 0) * { * tdbPause.AddMetaData(BlockDescriptorTitle.Block_ID, pauseInTStates + " cycles"); * * int by1000 = pauseInTStates / 70000; * int rem1000 = pauseInTStates % 70000; * * if (by1000 > 1) * { * tdbPause.DataPeriods.Add(35000); * tdbPause.DataPeriods.Add(pauseInTStates - 35000); * } * else * { * tdbPause.DataPeriods.Add(pauseInTStates); * tdbPause.DataPeriods.Add(0); * } * * _datacorder.DataBlocks.Add(tdbPause); * } */ } }