@ozdemirburak/morse-code-translator
Version:
Morse code translator helps you convert text to Morse code and vice versa, with the option to play Morse code audio.
354 lines (349 loc) • 15.7 kB
JavaScript
;
const baseCharacters = {
'1': {
'A': '01', 'B': '1000', 'C': '1010', 'D': '100', 'E': '0', 'F': '0010',
'G': '110', 'H': '0000', 'I': '00', 'J': '0111', 'K': '101', 'L': '0100',
'M': '11', 'N': '10', 'O': '111', 'P': '0110', 'Q': '1101', 'R': '010',
'S': '000', 'T': '1', 'U': '001', 'V': '0001', 'W': '011', 'X': '1001',
'Y': '1011', 'Z': '1100'
},
'2': {
'0': '11111', '1': '01111', '2': '00111', '3': '00011', '4': '00001',
'5': '00000', '6': '10000', '7': '11000', '8': '11100', '9': '11110'
},
'3': {
'.': '010101', ',': '110011', '?': '001100', '\'': '011110', '!': '101011', '/': '10010',
'(': '10110', ')': '101101', '&': '01000', ':': '111000', ';': '101010', '=': '10001',
'+': '01010', '-': '100001', '_': '001101', '"': '010010', '$': '0001001', '@': '011010',
'¿': '00101', '¡': '110001'
},
'4': {
'Ã': '01101', 'Á': '01101', 'Å': '01101', 'À': '01101', 'Â': '01101', 'Ä': '0101',
'Ą': '0101', 'Æ': '0101', 'Ç': '10100', 'Ć': '10100', 'Ĉ': '10100', 'Č': '110',
'Ð': '00110', 'È': '01001', 'Ę': '00100', 'Ë': '00100', 'É': '00100',
'Ê': '10010', 'Ğ': '11010', 'Ĝ': '11010', 'Ĥ': '1111', 'İ': '01001', 'Ï': '10011',
'Ì': '01110', 'Ĵ': '01110', 'Ł': '01001', 'Ń': '11011', 'Ñ': '11011', 'Ó': '1110',
'Ò': '1110', 'Ö': '1110', 'Ô': '1110', 'Ø': '1110', 'Ś': '0001000', 'Ş': '01100',
'Ș': '1111', 'Š': '1111', 'Ŝ': '00010', 'ß': '000000', 'Þ': '01100', 'Ü': '0011',
'Ù': '0011', 'Ŭ': '0011', 'Ž': '11001', 'Ź': '110010', 'Ż': '11001'
},
'5': {
'А': '01', 'Б': '1000', 'В': '011', 'Г': '110', 'Д': '100', 'Е': '0',
'Ж': '0001', 'З': '1100', 'И': '00', 'Й': '0111', 'К': '101', 'Л': '0100',
'М': '11', 'Н': '10', 'О': '111', 'П': '0110', 'Р': '010', 'С': '000',
'Т': '1', 'У': '001', 'Ф': '0010', 'Х': '0000', 'Ц': '1010', 'Ч': '1110',
'Ш': '1111', 'Щ': '1101', 'Ъ': '11011', 'Ы': '1011', 'Ь': '1001', 'Э': '00100',
'Ю': '0011', 'Я': '0101', 'Ї': '01110', 'Є': '00100', 'І': '00', 'Ґ': '110'
},
'6': {
'Α': '01', 'Β': '1000', 'Γ': '110', 'Δ': '100', 'Ε': '0', 'Ζ': '1100',
'Η': '0000', 'Θ': '1010', 'Ι': '00', 'Κ': '101', 'Λ': '0100', 'Μ': '11',
'Ν': '10', 'Ξ': '1001', 'Ο': '111', 'Π': '0110', 'Ρ': '010', 'Σ': '000',
'Τ': '1', 'Υ': '1011', 'Φ': '0010', 'Χ': '1111', 'Ψ': '1101', 'Ω': '011'
},
'7': {
'א': '01', 'ב': '1000', 'ג': '110', 'ד': '100', 'ה': '111', 'ו': '0',
'ז': '1100', 'ח': '0000', 'ט': '001', 'י': '00', 'כ': '101', 'ל': '0100',
'מ': '11', 'נ': '10', 'ס': '1010', 'ע': '0111', 'פ': '0110', 'צ': '011',
'ק': '1101', 'ר': '010', 'ש': '000', 'ת': '1'
},
'8': {
'ا': '01', 'ب': '1000', 'ت': '1', 'ث': '1010', 'ج': '0111', 'ح': '0000',
'خ': '111', 'د': '100', 'ذ': '1100', 'ر': '010', 'ز': '1110', 'س': '000',
'ش': '1111', 'ص': '1001', 'ض': '0001', 'ط': '001', 'ظ': '1011', 'ع': '0101',
'غ': '110', 'ف': '0010', 'ق': '1101', 'ك': '101', 'ل': '0100', 'م': '11',
'ن': '10', 'ه': '00100', 'و': '011', 'ي': '00', 'ﺀ': '0'
},
'9': {
'ا': '01', 'ب': '1000', 'پ': '0110', 'ت': '1', 'ث': '1010', 'ج': '0111',
'چ': '1110', 'ح': '0000', 'خ': '1001', 'د': '100', 'ذ': '0001', 'ر': '010',
'ز': '1100', 'ژ': '110', 'س': '000', 'ش': '1111', 'ص': '0101', 'ض': '00100',
'ط': '001', 'ظ': '1011', 'ع': '111', 'غ': '0011', 'ف': '0010', 'ق': '111000',
'ک': '101', 'گ': '1101', 'ل': '0100', 'م': '11', 'ن': '10', 'و': '011',
'ه': '0', 'ی': '00'
},
'10': {
'ア': '11011', 'カ': '0100', 'サ': '10101', 'タ': '10', 'ナ': '010', 'ハ': '1000',
'マ': '1001', 'ヤ': '011', 'ラ': '000', 'ワ': '101', 'イ': '01', 'キ': '10100',
'シ': '11010', 'チ': '0010', 'ニ': '1010', 'ヒ': '11001', 'ミ': '00101', 'リ': '110',
'ヰ': '01001', 'ウ': '001', 'ク': '0001', 'ス': '11101', 'ツ': '0110', 'ヌ': '0000',
'フ': '1100', 'ム': '1', 'ユ': '10011', 'ル': '10110', 'ン': '01010', 'エ': '10111',
'ケ': '1011', 'セ': '01110', 'テ': '01011', 'ネ': '1101', 'ヘ': '0', 'メ': '10001',
'レ': '111', 'ヱ': '01100', 'オ': '01000', 'コ': '1111', 'ソ': '1110', 'ト': '00100',
'ノ': '0011', 'ホ': '100', 'モ': '10010', 'ヨ': '11', 'ロ': '0101', 'ヲ': '0111',
'゛': '00', '゜': '00110', '。': '010100', 'ー': '01101', '、': '010101',
'(': '101101', ')': '010010'
},
'11': {
'ㄱ': '0100', 'ㄴ': '0010', 'ㄷ': '1000', 'ㄹ': '0001', 'ㅁ': '11', 'ㅂ': '011',
'ㅅ': '110', 'ㅇ': '101', 'ㅈ': '0110', 'ㅊ': '1010', 'ㅋ': '1001', 'ㅌ': '1100',
'ㅍ': '111', 'ㅎ': '0111', 'ㅏ': '0', 'ㅑ': '00', 'ㅓ': '1', 'ㅕ': '000',
'ㅗ': '01', 'ㅛ': '10', 'ㅜ': '0000', 'ㅠ': '010', 'ㅡ': '100', 'ㅣ': '001',
'ㅐ': '1101', 'ㅔ': '1011'
},
'12': {
'ก': '110', 'ข': '1010', 'ค': '101', 'ง': '10110', 'จ': '10010',
'ฉ': '1111', 'ช': '1001', 'ซ': '1100', 'ญ': '0111', 'ด': '100',
'ต': '1', 'ถ': '10100', 'ท': '10011', 'น': '10', 'บ': '1000',
'ป': '0110', 'ผ': '1101', 'ฝ': '10101', 'พ': '01100', 'ฟ': '0010',
'ม': '11', 'ย': '1011', 'ร': '010', 'ล': '0100', 'ว': '011',
'ส': '000', 'ห': '0000', 'อ': '10001', 'ฮ': '11011', 'ฤ': '01011',
'ะ': '01000', 'า': '01', 'ิ': '00100', 'ี': '00', 'ึ': '00110',
'ื': '0011', 'ุ': '00101', 'ู': '1110', 'เ': '0', 'แ': '0101',
'ไ': '01001', 'โ': '111', 'ำ': '00010', '่': '001', '้': '0001',
'๊': '11000', '๋': '01010', 'ั': '01101', '็': '11100', '์': '11001',
'ๆ': '10111', 'ฯ': '11010'
}
};
const getCharacters = (options) => (Object.assign(Object.assign({}, baseCharacters), { '0': baseCharacters[options.priority], '1': Object.assign(Object.assign({}, baseCharacters['1']), { [options.separator]: options.space }) }));
const getMappedCharacters = (options, usePriority) => {
const mapped = {};
const characters = getCharacters(options);
for (const set in characters) {
mapped[set] = {};
for (const key in characters[set]) {
mapped[set][key] = characters[set][key].replace(/0/g, options.dot).replace(/1/g, options.dash);
}
}
if (!usePriority) {
delete mapped['0'];
}
return mapped;
};
const swapCharacters = (options) => {
const swapped = {};
const mappedCharacters = getMappedCharacters(options, true);
for (const set in mappedCharacters) {
for (const key in mappedCharacters[set]) {
if (typeof swapped[mappedCharacters[set][key]] === 'undefined') {
swapped[mappedCharacters[set][key]] = key;
}
}
}
return swapped;
};
const getOptions = (opts = {}) => {
var _a, _b, _c;
const options = Object.assign(Object.assign({}, opts), { dash: opts.dash || '-', dot: opts.dot || '.', space: opts.space || '/', separator: opts.separator || ' ', invalid: opts.invalid || '#', priority: opts.priority || 1, wpm: opts.wpm, unit: opts.unit || 0.08, fwUnit: opts.fwUnit || opts.unit || 0.08, volume: opts.volume || 100, oscillator: Object.assign(Object.assign({}, opts.oscillator), { type: ((_a = opts.oscillator) === null || _a === void 0 ? void 0 : _a.type) || 'sine', frequency: ((_b = opts.oscillator) === null || _b === void 0 ? void 0 : _b.frequency) || 500, onended: ((_c = opts.oscillator) === null || _c === void 0 ? void 0 : _c.onended) || null // event that fires when the tone has stopped playing
}) });
return options;
};
const getGainTimings = (morse, opts, currentTime = 0) => {
const timings = [];
let { unit, fwUnit } = opts;
let time = 0;
if (opts.wpm) {
// wpm mode uses standardised units
unit = fwUnit = 60 / (opts.wpm * 50);
}
timings.push([0, time]);
const tone = (i) => {
timings.push([1 * (opts.volume / 100.0), currentTime + time]);
time += i * unit;
};
const silence = (i) => {
timings.push([0, currentTime + time]);
time += i * unit;
};
const gap = (i) => {
timings.push([0, currentTime + time]);
time += i * fwUnit;
};
for (let i = 0, addSilence = false; i <= morse.length; i++) {
if (morse[i] === opts.space) {
gap(7);
addSilence = false;
}
else if (morse[i] === opts.dot) {
if (addSilence)
silence(1);
else
addSilence = true;
tone(1);
}
else if (morse[i] === opts.dash) {
if (addSilence)
silence(1);
else
addSilence = true;
tone(3);
}
else if ((typeof morse[i + 1] !== 'undefined' && morse[i + 1] !== opts.space) &&
(typeof morse[i - 1] !== 'undefined' && morse[i - 1] !== opts.space)) {
gap(3);
addSilence = false;
}
}
return [timings, time];
};
// Source: https://github.com/mattdiamond/Recorderjs/blob/master/src/recorder.js#L155
const encodeWAV = (sampleRate, samples) => {
const buffer = new ArrayBuffer(44 + samples.length * 2);
const view = new DataView(buffer);
const writeString = (view, offset, string) => {
for (let i = 0; i < string.length; i++) {
view.setUint8(offset + i, string.charCodeAt(i));
}
};
// RIFF identifier
writeString(view, 0, 'RIFF');
// RIFF chunk length
view.setUint32(4, 36 + samples.length * 2, true);
// RIFF type
writeString(view, 8, 'WAVE');
// format chunk identifier
writeString(view, 12, 'fmt ');
// format chunk length
view.setUint32(16, 16, true);
// sample format (raw)
view.setUint16(20, 1, true);
// channel count
view.setUint16(22, 1, true);
// sample rate
view.setUint32(24, sampleRate, true);
// byte rate (sample rate * block align)
view.setUint32(28, sampleRate * 4, true);
// block align (channel count * bytes per sample)
view.setUint16(32, 2, true);
// bits per sample
view.setUint16(34, 16, true);
// data chunk identifier
writeString(view, 36, 'data');
// data chunk length
view.setUint32(40, samples.length * 2, true);
// to PCM
const floatTo16BitPCM = (output, offset, input) => {
for (let i = 0; i < input.length; i++, offset += 2) {
const s = Math.max(-1, Math.min(1, input[i]));
output.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7FFF, true);
}
};
floatTo16BitPCM(view, 44, samples);
return view;
};
const audio = (morse, options) => {
let AudioContextClass = window.AudioContext || window.webkitAudioContext;
let OfflineAudioContextClass = window.OfflineAudioContext || window.webkitOfflineAudioContext;
if (!AudioContextClass || !OfflineAudioContextClass) {
throw new Error('Web Audio API is not supported in this browser');
}
const context = new AudioContextClass();
const [gainValues, totalTime] = getGainTimings(morse, options);
const offlineContext = new OfflineAudioContextClass(1, 44100 * totalTime, 44100);
const oscillator = offlineContext.createOscillator();
const gainNode = offlineContext.createGain();
oscillator.type = options.oscillator.type;
oscillator.frequency.value = options.oscillator.frequency;
gainValues.forEach(([value, time]) => gainNode.gain.setValueAtTime(value, time));
oscillator.connect(gainNode);
gainNode.connect(offlineContext.destination);
let source;
// Render the audio buffer
const render = new Promise((resolve, reject) => {
oscillator.start(0);
offlineContext.startRendering();
offlineContext.oncomplete = (e) => {
try {
source = context.createBufferSource();
source.buffer = e.renderedBuffer;
source.connect(context.destination);
source.onended = options.oscillator.onended;
resolve();
}
catch (err) {
reject(err);
}
};
offlineContext.onerror = (err) => {
reject(err);
};
});
let timeout;
const play = async () => {
await render;
if (context.state === 'suspended') {
await context.resume();
}
source.start(context.currentTime);
timeout = window.setTimeout(() => { stop(); }, totalTime * 1000);
};
const stop = () => {
clearTimeout(timeout);
if (source) {
source.stop(0);
}
};
const getWaveBlob = async () => {
await render;
const waveData = encodeWAV(offlineContext.sampleRate, source.buffer.getChannelData(0));
return new Blob([waveData], { type: 'audio/wav' });
};
const getWaveUrl = async () => {
const audioBlob = await getWaveBlob();
return URL.createObjectURL(audioBlob);
};
const exportWave = async (filename) => {
const waveUrl = await getWaveUrl();
const anchor = document.createElement('a');
anchor.href = waveUrl;
anchor.target = '_blank';
anchor.download = filename || 'morse.wav';
anchor.click();
};
return {
play,
stop,
getWaveBlob,
getWaveUrl,
exportWave,
context,
oscillator,
gainNode
};
};
(((name, root, factory) => {
if (typeof exports === 'object') {
module.exports = factory();
}
else if (typeof define === 'function' && define.amd) {
define(factory);
}
else if (root !== undefined) {
root[name] = factory();
}
})('morse-code-translator', globalThis, () => {
const encode = (text, opts) => {
const options = getOptions(opts);
const characters = getCharacters(options);
return [...text.replace(/\s+/g, options.separator).trim().toLocaleUpperCase()].map(function (character) {
for (const set in characters) {
if (typeof characters[set] !== 'undefined' && typeof characters[set][character] !== 'undefined') {
return characters[set][character];
}
}
return options.invalid;
}).join(options.separator).replace(/0/g, options.dot).replace(/1/g, options.dash);
};
const decode = (morse, opts) => {
const options = getOptions(opts);
const swapped = swapCharacters(options);
return morse.replace(/\s+/g, options.separator).trim().split(options.separator).map(function (characters) {
if (typeof swapped[characters] !== 'undefined') {
return swapped[characters];
}
return options.invalid;
}).join('');
};
const characters = (options, usePriority) => getMappedCharacters(getOptions(options), usePriority);
const audio$1 = (text, opts, morseString) => {
const morse = morseString || encode(text, opts);
const options = getOptions(opts);
return audio(morse, options);
};
return {
characters,
decode,
encode,
audio: audio$1
};
}));