This repository has been archived by the owner on Nov 20, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 31
/
morse-pro-keyer.js
154 lines (144 loc) · 6 KB
/
morse-pro-keyer.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
/*!
This code is © Copyright Stephen C. Phillips, 2018.
Email: steve@scphillips.com
*/
/*
Licensed under the EUPL, Version 1.2 or – as soon they will be approved by the European Commission - subsequent versions of the EUPL (the "Licence");
You may not use this work except in compliance with the Licence.
You may obtain a copy of the Licence at: https://joinup.ec.europa.eu/community/eupl/
Unless required by applicable law or agreed to in writing, software distributed under the Licence is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the Licence for the specific language governing permissions and limitations under the Licence.
*/
import * as WPM from './morse-pro-wpm';
import MorseDecoder from './morse-pro-decoder';
import MorsePlayerWAA from './morse-pro-player-waa';
/**
* The Morse keyer tests for input on a timer, plays the appropriate tone and passes the data to a decoder.
*
* @example
* var ditKey = 90; // Z
* var dahKey = 88; // X
* window.onkeyup = function(e) {
* if (e.keyCode === ditKey) { dit = false; }
* if (e.keyCode === dahKey) { dah = false; }
* };
* window.onkeydown = function(e) {
* var wasMiddle = !dit & !dah;
* if (e.keyCode === ditKey) { dit = true; }
* if (e.keyCode === dahKey) { dah = true; }
* if (wasMiddle & (dit | dah)) { keyer.start(); }
* };
* var keyCallback = function() {
* return ((dit === true) * 1) + ((dah === true) * 2);
* };
* var messageCallback = function(d) {
* console.log(d.message);
* };
* keyer = new MorseKeyer(keyCallback, 20, 20, 550, messageCallback);
*/
export default class MorseKeyer {
/**
* @param {function(): number} keyCallback - A function which should return 0, 1, 2, or 3 from the vitual "paddle" depending if nothing, a dit, a dah or both is detected. This implementation will play dits if both keys are detected.
* @param {number} [wpm=20] - Speed of the keyer.
* @param {number} [fwpm=wpm] - Farnsworth speed of the keyer.
* @param {number} [frequency=550] - The frequency in Hz for the sidetone.
* @param {function()} messageCallback - A function called with {message: string, timings: number[], morse: string} for each decoded part (see MorseDecoder). Its use here will result in a single character being returned each time.
*/
constructor(keyCallback, wpm = 20, fwpm = wpm, frequency = 550, messageCallback = undefined) {
this.keyCallback = keyCallback;
this.wpm = wpm;
this.fwpm = fwpm;
this.player = new MorsePlayerWAA();
this.player.frequency = frequency;
this.decoder = new MorseDecoder(this.wpm, this.fwpm, messageCallback);
this.decoder.noiseThreshold = 0;
this.ditLen = WPM.ditLength(wpm); // duration of dit in ms
this.fditLen = WPM.fditLength(wpm, fwpm); // TODO: finish fwpm bit
this._state = { playing: false };
}
/**
* @access: private
*/
_check() {
var key = this.keyCallback();
var ditOrDah = this._ditOrDah(key);
var beepLen; // length of beep
var silenceLen; // length of silence
var now = (new Date()).getTime();
if (this._state.lastTime !== undefined) {
this.decoder.addTiming(this._state.lastTime - now); // add how long since we've last been here as silence
}
if (ditOrDah === undefined) {
// If no keypress is detected then continue pushing chunks of silence to the decoder to complete the character and add a space
beepLen = 0;
this._state.playing = false; // make it interupterable: means that a new char can start whenever
switch (this._state.spaceCounter) {
case 0:
// we've already waited 1 ditLen, need to make it 1 fditLen plus 2 more
silenceLen = (this.fditLen - this.ditLen) + (2 * this.fditLen);
break;
case 1:
silenceLen = (4 * this.fditLen);
break;
case 2:
silenceLen = 0;
this.stop();
break;
}
this._state.spaceCounter++;
} else {
this._state.spaceCounter = 0;
beepLen = (ditOrDah ? 1 : 3) * this.ditLen;
this._playTone(beepLen);
this.decoder.addTiming(beepLen);
silenceLen = this.ditLen; // while playing, assume we are inside a char and so wait 1 ditLen
}
this._state.lastTime = now + beepLen;
if (beepLen + silenceLen) this.timer = setTimeout(this._check.bind(this), beepLen + silenceLen); // check key state again after the dit or dah and after a dit-space
}
/**
* Translate key input into whether to play nothing, dit, or dah
* @returns undefined, true or false for nothing, dit or dah
* @access: private
*/
_ditOrDah(input) {
if (input & 1) {
return true;
} else if (input === 2) {
return false;
} else {
return undefined;
}
}
/**
* Call this method when an initial key-press (or equivalent) is detected.
*/
start() {
if (this._state.playing) {
// If the keyer is already playing then ignore a new start.
return;
} else {
this._state.playing = true;
this._state.spaceCounter = 0;
this._state.lastTime = undefined; // removes extended pauses
clearTimeout(this.timer);
this._check();
}
}
/**
* This method can be called externally to stop the keyer but is also used internally when no key-press is detected.
*/
stop() {
this._state.playing = false;
clearTimeout(this.timer);
}
/**
* Play a dit or dah sidetone.
* @param {number} duration - number of milliseconds to play
* @access: private
*/
_playTone(duration) {
this.player.load([duration]);
this.player.playFromStart();
}
}