mirror of
https://github.com/genemecija/learn-morse-code.git
synced 2025-12-06 07:02:00 +01:00
349 lines
11 KiB
JavaScript
349 lines
11 KiB
JavaScript
import {useEffect, useContext} from 'react'
|
|
import config from '../config.json'
|
|
import {MorseBufferContext} from '../contexts/morseBufferContext'
|
|
|
|
// SINGLE/DUAL LEVER TELEGRAPH - Iambic A
|
|
|
|
function useElectronicKey(mode = 'practice') {
|
|
|
|
const {morseCharBuffer, setMorseCharBuffer, morseWords, setMorseWords} = useContext(MorseBufferContext)
|
|
|
|
const timingUnit = config.timingUnit
|
|
|
|
let ratio = 0.2
|
|
const ditMaxTime = 80 // ditMaxTime * 0.365 to get ms, e.g. 85 * 0.365 ~= 31ms
|
|
const letterGapMinTime = ditMaxTime*ratio*3 //config.practiceSpeed.normal*3
|
|
const wordGapMaxTime = ditMaxTime*ratio*7 // config.practiceSpeed.normal*7
|
|
const morseHistorySize = config.historySize
|
|
|
|
let leftIsPressed = false
|
|
let rightIsPressed = false
|
|
let queueRunning = false
|
|
let queue = []
|
|
let pressedFirst = null
|
|
|
|
let depressSyncTime
|
|
let depressSyncTimer
|
|
let depressSyncTimerRunning = false
|
|
let gapTime = 0
|
|
let gapTimer = 0
|
|
// let gapTimerRunning = false
|
|
let paddlesReleasedSimultaneously = false
|
|
|
|
// function consoleLogVars() {
|
|
// // Log variables (Debug tool)
|
|
// console.log('<VARS>');
|
|
// console.log('leftIsPressed', leftIsPressed);
|
|
// console.log('rightIsPressed', rightIsPressed);
|
|
// console.log('queueRunning', queueRunning);
|
|
// console.log('queue', queue);
|
|
// console.log('pressedFirst', pressedFirst);
|
|
// console.log('gapTime', gapTime);
|
|
// console.log('paddlesReleasedSimultaneously', paddlesReleasedSimultaneously);
|
|
// console.log('</VARS>');
|
|
// };
|
|
|
|
let currentPromise = Promise.resolve()
|
|
|
|
// Audio Setup
|
|
let AudioContext = window.AudioContext || window.webkitAudioContext || false
|
|
let context
|
|
window.AudioContext = window.AudioContext || window.webkitAudioContext;
|
|
if (AudioContext) {
|
|
context = new AudioContext()
|
|
} else {
|
|
context = null
|
|
}
|
|
let frequency = config.frequency
|
|
|
|
let toneTimer = 0
|
|
let toneTime = 0
|
|
let start = 0
|
|
let end = 0
|
|
|
|
// Promisify playing Dits and Dahs
|
|
function play(ditDah) {
|
|
let playDuration = ((ditDah === '.') ? ditMaxTime : ditMaxTime*3)
|
|
|
|
return new Promise((resolve, reject) => {
|
|
if (context.state === 'interrupted') {
|
|
context.resume()
|
|
}
|
|
|
|
let o = context.createOscillator()
|
|
o.frequency.value = frequency
|
|
o.type = "sine"
|
|
o.onended = () => {
|
|
resolve()
|
|
}
|
|
|
|
let startTime = context.currentTime;
|
|
|
|
let g = context.createGain()
|
|
g.gain.exponentialRampToValueAtTime(config.mainVolume, startTime)
|
|
g.gain.setValueAtTime(config.mainVolume, startTime)
|
|
o.connect(g)
|
|
g.connect(context.destination)
|
|
|
|
o.start(startTime)
|
|
|
|
// // for troubleshooting ditDah length in milliseconds
|
|
// toneTimer = setInterval(() => {
|
|
// toneTime += 1
|
|
// }, 1);
|
|
// start = toneTime
|
|
// // </>
|
|
|
|
g.gain.setTargetAtTime(0.0001, startTime + playDuration/1000, 0.001)
|
|
o.stop(startTime + playDuration/1000 + 0.05)
|
|
})
|
|
}
|
|
|
|
function playWithSpaces(ditDah) {
|
|
let delay = (ditDah === '.') ? ditMaxTime + ditMaxTime : ditMaxTime*3 + ditMaxTime
|
|
|
|
return new Promise(function(resolve) {
|
|
if (ditDah === '.' || ditDah === '-') {
|
|
|
|
clearInterval(gapTimer)
|
|
checkGapBetweenInputs()
|
|
setMorseCharBuffer(prev => prev + ditDah)
|
|
|
|
play(ditDah)
|
|
.then(setTimeout(() => {
|
|
|
|
// START GAP TIMER
|
|
// gapTimerRunning = true
|
|
gapTimer = setInterval(() => {
|
|
gapTime += 1
|
|
|
|
if (gapTime >= wordGapMaxTime) {
|
|
setMorseCharBuffer(prev => prev + '/')
|
|
clearInterval(gapTimer)
|
|
gapTimer = 0
|
|
gapTime = 0
|
|
}
|
|
}, timingUnit)
|
|
|
|
resolve();
|
|
}, delay))
|
|
} else {
|
|
setTimeout(() => {
|
|
resolve();
|
|
}, delay)
|
|
}
|
|
});
|
|
}
|
|
|
|
function executeQueue() {
|
|
let localQueue = queue
|
|
|
|
// Set waitTime to completion of queue (ditDah time + following silences)
|
|
let waitTime = 0
|
|
for (let i in localQueue) {
|
|
if (localQueue[i] === '.') {
|
|
waitTime += ditMaxTime*2
|
|
} else if (localQueue[i] === '-') {
|
|
waitTime += ditMaxTime*4
|
|
}
|
|
}
|
|
|
|
// Cleanup
|
|
function cleanup() {
|
|
queueRunning = false
|
|
queue = []
|
|
sendPressedToQueue() // Check if anything still pressed down on queue finish
|
|
}
|
|
|
|
// Wait till completion of queue to execute
|
|
const clear = setTimeout(() => {
|
|
cleanup()
|
|
}, waitTime)
|
|
|
|
// Execute queue
|
|
queueRunning = true
|
|
for (let i = 0; i < localQueue.length; i++) {
|
|
if (paddlesReleasedSimultaneously === true) {
|
|
localQueue.pop()
|
|
clearTimeout(clear)
|
|
cleanup()
|
|
}
|
|
currentPromise = currentPromise.then(() => {
|
|
return playWithSpaces(localQueue[i])
|
|
});
|
|
}
|
|
}
|
|
|
|
function sendPressedToQueue() {
|
|
if (leftIsPressed && rightIsPressed) {
|
|
if (pressedFirst === 'left') {
|
|
queue.push('-')
|
|
pressedFirst = null
|
|
|
|
} else {
|
|
queue.push('.')
|
|
queue.push('-')
|
|
}
|
|
}
|
|
else if (leftIsPressed && !rightIsPressed) {
|
|
queue.push('.')
|
|
}
|
|
else if (rightIsPressed && !leftIsPressed) {
|
|
queue.push('-')
|
|
}
|
|
if (queue.length > 0) {
|
|
executeQueue()
|
|
}
|
|
}
|
|
|
|
function handleInputStart(event) {
|
|
event.preventDefault()
|
|
|
|
paddlesReleasedSimultaneously = false
|
|
|
|
if (event.repeat) { return }
|
|
|
|
if (event.keyCode === 188) {
|
|
leftIsPressed = true
|
|
if (!rightIsPressed) { pressedFirst = 'left'}
|
|
|
|
// Prevent further input if queue is executing
|
|
if (!queueRunning) {
|
|
sendPressedToQueue()
|
|
}
|
|
}
|
|
else if (event.keyCode === 190) {
|
|
rightIsPressed = true
|
|
if (!leftIsPressed) { pressedFirst = 'right'}
|
|
|
|
// Prevent further input if queue is executing
|
|
if (!queueRunning) {
|
|
sendPressedToQueue()
|
|
}
|
|
}
|
|
}
|
|
|
|
function handleInputEnd(event) {
|
|
event.preventDefault()
|
|
|
|
if (event.keyCode === 188) {
|
|
leftIsPressed = false
|
|
|
|
if (pressedFirst === 'left') { pressedFirst = null }
|
|
|
|
if (!depressSyncTimerRunning) { startDepressSyncTimer() }
|
|
else { stopDepressSyncTimer() }
|
|
}
|
|
if (event.keyCode === 190) {
|
|
rightIsPressed = false
|
|
if (pressedFirst === 'right') { pressedFirst = null }
|
|
|
|
if (!depressSyncTimerRunning) { startDepressSyncTimer() }
|
|
else { stopDepressSyncTimer() }
|
|
}
|
|
}
|
|
|
|
|
|
function startDepressSyncTimer() {
|
|
depressSyncTimerRunning = true
|
|
// Reset depressSyncTime
|
|
depressSyncTime = 0
|
|
// Start depressSyncTimer
|
|
depressSyncTimer = setInterval(() => {
|
|
depressSyncTime += 1
|
|
if (depressSyncTime > 20) {
|
|
depressSyncTimerRunning = false
|
|
clearInterval(depressSyncTimer)
|
|
depressSyncTime = 0
|
|
}
|
|
}, 1);
|
|
}
|
|
function stopDepressSyncTimer() {
|
|
depressSyncTimerRunning = false
|
|
clearInterval(depressSyncTimer)
|
|
if (depressSyncTime < 10) {
|
|
paddlesReleasedSimultaneously = true
|
|
queue.pop()
|
|
console.log('paddles released', queue);
|
|
}
|
|
depressSyncTime = 0
|
|
}
|
|
function startGapTimer() {
|
|
gapTime = 0
|
|
gapTimer = setInterval(() => {
|
|
gapTime += 1
|
|
|
|
// Gap between words
|
|
if (mode === 'practice' && gapTime >= wordGapMaxTime) {
|
|
setMorseCharBuffer(prev => prev + '/')
|
|
clearInterval(gapTimer)
|
|
gapTimer = 0
|
|
gapTime = 0
|
|
}
|
|
if (mode === 'challenge' && gapTime >= letterGapMinTime) {
|
|
setMorseCharBuffer(prev => prev + '_')
|
|
clearInterval(gapTimer)
|
|
gapTimer = 0
|
|
gapTime = 0
|
|
}
|
|
}, timingUnit);
|
|
}
|
|
function checkGapBetweenInputs() {
|
|
// Check Gap between letters
|
|
// console.log('gapTime', gapTime);
|
|
// console.log('letterGapMinTime', letterGapMinTime);
|
|
// console.log('wordGapMaxTime', wordGapMaxTime);
|
|
if (gapTime >= letterGapMinTime && gapTime < wordGapMaxTime) {
|
|
// console.log('letterGapMinTime <= gapTime < wordGapMaxTime:',letterGapMinTime, gapTime, wordGapMaxTime);
|
|
if (mode === 'practice') {
|
|
setMorseCharBuffer(prev => prev + ' ')
|
|
gapTime = 0
|
|
} else if (mode === 'challenge') {
|
|
console.log("UNDERSCORE ADDED");
|
|
setMorseCharBuffer(prev => prev + '_')
|
|
}
|
|
clearInterval(gapTimer)
|
|
gapTimer = 0
|
|
}
|
|
}
|
|
|
|
useEffect(() => {
|
|
document.addEventListener('keydown', handleInputStart)
|
|
document.addEventListener('keyup', handleInputEnd)
|
|
|
|
const morseButton = document.getElementById('morseButton')
|
|
morseButton.addEventListener('mousedown', handleInputStart)
|
|
morseButton.addEventListener('touchstart', handleInputStart)
|
|
morseButton.addEventListener('mouseup', handleInputEnd)
|
|
morseButton.addEventListener('touchend', handleInputEnd)
|
|
|
|
return function cleanup() {
|
|
document.removeEventListener('keydown', handleInputStart)
|
|
document.removeEventListener('keyup', handleInputEnd)
|
|
}
|
|
// eslint-disable-next-line
|
|
}, [])
|
|
|
|
useEffect(() => {
|
|
// PRACTICE MODE
|
|
if (morseCharBuffer.slice(-1) === '/' && mode === 'practice') {
|
|
// Remove forward slash
|
|
let val = morseCharBuffer.slice(0,morseCharBuffer.length-1)
|
|
|
|
setMorseWords(prev => [val, ...prev])
|
|
|
|
if (morseWords.length >= morseHistorySize) {
|
|
setMorseWords(prev => prev.slice(0,prev.length-1))
|
|
}
|
|
setMorseCharBuffer('')
|
|
}
|
|
// CHALLENGE MODE: leave forward slash there; to be parsed by ChallengeDisplay.js
|
|
// else if (morseCharBuffer.slice(-1) === '/' && mode === 'challenge') {
|
|
|
|
// }
|
|
|
|
// eslint-disable-next-line
|
|
}, [morseCharBuffer])
|
|
}
|
|
|
|
export default useElectronicKey |