vail/static/scripts/robokeyer.mjs

218 lines
4.4 KiB
JavaScript

/** Silent period between words */
const PAUSE_WORD = -7
/** Silent period between letters */
const PAUSE_LETTER = -3
/** Silent period between dits and dash */
const PAUSE = -1
/** Length of a dit */
const DIT = 1
/** Length of a dah */
const DAH = 3
const MorseMap = {
"\x04": ".-.-.", // End Of Transmission
"\x18": "........", // Cancel
"0": "-----",
"1": ".----",
"2": "..---",
"3": "...--",
"4": "....-",
"5": ".....",
"6": "-....",
"7": "--...",
"8": "---..",
"9": "----.",
"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": "--..",
".": ".-.-.-",
",": "--..--",
"?": "..--..",
"'": ".----.",
"!": "-.-.--",
"/": "-..-.",
"(": "-.--.",
")": "-.--.-",
"&": ".-...",
":": "---...",
";": "---...",
"=": "-...-",
"+": ".-.-.",
"-": "-....-",
"_": "--..-.",
"\"": ".-..-.",
"$": "...-..-",
"@": ".--.-.",
}
/**
* Robo Keyer. It sends morse code so you don't have to!
*/
class Keyer {
/**
* Create a Keyer
*
* @param {TxControl} beginTxFunc Callback to begin transmitting
* @param {TxControl} endTxFunc Callback to end transmitting
*/
constructor(beginTxFunc, endTxFunc) {
this.beginTxFunc = beginTxFunc
this.endTxFunc = endTxFunc
this.ditDuration = 100
this.pauseMultiplier = 1
this.queue = []
this.pulseTimer = null
}
pulse() {
let next = this.queue.shift()
if (next == null) {
// Nothing left on the queue, stop the machine
this.pulseTimer = null
return
}
if (next < 0) {
next *= -1
if (next > 1) {
// Don't adjust spacing within a letter
next *= this.pauseMultiplier
} else {
this.endTxFunc()
}
} else {
this.beginTxFunc()
}
this.pulseTimer = setTimeout(() => this.pulse(), next * this.ditDuration)
}
maybePulse() {
// If there's no timer running right now, restart the pulse
if (!this.pulseTimer) {
this.pulse()
}
}
/**
* Return true if we are currently playing out something
*/
Busy() {
return Boolean(this.pulseTimer)
}
/**
* Set a new dit interval (transmission rate)
*
* @param {number} duration Dit duration (milliseconds)
*/
SetDitDuration(duration) {
this.ditDuration = duration
}
/**
* Set a new pause multiplier.
*
* This slows down the inter-letter and inter-word pauses,
* which can aid in learning.
*
* @param {number} multiplier Pause multiplier
*/
SetPauseMultiplier(multiplier) {
this.pauseMultiplier = multiplier
}
/**
* Delete anything left on the queue.
*/
Flush() {
this.queue.splice(0)
}
/**
* Add to the output queue, and start processing the queue if it's not currently being processed.
*
* @param {number} key A duration, in dits. Negative durations are silent.
*/
Enqueue(key) {
this.queue.push(key)
if (key > 0) {
this.queue.push(PAUSE)
}
this.maybePulse()
}
/**
* Enqueue a morse code string (eg "... --- ...")
*
* @param {string} ms String to enqueue
*/
EnqueueMorseString(ms) {
for (let mc of ms) {
switch (mc) {
case ".":
this.Enqueue(DIT)
break
case "-":
this.Enqueue(DAH)
break
case " ":
this.Enqueue(PAUSE_LETTER)
break
}
}
}
/**
* Enqueue an ASCII string (eg "SOS help")
*
* @param {string} s String to enqueue
*/
EnqueueAsciiString(s, {pauseLetter = PAUSE_LETTER, pauseWord = PAUSE_WORD} = {}) {
for (let c of s.toLowerCase()) {
let m = MorseMap[c]
if (m) {
this.EnqueueMorseString(m)
this.Enqueue(pauseLetter)
continue
}
switch (c) {
case " ":
case "\n":
case "\t":
this.Enqueue(pauseWord)
break
default:
console.warn("Unable to encode '" + c + "'!")
break
}
}
}
}
export {Keyer}