Example #1
0
        /// <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);
        }
Example #2
0
        /// <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;
                }
            }
        }
Example #3
0
        /// <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();
             */
        }
Example #4
0
        /// <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);
        }
Example #5
0
        /// <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);
            }
        }
Example #6
0
        /// <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);
                 * }
                 */
            }
        }