The Spark
You know that feeling when you stumble upon an old telegraph machine in a museum and think, "Man, that's cool... but what if it had RGB lighting?"
Okay, maybe that's just me.
But seriously, I've always been fascinated by Morse code. There's something beautifully elegant about reducing human language to just two elements: a dot and a dash. It's like the ultimate compression algorithm, invented in 1836, way before we even had computers.
So I decided to build MorseWave - a modern Morse code encoder/decoder that looks like it fell through a time portal from a cyberpunk 1980s.
Why Rust? Why WebAssembly?
Short answer: Because JavaScript alone felt too... slow for something this nerdy.
Long answer: I wanted this thing to be fast. Like, encode-a-novel-in-under-a-millisecond fast. Rust gives you that near-metal performance while keeping you from shooting yourself in the foot with memory bugs. Plus, compiling to WebAssembly means I can run this speed demon directly in the browser.
The best part? No npm package hell. No framework drama. Just pure, unadulterated performance.
The Tech Stack (Or: My Toolbox of Awesome)
Here's what I threw into this digital blender:
- Rust - For the core logic (because speed matters)
- WebAssembly - To bring that Rust goodness to the browser
- Web Audio API - For those authentic 800Hz beeps
- Canvas API - To draw some sick waveforms
- Vanilla JavaScript - Yes, really. No React. No Vue. Fight me.
- CSS3 - For that sweet CRT monitor aesthetic
The Fun Parts
1. The Encoding Algorithm
At its core, Morse code is just a giant lookup table. 'A' becomes '.-', 'B' becomes '-...', and so on. Simple, right?
pub fn encode(&self, text: &str) -> String {
text.to_uppercase()
.chars()
.filter_map(|ch| self.encode_map.get(&ch))
.map(|s| s.to_string())
.collect::<Vec<_>>()
.join(" ")
}
This beauty converts your text to uppercase, looks up each character in a HashMap, and joins them with spaces. Runs in O(n) time. Chef's kiss π¨βπ³
2. Audio Synthesis (AKA Making Bleeps)
Turns out, making a computer go "beep" is surprisingly involved. The Web Audio API is powerful but... quirky.
let oscillator = self.context.create_oscillator()?;
oscillator.set_type(OscillatorType::Sine);
oscillator.frequency().set_value(800.0); // Sweet spot for Morse
That 800Hz frequency? That's the traditional Morse code tone. I tried other frequencies. 600Hz sounded too mellow. 1000Hz was too harsh. 800Hz is the Goldilocks zone.
3. The Retro UI
Here's where I went full nostalgia mode. I wanted it to look like a terminal from WarGames or TRON.
Scanlines? Check. β
Phosphor green glow? Double check. ββ
CRT screen curvature? Okay, I didn't go that far, but the vignette effect is pretty sick.
.scanlines {
background: linear-gradient(
to bottom,
transparent 50%,
rgba(0, 0, 0, 0.1) 50%
);
animation: scan 8s linear infinite;
}
The scanline animation literally scans across your screen. It's completely unnecessary. It's absolutely essential.
The Challenges (Or: What Made Me Question My Life Choices)
WASM + Web Audio = π
Getting Rust to play nice with the Web Audio API through WASM bindings was... an adventure. The web-sys crate is amazing, but the type system is strict. Like, "I-will-fight-you-over-a-semicolon" strict.
I spent two hours debugging why audio wasn't playing. The issue? I forgot to call start_with_when() on the oscillator. Two. Hours.
Timing is Everything
Morse code has specific timing rules:
- Dot = 1 unit
- Dash = 3 units
- Space between elements = 1 unit
- Space between letters = 3 units
- Space between words = 7 units
Getting these timings right while accounting for JavaScript's event loop and audio context timing was like conducting an orchestra while juggling chainsaws.
The "Wait, How Do I Actually Test This?" Moment
Unit testing is great until you realize you're testing... Morse code. How do you verify that your audio playback is correct? Listen to it 500 times?
I ended up creating test cases like:
#[test]
fn test_sos() {
let codec = MorseCodec::new();
assert_eq!(codec.encode("SOS"), "... --- ...");
}
Simple. Effective. Saved my sanity.
The Cool Features You Didn't Know You Needed
Manual Telegraph Mode
You can literally tap out Morse code like an old-timey telegraph operator. Press . for dot, - for dash. It's weirdly satisfying. I may have spent 20 minutes just tapping out random messages.
Speed Control
Adjustable from 5 WPM (Words Per Minute) to 40 WPM. Beginners start slow. Pros can go full speed demon. I can barely handle 15 WPM before my brain melts.
Transmission History
Every message you encode gets saved with a timestamp. Why? Because I wanted to see my Morse code journey. It's like a diary, but nerdier.
What I Learned
Rust is amazing - Once you get past the learning curve, it's like programming with guardrails made of titanium.
WebAssembly is the future - Seriously. Near-native performance in the browser? Sign me up.
Retro aesthetics never die - People love that CRT look. Nostalgia is a powerful drug.
Documentation matters - I wrote comprehensive docs because future-me (and other developers) will thank past-me.
Sometimes simple is better - No framework. No build tools beyond wasm-pack. Just pure web technologies working together.
Performance Numbers (Because We're Developers and We Love Benchmarks)
- Encoding/Decoding: <1ms for typical messages
- WASM bundle size: ~50KB (gzipped)
- Initial load time: <100ms
- Memory usage: <10MB
- Audio latency: <50ms
Not bad for a weekend project that turned into a week-long obsession.
Try It Yourself
The whole thing is open source and live:
π Live Demo: chronocoders.github.io/morsewave
π¦ Crates.io: crates.io/crates/morsewave
π» GitHub: github.com/ChronoCoders/morsewave
Want to add it to your Rust project?
[dependencies]
morsewave = "0.1.0"
What's Next?
Ideas I'm toying with:
- WebRTC integration - Real-time Morse code chat, anyone?
- Mobile apps - Morse on the go
- Game mode - Learn Morse code through challenges
- Custom themes - Not everyone wants phosphor green (heathens)
Final Thoughts
Building MorseWave taught me that sometimes the best projects are the ones that make you smile. Yeah, it's a niche tool. Yeah, most people will never use Morse code in real life. But it's fun. It's nostalgic. And it works beautifully.
Plus, now I can tap out "SEND PIZZA" in Morse code when I'm debugging at 3 AM. That's worth something, right?
If you build something with it, or just think it's cool, drop a star on GitHub. And if you find any bugs... well, that's what the issues tab is for. π
Built with β€οΈ, Rust, and an unhealthy amount of caffeine
Comments? Questions? Morse Code Messages?
Drop them below! I'd love to hear what you think or what features you'd like to see.
And if you're wondering: yes, I can now actually read Morse code. Slowly. Very slowly. But I'm getting there!
P.S. - The first person to send me a valid pull request written entirely in Morse code comments gets eternal glory. And probably a mention in the README.
Top comments (0)