Every formula the system uses, in one place, each with a worked example. The channel and algorithm documents explain why each formula looks the way it does; this page is the quick lookup.
Back to the documentation index.
envelope = "APCM"(4) + type(1) + nameLen(1) + name + mimeLen(1) + mime + data
overhead = 7 + len(name) + len(mime)
| Content | Name | MIME | Data | Envelope |
|---|---|---|---|---|
| Text “sos” | empty (0) | empty (0) | 3 | 7 + 3 = 10 B |
| Text “Hello from sound!” | 0 | 0 | 17 | 24 B |
| Text, 100 ASCII characters | 0 | 0 | 100 | 107 B |
| JPEG 5 100 B | e.g. photo.jpg (9) |
image/jpeg (10) |
5 100 | 5 126 B |
Text and links carry no name or MIME type: the type byte already identifies them, and the receiver shows them inline. ChatPayloadCodec.overheadBytes() computes the overhead without building an envelope.
Details: Data Formats §2.
blockLen = first b in [160, 240, 330] with ceil(envelope / b) ≤ 48, otherwise 330
K = ceil(envelope / blockLen)
| Envelope | Test | blockLen | K |
|---|---|---|---|
| 5 130 B | ⌈5130/160⌉ = 33 ≤ 48 | 160 | 33 |
| 10 300 B | ⌈10300/160⌉ = 65 > 48; ⌈10300/240⌉ = 43 ≤ 48 | 240 | 43 |
| 80 055 B | 501, 334, 243: all > 48 | 330 | 243 |
The thresholds are 48 × 160 = 7 680 B and 48 × 240 = 11 520 B.
frameBytes = 22 (header) + blockLen + 4 (CRC-32) = blockLen + 26
version = smallest v whose EC-L byte capacity ≥ frameBytes
modules = 4v + 17
| blockLen | frameBytes | Version | Capacity | Modules | Fill |
|---|---|---|---|---|---|
| 160 | 186 | 8 | 192 | 49 | 97% |
| 240 | 266 | 10 | 271 | 57 | 98% |
| 330 | 356 | 12 | 367 | 65 | 97% |
| 600 | 626 | 17 | 644 | 85 | 97% |
Framing efficiency = blockLen / frameBytes: 160/186 = 86%, 330/356 = 93%.
frame period = 1000 / fps 12 fps → 83.3 ms ; 8 fps → 125 ms
nominal rate = blockLen × fps 330 × 12 = 3 960 B/s
cap frames = 600 s × fps 12 fps → 7 200 frames ; 8 fps → 4 800
yield = 0.70 (≤160) | 0.65 (≤240) | 0.55 (≤330) | 0.35 (larger)
ETA = ceil( (K + 2) / (fps × yield) )
| Payload | K | fps × yield | ETA |
|---|---|---|---|
| 10 B | 1 | 8.4 | ⌈3/8.4⌉ = 1 s |
| 5 130 B | 33 | 8.4 | ⌈35/8.4⌉ = 5 s |
| 10 300 B | 43 | 7.8 | ⌈45/7.8⌉ = 6 s |
| 80 055 B | 243 | 6.6 | ⌈245/6.6⌉ = 38 s |
| 80 055 B, Safe | 501 | 5.6 | ⌈503/5.6⌉ = 90 s |
Full table: Light Channel §10.
goodput ≈ blockLen × fps × decodeRate × K / (K + overhead)
For v12 at 12 fps with a 55% decode rate and K = 243: 330 × 12 × 0.55 × 243/245 ≈ 2.16 KB/s.
receiver crop = 0.98 × 720 = 706 px
pixels per module = (QR share of crop) / (modules + 8 quiet-zone modules)
screen module = floor(shortSide × dpr / (modules + 8))
| QR | Modules + 8 | px/module at 353 px | Screen px/module at 1 080 px |
|---|---|---|---|
| v8 | 57 | 6.2 | floor(1080/57) = 18 |
| v12 | 73 | 4.8 | floor(1080/73) = 14 |
| v17 | 93 | 3.8 | floor(1080/93) = 11 |
| v20 | 105 | 3.4 | floor(1080/105) = 10 |
Frame hold versus camera: 83 ms / 33 ms ≈ 2.5 captures per code at 12 fps. Blur analysis (erf tables): Signal Processing.
K ≤ 8 : cycle through all 2^K − 1 non-empty subsets (shuffled)
9 ≤ K ≤ 256 : each source block included with p = ½ (mean degree K/2)
K > 256 : degree d = min(K/2, ceil(2 ln K) + 8)
Example, K = 422: ⌈2 ln 422⌉ + 8 = ⌈12.09⌉ + 8 = 21 blocks per repair symbol.
Dense (p = ½), after K + m symbols: P(failure) ≤ 2^−m
m = 2 → ≤ 25% m = 5 → ≤ 3.1% m = 10 → ≤ 0.1%
Small K: all 2^K − 1 subsets are cycled, so full rank arrives within one pass
Sparse: P(a block is never covered by n symbols of degree d) ≈ e^(−n·d/K)
With a carousel of K frames and loss rate q, you need every specific frame, so the expected number of passes grows with ln K. With a fountain you need any K + ε frames:
fountain frames ≈ (K + 2) / (1 − q)
For K = 13 at q = 30%: fountain ≈ 15 / 0.7 ≈ 21–22 frames. A carousel needs ≈3.9 passes ≈ 49 frames. Derivation: Fountain Code.
memory ≈ K × (4·⌈K/32⌉ + blockLen) bytes (32-bit coefficient words + payload per row)
For K = 422, blockLen = 330: 422 × (56 + 330) ≈ 163 KB.
bin spacing = fs / N = 44 100 / 1 024 = 43.066 Hz
tone(g, v) = (40 + 16g + v) × 43.066 Hz g = group, v = nibble 0–15
sync tones = bin 28 (1 205.9 Hz) and bin 36 (1 550.4 Hz)
symbol = F × 1 024 samples F = 3…6
bits/symbol = 4 × G
raw rate = 4G × fs / (F × 1 024)
amplitude = 0.98 / tones peak never exceeds 0.98
| Profile | G | F | Symbol | Raw rate |
|---|---|---|---|---|
| Rugged | 6 | 6 | 139.3 ms | 24 bits / 0.1393 s = 172 b/s |
| Safe | 6 | 4 | 92.9 ms | 258 b/s |
| Standard | 8 | 4 | 92.9 ms | 345 b/s |
| Fast | 8 | 3 | 69.7 ms | 459 b/s |
| Silent Robust | 1 | 3 | 69.7 ms | 57 b/s |
| Silent | 1 | 2 | 46.4 ms | 4 bits / 0.0464 s = 86 b/s |
Example: group 2, nibble 0xA → bin 40 + 32 + 10 = 82 → 82 × 43.066 = 3 531 Hz.
Silent band. Tones use every second bin from 431, and the sync tones are separate bins below the data tones:
tone(v) = (431 + 2v) × 43.066 Hz v = 0–15 → 18 562 … 19 854 Hz
sync tones = bin 424 (18 260 Hz), then bin 427 (18 389 Hz), one per marker half
tone spacing = 2 × 43.066 = 86.1 Hz Doppler at 0.3 m/s ≈ 17 Hz
amplitude = 0.8 one tone at a time
guard = first 1 024 samples of each symbol ignored by the demodulator
codeword = 11 (header 9 + CRC-16 2) + L + P
dataSymbols = ceil(codeword / (G/2)) G/2 bytes per symbol
frameSamples = 2 048 (marker) + dataSymbols × F × 1 024
frameTime = frameSamples / 44 100
net rate = L / frameTime
| Profile | L | P | Codeword | Symbols | Samples | Frame time | Net |
|---|---|---|---|---|---|---|---|
| Rugged | 32 | 20 | 63 | 21 | 131 072 | 2.972 s | 10.8 B/s |
| Safe | 48 | 24 | 83 | 28 | 116 736 | 2.647 s | 18.1 B/s |
| Standard | 64 | 24 | 99 | 25 | 104 448 | 2.368 s | 27.0 B/s |
| Fast | 64 | 24 | 99 | 25 | 78 848 | 1.788 s | 35.8 B/s |
| Silent Robust | 24 | 16 | 51 | 102 | 315 392 | 7.152 s | 3.4 B/s |
| Silent | 24 | 16 | 51 | 102 | 210 944 | 4.783 s | 5.0 B/s |
With G = 1 a symbol carries half a byte, so dataSymbols = ceil(2 × codeword / G) = 102.
K = ceil(envelope / L)
expectedSymbols = ceil(1.25 K) + 2 progress target
symbolBudget = max(6K, K + 24) sender stops after this many frames
burst = max(2, min(4, K)) frames per WAV, after 120 ms of silence
time ≈ expectedSymbols × frameTime (+ 0.12 s per burst)
| Envelope | Profile | K | Frames | Time |
|---|---|---|---|---|
| 10 B | Standard | 1 | ⌈1.25⌉ + 2 = 4 | 4 × 2.368 = 9.5 s |
| 10 B | Silent | 1 | 4 | 4 × 4.783 = 19.1 s |
| 107 B | Silent | ⌈107/24⌉ = 5 | ⌈6.25⌉ + 2 = 9 | 9 × 4.783 = 43 s |
| 500 B | Rugged | 16 | 20 + 2 = 22 | 22 × 2.972 = 65 s |
| 500 B | Fast | 8 | 10 + 2 = 12 | 12 × 1.788 = 21 s |
| 2.1 KB | Standard | 33 | ⌈41.25⌉ + 2 = 44 | 44 × 2.368 = 1.7 min |
Session ID: (millisecondsSinceEpoch ~/ 97) & 0xFF.
RS(n, k) over GF(256), primitive polynomial 0x11D, n = 11 + L + P, k = 11 + L
corrects e errors and f erasures when 2e + f ≤ P
GMD retries erase the 4, 8, 12, … least-confident bytes while keeping ≥ 4 parity spare
| Profile | P | Errors only | Erasures only | Example mix |
|---|---|---|---|---|
| Rugged | 20 | 10 | 20 | 6 errors + 8 erasures (12 + 8 = 20) |
| Standard | 24 | 12 | 24 | 9 errors + 4 erasures (18 + 4 = 22 ≤ 24) |
Worked GMD case (P = 24). 13 errors fail errors-only decoding (26 > 24). The first GMD retry erases the 4 weakest bytes. If those include 4 of the errors, 9 errors + 4 erasures remain: 2·9 + 4 = 22 ≤ 24, so the frame is repaired.
Field facts: α = 0x02, α⁸ = 0x1D (since x⁸ ≡ x⁴ + x³ + x² + 1). Full walkthrough: Reed-Solomon.
marker score = min over both half-windows of 2 · min(P28, P36) / E
ideal (pure marker) = N/2 = 512
noise ≈ 1
lock threshold = 8
leading edge = first sample where score ≥ 50% of the peak
refine span = min(symbolSamples / 8, 512)
search step = 64 samples
Silent markers play bin 424 in the first half and bin 427 in the second. Each half then scores 1 · P / E for its single tone, which is also 512 when aligned, so the same threshold works. Silent syncs high-pass the audio at 16 kHz before scoring, otherwise speech energy in E buries the marker.
Processing gain of a Goertzel bin integrated over one symbol of L = F × 1 024 samples (a coherent tone gains L²/2 in power while white noise gains L):
gain = 10 log10(L / 2)
| Profile | L | Gain |
|---|---|---|
| Fast | 3 072 | 31.9 dB |
| Standard / Safe | 4 096 | 33.1 dB |
| Rugged | 6 144 | 34.9 dB |
| Silent (guard excluded) | 1 024 | 27.1 dB |
| Silent Robust (guard excluded) | 2 048 | 30.1 dB |
Silent’s shorter window is offset by level: one tone at 0.8 against Standard’s 0.1225 per tone is 20·log10(0.8 / 0.1225) ≈ 16.3 dB more per tone.
Derivation: Signal Processing.
bit 0 period = 80 + 50 + 60 = 190 ms
bit 1 period = 180 + 50 + 60 = 290 ms
average = 240 ms → 4.17 bit/s → 0.52 B/s
decision = pulse ≥ 130 ms → 1
pulse detect = |‖a‖ − baseline| ≥ 1.4 m/s²; baseline ← 0.92·b + 0.08·‖a‖
reject = pulses < 25 ms
| Message | On-air bytes | Bits | Time |
|---|---|---|---|
| “hi” | 9 + 28 = 37 | 304 | ≈73 s |
| “hello” | 12 + 28 = 40 | 328 | ≈79 s |
| Full 48-byte payload | 76 | 616 | ≈148 s |
packet = 24-byte header + payload + CRC-32 (4) (little-endian fields; 28 bytes overhead)
fragments = ceil(data / packetSize)
window = [lastAcked + 1, lastAcked + windowSize]
| Mode | packetSize | Window | ACK timeout | Retries | Worst wait per packet |
|---|---|---|---|---|---|
| Simulation | 256 | 8 | 500 ms | 5 | 6 × 0.5 = 3 s |
| Hardware, optical | 1 400 | 4 | 20 s | 8 | 9 × 20 = 180 s |
| Hardware, acoustic | 512 | 4 | 20 s | 8 | 180 s |
| Hardware, vibration | 48 | 4 | 20 s | 8 | 180 s |
Example: the raw payload “sos” as a single packet is 24 + 3 + 4 = 31 bytes (see Data Formats).
T = min(throughput / 25 000, 1) R = reliability L = max(1 − latency/500, 0)
score = 0.35 T + 0.25 R + 0.15 L + 0.15 C + 0.10 S
degraded : score < 0.65
switch : degraded AND alternative − current ≥ 0.15
evaluate : every 20 loop iterations, alternatives tested with 20 packets
Worked example (optical collapsing to 2 kbps, 30% loss, 500 ms): 0.35·0.08 + 0.25·0.70 + 0.15·0 + 0.15·0.30 + 0.10·0.20 = 0.268, so the engine switches to acoustic at 0.70. See Adaptive Engine §10.
| Algorithm | Parameters | Check value (“123456789”) | Used by |
|---|---|---|---|
| CRC-32 (IEEE) | poly 0xEDB88320 reflected, init/xorout 0xFFFFFFFF | CBF43926 | Light frames, packets, de-duplication |
| CRC-16/CCITT-FALSE | poly 0x1021, init 0xFFFF, no reflection | 29B1 | Sound frames |
Light session ID = (CRC32(envelope) XOR (blockLen × 0x9E3779B1)) & 0xFFFFFFFF ; 0 → 1
Sound session ID = (ms ~/ 97) & 0xFF
The same content at the same density always gets the same Light session ID, which is what makes Resume streaming work.
compressImageForTransfer)fit inside 960 × 960 → JPEG q78
while size > 120 KiB and q > 40: q −= 8 (78, 70, 62, 54, 46, 38)
still too big and > 640 px → resize to 640, q65
tool/make_sample_media.py)budget = target_kb × 1024 bytes
total kbps = budget × 8 / 1000 / duration × 0.96 (4% container margin)
video kbps = total − audio − 3 (MP4; −1 for WebM)
retry : video kbps × budget / size × 0.97 until size ≤ budget
Example: 200 KB, 30 s, 12 kbps audio → 204 800 × 8 / 1000 / 30 × 0.96 = 52.4 kbps total → 37.4 kbps video.
| File | Light (Auto) | Sound (Standard) | Vibration |
|---|---|---|---|
| 5 KB photo (5 126 B envelope) | ⌈35/8.4⌉ ≈ 5 s | K = 81 → 104 frames × 2.368 s ≈ 4.1 min | 107 packets, ≈8.1 KB on air ≈ 4.3 h (plus ACK waits) |
| 80 KB video | ≈38 s | Over the 8 KiB fountain limit | Not practical |
| Light | Sound | Vibration | |
|---|---|---|---|
| Carrier | QR codes at 8–12 fps | 1.2–7.2 kHz tones, or 18.3–19.9 kHz (Silent) | 80/180 ms buzzes |
| Net rate | ≈1.3–2.5 KB/s | 10.8–35.8 B/s audible; 3.4–5.0 B/s Silent | ≈0.5 B/s |
| Ratio to vibration | ≈2 500–4 800× | ≈20–70× | 1× |
| Integrity | CRC-32 + QR’s RS | RS + CRC-16 + GMD | Packet CRC-32 |
| Loss recovery | LT fountain | LT fountain | ACK/NACK retransmit |
| Typical range | 15–40 cm | 0.3–2 m | Phones touching |