2022-05-08 11:33:25 -06:00
|
|
|
|
/**
|
|
|
|
|
* A number of keyers.
|
|
|
|
|
*
|
|
|
|
|
* The document "All About Squeeze-Keying" by Karl Fischer, DJ5IL, was
|
|
|
|
|
* absolutely instrumental in correctly (I hope) implementing everything more
|
|
|
|
|
* advanced than the bug keyer.
|
|
|
|
|
*/
|
|
|
|
|
|
2021-04-26 14:33:48 -06:00
|
|
|
|
/** 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
|
2022-05-08 14:49:41 -06:00
|
|
|
|
/** Length of a dit */
|
2021-04-26 14:33:48 -06:00
|
|
|
|
const DIT = 1
|
2022-05-08 14:49:41 -06:00
|
|
|
|
/** Length of a dah */
|
2021-04-26 14:33:48 -06:00
|
|
|
|
const DAH = 3
|
|
|
|
|
|
2022-05-08 11:33:25 -06:00
|
|
|
|
/**
|
|
|
|
|
* A time duration.
|
|
|
|
|
*
|
|
|
|
|
* JavaScript uses milliseconds in most (but not all) places.
|
|
|
|
|
* I've found it helpful to be able to multiply by a unit, so it's clear what's going on.
|
|
|
|
|
*
|
|
|
|
|
* @typedef {number} Duration
|
|
|
|
|
*/
|
|
|
|
|
/** @type {Duration} */
|
|
|
|
|
const Millisecond = 1
|
|
|
|
|
/** @type {Duration} */
|
|
|
|
|
const Second = 1000 * Millisecond
|
|
|
|
|
/** @type {Duration} */
|
|
|
|
|
const Minute = 60 * Second
|
|
|
|
|
/** @type {Duration} */
|
|
|
|
|
const Hour = 60 * Minute
|
|
|
|
|
|
2021-04-26 14:33:48 -06:00
|
|
|
|
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": "--..",
|
|
|
|
|
".": ".-.-.-",
|
|
|
|
|
",": "--..--",
|
|
|
|
|
"?": "..--..",
|
|
|
|
|
"'": ".----.",
|
|
|
|
|
"!": "-.-.--",
|
|
|
|
|
"/": "-..-.",
|
|
|
|
|
"(": "-.--.",
|
|
|
|
|
")": "-.--.-",
|
|
|
|
|
"&": ".-...",
|
|
|
|
|
":": "---...",
|
|
|
|
|
";": "---...",
|
|
|
|
|
"=": "-...-",
|
|
|
|
|
"+": ".-.-.",
|
|
|
|
|
"-": "-....-",
|
|
|
|
|
"_": "--..-.",
|
|
|
|
|
"\"": ".-..-.",
|
|
|
|
|
"$": "...-..-",
|
|
|
|
|
"@": ".--.-.",
|
|
|
|
|
}
|
|
|
|
|
|
2022-04-22 18:14:55 -06:00
|
|
|
|
/**
|
|
|
|
|
* Return the inverse of the input.
|
|
|
|
|
* If you give it dit, it returns dah, and vice-versa.
|
|
|
|
|
*
|
|
|
|
|
* @param ditdah What to invert
|
|
|
|
|
* @returns The inverse of ditdah
|
|
|
|
|
*/
|
2022-05-08 11:33:25 -06:00
|
|
|
|
function not(ditdah) {
|
2022-04-22 18:14:55 -06:00
|
|
|
|
if (ditdah == DIT) {
|
|
|
|
|
return DAH
|
|
|
|
|
}
|
|
|
|
|
return DIT
|
|
|
|
|
}
|
|
|
|
|
|
2022-05-08 14:49:41 -06:00
|
|
|
|
/**
|
|
|
|
|
* Queue Set: A Set you can shift and pop.
|
|
|
|
|
*
|
|
|
|
|
* Performance of this implementation may be bad for large sets.
|
|
|
|
|
*/
|
|
|
|
|
class QSet extends Set {
|
|
|
|
|
shift() {
|
|
|
|
|
let r = [...this].shift()
|
|
|
|
|
this.delete(r)
|
|
|
|
|
return r
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pop() {
|
|
|
|
|
let r = [...this].pop()
|
|
|
|
|
this.delete(r)
|
|
|
|
|
return r
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-26 14:33:48 -06:00
|
|
|
|
/**
|
|
|
|
|
* A callback to start or stop transmission
|
|
|
|
|
*
|
|
|
|
|
* @callback TxControl
|
|
|
|
|
*/
|
|
|
|
|
|
2022-05-08 11:33:25 -06:00
|
|
|
|
/**
|
|
|
|
|
* A straight keyer.
|
|
|
|
|
*
|
|
|
|
|
* This is one or more relays wired in parallel. Each relay has an associated
|
|
|
|
|
* input key. You press any key, and it starts transmitting until all keys are
|
|
|
|
|
* released.
|
|
|
|
|
*/
|
|
|
|
|
class StraightKeyer {
|
|
|
|
|
/**
|
|
|
|
|
* @param {TxControl} beginTxFunc Callback to begin transmitting
|
|
|
|
|
* @param {TxControl} endTxFunc Callback to end transmitting
|
|
|
|
|
*/
|
|
|
|
|
constructor(beginTxFunc, endTxFunc) {
|
|
|
|
|
this.beginTxFunc = beginTxFunc
|
|
|
|
|
this.endTxFunc = endTxFunc
|
|
|
|
|
this.Reset()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Returns a list of names for keys supported by this keyer.
|
|
|
|
|
*
|
|
|
|
|
* @returns {Array.<string>} A list of key names
|
|
|
|
|
*/
|
|
|
|
|
KeyNames() {
|
|
|
|
|
return ["Key"]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Reset state and stop all transmissions.
|
|
|
|
|
*/
|
|
|
|
|
Reset() {
|
|
|
|
|
this.endTxFunc()
|
|
|
|
|
this.txRelays = []
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Returns the state of a single transmit relay.
|
|
|
|
|
*
|
|
|
|
|
* If n is not provided, return the state of all relays wired in parallel.
|
|
|
|
|
*
|
|
|
|
|
* @param {number} n Relay number
|
|
|
|
|
* @returns {bool} True if relay is closed
|
|
|
|
|
*/
|
|
|
|
|
TxClosed(n=null) {
|
|
|
|
|
if (n == null) {
|
|
|
|
|
return this.txRelays.some(Boolean)
|
|
|
|
|
}
|
|
|
|
|
return this.txRelays[n]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Close a transmit relay.
|
|
|
|
|
*
|
|
|
|
|
* In most of these keyers, you have multiple things that can transmit. In
|
|
|
|
|
* the circuit, they'd all be wired together in parallel. We instead keep
|
|
|
|
|
* track of relay state here, and start or stop transmitting based on the
|
|
|
|
|
* logical of of all relays.
|
|
|
|
|
*
|
|
|
|
|
* @param {number} n Relay number
|
|
|
|
|
* @param {bool} closed True if relay should be closed
|
|
|
|
|
*/
|
|
|
|
|
Tx(n, closed) {
|
|
|
|
|
let wasClosed = this.TxClosed()
|
|
|
|
|
this.txRelays[n] = closed
|
|
|
|
|
let nowClosed = this.TxClosed()
|
|
|
|
|
|
|
|
|
|
if (wasClosed != nowClosed) {
|
|
|
|
|
if (nowClosed) {
|
|
|
|
|
this.beginTxFunc()
|
|
|
|
|
} else {
|
|
|
|
|
this.endTxFunc()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* React to a key being pressed.
|
|
|
|
|
*
|
|
|
|
|
* @param {number} key Which key was pressed
|
|
|
|
|
* @param {bool} pressed True if the key was pressed
|
|
|
|
|
*/
|
|
|
|
|
Key(key, pressed) {
|
|
|
|
|
this.Tx(key, pressed)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* A "Cootie" or "Double Speed Key" is just two straight keys in parallel.
|
|
|
|
|
*/
|
|
|
|
|
class CootieKeyer extends StraightKeyer {
|
|
|
|
|
KeyNames() {
|
|
|
|
|
return ["Key", "Key"]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* A Vibroplex "Bug".
|
|
|
|
|
*
|
|
|
|
|
* Left key send dits over and over until you let go.
|
|
|
|
|
* Right key works just like a stright key.
|
|
|
|
|
*/
|
|
|
|
|
class BugKeyer extends StraightKeyer {
|
|
|
|
|
KeyNames() {
|
|
|
|
|
return ["· ", "Key"]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Reset() {
|
|
|
|
|
super.Reset()
|
|
|
|
|
this.SetDitDuration(100 * Millisecond)
|
|
|
|
|
if (this.pulseTimer) {
|
|
|
|
|
clearInterval(this.pulseTimer)
|
|
|
|
|
this.pulseTimer = null
|
|
|
|
|
}
|
2022-05-11 19:17:08 -06:00
|
|
|
|
this.keyPressed = [false, false]
|
2022-05-08 11:33:25 -06:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Set the duration of dit.
|
|
|
|
|
*
|
|
|
|
|
* @param {Duration} d New dit duration
|
|
|
|
|
*/
|
|
|
|
|
SetDitDuration(d) {
|
|
|
|
|
this.ditDuration = d
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Key(key, pressed) {
|
|
|
|
|
this.keyPressed[key] = pressed
|
|
|
|
|
if (key == 0) {
|
|
|
|
|
this.beginPulsing()
|
|
|
|
|
} else {
|
|
|
|
|
super.Key(key, pressed)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Begin a pulse if it hasn't already begun
|
|
|
|
|
*/
|
|
|
|
|
beginPulsing() {
|
|
|
|
|
if (!this.pulseTimer) {
|
|
|
|
|
this.pulse()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pulse() {
|
|
|
|
|
if (this.TxClosed(0)) {
|
|
|
|
|
// If we were transmitting, pause
|
|
|
|
|
this.Tx(0, false)
|
|
|
|
|
} else if (this.keyPressed[0]) {
|
|
|
|
|
// If the key was pressed, transmit
|
|
|
|
|
this.Tx(0, true)
|
|
|
|
|
} else {
|
|
|
|
|
// If the key wasn't pressed, stop pulsing
|
|
|
|
|
this.pulseTimer = null
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
this.pulseTimer = setTimeout(() => this.pulse(), this.ditDuration)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Electronic Bug Keyer
|
|
|
|
|
*
|
|
|
|
|
* Repeats both dits and dahs, ensuring proper pauses.
|
|
|
|
|
*
|
|
|
|
|
* I think the original ElBug Keyers did not have two paddles, so I've taken the
|
|
|
|
|
* liberty of making it so that whatever you pressed last is what gets repeated,
|
|
|
|
|
* similar to a modern computer keyboard.
|
|
|
|
|
*/
|
|
|
|
|
class ElBugKeyer extends BugKeyer {
|
|
|
|
|
KeyNames() {
|
|
|
|
|
return ["· ", "−"]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Reset() {
|
|
|
|
|
super.Reset()
|
2022-05-11 19:17:08 -06:00
|
|
|
|
this.nextRepeat = -1 // What to send next, if we're repeating
|
2022-05-08 11:33:25 -06:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Key(key, pressed) {
|
|
|
|
|
this.keyPressed[key] = pressed
|
|
|
|
|
if (pressed) {
|
2022-05-11 19:17:08 -06:00
|
|
|
|
this.nextRepeat = key
|
2022-05-08 11:33:25 -06:00
|
|
|
|
} else {
|
2022-05-11 19:17:08 -06:00
|
|
|
|
this.nextRepeat = this.keyPressed.findIndex(Boolean)
|
2022-05-08 11:33:25 -06:00
|
|
|
|
}
|
|
|
|
|
this.beginPulsing()
|
|
|
|
|
}
|
|
|
|
|
|
2022-05-08 14:49:41 -06:00
|
|
|
|
/**
|
|
|
|
|
* Computes transmission duration for a given key.
|
|
|
|
|
*
|
|
|
|
|
* @param {number} key Key to calculate
|
|
|
|
|
* @returns {Duration} Duration of transmission
|
|
|
|
|
*/
|
|
|
|
|
keyDuration(key) {
|
|
|
|
|
switch (key) {
|
|
|
|
|
case 0:
|
|
|
|
|
return DIT * this.ditDuration
|
|
|
|
|
case 1:
|
|
|
|
|
return DAH * this.ditDuration
|
|
|
|
|
}
|
|
|
|
|
return 0
|
|
|
|
|
}
|
|
|
|
|
|
2022-05-08 11:33:25 -06:00
|
|
|
|
/**
|
2022-05-11 19:17:08 -06:00
|
|
|
|
* Calculates the key to auto-transmit next.
|
2022-05-08 11:33:25 -06:00
|
|
|
|
*
|
2022-05-11 19:17:08 -06:00
|
|
|
|
* If there is nothing to send, returns -1.
|
2022-05-08 11:33:25 -06:00
|
|
|
|
*
|
2022-05-11 19:17:08 -06:00
|
|
|
|
* @returns {number} Key to transmit
|
2022-05-08 11:33:25 -06:00
|
|
|
|
*/
|
2022-05-11 19:17:08 -06:00
|
|
|
|
nextTx() {
|
2022-05-08 14:49:41 -06:00
|
|
|
|
if (!this.keyPressed.some(Boolean)) {
|
2022-05-11 19:17:08 -06:00
|
|
|
|
return -1
|
2022-05-08 11:33:25 -06:00
|
|
|
|
}
|
2022-05-11 19:17:08 -06:00
|
|
|
|
return this.nextRepeat
|
2022-05-08 11:33:25 -06:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pulse() {
|
|
|
|
|
let nextPulse = 0
|
|
|
|
|
|
2022-05-11 19:17:08 -06:00
|
|
|
|
if (this.TxClosed(0)) {
|
|
|
|
|
// Pause if we're currently transmitting
|
2022-05-08 11:33:25 -06:00
|
|
|
|
nextPulse = this.ditDuration
|
2022-05-08 14:49:41 -06:00
|
|
|
|
this.Tx(0, false)
|
2022-05-11 19:17:08 -06:00
|
|
|
|
} else {
|
|
|
|
|
let next = this.nextTx()
|
|
|
|
|
if (next >= 0) {
|
|
|
|
|
nextPulse = this.keyDuration(next)
|
|
|
|
|
this.Tx(0, true)
|
|
|
|
|
}
|
2022-05-08 11:33:25 -06:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (nextPulse) {
|
|
|
|
|
this.pulseTimer = setTimeout(() => this.pulse(), nextPulse)
|
|
|
|
|
} else {
|
|
|
|
|
this.pulseTimer = null
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2022-05-08 21:52:12 -06:00
|
|
|
|
* Ultimatic Keyer.
|
2022-05-08 11:33:25 -06:00
|
|
|
|
*
|
2022-05-08 21:52:12 -06:00
|
|
|
|
* If you know what an Iambic keyer does, this works similarly, but doesn't go
|
|
|
|
|
* back and forth when both keys are held.
|
2022-05-08 11:33:25 -06:00
|
|
|
|
*/
|
2022-05-08 21:52:12 -06:00
|
|
|
|
class UltimaticKeyer extends ElBugKeyer {
|
2022-05-08 11:33:25 -06:00
|
|
|
|
Reset() {
|
|
|
|
|
super.Reset()
|
2022-05-08 14:49:41 -06:00
|
|
|
|
this.queue = new QSet()
|
2022-05-08 11:33:25 -06:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Key(key, pressed) {
|
2022-05-08 21:52:12 -06:00
|
|
|
|
if (pressed) {
|
|
|
|
|
this.queue.add(key)
|
2022-05-08 11:33:25 -06:00
|
|
|
|
}
|
2022-05-08 14:49:41 -06:00
|
|
|
|
super.Key(key, pressed)
|
2022-05-08 11:33:25 -06:00
|
|
|
|
}
|
|
|
|
|
|
2022-05-11 19:17:08 -06:00
|
|
|
|
nextTx() {
|
2022-05-08 14:49:41 -06:00
|
|
|
|
let key = this.queue.shift()
|
|
|
|
|
if (key != null) {
|
2022-05-11 19:17:08 -06:00
|
|
|
|
return key
|
2022-05-08 11:33:25 -06:00
|
|
|
|
}
|
2022-05-11 19:17:08 -06:00
|
|
|
|
return super.nextTx()
|
2022-05-08 11:33:25 -06:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-05-08 21:52:12 -06:00
|
|
|
|
/**
|
|
|
|
|
* Single dot memory keyer.
|
|
|
|
|
*
|
|
|
|
|
* If you tap dit while a dah is sending, it queues up a dit to send, but
|
|
|
|
|
* reverts back to dah until the dah key is released or the dit key is pressed
|
|
|
|
|
* again. In other words, if the dah is held, it only pay attention to the edge
|
|
|
|
|
* on dit.
|
|
|
|
|
*/
|
|
|
|
|
class SingleDotKeyer extends ElBugKeyer {
|
|
|
|
|
Reset() {
|
|
|
|
|
super.Reset()
|
|
|
|
|
this.queue = new QSet()
|
|
|
|
|
}
|
|
|
|
|
|
2022-05-08 14:49:41 -06:00
|
|
|
|
Key(key, pressed) {
|
2022-05-08 21:52:12 -06:00
|
|
|
|
if (pressed && (key == 0)) {
|
2022-05-11 19:17:08 -06:00
|
|
|
|
this.queue.add(key)
|
2022-05-08 14:49:41 -06:00
|
|
|
|
}
|
|
|
|
|
super.Key(key, pressed)
|
|
|
|
|
}
|
2022-05-08 21:52:12 -06:00
|
|
|
|
|
2022-05-11 19:17:08 -06:00
|
|
|
|
nextTx() {
|
2022-05-08 21:52:12 -06:00
|
|
|
|
let key = this.queue.shift()
|
|
|
|
|
if (key != null) {
|
2022-05-11 19:17:08 -06:00
|
|
|
|
return key
|
2022-05-08 21:52:12 -06:00
|
|
|
|
}
|
|
|
|
|
for (let key of [1, 0]) {
|
|
|
|
|
if (this.keyPressed[key]) {
|
2022-05-11 19:17:08 -06:00
|
|
|
|
return key
|
2022-05-08 21:52:12 -06:00
|
|
|
|
}
|
|
|
|
|
}
|
2022-05-11 19:17:08 -06:00
|
|
|
|
return -1
|
2022-05-08 21:52:12 -06:00
|
|
|
|
}
|
2022-05-11 19:17:08 -06:00
|
|
|
|
}
|
2022-05-08 21:52:12 -06:00
|
|
|
|
|
2022-05-11 19:17:08 -06:00
|
|
|
|
/**
|
|
|
|
|
* "Plain" Iambic keyer.
|
|
|
|
|
*/
|
|
|
|
|
class IambicKeyer extends ElBugKeyer {
|
|
|
|
|
nextTx() {
|
|
|
|
|
let next = super.nextTx()
|
|
|
|
|
if (this.keyPressed.every(Boolean)) {
|
|
|
|
|
this.nextRepeat = 1 - this.nextRepeat
|
|
|
|
|
}
|
|
|
|
|
return next
|
|
|
|
|
}
|
2022-05-08 14:49:41 -06:00
|
|
|
|
}
|
|
|
|
|
|
2022-05-11 19:17:08 -06:00
|
|
|
|
class IambicAKeyer extends IambicKeyer {
|
|
|
|
|
Reset() {
|
|
|
|
|
super.Reset()
|
|
|
|
|
this.queue = new QSet()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Key(key, pressed) {
|
|
|
|
|
if (pressed && (key == 0)) {
|
|
|
|
|
this.queue.add(key)
|
|
|
|
|
}
|
|
|
|
|
super.Key(key, pressed)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
nextTx() {
|
|
|
|
|
let next = super.nextTx()
|
|
|
|
|
let key = this.queue.shift()
|
|
|
|
|
if (key != null) {
|
|
|
|
|
return key
|
|
|
|
|
}
|
|
|
|
|
return next
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-05-08 21:52:12 -06:00
|
|
|
|
|
2021-04-26 14:33:48 -06:00
|
|
|
|
/**
|
2021-04-27 17:30:16 -06:00
|
|
|
|
* Keyer class. This handles iambic and straight key input.
|
2021-04-26 14:33:48 -06:00
|
|
|
|
*
|
|
|
|
|
* This will handle the following things that people appear to want with iambic input:
|
|
|
|
|
*
|
|
|
|
|
* - Typematic: you hold the key down and it repeats evenly-spaced tones
|
|
|
|
|
* - Typeahead: if you hit a key while it's still transmitting the last-entered one, it queues up your next entered one
|
|
|
|
|
*/
|
2022-05-08 11:33:25 -06:00
|
|
|
|
class OldKeyer {
|
2021-04-26 14:33:48 -06:00
|
|
|
|
/**
|
2022-04-19 22:41:09 -06:00
|
|
|
|
* Create a Keyer
|
2021-04-26 14:33:48 -06:00
|
|
|
|
*
|
2021-04-27 18:37:25 -06:00
|
|
|
|
* @param {TxControl} beginTxFunc Callback to begin transmitting
|
|
|
|
|
* @param {TxControl} endTxFunc Callback to end transmitting
|
2021-04-26 14:33:48 -06:00
|
|
|
|
* @param {number} intervalDuration Dit duration (milliseconds)
|
2021-04-27 18:37:25 -06:00
|
|
|
|
* @param {number} pauseMultiplier How long to stretch out inter-letter and inter-word pauses
|
2021-04-26 14:33:48 -06:00
|
|
|
|
*/
|
2021-04-26 17:55:46 -06:00
|
|
|
|
constructor(beginTxFunc, endTxFunc, {intervalDuration=100, pauseMultiplier=1}={}) {
|
2021-04-26 14:33:48 -06:00
|
|
|
|
this.beginTxFunc = beginTxFunc
|
|
|
|
|
this.endTxFunc = endTxFunc
|
|
|
|
|
this.intervalDuration = intervalDuration
|
2021-04-26 17:55:46 -06:00
|
|
|
|
this.pauseMultiplier = pauseMultiplier
|
2021-04-26 14:33:48 -06:00
|
|
|
|
this.ditDown = false
|
|
|
|
|
this.dahDown = false
|
2022-04-19 22:41:09 -06:00
|
|
|
|
this.typeahead = false
|
|
|
|
|
this.iambicModeB = true
|
2021-04-26 14:33:48 -06:00
|
|
|
|
this.last = null
|
|
|
|
|
this.queue = []
|
|
|
|
|
this.pulseTimer = null
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pulse() {
|
|
|
|
|
if (this.queue.length == 0) {
|
|
|
|
|
let next = this.typematic()
|
|
|
|
|
if (next) {
|
|
|
|
|
// Barkeep! Another round!
|
|
|
|
|
this.Enqueue(next)
|
|
|
|
|
} else {
|
|
|
|
|
// Nothing left on the queue, stop the machine
|
|
|
|
|
this.pulseTimer = null
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let next = this.queue.shift()
|
|
|
|
|
if (next < 0) {
|
2021-04-26 17:55:46 -06:00
|
|
|
|
next *= -1
|
|
|
|
|
if (next > 1) {
|
|
|
|
|
// Don't adjust spacing within a letter
|
|
|
|
|
next *= this.pauseMultiplier
|
2022-04-21 22:05:51 -06:00
|
|
|
|
} else {
|
|
|
|
|
this.endTxFunc()
|
2022-04-24 17:13:56 -06:00
|
|
|
|
if (this.txChart) {
|
|
|
|
|
this.txChart.Add(Date.now(), 0)
|
|
|
|
|
}
|
2021-04-26 17:55:46 -06:00
|
|
|
|
}
|
2021-04-26 14:33:48 -06:00
|
|
|
|
} else {
|
|
|
|
|
this.last = next
|
|
|
|
|
this.beginTxFunc()
|
2022-04-24 17:13:56 -06:00
|
|
|
|
if (this.txChart) {
|
|
|
|
|
this.txChart.Add(Date.now(), 1)
|
|
|
|
|
}
|
2021-04-26 14:33:48 -06:00
|
|
|
|
}
|
|
|
|
|
this.pulseTimer = setTimeout(() => this.pulse(), next * this.intervalDuration)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
maybePulse() {
|
|
|
|
|
// If there's no timer running right now, restart the pulse
|
|
|
|
|
if (!this.pulseTimer) {
|
|
|
|
|
this.pulse()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
typematic() {
|
|
|
|
|
if (this.ditDown && this.dahDown) {
|
2022-04-22 18:14:55 -06:00
|
|
|
|
this.modeBQueue = this.last
|
2022-05-08 11:33:25 -06:00
|
|
|
|
this.last = not(this.last)
|
2021-04-26 14:33:48 -06:00
|
|
|
|
} else if (this.ditDown) {
|
2022-04-22 18:14:55 -06:00
|
|
|
|
this.modeBQueue = null
|
2021-04-26 14:33:48 -06:00
|
|
|
|
this.last = DIT
|
|
|
|
|
} else if (this.dahDown) {
|
2022-04-22 18:14:55 -06:00
|
|
|
|
this.modeBQueue = null
|
2021-04-26 14:33:48 -06:00
|
|
|
|
this.last = DAH
|
2022-04-22 18:14:55 -06:00
|
|
|
|
} else if (this.modeBQueue && this.iambicModeB) {
|
|
|
|
|
this.last = this.modeBQueue
|
|
|
|
|
this.modeBQueue = null
|
2021-04-26 14:33:48 -06:00
|
|
|
|
} else {
|
|
|
|
|
this.last = null
|
2022-04-22 18:14:55 -06:00
|
|
|
|
this.modeBQueue = null
|
2021-04-26 14:33:48 -06:00
|
|
|
|
}
|
|
|
|
|
return this.last
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-26 17:55:46 -06:00
|
|
|
|
/**
|
|
|
|
|
* Return true if we are currently playing out something
|
|
|
|
|
*/
|
|
|
|
|
Busy() {
|
|
|
|
|
return this.pulseTimer
|
|
|
|
|
}
|
2021-04-27 17:30:16 -06:00
|
|
|
|
|
2021-04-26 14:33:48 -06:00
|
|
|
|
/**
|
|
|
|
|
* Set a new dit interval (transmission rate)
|
|
|
|
|
*
|
|
|
|
|
* @param {number} duration Dit duration (milliseconds)
|
|
|
|
|
*/
|
|
|
|
|
SetIntervalDuration(duration) {
|
|
|
|
|
this.intervalDuration = duration
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-26 17:55:46 -06:00
|
|
|
|
/**
|
|
|
|
|
* 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
|
|
|
|
|
}
|
|
|
|
|
|
2022-04-19 22:41:09 -06:00
|
|
|
|
/**
|
|
|
|
|
* Set Iambic mode B.
|
|
|
|
|
*
|
2022-04-22 18:14:55 -06:00
|
|
|
|
* Near as I can tell, B sends one more tone than was entered, when
|
|
|
|
|
* both keys are held down.
|
|
|
|
|
* This logic happens in the typematic code.
|
|
|
|
|
*
|
|
|
|
|
* ▁▁▔▔▔▔▔▔▔▁▁▁▁ Dit key
|
|
|
|
|
*
|
|
|
|
|
* ▁▔▔▔▔▔▔▔▔▁▁▁▁ Dah key
|
|
|
|
|
*
|
|
|
|
|
* ▁▔▔▔▁▔▁▔▔▔▁▁▁ Mode A output
|
|
|
|
|
*
|
|
|
|
|
* ▁▔▔▔▁▔▁▔▔▔▁▔▁ Mode B output
|
2022-04-19 22:41:09 -06:00
|
|
|
|
*
|
|
|
|
|
* @param {boolean} value True to set mode to B
|
|
|
|
|
*/
|
|
|
|
|
SetIambicModeB(value) {
|
|
|
|
|
this.iambicModeB = Boolean(value)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Enable/disable typeahead.
|
|
|
|
|
*
|
|
|
|
|
* Typeahead maintains a key buffer, so you can key in dits and dahs faster than the
|
|
|
|
|
* Iambic keyer can play them out.
|
|
|
|
|
*
|
|
|
|
|
* Some people apparently expect this behavior, and have trouble if it isn't enabled.
|
|
|
|
|
* For others, having this enabled makes it feel like they have a "phantom keyer"
|
|
|
|
|
* entering keys they did not send.
|
|
|
|
|
*
|
|
|
|
|
* @param value True to enable typeahead
|
|
|
|
|
*/
|
|
|
|
|
SetTypeahead(value) {
|
|
|
|
|
this.typeahead = value
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-28 14:28:59 -06:00
|
|
|
|
/**
|
|
|
|
|
* Delete anything left on the queue.
|
|
|
|
|
*/
|
|
|
|
|
Flush() {
|
|
|
|
|
this.queue.splice(0)
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-26 14:33:48 -06:00
|
|
|
|
/**
|
|
|
|
|
* 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()
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-27 18:37:25 -06:00
|
|
|
|
/**
|
|
|
|
|
* Enqueue a morse code string (eg "... --- ...")
|
|
|
|
|
*
|
|
|
|
|
* @param {string} ms String to enqueue
|
|
|
|
|
*/
|
2021-04-26 14:33:48 -06:00
|
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-27 18:37:25 -06:00
|
|
|
|
/**
|
|
|
|
|
* Enqueue an ASCII string (eg "SOS help")
|
|
|
|
|
*
|
|
|
|
|
* @param {string} s String to enqueue
|
|
|
|
|
*/
|
2021-04-26 17:55:46 -06:00
|
|
|
|
EnqueueAsciiString(s, {pauseLetter = PAUSE_LETTER, pauseWord = PAUSE_WORD} = {}) {
|
2021-04-26 14:33:48 -06:00
|
|
|
|
for (let c of s.toLowerCase()) {
|
|
|
|
|
let m = MorseMap[c]
|
|
|
|
|
if (m) {
|
|
|
|
|
this.EnqueueMorseString(m)
|
2021-04-26 17:55:46 -06:00
|
|
|
|
this.Enqueue(pauseLetter)
|
2021-04-26 14:33:48 -06:00
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
switch (c) {
|
|
|
|
|
case " ":
|
|
|
|
|
case "\n":
|
|
|
|
|
case "\t":
|
2021-04-26 17:55:46 -06:00
|
|
|
|
this.Enqueue(pauseWord)
|
2021-04-26 14:33:48 -06:00
|
|
|
|
break
|
|
|
|
|
default:
|
|
|
|
|
console.warn("Unable to encode '" + c + "'!")
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2021-04-27 17:30:16 -06:00
|
|
|
|
* Do something to the straight key
|
|
|
|
|
*
|
|
|
|
|
* @param down True if key was pressed
|
|
|
|
|
*/
|
|
|
|
|
Straight(down) {
|
|
|
|
|
if (down) {
|
|
|
|
|
this.beginTxFunc()
|
|
|
|
|
} else {
|
|
|
|
|
this.endTxFunc()
|
2021-04-26 14:33:48 -06:00
|
|
|
|
}
|
2021-04-27 17:30:16 -06:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Do something to the dit key
|
|
|
|
|
*
|
|
|
|
|
* @param down True if key was pressed
|
|
|
|
|
*/
|
|
|
|
|
Dit(down) {
|
|
|
|
|
this.ditDown = down
|
2022-04-23 21:23:05 -06:00
|
|
|
|
if (down) {
|
|
|
|
|
if (this.typeahead
|
|
|
|
|
|| !this.Busy()
|
|
|
|
|
|| (this.iambicModeB && (this.last == DAH))) {
|
|
|
|
|
this.Enqueue(DIT)
|
|
|
|
|
}
|
2021-04-27 17:30:16 -06:00
|
|
|
|
}
|
|
|
|
|
}
|
2021-04-26 14:33:48 -06:00
|
|
|
|
|
2021-04-27 17:30:16 -06:00
|
|
|
|
/**
|
|
|
|
|
* Do something to the dah key
|
|
|
|
|
*
|
|
|
|
|
* @param down True if key was pressed
|
|
|
|
|
*/
|
|
|
|
|
Dah(down) {
|
|
|
|
|
this.dahDown = down
|
2022-04-23 21:23:05 -06:00
|
|
|
|
if (down) {
|
|
|
|
|
if (this.typeahead
|
|
|
|
|
|| !this.Busy()
|
|
|
|
|
|| (this.iambicModeB && (this.last == DIT))) {
|
|
|
|
|
this.Enqueue(DAH)
|
|
|
|
|
}
|
2021-04-26 14:33:48 -06:00
|
|
|
|
}
|
2021-04-27 17:30:16 -06:00
|
|
|
|
}
|
2021-04-26 14:33:48 -06:00
|
|
|
|
}
|
|
|
|
|
|
2022-05-11 19:17:08 -06:00
|
|
|
|
export {
|
|
|
|
|
StraightKeyer,
|
|
|
|
|
CootieKeyer, BugKeyer, ElBugKeyer,
|
|
|
|
|
SingleDotKeyer, UltimaticKeyer,
|
|
|
|
|
IambicKeyer, IambicAKeyer,
|
|
|
|
|
}
|