Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

USB Support #51

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 23 additions & 8 deletions src/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -152,18 +152,34 @@ <h5>Your Station Settings</h5>
<label for="yourSpeed" class="form-label">Speed (WPM)</label>
<input type="number" id="yourSpeed" name="yourSpeed" class="form-control" value="20" min="1"
max="100" step="1" required>
<div class="invalid-feedback"></div>
</div>

<div class="col-6 col-md-4 col-lg-2">
<label for="yourSidetone" class="form-label">Sidetone (Hz)</label>
<input type="number" id="yourSidetone" name="yourSidetone" class="form-control" value="600"
min="50" max="9999" step="1" required>
<input type="number" id="yourSidetone" name="yourSidetone" class="form-control" value="550" min="100"
max="2000" step="1" required>
<div class="invalid-feedback"></div>
</div>

<div class="col-6 col-md-4 col-lg-2">
<label for="yourVolume" class="form-label">Sidetone Volume</label>
<input type="number" id="yourVolume" name="yourVolume" class="form-control" value="70"
min="0"
<label for="yourVolume" class="form-label">Sidetone Volume (%)</label>
<input type="number" id="yourVolume" name="yourVolume" class="form-control" value="50" min="0"
max="100" step="1" required>
<div class="invalid-feedback"></div>
</div>

<div class="col-6 col-md-4 col-lg-2">
<label for="keyerMode" class="form-label">Keyer Mode (optional for USB adapters)</label>
<select id="keyerMode" class="form-select">
<option value="1">Straight Key</option>
<option value="2">Iambic A</option>
<option value="3" selected>Iambic B</option>
<option value="4">Ultimatic</option>
</select>
<div class="invalid-feedback"></div>
</div>

</div>
</div>
</div>
Expand Down Expand Up @@ -317,10 +333,9 @@ <h5>Responding Station Settings</h5>
</div>
</div>
</div>


</div>


<!-- Effects -->
<!-- Effects -->
<div class="accordion-item">
Expand Down Expand Up @@ -580,7 +595,7 @@ <h3 class="modal-title" id="helpModalLabel">Getting Started</h3>
<p class="card-text">After completing an exchange, click "TU" to send a wrap-up, thank
you message. Each mode has a slightly different way of completing.</p>
<p class="card-text">After clicking TU, new stations sometimes hop in! But, in case they
don't, you can always go back and click CQ.</p>
dont, you can always go back and click CQ.</p>
</div>
</div>
</div>
Expand Down
29 changes: 27 additions & 2 deletions src/js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import {
import {getYourStation, getCallingStation} from "./stationGenerator.js";
import {updateStaticIntensity} from "./audio.js";
import {modeLogicConfig, modeUIConfig} from "./modes.js";
import { morseInput } from './morse-input/morse-input.js'; // Import morse input functionality

/**
* Application state variables.
Expand Down Expand Up @@ -82,12 +83,18 @@ document.addEventListener('DOMContentLoaded', () => {
const modeRadios = document.querySelectorAll('input[name="mode"]');
const yourCallsign = document.getElementById("yourCallsign");
const yourName = document.getElementById("yourName");
const yourState = document.getElementById("yourState"); // Added yourState
const yourSpeed = document.getElementById("yourSpeed");
const yourSidetone = document.getElementById("yourSidetone");
const yourVolume = document.getElementById("yourVolume");
const keyerMode = document.getElementById("keyerMode");

// Event Listeners
cqButton.addEventListener('click', cq);
cqButton.addEventListener('click', () => {
// Initialize morse input when user starts using MorseWalker
morseInput.initialize();
cq();
});
sendButton.addEventListener('click', send);
tuButton.addEventListener('click', tu);
resetButton.addEventListener('click', reset);
Expand All @@ -114,6 +121,15 @@ document.addEventListener('DOMContentLoaded', () => {
// Toggle the Farnsworth speed input when the checkbox changes
enableFarnsworthCheckbox.addEventListener('change', () => {
farnsworthSpeedInput.disabled = !enableFarnsworthCheckbox.checked;
if (enableFarnsworthCheckbox.checked) {
morseInput.updateSettings({ farnsworth: parseInt(farnsworthSpeedInput.value) });
}
});

farnsworthSpeedInput.addEventListener('input', () => {
if (enableFarnsworthCheckbox.checked) {
morseInput.updateSettings({ farnsworth: parseInt(farnsworthSpeedInput.value) });
}
});

// Add hotkey for CQ (Ctrl + Shift + C)
Expand Down Expand Up @@ -161,7 +177,8 @@ document.addEventListener('keydown', (event) => {
yourState: "yourState", // Added yourState
yourSpeed: "yourSpeed",
yourSidetone: "yourSidetone",
yourVolume: "yourVolume"
yourVolume: "yourVolume",
keyerMode: "keyerMode"
};

/**
Expand All @@ -177,6 +194,7 @@ document.addEventListener('keydown', (event) => {
yourSpeed.value = localStorage.getItem(keys.yourSpeed) || yourSpeed.value;
yourSidetone.value = localStorage.getItem(keys.yourSidetone) || yourSidetone.value;
yourVolume.value = localStorage.getItem(keys.yourVolume) || yourVolume.value;
keyerMode.value = localStorage.getItem(keys.keyerMode) || keyerMode.value;

// Save user settings to localStorage on input change
yourCallsign.addEventListener("input", () => {
Expand All @@ -190,13 +208,20 @@ document.addEventListener('keydown', (event) => {
});
yourSpeed.addEventListener("input", () => {
localStorage.setItem(keys.yourSpeed, yourSpeed.value);
// Update morse input speed when changed
morseInput.updateSettings({ wpm: parseInt(yourSpeed.value) });
});
yourSidetone.addEventListener("input", () => {
localStorage.setItem(keys.yourSidetone, yourSidetone.value);
// Update morse input tone when changed
morseInput.updateSettings({ tone: parseInt(yourSidetone.value) });
});
yourVolume.addEventListener("input", () => {
localStorage.setItem(keys.yourVolume, yourVolume.value);
});
keyerMode.addEventListener("change", () => {
localStorage.setItem(keys.keyerMode, keyerMode.value);
});

// Handle QRN intensity changes
const qrnRadioButtons = document.querySelectorAll('input[name="qrn"]');
Expand Down
1 change: 1 addition & 0 deletions src/js/inputs.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ function getDOMInputs() {
yourCallsign: document.getElementById('yourCallsign').value.trim().toUpperCase(),
yourName: document.getElementById('yourName').value.trim(),
yourState: document.getElementById('yourState').value.trim().toUpperCase(), // Convert to uppercase for consistency
keyerMode: parseInt(document.getElementById('keyerMode').value, 10),
yourSpeed: parseInt(document.getElementById('yourSpeed').value, 10),
yourSidetone: parseInt(document.getElementById('yourSidetone').value, 10),
// convert volume to a float between 0 and 1
Expand Down
148 changes: 148 additions & 0 deletions src/js/morse-input/decoder.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
const morseToAlphabet = new Map([
["12", "A"],
["2111", "B"],
["2121", "C"],
["211", "D"],
["1", "E"],
["1121", "F"],
["221", "G"],
["1111", "H"],
["11", "I"],
["1222", "J"],
["212", "K"],
["1211", "L"],
["22", "M"],
["21", "N"],
["222", "O"],
["1221", "P"],
["2212", "Q"],
["121", "R"],
["111", "S"],
["2", "T"],
["112", "U"],
["1112", "V"],
["122", "W"],
["2112", "X"],
["2122", "Y"],
["2211", "Z"],
["12222", "1"],
["11222", "2"],
["11122", "3"],
["11112", "4"],
["11111", "5"],
["21111", "6"],
["22111", "7"],
["22211", "8"],
["22221", "9"],
["22222", "0"],
["121212", "."],
["221122", ","],
["21121", "/"],
["112211", "?"],
["212122", "!"],
["211112", "-"],
["21221", "("],
["212212", ")"],
["222111", ":"],
]);

export class Decoder {
constructor(onLetterDecoded) {
this.onLetterDecoded = onLetterDecoded; // Store the callback function
this.lastLetter = '';
this.decodeArray = '';
this.unit = 80; // adjustment: short dit reduces, long dah lengthens
this.keyStartTime = null;
this.keyEndTime = null;
this.spaceTimer = null;
this.farnsworth = 3;
this.wordTimer = null; // Timer for word boundaries
this.wordTimeout = this.unit * 7; // A typical word gap is 7 units
}

keyOn() {
clearTimeout(this.spaceTimer);
clearTimeout(this.wordTimer); // Clear the wordTimer as well since we are receiving input
this.keyStartTime = Date.now();
//var pauseDuration = (this.keyEndTime) ? this.keyStartTime - this.keyEndTime : 0;
//if (pauseDuration > this.unit + (this.unit/10)) { // end sequence and decode letter
//this.registerLetter();
//}
}

keyOff() {
this.keyEndTime = Date.now();
var keyDuration = (this.keyStartTime) ? this.keyEndTime - this.keyStartTime : 0;
if (keyDuration < this.unit) {
// reduce unit based on short dit
this.unit = (keyDuration + this.unit) / 2;
this.registerDit();
} else if (keyDuration > this.unit * 3) {
// lengthen unit based on long dah
this.unit = ((keyDuration / 3) + this.unit) / 2;
this.registerDah();
} else {
var ditAndDahThreshold = (this.unit * 2);
if (keyDuration >= ditAndDahThreshold) {
this.registerDah();
} else {
this.registerDit();
}
}
let spaceTime = this.unit * this.farnsworth;
this.spaceTimer = setTimeout(() => { // end sequence and decode letter
this.updateLastLetter(this.morseToLetter(this.decodeArray));
this.decodeArray = '';
this.startWordTimer(); // Start the word timer after finishing a letter
}, spaceTime, "keyOff");
}

registerDit() {
this.decodeArray += '1';
}

registerDah() {
this.decodeArray += '2';
}

updateLastLetter(letter) {
//updateCurrentLetter(letter);
this.lastLetter = letter;
//console.log(this.lastLetter);

// Notify the callback function that a new letter is decoded
if (this.onLetterDecoded) {
this.onLetterDecoded(letter);
}
}

morseToLetter(sequence) {
var letter = morseToAlphabet.get(sequence);
if (letter) {
return letter;
} else {
return '*';
}
}

startWordTimer() {
// Comment out word spacing for MorseWalker integration
// Set up the word timer to add a space after a word boundary
/*
this.wordTimer = setTimeout(() => {
// Update with a space to indicate a word boundary
if (this.onLetterDecoded) {
this.onLetterDecoded(' ');
}
}, this.wordTimeout);
*/
}

calculateWpm() {
return 60000 / (this.unit * 50);
}

setFarnsworth(farnsworth) {
this.farnsworth = farnsworth;
}
}
Loading