Documentation
¶
Overview ¶
Package murmur is the library form of the murmur command: one-way acoustic transfer between two nearby machines.
The point of the library is the same as the point of the command line tool. Two laptops, a few metres apart, no cable, no network, no pairing ritual: one side calls Encode, the other feeds a PCM stream to Decode, and the payload crosses the room as sound. It is for keys, tokens and small configuration - a kilobyte takes about a minute.
There is one way to receive and one way to send, and neither speaks back:
// Receiver: decode whatever a recorder hands over. Opening the microphone is
// the recorder's job - `pw-record --raw --format=s16 --rate=48000 --channels=1
// - | murmur decode` - and everything here is a plain io.Reader of raw 16-bit
// mono PCM at 48 kHz.
stream, err := murmur.Decode(os.Stdin)
if err != nil {
return err
}
defer stream.Close()
for msg := range stream.Chan() {
os.Stdout.Write(msg.Data)
}
// Sender: produce the sound and play it wherever you like.
err := murmur.Encode(os.Stdout, murmur.Payload{Data: key})
Decode is strictly passive, and Encode only produces audio. That is what lets the sound be piped, saved, or replayed later, and it is why there is no acknowledgement anywhere in this package.
The pieces underneath are in pkg: the plain codec in pkg/link, the modulation in pkg/modulation, the wire format in pkg/mac, and the PCM seam - format, conversion, capture high-pass, and the in-process devices - in pkg/audio.
Index ¶
Constants ¶
const EncodeLeadInMS = 300
EncodeLeadInMS is the silence written before the first burst of every encoded stream.
It is not cosmetic. A player started fresh - `murmur encode | pw-play`, the documented way to use this - hands its first block to PipeWire while the sound card is still waking, and the card plays that block rotated. The rotation is not a click: on the machine this was written on the first block came back shifted by 828 ms, which is far longer than the 8 ms synchronisation chirp, so a burst that starts in that block is lost whole - preamble and all - and no receiver can find a preamble that was never played where it was listening. That is why a real transfer loses its first fragment while every later one arrives: by then the stream is warm. A lead-in several player blocks long means the wake lands on silence and the first burst plays intact.
Variables ¶
This section is empty.
Functions ¶
func Encode ¶
Encode writes one payload to w as sound: the bytes of a complete plain message, ready to be played at a machine running 'pw-record ... | murmur decode'.
w, _ := os.Create("key.raw")
err := murmur.Encode(w, murmur.Payload{Data: key})
The output is raw 16-bit little-endian PCM at audio.DefaultSampleRate, which is what `pw-cat` and `aplay` take on standard input once they are told the format. WithWAV writes a header instead, so that the result is a file any player can open without being told anything. Nothing is transmitted here and nothing is listened for: this function produces audio, and the caller decides where to send it. That is what makes the plain path useful - the same bytes can be played once, played twice, or saved and played tomorrow.
Types ¶
type Message ¶
type Message struct {
// Data is the payload, already checked against its digest.
Data []byte
// Time is when the message finished arriving.
Time time.Time
}
Message is one payload that arrived.
type Option ¶
type Option func(*options)
Option adjusts how a receiver or a sender opens the link.
func WithNotes ¶
WithNotes receives problems that did not stop the receiver: a message that could not be reassembled, a burst that belonged to an encrypted session, a fragment that never arrived. They are for the user - the library still does no output of its own - and returning from the function must be quick, because it runs on the receiving goroutine.
func WithScheme ¶
func WithScheme(s modulation.Scheme) Option
WithScheme overrides the modulation, which is how the command line applies its transmit level: the peak amplitude is a property of the scheme, so a loudness policy that did not reach the scheme would be a flag that changes nothing.
type Payload ¶
type Payload struct {
// Data is the bytes.
Data []byte
}
Payload is one thing to send.
type Stream ¶
type Stream struct {
// contains filtered or unexported fields
}
Stream is what Decode returns: a receiver reading samples from its source.
func Decode ¶
Decode reads raw 16-bit mono PCM at 48 kHz from r and returns the messages it can decode. It is the inverse of Encode - sound in, bytes out - and it never opens an audio device, so it is the receiver for anything that can write PCM to a pipe or a file. Pair it with a recorder for a live microphone:
// pw-record --raw --format=s16 --rate=48000 --channels=1 - | murmur decode
stream, err := murmur.Decode(os.Stdin)
stream, err := murmur.Decode(os.Stdin)
if err != nil {
return err
}
defer stream.Close()
for msg := range stream.Chan() {
fmt.Println(len(msg.Data))
}
r is read until io.EOF, at which point Chan closes. The samples must already be 16-bit mono at 48 kHz; Decode does not resample, and a different rate fails silently rather than loudly.
func (*Stream) Chan ¶
Chan delivers arriving messages, oldest first. It is closed when the receiver stops, so `for msg := range s.Chan()` ends on Close or on a fatal error.
Directories
¶
| Path | Synopsis |
|---|---|
|
cmd
|
|
|
murmur
command
Command murmur transfers data between nearby machines using sound.
|
Command murmur transfers data between nearby machines using sound. |
|
internal
|
|
|
cli
Package cli implements the murmur command line.
|
Package cli implements the murmur command line. |
|
pkg
|
|
|
audio
Package audio is the acoustic I/O seam for murmur.
|
Package audio is the acoustic I/O seam for murmur. |
|
dsp
Package dsp holds the sample-level building blocks: transforms, filters, resampling, windows and detection.
|
Package dsp holds the sample-level building blocks: transforms, filters, resampling, windows and detection. |
|
fec
Package fec provides the error-detection and error-correction primitives used inside a burst.
|
Package fec provides the error-detection and error-correction primitives used inside a burst. |
|
link
Package link is murmur's one-way path: no handshake, no acknowledgement, no encryption.
|
Package link is murmur's one-way path: no handshake, no acknowledgement, no encryption. |
|
mac
Package mac owns the bytes of a burst: the frame header, its encoding, and the integrity check that decides whether a burst is used or discarded.
|
Package mac owns the bytes of a burst: the frame header, its encoding, and the integrity check that decides whether a burst is used or discarded. |
|
modulation
Package modulation turns frame bytes into a waveform, and a waveform back into frame bytes.
|
Package modulation turns frame bytes into a waveform, and a waveform back into frame bytes. |
|
sim
Package sim models the acoustic channel for tests and for `murmur bench`.
|
Package sim models the acoustic channel for tests and for `murmur bench`. |
|
transfer
Package transfer gives the acoustic link file semantics.
|
Package transfer gives the acoustic link file semantics. |