Skip to main content
Back to Articles
2026 / 01
| 3 min read

Storage Peripherals and Audio Realism

Floppy, hard drive, and cassette peripherals with Kansas City Standard encoding, plus a peripheral audio bus that makes the machine feel physical.

emulator peripherals cassette audio kcs

I spent most of today listening to the emulator with my eyes closed. The hard drive clicked, the floppy head swept, and the cassette motor hummed—but they didn’t sound like they belonged in the same room. Each device had its own volume, its own frequency response, its own idea of what “mechanical” meant. The audio path was a tangle of one-off connections, and it showed.

So I flattened it into a bus system:

Generators → Buses → Master Chain → Destination

There is now a dedicated peripheral bus with an 8 kHz lowpass filter. That filter is the constraint; stepper motors and plastic cassette decks do not sound like full-range synthesizers, so the bus enforces a shared timbre. The PeripheralAudioHelper class connects devices to the bus and standardizes the verbs: playTone(), playSweep(), playClicks(), startMotor().

The floppy, hard drive, and cassette all speak the same audio protocol now. Volume control becomes global rather than device-specific, and a mute toggle actually mutes.

Hard drives with geometry

The hard drive peripheral is not a skin; it is a drive with geometry and capacity that feeds back into behaviour. The project ships four variants, each with its own era and feel:

  • Seagate ST-225 (20MB MFM, 1984)
  • Seagate ST-4096 (80MB MFM, 1987)
  • Conner CP-3104 (120MB IDE, 1991)
  • WD Caviar 2540 (540MB EIDE, 1995)

Geometry matters because it gives the emulator something to own: cylinders, heads, sectors per track. Format operations are slower on larger drives. Seek sounds are tuned per drive. If you switch drives in the peripheral shop, you hear the difference.

Cassette as a real transport

The cassette deck is a Kansas City Standard recorder with a real transport model. It moves tape, tracks position, and stores programs with a baud rate and a checksum. The encoder is explicit about its constraints:

  • Mark (1) = 2400 Hz
  • Space (0) = 1200 Hz
  • Baud rates = 300 / 1200 / 2400

The program flow is simple: start the motor, stream FSK, advance position, stop at the end of data. It can go wrong the way tape does—the machine remembers where you left off.

Boot theatre

The BIOS boot sequence now plays the mechanical sounds when it “detects” owned hardware. That is not ROM BIOS code; it is the frontend boot path, but it creates the illusion of a living box. When a floppy is present, it performs a head calibration sweep. When a hard drive is present, it spins up and clunks a few seeks. The sound is routed through the same peripheral bus, so the boot feels consistent with everything that follows.

Wrong numbers got a voice

I also expanded the wrong-number system into six categories with 20 variations each. The line can now land on a busy signal, a calm operator, an annoyed person, or a random IVR system. Those announcements are audio files, routed through the line bus with its own phone-line filter, so they sit in the same world as DTMF and handshake tones.

Two bugs from the edge cases

A Z-Machine branch offset was off by one. The game would “mostly” work, which is a special kind of wrong—it took longer to notice than to fix.

Audio context initialization in Lit components must happen after a user gesture. The browser enforces it, so the code has to wait for it. I moved the storage peripherals to async audio initialization, which means the sounds don’t try to play before the user has clicked something.

Tomorrow I’m finishing the CC-40 backend and launching the IVR system properly. The machine feels tangible enough now that the polish work feels worth doing.

Previous: 2026-01-28