- JavaScript 70.1%
- Python 13.9%
- HTML 10.6%
- CSS 5.3%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| deploy | ||
| web | ||
| .gitignore | ||
| koch.py | ||
| LICENSE.txt | ||
| morse.py | ||
| morse_ref_chart.txt | ||
| morseTable.py | ||
| output.mp3 | ||
| output.wav | ||
| play.py | ||
| README.md | ||
Simplified Morse practice frontend
The code and documentation added or modified for this fork were written by gpt-6-astra, acting as the Codex coding agent under user direction. The original upstream code is credited to cduck/morse.
Live app: Morse practice. Repository: samuel/morse-exam-prep.
Run python3 -m http.server 8000 from this directory and open
http://localhost:8000/web/play.html. No build step or frontend dependencies are required.
Quick start
- Level 1 is shown on page load. Click a charset button such as EI. It generates 200 practice characters by default, adds spaces using English word-length frequencies, and starts the 5-second countdown before playback.
- Click Pause to stop during the countdown or playback. Selecting another top charset generates a fresh message and starts a new countdown.
- Open Text options for custom generation, navigation, Restart, and Text to play. Custom generation buttons prepare the text; press Play to start it.
On a fresh visit with no saved message, the editor is empty and Play is dimmed and disabled. Generating, entering, or loading nonblank text enables playback. Clearing the text while paused disables Play again. Clearing it during playback keeps Pause available so you can still stop the audio.
Practice controls
Only the current level’s presets are shown, starting with Level 1 on page load. Use Previous level and Next level to move through the six levels. The current level is always open; navigation is disabled at the first and last boundaries. Changing levels does not generate text or interrupt playback:
| Level | Presets | Percentage |
|---|---|---|
| 1 | EI · EIA · TN · TNM · EIATNM | 46% |
| 2 | SHV · UF · SHVUF · EIATNM SHVUF | 64% |
| 3 | RL · WPJ · RLWPJ · SHVUF RLWPJ · EIATNM SHVUF RLWPJ | 79% |
| 4 | DBX · KCY · DBXKCY · EIATNM SHVUF RLWPJ DBXKCY | 90% |
| 5 | GZQO · A-Z | 100% |
| 6 | 0-9 · ?.-=;, · A-Z0-9?.-=;, · Random Aesop fable | 105% |
Percentages appear outside the buttons, aligned to the right edge of each row. They are display labels only and do not change the generated charset. Each group wraps on narrow screens. Clicking a preset selects that charset and immediately generates practice with spaces using the configured character count. Playback starts automatically after the configured initial pause.
The generation status identifies the charset, such as “Generated 200 characters
using TNM.” Complete alphabet and digit sets are summarized as A-Z and 0-9.
Playback
Play / Pause works during the initial countdown and playback. Restart begins again with the full initial delay. Resuming a paused initial countdown starts that delay again; resuming midway does not add it.
The editor starts empty unless a saved practice message is available. The old built-in sample is ignored. Play is dimmed and disabled for empty or whitespace-only text. Pause remains available if the editor is cleared during playback.
Playback uses a short audio buffer. If a browser timer is delayed, the next unscheduled character continues at normal Morse timing instead of compressing missed tones together. A long browser stall can still cause an audible gap. Audio gain is fixed at 1; use the device’s system volume to adjust loudness. Previously saved app-volume settings are ignored.
Automatic audio recovery
The app rebuilds its audio connection when returning after at least 30 seconds away from the page, or on the first pointer/keyboard interaction after at least one minute without interaction. Returning from the browser’s page cache also refreshes audio. It does not periodically refresh while you listen without touching the page, and no separate recovery button is needed.
If playback was active, recovery resumes the existing message from the current character, which may be repeated, and preserves any remaining initial countdown. If paused, it stays paused. Pan, frequency, speed, spacing, and text are preserved. Playback scheduling waits for the audio context to resume successfully; Pause or a new preset cancels an older pending start. A failed start or five-second timeout leaves Play available for retry instead of advancing silently.
This targets Safari’s silent-audio failures after inactivity, but does not guarantee recovery from every browser or device audio failure. Long-idle behavior still needs confirmation on real Safari devices.
Text options
This dropdown is collapsed by default. Its rows are:
-
Navigate to character, with Restart.
-
The editable Text to play. Typed, pasted, generated, saved, and imported text uses only
A-Za-z0-9?.-=;,, spaces, and line breaks. Other characters are discarded; tabs and nonbreaking spaces become ordinary spaces. Invalid characters in a generation charset are ignored. -
Input charset, spanning the controls column. Its row count adjusts to fit 42 characters at the current width. Manual resizing is available; there is no character limit.
-
Number of chars, defaulting to 200 before spaces are added.
-
Generate text to play, with separate buttons for generation with and without spaces.
-
Load text to play from file instead. Only the first 1,000 accepted characters are used, including spaces and line breaks. Invalid characters do not count toward this limit: the loader continues scanning until it has enough accepted text or reaches the end of the file. Loading stays entirely in the browser, reading in small chunks rather than holding the whole file in memory. The character-count setting applies to generation, not file loading.
-
Load an Aesop fable instead. Pick a filename from the dropdown to load its text, then press Play. Loading pauses any current playback and resets the position. Like file uploads, stories are filtered to the supported characters and limited to the first 1,000 accepted characters, including spacing.
Level 6 also has Random Aesop fable: it chooses uniformly from the local collection, loads the text, and immediately starts playback with the configured initial pause. It works while Text options is collapsed. The selected filename is shown in the picker and status message.
Story credits: The fables are traditionally attributed to Aesop. Credit and thanks to barbarabai for making the collection available as individual text files in barbarabai/aesop. These story texts were sourced from that repository, not written for this app.
The 311 filenames and complete story texts are stored in web/aesop.js, pinned to source revision 206f1a6c5d96fe419c558b0969d117b60d9b1e8c. The catalog also records its source and revision in its header. No GitHub request is needed at runtime. Source text was decoded as UTF-8 where valid, otherwise Windows-1252; filtering happens only on load. To update the snapshot, replace the catalog entries from a reviewed source revision, retain the original filenames, decoded contents, and attribution, update the revision comment and HTML asset version, and run node web/play.test.cjs before deploying.
Closing the dropdown only hides these controls. The message remains available for playback and restart. Open it to edit the text or view character highlighting.
Audio options
This separate dropdown is also collapsed by default. It contains the audio preset, initial pause, frequency, pan, speed, and Farnsworth spacing.
Each page starts at 8 WPM, Farnsworth 0 (normal spacing), 700 Hz, centered pan (0), and a 5-second initial pause. ASOC exam india default - audio restores these five settings without changing the text, charset, character count, or playback position. There are no volume, background-audio, or frequency-preset controls.
Morse Reference
The third dropdown displays morse_ref_chart.txt verbatim in monospace, preserving its spaces and line breaks. The chart scrolls horizontally on narrow screens. All three dropdowns are separated by a 12px gap.
Generation and spacing
Generate with spaces is the default for all top charset presets. In Text options,
choose Generate with spaces or Generate without spaces for the custom charset, then press Play.
Custom generation normalizes letters to uppercase, ignores whitespace and duplicates,
and rejects unsupported characters. Enter actual characters in the custom field;
the A-Z and 0-9 preset buttons expand their ranges automatically.
For example, spaced output might look like TN MTN T NMTN; unspaced output is continuous, such as TNMTNTNMTN.
Spaced generation samples each group length independently from the observed English word-occurrence counts in Peter Norvig’s Google Books analysis. It uses lengths 1–23, including rare long groups. Three-character groups are most common (about 20.5%); five-character groups occur about 10.7% of the time. The source distribution averages about 4.79 characters per group. These are random character groups drawn from your charset, not English words.
The configured character count excludes inserted spaces: 200 means 200 practice characters plus separators. The final group is shortened when necessary to reach that exact count, and no leading or trailing space is added. Individual sessions will vary from the source frequencies, especially for short practice texts.
Run the frontend logic checks with node --test web/play.test.cjs.
To host on GitHub Pages, publish the repository root and visit /web/play.html.
Morse code audio generation tool
This tool can either play morse code directly or save to a WAV file. It translates plain text from stdin or the command line. The tone frequency, words per minute, and Farnsworth timing are also configurable through command line arguments. It should work on Mac, Linux, and Windows but has only been tested to play audio on Mac.
The original upstream HTML5 frontend is available here.
Usage
Playing sound:
$ echo "Morse code message." |python3 play.py -f 750 --wpm 10
Create audio file:
$ echo "Morse code message." |python3 play.py -f 750 --wpm 10 -o output.wav
Output: output.wav (converted to mp3)
Translate to just . and - characters:
$ echo "Morse code message." |python3 morse.py
-- --- .-. ... . -.-. --- -.. . -- . ... ... .- --. . .-.-.-
Learn Morse code using Koch's method
Play a random sequence of 100 A, T, and R letters
$ ./koch.py --wpm=30 --fs=10 -l 100 "ATR"
Other audio formats
Convert to mp3 or other formats with FFmpeg:
ffmpeg -i output.wav output.mp3
Output: output.mp3
Dependencies
- Python 3
- NumPy
- SciPy
- sounddevice which also needs PortAudio
Server hosting
Live site: https://morse.samuelshadrach.com (Cloudflare proxy, Nginx origin).
Only play.html, play.css, play.js, aesop.js, morse.js, and morseTable.js from web/ are deployed to /var/www/morse. The repository, tests, and credentials are not published there.
The reference chart is embedded in web/play.html; keep its <pre id="morseReference"> content identical to morse_ref_chart.txt when updating it. No additional public file route is needed.
The HTML references versioned JavaScript and stylesheet URLs. Bump the affected asset versions when deploying changes so cached clients receive them.
The Nginx configuration is tracked in deploy/nginx.conf; other application paths return 404.
HTTP redirects to HTTPS, except the isolated ACME challenge directory.
Certbot renews the Let's Encrypt origin certificate using certbot.timer and
/var/www/letsencrypt for validation. The deploy hook in deploy/reload-nginx.sh
validates and reloads Nginx after renewal. Certificate private keys remain outside
the repository and public web directory, under /etc/letsencrypt.