Module spectrusty_formats::tap

source ·
Expand description

TAP file format utilities.

TAP format

A TAP file consists of blocks of data each prepended by a 2 byte (LSB) block length indicator. Those blocks will be referenced here as TAP chunks.

The standard Spectrum’s ROM TAPE routine produces 2 kinds of blocks:

This is determined by the first byte of each block, here called a flag byte.

A flag byte is 0x00 for header blocks and 0xff for data blocks. After the flag byte, the actual data follows, after which a checksum byte, calculated such that XORing all the data bytes together (including the flag byte) produces 0.

The structure of the 17 byte header is as follows.

offsetsizedescription
01type (0,1,2,3)
110filename (padded with space)
112length of data block (LSB first)
132parameter 1 (LSB first)
152parameter 2 (LSB first)

These 17 bytes are prefixed by the flag byte (0x00) and suffixed by the checksum byte to produce the 19-byte block seen on tape. 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 array files finally, the byte at position 14 decimal holds the variable name.

For example, SAVE “ROM” CODE 0,2 will produce the following data on tape:

      |-------------- TAP chunk -------------|       |TAP chunk|
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!).............^^

The above text uses material from the “TAP format” article on the Sinclair FAQ wiki and is released under the Creative Commons Attribution-Share Alike License.

Interpreting TAP files

Reading byte containers

To interpret a byte container as a TAP chunk the TapChunk wrapper is provided. The TapChunkIter iterator can be used to produce TapChunks over the byte container in the TAP format.

use std::io::Read;
use spectrusty_formats::tap::*;

let mut tapfile = std::fs::File::open("some.tap")?;
let mut buffer = Vec::new();
tapfile.read_to_end(&mut buffer)?;
for chunk in TapChunkIter::from(&buffer) {
    // do something with chunk
    println!("{}", chunk);
}

Directly from readers

TapChunkReader helps to read TAP chunks directly from a byte stream that implements both Read and Seek interfaces.

use std::io::Read;
use spectrusty_formats::tap::*;

let mut tapfile = std::fs::File::open("some.tap")?;
let mut tap_reader = read_tap(tapfile);

let mut buf = Vec::new();
while let Some(size) = tap_reader.next_chunk()? {
    buf.clear();
    tap_reader.read_to_end(&mut buf)?;
    let chunk = TapChunk::from(&buf);
    // do something with chunk
    println!("{}", chunk);
}

Browsing TAP chunks

A TapChunkInfo is an enum providing information about the chunk and can be created from both byte containers as well as from std::io::Take readers using the TryFrom interface.

An iterator producing TapChunkInfo can be created from TapChunkReader.

use spectrusty_formats::tap::*;

let mut tapfile = std::fs::File::open("some.tap")?;
let mut tap_reader = read_tap(tapfile);
for info in TapReadInfoIter::from(&mut tap_reader) {
    println!("{}", info?);
}

TAPE pulses

To provide a TAPE signal for the Spectrum emulator a pulse interval encoder is provided. It encodes data as TAPE pulse intervals providing results via the Iterator interface while reading bytes from the underlying reader.

pulse::ReadEncPulseIter emits lead pulses following the sync and data pulses for the bytes read until the reader reaches the end of file or the pulse::ReadEncPulseIter::reset method is called.

For a more convenient way to encode TAP data which contains many chunks, the TapChunkPulseIter is provided. It wraps pulse::ReadEncPulseIter providing it with the chunk data resetting it before each next chunk.

use spectrusty::{memory::Memory48k, chip::{ula::UlaPAL, EarIn}};

let mut ula = UlaPAL::<Memory48k>::default();
//...
use spectrusty_formats::tap::*;

let mut tapfile = std::fs::File::open("some.tap")?;
let mut pulse_iter = read_tap_pulse_iter(tapfile);

// feed the buffer fragmentarily before each emulated frame
ula.feed_ear_in(&mut pulse_iter, Some(1));

// ... or feed the buffer at once ...
ula.feed_ear_in(pulse_iter, None);

Writing data to TAP files

The structure of TAP files allows us to easily append more blocks to them.

TapChunkWriter provides methods to write TAP chunks to streams implementing Write and Seek interfaces.

use std::io::Write;
use spectrusty_formats::tap::*;

let mut tapfile = std::fs::File::create("output.tap")?;
let mut tap_writer = write_tap(tapfile)?;

// let's create a TAP header for a CODE block
let header = Header::new_code(1)
                    .with_start(0x8000)
                    .with_name("return");
// now write it
tap_writer.write_header(&header)?;

// now the data block
let mut tran = tap_writer.begin()?;
tran.write(&[DATA_BLOCK_FLAG, 201])?;
// appends checksum byte and commits block
tran.commit(true)?;

TAPE pulses

A TAPE signal generated by the Spectrum’s SAVE routines can be written as TAP chunk data with the same instance of TapChunkWriter.

use spectrusty::{memory::Memory48k, chip::{ula::UlaPAL, MicOut}};

let mut ula = UlaPAL::<Memory48k>::default();
//...
use std::io::Write;
use spectrusty_formats::tap::*;

let mut tapfile = std::fs::File::create("output.tap")?;
let mut tap_writer = write_tap(tapfile)?;

// get pulses from MIC out data after rendering each frame
let pulse_iter = ula.mic_out_pulse_iter();
// write them as *TAP chunks*
let chunks = tap_writer.write_pulses_as_tap_chunks(pulse_iter)?;
if chunks != 0 {
    println!("Saved: {} chunks", chunks);
}

Modules

TAPE pulse signal encoding and decoding.

Structs

Represents the TAP header block.
The TAP chunk.
Implements an iterator of TapChunk objects over the array of bytes.
Implements an iterator of T-state pulse intervals over the TapChunkReader. See also: ReadEncPulseIter.
Implements a Reader of TAP chunks data.
A TapChunkWriter transaction holder. Created by TapChunkWriter::begin.
A tool for writing TAP file chunks to byte streams.
Implements an iterator of TapChunkInfo instances from any mutable reference to TapChunkReader including anything (like smart pointers) that dereferences to it.

Enums

The TAP block type of the next chunk following a Header.
The TAP chunk meta-data.

Constants

Traits

A trait with tools implemented by tap chunk readers.

Functions

Calculates bit toggle checksum from the given iterator of u8.
Creates an instance of TapChunkReader from the given reader.
Creates an instance of TapChunkPulseIter from the given reader.
Calculates bit toggle checksum from the given iterator of the result of u8. Useful with std::io::Bytes.
Creates an instance of TapChunkWriter from the given writer on success.