zebra report

how it works  ·  a volume-modem chatroom over webrtc  ·  open the chat  ·  unturf

Zebra report is a two-person chatroom where your words never travel as network packets. Each message is encoded into the loudness of an audio tone, carried inside an encrypted WebRTC voice stream, and decoded back into text on the far side. A network observer sees an ordinary encrypted call. The chat lives in the amplitude.

This page walks through the whole machine: how two browsers find each other with no copy-paste, how data rides on volume, why a speech codec nearly killed it, and the error correction that finally made it solid.

▶ open the chat

1 · the idea: data in amplitude, not packets

The original zebra-report channel modulates a browser tab's PulseAudio volume: step the volume through values at a fixed clock rate and the other side reads those steps back as data, exactly like a dial-up modem. The payload lives in a side channel that the network stack never sees.

The browser version generates a pure 440 Hz tone locally (no microphone) and swings its output gain per clock tick. That modulated tone becomes the outbound WebRTC audio track. Wireshark sees only encrypted SRTP; the words are in the amplitude envelope inside it.

2 · bootstrapping with no copy-paste

WebRTC needs the two peers to swap connection descriptions (SDP) before media flows. The usual way is to copy a blob of text from one device to the other and back. We replaced that with a tiny signaling relay:

A: rendezvous code ──▶ wss://cors-proxy.uncloseai.com/zebra-signal ◀── code :B (relay pairs the two, forwards encrypted SDP) both type the same code · no link · no QR · no paste

Both peers type the same rendezvous code. The page derives an opaque room id and an encryption key from that code, connects to the relay, and the relay forwards the offer/answer between the two. The SDP is encrypted with the code before it leaves the page, so the relay only ever sees ciphertext and an opaque room id. It carries setup only; chat content never touches it.

3 · the carrier and the modem

At connection time each browser starts a 440 Hz tone and modulates its gain. Rather than a plain on/off (one bit per tick), the modem is multi-level: each clock tick holds the volume at one of N levels, so a symbol carries log₂(N) bits — 4 levels = 2 bits, 16 levels = 4 bits per tick.

Each byte is framed by a minimum-level start symbol and a maximum-level stop symbol. The receiver locks onto that high-to-low edge, then re-derives its own low and high amplitude reference from the start and stop of every byte — so the level mapping self-calibrates continuously and tolerates drift in the channel's gain.

4 · the codec trap

The first cross-machine attempts decoded nothing. The cause was the audio codec. WebRTC defaults to Opus, a perceptual speech codec that re-quantizes audio in 20 ms frames and effectively normalizes loudness. That is precisely the information our modem rides on, so Opus erased it.

Two fixes restored a clean channel:

5 · making it reliable

Even on a clean channel, a wireless link is lossy. Three layers make the chat dependable:

6 · error correction (the eureka)

The last problem was the worst kind: messages mostly worked, then a frame would arrive one bit wrong, fail its CRC, get dropped, and the sender would retry forever. The channel was good enough to almost work.

The fix is forward error correction. Each data byte is carried as a Hamming(12,8) codeword — 8 data bits plus 4 parity bits — inside one byte-frame. Any single flipped bit is located and corrected on the far side before the CRC check, so a marginal channel self-heals instead of dropping the frame.

Hamming corrects single-bit errors, but a misread volume level can flip two bits at once. So the levels are Gray-coded: adjacent amplitude levels differ by exactly one bit. The most common error — reading a level as its neighbour — becomes a single-bit flip, which is exactly what Hamming repairs. Together they turned a channel that gave up into one that delivers.

7 · encryption & threat model

Chat content is end-to-end encrypted at the application layer, on top of WebRTC's own SRTP encryption. Two modes:

Protects against a network observer reading chat content. Does not protect against another process on either endpoint recording the local audio, or an endpoint compromise — that local audio trust boundary is the surface this project documents.

8 · testing without two phones

The modem and protocol are validated by a Node test suite that runs the real shipped code — no browser and no second device required (make test-web). It covers the CRC and frame codec, the Hamming and Gray logic, a full multi-level modem roundtrip through a simulated noisy channel, and recovery of off-by-one symbol errors. Over three thousand assertions guard every change to the wire format.


Type a rendezvous code on two devices and watch text cross on nothing but the loudness of a tone.

▶ open the chat

zebra report · volume modem chatroom · unturf