Morse Code keyer and visualizer.
Supported keyer modes: Iambic A, Iambic B, Ultimatic and Straight.
keyer is a state machine program. It receives events from stdin and produces events to stdout.
Input events (4):
dit down, dit up, dah down, dah up
which basically indicates if the dit/dah paddle is pressed.
Output events (10):
dit down/dit up/dah down/dah up: just echoes of stdindit,dah: a dit/dah is recognizedkey on,key off: output key (tone) starts/stopschar,word: indicates the boundary of a character/word
The visualizer GUI subproject receives stdin events:
dit down,dit up,dah down,dah upkey on,key off
and draws a representation of the dit/dah/tone lanes in realtime to demonstrate the timing.
cargo build && cargo build --manifest-path=visualizer/Cargo.toml Some interesting things can be done with programs above and these Python scripts altogether.
audio-out.py: play tones according tokey onandkey offfrom stdingamepad-pipe.py: map two buttons of my gamepad to these four paddle events ((dit|dah) (down|up))interface/read-serial.py: read bytes sent from an ESP8266 chipboard, from/dev/ttyUSBx, and map them to these four paddle events
I literally grabbed a $1.5 ESP8266 and made it the keyer interface.
So first, grab an ESP8266, and program it:
arduino-cli compile --fqbn esp8266:esp8266:nodemcuv2 interface
arduino-cli upload -p /dev/ttyUSB0 --fqbn esp8266:esp8266:nodemcuv2 interfaceThen set things up like this:
Pins and mappings:
- D1 low: dit down - 0x01
- D1 high: dit up - 0x02
- D2 low: dah down - 0x03
- D2 high: dah up - 0x04
Corresponding byte will be sent to the serial @ 115200 baud. Read them using interface/read-serial.py.
(There's also a *nix-specific C implementation of read-serial. From my experience there's no difference with regard to latency.)
Combine things together:
interface/read-serial.py | target/debug/keyer -m ultimatic -w25 | pee ./audio-out.py visualizer/target/debug/visualizer ./decoder-wtype.pyDemonstration:
dual-lever-paddle-cw.mp4
Note the ./decoder-wtype.py is optional. It receives dit, dah, char, word events and decodes them, and use wtype to type on Wayland.
Similar to above:
./gamepad-pipe.py | target/debug/keyer -m ultimatic -w25 | pee ./audio-out.py visualizer/target/debug/visualizer ./decoder-wtype.pyDemonstration:
gamepad-cw.mp4
See the android directory.
Demonstration:
morse.ime.demo.mp4
(Audio latency happens here because it's that the screen recorder sucks but not the app has the issue.)
I use this as a software-defined Morse keyer solution. Stuff is mostly vibe coded with OpenCode:deepseek-v4-pro - fair warning. The README however, is written by me all by-hand :).
The project is inspired by https://www.youtube.com/watch?v=Hn4j2nfdKNE and their https://didahdit.com website.