A8CAS - software for reading/writing Atari 8-bit cassettes

CAS file format - description

CAS is a binary file format originally introduced by Ernest R. Schreurs' as the output format of his WAV2CAS utility. It is intended to store Atari tapes efficiently.

Originally, CAS format could not store non-standard signals that are common on tapes with copy-protection measures. In A8CAS, the file format has been extended to support those tapes, and also to support tapes in turbo formats. The extension is backwards-compatible - that is, all "old" CAS files are readable by liba8cas.

Here is an example of a file in the CAS format, containing several types of chunks: Bang! Bank!


The file in CAS format is composed of chunks, each having a common structure - an 8-byte header and some data. Each chunk consists of:

OffsetSize (bytes)NameContains
04chunk_type4-letter string.
42chunk_lengthChunk length (not including header)
62auxContents depend on chunk type
8chunk_lengthdataContents depend on chunk type

So, each chunk has size chunk_length+8 bytes.

All numeric values stored in a CAS file are little-endian.

Chunk types

There are several possible chunk types, each identified by a 4-letter string. Those are:

FUJI - tape description

OffsetSize (bytes)NameContains
42chunk_lengthchunk's length
8chunk_lengthdescriptiontape's description (UTF-8)

A CAS file must begin with a FUJI chunk. This chunk holds the tape's description (as an UTF-8 string) in data. The chunk_length field holds the description's length. The description may be empty - then the chunk_length is 0.

A CAS file may contain more FUJI chunks - they can be used to label different parts of a tape, such as separate files recorded one after another. However the current version of liba8cas ignores all but the first FUJI chunk.

baud - baudrate for subsequent SIO records

OffsetSize (bytes)NameContains
42chunk_length0x00 00 (0 bytes)
62baudratebaudrate of subsequent data chunks

A baud chunk holds the baudrate of the following data chunks (until a next baud chunk). The chunk's length is always 0 (+8 bytes of the header), and the baudrate is stored in the baudrate field. If no baud chunk is encountered before a data chunk, its baudrate is set to the default value of 600.

data - standard SIO record

OffsetSize (bytes)NameContains
42chunk_lengthchunk's length
62irg_lengthlength of IRG before this record, in ms
8chunk_lengthdatablock's data

A data chunk contains a standard tape record as read or written by Atari's SIO. Those records normally have a length of 132 bytes, start with two 0x55 bytes, and end with a checksum byte - however those are not necessary. The record's baudrate is the baudrate stored in the previous baud chunk (if no baud chunk has been encountered yet, a standard baudrate of 600 is assumed).

The irg_length field holds length of an Inter-Record Gap (IRG) before this chunk, in milliseconds. The chunk_length field contains number of bytes in the record. data contains all the record's bytes.

fsk  - non-standard SIO signals

OffsetSize (bytes)NameContains
04chunk_typefsk  (ends with space - the length is 4 characters)
42chunk_lengthchunk's length
62irg_lengthlength of IRG before this record, in ms
8chunk_lengthdatalengths (in 1/10's ms) of alternating 0/1 signals, as unsigned 16-bit values

An fsk  chunk holds a raw sequence of lengths of FSK signals (0s and 1s). It is needed to store special signals written on a tape, or any data that could not be recognised as a standard tape record.

The irg_length field holds length of an Inter-Record Gap (IRG) before this chunk, in milliseconds. The data field consists of chunk_length/2 16-bit unsigned numbers (LSB first) - each number represents length of a single signal (0 or 1) in 1/10's of milliseconds.

Example: the sequence 0x66 73 6b 20 0a 00 11 01 00 01 10 01 80 00 20 00 80 02 is an fsk  block that means:

NameRaw bytesvalue
chunk_type66 73 6b 20chunk's identifier
chunk_length0a 00Length is 0x000a = 10 bytes
aux11 01IRG is 0x0111 = 273 milliseconds
data00 010x0100 = 256: 25.6ms of logical 0
10 010x0110 = 272: 27.2ms of logical 1
80 000x0080 = 128: 12.8ms of logical 0
20 000x0020 = 32: 3.2ms of logical 1
80 020x0280 = 640: 64ms of logical 0

pwms - settings for subsequent turbo records

OffsetSize (bytes)NameContains
42chunk_length0x02 00 (2 bytes)
61bits 0-1 - pulse_type
  • %01 - a pulse consists of a falling edge and then a rising edge in that order. Both edges have equal length.
  • %10 - a pulse consists of a rising edge and then a falling edge in that order. Both edges have equal length.
bit 2 - bit_order
  • 0 - least significant bit first
  • 1 - most significant bit first
bits 3-7 - ignoredreserved for future use
71ignoredreserved for future use
82sampleratea base value in Hz, from which subsequent pulse lengths will be derived

A pwms chunk has similar meaning for turbo transmission as a baud chunk for normal SIO transmission. It defines parameters for decoding all subsequent pwmc, pwmd and pwml chunks (until a next pwms chunk). The chunk's length is always 2 (+8 bytes of the header).

In turbo transmission, bits are encoded using PWM - bits 0 and 1 are represented by pulses of different lengths. A single pulse consists of two edges: one rising, one falling. The pulse_type field defines the exact structure of a pulse. (In most turbo systems, a rising edge in a sound signal causes a logical 1 to appear on SIO's DATA IN pin; a falling edge causes a logical 0 to appear.)

The bit_order field defines bit order of data stored in susequent pwmd chunks.

The samplerate field is used in pwmc, pwmd and pwml chunks to determine lengths of pulses. If a pulse has given length of n, it means that its real length is n/samlperate seconds.

pwmc - sequence of turbo signals

OffsetSize (bytes)NameContains
42chunk_lengthchunk's length
62silence_lengthlength of silence before this block, in ms
8chunk_lengthdatasequence of 3-byte elements, each defining length of a single signal:
Size (bytes)Contains
1Length of a single pulse
2Length of the signal, in pulses

A pwmc chunk holds a sequence of signals. A signal is simply a sinewave of specified frequency, lasting a specified amount of pulses. Such signals appear at the beginning of each turbo data block and are used for synchronisation.

The data field contains signals, each one defined by 3 bytes. Therefore a pwmc chunk holds chunk_length/3 signals.

Each signal is defined in terms of 2 values: length of a single pulse and number of pulses. For example, a signal defined by bytes 0x03 20 01 is a signal that consists of 0x0120 = 276 pulses each of length 0x03 = 3 (total signal's length = 276x3. See samplerate field in the last encountered pwms chunk for length's base rate).

pwmd - turbo record with data

OffsetSize (bytes)NameContains
04chunk typepwmd
42chunk_lengthchunk's length
61pulse_0_lengthlength of pulses representing logical 0
71pulse_1_lengthlength of pulses representing logical 1
8chunk_lengthdatablock's data

A pwmd chunk has similar purpose as a data chunk, but for turbo transmission - it holds a block of bytes encoded in PWM. The fields pulse_0_length and pulse_1_length contain lengths of pulses that are used do encode bits in this chunk. The data field contains the block's bytes. Bit order of the block's bytes is defined by the bit_order field in the last encountered pwms chunk.


OffsetSize (bytes)NameContains
04chunk typepwml
42chunk_lengthchunk's length
62silence_lengthlength of silence before this block, in ms
8chunk_lengthdatalengths of PWM states, as unsigned 16-bit values.

A pwml chunk has similar purpose as a fsk  chunk, but for turbo transmission - it holds a sequence of raw PWM states. Each state can be high = rising edge, or low = falling edge. pwml chunks are used as a last-resort method of storing PWM data that cannot be encoded into bytes (in other words, into a pwmd chunk).

A pwml chunk contains chunk_length/2 states - each state is encoded as a 2-byte number in the data field. Like always, a signals length should be divided by samplerate from the previous pwms chunk to get the length in seconds.

Interpretation of the values in the data field depends on the previous pwms block's pulse_type value: