Tools for a 1987 Sampler

Tools for a 1987 Sampler

Recently I wrote about the gap between my Casio FZ series sampler and the tools for getting samples onto it. fizzle is what I have been making to fill it. It turns modern sample files into disk images a Casio FZ can read.

A tool for this kind of job already existed, just not for the machine I cared about. Chicken Systems Translator converts between sampler formats, and I use it to move SFZ instruments onto my Akai S3000XL. The Casio FZ series is not on its list. Awave Studio reads and writes individual FZ files but not the disk format, and only on Windows. So the samples that played on the Akai would not play on the Casio, even though the Casio is the more interesting instrument to my ears.

There was another reason to do it myself. I wanted to experiment with pair programming with an AI, in this case Claude Code, and a binary format suits that well: either the disk loads on the sampler or it does not, so I always knew whether a change had worked.

Go was a good fit. Cross-compiling a static binary for macOS, Linux and Windows is one command each, and most of the libraries I used are MIT licensed, so I can hand the binaries to someone else without much thought.

I ran Casio's 1987 FZ-1 Data Structures document through OCR, fed it to the model, and we wrote code against it. Then I put a disk image onto the Gotek floppy emulator in my FZ-10M and saw what the sampler made of it. Some of it matched the document. Some of it did not, and the front panel of an FZ-10M is small, with one corner of mine hard to read, so working out why was slow. Then we fixed something and went round again.

For most jobs the file that matters is the fzf. An fzf (full dump) holds up to 64 samples with a bank mapping over the top that decides which key plays which sample, on which channel, through which output. The Casio loads one of them in a single operation, which is easier than loading voices one at a time.

A big instrument does not always fit on one floppy, though, and splitting it across two was the part that gave me trouble. The FZ only has 2 MB of sample memory, so a large patch has to span two disks. My first attempt put a bank sector and voice headers on both disks, on the assumption that each disk ought to describe itself. The sampler loaded both disks, but the audio from the second disk came in with a glitch at the start that sounded like clipping. At first I assumed one of my own samples was clipping.

Claude Code and I went round this for a while. Every idea it had was reasonable, and none of them were right, because the answer was not in the spec or in my code. I only worked it out by saving a two-disk set from the FZ-10M itself and reading the bytes. The second disk had no bank sector and no headers at all. It was just the leftover audio from the first disk, and disk one carried the parameters for the whole instrument, including the voices whose audio ended up on disk two. The headers I had been writing to the front of my second disk were what the sampler was playing as that glitch. Once I understood that, the fix was small. The model could work from the format I had described to it, but it could not know the one thing I had never told it, because that thing only existed in what the hardware wrote to the disk. I had to go and find it.

fizzle also goes the other way now: an SFZ instrument becomes an fzf, and an fzf can be taken back apart into SFZ with its WAVs, so a patch made on the hardware can come back into a DAW. Round-tripping turned out to be a good way to catch mistakes. If a file does not come back the same as it went in, something is wrong, and you notice straight away.

Once it was handling real instruments, I gathered a pile of old FZ disks from around the internet and ran the lot through fizzle at once. That found problems no single disk had shown me. The 1987 document is mostly right, but it is not the same as the disks people actually have. One field I had treated as a voice count was really a count of key splits. Most of the time those are the same number, so it never mattered; on the disks where they differ, my code fell over. Others had small corruptions that probably crept in years ago, when someone copied a floppy onto a hard drive.

Working this way was good, though a few things grated. Sometimes it ignored instructions I had written down plainly: I would set a rule and watch it get broken a few messages later. More than once it tried to get a failing test to pass by quietly weakening the test instead of fixing the code, which is the last thing you want from something meant to be keeping you honest. So I learned to keep the strict checksum tests in place and to read the diffs myself rather than take its word for what it had done.

It still sped the work up. A fiddly byte layout never bored it, and it was good company for the slow parts late at night. It did not save me from having to understand the problem, though. The two-disk bug is the clearest example: the answer was not written down anywhere, so it had to come from me.

The samples that play on my Akai play on the Casio now too, and the FZ-10M has gone from a fussy curiosity to something I can actually load with my own sounds. There are more tools on the way, and the short video I promised last time is coming soon.