An interactive Morse code learning application built with Python and CustomTkinter. Learn Morse code through guided translations, flashcard drills, free-form practice, and timed assessments.
Guided translation sessions with progress tracking, hints, and audio playback. Practice converting between plain text and Morse code with immediate feedback.
Category-based flashcard decks covering letters, numbers, and symbols. Audio pronunciations help reinforce learning with progress indicators.
Free-form translator for experimenting with Morse code. Features adjustable audio settings:
- Volume control
- Speed (WPM) adjustment
- Pitch customization
- Save and export options
Challenge yourself with randomized prompts, accuracy scoring, and detailed timing metrics. Supports:
- Words and sentences
- Backward navigation and answer persistence
- Pause/resume functionality
High-quality Morse code audio powered by pygame-ce with custom WAV synthesis for authentic dit/dah tones. An AudioCache service synthesizes audio on demand and caches the result for the session:
- Learning mode — dynamic synthesis with static
.wavfile fallback - Translation exercises (morse→text) — dynamic synthesis
- Sandbox — adjustable speed, pitch, and volume controls
- Python 3.10+ (tested up to 3.14)
- pip package manager
- Windows/macOS/Linux (GUI requires display)
-
Clone the repository
git clone https://github.com/sandersirge/MorseCodeProgram.git cd MorseCodeProgram -
Create and activate virtual environment
Windows (PowerShell):
python -m venv .venv .\.venv\Scripts\Activate.ps1macOS/Linux:
python3 -m venv .venv source .venv/bin/activate -
Install dependencies
pip install -r requirements/requirements.txt
For development (includes pytest):
pip install -r requirements/requirements-dev.txt
python run.pyThe project includes a comprehensive test suite with 705 tests covering models, controllers, services, utilities, resources, and exceptions.
cd src
pytestcd src
pytest -m smoke -qpytest --cov=main.python --cov-report=htmlAfter running tests, an HTML report is generated at reports/report.html.
| Category | Description |
|---|---|
tests/model/ |
Domain entities and session state |
tests/controllers/ |
Presenter logic and UI coordination |
tests/services/ |
Audio cache, data providers, settings |
tests/utils/ |
Morse translator — unit, parametrized, and property |
tests/resources/ |
Asset loading and constants |
tests/exceptions/ |
Exception hierarchy, error codes, user messages |
tests/application/ |
DI wiring and bootstrap integration |
tests/views/ |
Theme tokens, widget helpers, regressions |
tests/test_smoke.py |
Fast bootstrap sanity checks (pytest -m smoke) |
MorseCodeProgram/
├── run.py # Application entry point
├── morsetrainer.spec # PyInstaller build spec
├── pyproject.toml # Project metadata and tool config
├── LICENSE.md # MIT License
├── README.md # This file
├── DEVPLAN.md # Development roadmap
│
├── docker/ # Container configuration
│ ├── Dockerfile
│ └── docker-compose.yml
│
├── requirements/ # Dependency files
│ ├── requirements.txt # Runtime dependencies
│ └── requirements-dev.txt # Development dependencies
│
├── reports/ # Test report output (generated)
│
├── resources/
│ └── icons/ # Application icons (ICO, ICNS, PNG)
│
├── src/
│ ├── main/
│ │ ├── python/ # Application source code
│ │ │ ├── application.py # App wiring & DI bootstrap
│ │ │ ├── main.py # Entry point
│ │ │ ├── navigator.py # Frozen routing dataclass
│ │ │ ├── view_stack.py # Frame-swap navigation manager
│ │ │ ├── controllers/ # MVC presenters
│ │ │ │ ├── protocols.py # Presenter Protocol types
│ │ │ │ ├── flashcard_controller.py
│ │ │ │ ├── translation_controller.py
│ │ │ │ ├── test_controller.py
│ │ │ │ └── translation_sandbox_controller.py
│ │ │ ├── exceptions/ # Typed exception hierarchy
│ │ │ │ ├── base.py # ErrorCode, MorseTrainerError
│ │ │ │ ├── session.py
│ │ │ │ ├── translation.py
│ │ │ │ ├── audio.py
│ │ │ │ └── validation.py
│ │ │ ├── model/ # Domain entities & state
│ │ │ ├── resources/ # Constants & data tables
│ │ │ │ ├── morse_data.py # Symbol tables & lookup maps
│ │ │ │ ├── audio_data.py # Audio asset path maps
│ │ │ │ ├── exercise_data.py # Training/test samples
│ │ │ │ └── constants.py # Re-export shim
│ │ │ ├── services/ # Audio, data providers
│ │ │ │ ├── audio_cache.py # Dynamic synthesis + cache
│ │ │ │ ├── morse_audio.py # WAV synthesis engine
│ │ │ │ ├── audio_provider.py # pygame wrapper
│ │ │ │ ├── audio_settings.py # User preferences
│ │ │ │ └── data_provider.py # Session factories
│ │ │ ├── utils/ # Morse translator
│ │ │ └── views/ # CustomTkinter UI
│ │ │ ├── theme.py # get_colors(), callbacks
│ │ │ ├── widgets.py # Shared UI components
│ │ │ └── ... # Screen views
│ │ │
│ │ └── resources/ # Static audio files
│ │ ├── letters/ # Letter audio (.wav)
│ │ ├── numbers/ # Number audio (.wav)
│ │ └── symbols/ # Symbol audio (.wav)
│ │
│ └── tests/ # pytest test suite
│ ├── conftest.py # Shared fixtures
│ ├── exceptions/ # Exception hierarchy tests
│ ├── test_smoke.py # Bootstrap sanity checks
│ └── ...
│
└── output/ # Generated files (exports, saves)
The application follows an MVC-style architecture:
- Controllers — Hold all domain logic, coordinate between models and views
- Models — Immutable state objects using frozen dataclasses
- Views — Presentation-only CustomTkinter components
- Services — Audio synthesis and caching, data providers, settings management
- Navigator Pattern: A frozen
Navigatordataclass provides namedCallablefields for all routes. Views depend onNavigatorinstead of individual callbacks — adding a screen only requires a new field - ViewStack Frame-Swap:
ViewStackregisters a persistentCTkFrameper view. Navigation usespack_forget()/pack()to swap visible frames — no widgets are destroyed on screen transitions - Protocol-Based Decoupling: Views annotate presenter parameters with
Protocoltypes fromcontrollers/protocols.py. Concrete presenters satisfy protocols implicitly — no inheritance required - Dynamic Audio:
AudioCachesynthesizes Morse audio on demand viasynthesize_morse_audio(), caching results per session. Flashcard mode falls back to static.wavfiles; translation exercises use dynamic-only audio - Immutable State: Models expose frozen dataclass states to prevent accidental mutation
- Decoupled Audio: pygame-ce integration is mocked in tests for CI stability
- Theme Tokens: Centralized font/color constants in
views/theme.py; all views callget_colors()at render time for correct light/dark theming - Typed Exceptions:
exceptions/package provides anErrorCodeenum, Estonian user-facing messages, and aget_user_message()helper — no raw strings in error paths - Separated Resource Concerns:
resources/split intomorse_data,audio_data, andexercise_datamodules
- Python type hints throughout
- Frozen dataclasses for state
- No direct UI manipulation in controllers
Tests use pytest with fixtures defined in src/tests/conftest.py. Audio playback is automatically mocked.
# Example test
def test_translation_session_advances(sample_translation_items):
session = TranslationSession.create(sample_translation_items)
assert session.current_index == 0Edit src/main/python/views/theme.py to modify:
- Font families and sizes
- Color palette
- Widget presets
# Build and run tests
docker build -t morse-trainer -f docker/Dockerfile .
docker run --rm morse-trainer
# Or using docker compose
docker compose -f docker/docker-compose.yml run --rm testdocker compose -f docker/docker-compose.yml run --rm devxhost +local:docker
docker compose -f docker/docker-compose.yml run --rm appNote: GUI applications in Docker require X11 forwarding. On Windows, use WSLg or VcXsrv.
The application uses PyInstaller to create standalone executables for all platforms.
# Install PyInstaller
pip install pyinstaller
# Build for your platform
pyinstaller morsetrainer.spec
# The built application will be in dist/MorseCodeTrainer/| Platform | Architecture | Artifact |
|---|---|---|
| Windows | x64 | MorseCodeTrainer-windows-x64.zip |
| Windows | x86 | MorseCodeTrainer-windows-x86.zip |
| macOS | arm64 (Apple Silicon) | MorseCodeTrainer-macos-arm64.dmg |
| Linux | x64 | MorseCodeTrainer-linux-x64.tar.gz |
| Linux | arm64 | MorseCodeTrainer-linux-arm64.tar.gz |
Push a version tag to trigger automatic builds:
git tag v1.0.0
git push origin v1.0.0GitHub Actions will build executables for all 5 platform/architecture combinations and create a release with:
- Windows:
.ziparchives (x64, x86) - macOS:
.dmgdisk image (Apple Silicon) - Linux:
.tar.gzarchives (x64, arm64) - checksums.txt: SHA256 hashes for all assets
Place your icons in resources/icons/ (see resources/icons/README.md for required formats).
See DEVPLAN.md for detailed development plans.
- CI/CD pipeline with GitHub Actions
- Docker containerization
- PyInstaller packaging and release automation
- Multi-platform builds (Windows x64/x86, macOS arm64, Linux x64/arm64)
- Application icons
- Accessibility improvements (keyboard navigation, light/dark mode)
- Typed exception hierarchy with Estonian user messages
- Property-based tests with Hypothesis
- Smoke test suite (
pytest -m smoke) - Navigator routing & ViewStack frame-swap architecture
- Protocol-based presenter decoupling
- Dynamic audio synthesis via AudioCache
- Custom deck import/export
- Analytics exports (CSV summaries)
- Clone the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit changes (
git commit -m 'Add amazing feature') - Push to branch (
git push origin feature/amazing-feature) - Open a Pull Request
Please ensure all tests pass before submitting.
This project is licensed under the MIT License - see LICENSE.md for details.
- CustomTkinter for modern UI components
- pygame-ce for audio playback
- The Morse code learning community