Ryder's Linn 9000 Tools

Read, extract and build sample disks for the Linn 9000 — including .hfe images for a Gotek.

The disk format was reverse-engineered from real media: two floppies read on a plain USB drive, plus thirteen library disks off a vendor Gotek stick. 676 samples across 15 disks were used to check every field.

The format

720K disk, 737,280 bytes, no filesystem. 4096-byte blocks, block N at N × 4096 — so the directory is block 0 and blocks 1–179 hold audio.

PartDetail
Directory64 slots of 32 bytes: 6-char name, length at 13–14, block list at 16–31
Block list16 bytes wide → 65,536 bytes max per sample (2.05 s). May be fragmented
Sample header16 bytes on the first block: length, then the name again
Audio8-bit µ-law, stored without the bit inversion G.711 telephony applies
Sample rateNot stored. 32 kHz is inferred from the 808 kick's pitch
µ-law was the hard part. Read as linear PCM every decay tail turns into a full-scale buzz. Measured on a sustained bass tone, harmonic distortion is 0.2% decoded as µ-law against 24.2% linear — and 159% decoded the telephony way.

The tools

linn9000.py

List, inspect and extract a disk to WAV. Trims the trailing silence padding, which is often most of the file.

./linn9000.py list    disk.img
./linn9000.py extract disk.img -o out/

linn9000build.py

A folder of WAVs to a disk image. Resamples to 32 kHz mono and µ-law encodes. --gain kit keeps the balance between sounds instead of normalising each one flat. Limits are reported, not silently applied.

./linn9000build.py MYKIT/ -o mykit.img

hfe2img.py · img2hfe.py

The Gotek round trip. The writer patches a real formatted blank rather than synthesising MFM, so every gap, sync mark and timing detail stays as a working disk has them. Verified byte-exact both ways, with all 1440 CRCs valid.

./img2hfe.py mykit.img -o mykit.hfe

linn9000edit.py

Browser kit builder on port 8003. The machine's 18 voices in the panel's own order, three rows by six — note bass and hi-hat sit on the bottom row. Load an existing .img or .hfe and it matches samples to voices by name; click a file or a filled pad to hear it; Build writes both .img and .hfe.

Tom 1
Tom 2
Ride 1
Ride 2
Hi Conga
Lo Conga
Sidestick
Snare
Tom 3
Crash 1
Cowbell
Claps
Hi Hat
Bass
Tom 4
Crash 2
Tambourine
Cabasa

About pads: the disk stores only 64 named samples — there is no pad-assignment field in the format, and the Linn assigns sounds to pads from its own front panel. The grid names each sample after the voice you drop it on, so the machine's list reads like a kit. It cannot pre-assign the pads.

Getting a kit onto the machine

./linn9000build.py MYKIT/ -o mykit.img
./img2hfe.py mykit.img -o mykit.hfe
cp mykit.hfe /Volumes/GOTEK/

Select it on the drive, wait ~2 s for it to load, then load sounds on the Linn as you would from a floppy.

Traps

2.05 s per sampleA hard ceiling from the 16-byte block list. Nothing raises it
6-character namesUpper case. The builder renames collisions rather than overwrite
Rate is a guessApplied uniformly, so if it is wrong everything is wrong by the same interval
Vendor notes misleadThey are a Prophet document retitled. These disks are readable on a PC or Mac — standard IBM MFM, 9×512

Still unknown

Directory bytes 10–11 and sample-header bytes 8–15. Neither correlates with anything measurable — size, pitch, block count or position. They are labelled UNKNOWN in the code rather than given an invented meaning.

Download

Python 3 and numpy. linnfmt.py is shared by the others; keep them in one folder.

FileWhat it does
linn9000.pylist, inspect and extract a disk to WAV
linn9000build.pyfolder of WAVs → a disk image
hfe2img.pyHFE (Gotek) → raw sectors
img2hfe.pyraw image → HFE for the Gotek
linn9000edit.pybrowser kit builder, 18 pads
linnfmt.pythe format itself, shared module
blank-linn9000-720k.hfeformatted empty disk, the write donor
README.mdhow every field was pinned down