Sonar Share

Send text between two Macs using nothing but sound. Encrypted with a shared passphrase, and the receiver answers to confirm it arrived.

macOS 13+ · Universal (Apple Silicon & Intel) · MIT licensed

Install from the terminal
curl -fsSL https://raw.githubusercontent.com/eladb/sonar-share/main/install.sh | bash

Pin a specific release by passing its tag — | bash -s -- v4.0.0. Releases can't talk to each other, so pin both Macs or neither.

The Send tab, with a message typed in and a volume slider The Receive tab, showing a decoded message

How you use it

  1. Type the same shared passphrase on both Macs — or leave it blank to send in the clear.
  2. On the receiving Mac, open Receive and press Start Listening.
  3. On the sending Mac, type a message under Send and press Transmit.
  4. The text appears on the receiver, which answers to say what arrived. The sender shows Delivered — and keeps retrying on its own until it does, or until you press Stop.

Both machines need microphone access: the sender has to hear the reply. Keep them within a couple of metres, speakers pointed at the other Mac's microphone, at normal listening volume. Forty characters cross the room in about five seconds.

Encrypted with a passphrase

Set a passphrase on both Macs and the payload is sealed with AES-256-GCM, keyed by PBKDF2-HMAC-SHA256 at 200,000 iterations over a fresh random salt per message. Anyone in earshot can still record the tones and demodulate them — the modem has no secrets — but what they recover is ciphertext, and a frame altered by a single bit is rejected rather than displayed. Every reply is authenticated with the same passphrase, so a bystander can't fake a delivery confirmation.

A passphrase that doesn't match fails immediately and says so. The handshake settles it before a single byte of the message goes out: the receiver's answer has to carry a token only the right passphrase can produce. Both Macs report that the other is using a different passphrase within a few seconds, rather than one of them grinding through retries into what looks like an empty room.

Built for a room with people in it

The message travels in 32-byte pieces. The sender transmits what's outstanding, the receiver answers with a map of what actually landed, and only the missing pieces go again. A conversation happening over the top of a transfer slows it down instead of killing it, and a progress bar counts the pieces confirmed — a count that only ever moves forward, so nothing already banked is thrown away.

It keeps trying until it works, or until you stop it. There is no attempt limit. Press Transmit before anyone has pressed Receive and it keeps probing; start the receiver whenever you get there and it connects on the next probe. Starting the two sides by hand in either order is a supported case — a receiver that comes up in the middle of a transfer says so, and the sender starts over rather than sending pieces that could never be assembled.

The short probe still comes first, because committing the payload blind into an empty room is minutes of noise before anything reports failure. It settles in a couple of seconds both whether anyone is listening and whether they hold the same passphrase. A message is shown exactly once no matter how many replies go missing, and a receiver that can't decrypt what it heard refuses to confirm it — so the sender reports failure rather than false success.

You can hear which way it's going

The two directions use different tones: a low warble carries probes and pieces, a higher chirp carries the answers. Beyond making an exchange easy to follow by ear, it means each Mac listens only on the band it isn't transmitting on, so hearing its own output across the room stops being something the protocol has to work around.

How it works

Sonar Share is a 16-tone MFSK modem in roughly 1,600 lines of Swift. Each 50 ms symbol carries one nibble, so bytes travel at ten per second.

Forward band1400 Hz sync, plus 1600–4000 Hz in 160 Hz steps — probes and pieces
Reverse band4560 Hz sync, plus 4720–7120 Hz in 160 Hz steps — answers
Symbol rate20 baud — 4 bits per symbol, 10 bytes/second
Payloadup to 255 bytes of UTF-8 (227 with a passphrase), in 32-byte pieces
Frame0xA5 marker · kind+flags · length · payload · CRC-16/CCITT
Repairselective repeat over a piece bitmap, unlimited retries

In simulation — with room echo, microphone noise, and clock drift between the two machines — decoding stays reliable down to about −6 dB SNR.

Worth knowing