cw
Version:
A comprehensive Morse Code (CW) library
164 lines (159 loc) • 4.69 kB
JavaScript
/**
* Morse Code Timing Utilities
*
* This module provides functions for calculating Morse code timing parameters.
*
* Farnsworth Timing:
* Farnsworth timing is a method of sending Morse code where the individual characters
* are sent at a higher speed (wpm), but the spacing between characters and words is
* increased to achieve a lower effective speed (fwpm). This technique was developed by
* Russ Farnsworth (W6TTB) to help students learn Morse code more effectively.
*
* The idea behind Farnsworth timing is that learners can become accustomed to hearing
* and recognizing characters at higher speeds, while the extended spacing gives them
* more time to process each character before the next one arrives. As proficiency
* increases, the spacing can be gradually reduced until standard timing is achieved.
*
* In standard Morse timing, characters and spaces are proportional to the dit length.
* In Farnsworth timing, the characters maintain the same internal timing as they would
* at the higher speed, but the spaces between characters and words are extended.
*/
/**
* Calculate the dit length in seconds based on words per minute
* @param wpm Words per minute
* @returns Dit length in seconds
*/
function tdit(wpm) {
return 60 / (50 * wpm);
}
/**
* Calculate the Farnsworth dit length in seconds
* @param wpm Words per minute
* @param fwpm Farnsworth words per minute (defaults to wpm if not provided)
* @returns Farnsworth dit length in seconds
*/
function tfdit(wpm, fwpm) {
// Ensure fwpm defaults to wpm when undefined or null
const effectiveFwpm = fwpm === undefined ? wpm : fwpm;
return (300 * wpm - 186 * effectiveFwpm) / (95 * wpm * effectiveFwpm);
}
const codes = {
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: "--..",
"1": ".----",
"2": "..---",
"3": "...--",
"4": "....-",
"5": ".....",
"6": "-....",
"7": "--...",
"8": "---..",
"9": "----.",
"0": "-----",
"=": "-...-",
".": ".-.-.-",
",": "--..--",
"/": "-..-.",
"?": "..--..",
};
const DEFAULT_TONE_FREQUENCY = 700;
const DEFAULT_WPM = 20;
const DEFAULT_FWPM = 10;
const START_DELAY = 0.5;
const TRANSITION_PERIOD = 0.002;
const DAH_LENGTH = 3;
const INTER_WORD_SPACING = 3;
const INTRA_WORD_SPACING = 4; // Actually 7, but we always add the inter-word spacing (3) so 7-3=4
/**
* Initialize the Web Audio API context
* @param opts Options for initializing the audio context
* @returns The initialized audio context or undefined if window is not available
*/
function initAudioContext(opts = {}) {
if (typeof window === "undefined")
return;
const tone = opts.tone || DEFAULT_TONE_FREQUENCY;
const actx = new (window.AudioContext || window.webkitAudioContext)();
const osc = actx.createOscillator();
const gain = actx.createGain();
gain.connect(actx.destination);
gain.gain.value = 0;
osc.frequency.value = tone;
osc.connect(gain);
osc.start();
return {
actx,
osc,
gain,
};
}
/**
* Play a word or phrase in Morse code
* @param word The word or phrase to play
* @param opts Options for playback
* @returns The total duration of playback in milliseconds
*/
function play(word, opts = {}) {
const ctx = opts.actx || initAudioContext(opts);
if (!ctx)
return 0;
const { actx, gain } = ctx;
const ct = actx.currentTime;
const wpm = opts.wpm || DEFAULT_WPM;
const fwpm = opts.fwpm || DEFAULT_FWPM;
const td = tdit(wpm);
const tfd = tfdit(wpm, fwpm);
let t = ct + START_DELAY;
for (let i = 0; i < word.length; i++) {
const w = word[i];
if (w === " ") {
t += tfd * INTRA_WORD_SPACING;
continue;
}
const c = codes[w.toUpperCase()];
if (!c)
continue; // Skip characters that don't have a code
for (let j = 0; j < c.length; j++) {
const l = c[j] === "-" ? td * DAH_LENGTH : td;
gain.gain.setTargetAtTime(1, t, TRANSITION_PERIOD);
gain.gain.setTargetAtTime(0, t + l, TRANSITION_PERIOD);
t += l + td;
}
t -= td;
t += tfd * INTER_WORD_SPACING;
}
return t * 1000;
}
const cw = {
tdit,
tfdit,
codes,
initAudioContext,
play
};
export { cw as default };
//# sourceMappingURL=cw.esm.js.map