diff --git a/index.html b/index.html index 04dd9ce02115b75e8d6fed99bb02e60a7bb1c571..86e42aa5dbd039d88d9b5098de34d73ea9fce075 100644 --- a/index.html +++ b/index.html @@ -15,6 +15,7 @@ <!-- <button type="button" id="reload" onclick="window.location.reload()"> Reload </button> --> + <!-- <button type="button" id="load-audio-btn">Load Audio</button> --> </aside> <script type="module" src="/src/main.js"></script> </body> diff --git a/src/main.js b/src/main.js index e3eeb71b8fe38dfc860f6af6b58df03dfbe64261..5547a40e430a4e3a0bc1b415201adcbc3366dca5 100644 --- a/src/main.js +++ b/src/main.js @@ -5,7 +5,7 @@ const config = { controls: { mouse: true, button: true, - voice: true, + voice: false, motion: true, }, }; @@ -102,8 +102,10 @@ const navigation = { }, }; +let logId = 0; + // Create WebSocket connection. -const socket = new WebSocket(`wss://192.168.227.52:443`); +const socket = new WebSocket(`wss://192.168.27.52:443`); // init application function init() { @@ -247,7 +249,7 @@ function buildPageContents(object) { if (item.label.toLowerCase() !== item.command.toLowerCase()) { const commandEl = document.createElement("div"); commandEl.className = "command"; - commandEl.innerHTML = item.command; + commandEl.innerHTML = `<span><img src="/src/media/mic.svg" alt="mic"/><span>${item.command}</span></span>`; itemEl.appendChild(commandEl); } @@ -305,7 +307,7 @@ function focusNavigationItem(index, broadcast = true) { const navigationItem = document.querySelector( `#navigation > ul > li:nth-child(${index})` ); - navigationItem.dataset.selected = ""; + if (navigationItem) navigationItem.dataset.selected = ""; // broadcast focus event to other clients if (broadcast && socket.readyState === WebSocket.OPEN) @@ -426,6 +428,40 @@ function addMouseControls() { // add button controls // =================== function addButtonControls() { + // trying to access the audio buttons of android device + // let audioButton = document.getElementById("load-audio-btn"); + // console.log(audioButton); + // audioButton.addEventListener("click", (event) => { + + // console.log("load audio"); + // // create an AudioContext object + // var audioCtx = new AudioContext(); + + // // create an HTMLAudioElement + // var audio = new Audio("/src/media/White-Noise.mp3"); + + // // create an AudioBufferSourceNode + // var source = audioCtx.createMediaElementSource(audio); + + // //audio.play(); + + // // connect the AudioBufferSourceNode to the AudioContext's destination + // source.connect(audioCtx.destination); + + // // add an event listener to the 'volumechange' event + // audio.addEventListener("volumechange", function (event) { + // console.log("volume change event"); + // // get the new volume value + // var volume = audio.volume; + + // // set the gain of the AudioBufferSourceNode to the new volume value + // var gainNode = audioCtx.createGain(); + // gainNode.gain.value = volume; + // source.connect(gainNode); + // gainNode.connect(audioCtx.destination); + // }); + // }); + // add two button controls for menu document.addEventListener("keydown", onKeyDown); @@ -451,7 +487,6 @@ function addButtonControls() { else if (event.code === "ArrowRight") moveDownNavigationLevel(); else if (event.code === "ArrowLeft" || event.code === "Escape") moveUpNavigationLevel(); - else if (event.code === "VolumeUp") printLogMsg("yes"); } // const toggleButtonControlsEl = document.getElementById( @@ -573,19 +608,23 @@ function addVoiceControls() { // add motion controls // =================== function addMotionControls() { - let referenceY; - let sensitivity = 4000; + let referenceY; // current zero reference point + let sensitivity = 5000; // change motion sensitivity let screenCenter = new Point(window.innerWidth / 2, window.innerHeight / 2); let idleTimeout; - let idleDelay = 3000; + let intervalId; + let idleDelay = 3000; //idle time before reseting reference point + let autoSelect = false; // selecting items with time delay + let drawMotionFlag = true; // draw motion values for debugging // motion buffer stuff - let motionBuffer = []; - let lastAverageY = 0; - let bufferIterations = 0; - const bufferIterationLimit = 10; - const bufferLength = 20; - const motionThreshold = 0.02; + let motionBuffer = []; // motion value buffer + let lastAverageY = 0; // last average of buffer values + let bufferIterations = 0; // how many times buffer has been iterated + const bufferIterationLimit = 5; // iterations before new average calculation is triggered + const bufferLength = 10; // length of value buffer + const noiseThreshold = 0.02; // threshold to avoid general sensor noise + const inactiveThreshold = 10; // threshold to define inactive area in the reference // create canvas element for motion visualization const canvasEl = document.createElement("canvas"); @@ -617,68 +656,105 @@ function addMotionControls() { // write quaternion y value into motion buffer motionBuffer.push(y); - // limit buffer length - if (motionBuffer.length > bufferLength) motionBuffer.shift(); - // check motion range within buffer - if (motionBuffer.length === bufferLength) { - let averageY, averageYDiff; - // calculate average every bufferIterations - if (!averageY || bufferIterations > bufferIterationLimit) { - // calculate average value of array numbers - averageY = motionBuffer.reduce((a, b) => a + b) / motionBuffer.length; - if (!referenceY) referenceY = averageY; // set initial reference point - bufferIterations = 0; - } - bufferIterations++; - averageYDiff = Math.abs(lastAverageY - averageY); - if (averageYDiff > motionThreshold && referenceY !== undefined) { - // reseting reference point after a delay of holding still - clearTimeout(idleTimeout); - idleTimeout = setTimeout(() => { - referenceY = averageY; - - // start approaching new reference point - // somehow this currently only executes once... - let incrementValue = 0.001; - const intervalId = setInterval(() => { - if (referenceY < averageY) { - referenceY += incrementValue; - if (referenceY >= averageY) { - clearInterval(intervalId); - referenceY = averageY; // Ensure exact match with target - } - } else if (referenceY > averageY) { - referenceY -= incrementValue; - if (referenceY <= averageY) { - clearInterval(intervalId); - referenceY = averageY; // Ensure exact match with target - } - } else clearInterval(intervalId); - }, 100); - }, idleDelay); - - lastAverageY = averageY; + // wait for buffer to collect enough datapoints + if (motionBuffer.length > bufferLength) { + motionBuffer.shift(); // limit buffer length + + // calculate the new average value as soon as bufferiterationlimit reached + if (bufferIterations > bufferIterationLimit) { + // calculate average value of values + let averageY = + motionBuffer.reduce((a, b) => a + b) / motionBuffer.length; + + // on first iteration + if (!referenceY) { + referenceY = averageY; // set initial reference point + lastAverageY = averageY; // set initial last avaerage point + } + + // calculate target position let targetY = averageY; targetY = (targetY - referenceY) * -sensitivity; - drawMotion(targetY); - let itemEl = focusClosestItem(targetY); - let progressEl = itemEl.querySelector(".progress"); - progressEl.style.animation = "progress 3s ease-out forwards"; + // draw sensor motion for debugging + if (drawMotionFlag) drawMotion(targetY); + + // select item at target position + let averageYDiff = Math.abs(lastAverageY - averageY); + let distanceToReference = Math.abs(targetY); + + if ( + averageYDiff > noiseThreshold && // exeeding noise threshold + distanceToReference > inactiveThreshold // exeeding min distance to reference + ) { + // clear timeout monitoring if pointer is within reasonable distance + if (distanceToReference < 400) { + clearTimeout(idleTimeout); + clearInterval(intervalId); + + // reseting reference point after a delay of holding still + idleTimeout = setTimeout(() => { + printLogMsg("restart timer"); + // start approaching new reference point + let incrementValue = 0.01; + intervalId = setInterval(() => { + if (referenceY < averageY) { + printLogMsg("down"); + referenceY += incrementValue; + if (referenceY >= averageY) { + printLogMsg("clear interval down"); + referenceY = averageY; // Ensure exact match with target + clearInterval(intervalId); + } + } else if (referenceY > averageY) { + printLogMsg("up"); + referenceY -= incrementValue; + if (referenceY <= averageY) { + printLogMsg("clear interval up"); + referenceY = averageY; // Ensure exact match with target + clearInterval(intervalId); + } + } else { + printLogMsg("clear interval neutral"); + clearInterval(intervalId); + } + }, 100); + focusClosestItem(referenceY); + }, idleDelay); + } + + lastAverageY = averageY; // save averageY for later comparison + + let itemEl = focusClosestItem(targetY); + + if (autoSelect) { + let progressEl = itemEl.querySelector(".progress"); + progressEl.style.animation = "progress 3s ease-out forwards"; + } + } + bufferIterations = 0; } + bufferIterations++; } } function drawMotion(y) { if (canvasEl.getContext) { const ctx = canvasEl.getContext("2d"); - const pointSize = 20; + const pointSize = 10; ctx.clearRect(0, 0, canvasEl.width, canvasEl.height); ctx.strokeStyle = "rgba(255, 0, 255, .7)"; ctx.lineWidth = 5; ctx.beginPath(); + ctx.arc( + screenCenter.x, + screenCenter.y, + inactiveThreshold, + 0, + 2 * Math.PI + ); ctx.moveTo(screenCenter.x, screenCenter.y); ctx.lineTo(screenCenter.x, screenCenter.y + y); ctx.stroke(); @@ -686,6 +762,8 @@ function addMotionControls() { ctx.beginPath(); ctx.arc(screenCenter.x, screenCenter.y + y, pointSize, 0, 2 * Math.PI); + ctx.fill(); + ctx.fillStyle = "rgb(255, 0, 255)"; ctx.stroke(); } } @@ -695,8 +773,9 @@ function addMotionControls() { function printLogMsg(message) { const logEl = document.getElementById("log"); if (logEl.scrollHeight >= 10000) logEl.innerHTML = ""; - logEl.innerHTML += `${message}<br>`; + logEl.innerHTML += `${logId}: ${message}<br>`; logEl.scrollTop = logEl.scrollHeight; + logId++; console.log(message); } diff --git a/src/media/White-Noise.mp3 b/src/media/White-Noise.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..86410105e2157fa3b26a61941e6c00d06c520efa Binary files /dev/null and b/src/media/White-Noise.mp3 differ diff --git a/src/styles/main.css b/src/styles/main.css index 1c87e4dfdac1d5f0a6f2485433d6a72e0443e9db..0f2f0f252c885e8476c7a31439ccf82b2a713d47 100644 --- a/src/styles/main.css +++ b/src/styles/main.css @@ -69,20 +69,28 @@ background-image: url("/src/media/x.svg"); } .link .command { - font-size: 0.4em; - line-height: 1em; - font-style: italic; + margin-top: -0.3em; + opacity: 0.75; +} +.link .command > span { + font-size: 0.5em; + font-weight: 300; text-transform: capitalize; - color: var(--color-grey-01); + border: solid 2px white; + border-radius: 10px; + display: inline-flex; + align-items: center; + padding: 0 0.4em; + height: 1.6em; } -.link .command:before { - content: ""; +.link .command > span > span:before, +.link .command > span > span:after { + content: "\""; +} +.link .command img { display: inline-block; - width: 0.8em; - height: 0.8em; margin-right: 0.4em; - background: url("/src/media/mic.svg") center center no-repeat; - background-size: 0.8em; + height: 0.8em; } .link[data-selected] a { border-color: var(--color-white); @@ -92,6 +100,18 @@ opacity: 1; background-position: center center; } +.link[data-selected] .command { + opacity: 1; +} +.link[data-selected] .command > span { + background: white; + color: black; + border-top-left-radius: 0; + border-top-right-radius: 0; +} +.link[data-selected] .command img { + filter: invert(1); +} .link .progress { height: 100%; width: 0%; diff --git a/src/styles/main.less b/src/styles/main.less index f9a3b506b8fd9bde17bd960293e9761bab0eb14c..3f79a0fb93d3f71e0e904317bd52988a122cf1d1 100644 --- a/src/styles/main.less +++ b/src/styles/main.less @@ -78,20 +78,29 @@ } .command { - font-size: 0.4em; - line-height: 1em; - font-style: italic; - text-transform: capitalize; - color: var(--color-grey-01); - - &:before { - content: ""; + margin-top: -.3em; + opacity: .75; + + > span { + font-size: 0.5em; + font-weight: 300; + text-transform: capitalize; + border: solid 2px white; + border-radius: 10px; + display: inline-flex; + align-items: center; + padding: 0 .4em; + height: 1.6em; + + > span:before, > span:after { + content:"\""; + } + } + + img { display: inline-block; - width: 0.8em; - height: 0.8em; - margin-right: 0.4em; - background: url("/src/media/mic.svg") center center no-repeat; - background-size: 0.8em; + margin-right: .4em; + height: .8em; } } @@ -104,6 +113,19 @@ opacity: 1; background-position: center center; } + + .command { + opacity: 1; + > span { + background: white; + color: black; + border-top-left-radius: 0; + border-top-right-radius: 0; + } + img { + filter: invert(1); + } + } } .progress {