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:
- a header block
- a data block
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.
offset | size | description |
---|---|---|
0 | 1 | type (0,1,2,3) |
1 | 10 | filename (padded with space) |
11 | 2 | length of data block (LSB first) |
13 | 2 | parameter 1 (LSB first) |
15 | 2 | parameter 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
Structs
Enums
Constants
Traits
Functions
u8
.u8
.
Useful with std::io::Bytes.