A real time morse code decoder written in C++ that listens to microphone input and prints decoded text to the terminal.
The program runs a four-stage pipeline from raw audio to decoded letters.
Stage 1 — Audio capture PortAudio runs a background thread that reads from the microphone every ~11ms, collecting 512 raw float samples per callback. These are compressed into a single RMS (root mean square) value representing the loudness of that window. The value is stored atomically so the main thread can read it safely across threads.
Stage 2 — Signal processor Every 100ms the main loop reads the latest RMS and passes it to the signal processor, which answers one question: is a tone playing or is it silence? Rather than using a fixed threshold, it tracks an exponential moving average (EMA) of the background noise floor — updating only during silence so loud tones don't corrupt the baseline. If the current RMS exceeds the noise floor by a configurable sensitivity factor, a tone is detected.
Stage 3 — Timing classifier Converts the boolean tone/silence stream into morse events. Counts consecutive on/off frames and classifies them at transitions — a short tone is a dot, a long tone is a dash, a medium silence is a letter gap, a long silence is a word gap. Short silences between dots and dashes in the same letter are ignored.
Stage 4 — Decoder
Accumulates dots and dashes into a pattern string. On a letter gap or word gap, looks up the pattern in a hash map of the full morse alphabet and prints the corresponding character. Unrecognized patterns print as ?.
Microphone
→ PortAudio thread → audioCallback → RMS → atomic store
→ Main thread (100ms loop)
→ SignalProcessor → bool toneOn
→ TimingClassifier → Event (DOT/DASH/LETTER_GAP/WORD_GAP)
→ Decoder → character printed to terminal
- C++17
- CMake 3.15+
- PortAudio (
libportaudio2,portaudio19-dev)
sudo apt install libportaudio2 portaudio19-dev
mkdir build && cd build
cmake ..
cmake --build ../morse_decoderPlay morse audio near your microphone or tap on a surface. Decoded text prints to the terminal. Wait ~1.5 seconds after the last tone for the final letter to flush.
Thresholds in main.cpp can be adjusted based on your microphone and tapping speed:
SignalProcessor signalP(0.1f, 3.0f); // alpha, sensitivity
TimingClassifier tC(2, 6); // dotThreshold, dashThreshold (in frames)At 100ms per frame and 5 WPM morse speed, dots are ~2-3 frames and letter gaps are ~7 frames.
src/
audio.cpp / audio.h — PortAudio capture, RMS computation
signal.cpp / signal.h — Adaptive noise floor, tone detection
edge.cpp / edge.h — Rising/falling edge detection
timing.cpp / timing.h — Dot/dash/gap classification
decoder.cpp / decoder.h — Morse table lookup, character output
main.cpp — Pipeline orchestration
CMakeLists.txt