morse-code-audio
v0.1.0
Published
Morse code in JavaScript: translate text, build ITU timing with Farnsworth spacing, render WAV audio, and decode Morse from recorded or live audio. No dependencies.
Downloads
329
Maintainers
Readme
morse-code-audio
Morse code in plain JavaScript: translate text both ways, build exact ITU timing with Farnsworth spacing, render click-free audio to a WAV file, and decode Morse from a recording or a live microphone. One ES module, no dependencies, no DOM. Runs in the browser and in Node 18+.
Live demo: morsecodefree.com uses this code for its translator, audio decoder and Koch trainer.
Install
npm install morse-code-audioOr in the browser without a build step:
<script type="module">
import { encode } from "https://cdn.jsdelivr.net/npm/morse-code-audio/+esm";
</script>Translate
import { encode, decode, translate } from "morse-code-audio";
encode("SOS help").morse; // "... --- ... / .... . .-.. .--."
decode("... --- ...").text; // "SOS"
translate("-- --- .-. ... ."); // { dir: "decode", output: "MORSE", unknown: [] }
encode("<AR> done").morse; // prosigns in angle brackets: ".-.-. / -.. --- -. ."Morse is written with . and -, one space between letters and / between words. Input in other notations
(·, •, _, −, | as a word break) is normalized. Unknown characters become # and are listed in unknown.
The table covers ITU-R M.1677-1 letters, figures and punctuation, common extra signs, accented letters and prosigns.
Audio
import { encode, schedule, render, wav } from "morse-code-audio";
import { writeFileSync } from "node:fs";
const { tones } = schedule(encode("CQ CQ").morse, { wpm: 20, fwpm: 12 }); // 20 WPM characters, 12 WPM Farnsworth
const samples = render(tones, { freq: 600 }); // Float32Array, raised-cosine ramps
writeFileSync("cq.wav", wav(samples)); // 16-bit mono WAVschedule returns each tone's start, dur and pos (its index in the Morse string), so you can highlight dots
and dashes in sync with playback. timing(wpm, fwpm) gives the unit, letter gap and word gap in seconds (PARIS
standard, ARRL Farnsworth formula).
Decode Morse from audio
import { decodeAudio } from "morse-code-audio";
const { text, morse, wpm, freq } = decodeAudio(samples, 44100);It finds the strongest tone between 300 and 1500 Hz, follows its level with a sliding Goertzel filter, estimates the
dot length (and so the speed) by clustering, and reads dots, dashes, letter and word gaps. Pass { freq } to skip
tone detection. It handles noise, speed changes between recordings and Farnsworth spacing.
From a file in the browser:
const ac = new AudioContext();
const buffer = await ac.decodeAudioData(await file.arrayBuffer());
const result = decodeAudio(buffer.getChannelData(0), buffer.sampleRate);From the microphone, collect a few seconds of samples and decode the window repeatedly:
const stream = await navigator.mediaDevices.getUserMedia({
audio: { echoCancellation: false, noiseSuppression: false, autoGainControl: false },
});
const ac = new AudioContext();
const src = ac.createMediaStreamSource(stream);
const proc = ac.createScriptProcessor(4096, 1, 1);
const chunks = [];
proc.onaudioprocess = (e) => {
chunks.push(new Float32Array(e.inputBuffer.getChannelData(0)));
if (chunks.length > 300) chunks.shift(); // keep about the last 30 seconds
};
src.connect(proc).connect(ac.destination);
setInterval(() => {
const all = new Float32Array(chunks.reduce((n, c) => n + c.length, 0));
let o = 0;
for (const c of chunks) { all.set(c, o); o += c.length; }
console.log(decodeAudio(all, ac.sampleRate).text);
}, 1000);Turn off echo cancellation and noise suppression as shown: browsers otherwise treat a steady tone as noise and remove it.
Koch method
import { KOCH, kochReady } from "morse-code-audio";
KOCH.slice(0, 2); // ["K", "M"]: the order most trainers use, one new character at a time
kochReady(lastAnswers); // true after 20 answers with at least 18 right: add the next characterTest
npm testLicense
MIT
