-
Notifications
You must be signed in to change notification settings - Fork 0
/
morse-pro-keyer.js
148 lines (140 loc) Β· 5.67 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
/*!
This code is Β© Copyright Stephen C. Phillips, 2019-2023.
Email: steve@morsecode.world
*/
/*
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.
*/
/**
* 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);
* };
* decoder = new MorseDecoder({wpm:20, messageCallback});
* player = new MorsePlayerWAA({frequency:550});
* keyer = new MorseKeyer({keyCallback, decoder, player});
*/
export default class MorseKeyer {
/**
* @param {Object} params - optional parameters.
* @param {function(): number} params.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 {MorseDecoder} params.decoder - Configured MorseDecoder.
* @param {MorsePlayerWAA} params.player - Configured MorsePlayerWAA.
*/
constructor({keyCallback, decoder, player}) {
this.keyCallback = keyCallback;
this.player = player;
this.decoder = decoder;
this.decoder.noiseThreshold = 0;
this.ditLen = this.decoder.lengths['.']; // duration of dit in ms
this.fditLen = this.ditLen; // TODO: finish fwpm bit
this._state = { playing: false };
}
/**
* @access private
*/
_check() {
let key = this.keyCallback();
let ditOrDah = this._ditOrDah(key);
let beepLen; // length of beep
let silenceLen; // length of silence
let 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.play({
timings: [duration]
});
}
}