From 82a505344ce000e365b94e767049788581269e89 Mon Sep 17 00:00:00 2001 From: Eddie Kim Date: Fri, 25 Oct 2024 12:16:42 -0700 Subject: [PATCH] Add files via upload --- static/scripts/decoder.js | 116 +++++++++++++++++ static/scripts/keyer.js | 254 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 370 insertions(+) create mode 100644 static/scripts/decoder.js create mode 100644 static/scripts/keyer.js diff --git a/static/scripts/decoder.js b/static/scripts/decoder.js new file mode 100644 index 0000000..b97d0c7 --- /dev/null +++ b/static/scripts/decoder.js @@ -0,0 +1,116 @@ +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"], +]); + +class Decoder { + constructor() { + 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; + } + + keyOn() { + clearTimeout(this.spaceTimer); + 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 = ''; + }, spaceTime, "keyOff"); + } + + registerDit() { + this.decodeArray += '1'; + } + + registerDah() { + this.decodeArray += '2'; + } + + updateLastLetter(letter) { + updateCurrentLetter(letter); + this.lastLetter = letter; + //console.log(this.lastLetter); + } + + morseToLetter(sequence) { + var letter = morseToAlphabet.get(sequence); + if (letter) { + return letter; + } else { + return '*'; + } + } + + calculateWpm() { + return 60000 / (this.unit * 50); + } + + setFarnsworth(farnsworth) { + this.farnsworth = farnsworth; + } +} diff --git a/static/scripts/keyer.js b/static/scripts/keyer.js new file mode 100644 index 0000000..793e3b2 --- /dev/null +++ b/static/scripts/keyer.js @@ -0,0 +1,254 @@ +class Keyer { + constructor(sndr, decoder) { + this.sndr = sndr; // Sounder instance + this.decoder = decoder; // Decoder instance + // Using [ and ] for dits and dahs respectively for compatibility with the vband usb interface + this.ditKey1 = 'ControlLeft'; + this.dahKey1 = 'ControlRight'; + this.ditKey2 = 'BracketLeft'; + this.dahKey2 = 'BracketRight'; + this.wpm = 20; + this.unit = 60; // length of dit in milliseconds; 60 is 20wpm + this.mode = 2; // 1: straight key, 2: iambicA, 3: iambicB, 4: ultimatic + this.tone = 550; + this.queue = []; + this.ditKeyState = 0; + this.dahKeyState = 0; + this.lastKey = null; + this.ditStreak = 0; + this.dahStreak = 0; + this.streak = 0; + this.ditStart = null; + this.ditStop = null; + this.dahStart = null; + this.dahStop = null; + this.sending = false; + this.lastSendTimestamp = null; + this.oscillatorTimer = setInterval(() => { + this.oscillate(); + }, 0); + } + + setWpm(wpm){ + this.wpm = wpm; + this.unit = 60000 / (wpm * 50) // based on the PARIS method 60 seconds / 50 elements per word * WPM + this.decoder.unit = this.unit; + } + + setMode(mode){ + this.mode = mode; + } + + setTone(tone){ + this.tone = tone; + } + + sendSignal() { + this.sending = true; + //console.log('startSignal'); + if (restartAudioNeeded()) { + restartAudio(); + } + this.sndr.setTone(this.tone); + this.sndr.on(); + this.decoder.keyOn(); + } + + stopSignal() { + //console.log('stopSignal'); + this.sndr.off(); + this.decoder.keyOff(); + this.lastSendTimestamp = Date.now(); + setTimeout(() => { + this.sending = false; + }, this.unit); + } + + press(event, down, mode=this.mode) { + if (mode > 1 && event.code != this.ditKey1 && event.code != this.dahKey1 && event.code != this.ditKey2 && event.code != this.dahKey2) return; + if (mode == 1) { + if (down) { + if (restartAudioNeeded()) { + restartAudio(); + } + this.sndr.setTone(this.tone); + this.sndr.on(); + this.decoder.keyOn(); + + } else { + this.sndr.off(); + this.decoder.keyOff(); + } + } else if (mode > 1) { + //console.log(key); + if (event.code == this.ditKey1 || event.code == this.ditKey2) { + if (down) { // dit key down + this.ditKeyState = 1; + this.ditStart = Date.now() + } else { // dit key up + this.ditKeyState = 0; + this.ditStop = Date.now() + } + } + if (event.code == this.dahKey1 || event.code == this.dahKey2) { + if (down) { // dah key down + this.dahKeyState = 1; + this.dahStart = Date.now() + } else { // dah key up + this.dahKeyState = 0; + this.dahStop = Date.now() + } + } + } + } + + processQueue() { + //console.log('processQueue'); + if (!this.sending && this.queue.length) { + this.lastKey = this.queue.shift(); + var signalLength = (this.lastKey == 1) ? this.unit : this.unit * 3; + this.sendSignal(); + setTimeout(() => { + this.stopSignal(); + }, signalLength); + } + } + + oscillatev1() { + if (!this.ditKeyState && !this.dahKeyState) { + this.queue = []; + } + if (this.ditKeyState) { + if (this.queue.length == 0) { + if (!this.dahKeyState && !this.sending || this.lastKey == 2) { + this.queue.push(1); + } + } + } + if (this.dahKeyState) { + if (this.queue.length == 0) { + if (!this.ditKeyState && !this.sending || this.lastKey == 1) { + this.queue.push(2); + } + } + } + if (!this.sending && Date.now() - this.lastSendTimestamp > this.unit) { + this.processQueue(); + } + } + + oscillatev2() { + if (this.mode == 2 && !this.ditKeyState && !this.dahKeyState) { // Iambic B doesn't clear the queue + if (this.streak > 1) { + //console.log(this.streak + " queue: " + this.queue[0]); + this.queue = []; + } + this.streak = 0; + } + if (this.ditKeyState) { + if (this.queue.length < 1) { + if (!this.sending || this.lastKey == 2) { + this.queue.push(1); + if (this.lastKey == 2 && this.dahKeyState) { + this.streak++; + } else { + this.streak = 0; + } + } + } + } + if (this.dahKeyState) { + if (this.queue.length < 1) { + if (!this.sending || this.lastKey == 1) { + this.queue.push(2); + if (this.lastKey == 1 && this.ditKeyState) { + this.streak++; + } else { + this.streak = 0; + } + } + } + } + if (!this.sending && Date.now() - this.lastSendTimestamp >= this.unit) { + this.processQueue(); + } + } + + oscillatev3() { + if (this.mode == 2 && !this.ditKeyState && !this.dahKeyState && this.queue.length) { // Iambic B doesn't clear the queue + if ((this.ditStreak && this.queue[0] == 1) || (this.dahStreak && this.queue[0] == 2)) { + console.log("ditStreak: "+this.ditStreak+" dahStreak: "+this.dahStreak+" queue: "+this.queue[0]); + this.queue = []; + } + //console.log("NO CLEAR ditStreak: "+this.ditStreak+" dahStreak: "+this.dahStreak+" queue: "+this.queue[0]); + this.ditStreak = 0; + this.dahStreak = 0; + } + if (this.ditKeyState) { + if (!this.queue.length) { + if (!this.sending || this.lastKey == 2) { + this.queue.push(1); + this.ditStreak++; + } + } else if (!this.dahKeyState && this.queue[0] == 2) { // dah was canceled. Replace in queue + this.queue[0] = 1; + this.dahStreak = 0; + this.ditStreak++; + } + } + if (this.dahKeyState) { + if (!this.queue.length) { + if (!this.sending || this.lastKey == 1) { + this.queue.push(2); + this.dahStreak++; + } + } else if (!this.ditKeyState && this.queue[0] == 1) { // dit was canceled. Replace in queue + this.queue[0] = 2; + this.ditStreak = 0; + this.dahStreak++; + } + } + if (!this.sending && Date.now() - this.lastSendTimestamp >= this.unit) { + this.processQueue(); + } + } + + oscillate() { + if (this.mode == 2 && !this.ditKeyState && !this.dahKeyState && this.queue.length) { + if (this.queue[0] == 1) { // Dit is in the queue + if (this.ditStart < this.dahStart || this.ditStop - this.ditStart > this.unit * 4) { + this.queue.pop(); + } + } else { // Dah is in the queue + if (this.dahStart < this.ditStart || this.dahStop - this.dahStart > this.unit * 2) { + this.queue.pop(); + } + } + } + if (this.ditKeyState) { + if (this.queue.length == 0) { + if ((!this.dahKeyState && !this.sending) || this.lastKey == 2) { + this.queue.push(1); + } + } else { // dah key was lifted and is still in queue + if (this.mode == 2 && !this.dahKeyState && this.dahStart < this.ditStart && this.queue[0] == 2) { + this.queue.pop(); + } + } + } + if (this.dahKeyState) { + if (this.queue.length == 0) { + if ((!this.ditKeyState && !this.sending) || this.lastKey == 1) { + this.queue.push(2); + } + } else { // dit key was lifted and is still in queue + if (this.mode == 2 && !this.ditKeyState && this.ditStart < this.dahStart && this.queue[0] == 1) { + this.queue.pop(); + } + } + } + if (!this.sending && Date.now() - this.lastSendTimestamp > this.unit) { + this.processQueue(); + } + } +}