Encoding

MORSE CODE

Translate text to Morse code and back. Listen to the audio with adjustable speed.

Visual
Audio Playback
15 WPM
Reference Chart
Developer Reference

Core Algorithm & Standalone Script

Standalone, zero-dependency JavaScript implementation powering this tool. Free to inspect, copy, and build upon.

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':'----.',
  '.':'.-.-.-',',':'--..--','?':'..--..','!':'-.-.--','/':'-..-.',
  '(':'-.--.',')':'-.--.-','&':'.-...',':':'---...',';':'-.-.-.',
  '=':'-...-','+':'.-.-.','−':'-....-','"':'.-..-.','\'':'.----.',
  '@':'.--.-.'
};
const REVERSE = {};
for (const [k, v] of Object.entries(MORSE)) REVERSE[v] = k;

function textToMorse() {
  const text = document.getElementById('textInput').value.toUpperCase();
  const morse = text.split('').map(ch => {
    if (ch === ' ') return '/';
    return MORSE[ch] || '';
  }).filter(Boolean).join(' ');
  document.getElementById('morseInput').value = morse;
  updateVisual(morse);
}

function morseToText() {
  const morse = document.getElementById('morseInput').value.trim();
  const text = morse.split(/\s*\/\s*/).map(word =>
    word.split(/\s+/).map(code => REVERSE[code] || '').join('')
  ).join(' ');
  document.getElementById('textInput').value = text;
  updateVisual(morse);
}

function updateVisual(morse) {
  const container = document.getElementById('visualMorse');
  container.innerHTML = '';
  const tokens = morse.split('');
  let i = 0;
  while (i < tokens.length) {
    if (tokens[i] === '.') {
      const el = document.createElement('span');
      el.className = 'dot'; el.dataset.idx = container.children.length;
      container.appendChild(el);
    } else if (tokens[i] === '-') {
      const el = document.createElement('span');
      el.className = 'dash'; el.dataset.idx = container.children.length;
      container.appendChild(el);
    } else if (tokens[i] === '/') {
      const el = document.createElement('span');
      el.className = 'word-space';
      container.appendChild(el);
    } else if (tokens[i] === ' ') {
      const el = document.createElement('span');
      el.className = 'morse-space';
      container.appendChild(el);
    }
    i++;
  }
}

let audioCtx, playing = false, stopFlag = false;

async function playMorse() {
  if (playing) return;
  const morse = document.getElementById('morseInput').value.trim();
  if (!morse) return;
  stopFlag = false; playing = true;
  document.getElementById('playBtn').textContent = 'Playing...';

  audioCtx = audioCtx || new (window.AudioContext || window.webkitAudioContext)();
  const wpm = parseInt(document.getElementById('speedSlider').value);
  const dotLen = 1.2 / wpm;
  const elements = document.querySelectorAll('#visualMorse .dot, #visualMorse .dash');
  let elIdx = 0;

  for (let i = 0; i < morse.length; i++) {
    if (stopFlag) break;
    const ch = morse[i];
    if (ch === '.') {
      if (elements[elIdx]) elements[elIdx].classList.add('active');
      await beep(audioCtx, dotLen);
      if (elements[elIdx]) elements[elIdx].classList.remove('active');
      elIdx++;
      await sleep(dotLen * 1000);
    } else if (ch === '-') {
      if (elements[elIdx]) elements[elIdx].classList.add('active');
      await beep(audioCtx, dotLen * 3);
      if (elements[elIdx]) elements[elIdx].classList.remove('active');
      elIdx++;
      await sleep(dotLen * 1000);
    } else if (ch === ' ') {
      await sleep(dotLen * 3000);
    } else if (ch === '/') {
      await sleep(dotLen * 7000);
    }
  }
  playing = false;
  document.getElementById('playBtn').textContent = 'Play';
}

function stopMorse() {
  stopFlag = true; playing = false;
  document.getElementById('playBtn').textContent = 'Play';
  document.querySelectorAll('#visualMorse .dot, #visualMorse .dash').forEach(el => el.classList.remove('active'));
}

function beep(ctx, duration) {
  return new Promise(resolve => {
    const osc = ctx.createOscillator();
    const gain = ctx.createGain();
    osc.frequency.value = 600;
    osc.type = 'sine';
    gain.gain.value = 0.3;
    osc.connect(gain);
    gain.connect(ctx.destination);
    osc.start();
    osc.stop(ctx.currentTime + duration);
    osc.onended = resolve;
  });
}

function sleep(ms) { return new Promise(r => setTimeout(r, ms)); }

function copyText(id) {
  navigator.clipboard.writeText(document.getElementById(id).value).catch(() => {});
}

document.getElementById('speedSlider').oninput = function() {
  document.getElementById('speedVal').textContent = this.value + ' WPM';
};

// Reference grid
const grid = document.getElementById('refGrid');
for (const [ch, code] of Object.entries(MORSE)) {
  const item = document.createElement('div');
  item.className = 'ref-item';
  item.innerHTML = `<div class="ref-char">${ch}</div><div class="ref-code">${code}</div>`;
  grid.appendChild(item);
}