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
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.
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.
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.
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.
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.
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 band | 1400 Hz sync, plus 1600–4000 Hz in 160 Hz steps — probes and pieces |
|---|---|
| Reverse band | 4560 Hz sync, plus 4720–7120 Hz in 160 Hz steps — answers |
| Symbol rate | 20 baud — 4 bits per symbol, 10 bytes/second |
| Payload | up to 255 bytes of UTF-8 (227 with a passphrase), in 32-byte pieces |
| Frame | 0xA5 marker · kind+flags · length · payload · CRC-16/CCITT |
| Repair | selective 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.