vail/static/scripts/keyers.mjs

495 lines
8.8 KiB
JavaScript
Raw Normal View History

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.
*/
2022-05-14 18:51:05 -06:00
import * as RoboKeyer from "./robokeyer.mjs"
2021-04-26 14:33:48 -06:00
/** 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
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
/**
2022-05-22 21:37:36 -06:00
* Definition of a transmitter type.
2021-04-26 14:33:48 -06:00
*
2022-05-22 21:37:36 -06:00
* The VailClient class implements this.
2021-04-26 14:33:48 -06:00
*/
2022-05-22 21:37:36 -06:00
class Transmitter {
/** Begin transmitting */
BeginTx() {}
/** End transmitting */
EndTx() {}
}
2021-04-26 14:33:48 -06:00
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 {
/**
2022-05-22 21:37:36 -06:00
* @param {Transmitter} output Transmitter object
2022-05-08 11:33:25 -06:00
*/
2022-05-22 21:37:36 -06:00
constructor(output) {
this.output = output
2022-05-08 11:33:25 -06:00
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() {
2022-05-22 21:37:36 -06:00
this.output.EndTx()
2022-05-08 11:33:25 -06:00
this.txRelays = []
}
2022-05-14 18:51:05 -06:00
/**
* Set the duration of dit.
*
* @param {Duration} d New dit duration
*/
SetDitDuration(d) {
this.ditDuration = d
}
/**
* Clean up all timers, etc.
*/
Release() {}
2022-05-08 11:33:25 -06:00
/**
* 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) {
2022-05-22 21:37:36 -06:00
this.output.BeginTx()
2022-05-08 11:33:25 -06:00
} else {
2022-05-22 21:37:36 -06:00
this.output.EndTx()
2022-05-08 11:33:25 -06:00
}
}
}
/**
* 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() {
2022-05-14 18:51:05 -06:00
return ["Dit", "Key"]
2022-05-08 11:33:25 -06:00
}
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
}
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() {
2022-05-14 18:51:05 -06:00
return ["Dit", "Dah"]
2022-05-08 11:33:25 -06:00
}
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
2022-05-11 20:07:49 -06:00
class IambicBKeyer extends IambicKeyer {
Reset() {
super.Reset()
this.queue = new QSet()
}
Key(key, pressed) {
if (pressed) {
this.queue.add(key)
}
super.Key(key, pressed)
}
nextTx() {
for (let key of [0,1]) {
if (this.keyPressed[key]) {
this.queue.add(key)
}
}
let next = this.queue.shift()
if (next == null) {
return -1
}
return next
}
}
2022-05-14 18:51:05 -06:00
class KeyaheadKeyer extends ElBugKeyer {
Reset() {
super.Reset()
2021-04-26 14:33:48 -06:00
this.queue = []
}
2022-05-14 18:51:05 -06:00
Key(key, pressed) {
if (pressed) {
this.queue.push(key)
2021-04-26 14:33:48 -06:00
}
2022-05-14 18:51:05 -06:00
super.Key(key, pressed)
2021-04-28 14:28:59 -06:00
}
2022-05-14 18:51:05 -06:00
nextTx() {
let next = this.queue.shift()
if (next != null) {
return next
2021-04-26 14:33:48 -06:00
}
2022-05-14 18:51:05 -06:00
return super.nextTx()
2021-04-26 14:33:48 -06:00
}
2022-05-14 18:51:05 -06:00
}
2021-04-26 14:33:48 -06:00
2022-05-14 18:51:05 -06:00
/**
* A dictionary of all available keyers
*/
const Keyers = {
straight: StraightKeyer,
cootie: CootieKeyer,
bug: BugKeyer,
elbug: ElBugKeyer,
singledot: SingleDotKeyer,
ultimatic: UltimaticKeyer,
iambic: IambicKeyer,
iambica: IambicAKeyer,
iambicb: IambicBKeyer,
keyahead: KeyaheadKeyer,
robo: RoboKeyer.Keyer,
2021-04-26 14:33:48 -06:00
}
2022-05-22 21:37:36 -06:00
const Numbers = {
straight: 1,
cootie: 1,
bug: 2,
elbug: 3,
singledot: 4,
ultimatic: 5,
iambic: 6,
iambica: 7,
iambicb: 8,
keyahead: 9,
}
2022-05-11 19:17:08 -06:00
export {
2022-05-22 21:37:36 -06:00
Keyers, Numbers,
2022-05-11 19:17:08 -06:00
}