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.
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.
| Part | Detail |
|---|---|
| Directory | 64 slots of 32 bytes: 6-char name, length at 13–14, block list at 16–31 |
| Block list | 16 bytes wide → 65,536 bytes max per sample (2.05 s). May be fragmented |
| Sample header | 16 bytes on the first block: length, then the name again |
| Audio | 8-bit µ-law, stored without the bit inversion G.711 telephony applies |
| Sample rate | Not stored. 32 kHz is inferred from the 808 kick's pitch |
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/
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
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
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.
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.
./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.
| 2.05 s per sample | A hard ceiling from the 16-byte block list. Nothing raises it |
| 6-character names | Upper case. The builder renames collisions rather than overwrite |
| Rate is a guess | Applied uniformly, so if it is wrong everything is wrong by the same interval |
| Vendor notes mislead | They are a Prophet document retitled. These disks are readable on a PC or Mac — standard IBM MFM, 9×512 |
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.
Python 3 and numpy. linnfmt.py
is shared by the others; keep them in one folder.
| File | What it does |
|---|---|
| linn9000.py | list, inspect and extract a disk to WAV |
| linn9000build.py | folder of WAVs → a disk image |
| hfe2img.py | HFE (Gotek) → raw sectors |
| img2hfe.py | raw image → HFE for the Gotek |
| linn9000edit.py | browser kit builder, 18 pads |
| linnfmt.py | the format itself, shared module |
| blank-linn9000-720k.hfe | formatted empty disk, the write donor |
| README.md | how every field was pinned down |