Quadbike 2 Manual

by 'Diminished'
revision 5
1st January 2026

  1. What?
  2. Terminology
  3. Package Contents
  4. Digitising a Tape
  5. Diagnostics
  6. Building Quadbike
  7. How it Works
  8. Output Formats, Part Two
  9. PHP Scripts
  10. Revision History

1. What?

This is a piece of software for transcribing digitised recordings of Acorn format tapes (as used on the BBC Micro and Acorn Electron) to TIBET files, or CSW files.

It may also find use in transcribing recordings from other microcomputers that adhere to the CUTS or Kansas City tape standards, such as the Compukit UK101.

2. Terminology

The process of playing a tape on a tape player, and recording the resulting signal using a PC to an audio file such as WAV, will be referred to here as digitisation.

The process of using Quadbike (or another tool) to process the resulting audio file into some sort of data image format will be referred to as transcription.

One half-cycle of 1200 Hz tone, or one full-cycle of 2400 Hz tone, will be referred to as an atom. The region between each pair of red lines in the diagram below represents one atom:

atoms

3. Package Contents

4. Digitising a Tape

Preparation

Before digitising a tape, try to observe the following recommendations:

Before beginning the digitisation process proper, wind the tape forwards into a section where signal exists, and adjust the tape player's volume level, and possibly the input record level on the PC. Try to pick a volume that yields a recording that reaches roughly half of the maximum allowed "clipping" level, like so:

Appropriate Volume Level

This will hopefully provide sufficient headroom to accommodate any spike in volume over the length of the tape, but also ensure a sufficiently powerful signal that Quadbike receives good bit resolution.

If the audio "clips" (i.e. exceeds the maximum PC input volume level) at any point during a data block on the tape, then the audio file should be considered worthless, and must be discarded:

Clipped Input

In this case, another digitisation attempt should be made, with a lower volume set on the tape player.

Certain cassette players have "auto-reverse" features, which can play the tape in either direction without having to flip it over physically. Playing a tape in the opposite direction in this way can sometimes yield a better quality recording.

Record Settings

You should perform these recordings at either 44100 Hz or 48000 Hz sampling rates. (Quadbike will also accept 22050 Hz input files, but it will reject sample rates lower than this). Stereo is recommended because it gives "two bites of the cherry"—if one channel's signal is incomprehensible to the software at a particular point on the tape, it may be possible to substitute the audio from the other channel instead. This may be done either before transcription, by copying and pasting audio between channels of the digitised file, or after transcription by amalgamating the resultant TIBET files.

There is some evidence that digitising using 24-bit resolution may be substantially superior to 16-bit resolution. If you have a capture device that is capable of 24-bit sampling resolution (usually an external USB device intended for music production), it is a good idea to exploit it. This is particularly advisable if the tape exhibits a wide dynamic range (i.e. some parts of the tape are very quiet).

Do not be tempted to process the digitised audio before feeding it to Quadbike. A processing step such as applying a bandpass filter might seem like a sensible choice, but doing so generally leads to inferior outcomes. (Besides, Quadbike has its own optional bandpass filter which you can activate using the -f option, although again, this rarely improves things). It is also better to keep a raw copy of the input audio in as unadulterated a form as possible. You can always add processing later if you want to, but you cannot take it away.

If you are able to do so, you might consider making a copy of the raw input audio available for community use. A number of repositories of Acorn tape audio already exist, but more would be very welcome:

The output format in which the file is saved must be a lossless one. Do not save to MP3 or another lossy audio format! Appropriate output formats include WAV, AIFF and FLAC. Do not export in an 8-bit format. If you digitised the tape in 24-bit resolution, you should use a 24-bit output format. If you captured in 16-bit, then a standard 16-bit integer format will suffice.

Using Quadbike

Requirements

Quadbike currently requires a 64-bit operating system. For longer tapes, you may need at least 4 GB of RAM.

Windows

If you are using the supplied, pre-built Windows binaries, you have a choice of two different executables. Note that these exe files are command-line executables, which must be run from within cmd.exe. There is no GUI for this software.

Linux and macOS

No binaries are provided for these two operating systems, so you will have to build your own. See the section on Building Quadbike for more details.

Quadbike for Linux has so far been built and tested successfully on x86-64. A macOS version has been built and tested successfully on both Intel and Apple Silicon (M4) machines.

A Note on Output Formats

This is a brief discussion of Quadbike's output formats. There is a more in-depth discussion on this subject later in this document.

Quadbike currently offers two output formats—its own format TIBET (as well as the gzip-compressed version of this, TIBETZ), and CSW. CSW encodes input audio as a simple list of pulse lengths:

csw-primer

Although the CSW file format has some fundamental problems (see the dedicated section on this later), it is admittedly useful at this time because many BBC Micro emulators will load CSW natively, and it can also be fed to the Windows tool MakeUEF, to attempt to produce a UEF file.

TIBET files are a simple text-file-based representation of the atoms present on the tape. TIBET files may be processed into UEFs using a PHP script—tibetuef.php—which is included with Quadbike. I consider TIBET to be a superior format to CSW, but it is very new, and work is ongoing to improve its compatibility. Development of native emulator support for this format is under way.

UEF support in Quadbike itself may appear at some point in the future. Or not.

A Note on Phase

With PLL or walk synchronisation modes, Quadbike needs to know the phase of the input signal at all times. By default, it will auto-detect a fixed phase value, and apply it to the entire input length. This is usually appropriate for commercial recordings. If, however, you are processing an amateur recording that may contain different phases on the same tape (usually because it has been written to the tape in multiple sessions, perhaps using various different tape recorders), the phase may change through the course of the recording. In this case, you are advised either to use the -p block option, which will attempt to detect the phase individually for every block on the tape, or the phase-insensitive -s freq option, which selects frequency sync mode. In walk and PLL sync modes, Quadbike will report per-block detected phases as it runs, which can help the user to make an informed decision about this.

Phase is covered in detail in its own section later, and will also be discussed in the context of the PLL and walk synchronisation methods.

Basic Usage

Once you have successfully digitised the tape to an audio file, such as WAV, AIFF or FLAC, you may now attempt to process the file using Quadbike. Assuming the digitised input is in a file called input.wav, you can get the default TIBET output file qb.tibet with this simple command:

quadbike input.wav

or, if you wish to choose the name of the output TIBET file (-t):

quadbike -t output.tibet input.wav

Gzipped TIBET output is also available, the so-called "TIBETZ" format, using the -z option:

quadbike -z output.tibetz input.wav

If you want CSW output rather than TIBET, use -o instead of -t:

quadbike -o output.csw input.wav

Quadbike can also produce these in combination, so if you want all possible output formats at once:

quadbike -o output.csw -z output.tibetz -t output.tibet input.wav

Estimating Transcription Quality

If a tape is stubborn, you will run Quadbike repeatedly, tuning its parameters to maximise transcription fidelity. The process for doing this will be outlined next, but first it will be useful to consider how to measure the quality of a transcription.

There are a number of ways to judge how successful an attempted transcription has been. The most immediate of these involves examining Quadbike's output messages. Towards the end of these messages, you should see lines like

    Odd runs (before fix): (data 0/1): 10442/6037; (squawk 0/1): 0/0.
    Odd runs (correction): Inserted 16479 atoms.
    Odd runs (after fix) : (data 0/1): 0/0; (squawk 0/1): 0/0.

"Odd runs" will be described properly later, but for now, remember that they are undesirable. The first of these three lines above (i.e. the "before fix" line in bold) is the important one—you want these numbers to be as small as possible. The above example is of a very poor transcription suffering from tens of thousands of odd runs (10442/6037). Ideal transcriptions will have a value of 0/0 in this field.

If you are trying to secure the best quality transcription for a given audio file, you should prefer configurations that make these numbers as small as possible. Ideally, there will be zero odd runs.

Scan Mode (new in 2.0.4)

Quadbike 2.0.4 introduced the --find-best-hf-pwr option, also known as "scan mode", which will try to find the best values of --scale-hf-pwr for a given input file and configuration. (This may take a while to run.)

For example:

quadbike --find-best-hf-pwr input.wav

You can use tuning options in combination with this mode, so to scan the right channel using PLL sync and a forced phase of 90°:

quadbike -c R -s pll -p 90 --find-best-hf-pwr input.wav

This will produce output resembling the following:

    Looking for best 2400 Hz scaler: done.
      Odd run measurements for 2400 Hz scaler values:
        0.4 :     28     24     12      2      2      2      3      3
        1.2 :     13     25     29     29     29     29     33     41
        2.0 :     49     67     91    133    169    246    363    487
        2.8 :    673    926   1272   1720   2240   2858   3568   4417
      Best scan odd-run value detected: 2.

      Try these --scale-hf-pwr values:
        0.7, 0.8, 0.9.

(Note that no output file is produced in this mode; Quadbike should now be run again using the wisdom gained by the scan.)

Here, a scan has determined that the lowest achievable number of odd runs in this configuration is 2. The final line of output here recommends scaler values in the range 0.7 to 0.9 to achieve this minimum number of odd runs. Further transcription attempts should therefore be performed using --scale-hf-pwr 0.8 or so.

Scan mode is very useful when trying to find the optimum way to proceed with a troublesome tape.

Note that not all user options are respected during this scan. For example, odd-run correction is disabled in frequency-sync mode (i.e. the equivalent of --no-default-odd-fix), so that the odd run count can be measured. To save time, tape speed is not measured in scan mode; 1.0 is assumed.

Tweaking Parameters: Recommended Approach

It is suggested that you try the following configurations, in the following order, to transcribe a recording of a tape. If a configuration fails to produce meaningful output, then you should move on to the next one.

Frequency sync mode is the fastest, is often the most fidelitous, and is insensitive to the input phase. This is therefore the default sync mode, but it may also be selected explicitly with -s freq. Note that prior to 2.0.3, the default sync mode was PLL, but this has been changed following some testing. (For more information on synchronisation methods, see the relevant section):

quadbike -s freq -t output.tibet input.wav

(Note that by default Quadbike will refuse to overwrite existing output files; if you want it to do this as you try multiple runs with different configurations, -! is the relevant option.)

If the output is not useful, and you digitised the tape in stereo, try repeating the above using the right audio channel (-c R) instead of the left (this example selects CSW output instead of TIBET):

quadbike -c R -! -s freq -o output.csw input.wav

The next option to try is probably the --scale-hf-pwr <x.y> option (e.g. --scale-hf-pwr 2.0), which multiplies the 2400 Hz signal power by a constant decimal value (between 0.5 and 3.5—the default is 1.0, or 0.9 for frequency sync mode). Use the --find-best-hf-pwr "scan mode" option first in order to find a sensible value to use here.

One miscellaneous note on --scale-hf-pwr usage: If you are transcribing a "perfect" input signal that has not been digitised from a real tape, but rather has been generated using a piece of software (e.g. PlayUEF, csw2wav.php), you may encounter serious problems unless you attenuate the 2400 Hz power using --scale-hf-pwr 0.5 for example. The reason for this is currently unknown.

If the output is still troublesome even with an optimal --scale-hf-pwr value, you might try PLL sync mode next (-s pll):

quadbike -! -s pll -t output.tibet input.wav

PLL mode is phase-sensitive, so remember to add -p block (see above) if needed.

You can of course combine -s pll with --scale-hf-pwr and -c R; all such combinations are valid possibilities.

If you still have no useful output at this point, things start to get trickier. If there is just a single block or two on the tape causing errors, it may still be possible to produce a meaningful result using some of Quadbike's tuning options. If, however, there are a large number of block errors, your time might be better spent attempting to get another capture of the tape, perhaps with higher input volume levels, employing different equipment, or using a higher bit depth or sampling rate.

Quadbike has some other options—you can see these by looking at the help display:

quadbike -h

These will be discussed in detail in later sections.

Messages

The program output should hopefully be fairly self-explanatory, but there are a few points to mention:

Messages prefaced by E: are errors. These are nearly always fatal, e.g.:

E: Right channel selected on mono input file. Aborting.
E: Error code #21.

Messages prefaced by W: are warnings, e.g.:

W: [243408] -> [243428]: (-s freq) forced-even run [1 -> 2]

Quadbike will display more warnings if you start it with the -v switch.

Also of note in this output is the square-bracket notation. Square brackets containing an integer (e.g. [243408] above) refer to the sample number in the input file at which an event occurs, which makes it easy to locate the source of any errors or warnings in the audio.

The software makes wide use of progress indicators, which keep the user updated with the progress of the current sub-operation. If you are piping output messages to a text file, it is usually better to switch progress indicators off; this may be accomplished with the -q switch.

Pre-processing

Two "front-end" transformations are available that may be performed by Quadbike on the input signal before any further processing is done—normalisation, and bandpass filtering. Neither is particularly useful.

The -n option activates normalisation.

The -f option enables the bandpass filter, with a passband between 400 Hz and 3200 Hz.

Quadbike's internal signal processing is all performed using floating-point numbers rather than integer values, so normalisation (to a nominal amplitude of 1.0) is only likely to improve transcription fidelity in niche cases.

Bandpass filtering might seem like a worthwhile activity for a signal of this type, but experiments suggest that it usually degrades transcription fidelity, rather than improving it.

Don't bother with pre-processing unless you're desperate.

5. Diagnostics

Quadbike has a diagnostic capability, which allows the user to see some of the intermediate data it generates during processing. Much of this intermediate data may be saved to disc as a collection of WAV files. These WAV files are intended to be viewed in an audio editing tool such as Audacity, placed alongside the original input signal. Playing them back is not recommended!

To produce diagnostic files, create an empty directory somewhere, and pass its path to Quadbike using the --inspect-dir option:

quadbike --inspect-dir /path/to/inspect/dir input.wav

As with normal output files, Quadbike will refuse to overwrite already-existing diagnostic files by default. Once again, this may be overridden with the -! option.

Be warned that you may need a lot of free disc space. In the worst case, Quadbike may create twenty or so diagnostic WAV files. Each of these will be of a similar duration to the input audio file (although they are all 8-bit rather than 16-bit). Beware!

With --inspect-dir, Quadbike will always create the following files, regardless of the sync mode:

With -s pll, the following optional files will also be generated:

If --no-default-odd-fix is not specified, -s pll will also generate this file:

With -s walk, the following optional files will also be generated:

With -s freq, the following optional file will be generated:

If the pre-filter was selected (-f), this file will also be generated:

Many of the diagrams in this document were created using Quadbike's diagnostic output files.

6. Building Quadbike

Windows

You will need to download an appropriate libsndfile runtime package (e.g. libsndfile-1.2.2-win64.zip), and extract it somewhere. You will also need zlib. I have included zlib.lib, zlib.h and zconf.h files (for 64-bit Windows) matching the bundled zlib.dll version in the windows/zlib-build-deps directory, to save you having to build zlib yourself.

The code has successfully been built using Visual Studio 2026. I have not provided a Visual Studio project, but the steps to take to create a working build project for 64-bit Windows go something like this:

In my testing, AVX-512 offers no performance advantage relative to AVX2 at this time. The provided build of the vectorised Windows-x64 edition of Quadbike is therefore an AVX2 type, in order to remain compatible with CPUs going back to 2013. At some point I may revisit this to see if the AVX-512 version can be improved.

Linux and MacOS

On Linux, you will need the libsndfile-dev (or similar) and zlib packages pre-installed, as well as a C compiler (either gcc or clang).

On MacOS, you will need to get a development libsndfile from somewhere. You can build this yourself from source, or obtain a copy from e.g. the Homebrew project. You will obviously also need the XCode development tools installed.

If you have libsndfile or zlib installed in a non-standard location, you will need to edit the src/build.sh file and alter the I and LP variables to reflect this. If you have installed these packages using your Linux distribution's package manager, then this will not be necessary.

Change directory to src/, and make the build.sh script executable:

$ cd src
$ chmod u+x build.sh

A typical command to build for Linux would be:

$ ./build.sh linux gcc release

For macOS:

$ ./build.sh macos clang release

If the build was successful, you should now be able to run Quadbike by typing:

$ ./quadbike -h

The build.sh script takes the following options:

build.sh <linux|macos> <gcc|clang> <debug|release>

These arguments should be self-explanatory—they pick a target operating system, preferred compiler and the type of build required.

Prior to 2.0.3, build.sh featured a fourth argument to configure SIMD support. Now, Quadbike's arithmetic is expressed by default using 16-wide vectors; it is left to the compiler to decompose these 16-wide operations into more numerous, smaller calculations, if your CPU does not support 16-wide SIMD float operations.

7. How it Works

Signal

BeebWiki has a useful primer on the Acorn cassette format.

It states that the low frequency used by the BBC Micro to represent a 0-bit is 1201.9 Hz, which makes the high frequency used to represent a 1-bit twice this, at 2403.8 Hz. These values are the nominal frequencies used by Quadbike. Although the terms "1200 Hz" and "2400 Hz" are used in this document, it should be borne in mind that the correct frequencies are actually fractionally higher than this.

Goertzel's Algorithm

Quadbike uses Goertzel's algorithm (a means of realising a partial Fourier transform) to compute the instantaneous power of the two significant frequencies present in an Acorn tape signal.

Quadbike works on short pieces ("blocks") of sampled input data of length N. The discussion of the Goertzel algorithm found at embedded.com recommends that "you want the target frequencies to be integer multiples of sample_rate / N". Thus we choose this block size to be N = sample_rate / 1201.9. At 44100 Hz, this implies a block size N of 37 samples (although this number will be modulated by detected span tape speed—more on which later).

one Goertzel sample

The diagram above shows a highlighted 37-sample block of input (the upper trace). The Goertzel algorithm is run twice over this block with two different omega values (normalised frequency in radians per sample), corresponding to the queried frequencies of 1200 Hz and 2400 Hz. This yields a pair of output values, which are the 1200 Hz power ("0-power") and 2400 Hz power ("1-power") measured across the input block.

The output values are considered to lie at the centre of this 37-sample input block, so the arrows on the power0 and power1 traces show where the outputs of this operation are placed into the power streams.

The input block is then advanced one sample later in the source audio, and another pair of Goertzel runs is performed to compute the next 0-power and 1-power output values. This process continues over the entire input audio file, to produce two complete power traces.

By subtracting one from the other samplewise, and taking the absolute value of the result, we can also compute a "confidence" level. This will drop to near-zero at the transition points where the signal flips from 1200 Hz to 2400 Hz and vice-versa (bottom trace):

power0, power1, confidence

Spans

Acorn tapes are composed of regions that can be categorised one of three ways:

Before Quadbike does any further processing, it identifies these regions and marks them up as "spans" of one of these categories. This analysis is done on the basis of the Goertzel power values just measured, according to a fairly basic set of rules:

There is some hysteresis applied to the above process. The threshold for switching from a silent span to a signal span is substantially greater than the threshold for switching from a signal span to a silent span. This is done to try to ensure that data spans begin right up against the start bit, with no "setup noise" sneaking its way onto the beginning of the data span.

This algorithm has a "false positive" bias: There is a tendency to incorrectly mark up spans that should be silent as signal spans. As a result, a second processing step is performed. The Goertzel 0-power and 1-power are both summed across each span and then divided by the span length, to yield a pair of integrated power values for each span. These sums are then compared to a third threshold. If both 0 and 1 summed power values are below this third threshold, and the span is a data span, it will be posthumously converted to a silent span.

As of Quadbike 2.0.1, this third threshold may be controlled using the --hush-thresh command-line option. If a tape transcription attempt is producing a number of "junk" data spans that should in fact be silent, you can try raising this threshold. This will increase the probability that marginal data spans will slip "under the bar" and be converted to silent spans. --hush-thresh takes a value between 0 and 100, which describes the range between a pair of sensible upper and lower limit values.

The thresholds employing during these processes are not absolute values—instead they are a fraction of the maximum instantaneous power detected in the signal. If the maximum Goertzel power measured across the entire tape is, say, 100.0, then the threshold values that are actually used will be scaled (multiplied) by this measured 100.0 maximum value. If the input signal is quieter and its maximum detected power value is, say, 50.0, then the thresholds actually used in these processes will be half what they were for the 100.0 case.

spans

The three span types are processed differently—no transcription is attempted within leader or silent spans. Leader spans are instead filled with synthetic 1-valued atoms in a quantity that is appropriate for their length (modulated by the span's measured tape speed).

Two adjacent spans cannot be of the same type; it is not possible to encounter a data span which is followed immediately by another data span.

"Squawks"

Many commercial tapes feature short bursts of cycles that contain no meaningful data. UEF refers to these as "security cycles". These can appear in a few different contexts, but they most commonly crop up immediately following silence on the tape, immediately before leader tone begins. Such post-silence cycle bursts are referred to here as "squawks". Quadbike mostly treats them as data spans, but there are some points to note.

squawk1

Cycle bursts immediately following silence are an issue if the PLL is used for synchronisation, since the PLL will have been offered no opportunity to lock onto any carrier before the squawk. An unconventional strategy must be employed in this case to allow the PLL to achieve good lock. This approach will be discussed in detail when PLL lock is described later on.

Squawks have a few notable properties:

  1. They are always relatively short, lasting no longer than a few dozen cycles.

  2. They may contain lone pulses (half-cycles) of tone.

    Quadbike's smallest unit of resolution is one atom. Quadbike cannot detect singleton pulses of 2400 Hz tone (although it can detect singleton pulses of 1200 Hz tone, as these are atoms). Both TIBET and CSW output file formats offer ways of representing lone 2400 Hz pulses, but Quadbike is unable to transcribe these.

  3. Squawks may contain odd runs of tone.

    The squawk in the above example begins with three cycles of 2400 Hz tone. At 1200 baud, this three-cycle sequence essentially represents 1½ bits. Sequences like this that are composed of an odd number of atoms are known as "odd runs". Odd runs will be elucidated (in the context of data spans rather than squawks) later on.

Squawk-like cycle bursts that are not immediately preceded by silence on the tape receive no special treatment. As far as Quadbike is concerned, these are just normal data spans.

Tape Speed

For both recording and playback, the speed of the tape will not be perfectly nominal. Tape speed errors will take the form both of bias (a constant error caused by mis-calibration of the spindle speed on either recording or playback), and wow/flutter (periodic changes in the tape's speed as it plays).

Another potentially disastrous source of tape speed error is a sticking tape spindle. Once again, it is highly advised that tapes are fast-forwarded through from start to finish in both directions before they are captured to a digital audio format, in the hope that this will help to free up any stickiness in the mechanism. You might also try gently flexing the cassette shell to loosen things up.

It should be remembered in general that tape speed is a variable, not a constant.

In an attempt to counter this problem, Quadbike measures a distinct tape speed value for each non-silent input span (data or leader). This is performed by picking a region of audio data in the middle of each such span, and repeatedly running the Goertzel algorithm on this region with an incrementing query frequency, to attempt to measure what the "2400 Hz" frequency actually is.

Currently, frequencies are searched from 1803 Hz up to 3004 Hz in increments of 1 Hz. Goertzel power is computed over the tested region for all of these queried frequencies. The frequency at which Goertzel power was maximised—i.e. where the queried frequency most closely matches the frequency on the tape—is then chosen as the measured frequency. Tape speed may be calculated as (chosen frequency / 2403.8 Hz).

Quadbike's permissive range for this process is tighter than its measurement range; a tape speed measurement that lies outside of the permitted range will be rejected. If the span under test is a data span which also contains some 1200 Hz tone, then the test will be repeated from 900 Hz to 1502 Hz in the hope of obtaining a more sensible measurement.

If both attempts to measure tape speed for a span result in nonsensical outcomes, then the span will simply inherit the most recent valid tape speed that was measured on a previous span. If no such valid speed has yet been established, 1.0 is assumed.

These measured per-span tape speeds are used in further decoding work later on.

To save time, tape speed is not measured when using --find-best-hf-pwr "scan mode"; 1.0 is assumed.

Synchronisation Methods

Goertzel's algorithm is fine for determining whether sections of input audio are 1200 Hz or 2400 Hz, but to go from these 0-power and 1-power traces to recovering actual bits of data requires an additional step—synchronisation. For example, although this diagram clearly shows a long run of 1200 Hz cycles, it is not clear from the power traces precisely how many of these cycles there are:

sync

It is therefore necessary somehow to derive a list of atom timings—the instants at which the two competing signal powers should be measured and compared, to decide whether 0-power or 1-power is the greater. This process of determining such a list of instants is known as synchronisation, which produces the bottom trace in the diagram below:

sync2

As mentioned, Quadbike offers three synchronisation methods, selectable using the -s command line switch:

These will shortly be discussed in detail, but first it is necessary to say a word or two about phase.

Phase Considerations

The BeebWiki article linked earlier describes the tedious phenomenon of phase well.

Here is an example:

phases

These four traces show the same signal, but with four different phase values. "Phase" in this case refers to the point in a cycle where the frequency switches from 1200 Hz to 2400 Hz or vice versa. All four of these traces are valid Acorn tape signals, but all four change frequencies at different points in the cycle (marked by the vertical line).

In general, an audio recording of an Acorn tape has an unknown phase value. Depending on how the tape was originally recorded, the phase value may even change from one file on tape to the next.

Two of Quadbike's synchronisation modes (-s pll and -s walk) have phase dependencies, such that the phase of the signal needs to be determined before processing can be performed. The frequency domain synchronisation mode (-s freq), however, is phase-agnostic.

There are currently six ways to tell Quadbike how to deal with phase.

Additionally, there are two algorithms available with -p block and -p auto for determining the input phase. One of these algorithms may be selected using the --phase-det option:

You may notice that these two options mirror the -s pll and -s walk sync modes. By default, if using automatic phase detection, Quadbike will select the phase detection algorithm that matches the sync mode used. This generally produces the best result. However, if desired, it is possible to change this by forcing the other phase detection scheme, e.g.

-s pll --phase-det walk

(synchronise using PLL, but detect phase using walk mode)

or

-s walk --phase-det pll

(synchronise using walk mode, but detect phase using the PLL.)

Quadbike will report the results of automatic phase detection for both -p block and -p auto. Here is an example with -p auto (which is the default):

Phases (PLL ):
   b_a_a_a_b_a_b_a_a_a_a_ c_b_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_b_
  b_a_a_a_a_a_a_a_b_a_a_a_a_a_a_a_a_a_a_a_a_ a_a_a_a_a_a_a_a_a_a_a_ a_a_
  a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_a_b_a_a_a_a_b_a_a_a_b_b_a_a_a_a_
  b_b_a_a_a_c_a_ 
Using majority span phase verdict: 0 (a)

Silent spans have no phase, and so are represented by spaces. Leader spans—for which the concept of phase is not meaningful—are represented as underscores. Data spans appear as the codes a - d, corresponding to detected phases 0, 90, 180 and 270.

In "auto" phase mode, Quadbike will take the most commonly occurring value, and use it as the global phase to decode the entire tape. In the example above, a (phase 0) is the most commonly occurring span phase, so this is picked as the global value for the whole source. Remember that if you have run Quadbike once already using -p auto (the default), you can then supply this measured phase value manually to Quadbike for future runs, which saves time over having to detect the phase every time.

The "block" phase mode works similarly, except the phase for each data span is used for the decoding of that span only. Again, -p block is the option to try if the source is suspected to contain multiple phases (or use the phase-insensitive sync mode -s freq).

It should be noted that Quadbike's CSW output (-o) always produces CSWs with a global phase of 0, regardless of what the input phase was. For more on CSW and phase, see later.

Walk Mode (-s walk)

Synchronisation

We now return to the matter of synchronisation—when to sample the 0-power and 1-power, in order to identify bits on the tape.

In walk mode (-s walk), the position of atom instants is determined in the "traditional" way—by inspecting the input waveform.

Quadbike attempts to identify all of the peaks and valleys within the input signal (which are known as "features"). Once each feature has been identified, it is added to a list along with the sample number in the input at which it was found.

Phase 0

For a phase value of 0, atoms are generated at both peaks and valleys of 1200 Hz cycles, and half-way between each peak and valley for 2400 Hz cycles (but not between valley and peak). See the red dots:

sync-walk-phase-0
Phase 90

For a phase value of 90, atoms are generated between each peak and subsequent valley of 1200 Hz cycles (but not between valley and peak), and at the valleys of 2400 Hz cycles.

sync-walk-phase-90-1

You will notice that this leaves gaps in the atom timings, but these are filled in through interpolation (the downwards-pointing spikes labelled with +):

sync-walk-phase-90-2
Phase 180

Phase 180 is the inverse of phase 0, so atoms are generated at both peaks and valleys of 1200 Hz cycles (as for phase 0), and half-way between each valley and subsequent peak of 2400 Hz cycles (but not between peak and valley):

sync-walk-phase-180
Phase 270

Phase 270 is the inverse of phase 90. Atoms are generated between each peak and subsequent valley of 1200 Hz cycles (but not between valley and peak), as for phase 90, and at the peaks of 2400 Hz cycles.

sync-walk-phase-270-1

Again, for phase 270, gaps are left in the atom timing, which are filled through interpolation:

sync-walk-phase-270-2
Interpolation

The replacement of missing atoms by interpolation in walk mode is not exclusive to adding missing cycles from phase 90 and phase 270. In general, any gap in the timings list longer than 1.6 ideal atom lengths may have synthetic atoms interpolated into it. This helps to provide some defence against transient situations in which the input signal is degraded to the point at which features are not easily identifiable.

Phase Detection

Walk-mode phase detection (--phase-det walk) is performed by considering the same list of features (peaks and valleys) that is used for the walk-mode synchronisation method above. Specifically, it examines the durations of the gaps between features, looking for certain patterns that occur at points where the frequency changes.

Let the feature numbered x be denoted by Fx. Let the length in samples of one atom (a single 2400 Hz cycle) at this sampling rate be denoted by 2L. Then, we are interested in the prevalence of gaps having lengths of L, (3L / 2), and 2L. We would expect to find occurrences of the following gap timing patterns when frequencies switch from 1200 Hz to 2400 Hz:

For phase 0:
phase-det-walk-phase-0
For phase 90:
phase-det-walk-phase-90
For phase 180:

Phase 180 has the same timings as phase 0, but it starts on a valley rather than a peak:

phase-det-walk-phase-180
For phase 270:

Finally, phase 270 has the same timings as phase 90, but it starts on a peak rather than a valley:

phase-det-walk-phase-270
Scoring

Scores for each of the four possible phases begin at zero. Scores are updated while scanning each data span. Whenever one of the above timing phenomena is observed during this scan, the score for the corresponding phase is incremented. The phase with the highest score by the time the span has been fully checked is declared the winner, and used as the detected phase for the whole span.

PLL Mode (-s pll)

Carrier Recovery

PLL synchronisation works by using a software phase-locked loop (PLL). This outputs a synthetic carrier signal at 2400 Hz, synchronised to the source audio. In order to lock onto the the source audio, the PLL requires a continuous 2400 Hz input signal derived from this source. Since the source flips between sections of 1200 Hz and 2400 Hz, some processing needs to be done on it in order to generate a continuous 2400 Hz PLL input signal. Specifically, the 1200 Hz sections will need to be replaced with 2400 Hz sections instead.

We wish to double the frequency of the 1200 Hz sections of the source:

The 1200 Hz parts of the signal are of the following form:

S[n] = cos ((ωn / R) + Φ)

Remembering the double-angle formula for cosine:

cos 2x = 2 cos2 x - 1

This implies that we can double the frequency of 1200 Hz sections up to 2400 Hz simply by squaring the input signal:

2.S2[n] = cos ((2ωn / R) + ) + 1

(We are unconcerned with the constant +1 and the factor of 2; we only care about the frequencies involved.)

Perhaps we could then mix the squared signal with the original signal S, to form an unbroken 2400 Hz signal throughout the input? Unfortunately, squaring the signal will also double the amount of phase shift, as Φ has now become 2Φ. This has the unfortunate side-effect that signals S and S2 will no longer be in-phase with one another, and so will suffer discontinuities if mixed together.

To combat this problem, the squared input signal S2 may be shifted backwards or forwards in time by some constant number of samples K before mixing it with the unmodified, original input signal S, to produce a new signal:

X[n] = S2[n + K] + S[n]

K is a constant shift value in samples (well, technically it's a function of Φ—different values of K will be appropriate for different input phases). Suitable values of K for this application have simply been determined by experiment.

Of course, squaring the signal will not only double the 1200 Hz tones up to 2400 Hz, but also double the 2400 Hz tones up to 4800 Hz. The mixed signal X will now contain tones at 1200 Hz from S, 2400 Hz from both S and S2, and 4800 Hz from S2. In order to recover a clean 2400 Hz signal to feed into the PLL, a powerful bandpass filter with a passband between 2200 Hz and 2600 Hz is applied to the mixed signal X. This FIR filter introduces a significant amount of delay, but this delay may be trivially eliminated simply by moving each output sample from the filter "back in time" by an amount equal to the filter delay, yielding the final recovered carrier C:

carrier recovery

We end up with a continuous 2400 Hz tone to feed to the PLL.

Phase Detection

If the phase of the input is already known (e.g. by forcing it using -p), then only a single PLL input signal X needs to be generated, with an amount of shift K appropriate for the known phase. If, however, the phase is to be determined, then four candidate PLL input signals XΦ must be generated with four different candidate shift values KΦ, each chosen appropriately for one of the four possible phases. Four independent PLLs with independent states are then initialised, and the four candidate signals XΦ are run through these PLLs. The phase of the signal that produces the strongest PLL lock is favoured.

PLL Lock

The PLL will attempt to lock on to the phase of the recovered carrier signal with which it is fed. This results in a number, the lock quality value, which is updated instantaneously as the recovered carrier is processed. (This value can be inspected in the diagnostic file pll_lock.wav, if the diagnostic mode --inspect-dir is activated). Values greater than about 0.5 indicate a strong lock on the input signal.

During periods of silence, the PLL will lose lock, and the lock quality value will fall to below zero. Poor lock values may also occur during sections of tape where the input signal is degraded or noisy. The diagnostic file pll_lock.wav may be inspected for regions where the PLL has experienced poor lock quality.

PLL Lock and Squawks

Situations may occur where a burst of data occurs immediately following a region of silence on the tape. The most common phenomenon of this type is the "squawk" case, where a short burst of "security cycles" is observed at the very start of a section of leader tone. In these cases, the PLL has had no opportunity to lock onto any preceding carrier signal, and so it will not be able to synchronise well to the data burst, possibly leading to a mistranscription of the squawk.

To combat this problem, situations where data spans immediately follow silent spans are treated specially by the PLL. It is not possible to run the PLL forwards through the preceding leader section (as there is no preceding leader section), so instead everything is reversed: The PLL plays the subsequent leader section in reverse, and achieves lock on this leader. Then, it continues reversing off the start of the leader span into the data span, and transcribes the data span backwards. It then subsequently reverses this decoded data.

squawk2_pll

Once this has been done, the PLL once more locks onto the leader span, but this time played forwards, and continues through the remainder of the file as normal.

Frequency Mode (-s freq)

Frequency mode synchronisation is perhaps the simplest of the three synchronisation methods. It identifies the instants at which the Goertzel power flips from 1-dominant to 0-dominant, or vice versa, and then attempts to fit an appropriate number of atoms between these transitions. The number and position of these is based on the nominal atom length (modulated by the span's measured tape speed.)

The diagram below shows this principle for a run of four zero-valued atoms (at 44100 Hz):

freq-mode-sync

In this case, we have a zero-valued stretch with a length of 81 samples. The nominal length of an atom at 44100 Hz (assuming a tape speed of 1.0) is 18.36 samples. Four such pieces may be fitted into the 0-valued run here, with 7.56 samples left over. Once the correct number of pieces is known, a best fit strategy is used, yielding four pieces of lengths 20, 20, 20 and 21 samples. Sync is generated in the middle of each of these pieces, which finally produces the atom value sequence shown at the bottom.

Odd Runs and Fuzzy Bits

As mentioned, one atom represents (approximately) 1/2400th of a second. At 1200 baud, each atom therefore represents half of a bit. This leaves Quadbike vulnerable to a type of transcription error known as an "odd run".

At 1200 baud, similarly-valued atoms should occur in pairs. One might expect atom value sequences such as:

00 00 11 00 11 11

Since each pair of atoms decodes to a bit, the above atom sequence decodes to the bitstream

001011

Within valid 1200 baud data, any unbroken run of similarly-valued atoms should therefore have a length that is an even number.

However, it is quite possible for sequences to contain unbroken runs of similarly-valued atoms that have odd lengths. This is the "odd runs" problem. For example, the below sequence contains four odd runs:

00 01 11 01 11 11

This sequence of atoms decodes ambiguously:

0?1?11

The bits denoted by question marks are "fuzzy bits", whose value is undefined. Quadbike has a number of strategies for correcting fuzzy bits. Note that these strategies are not applied to spans that have been identified as squawks. Odd runs are a common feature of squawks; since these squawks contain no meaningful data, they are transcribed "warts and all", rather than being corrected.

Tackling Fuzzy Bits

Atom-Flip Correction (-e)

Atom-flip correction can be used to fix situations where two odd runs lie directly adjacent to one another, such as in the following atom sequence:

000111

The clear implication here is that one of the two atoms either side of the partition has an incorrect value. Flipping the value of one of the atoms will correct both of these odd runs simultaneously.

There are six strategies for deciding which atom should be flipped:

If the PLL is used and atom-flip correction is enabled, this correction will be applied before the PLL-mode cycle-insertion fix strategy is used. This strategy will be described next.

PLL Mode Correction

In PLL mode (-s pll), the most common mode of odd-run failure is a missing atom. Thus, by default, odd runs in PLL mode are corrected by inserting another atom into the odd run, such that

0011100

becomes

00111100

In order to make space for the new atom, some of the other atoms in the span will be shortened. At 44100 Hz, the nominal (integer) atom length is 18 samples. If, for example, the new atom is inserted right in the middle of a span, nine of the span's atoms prior to the odd run will be shortened by one sample, and nine of the span's atoms after the odd run will also be shortened by one sample. This will free up the 18 samples of space needed to accommodate the new, remedial atom.

You can disable PLL-mode fuzzy bit correction with the --no-default-odd-fix switch.

Frequency Mode Prevention

As mentioned earlier, atom timing in frequency mode (-s freq) is determined simply by dividing up the interval between frequency transitions into atom-sized pieces. Odd-run prevention in this mode simply forces the resulting number of atoms to be even. Unlike PLL mode, this is a prevention strategy rather than a correction strategy, and is therefore applied before the atom-flip correction activated by the -e option (above). Unless frequency mode odd-run prevention is disabled, this means that -e is basically useless with -s freq.

You can disable frequency mode fuzzy bit prevention, again, with the --no-default-odd-fix switch.

Polarity Reversals

A second problem arises if an odd run of specifically 0-valued atoms occurs. Since a 0-valued atom is only half of a cycle, an odd number of consecutive 0-valued atoms implicitly reverses the output polarity of the data. For example, a CSW that contains a 3-atom, 0-valued odd run would produce a pulse train that looks like the following:

odd-run-polarity-switch

(This problem does not occur with odd runs of 1-valued atoms. A 1-valued atom is a complete cycle—two pulses, in CSW parlance—so it cannot reverse the polarity).

This is a particular problem when feeding a CSW to MakeUEF, as MakeUEF needs to be explicitly told the phase of its input data. In order to pass a CSW produced by Quadbike into MakeUEF, it would be necessary to know and inform MakeUEF of every instance where a 0-valued odd run reverses the output polarity. Quadbike version 1 suffered from this problem, but version 2 contains a strategem for correcting outputted CSWs such that they may hopefully always be decoded with MakeUEF's default phase value of 0.

Silent and leader spans can never reverse polarity: Silent spans do not contain any cycles at all, and leader spans only contain 1-cycles, which cannot cause polarity reversals as mentioned above. This leaves data spans (including squawks).

Note that the fuzzy bit correction strategies already outlined in the previous section go a long way towards ameliorating this problem. However, these strategies are not applied to squawks for reasons already discussed, so squawk spans will need some further help to correct the polarity of any data that follows them.

If a data span contains e.g. two 0-valued odd runs, or four, or six, then overall the span will not reverse the polarity—the problem will correct itself again before the span finishes. (Yes, the span will likely contain fuzzy bits that produce transcription errors, but that is a separate issue). However, if a span contains an odd number of 0-valued odd runs (stick with me here), it will have the unwanted effect of reversing the polarity for the remainder of the file.

If CSW output is enabled, Quadbike will detect and correct this before writing the CSW. It does so by inserting an extra pulse at the start of the next span, whether that span is data or leader. Once more, space for this remedial atom is allocated by shortening some of the other atoms in the span. Silent spans are normally rendered by Quadbike as containing two CSW pulses, each half the length of the span (to avoid reversing the polarity). If a corrective atom is added, a silent span will now contain three CSW pulses, each one-third of the length of the span.

If you run Quadbike with extra warnings enabled (-v), and tell it to produce a CSW, you may see messages like this:

  W: [345574] squawk span #1 flips polarity; 1 odd runs, 0 silent cycs
    - [346483] squeeze extra cycle into leader span #2

You can disable this correction using --no-csw-phase-fix. Emulators are in general a lot less fussy than MakeUEF when it comes to CSW polarity.

Shift Search

One other feature in Quadbike which can improve the fidelity of transcriptions is its "shift search". In walk (-s walk) and PLL (-s pll) modes, Quadbike will evaluate each data span a number of times, adding or subtracting an offset to or from the instants at which atoms are sampled from the Goertzel power. It will then pick the offset that produced the most confident result across the whole span.

Suppose a span contains n atoms. Suppose also that the synchronisation method indicates that the Goertzel power should be sampled at a set of sample numbers Sn. The shift search will first evaluate the span using the set of sampling instants (Sn - 2), summing the confidence values measured at every sampling instant. Then, it will try the set of sampling instants (Sn - 1), followed by Sn, (Sn + 1), and finally (Sn + 2). The shift value that yielded the best summed confidence value is used for the final evaluation of the span.

Quadbike will also report the results of this process:

    Data span optimum shift search (+/- 2 samples):
      +1  ..  ..  ..  +2  ..s +1  +2s +1  -2  +2s +1  +2  ..s +1  +2  ..s +1  
      +2  ..s +1  +2  ..s +1  +2  -2s +1  +2  ..s +1  -2  ..s +1  +2s +1  +2  
      -2s +1  +2  -2s +1  +2  ..s +1  +2  -2s +1  +2  -2s +1  +2  ..s +1  +2  
      ..s +1  -2s +1  -2s +1  +2s +1  +2s +1  +2s +1  +2s +1  -2s +1  +2s +1  
      +2s +1  -2s +1  +2s +1  +2s +1  +2  +1  

Each of the space-separated codes in the output above represents one data span. A positive or negative integer represents the amount of shift that has been applied to the sampling instants for that data span. Codes postfixed with an s represent squawk spans. If the amount of shift is unchanged from the previous span, then the span is simply represented by a pair of dots (..).

It is possible to specify an explicit global shift value to be applied to all spans in the input, using the --force-shift option. Using --force-shift 0 will employ unadulterated atom timings derived from the synchronisation method, effectively disabling the shift search feature.

8. Output Formats, Part Two

Problems with CSW

In the past, CSW (via its generating application CSW.exe) has been the standard format for transcribing tapes from audio files. Provided a tape uses standard 8N1 serial framing (i.e. it is not copy-protected), CSW files would typically be fed to the Windows application MakeUEF to produce a UEF file:

wav-csw-uef

There are multiple problems with both CSW and UEF. Taking CSW first:

On the face of it, CSW is very simple. It just measures the pulse lengths (in samples) of positive and negative half-cycles, and then RLE-compresses those pulse lengths into a stream. Here is an example at 44100 Hz (pulse lengths measured in samples are annotated in red):

csw-phase-1

This appears easy to decode—just set a threshold between the length of a 1200 Hz half-cycle and the length of a 2400 Hz half-cycle (the "three-halves" length), and make a decision about whether the bit is a 1 or a 0 based on those pulse lengths.

However, things get sticky because the phase of the signal is not guaranteed to be 0 or 180—the switch from one frequency to the other does not necessarily occur at the zero-crossing point. For example, if the phase is 90 rather than 0, the pulse length changes at the peak of the wave, not at the zero-crossing. Now things get messy, and unfortunately the results are open to interpretation, particularly if the phase is not known by the application reading the CSW file:

csw-phase-2

Both the BeebEm for Windows and B-Em emulators employ the naïve method of comparing the length of CSW pulses against the threshold "three-halves" value, in order to distinguish between a 1-pulse and a 0-pulse. (13 samples corresponds to the 44100 Hz "three-halves" pulse length.) Both emulators therefore implicitly assume that the phase of the CSW file is either 0 or 180. However, at a phase of 90 or 270, a "three-halves" pulse will occur on every bit transition in a CSW file, rather than being a threshold value which should never actually occur. CSWs with phases of 90 or 270 will therefore be incomprehensible to these two emulators.

Worse still, 13 is only the correct three-halves value at a 44100 Hz sampling rate. To (vanilla, non-TOH) B-Em and BeebEm, CSWs produced at 22050 Hz or 48000 Hz are illegible.

The situation with CSWs loaded by emulators could be fixed for non-44100 Hz sampling rates by computing the correct three-halves threshold value for the rate provided by the CSW's header. (Indeed, I am working on such a patch to B-Em.) However, this still does not solve the phase problem. As outlined in the section on walk synchronisation above, it is possible to autodetect a likely phase value based on pulse lengths. Unfortunately, phase values determined in this way are not guaranteed to be accurate. Furthermore, I would argue that it should not be the job of an emulator to guess phase, as different emulators are likely to interpret a particular CSW file in different ways. One "correct" solution might be to extend the CSW header to include a phase value, but since the specification is closed, this is not possible.

In short, absent some sort of guarantee in the CSW specification regarding phase, it is my view that the phase ambiguity of CSW files makes the format largely worthless.

To aid compatibility with the above emulators in their current state, and to expedite parsing of Quadbike-generated CSW files with MakeUEF, Quadbike always generates CSWs with a phase of zero.

To pour fuel on the fire, the beebjit emulator uses a heuristic-based CSW loader, which often produces a different outcome to the daft three-halves method employed by the B-Em and BeebEm emulators. This is an example of an emulator feature-creeping into the void left by CSW's underspecification. The result is that a particular CSW file might load in one emulator, but might not load in a different emulator. This seems an absurd situation for an archival format.

Problems with UEF

UEF is a higher-level format than CSW, and while it is less deranged, it is not free of problems.

There are essentially four UEF chunk types that can usefully be used to store data for Beeb and Electron tapes:

UEF Chunk &100

Chunk &100 holds standard Beeb/Electron MOS-format data, with 8N1 serial framing (as might be produced by e.g. *LOAD and *SAVE). This chunk type is entirely suitable for tapes that do not reprogram the ACIA to alter the serial framing. One advantage of this block type is that the data appears explicitly in the UEF file (at least, after the UEF has been gunzipped), making it possible to search through UEF files for strings and so forth. All UEF-aware software supports the &100 chunk type. However, it is of no use for copy-protected tapes that eschew standard 8N1 serial framing.

UEF Chunk &104

Chunk &104 specifies a framing that is to be used for the entire chunk, meaning that non-standard framings like 7N1 may be accommodated by one of these chunks. This chunk type has some degree of emulator support, although it has historically been somewhat neglected. However, there is an obvious issue with this chunk type—a piece of software that generates UEFs from cycle data does not have a straightforward way of knowing what the framing is. While it may be possible to autodetect the framing, the result is not guaranteed to be accurate.

Additionally, some copy-protection mechanisms have been known to switch framings in the middle of a block. The only way to represent this phenomenon within a UEF file using chunk &104 would be to declare two chunks, one immediately after the other, with different defined framings. More gravely, it is hard for transcription software to auto-detect this sudden change in framing. The copy-protection could of course be reverse-engineered and the framing specified manually, but there is no reason at all why such arduous labour should even be necessary.

Chunks &100 and &104 are therefore unsuitable for general arbitrary data streams which may switch framings or baud rates on-the-fly.

UEF Chunk &102

Chunk &102 is an explicit representation of individual bits on the tape, including start, stop and parity bits. In theory, this chunk type should be suitable for representing any possible serial framing at 1200 baud: Bits are passed on directly to the emulator, whose ACIA will already have been correctly programmed to receive them. There is no need for any kind of "framing autodetection" by UEF generators producing this chunk type.

This seems like it should be a perfect solution, but there are a couple of problems:

UEF Chunk &114

This leaves chunk &114, which is intended for "squawks" or "security cycles", and not for actual data. The use of chunk &114 for data rather than squawks is admittedly an abuse of this chunk type. Nevertheless, &114 is the only chunk type currently defined in the UEF specification that offers the same level of generality as TIBET or CSW, because it allows for transcription of cycles, rather than bits. As a result, even tapes that switch arbitrarily between 1200 and 300 baud may be correctly transcribed without the UEF generator requiring any further context about where such switches occur. Chunk &114 offers a very generous 24-bit value in its header to express how many cycles it contains, which is enough to hold at least two hours of audio per chunk.

Or, you could use the TIBET format.

TIBET Specification

TIBET is a mid-level tape representation, lying between CSW's low-level pulse transcription and UEF's high-level bit transcription.

Quadbike currently produces TIBET files having version 0.5.

The following defines TIBET file version 0.5.

Basic Rules

TIBET is essentially just a text file.

TIBET files that contain any bytes lying outside the range &20 to &7E inclusive, with the exception of the newline character, &A, are illegal. This is not a UTF-8 file, nor is it 8-bit.

The extension for an uncompressed TIBET file is .tibet. For a gzipped TIBET file, the extension is .tibetz.

In order for a decoder to meet the TIBET specification, it must recognise and understand every aspect of this specification. It is not acceptable for a decoder to ignore any lonewords, datawords, or any other directives. There is one exception to this—hints—which are datawords beginning with the slash (/) character. Hints are explicitly optional—more on this shortly.

Any situation that is referred to in the text as being "illegal" means that a TIBET decoder must reject the entire file.

In the following discussion, values within angle brackets < and > are tokens of some sort, whose meaning will be defined in this specification. Tokens may not contain spaces or other whitespace.

Any violation of the above rules for tokens must result in the decoder rejecting the TIBET file.

Other token types will be defined shortly.

Lines and Sub-Lines

A "line" is defined as:

These regions are exclusive—the &A character itself is never included in the region.

Within each line, a "sub-line" is defined as follows:

The region from the start of a line to the first &23 (hash) character, or to the end of the line (if no &23 character exists). Again, this region is exclusive and does not include the &23 character.

TIBET decoders must process the input as a series of these sub-lines.

Blank Sub-Lines

A blank sub-line is defined as either an empty (zero-length) sub-line, or a sub-line composed entirely of spaces (&20).

Blank sub-lines may exist within the TIBET file for formatting purposes. Decoders must just ignore them.

Header Sub-Line

We define two tokens <major> and <minor>, each of which is a single <integer>.

We further define the token <version> as follows:

<major>.<minor>

(where <major> and <minor> are separated by a single full stop (&2e) character).

Then, the first non-blank sub-line in the file must take exactly the following form:

tibet<SPC><version><?spaces ...>

Between them, <major> and <minor> completely describe the file's version. Decoders must reject files containing any file with a version that they are unable to parse.

States

At the start of each sub-line, a TIBET decoder must be in one of two states: "metadata" state, or "data" state.

The decoder must begin the TIBET file in metadata state.

Metadata State

In metadata state, all sub-lines must take one of the following two forms. Any sub-line that does not match one of these two forms is illegal.

<loneword><?spaces ...>

or

<dataword><SPC><data><?spaces ...>

Only one of these forms is legal in any situation, depending on whether the sub-line begins by matching a loneword or a dataword.

The specification of the <data> token depends on the value of the preceding <dataword>, as outlined shortly in the Datawords section.

Lonewords

The currently defined lonewords are as follows:

Dataword
datadata and squawk are special—they mark the beginning of a "data section". Upon encountering either a data or squawk loneword, a TIBET decoder must switch from "metadata state" into "data state" for the start of the next sub-line.
squawk

Although Quadbike attempts to recognise squawks ("security cycles") based on simple logic, and it will label identified sections as such, there is no semantic distinction between the data and squawk lonewords. Remember that as far as a microcomputer is concerned, there is no difference between them—data bits are just data bits.

Datawords

Currently defined datawords, along with a description of their data field, are as follows:

DatawordValue
tibet<version>This is a duplicate of the Header Sub-Line, above. It is allowable in the body of the file so that TIBET files may be legally concatenated. <version> must exactly match the copy of the file version that was provided in the Header Sub-Line; if it does not, then the file must be rejected. It is not possible to "change the file version" half-way through. This dataword has one other effect—it must cancel any persistent, currently-active hints that the decoder may be observing. This is to ensure that a decode of a two-part concatenated TIBET file treats the second part of the file with the same parameters as it would have had if part two had been loaded singly. See the Notes on Hints section below for the default values which should be used for this hint reset.
silence<decimal>This represents a gap on the tape having a length of <decimal> seconds. Its value may not exceed 36,000.0. In order to interpret the significance of a gap, this silence value should be multiplied by 1200.0 and then conventionally rounded, to yield the length of the gap in atom-pairs (i.e. 1200-baud bits). If this rounded value is zero, then the gap should just be ignored.
leader<integer>This represents a section of leader tone having a length of <integer> number of 2400 Hz cycles. Its value must be non-zero, and may not exceed 100,000,000.

Any violation of the above constraints must cause the file to be rejected.

Hints

There are also some datawords defined that start with a slash character '/' (&2F). These so-called hints specify attributes that apply to the following data, squawk or leader span. Decoders are welcome to ignore hints—they are optional.

DatawordValueApplicable toPersistent?Notes
/time<decimal>
  • data
  • squawk
  • leader
noThis is an instantaneous timestamp, in seconds, since the start of the file. The value of <decimal> may not exceed 36,000.0. This hint is intended as an aid to locating features in the source audio file.
/speed<decimal>
  • data
  • squawk
  • leader
yesThis is the average tape speed of the next data, squawk or leader section (1.0 represents nominal speed). The value of <decimal> must be greater than 0.5 and less than 1.5.
/phaseOne of the following:
  • 0
  • 90
  • 180
  • 270
  • data
  • squawk
yesThis indicates the phase of the next data or squawk span.
/framingOne of the following:
  • 7E2
  • 7O2
  • 7E1
  • 7O1
  • 8N2
  • 8N1
  • 8E1
  • 8O1
  • data
  • squawk
yesThis represents a change in serial framing. This value is case-sensitive. Quadbike cannot reliably detect serial framing, so it never emits this dataword. However, it may be useful to add framing lines to a TIBET file manually.
/baud<integer>
  • data
  • squawk
yesThis represents a change in baud rate. <integer> must have a value of either 300 or 1200. Other values are illegal. Again, Quadbike is unable to detect baud rate reliably, so it never emits this dataword. Once more, it is possible to edit a TIBET file manually to add this directive in appropriate places, for copy-protected tapes utilising baud rate switching (e.g. Viper's Ultron).
Notes on Hints

A few important notes on hints:

Data State

As in metadata state, data state must be parsed one sub-line at a time. Each sub-line must match one of two possible sequences.

The first legal sequence is:

end<?spaces ...>

The end directive informs decoders that the current data section is finished. Decoders must switch back into metadata state for the start of the next sub-line.

If the end directive is not matched, then the sub-line is assumed to contain actual data from the tape. Each sub-line must consist of an unbroken string of zero or more either tone characters or (in TIBET 0.5) spaces (&20). Each tone character represents the logic value of one atom. Valid tone characters are:

Tone CharacterMeaning
-half a cycle of 1200 Hz tone; nominal duration ≈1/2400 seconds
.one complete cycle of 2400 Hz tone; nominal duration ≈1/2400 seconds
Phalf a cycle of 2400 Hz tone; nominal duration ≈1/4800 seconds

Any non-tone, non-space character is illegal and must cause the file to be rejected.

Note that two P characters may not appear next to each other, since two half-cycles of 2400 Hz tone is obviously equivalent to a full cycle of 2400 Hz tone. Therefore, in place of a pair of P tone characters, a single . character must be used instead. The sequence PP is illegal.

The P tone character only exists in the specification because of UEF's requirement to recognise lone pulses as part of a "security cycles" sequence (see the section on Squawks, earlier). Quadbike does not currently emit the P character. It is included in the specification only for full compatibility with UEF's "security cycles" chunk, type &114. The correct behaviour of a decoder upon encountering a P character is to ignore it, as a half-cycle of 2400 Hz tone does not represent a full bit.

Typically, then, a data section will consist of a sequence of sub-lines that look similar to the following. At 1200 baud, each bit of data is made up of a pair of tone characters:

----..--..--..----..--..--..--..--..--..------....----..--..----

At 1200 baud, each tone character represents half of a bit, and so this sequence decodes to the bitstream:

00101010010101010101000110010100

In version 0.4 of the TIBET specification, a constraint was applied that adjacent sections were not allowed to be of the same type. Explicitly: A silent section could not be followed directly by another silent section; a leader section could not be followed directly by another leader section; and a data or squawk section could not be followed directly by another data or squawk section. Adjacent sections had to be of different types.

This restriction proved overly onerous, so it has been relaxed in version 0.5.

Fuzzy Bits
1200 Baud

There is a pitfall here—odd runs. Based on this specification, it is of course possible for two halves of a bit to have opposing values, for example:

...---
implying a bit sequence

1?0

where the value of the bit ? is undefined. Such occurrences do not make any sense when decoded as data, but they do occur within real tapes. This possibility is permitted in the TIBET specification because such sequences can occur entirely legally during "squawks" or "security cycles" on the tape, and it was considered important to offer a method of transcribing them accurately.

This specification asserts that in a situation where a fuzzy bit must be interpreted as real data, the correct course of action is to skip the first atom of the fuzzy bit, and attempt to continue decoding. Consider this 1200-baud sequence of tone characters:

012345678
--.--..--

The correct approach to decoding this ambiguous sequence is as follows:

The reason for this approach is to guarantee good synchronisation. Other strategies might lead to a situation where the decoder remains exactly one atom out-of-step with the data. If this were the case, every bit transition would incorrectly decode to a fuzzy bit, even though the data following the original fuzzy bit might be entirely legal.

300 Baud

A similar situation may occur with 300 baud data. At this bit rate, a single bit is composed of eight tone characters:

--------........--------

This decodes to the bit sequence

010

Fuzzy bits may also occur in 300 baud data. With eight atoms per bit, more ambiguous combinations are possible. Each of these examples represents one fuzzy bit:

--......
--...---
-------.

Once again, the correct approach for decoders is to skip the first atom of a fuzzy bit, and attempt to resynchronise starting at the second atom. It might be tempting to examine the fuzzy bit's eight atoms and attempt to evaluate some sort of "majority verdict" to determine the bit's value. Indeed, this might well lead to a more accurate decoding. Again, however, it would leave decoders vulnerable to mis-synchronisation for the remainder of the data section. Do not implement TIBET decoders like this.

Data Continuity

It must be remembered that data is continuous, regardless of the span in which that data is specified. Bits emerging from a TIBET decoder—irrespective of what type of span those bits come from—form a continuum of data. This may be important in certain situations.

Firstly, line breaks within data and squawk sections have no special meaning—they exist purely as an aid to readability. The atom stream is continuous across sub-lines in the TIBET file.

Secondly, there is no semantic distinction between data and leader sections. Here is an example byte of 8N1 data at 1200 baud:

--..--..--..--..--..
S 1 0 1 0 1 0 1 0 S

The final stop bit is always a 1, which is of course composed of 2400 Hz cycles. If this byte is the final byte of a data section, and the data section is followed immediately by a leader span, an equally valid representation of this byte would be one in which the stop bit is missing:

--..--..--..--..--
S 1 0 1 0 1 0 1 0

This is still valid, because the "missing" stop bit will be filled in by the subsequent leader tone.

This extends to more than just stop bits. Again, as the final byte of a data section followed by leader, this is also a valid 8N1/1200 byte:

--..--
S 1 0

Once more, all the missing bits in this 8N1/1200 frame will be filled in with 2400 Hz cycles by the following leader tone. Since the stop bit is always a 1, this is a valid frame with a bitstream of S10111111S (and a byte value of &FD, since bits on tape are LSB-first).

The above examples deal with whole bits, but incomplete 1-bits may also be filled in by leader tone. Consider this 300-baud frame:

--------........--------........--------........--------........--------..
   S       1       0       1       0       1       0       1       0     ?

The stop bit here is missing ¾ of the necessary 2400 Hz cycles; however, once again, they will be populated by the subsequent leader section.

As this demonstrates, it is essential to implement TIBET bit decoders in such a way that data and leader atoms are processed as a continuum, rather than broken up into sub-lines, or sections. Leader sections must be considered in terms of the atoms they contain, and not as monolithic, indivisible "leader sections".

This completes the TIBET specification.

9. PHP Scripts

Some PHP scripts are also included with Quadbike, which are useful for inspection and further processing of CSW, TIBET, UEF and WAV files. These ought to work with PHP versions 7 and 8. They are licenced under GPL2.

You may need to increase PHP's memory limit to use these—particularly csw2wav.php.

lsblocks.php

This PHP script will load a CSW file, and attempt to catalogue any MOS-standard 8N1 blocks present in the source.

Note that currently it will only work correctly with CSW files having a phase of 0 or 180. CSWs emitted by Quadbike meet this requirement, but no guarantees can be made regarding CSW files from other sources:

lsblocks.php, v1.0

Usage:

  php -f lsblocks-1.0.php [options] <CSW/TIBET/TIBETZ file>

where [options] may be:
  +b <block ID>   inspect one particular block only
  +v              print verbose block details
  +e              print verbose error details
  +k              print block(s) as detokenised BASIC
  +p              generate errors if adjacent blocks have different polarities
                  (CSW only)
  +r              attempt bad-CRC repair by flipping bits
  +x <dir.>       extract blocks to target directory
  +!              with +x, allow overwriting existing files
  +m              with +x, create directory if it doesn't exist

Running lsblocks.php will produce default output similar to the following:

M: Loading /tmp/qb.csw ... 29201 bytes.

CSW Version:    2.0
App:            "Quadbike 2.0.1"
Rate:           44100
Pulses:         346016
Polarity:       starts high
Zipped:         yes
Hdr. ext. len.: 0

M: Body starts at 0x34
M: Unzipped body from 29149 to 346032 bytes.
M: Counted 346016 pulses (OK).
Decoded 6672 bytes.
----|----------|------------|--------|--------|----|----|--------|---|--|--
 id    smpnum     filename     load     exec    num  len nextfile flg hE dE
----|----------|------------|--------|--------|----|----|--------|---|--|--
#  0 [  410382]     "FOOTER"      900      900    0  100 ffffffff
#  1 [  557328]     "FOOTER"      900      900    1  100 ffffffff
#  2 [  704235]     "FOOTER"      900      900    2  100 ffffffff
#  3 [  851215]     "FOOTER"      900      900    3  100 ffffffff
#  4 [  998179]     "FOOTER"      900      900    4  100 ffffffff
#  5 [ 1145144]     "FOOTER"      900      900    5  100 ffffffff
#  6 [ 1292095]     "FOOTER"      900      900    6  100 ffffffff
#  7 [ 1439032]     "FOOTER"      900      900    7  100 ffffffff
#  8 [ 1585991]     "FOOTER"      900      900    8  100 ffffffff
#  9 [ 1732910]     "FOOTER"      900      900    9  100 ffffffff
# 10 [ 1879775]     "FOOTER"      900      900    a  100 ffffffff
# 11 [ 2026697]     "FOOTER"      900      900    b  100 ffffffff
# 12 [ 2173612]     "FOOTER"      900      900    c  100 ffffffff
# 13 [ 2320473]     "FOOTER"      900      900    d  100 ffffffff
# 14 [ 2467413]     "FOOTER"      900      900    e  100 ffffffff
# 15 [ 2614298]     "FOOTER"      900      900    f  100 ffffffff
# 16 [ 2761162]     "FOOTER"      900      900   10  100 ffffffff
# 17 [ 2908087]     "FOOTER"      900      900   11  100 ffffffff
# 18 [ 3054944]     "FOOTER"      900      900   12  100 ffffffff
# 19 [ 3201807]     "FOOTER"      900      900   13  100 ffffffff
# 20 [ 3348652]     "FOOTER"      900      900   14  100 ffffffff
# 21 [ 3495465]     "FOOTER"      900      900   15  100 ffffffff
# 22 [ 3642371]     "FOOTER"      900      900   16  100 ffffffff
# 23 [ 3789169]     "FOOTER"      900      900   17   30 ffffffff F
M: Metrics|Errors|0|Blocks|25

The final line of output shows a count of the number of blocks and errors encountered, which is useful for automated testing.

These columns should mostly explain themselves, but a few notes:

The first column—id—is a block ID. If you wish to use the +b option to investigate a particular block more closely (usually because it contains errors), the block ID to use for this is the one listed in this column.

The flg column reports the block flags—L for locked, E for empty, and F for final.

The hE and dE columns report errors: HC for a header CRC error, DC for a data CRC error, and DI for incomplete data.

The +b, +v and +e options are useful for diagnosing transcription errors in the input CSW. Like Quadbike, lsblocks.php widely reports sample numbers in square brackets, which helps to pinpoint error locations in the source audio.

The +r option will attempt to correct for bad block CRCs by experimentally flipping data bits. If a single erroneous bit in a block is causing its MOS checksum to fail, this option can sometimes locate it.

The handy +k option will attempt to LIST any BBC BASIC it finds in each block.

One other useful capability of lsblocks.php is that it can decode all the blocks within a CSW file to files on the local filesystem, using the +x option. The +m and +! options grant extra permissions to create the target directory and overwrite existing files in it.

For CSW, the +p option will generate extra errors if a polarity switch is detected from one block to the next. This is specifically flagged, as it is a situation which causes trouble for MakeUEF without manual intervention.

More information can be found at the top of the PHP file itself.

csw2wav.php

This PHP script will take a CSW file and generate a corresponding 16-bit mono WAV file from it. As mentioned above, you may need to increase PHP's memory limit.

csw2wav.php v3.2

Usage:

  php -f csw2wav.php [options] <CSW file> <output WAV file>

where [options] may be:
  +p  produce square wave pulses rather than sine waves
  +s  render silent sections as pulses, rather than as silence
  +f  flip polarity

Output file will be 16-bit mono.

tibetuef.php

This PHP script will take a TIBET file and attempt to generate a UEF file from it.

tibetuef.php 0.10

Usage:

  php -f tibetuef.php [options] <input TIBET file> <output UEF file>

where [options] may be:

  +t       use "/time" hints in TIBET file to insert &120 label chunks into UEF

  +102     use chunk &102 for data
  +104     use chunk &104 for data
  +114     use chunk &114 for data

  +112     use chunk &112 instead of &116 for silence

  +no-117  omit baud rate chunk &117 (Elkulator compatibility)

  +nz      do not compress output UEF file

2400 baud is assumed, unless /baud hints exist in the TIBET to alter this. Quadbike cannot insert such hints itself. If 300 baud is appropriate, then either: (a) /baud hints will need to be added to the TIBET file manually at appropriate points, or (b) chunk type &114 will need to be used for encoding the data (see below).

By default, 8N1 serial framing is assumed, and UEF chunk &100 is used to hold data. If /framing hints have been manually added to the TIBET file to change the framing, then chunk &104 will be used instead for any spans that use non-8N1 framings.

This behaviour may be overridden, though:

The TIBET hints /phase and /speed are emitted by Quadbike, but ignored by tibetuef.php: Although UEF chunks do exist to encapsulate these pieces of information, phase and tape speed are partially functions of playback. Speed variations or phase changes introduced when a tape is played back are therefore not representations of the source material. Hence, it is argued that these are not quantities which are meaningful to include in UEF files.

uef_inlay.php

This PHP script may be used to attach inlay scans (bitmap images) to UEF files using UEF chunk &3. As is noted in the usage text, it is strongly recommended to pre-size the images and save them as GIF before loading them using this script.

uef_inlay.php, version 0.2

Help:

  php -f uef_inlay.php [options] <UEF in> <UEF out>

  [options] may be:

    +f <image file>   append an image; GIF recommended; JPEG, PNG also work
    +p <position>     specify chunk insert position for next image
    +s <size in px>   specify maximum width or height for next image
    +g                use greyscale for next image
    +!                allow overwriting output UEF file
    +h                show help

Typical usage to attach two inlay scans to a UEF file might be:

  php -f uef_inlay.php +f scan1.gif +p 1 +f scan2.gif input.uef output.uef

The best input format is likely to be a GIF that is already smaller than the
chosen size (+s) for the image (default 160); PHP is quite bad at resizing and
converting images. Ensuring that input arrives at the right size and with a sane
palette is likely to yield best results.

You will need to use a version of PHP that includes the GD extension for image manipulation. I believe the Windows binaries from php.net contain this extension. On Debian derivatives and on Fedora, I think the required package is php-gd.

Unless overridden, this tool will attempt to insert inlay scan chunk &3 after the very first existing chunk on the tape (the rationale being that the first chunk will probably be the &0000 origin chunk). Again, unless overridden, any further images will be placed after the previously added image.

At present, the only bit depth supported is 8-bit. If a scan is in colour, it is assigned a 256-colour palette. If you want control over the palette (highly recommended), you can supply images in the GIF format, already at the target size. JPG and PNG also work, provided your GD installation supports them. Images will be resized if needed, although this may derange the palette. Greyscale images use 256 fixed shades of grey.

An image size of either 128x128 (~16K per scan before compression) or 160x160 (~26K per scan before compression) is recommended as the standard size for this chunk. Images don't have to be square, though.

10. Revision History

v2.0.4 (January 2026)

v2.0.3 (June 2025)

v2.0.2 (March 2025)

v2.0.1

fin