From 92b528616596d9426edc497dc309d3275c779fab Mon Sep 17 00:00:00 2001 From: Gene Mecija Date: Thu, 16 Jan 2020 01:18:40 -0800 Subject: [PATCH] Electronic key functionality progress, context separation --- src/App.js | 25 +- src/app-modes/ChallengeMode.js | 4 +- src/app-modes/PracticeMode.js | 23 +- src/app-modes/TimedMode.js | 4 +- src/components/ElectronicKey.js | 7 + src/components/KeyTypePicker.js | 25 ++ src/components/ModePicker.js | 2 +- src/components/MorseBufferDisplay.js | 2 +- src/components/StraightKey.js | 9 + src/config.json | 3 +- src/{ => contexts}/gameContext.js | 0 src/contexts/keyTypeContext.js | 24 ++ src/contexts/morseBufferContext.js | 42 +++ src/hooks/useElectronicKey.js | 335 +++++++++++++++++ src/hooks/useElectronicKey_iambicB.js | 344 ++++++++++++++++++ src/hooks/useKeySelection.js | 0 src/hooks/usePaddleTelegraph.js | 175 --------- .../{useTelegraph.js => useStraightKey.js} | 20 +- src/index.js | 8 +- 19 files changed, 848 insertions(+), 204 deletions(-) create mode 100644 src/components/ElectronicKey.js create mode 100644 src/components/KeyTypePicker.js create mode 100644 src/components/StraightKey.js rename src/{ => contexts}/gameContext.js (100%) create mode 100644 src/contexts/keyTypeContext.js create mode 100644 src/contexts/morseBufferContext.js create mode 100644 src/hooks/useElectronicKey.js create mode 100644 src/hooks/useElectronicKey_iambicB.js create mode 100644 src/hooks/useKeySelection.js delete mode 100644 src/hooks/usePaddleTelegraph.js rename src/hooks/{useTelegraph.js => useStraightKey.js} (86%) diff --git a/src/App.js b/src/App.js index 0413752..43e0f89 100644 --- a/src/App.js +++ b/src/App.js @@ -1,12 +1,19 @@ -import React, {useContext, useEffect} from 'react'; +import React, {useContext} from 'react'; import './css/App.css'; import MorseButton from './components/MorseButton' import ModePicker from './components/ModePicker' +import KeyTypePicker from './components/KeyTypePicker' // import MorseDisplay from './components/MorseDisplay' // import MorseBufferDisplay from './components/MorseBufferDisplay' // import GameClock from "./components/GameClock" // import ChallengeWord from "./components/ChallengeWord" -import {GameModeContext} from "./gameContext" +import {GameModeContext} from "./contexts/gameContext" +import {KeyTypeContext} from "./contexts/keyTypeContext" +import {MorseBufferContextProvider} from "./contexts/morseBufferContext" + +import StraightKey from './components/StraightKey' +import ElectronicKey from './components/ElectronicKey' + import PracticeMode from './app-modes/PracticeMode'; import TimedMode from './app-modes/TimedMode' import ChallengeMode from './app-modes/ChallengeMode' @@ -16,15 +23,23 @@ function App() { console.log('App.js rendered') const {gameMode} = useContext(GameModeContext) + const {keyType} = useContext(KeyTypeContext) + return (
+ + + + {keyType === "straight" && } + {keyType === "electronic" && } - {gameMode === 'practice' && } - {gameMode === 'timed' && } - {gameMode === 'challenge' && } + {gameMode === 'practice' && } + {gameMode === 'timed' && } + {gameMode === 'challenge' && } +
diff --git a/src/app-modes/ChallengeMode.js b/src/app-modes/ChallengeMode.js index 8577d9c..5c941b2 100644 --- a/src/app-modes/ChallengeMode.js +++ b/src/app-modes/ChallengeMode.js @@ -1,7 +1,7 @@ import React from 'react'; import '../css/App.css'; import morseCode from '../data/morse-reverse.json' -import useTelegraph from '../hooks/useTelegraph'; +import useStraightKey from '../hooks/useStraightKey'; // import ChallengeWord from '../components/ChallengeWord' // import MorseBufferDisplay from '../components/MorseBufferDisplay' // import ChallengeDisplay from '../components/ChallengeDisplay'; @@ -11,7 +11,7 @@ function ChallengeMode() { console.log("ChallengeMode loaded"); let word = "morse" - const {morseCharBuffer} = useTelegraph('challenge') + const {morseCharBuffer} = useStraightKey('challenge') // console.log('morseCharBuffer:', morseCharBuffer, '|END'); let morseLetters = morseCharBuffer.split('_').filter(l => l !== '') // console.log('morseLetters:', morseLetters, morseLetters.length); diff --git a/src/app-modes/PracticeMode.js b/src/app-modes/PracticeMode.js index b9451e5..78ca163 100644 --- a/src/app-modes/PracticeMode.js +++ b/src/app-modes/PracticeMode.js @@ -1,23 +1,28 @@ -import React from 'react'; +import React, {useContext} from 'react'; import '../css/App.css'; -// import useTelegraph from '../hooks/useTelegraph'; -import usePaddleTelegraph from '../hooks/usePaddleTelegraph'; +import useStraightKey from '../hooks/useStraightKey'; +import useElectronicKey from '../hooks/useElectronicKey'; import MorseBufferDisplay from '../components/MorseBufferDisplay' import MorseDisplay from '../components/MorseDisplay' +import {MorseBufferContext} from "../contexts/morseBufferContext" + function PracticeMode() { - console.log("PracticeMode loaded"); - // const {morseCharBuffer, morseWords, clearHistory} = useTelegraph('practice') - usePaddleTelegraph() + // const [telegraphType, setTelegraphType] = useState('electronic') + + // useElectronicKey() + // const {morseCharBuffer, morseWords, clearHistory} = useStraightKey('practice') + const {morseCharBuffer, morseWords} = useContext(MorseBufferContext) + return ( <> - {/*
-
*/} -
+
+
); + } export default React.memo(PracticeMode); diff --git a/src/app-modes/TimedMode.js b/src/app-modes/TimedMode.js index 16d4df4..9ee1ee4 100644 --- a/src/app-modes/TimedMode.js +++ b/src/app-modes/TimedMode.js @@ -1,13 +1,13 @@ import React from 'react'; import '../css/App.css'; -import useTelegraph from '../hooks/useTelegraph'; +import useStraightKey from '../hooks/useStraightKey'; import GameClock from '../components/GameClock' import MorseBufferDisplay from '../components/MorseBufferDisplay' import MorseDisplay from '../components/MorseDisplay' function TimedMode() { - const {morseCharBuffer, morseWords} = useTelegraph('timed') + const {morseCharBuffer, morseWords} = useStraightKey('timed') console.log('TimedMode.js rendered') diff --git a/src/components/ElectronicKey.js b/src/components/ElectronicKey.js new file mode 100644 index 0000000..3d58490 --- /dev/null +++ b/src/components/ElectronicKey.js @@ -0,0 +1,7 @@ +import React from 'react' +// import useStraightKey from '../hooks/useStraightKey'; +import useElectronicKey from '../hooks/useElectronicKey'; + +export default React.memo(function StraightKey() { + useElectronicKey() +}) \ No newline at end of file diff --git a/src/components/KeyTypePicker.js b/src/components/KeyTypePicker.js new file mode 100644 index 0000000..6fb4f21 --- /dev/null +++ b/src/components/KeyTypePicker.js @@ -0,0 +1,25 @@ +import React, {useContext} from "react" +import {KeyTypeContext} from "../contexts/keyTypeContext" + +function KeyTypePicker() { + + const {switchKeyType} = useContext(KeyTypeContext) + + function handleClick(e) { + switchKeyType(e.target.id) + console.log("Switched to " + e.target.id + " keyType."); + } + + return ( +
+ + +
+ ) +} + +export default React.memo(KeyTypePicker) \ No newline at end of file diff --git a/src/components/ModePicker.js b/src/components/ModePicker.js index 0d4ee78..3c0453c 100644 --- a/src/components/ModePicker.js +++ b/src/components/ModePicker.js @@ -1,5 +1,5 @@ import React, {useContext} from "react" -import {GameModeContext} from "../gameContext" +import {GameModeContext} from "../contexts/gameContext" function ModePicker() { diff --git a/src/components/MorseBufferDisplay.js b/src/components/MorseBufferDisplay.js index 09b75cf..92aa736 100644 --- a/src/components/MorseBufferDisplay.js +++ b/src/components/MorseBufferDisplay.js @@ -3,7 +3,7 @@ import DitDahDisplay from "./DitDahDisplay" import morseCode from '../data/morse-reverse.json' function MorseBufferDisplay(props) { - + let ditDahs = props.buffer.split('').map((ditdah,index) => ) let alphanumeric = '' diff --git a/src/components/StraightKey.js b/src/components/StraightKey.js new file mode 100644 index 0000000..8f2b825 --- /dev/null +++ b/src/components/StraightKey.js @@ -0,0 +1,9 @@ +import React from 'react' +import useStraightKey from '../hooks/useStraightKey'; +// import useElectronicKey from '../hooks/useElectronicKey'; + +export default React.memo(function StraightKey(props) { + + useStraightKey(props.gameMode) + +}) \ No newline at end of file diff --git a/src/config.json b/src/config.json index bf51225..ea050cd 100644 --- a/src/config.json +++ b/src/config.json @@ -7,5 +7,6 @@ "slow": 40, "normal": 24, "fast": 17 - } + }, + "historySize": 5 } \ No newline at end of file diff --git a/src/gameContext.js b/src/contexts/gameContext.js similarity index 100% rename from src/gameContext.js rename to src/contexts/gameContext.js diff --git a/src/contexts/keyTypeContext.js b/src/contexts/keyTypeContext.js new file mode 100644 index 0000000..1013e2f --- /dev/null +++ b/src/contexts/keyTypeContext.js @@ -0,0 +1,24 @@ +import React, {Component} from "react" + +const KeyTypeContext = React.createContext() + +class KeyTypeContextProvider extends Component { + state = { + keyType: "straight" + } + + switchKeyType = (type = "straight") => { + this.setState({keyType: type}) + } + + render() { + return ( + + {this.props.children} + + ) + } + +} + +export {KeyTypeContextProvider, KeyTypeContext} diff --git a/src/contexts/morseBufferContext.js b/src/contexts/morseBufferContext.js new file mode 100644 index 0000000..111e05b --- /dev/null +++ b/src/contexts/morseBufferContext.js @@ -0,0 +1,42 @@ +import React, {useState} from "react" + +const MorseBufferContext = React.createContext() + +function MorseBufferContextProvider(props) { + // state = { + // morseCharBuffer: '', + // morseWords: [] + // //morseCharBuffer, morseWords, clearHistory, setMorseCharBuffer, setMorseWords + // } + + const [morseCharBuffer, setMorseCharBuffer] = useState('') + const [morseWords, setMorseWords] = useState([]) + + + // switchKeyType = (type = "straight") => { + // this.setState({keyType: type}) + // } + + // setMorseCharBuffer = (value) => { + // this.setState({morseCharBuffer: value}) + // } + // setMorseWords = (value) => { + // this.setState({morseWords: value}) + // } + + // render() { + return ( + + {props.children} + + ) + // } + +} + +export {MorseBufferContextProvider, MorseBufferContext} diff --git a/src/hooks/useElectronicKey.js b/src/hooks/useElectronicKey.js new file mode 100644 index 0000000..e095b9c --- /dev/null +++ b/src/hooks/useElectronicKey.js @@ -0,0 +1,335 @@ +import {useEffect, useContext, useState} from 'react' +import config from '../config.json' +import {MorseBufferContext} from '../contexts/morseBufferContext' + +// SINGLE/DUAL LEVER TELEGRAPH + +function useElectronicKey(mode = 'practice') { + + const {morseCharBuffer, setMorseCharBuffer, morseWords, setMorseWords} = useContext(MorseBufferContext) + + const timingUnit = config.timingUnit + + const ditMaxTime = 85 // ditMaxTime * 0.365 to get ms, e.g. 85 * 0.365 ~= 31ms + const letterGapMinTime = ditMaxTime*0.36*3 //config.practiceSpeed.normal*3 + const wordGapMaxTime = ditMaxTime*0.36*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 gapTimer = 0 + let gapTime = 0 + let paddlesReleasedSimultaneously = false + + + 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 = () => { + // clearInterval(toneTimer) + // end = toneTime + // console.log('duration:', end-start); + 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 === '-') { + play(ditDah) + .then(setTimeout(() => { + setMorseCharBuffer(prev => prev + ditDah) + resolve(); + }, delay)) + } else { + setTimeout(() => { + // setMorseCharBuffer(prev => prev + ' ') + resolve(); + }, delay) + } + }); + } + + function executeQueue() { + let localQueue = queue + + // Set waitTime to completion of queue (including spaces) + let waitTime = 0 + for (let i in localQueue) { + if (localQueue[i] === '.') { + waitTime += ditMaxTime*2 + } else if (localQueue[i] === '-') { + waitTime += ditMaxTime*4 + } + } + + queueRunning = true + + // Wait till completion of queue to execute + const clear = setTimeout(() => { + queueRunning = false + queue = [] + checkPressed() + }, waitTime) + + // Execute queue + for (let i = 0; i < localQueue.length; i++) { + currentPromise = currentPromise.then(() => { + return playWithSpaces(localQueue[i]) + }); + } + } + + function checkPressed() { + if (leftIsPressed && rightIsPressed) { + // queue.push(queue.slice(-1) === '.' ? '-' : '.') + if (pressedFirst === 'left') { + queue.push('-') + if (!paddlesReleasedSimultaneously) { + // console.log('one'); + queue.push('.') + } + } else { + queue.push('.') + if (!paddlesReleasedSimultaneously) { + // console.log('two'); + queue.push('-') + } + } + } + else if (leftIsPressed && !rightIsPressed) { + queue.push('.') + } + else if (rightIsPressed && !leftIsPressed) { + queue.push('-') + } + if (queue.length > 0) { + executeQueue() + } + } + + function handleInputStart(event) { + event.preventDefault() + + // if (event.keyCode === 188) { + // depressSyncTimeout() + // } + // else if (event.keyCode === 190) { + // testQueue.push('.') + // console.log('testQueue', testQueue); + // } + // else if (event.target.id === 'morseButton') { + // testQueue = [] + // } + // while (testQueue.length > 0) { + // currentPromise = currentPromise.then(() => { + // return playWithSpaces(testQueue.shift()) + // }) + // } + paddlesReleasedSimultaneously = false + + if (event.repeat) { return } + + // if (!leftIsPressed && !rightIsPressed) { + // clearInterval(gapTimer) + // checkGapBetweenInputs() + // } + + if (event.keyCode === 188) { + leftIsPressed = true + if (!rightIsPressed) { pressedFirst = 'left'} + + // Prevent further input if queue is executing + if (!queueRunning) { + checkPressed() + } + } + else if (event.keyCode === 190) { + rightIsPressed = true + if (!leftIsPressed) { pressedFirst = 'right'} + + // Prevent further input if queue is executing + if (!queueRunning) { + checkPressed() + } + } + } + + + 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 + if (gapTime >= letterGapMinTime && gapTime < wordGapMaxTime) { + console.log('letterGapMinTime <= gapTime < wordGapMaxTime:',letterGapMinTime, gapTime, wordGapMaxTime); + if (mode === 'practice') { + setMorseCharBuffer(prev => prev + ' ') + } else if (mode === 'challenge') { + console.log("UNDERSCORE ADDED"); + setMorseCharBuffer(prev => prev + '_') + } + clearInterval(gapTimer) + gapTimer = 0 + } + } + + function handleInputEnd(event) { + event.preventDefault() + + if (event.keyCode === 188) { + leftIsPressed = false + + if (pressedFirst === 'left') { pressedFirst = null } + + if (!depressSyncTimerRunning) { startDepressSyncTimer() } + else { stopDepressSyncTimer() } + } + else if (event.keyCode === 190) { + rightIsPressed = false + if (pressedFirst === 'right') { pressedFirst = null } + + if (!depressSyncTimerRunning) { startDepressSyncTimer() } + else { stopDepressSyncTimer() } + } + // if (!leftIsPressed && !rightIsPressed ) { startGapTimer() } + } + + 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 \ No newline at end of file diff --git a/src/hooks/useElectronicKey_iambicB.js b/src/hooks/useElectronicKey_iambicB.js new file mode 100644 index 0000000..2f70fc9 --- /dev/null +++ b/src/hooks/useElectronicKey_iambicB.js @@ -0,0 +1,344 @@ +import {useEffect, useContext, useState} from 'react' +import config from '../config.json' +import {MorseBufferContext} from '../contexts/morseBufferContext' + +// SINGLE/DUAL LEVER TELEGRAPH + +function useElectronicKey(mode = 'practice') { + + const {morseCharBuffer, setMorseCharBuffer, morseWords, setMorseWords} = useContext(MorseBufferContext) + + const timingUnit = config.timingUnit + + const ditMaxTime = 85 // ditMaxTime * 0.365 to get ms, e.g. 85 * 0.365 ~= 31ms + const letterGapMinTime = ditMaxTime*0.36*3 //config.practiceSpeed.normal*3 + const wordGapMaxTime = ditMaxTime*0.36*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 gapTimer = 0 + let gapTime = 0 + const [paddlesReleasedSimultaneously, setPaddlesReleasedSimultaneously] = useState(false) + + + 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 = () => { + // clearInterval(toneTimer) + // end = toneTime + // console.log('duration:', end-start); + 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 === '-') { + play(ditDah) + .then(setTimeout(() => { + setMorseCharBuffer(prev => prev + ditDah) + resolve(); + }, delay)) + } else { + setTimeout(() => { + // setMorseCharBuffer(prev => prev + ' ') + resolve(); + }, delay) + } + }); + } + + function executeQueue() { + let localQueue = queue + + // Set waitTime to completion of queue (including spaces) + let waitTime = 0 + for (let i in localQueue) { + if (localQueue[i] === '.') { + waitTime += ditMaxTime*2 + } else if (localQueue[i] === '-') { + waitTime += ditMaxTime*4 + } + } + + queueRunning = true + + // Wait till completion of queue to execute + const clear = setTimeout(() => { + queueRunning = false + queue = [] + checkPressed() + }, waitTime) + + // Execute queue + for (let i = 0; i < localQueue.length; i++) { + currentPromise = currentPromise.then(() => { + return playWithSpaces(localQueue[i]) + }); + } + } + + function checkPressed() { + if (leftIsPressed && rightIsPressed) { + if (pressedFirst === 'left') { + queue.push('-') + if (!paddlesReleasedSimultaneously) { + console.log('one'); + queue.push('.') + } + } else { + queue.push('.') + if (!paddlesReleasedSimultaneously) { + console.log('two'); + queue.push('-') + } + } + } + else if (leftIsPressed && !rightIsPressed) { + queue.push('.') + } + else if (rightIsPressed && !leftIsPressed) { + queue.push('-') + } + if (queue.length > 0) { + executeQueue() + } + } + + // function depressSyncTimeout() { + // let depressSyncTimeO = setTimeout(() => { + // if (testQueue.length > 0) { + // let x = testQueue.shift() + // console.log('testQueue', x, testQueue); + // depressSyncTimeout() + // } else { + // clearTimeout(depressSyncTimeO) + // } + // }, 1000) + // } + + function handleInputStart(event) { + event.preventDefault() + + // if (event.keyCode === 188) { + // depressSyncTimeout() + // } + // else if (event.keyCode === 190) { + // testQueue.push('.') + // console.log('testQueue', testQueue); + // } + // else if (event.target.id === 'morseButton') { + // testQueue = [] + // } + // while (testQueue.length > 0) { + // currentPromise = currentPromise.then(() => { + // return playWithSpaces(testQueue.shift()) + // }) + // } + setPaddlesReleasedSimultaneously(false) + + if (event.repeat) { return } + + // if (!leftIsPressed && !rightIsPressed) { + // clearInterval(gapTimer) + // checkGapBetweenInputs() + // } + + if (event.keyCode === 188) { + leftIsPressed = true + if (!rightIsPressed) { pressedFirst = 'left'} + + // Prevent further input if queue is executing + if (!queueRunning) { + checkPressed() + } + } + else if (event.keyCode === 190) { + rightIsPressed = true + if (!leftIsPressed) { pressedFirst = 'right'} + + // Prevent further input if queue is executing + if (!queueRunning) { + checkPressed() + } + } + } + + + 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) { + setPaddlesReleasedSimultaneously(true) + } + 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 + if (gapTime >= letterGapMinTime && gapTime < wordGapMaxTime) { + console.log('letterGapMinTime <= gapTime < wordGapMaxTime:',letterGapMinTime, gapTime, wordGapMaxTime); + if (mode === 'practice') { + setMorseCharBuffer(prev => prev + ' ') + } else if (mode === 'challenge') { + console.log("UNDERSCORE ADDED"); + setMorseCharBuffer(prev => prev + '_') + } + clearInterval(gapTimer) + gapTimer = 0 + } + } + + function handleInputEnd(event) { + event.preventDefault() + + if (event.keyCode === 188) { + leftIsPressed = false + + if (pressedFirst === 'left') { pressedFirst = null } + + if (!depressSyncTimerRunning) { startDepressSyncTimer() } + else { stopDepressSyncTimer() } + } + else if (event.keyCode === 190) { + rightIsPressed = false + if (pressedFirst === 'right') { pressedFirst = null } + + if (!depressSyncTimerRunning) { startDepressSyncTimer() } + else { stopDepressSyncTimer() } + } + // if (!leftIsPressed && !rightIsPressed ) { startGapTimer() } + } + + 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 \ No newline at end of file diff --git a/src/hooks/useKeySelection.js b/src/hooks/useKeySelection.js new file mode 100644 index 0000000..e69de29 diff --git a/src/hooks/usePaddleTelegraph.js b/src/hooks/usePaddleTelegraph.js deleted file mode 100644 index 22bf72e..0000000 --- a/src/hooks/usePaddleTelegraph.js +++ /dev/null @@ -1,175 +0,0 @@ -import {useEffect} from 'react' -import config from '../config.json' - -// SINGLE/DUAL LEVER TELEGRAPH - -function usePaddleTelegraph() { - - const ditMaxTime = 85 - let leftIsPressed = false - let rightIsPressed = false - let queueRunning = false - let queue = [] - - 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 - - - // 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) - - 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 === '-') { - play(ditDah) - .then(setTimeout(() => { - resolve(); - }, delay)) - } else { - setTimeout(() => { - resolve(); - }, delay) - } - }); - } - - function executeQueue() { - let localQueue = queue - console.log('localQueue',localQueue); - - // Set wait time to completion of queue (including spaces) - let waitTime = 0 - for (let i in localQueue) { - if (localQueue[i] === '.') { - waitTime += ditMaxTime*2 - } else if (localQueue[i] === '-') { - waitTime += ditMaxTime*4 - } - } - - queueRunning = true - - // Wait till completion of queue to execute - setTimeout(() => { - queueRunning = false - queue = [] - checkPressed() - }, waitTime) - - // Execute queue - for (let i = 0; i < localQueue.length; i++) { - currentPromise = currentPromise.then(() => { - return playWithSpaces(localQueue[i]) - }); - } - } - - function checkPressed() { - if (leftIsPressed) { - queue.push('.') - } - if (rightIsPressed) { - queue.push('-') - } - if (queue.length > 0) { - executeQueue() - } - } - - function handleInputStart(event) { - event.preventDefault() - - if (event.repeat) { return } - - if (event.keyCode === 188) { - leftIsPressed = true - console.log("LEFT DOWN"); - - // Prevent further input if queue is executing - if (!queueRunning) { - checkPressed() - } - } - else if (event.keyCode === 190) { - rightIsPressed = true - console.log("RIGHT DOWN"); - - // Prevent further input if queue is executing - if (!queueRunning) { - checkPressed() - } - } - } - - function handleInputEnd(event) { - event.preventDefault() - - if (event.keyCode === 188) { - leftIsPressed = false - console.log("LEFT UP"); - } - else if (event.keyCode === 190) { - rightIsPressed = false - console.log("RIGHT UP"); - } - } - - 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 - }, []) - -} - -export default usePaddleTelegraph \ No newline at end of file diff --git a/src/hooks/useTelegraph.js b/src/hooks/useStraightKey.js similarity index 86% rename from src/hooks/useTelegraph.js rename to src/hooks/useStraightKey.js index 77050fc..fa35b91 100644 --- a/src/hooks/useTelegraph.js +++ b/src/hooks/useStraightKey.js @@ -1,12 +1,15 @@ -import {useState, useEffect} from 'react' +import {useEffect, useContext} from 'react' +import {MorseBufferContext} from '../contexts/morseBufferContext' import config from '../config.json' // STRAIGHT KEY TELEGRAPH -function useTelegraph(mode = 'practice') { +function useStraightKey(mode = 'practice') { - const [morseCharBuffer, setMorseCharBuffer] = useState('') // e.g. '-..' - const [morseWords, setMorseWords] = useState([]) // e.g. [['-..','.','-,'], ['...','---','...']] + const {morseCharBuffer, setMorseCharBuffer, morseWords, setMorseWords} = useContext(MorseBufferContext) + // const [morseCharBuffer, setMorseCharBuffer] = useState('') // e.g. '-..' + // const [morseWords, setMorseWords] = useState([]) // e.g. [['-..','.','-,'], ['...','---','...']] + let charTimer = 0 let charTime = 0 @@ -18,7 +21,7 @@ function useTelegraph(mode = 'practice') { const ditMaxTime = config.practiceSpeed.normal const letterGapMinTime = ditMaxTime*3 const wordGapMaxTime = ditMaxTime*7 - const morseHistorySize = 5 + const morseHistorySize = config.historySize // Tone Setup let AudioContext = window.AudioContext || window.webkitAudioContext || false @@ -169,6 +172,11 @@ function useTelegraph(mode = 'practice') { return function cleanup() { document.removeEventListener('keydown', handleInputStart) document.removeEventListener('keyup', handleInputEnd) + const morseButton = document.getElementById('morseButton') + morseButton.removeEventListener('mousedown', handleInputStart) + morseButton.removeEventListener('touchstart', handleInputStart) + morseButton.removeEventListener('mouseup', handleInputEnd) + morseButton.removeEventListener('touchend', handleInputEnd) clearHistory() } // eslint-disable-next-line @@ -199,4 +207,4 @@ function useTelegraph(mode = 'practice') { return {morseCharBuffer, morseWords, clearHistory, setMorseCharBuffer, setMorseWords} } -export default useTelegraph \ No newline at end of file +export default useStraightKey \ No newline at end of file diff --git a/src/index.js b/src/index.js index 959578c..83e7fd2 100644 --- a/src/index.js +++ b/src/index.js @@ -3,11 +3,15 @@ import ReactDOM from 'react-dom'; import './index.css'; import App from './App'; import * as serviceWorker from './serviceWorker'; -import {GameModeContextProvider} from "./gameContext" +import {GameModeContextProvider} from "./contexts/gameContext" +import {KeyTypeContextProvider} from "./contexts/keyTypeContext" + ReactDOM.render( - + + + , document.getElementById('root'));