DEV Community

Kal Oliver
Kal Oliver

Posted on

I Built a Morse Code Translator in the Browser With Plain JavaScript

I Built a Morse Code Translator in the Browser With Plain JavaScript

I've always found Morse code fascinating — a whole alphabet built from two symbols. So I did what developers do when they're curious about something: I built a tool for it. No frameworks, no build step, just JavaScript and the Web Audio API. Here's how the core of it works.

The data is the easy part

Morse code is just a lookup table. Every character maps to a string of dots and dashes:
const MORSE = {
A: '.-', B: '-...', C: '-.-.', D: '-..', E: '.',
F: '..-.', G: '--.', H: '....', I: '..', J: '.---',
K: '-.-', L: '.-..', M: '--', N: '-.', O: '---',
P: '.--.', Q: '--.-', R: '.-.', S: '...', T: '-',
U: '..-', V: '...-', W: '.--', X: '-..-', Y: '-.--',
Z: '--..',
0: '-----', 1: '.----', 2: '..---', 3: '...--', 4: '....-',
5: '.....', 6: '-....', 7: '--...', 8: '---..', 9: '----.'
};

Encoding: text → Morse

Split the input into characters, map each one, and use a / to mark word breaks:
function textToMorse(text) {
return text.toUpperCase().trim().split('').map(ch => {
if (ch === ' ') return '/';
return MORSE[ch] || '';
}).filter(Boolean).join(' ');
}

textToMorse('SOS'); // "... --- ..."

Decoding: Morse → text

The nice trick here is you don't need a second table — just invert the first one:

const REVERSE = Object.fromEntries(
Object.entries(MORSE).map(([char, code]) => [code, char])
);

function morseToText(morse) {
return morse.trim().split(' ')
.map(code => code === '/' ? ' ' : (REVERSE[code] || ''))
.join('');
}

morseToText('... --- ...'); // "SOS"

The fun part: making it beep

Text on a screen is fine, but Morse is meant to be heard. The Web Audio API lets you generate a clean tone without loading a single audio file:

const ctx = new (window.AudioContext || window.webkitAudioContext)();

function beep(duration) {
return new Promise(resolve => {
const osc = ctx.createOscillator();
const gain = ctx.createGain();
osc.type = 'sine';
osc.frequency.value = 600; // 600 Hz is the classic CW tone
osc.connect(gain);
gain.connect(ctx.destination);
osc.start();
setTimeout(() => { osc.stop(); resolve(); }, duration);
});
}

Timing is what makes it sound right

This is the detail most beginners miss. Morse isn't just short and long beeps — the silences are part of the language. The standard ratios are:

const DOT = 80; // one unit (ms)
const DASH = DOT * 3; // a dash is 3 units
const SYMBOL_GAP = DOT; // gap between dots/dashes in a letter
const LETTER_GAP = DOT * 3;
const WORD_GAP = DOT * 7;

Then playing a sequence is just walking the string and awaiting the right duration:
async function play(morse) {
for (const symbol of morse) {
if (symbol === '.') { await beep(DOT); }
else if (symbol === '-') { await beep(DASH); }
else if (symbol === ' ') { await wait(LETTER_GAP); }
else if (symbol === '/') { await wait(WORD_GAP); }
await wait(SYMBOL_GAP);
}
}

const wait = ms => new Promise(r => setTimeout(r, ms));

Get those gaps wrong and it sounds like noise. Get them right and suddenly it sounds like the real thing.

From snippet to real tool

The version above is the skeleton. The finished project adds things I didn't expect to need: adjustable speed (WPM), a light that flashes in sync with the audio for visual learners, Farnsworth timing to make it easier to learn by ear, and even a decoder that reads Morse out of an image. You can play with the live version here — Morse Code Translator — no sign-up, it just runs in the browser.

What I learned

The encoding is trivial; the experience is where all the work hides. Timing, audio, and accessibility turned a 20-line snippet into a real tool. If you're looking for a small weekend project that touches data structures, the Web Audio API, and async timing all at once, a Morse translator is a great one.

Would love feedback from other devs — what would you add? A real telegraph-key input mode is next on my list.

Top comments (0)