module Z80::TZX

Adds the TAP format support to your program.

Example:

puts Z80::TAP.parse_file("examples/calculator.tap").to_a

Program: "calculator" LINE 10 (226/226)
Bytes: "calculator" CODE 32768,61

# convert a TZX file to a TAP file (may not work for custom loaders)
Z80::TAP.parse_file('foobar.tzx') {|t| t.save_tap('foobar', append:true) }

Program.import_file will make use of Z80::TAP.read_data. Pass additional :index => n argument to Program.import_file to choose n'th chunk from a tap file.

import_file 'somefile.tap', :index => 2

You may

include Z80::TAP

in your Z80 Program class and instances of it will be enriched by the three additional methods:

In fact this mixin may be used on any class as long as either:

The method to_tap_chunk may be overridden to create other TAP file types. The default implementation creates a TYPE_CODE file based on code and org properties of the base class. The custom implementation may use one of the: TAP::HeaderBody.new_code, TAP::HeaderBody.new_program, TAP::HeaderBody.new_var_array helper methods to construct a proper TAP::HeaderBody instance.

See:

faqwiki.zxnet.co.uk/wiki/TAP_format

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

Constants

Body

A struct which represents the body chunk of a TAP file. Property data is a binary string containing the body data.

A struct which represents the header chunk of a TAP file.

Header struct properties:

  • type - 0 - program, 1 - number array, 2 - character array, 3 - code.

  • name as a string.

  • length the expected bytesize of the following body chunk as an integer.

  • p1 as an integer.

  • p2 as an integer.

For a program:

  • line returns the starting line of a program.

  • prog_length returns the length in bytes of the program itself.

  • vars_length returns the length in bytes of the variables data.

For a number or character array:

  • array_name returns the original variable name.

  • array_head returns the header octet of the original variable.

For a code:

  • address, addr and org returns the original code address.

TYPE_CHAR_ARRAY
TYPE_CODE
TYPE_NUMBER_ARRAY
TYPE_PROGRAM

Public Class Methods

parse_file(filename, &block) click to toggle source

Returns an Enumerator of TAP::HeaderBody chunks representing segments of a TAP file. Optionally unwraps TZX headers.

Pass a block to visit each chunk.

# File lib/z80/tap.rb, line 380
def parse_file(filename, &block)
        tap = File.open(filename, 'rb') {|f| f.read }
        TAP.parse_tap(tap, filename, &block)
end
parse_tap(tap, file='-', &block) click to toggle source

Returns an Enumerator of TAP::HeaderBody chunks representing segments of a TAP blob. Optionally unwraps TZX headers.

The tap argument must be a binary string. Pass a block to visit each chunk. Optionally pass a file name for error messages.

# File lib/z80/tap.rb, line 392
def parse_tap(tap, file='-', &block)
        tap, is_tzx = TAP.unpack_from_tzx_header tap, file
        enu = ::Enumerator.new do |y|
                header = nil
                loop do
                        if is_tzx
                                tap = TAP.unpack_from_tzx_chunk tap, file
                        end
                        size, tap = tap.unpack('va*')
                        break if size.nil? && tap.empty?
                        chunk, tap = tap.unpack("a#{size}a*")
                        raise TapeError, "Invalid TAP file checksum: `#{file}'." unless cksum(chunk)
                        raise TapeError, "TAP block too short: `#{file}'." unless chunk.bytesize == size
                        type, data, _cksum = chunk.unpack("Ca#{size-2}C")
                        case type
                        when 0x00
                                raise TapeError, "Invalid TAP header length: `#{file}'." unless data.bytesize == 17
                                header = Header.new(*data.unpack('CA10v3'))
                        when 0xff
                                unless header.nil?
                                        data, = data.unpack("a#{header.length}") if data.bytesize >= header.length
                                        raise TapeError, "TAP bytes length doesn't match length in header: `#{file}'." unless data.bytesize == header.length
                                end
                                chunk = HeaderBody.new header, Body.new(data)
                                header = nil
                                y << chunk
                        else
                                raise TapeError, "Invalid TAP file chunk: `#{file}'."
                        end
                end
        end
        if block_given?
                enu.each(&block)
        else
                enu
        end
end
read_chunk(filename, name:nil, index:nil) { |chunk| ... } click to toggle source

Reads a TAP::HeaderBody chunk from a TAP file.

Pass additional :name argument to search for the header name or a :index => n argument to choose the n'th chunk (1-based) from a file.

Pass a block to visit a chunk.

# File lib/z80/tap.rb, line 359
def read_chunk(filename, name:nil, index:nil)
        parser = parse_file(filename)
        chunk, = if name.nil?
                index = 1 if index.nil?
                parser.each.with_index(1).find { |chunk, i| i == index }
        else
                parser.find { |chunk| name === chunk.header.name }
        end
        return unless chunk
        if block_given?
                yield chunk
        else
                chunk
        end
end
read_data(filename, **opts) click to toggle source

Reads a data chunk from a TAP file. Returns a binary string.

Program.import_file uses this method to read from a TAP file.

See read_chunk for opts.

# File lib/z80/tap.rb, line 339
def read_data(filename, **opts)
        TAP.read_chunk(filename, **opts) do |chunk|
                if chunk.header
                        $stderr.puts "Importing: `#{filename}': (#{chunk.header.name})"
                else
                        $stderr.puts "Importing: `#{filename}': headerless chunk"
                end
                return chunk.body.data
        end
        raise "Chunk: #{index} not found in a TAP file: `#{filename}`"
end

Public Instance Methods

save_tap(filename, append:false, name:nil, **opts) click to toggle source

Saves self in a TAP file.

The tap data is being generated by to_tap_chunk.

filename specifies the file name to save to. The “.tap” extension may be omitted.

Options:
  • :name should contain max 10 ascii characters. If not given, the base name of a filename will be used instead.

  • :append if true the data will be appended to the file. Otherwise the file is being truncated.

Any additional option will be passed to the to_tap_chunk method.

# File lib/z80/tap.rb, line 77
def save_tap(filename, append:false, name:nil, **opts)
        name = File.basename(filename, '.tap') unless name
        to_tap_chunk(name, **opts).save_tap filename, append:append
end
to_tap(name, **opts) click to toggle source

Produces a TAP blob as a binary string from self.

A sugar for calling TAP::HeaderBody#to_tap method on the result produced by to_tap_chunk.

name should contain max 10 ascii characters. Any options given will be passed to the to_tap_chunk method.

# File lib/z80/tap.rb, line 88
def to_tap(name, **opts)
        to_tap_chunk(name, **opts).to_tap
end
to_tap_chunk(name, org:nil) click to toggle source

Creates a TAP::HeaderBody chunk from self.

By default it uses Z80#code and the Z80#org to produce the tap data.

This method is used by to_tap and save_tap.

name should contain max 10 ascii characters. Optionally org may be given to override the starting code address.

# File lib/z80/tap.rb, line 100
def to_tap_chunk(name, org:nil)
        HeaderBody.new_code(name, code, org||self.org)
end