A cross-platform HF weather-fax (WEFAX, emission J3C) decoder.
Isobar decodes the weather-fax images broadcast over shortwave by agencies
like JMH (Japan), NMG (USA), and BMV/ARC (various) — using only your
computer's sound card and an HF receiver. It is an independent,
from-scratch reimplementation that is interoperable with the
long-standing hobbyist software KG-FAX v1.1.3 (K.G, 2009): it reads
and writes KG-FAX .syn files, and uses the same settings schema (stored
as isobar.ini, importing an existing kgfax.ini on first run).
Recordings load at any sample rate — 12 kHz from an SDR, 48 kHz from a
sound card, whatever your receiver software writes — and in either way a WAV
can spell 16-bit PCM, including the WAVE_FORMAT_EXTENSIBLE header macOS
afconvert produces when converting from .m4a.
Not affiliated with, endorsed by, or derived from KG-FAX. Isobar's source is original, written from a functional specification produced by reverse engineering. See
NOTICEfor the full provenance statement anddocs/README.mdfor the legal footing.
Prebuilt, self-contained binaries for macOS, Windows, and Linux are attached to each Release — no FLTK/RtAudio install needed on the target machine:
- macOS —
Isobar-v<version>-macOS-AppleSilicon.dmg(M1 and later) orIsobar-v<version>-macOS-Intel.dmg. Each holds anIsobar.appbundle with the FLTK/RtAudio dylibs embedded inContents/Libraries/; ad-hoc signed, so right-click → Open the first time to clear Gatekeeper. Needs macOS 15 or newer, since the bundled libraries are built against it. - Windows —
Isobar-v<version>-windows.zip(a portable folder; FLTK + RtAudio are statically linked into the.exe, so there are no DLLs to install). x64; on Windows-on-ARM machines it runs under the system's own x64 emulation. - Linux —
Isobar-v<version>-linux-x86_64.AppImageorIsobar-v<version>-linux-aarch64.AppImage(a single file —chmod +xand run; the FLTK/RtAudio.sos are bundled inside). Theaarch64one is for ARM boards such as the Raspberry Pi. Both are built on Ubuntu 22.04, so they need glibc 2.35 or newer (Debian 12 / Raspberry Pi OS bookworm and up).
To build from source instead, see Build.
- Decodes WEFAX (WMO-No. 386 Part III §5): 1500/2300 Hz FM sub-carrier about 1900 Hz, 60 or 120 rpm, IOC 576. IOC-selection (300 Hz) / stop (450 Hz) tone detection arms and disarms capture automatically.
- Stations without per-line sync (WMO phasing only, e.g. VMW Wiluna): locks from the phasing preamble, then holds the phase through the chart at the line rate measured off that preamble, following stream dropouts from the picture's own content.
- Live reception from any sound input, or offline decode from a WAV recording. Spectrum scope + waterfall shown live during reception.
- Image tools: zoom/pan, vertical rotate toggle, XY flip, 4 color palettes, BMP/PNG export (color when a palette is applied, grayscale otherwise), print. BMP and grayscale PNG files can also be loaded back as images.
- Auto-save on stop tone or at max scan width:
.syn,.bmp, or grayscale.png(the small archival format — every chunk's CRC is checked when it is read back, so a file damaged in storage is reported rather than quietly decoded as a corrupt chart). - KG-FAX interop: round-trips
.synfiles (including the radix-255 line-count encoding and palette mode/invert bits in the header), and reads the older legacy "Syn Fax" variant (fixed 2000×2280 body). - Survives audio dropouts, which matters if you feed it a networked SDR (KiwiSDR and friends) over a long internet path — see below.
An internet-fed SDR loses audio when the stream stalls, and every lost chunk shifts the sync position for everything after it. Isobar follows that shift on the line it happens on, so the size of a dropout barely matters: measured by injecting gaps into a real off-air recording, a ten-second outage costs no damaged lines at all — you lose the ten seconds of chart it swallowed and nothing more.
What does matter is how often they happen, and the limit is the
LockAfter setting (Details… dialog, default 5): acquiring sync needs
that many consecutive undisturbed line periods, i.e. 2.5 seconds of clean
audio. At the default, one dropout every 3 seconds is fine (5% of lines
damaged); one every 2 seconds and it never locks at all.
If your feed drops out more often than that, lower LockAfter to 1–3.
It costs nothing on clean audio — 5, 3 and 2 all decode a good recording
identically — and it turns the one-dropout-every-2-seconds case from
unusable into 5% of lines damaged. The longer chain exists to stop false
locks on noisy HF; a networked SDR feed is the opposite problem, clean
but gappy, so it can afford a short one.
Isobar is plain C++17 built with CMake. The only third-party dependencies are FLTK (GUI), RtAudio (live audio), and zlib (PNG compression in the core — present by default on macOS, and pulled in by the Linux/Windows packages below).
cmake -B build -S .
cmake --build build # builds isobar-decode, isobar-gui, tests
ctest --test-dir build # runs the 22 headless testsInstall the dependencies first:
| OS | Command |
|---|---|
| macOS | brew install fltk rtaudio cmake |
| Debian/Ubuntu | sudo apt install libfltk1.3-dev librtaudio-dev zlib1g-dev cmake |
| Windows | vcpkg: fltk and rtaudio, with -DCMAKE_TOOLCHAIN_FILE=<vcpkg>/scripts/buildsystems/vcpkg.cmake (MSVC) |
# Decode a WAV recording to an image:
./build/isobar-decode recording.wav out.pgm
# Or open the GUI (then pick your radio's audio input):
./build/isobar-gui # Linux / Windows
open ./build/Isobar.app # macOS (the GUI builds as a .app bundle)On macOS the GUI builds as a proper Isobar.app bundle (with icon and the
required microphone-usage permission prompt); the first launch needs a
right-click → Open to clear Gatekeeper (the app is ad-hoc signed, not
notarized).
core/ C++17 DSP core: FM demod, sync, .syn, FFT, tone detect,
live scan, resample, BMP/PNG, palette. Only external dependency:
zlib (PNG compression). See core/README.md.
cli/ isobar-decode (the decoder CLI) + the headless test suite.
gui/ FLTK GUI: main window, scope, dialogs, RtAudio capture.
docs/ Functional specification (derived from reverse engineering) + plans.
Read docs/README.md first, then docs/01-program-analysis.md.
docs/06-release-process.md covers releasing (read before tagging).
cmake/ Templates filled in at configure time: the version header
(isobar_version.h.in; one source of truth) and the Windows
resource script (isobar.rc.in; exe icon + version metadata).
assets/ App icons (.icns / .ico / PNGs) — generated from jmh-portrait.svg
via tools/make-icons.sh.
macos/ Info.plist template for the .app bundle.
tools/ make-icons.sh (icon regeneration) + extract_dfm.py (dev/research).
v1.7.0 released — working software. All core receive features are
implemented and verified on real JMH recordings; see ROADMAP.md
for the milestone map (M0–M5 done; M6 = validation & release, largely
done — two validation items still open).
Stations that send no per-line sync — WMO phasing only, VMW Wiluna being
the verified case — decode as well, on a phase held from the preamble
rather than tracked (DEVIATIONS.md #19).
Reception is not limited to the original's 2280-line buffer: long charts
(e.g. XSG's ~2755-line broadcasts) are captured in full up to 4560 lines,
while .syn saves stay KG-FAX-compatible (2280 lines max).
Continuous builds run on macOS, Linux, and Windows — Intel and ARM alike —
via GitHub Actions (.github/workflows/); tags v*.*.* produce five
self-contained native release packages (two macOS .dmgs, a Windows .zip,
and x86_64 + aarch64 .AppImages) attached to a
GitHub Release.
Copyright © 2026 Sara Sakuragawa. Licensed under the GPLv3+ — see
LICENSE. Links to FLTK (LGPLv2 + static-linking exception) and
RtAudio (MIT); see NOTICE for third-party attribution.
