-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathkeyfull.html
506 lines (420 loc) · 16.5 KB
/
keyfull.html
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
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>CW Keyer</title>
</head>
<body>
<h2>Press keys to generate CW messages</h2>
<p>Press <b>Escape</b> to send the message.</p>
<script>
// Initialize variables
let keydown = 0;
let dtime = 0;
let utime = 0;
let cwmsg = "";
let practiceMode = false;
//const sidetone = new Audio("https://github.com/hcarter333/pico-w-mem-keyer/raw/refs/heads/main/sidetone.wav");
const FREQUENCY = 440;
var DOT_TIME = 300;
var DASH_TIME = DOT_TIME * 3;
var SYMBOL_BREAK = DOT_TIME;
var LETTER_BREAK = DOT_TIME * 3;
var WORD_BREAK = DOT_TIME * 7;
let note_context;
let note_node;
let gain_node;
let audioContextInitialized = false;
let audioResume = false;
let serialPort = null; // Serial port instance
let reader = null; // Stream reader
let prevCTS = null;
async function connectToSerialPort() {
try {
// Request user to select a serial port
serialPort = await navigator.serial.requestPort();
await serialPort.open({ baudRate: 9600 }); // Adjust baud rate as needed
console.log("Serial port connected.");
// Get signals object to read the CTS status
const signals = await serialPort.getSignals();
prevCTS = signals.clearToSend;
listenToSerialPort();
} catch (err) {
console.error("Failed to open serial port:", err);
serialPort = null;
}
}
async function listenToSerialPort() {
signals = await serialPort.getSignals();
while (serialPort.readable) {
signals = await serialPort.getSignals();
currentCTS = signals.clearToSend;
//console.log(`Clear To Send: ${signals.clearToSend}`);
//console.log(`Data Carrier Detect: ${signals.dataCarrierDetect}`);
//console.log(`Data Set Ready: ${signals.dataSetReady}`);
//console.log(`Ring Indicator: ${signals.ringIndicator}`);
if (currentCTS !== prevCTS) {
if (currentCTS) {
keyPress();
} else {
keyRelease();
}
prevCTS = currentCTS;
}
// console.log("reading serial =================")
// Allow small delay to prevent CPU hogging
await new Promise(resolve => setTimeout(resolve, 10));
}
}
async function initializeAudioContext() {
note_context = new AudioContext();
//await note_context.resume();
note_node = note_context.createOscillator();
gain_node = note_context.createGain();
note_node.frequency.value = FREQUENCY.toFixed(2);
gain_node.gain.value = 0;
note_node.connect(gain_node);
gain_node.connect(note_context.destination);
note_node.start();
audioContextInitialized = true;
}
//sidetone.loop = true; // Loop the sidetone
//sidetone.load();
if (!audioContextInitialized) {
initializeAudioContext();
}
// Histogram variables
const dtimeHistogramBins = new Array(50).fill(0); // 50 bins for 0-500ms with 10ms width
const utimeHistogramBins = new Array(50).fill(0); // 50 bins for 0-500ms with 10ms width
let dtimeCanvas, dtimeCtx;
let utimeCanvas, utimeCtx;
// Function to initialize the histogram canvases
document.addEventListener("DOMContentLoaded", () => {
const targetDiv = document.getElementById('metdivid');
const container = document.createElement("div");
container.style.display = "flex";
container.style.flexDirection = "row";
container.style.alignItems = "flex-start";
container.style.justifyContent = "center";
container.style.gap = "20px";
container.style.width = "100%";
targetDiv.appendChild(container);
// Create a div for the histograms
const histogramContainer = document.createElement("div");
histogramContainer.style.display = "flex";
histogramContainer.style.flexDirection = "column";
histogramContainer.style.alignItems = "center";
histogramContainer.style.width = "50%";
container.appendChild(histogramContainer);
// Add practice mode button
const buttonDiv = document.createElement("div");
buttonDiv.id = "buttonDiv";
buttonDiv.style.display = "flex";
buttonDiv.style.flexDirection = "column";
buttonDiv.style.alignItems = "center";
buttonDiv.style.width = "20%";
container.appendChild(buttonDiv);
const button = document.createElement("button");
button.id = "practiceModeButton";
button.innerText = "Enable Practice Mode";
button.addEventListener("click", togglePracticeMode);
buttonDiv.appendChild(button);
const halikeyButton = document.createElement("button");
halikeyButton.id = "startHalikey";
halikeyButton.innerText = "Start Halikey";
halikeyButton.addEventListener("click", toggleHalikey);
buttonDiv.appendChild(halikeyButton);
const generateButton = document.createElement("button");
generateButton.textContent = "Generate Histogram Image";
//generateButton.style.marginTop = "20px";
generateButton.addEventListener("click", combineCanvasesAndGenerateDownloadLink);
buttonDiv.appendChild(generateButton);
// Add a button to buttonDiv that calls playKeySequence with cwmsg
const playSequenceButton = document.createElement("button");
playSequenceButton.id = "playSequenceButton";
playSequenceButton.innerText = "Play Key Sequence";
playSequenceButton.addEventListener("click", () => playKeySequence(cwmsg));
buttonDiv.appendChild(playSequenceButton);
// Create title for dtime histogram
const dtimeTitle = document.createElement("div");
dtimeTitle.innerText = "Dot/Dash Times";
dtimeTitle.style.textAlign = "center";
dtimeTitle.style.fontSize = "16px";
dtimeTitle.style.marginBottom = "10px";
histogramContainer.appendChild(dtimeTitle);
// Create canvas for dtime histogram
dtimeCanvas = document.createElement("canvas");
dtimeCanvas.id = "dtimeHistogramCanvas";
dtimeCanvas.width = 435; // Width of canvas
dtimeCanvas.height = (dtimeCanvas.width * 8) / 9; // Maintain 9:16 aspect ratio
histogramContainer.appendChild(dtimeCanvas);
dtimeCtx = dtimeCanvas.getContext("2d");
drawDtimeHistogram();
// Create title for utime histogram
const utimeTitle = document.createElement("div");
utimeTitle.innerText = "wChar/btwnChar/btwnWord Times";
utimeTitle.style.textAlign = "center";
utimeTitle.style.fontSize = "16px";
utimeTitle.style.margin = "10px 0";
histogramContainer.appendChild(utimeTitle);
// Create canvas for utime histogram
utimeCanvas = document.createElement("canvas");
utimeCanvas.id = "utimeHistogramCanvas";
utimeCanvas.width = 435; // Width of canvas
utimeCanvas.height = (utimeCanvas.width * 8) / 9; // Maintain 9:16 aspect ratio
histogramContainer.appendChild(utimeCanvas);
utimeCtx = utimeCanvas.getContext("2d");
drawUtimeHistogram();
});
// Function to draw the dtime histogram
function drawDtimeHistogram() {
dtimeCtx.clearRect(0, 0, dtimeCanvas.width, dtimeCanvas.height); // Clear canvas
// Set up histogram dimensions
const barWidth = dtimeCanvas.width / dtimeHistogramBins.length;
const maxCount = Math.max(...dtimeHistogramBins);
// Draw bars
dtimeHistogramBins.forEach((count, index) => {
const barHeight = (count / maxCount) * dtimeCanvas.height;
dtimeCtx.fillStyle = "blue";
dtimeCtx.fillRect(index * barWidth, dtimeCanvas.height - barHeight, barWidth, barHeight);
});
// Draw axes
dtimeCtx.strokeStyle = "black";
dtimeCtx.beginPath();
dtimeCtx.moveTo(0, dtimeCanvas.height);
dtimeCtx.lineTo(dtimeCanvas.width, dtimeCanvas.height); // X-axis
dtimeCtx.lineTo(dtimeCanvas.width, 0); // Y-axis
dtimeCtx.stroke();
// Add labels to the x-axis every 10 bins
dtimeCtx.fillStyle = "black";
dtimeCtx.font = "12px sans-serif";
for (let i = 0; i <= dtimeHistogramBins.length; i += 10) {
const xPosition = i * barWidth;
dtimeCtx.fillText(i * 10, xPosition, dtimeCanvas.height - 5); // Label position
}
}
// Function to draw the utime histogram
function drawUtimeHistogram() {
utimeCtx.clearRect(0, 0, utimeCanvas.width, utimeCanvas.height); // Clear canvas
// Set up histogram dimensions
const barWidth = utimeCanvas.width / utimeHistogramBins.length;
const maxCount = Math.max(...utimeHistogramBins);
// Draw bars
utimeHistogramBins.forEach((count, index) => {
const barHeight = (count / maxCount) * utimeCanvas.height;
utimeCtx.fillStyle = "green";
utimeCtx.fillRect(index * barWidth, utimeCanvas.height - barHeight, barWidth, barHeight);
});
// Draw axes
utimeCtx.strokeStyle = "black";
utimeCtx.beginPath();
utimeCtx.moveTo(0, utimeCanvas.height);
utimeCtx.lineTo(utimeCanvas.width, utimeCanvas.height); // X-axis
utimeCtx.lineTo(utimeCanvas.width, 0); // Y-axis
utimeCtx.stroke();
// Add labels to the x-axis every 10 bins
utimeCtx.fillStyle = "black";
utimeCtx.font = "12px sans-serif";
for (let i = 0; i <= utimeHistogramBins.length; i += 10) {
const xPosition = i * barWidth;
utimeCtx.fillText(i * 10, xPosition, utimeCanvas.height - 5); // Label position
}
}
// Function to update the dtime histogram with a new value
function updateDtimeHistogram(dtime) {
if (dtime >= 0 && dtime <= 500) {
const binIndex = Math.floor(dtime / 10); // Determine bin index
dtimeHistogramBins[binIndex]++;
drawDtimeHistogram();
}
}
// Function to update the utime histogram with a new value
function updateUtimeHistogram(utime) {
if (utime >= 0 && utime <= 500) {
const binIndex = Math.floor(utime / 10); // Determine bin index
utimeHistogramBins[binIndex]++;
drawUtimeHistogram();
}
}
// Function to play the sidetone
function playSidetone() {
//sidetone.currentTime = 0;
//sidetone.play().catch(err => console.error("Error playing sidetone:", err));
gain_node.gain.setTargetAtTime(0.1, 0, 0.001)
}
// Function to stop the sidetone
function stopSidetone() {
//sidetone.pause();
//sidetone.currentTime = 0; // Reset audio playback position
gain_node.gain.setTargetAtTime(0, 0, 0.001)
}
function combineCanvasesAndGenerateDownloadLink() {
// Get the two canvas elements
const dtimeCanvas = document.getElementById("dtimeHistogramCanvas");
const utimeCanvas = document.getElementById("utimeHistogramCanvas");
const bDiv = document.getElementById('buttonDiv');
if (!dtimeCanvas || !utimeCanvas) {
console.error("Canvas elements not found.");
return;
}
// Set up labels
const dtimeLabel = "Dot/Dash Times";
const utimeLabel = "wChar/btwnChar/btwnWord Times";
// Create a new canvas to combine the two histograms and their labels
const combinedCanvas = document.createElement("canvas");
const labelHeight = 30; // Height for each label
combinedCanvas.width = Math.max(dtimeCanvas.width, utimeCanvas.width); // Use the larger width
combinedCanvas.height = dtimeCanvas.height + utimeCanvas.height + labelHeight * 2; // Combine heights with labels
const ctx = combinedCanvas.getContext("2d");
// Draw a white background
ctx.fillStyle = "white";
ctx.fillRect(0, 0, combinedCanvas.width, combinedCanvas.height);
// Draw the first label
ctx.fillStyle = "black";
ctx.font = "16px sans-serif";
ctx.textAlign = "center";
ctx.fillText(dtimeLabel, combinedCanvas.width / 2, labelHeight - 5);
// Draw the first canvas below the label
ctx.drawImage(dtimeCanvas, 0, labelHeight);
// Draw the second label
ctx.fillText(utimeLabel, combinedCanvas.width / 2, dtimeCanvas.height + labelHeight * 2 - 5);
// Draw the second canvas below the second label
ctx.drawImage(utimeCanvas, 0, dtimeCanvas.height + labelHeight * 2);
// Generate a Base64-encoded PNG image from the combined canvas
const base64Image = combinedCanvas.toDataURL("image/png");
// Create a download link
let downloadLink = document.getElementById("downloadHistogramLink");
if (!downloadLink) {
downloadLink = document.createElement("a");
downloadLink.id = "downloadHistogramLink";
downloadLink.style.display = "block";
downloadLink.style.marginTop = "20px";
downloadLink.style.textAlign = "right"; // Right-align the link
bDiv.appendChild(downloadLink);
}
downloadLink.href = base64Image;
downloadLink.download = "combined_histogram.png"; // Default filename
downloadLink.textContent = "Share your Results [png]";
}
// Function to handle key press
function keyPress() {
console.log("Key pressed");
if (keydown === 0) {
dtime = performance.now();
keydown = 1;
}
if(!audioResume){
note_context.resume();
audioResume = true;
}
if (utime !== 0) {
utime = performance.now() - utime;
console.log("Full utime: " + Math.round(utime));
cwmsg += Math.round(utime) + "+";
updateUtimeHistogram(Math.round(utime));
}
playSidetone();
}
// Function to handle key release
function keyRelease() {
console.log("Key released");
keydown = 0;
dtime = performance.now() - dtime;
console.log("Full dtime: " + Math.round(dtime));
cwmsg += Math.round(dtime) + "+";
updateDtimeHistogram(Math.round(dtime));
utime = performance.now();
stopSidetone();
}
// Function to send the CW message to the server
function sendCWMessage() {
if (practiceMode) {
console.log("Practice mode enabled. CW message not sent.");
return;
}
console.log("Sending CW message: " + cwmsg);
fetch(`http://192.168.4.1/light/skgo?msg=${cwmsg}`, { mode: 'no-cors' })
.then(() => console.log("CW message sent successfully."))
.catch(err => console.error("Error sending CW message:", err));
cwmsg = "";
dtime = 0;
utime = 0;
keydown = 0;
}
document.addEventListener("keydown", event => {
if (event.key === "Alt" || event.key === "Tab") {
return; // Ignore Alt and Tab keys
}
if (event.key !== "Escape" && keydown === 0) {
keyPress();
}
});
document.addEventListener("keyup", event => {
if (event.key === "Alt" || event.key === "Tab") {
return; // Ignore Alt and Tab keys
}
if (event.key !== "Escape") {
keyRelease();
} else {
console.log("Escape key pressed. Exiting.");
sendCWMessage();
}
});
// Function to toggle practice mode
function togglePracticeMode() {
practiceMode = !practiceMode;
const status = practiceMode ? "enabled" : "disabled";
console.log(`Practice mode ${status}.`);
document.getElementById("practiceModeButton").innerText = practiceMode ? "Disable Practice Mode" : "Enable Practice Mode";
}
//Function to enable hw key
async function toggleHalikey() {
if (!("serial" in navigator)) {
alert("Web Serial API is not supported in this browser.");
return;
}
// Connect to the serial port
await connectToSerialPort();
if(!audioResume){
note_context.resume();
audioResume = true;
}
document.getElementById("practiceModeButton").innerText = practiceMode ? "Disable Practice Mode" : "Enable Practice Mode";
}
// Function to alternate playing the sidetone based on keyDownUp intervals
function playKeySequence(keyDownUpString) {
const keyDownUp = keyDownUpString.split("+").map(Number); // Convert the string into an array of numbers
console.log(keyDownUpString)
async function playSequence() {
for (let i = 0; i < keyDownUp.length; i++) {
if (isNaN(keyDownUp[i])) continue; // Skip invalid entries
if (i % 2 === 0) {
// Even index: play the sidetone
playSidetone();
} else {
// Odd index: stop the sidetone
stopSidetone();
}
// Wait for the duration specified by the current interval
await new Promise(resolve => setTimeout(resolve, keyDownUp[i]));
}
// Ensure the sidetone is stopped after the sequence completes
stopSidetone();
}
playSequence();
}
// Main function
async function main() {
console.log("Press keys to generate CW messages. Press Escape to send the message.");
}
main();
</script>
<div class="metdiv" id="metdivid">
<!--Content for metdiv-->
<p>The app provides keyer sidetone on key down. As you key, it maintains two histograms: the one on top, (blue bars),
measures the the lengths of your dits and dahs; the one on bottom, (green bars), measures the length of your pauses between dits and dahs, your pauses between letters,
and your pauses between words.</p>
</div>
</body>
</html>