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}