Published on

Cold Storage for Warm Samplers, Part 1: Reverse-Engineering an E-mu Floppy with Greaseweazle

Authors
  • avatar
    Name
    Johanness Nilsson
    Mastodon

Floppy seconds

An E-mu EMAX II rack came in for service. The customer had already made a sensible call: leave the original floppy drive alone. These racks have SCSI, so mass storage and fast loading are handled — a modern solid-state floppy emulator (an OpenFlop-style board) buys you almost nothing. The floppy drive only earns its keep for the occasional job: booting the machine into E-mu's technician hardware test & diagnostics disk.

The floppy-writing machine is 1200 miles away

I've kept a clunky old Windows XP desktop around for exactly one reason: it could read and write floppy disks. That machine was, at the moment I needed it, about 1200 miles away. Useless to me.

A few years back, I'd built the portable answer: an open-source Greaseweazle in a tidy external enclosure. It's USB-powered, a proper Sony floppy mechanism inside, the height of the art right as the art was quietly being retired. (Sad aside: Sony shut down the last floppy-disk manufacturing line a few years ago. Rumor was it had been kept alive that long largely for the Japanese government, or so the story goes. We are writing to media nobody makes anymore.)

The Greaseweazle will happily write any floppy you ask it to. There's just one condition: you have to tell it the disk's architecture, the encoding, the data rate, the sector layout. And the EMAX II floppy format is undocumented. As of this writing Greaseweazle ships profiles for a hundred machines and not this one; there is no emu.emax2 in the list.

You cannot write what you cannot describe.

So before I could write a single diagnostics disk, I had to reverse-engineer the format. The only way to do that honestly is to stop guessing and get a genuine EMAX disk to tell me what it is.

A bench that boots the same tomorrow

First, the bench itself. It is no secret that I like reprodubibility. So Greaseweazle lives behind a Nix flake, greaseweazle-nix, and direnv drops me into it the moment I cd in:

# flake.nix (excerpt)
devShells.default = pkgs.mkShell {
  packages = [ greaseweazle pkgs.python3 pkgs.usbutils ];
  GW_DEVICE = "/dev/ttyACM0";
  # ...banner + device-present check...
};

One wart worth mentioning, because it'll bite anyone working offline: gw info unconditionally phones api.github.com on every run to check for newer firmware. On a bench that isn't on the internet, that call hangs for its five-second timeout and then vomits a FATAL ERROR: HTTPSConnectionPool ... Read timed out all over your device readout. There's no flag to turn it off, so the flake patches it out at build time:

greaseweazle = pkgs.greaseweazle.overridePythonAttrs (old: {
  postPatch = (old.postPatch or "") + ''
    substituteInPlace $(find . -path '*greaseweazle/tools/info.py') \
      --replace-fail "latest_version = latest_firmware()" \
                     "latest_version = (0, 0) # network check disabled"
  '';
});

When I actually want it. gw info, and the V4 answers instantly, no network.

Read one to write one

To learn the format I had to read a genuine EMAX disk at the flux level, and the only one I had was a customer's own boot disk. Not a pristine reference, either. It was a 1.44 MB HD floppy with the HD-detect hole uncovered, and when I pulled it apart it read back sort of valid, with some corruption scattered through it. Enough to extrapolate from; not enough to trust blindly.

That's fine, because flux doesn't care. Flux is the raw magnetic truth or the timing of every transition on the surface, format-agnostic, no assumptions about encoding or sector size baked in. Capture it once and I can decode it a hundred different ways at my leisure, and I never have to spin a potentially fragile original again:

gw read --drive A --tracks 'c=0-79:h=0-1' --revs 5 emax2_ref.scp

Eighty cylinders, both heads, five revolutions each into a Supercard Pro (.scp) flux image. Five revs because redundancy costs nothing and a corrupted 35-year-old surface has weak spots — a sector that reads dirty on revolution one might land clean on revolution four. I checked I got everything by parsing the SCP's own track table:

magic OK  revs=5  start_trk=0  end_trk=159
populated track entries: 160
  min track index=0  max track index=159
  gaps in range: none

160 of 160 tracks. Now the customer's disk can go back in its sleeve, and every question about the format gets answered against the flux capture file.

Ten sectors and a lie

Greaseweazle has a wonderful format-agnostic decoder, ibm.scan, that just looks at a flux dump and reports whatever IBM-style sectors it can find. Point it at the capture:

gw convert --format ibm.scan emax2_ref.scp /dev/null
T0.0: IBM MFM (10/10 sectors)
T0.1: IBM MFM (10/10 sectors)
...
Found 1586 sectors of 1600 (99%)

Ten sectors per track, MFM, 99% readable. Great. So I went with the nearest-looking built-in HD sampler format — akai.1600, which is 80×2, 10 sectors, 1024 bytes, MFM — and got:

Found 0 sectors of 1600 (0%)

Zero. Not "a few CRC errors." Zero. A known-good 10-sector HD MFM layout couldn't find a single sector on a disk that ibm.scan swears has 1586 of them. Every high-density (rate=500) format I tried did the same thing and got me nothing but frustration.

That contradiction was actually the answer that was staring me in the face. ibm.scan sweeps parameters to find sectors; the explicit formats assume them. If every HD assumption fails while the scan succeeds, the disk isn't HD. It's double density recorded on what looks like HD media. But I don't want to guess that — I want the disk to say it.

Seek truth at the source level

Greaseweazle is Python, and the flake already put its library on my path. So instead of brute-forcing format definitions, I asked the flux directly by, sweep the plausible RPM / data-rate / encoding combinations, decode the raw track, and print the actual bytes out of each sector's address mark (the c cylinder, h head, r record/sector-id, n size-code fields):

My question read like:

from greaseweazle.image.scp import SCP
from greaseweazle.codec.ibm.ibm import IBMTrack
from greaseweazle.track import PLLTrack

img = SCP.from_file("emax2_ref.scp", None, dict())
flux = img.get_track(0, 0).flux(); flux.cue_at_index()

for rpm in (300, 360):
    for rate in (250, 300, 500):                 # kbit/s
        raw = PLLTrack(time_per_rev=60/rpm, clock=5e-4/rate, data=flux, pll=None)
        areas = IBMTrack.mfm_decode_raw(raw)
        hdrs = [(a.idam.r, a.idam.n) for a in areas if a.__class__.__name__ == 'Sector']
        if hdrs:
            print(rpm, rate, sorted(set(hdrs)))

The disk answered:

SpindleData rateSectors foundSector IDs (r)Size code (n)
300 rpm250all 101–102 → 512 B
300 rpm3003garbage
360 rpm250partialpartial

At the disk's republic/static/images/reverse-engineering-emu-floppies-greaseweazle/cover.pngal spin speed (I'd measured 301 rpm with gw rpm), the sectors only make sense at 250 kbit/s = double density, with 512-byte sectors numbered 1 through 10! That's why akai.1600 found nothing: it was listening at 500 kbit/s for 1024-byte sectors. Right sector count, wrong everything else.

So the previously-undocumented E-mu EMAX II floppy format is:

  • IBM MFM, 250 kbit/s (double density)
  • 80 cylinders × 2 heads
  • 10 sectors/track, 512 bytes each, sector IDs 1–10
  • Total: 80 × 2 × 10 × 512 = 819,200 bytes — a tidy 800 KB

diskdef for posterity

Greaseweazle lets you declare custom formats in a diskdefs file. The entire EMAX II format, the thing that didn't exist an hour ago, is twelve lines:

disk emax2
    cyls = 80
    heads = 2
    tracks * ibm.mfm
        secs = 10
        bps = 512
        id = 1
        rate = 250
    end
end

The proof it's right is a clean round trip. Decode the reference capture with the new format:

gw convert --diskdefs config/diskdefs-emax.cfg --format emax2 \
    emax2_ref.scp emax2_ref.img
Found 1586 sectors of 1600 (99%)

Same 99% ibm.scan found blind and the missing 14 are genuinely-weak sectors on an old disk, not a format error. The output is exactly 819,200 bytes. The format is now fully described, which means Greaseweazle can finally do the thing I actually came here for.

Write it down

Now with emax2 defined, I could take the technicians Diagnostic Test Disk image and lay it onto a blank floppy. Historically that has been EMXP's job hand it the image and it writes an E-mu disk. Since EMXP was on the XP box, 1200 miles away at the Portland shop, the Greaseweazle pulls double duty: the tool that decoded the format also writes it:

gw write --diskdefs config/diskdefs-emax.cfg --format emax2 emax2_diag.img

Except the double-density discovery has one last physical sting, and the customer's own disk had been trying to tell me about it. The format is 250 kbit/s DD, but that boot floppy came in on HD media with its detect hole open, and so does every blank you can buy in 2026. A 3.5" drive picks its write current from the little media-sensor hole in the corner of the shell, not from anything Greaseweazle tells it. Put an HD disk in, hole uncovered, and the drive uses low HD write current; writing a DD-rate signal with it under-records the surface. My first write verified as garbage — the flux was there, but only about 2 sectors of 10 survived a read-back.

The fix is gloriously low-tech: cover the HD hole with a piece of opaque tape. Now the drive thinks it's holding a DD disk, selects the higher DD write current, and lays the bits down properly. (Genuine DD media works too, if you can still find it.) Tape on, write again = clean. Then the only test that actually counts: I carried the floppy over to the EMAX II, dropped it in, and it booted the Diagnostic Test disk off the machine's own vintage panasonic DD drive, first try.

Next: the other half

Writing a bit-perfect physical copy is one half of the job. The other half is working with what's inside the image reading the banks, converting patches, editing the filesystem an E-mu sampler expects. That belongs to a crusty, brilliant, Windows-only tool called EMXP, which is its own special kind of pain to run in 2026. A legit PITA, the kind that cost a customer of mine five hours of his life lost to macOS, Apple silicon and Wine hell.

Nix fixes that too. That's Part 2.

Everything above lives in greaseweazle-nix, including the emax2 diskdef.