A browser-based Morse code encoder, decoder and keyer built with Angular 19. Runs entirely in the browser — no installation, no backend. Provides real-time CW encoding and decoding with sub-millisecond timing precision, suitable for on-air operating at 5–50+ WPM.
Connect a physical key or paddle, and decoded Morse code appears instantly in a full-screen conversation with customisable letter size and colours — ideal for live demonstrations, training sessions, and events promoting interest in Morse code.
Decode — Decodes incoming Morse code in real time from a radio receiver's audio via CW tone detection. Dual RX/TX calibration pools auto-calibrate independently so your sending speed and the other station's speed never interfere with each other.
Encode — Converts typed text into perfectly timed Morse code audio. Supports "Send on Enter" (compose then send), "Live" (send as you type), and "Practice" (self-study CW trainer) modes.
Full-screen conversation — Opens directly from the ⛶ button and combines RX and TX history in one persistent view. A compact Key/Type toggle beside Close switches between mapped-key keying and inline text entry, never activates an output, and is remembered (with presets). Lens (fullscreen menu) shows only the latest character, very large, with its dots and dashes below, as a teaching aid; the fullscreen font, size (a ×1 to ×10 multiplier of a size that fits the screen) and colours are chosen there too. In Type mode focus goes straight to the conversation (and raises the on-screen keyboard on phones); Tab cycles conversation → play/transmit button → toolbar. Practice runs in Type mode only, and switching to Key stops it. On the main screen, Practice accepts typing only (keyer, mouse keyer and sprite are disabled).
Key — Five keyer modes: straight key, Iambic A, Iambic B, Ultimatic, and Single Lever, with full dit/dah memory and per-keyer reverse paddles. Input from an Arduino MIDI paddle interface (multiple mappings with independent settings), USB-serial adapter (multiple mappings with independent ports, pins, and polling — DSR/CTS/DCD/RI via Web Serial API), computer keyboard (multiple mappings with per-mapping paddle mode), mouse, or touchscreen. MIDI, serial, and keyboard input mappings support optional Name and Colour per mapping for multi-user conversation views. Experimental: physical key input via microphone with ultrasonic pilot-tone detection.
Key Your Radio — Drives your transmitter's keying line through multiple output methods: Arduino MIDI optocoupler (multiple mappings with per-mapping forward selectors), sound card optocoupler (DC or AC mode), USB-serial adapter (multiple mappings with per-mapping DTR/RTS pin, invert, and forward selectors via Web Serial API), or WinKeyer.
Online Relay — Relay Morse characters between app instances in real time over the internet via Firebase Realtime Database. Each character carries the sender's WPM and optional colour so the receiving station plays it back at the original rhythm. The channel name and secret together identify a database path; they are not user authentication or encryption. Input-specific names and colours are forwarded by default, with optional overrides on both the sending and receiving side.
Getting started — Start audio safely, try an interactive Morse example, and use goal-based Settings to find the relevant controls. Settings distinguish live changes from the separate per-device Save action.
Setup Wizard — Choose a goal for a guided, draft-and-review flow covering sending, typing, copy practice, decoding, MIDI or serial input, relay, radio-keying hardware, and teaching or demonstrating Morse (pick an audience and a session, such as a "What is Morse?" demo, a class conversation with a key for each student, a keyed SOS demo, a session run from your phone over Firebase RTDB, or a fair stand, and get a ready-to-run setup with a run sheet). Most guides start by asking for your level (Novice, Intermediate or Expert) and then show only the steps that suit it. Start from a saved preset to seed only the relevant draft settings, or save the reviewed proposal as a named preset without applying it. Changes apply only after review; new hardware routes require a separate explicit action, and device permissions are requested only after you choose a device.
Named Presets — Save, rename, duplicate, export, import, and review before applying reusable setups. Importing only adds a preset; it never applies it. Presets omit RTDB secrets and remain separate from both live settings and per-device profiles.
Trustworthy, accessible controls — Relay setup explains that channel details are not authentication or encryption. Core controls support keyboard, touch, and assistive-technology use; the fullscreen conversation combines RX/TX history with an explicit Key/Type choice.
The app supports several hardware interfaces for physical key input and transmitter keying output. Each has different trade-offs in responsiveness, background operation, and browser support.
The preferred interface is a simple Arduino-based USB MIDI adapter. An Arduino Pro Micro (ATmega32U4 or nRF52840) enumerates as a standard USB MIDI device — no drivers needed — and provides both paddle/key input and optocoupler keying output over a single USB connection. The v1.1.0 sketches support 16 configurable pins (10 inputs and 6 outputs by default) across two MIDI channels. The Web MIDI API delivers input events asynchronously (no polling), so timing is crisp and responsive even at high WPM. Crucially, MIDI works when the browser is in the background or minimised, unlike keyboard and mouse keyers which require the app to be in focus. This makes MIDI the most practical interface for on-air use where you may need to switch between applications while operating.
The arduino/ folder contains ready-made sketches for both board variants, with wiring diagrams for straight keys, iambic paddles, and optocoupler output circuits. See the Arduino README for build details.
Multiple MIDI devices can be used simultaneously — each MIDI Input and MIDI Output mapping is independently configurable with its own device, channel, notes, and routing. MIDI Input mappings also support optional Name and Colour fields, enabling multi-user conversation views where each operator's decoded text appears on separate colour-coded lines in the fullscreen display.
The built-in keyboard, mouse, and touch keyers require no extra hardware — convenient for practice and portable use. All five keyer modes are supported. The keyboard keyer supports multiple independent mappings, each with its own mode (straight key or paddle), key bindings, paddle mode, decoder source (RX/TX), reverse paddles toggle, and optional Name and Colour for multi-user conversation views. The mouse and touch keyers also support optional Name and Colour. The main limitation is that the browser tab must be in focus to receive these events, so they are not suitable for background operation.
A USB-serial adapter provides both input and output via the Web Serial API (Chrome/Edge only). Serial adapters are significantly cheaper than an Arduino MIDI interface (often under $2), but come with trade-offs. Like MIDI, serial port access works when the browser is in the background, making it suitable for on-air use.
- Output (DTR/RTS) — keys your transmitter by toggling DTR or RTS. Multiple output mappings are supported, each with its own port, pin (DTR or RTS), invert setting, and forward selector (TX, RX, or both). Reliable, sub-millisecond switching.
- Input (DSR/CTS/DCD/RI) — reads straight key or paddle closures from the adapter's input status pins via polling. Multiple input mappings are supported, each with its own port, pin assignments, poll interval, debounce, and decoder source. Supports all five keyer modes with an independent iambic keyer per mapping. Serial input mappings also support optional Name and Colour for multi-user conversation views.
When input and output are configured on the same port, the input service piggybacks on the output's open connection — no second port handle needed. Automatic mute suppression and decoder output routing prevent feedback when both are active simultaneously.
Common adapters (FTDI FT232R, CH340, CP2102, PL2303) are supported. Works with any adapter that exposes standard modem-status pins.
Limitations compared to MIDI:
- Desktop only — the Web Serial API is not available on Android or iOS. Chrome on Android does not enumerate USB-serial adapters even with USB OTG. If you need mobile support, use the Arduino MIDI interface instead.
- Polling latency — serial input relies on polling
getSignals()rather than asynchronous events. With the default 10 ms poll interval and 5 ms debounce, expect up to ~15 ms of input latency. This is fine for most operators, but fast CW operators (above ~40 WPM) may notice a perceivable lag compared to MIDI's event-driven input which has near-zero latency. Reducing the poll interval to 5 ms and debounce to 2 ms improves responsiveness but increases CPU usage.
For stations using a K1EL WinKeyer (WK2/WK3/WKUSB), the app can forward decoded or encoded text to the WinKeyer over its serial port. The WinKeyer generates hardware-precision CW keying independently — useful for relay, practice, or driving an external transmitter. The connection is established automatically on page load and the app attempts to reconnect when a USB device is unplugged and reconnected. Whether the browser remembers a previously granted port after re-plug depends on the device and browser — devices with a unique USB serial number (e.g. genuine FTDI adapters) are reliably matched, while some budget chipsets may require re-granting the port.
The sound card can serve double duty: an optocoupler output (DC or AC mode) keys the transmitter via the headphone jack, while microphone input with ultrasonic pilot-tone detection reads a physical key's closures. CW tone decoding of received audio works well and is the primary decode path for most setups; both the CW tone detector and the pilot-tone key input support optional Name and Colour for multi-user conversation views. The keying input side (pilot-tone detection) is experimental and sensitive to audio hardware latency, so MIDI remains the more robust choice for key input.
Every output (optocoupler, serial port, WinKeyer, sidetone, vibration, Firebase RTDB, MIDI) has an independent forward / active-on selector controlling whether it fires on TX signals, RX signals, or both. Each input (Mic, CW Tone, Keyboard, Mouse, Touch, Serial, MIDI) can be assigned to the RX or TX decoder pool, controlling both calibration and colour tagging in the fullscreen conversation view. Automatic loop detection suppresses output routing when a feedback loop is detected between outputs and inputs.
- Text Blurring — Hides decoded and/or encoded text behind a blur filter for copy-receiving practice. Configurable for RX only, TX only, or both. Press and hold the eye button to momentarily reveal the answer, or use the replay button above it to hear the last line again without redrawing the text.
- Copy Practice — Built-in CW copy trainer. The app plays random Morse sequences (characters, words, callsigns, or Koch method lessons with a Lesson selector and a firework finale when you complete lesson 40) and scores Type-along input with per-character colour-coded feedback and accuracy percentage. Three feedback modes (listen only, blurred reveal, type-along), optional 2–5 round Type-along sessions with a transient results summary, and a local/full pipeline selector support solo or externally routed practice.
- Send Practice — The mirror of Copy Practice: the app shows random characters, words, callsigns or a Koch lesson (with its own lesson setting) and you key them with a straight key, paddle, MIDI or serial key, or any other key input you pick. Keyed characters are scored with the same colour-coded feedback and accuracy, plus your keying speed in WPM. The prompt is marked green and red as you key (or checked at the end, automatically or with a button), the next character is shown bigger and blue, and mistakes need no deleting: the keying is lined up with the whole prompt after every character, repeats and slips are tolerated, and a round that is nearly done waits a few seconds for corrections. After a round, the next key you press starts the next one, and so does the first key when no round is showing; "Go to lesson" starts the next lesson's round at once. A pattern hint for the current character (on by default), Hear it first (plays the character you are about to key, on this device only),
<HH>to erase the last keyed character, 2–5 round sessions, and a "This device only" pipeline (default) that keeps keying off the serial, MIDI, WinKeyer and RTDB outputs. - Haptic Vibration — The device vibrates in sync with the sidetone while the key is down. Configurable for TX only, RX only, or both. Enhanced haptic timing compensates for Android motor spin-up latency. Android only (Chrome, Firefox, Edge).
- Screen Settings — Keep screen on (Screen Wake Lock) and Auto full screen (browser full screen after a tap, not shown on iPhone). Keep screen on prevents the screen from locking during operation. Important on mobile where screen lock suspends network connectivity, interrupting Firebase relay. Uses the Screen Wake Lock API (Chrome 84+, Edge 84+, Safari 16.4+).
- Progressive Web App — Installable as a standalone app on desktop (Chrome, Edge) and mobile (Android Chrome, iOS Safari). The Angular service worker caches the app shell and assets for offline access, and a Check for updates button (Settings → Other) fetches a new release without reloading. A short install hint in the Try Morse guide appears only when the app is not yet installed.
- iPhone support — The layout follows the visible screen on iPhone (on-screen keyboard, notch, rotation), audio resumes after iOS pauses it, and features iPhone browsers lack are shown as "Not available on this device". All iPhone-specific behaviour is switched on only by an iOS capability check, so Android and desktop are unchanged.
- Open the app in Chrome or Edge (required for full feature support including device selection and serial ports).
- Click Start Audio — grant microphone permission when prompted.
- Expand Settings to configure your input/output devices, keyer bindings, and WPM.
- Click Save — your configuration is saved per device profile and persists across sessions.
- Click the Help button from the top-right kebab menu for detailed documentation covering every feature, wiring diagrams, and real-world use-case examples.
| Browser | Supported | Notes |
|---|---|---|
| Chrome | Yes | Full support including Web Serial and Web MIDI |
| Edge | Yes | Full support including Web Serial and Web MIDI |
| Firefox | Partial | No Web Serial or Web MIDI — serial/MIDI unavailable |
| Safari | Partial | No Web Serial or Web MIDI — serial/MIDI unavailable |
| Chrome on iPhone | Partial | Built on Apple's WebKit, so it behaves like Safari: no Web Serial, Web MIDI or vibration (those cards show "Not available on this device"). Audio resumes after a tap when iOS pauses it, and the ring/silent switch can mute sound. Install the full-screen app from Safari. See Help → Install & Offline Use → Using Morse Code Studio on iPhone |
This project includes content that was fully or partially generated with AI tools (GitHub Copilot).
Some files may have been edited, curated, or integrated by humans, while others may be largely machine-generated.
The project includes .github/copilot-instructions.md as an aid to others who may use GitHub Copilot on the codebase.
Development followed an AI-assisted, human-led methodology where human creativity directed the architecture, system design, and hardware integration, while GitHub Copilot accelerated implementation and troubleshooting.
This approach compressed the development lifecycle from months into approximately 100 hours, enabling rapid iteration and extensive cross-device testing without sacrificing robust architecture or maintainability.
By shifting focus from boilerplate coding to high-level engineering — such as complex signals processing and loop detection — AI assistance made exploring new concepts faster, testing easier, and continuous refinement almost effortless (31 iterations in PRE-RELEASE-CHANGELOG).
npm install
ng serveNavigate to http://localhost:4200/. The app reloads automatically on source changes.
Contributions are welcome! Please read CONTRIBUTING.md for guidelines on how to submit issues, feature requests, and pull requests.
To report a security vulnerability, please see SECURITY.md.
The app connects to Firebase Realtime Database from a public browser client. The repository's example rules allow anonymous reads and writes; they are examples only and do not establish the rules used by a deployed project. Review the actual project's rules before enabling relay.
The channel name and secret form a database path, not an authentication or privacy boundary. Configure and review restrictive database rules and data validation in Firebase, and quotas, rate limits, and old-data cleanup in Firebase or related infrastructure. The app does not enforce these protections or limits, and it does not automatically clean up stored channel data.
To maximize freedom for downstream users, and because portions of AI-generated material may not be eligible for copyright protection in some jurisdictions, we dedicate this project to the public domain using CC0 1.0 Universal.
If any rights do exist, we waive them to the fullest extent permitted by law.
Morse Code Studio is marked CC0 1.0
— see LICENSE for details.
This project uses the Angular framework and various npm packages, many of which are licensed under the MIT License, which requires preserving their original copyright and license notices in distributed builds.
During a production build, Angular extracts all dependency license texts into:
dist/3rdpartylicenses.txt
This is enabled by setting "extractLicenses": true in angular.json, which is the recommended way to ensure all license obligations are met.
The fullscreen view bundles four fonts, self-hosted through Fontsource and licensed under the SIL Open Font License 1.1: Courier Prime, Merriweather, Atkinson Hyperlegible and B612 Mono. Each package's license file is kept in node_modules/@fontsource/ and is part of dist/3rdpartylicenses.txt.