TZX.js
A Javascript library (and command-line tool via node) to convert TZX (and TAP) files to audio output
…so now you can use your computer as a cassette player!
Command Line
Run the following command to convert a TZX to a WAV:
tzx -o my_wav_file.wav /some/dir/a_tzx.tzx
A TAP file is also supported and can simply be passed like so:
tzx -o my_wav_file.wav /some/dir/a_tzx.tap
If you have an alternate file extension, see the -t option. For a complete list of options see the -help option. A very useful option is -v for verbose output. And if you have, for example, a stereo cable try out the -l option which will generate a stereo audio file with only a single channel of sound (works like a charm for me).
API Usage
Basic Usage
This is a javascript API that depends on wav.js (well, anything that implements a similar interface - specifically it only cares about the frequency property and addSample). This is a simple example to get you started that will get a byte array containing wav data from an input byte array containing tzx data:
var tzxFile = []; // Fill this up with bytes
var wave = wav.create(1, 44100, wav.SampleSize.EIGHT);
var details = tzx.convertTzxToAudio(tzx.MachineSettings.ZXSpectrum48,
tzxFile, wave);
var rawWaveData = wave.toByteArray(); // Here we have the byte array
So I’ll let you head on over to wav.js to figure out the arguments to the create function on wav but briefly it means generate a wav file that is mono, 44.1kHz with 8 bits per sample.
We call the convertTzxToAudio function with the following arguments:
- An object that describes a machine (see MachineSettings for some pre-canned machines) that is going to receieve the audio input.
- An object that has a getLength and getByte function (that takes a single argument, the index) which should wrap up the input data or a good ol’ array - in the example above you can see the array approach (see the web example for why you might need to pass in something different - also some tzx files require stop-the-tape functionality - see information below).
- An object that implements the wav.js interface (just getFrequency, getChannelCount and addSample functions)
Finally I’m sure you can guess what toByteArray does.
There are two useful top-level functions available in the tzx module:
- convertTzxToAudio - Converts a TZX file to audio output
- convertTapToAudio - Converts a TAP file to audio output
Using in a Web Application
It is perfectly possible to use this even in an old web browser (as I avoided dealing with reading in a file and writing out a file), here is an example:
<!DOCTYPE html>
<html>
<head>
<script type="text/javascript" src="FileSaver.js"></script>
<script type="text/javascript" src="Blob.js"></script>
<script type="text/javascript" src="wav.js"></script>
<script type="text/javascript" src="tzx.js"></script>
<script type="text/javascript">
function downloadTzx(fileToConvert, downloadFileName, machineSettings) {
var req = new XMLHttpRequest();
req.overrideMimeType('text\/plain; charset=x-user-defined');
req.onreadystatechange = function () {
var done = this.done || 4;
if (this.readyState === done && req.status === 200) {
var wave = wav.create(1, 44100, wav.SampleSize.EIGHT);
var details = tzx.convertTzxToAudio(machineSettings, {
getLength: function() { return req.responseText.length; },
getByte: function(index) {
// throw away high-order byte (f7)
return req.responseText.charCodeAt(index) & 0xff;
}
}, wave);
var rawWaveData = wave.toByteArray();
// See NOTE below about this
saveAs(new Blob([new Uint8Array(rawWaveData)],
{type : 'application/octet-binary'}),
downloadFileName);
alert("Successfully converted '" + fileToConvert + "' to '" + downloadFileName
+ "' - TZX file is version: " + details.version.major + "."
+ details.version.minor + " and has " + details.blocks.length + " blocks.");
}
};
req.open('GET', fileToConvert, true);
req.send(null);
}
</script>
<body>
<a href="javascript:downloadTzx('data/example.tzx', 'example.wav', tzx.MachineSettings.ZXSpectrum48)">Download a Game</a>
</body>
</html>
NOTE You can see that I used the excellant Blob.js and FileSaver.js libraries. They provide the saveAs functionality and require a reasonably modern browser (notice I’m using a Uint8Array there).
There are also alternate ways, of course, to get the data but I chose an approach that should work on some really old browsers (with a bit of ActiveX magic you could even get this going on IE6)
Using from Node JS
Using it from nodejs is pretty straightforward, you have probably figured this out already but this should do the trick:
var fs = require('fs');
var constants = require('constants');
var wav = require("/Users/kevin/Projects/wav.js/master/src/wav.js");
var tzx = require("/Users/kevin/Projects/tzx.js/master/src/tzx.js");
var file = fs.readFileSync("input.tzx");
var tzxFile = {
getLength: function() { return file.length; },
getByte: function(index) {
return file[index];
}
};
var wave = wav.create(1, 44100, wav.SampleSize.EIGHT);
var details = tzx.convertTzxToAudio(tzx.MachineSettings.ZXSpectrum48, tzxFile, wave);
fs.writeFileSync("output.wav", new Buffer(wave.toByteArray()));
Note About STOP-THE-TAPE
So some TZX files contain “stop the tape” directives, meaning, well, what it says. The tzx.js API will deal with this by calling the function stopTheTapeTrigger on the output argument (in all the examples so far a wav.js WaveFile object returned from the create function). You will need to do something with this, the tzx command-line tool just puts a number on the end of the wav file and saves it, then creates a new one so you will get a bunch of .wav files generated for such a tzx file, thus giving the user the chance to stop the tape. This is a little snippet of code that does that:
var output = "my_wave.wav";
var wave = wav.create(1, 44100, wav.SampleSize.EIGHT);
var splitFileCount = 0;
var outputWrapper = {
addSample: wave.addSample,
getFrequency: wave.getFrequency,
getSampleSize: wave.getSampleSize,
stopTheTapeTrigger: function () {
splitFileCount += 1;
// Tack a number on the end of the file name
var newFile;
if (output.lastIndexOf('.wav') !== -1) {
newFile = output.substr(0, output.lastIndexOf('.')) + '_' + splitFileCount + '.wav';
} else {
newFile = output + '_' + splitFileCount;
}
// Write the old file and create a new one for the next part
fs.writeFileSync(newFile, new Buffer(wave.toByteArray()));
wave = wav.create(1, 44100, wav.SampleSize.EIGHT);
}
};
building
So you want to help me out, brilliant…
-
Go ahead and fork the repository and clone it to your local machine.
-
Now you have the source get the dependencies using npm
npm install
-
You have the dependencies, so now you can build it (and run the tests) using grunt.
grunt
-
That’s it - code away and send me pull requests please!
The unit tests (written using nodeunit) are in th the test folder, the source is in src and the output goes in dist
Further Reading
You can find some auto-generated documentation here if you want to see any more details.