vail

Internet morse code repeater
git clone https://git.woozle.org/neale/vail.git

vail / static / scripts
Neale Pickett  ·  2022-06-06

robokeyer.mjs

  1/** Silent period between words */
  2const PAUSE_WORD = -7
  3/** Silent period between letters */
  4const PAUSE_LETTER = -3
  5/** Silent period between dits and dash */
  6const PAUSE = -1
  7/** Length of a dit */
  8const DIT = 1
  9/** Length of a dah */
 10const DAH = 3
 11
 12const MorseMap = {
 13    "\x04": ".-.-.", // End Of Transmission
 14    "\x18": "........", // Cancel
 15    "0": "-----",
 16    "1": ".----",
 17    "2": "..---",
 18    "3": "...--",
 19    "4": "....-",
 20    "5": ".....",
 21    "6": "-....",
 22    "7": "--...",
 23    "8": "---..",
 24    "9": "----.",
 25    "a": ".-",
 26    "b": "-...",
 27    "c": "-.-.",
 28    "d": "-..",
 29    "e": ".",
 30    "f": "..-.",
 31    "g": "--.",
 32    "h": "....",
 33    "i": "..",
 34    "j": ".---",
 35    "k": "-.-",
 36    "l": ".-..",
 37    "m": "--",
 38    "n": "-.",
 39    "o": "---",
 40    "p": ".--.",
 41    "q": "--.-",
 42    "r": ".-.",
 43    "s": "...",
 44    "t": "-",
 45    "u": "..-",
 46    "v": "...-",
 47    "w": ".--",
 48    "x": "-..-",
 49    "y": "-.--",
 50    "z": "--..",
 51    ".": ".-.-.-",
 52    ",": "--..--",
 53	"?": "..--..",
 54	"'": ".----.",
 55    "!": "-.-.--",
 56	"/": "-..-.",
 57    "(": "-.--.",
 58	")": "-.--.-",
 59	"&": ".-...",
 60	":": "---...",
 61	";": "---...",
 62	"=": "-...-",
 63	"+": ".-.-.",
 64	"-": "-....-",
 65	"_": "--..-.",
 66	"\"": ".-..-.",
 67	"$": "...-..-",
 68    "@": ".--.-.",
 69}
 70
 71/**
 72 * Robo Keyer. It sends morse code so you don't have to!
 73 */
 74 class Keyer {
 75	/**
 76	 * Create a Keyer
 77	 * 
 78	 * @param {TxControl} beginTxFunc Callback to begin transmitting
 79	 * @param {TxControl} endTxFunc Callback to end transmitting
 80	 */
 81	constructor(beginTxFunc, endTxFunc) {
 82		this.beginTxFunc = beginTxFunc
 83		this.endTxFunc = endTxFunc
 84		this.ditDuration = 100
 85		this.pauseMultiplier = 1
 86		this.queue = []
 87		this.pulseTimer = null
 88	}
 89
 90	pulse() {
 91        let next = this.queue.shift()
 92
 93        if (next == null) {
 94            // Nothing left on the queue, stop the machine
 95            this.pulseTimer = null
 96            return
 97        }
 98
 99		if (next < 0) {
100			next *= -1
101			if (next > 1) {
102				// Don't adjust spacing within a letter
103				next *= this.pauseMultiplier
104			} else {
105				this.endTxFunc()
106			}
107		} else {
108			this.beginTxFunc()
109		}
110		this.pulseTimer = setTimeout(() => this.pulse(), next * this.ditDuration)
111	}
112
113	maybePulse() {
114		// If there's no timer running right now, restart the pulse
115		if (!this.pulseTimer) {
116			this.pulse()
117		}
118	}
119
120	/**
121	 * Return true if we are currently playing out something
122	 */
123	Busy() {
124		return Boolean(this.pulseTimer)
125	}
126
127	/**
128	  * Set a new dit interval (transmission rate)
129	  *
130	  * @param {number} duration Dit duration (milliseconds)
131	  */
132	SetDitDuration(duration) {
133		this.ditDuration = duration
134	}
135
136	/**
137	 * Set a new pause multiplier.
138	 * 
139	 * This slows down the inter-letter and inter-word pauses,
140	 * which can aid in learning.
141	 * 
142	 * @param {number} multiplier Pause multiplier
143	 */
144	SetPauseMultiplier(multiplier) {
145		this.pauseMultiplier = multiplier
146	}
147	
148	/**
149	 * Delete anything left on the queue.
150	 */
151	Flush() {
152		this.queue.splice(0)
153	}
154
155	/**
156	 * Add to the output queue, and start processing the queue if it's not currently being processed.
157	 * 
158	 * @param {number} key A duration, in dits. Negative durations are silent.
159	 */
160	Enqueue(key) {
161		this.queue.push(key)
162		if (key > 0) {
163			this.queue.push(PAUSE)
164		}
165		this.maybePulse()
166	}
167
168	/**
169	 * Enqueue a morse code string (eg "... --- ...")
170	 * 
171	 * @param {string} ms String to enqueue
172	 */
173    EnqueueMorseString(ms) {
174        for (let mc of ms) {
175            switch (mc) {
176                case ".":
177                    this.Enqueue(DIT)
178                    break
179                case "-":
180                    this.Enqueue(DAH)
181                    break
182                case " ":
183                    this.Enqueue(PAUSE_LETTER)
184                    break
185            }
186        }
187    }
188
189	/**
190	 * Enqueue an ASCII string (eg "SOS help")
191	 * 
192	 * @param {string} s String to enqueue
193	 */
194    EnqueueAsciiString(s, {pauseLetter = PAUSE_LETTER, pauseWord = PAUSE_WORD} = {}) {
195        for (let c of s.toLowerCase()) {
196            let m = MorseMap[c]
197            if (m) {
198                this.EnqueueMorseString(m)
199                this.Enqueue(pauseLetter)
200                continue
201            }
202
203            switch (c) {
204                case " ":
205				case "\n":
206				case "\t":
207                    this.Enqueue(pauseWord)
208                    break
209                default:
210                    console.warn("Unable to encode '" + c + "'!")
211                    break
212            }
213        }
214    }
215}
216
217export {Keyer}