Skip to content

Reading, Writing and Converting

Reading Files

Each format exposes a singleton IOFileFormat instance via its Instance property. Call Read with a Stream or a byte[]:

using var stream = File.OpenRead("tape.tap");
TapFile tap = TapFormat.Instance.Read(stream);

// Or from a byte array:
byte[] bytes = File.ReadAllBytes("tape.tap");
TapFile tap = TapFormat.Instance.Read(bytes);

The returned object is a strongly typed subclass of IOFile specific to the format.

If you don't know the format ahead of time, use IOFileFormat.Load with the set of formats you support — it picks the right one based on the file's extension:

IOFile file = IOFileFormat.Load("tape.tap", TapFormat.Instance, TzxFormat.Instance, PzxFormat.Instance);

Load also transparently decompresses .zip, .gz, .br, and .zst files — see Compression below.

Writing Files

An IOFile writes itself — pass a Stream to Write:

using var stream = File.Create("output.tap");
tap.Write(stream);

Use ToByteArray to get a byte[] instead, or Save to write straight to a directory — it works out the filename, appending the format's extension automatically:

byte[] bytes = tap.ToByteArray();

// Writes to "/path/to/output.tap":
string path = tap.Save("/path/to", "output");

Reading and Writing Asynchronously

Every reading and writing method has an asynchronous counterpart — ReadAsync, LoadAsync, WriteAsync, and SaveAsync — each taking an optional CancellationToken:

using var stream = File.OpenRead("tape.tap");
TapFile tap = await TapFormat.Instance.ReadAsync(stream);

using var output = File.Create("output.tap");
await tap.WriteAsync(output);

string path = await tap.SaveAsync("/path/to", "output");

Compression

Files can be written compressed by passing a CompressionFormat to Write, WriteAsync, Save, or SaveAsync. Supported formats are Zip, GZip, Brotli, and Zstandard:

using var stream = File.Create("output.wav.gz");
wav.Write(stream, "output.wav", CompressionFormat.GZip);

// Or when saving to a directory, which works out the compressed filename automatically:
string path = wav.Save("/path/to", "output", CompressionFormat.GZip); // "/path/to/output.wav.gz"

Zip compression stores the file as a single entry inside a .zip archive, using the filename argument as the name of that entry. The other formats append their own extension (.gz, .br, .zst) to the filename instead.

Load and LoadAsync transparently decompress a file based on its extension, so no special handling is needed to read a compressed file back.

Converting Between Formats

IOFileConversion provides static methods to convert a file from one format to another. Conversions are registered by each format and can be discovered at runtime.

Converting to a Known Type

Use the generic Convert<TTarget> overload when you know the target type at compile time:

TapFile tap = TapFormat.Instance.Read(stream);

// Convert TAP → TZX:
TzxFile tzx = IOFileConversion.Convert<TzxFile>(tap);

// Convert TAP → PZX:
PzxFile pzx = IOFileConversion.Convert<PzxFile>(tap);

// Convert TZX → TAP:
TapFile tapFromTzx = IOFileConversion.Convert<TapFile>(tzx);

Converting with Error Handling

Use TryConvert when the conversion might fail. This is useful for conversions like TZX → TAP or PZX → TAP where not all block types can be represented:

if (IOFileConversion.TryConvert(tzx, TapFormat.Instance, out var result, out var error))
{
    result.Write(stream);
}
else
{
    Console.WriteLine($"Conversion failed: {error}");
}

Converting to WAV

Use ConvertToWav to produce a WAV audio file from any tape format. An optional sample rate can be specified (default is 44100 Hz):

WavFile wav = IOFileConversion.ConvertToWav(tap);

// With a custom sample rate:
WavFile wav = IOFileConversion.ConvertToWav(tap, sampleRateHz: 48000);

Discovering Supported Conversions

Use GetSupportedConversionFormats to discover which target formats are available for a given source:

IReadOnlyList<IOFileFormat> targets = IOFileConversion.GetSupportedConversionFormats(TapFormat.Instance);
foreach (var format in targets)
{
    Console.WriteLine(format.Name);
}

Note that RZX recordings do not participate in conversion — no converters are registered to or from the format.