From a04786edd85cc691dc2382dfd32bfd5f7473acef Mon Sep 17 00:00:00 2001 From: Eugene Serb <46799701+eugene-serb@users.noreply.github.com> Date: Wed, 15 Jun 2022 15:35:22 +0300 Subject: [PATCH] First commit of the prototype of the vibration master program --- css/vibration-master.css | 77 ++++++++ index.html | 134 +++++++++++++ js/vibration-master.js | 392 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 603 insertions(+) create mode 100644 css/vibration-master.css create mode 100644 index.html create mode 100644 js/vibration-master.js diff --git a/css/vibration-master.css b/css/vibration-master.css new file mode 100644 index 0000000..fbd4a43 --- /dev/null +++ b/css/vibration-master.css @@ -0,0 +1,77 @@ +/* -------------- */ +/* GAMEPAD MASTER */ +/* -------------- */ + +@charset 'UTF-8'; + +.vibration-master { + display: flex; + flex-direction: column; + justify-content: flex-start; + gap: 16px; +} + +.message-box { + text-align: center; +} + +.device-box { + display: flex; + flex-direction: column; + justify-content: space-between; + gap: 16px; + text-align: center; +} + + .device-list { + display: flex; + flex-direction: row; + justify-content: space-around; + gap: 16px; + } + + .list-item { + max-width: 100%; + padding: 16px; + border: 4px solid var(--color-background); + outline: 2px solid var(--color-border-alpha); + border-radius: 4px; + display: flex; + flex-direction: column; + justify-content: space-between; + gap: 16px; + } + + @media only screen and (min-width: 640px) { + .list-item { + max-width: 50%; + } + } + + .list-item_selected { + outline: 2px solid var(--color-border); + } + + .list-item__info { + padding: 16px; + display: flex; + flex-direction: column; + gap: 16px; + } + + .list-item__info > div { + display: grid; + grid-template-columns: repeat(3, 1fr); + } + +.controls-banner { + width: 100%; + text-align: center; +} + + .controls-banner__list { + display: flex; + flex-direction: column; + justify-content: space-between; + } + diff --git a/index.html b/index.html new file mode 100644 index 0000000..4f02344 --- /dev/null +++ b/index.html @@ -0,0 +1,134 @@ + + + + Eugene Serb – Vibration Master + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ +
+ +
+
+ +
+

Eugene Serb – Vibration Master

+
+

Vibration Master

+
+
+ +
+
+

Device List

+
+
+
+

Disclaimer

+
+ + The author of the program code and the publisher is not responsible for the possible consequences of using the program. You use the program at your own risk. Please consult your doctor before using the program. + +
+
+
+
+
+ + + + + + + + + diff --git a/js/vibration-master.js b/js/vibration-master.js new file mode 100644 index 0000000..67461f4 --- /dev/null +++ b/js/vibration-master.js @@ -0,0 +1,392 @@ +/* -------------- */ +/* GAMEPAD MASTER */ +/* -------------- */ + +'use strict'; + +const __PATTERNS = [ + /* Constant, 0s, 1s */ + { + name: 'Constant Weak', + type: 'Simple', + pattern: [ + { + startDelay: 0, + duration: 1000, + weakMagnitude: 1.0, + strongMagnitude: 0.0, + }, + ], + }, + { + name: 'Constant Strong', + type: 'Simple', + pattern: [ + { + startDelay: 0, + duration: 1000, + weakMagnitude: 0.0, + strongMagnitude: 1.0, + }, + ], + }, + { + name: 'Constant Max', + type: 'Simple', + pattern: [ + { + startDelay: 0, + duration: 1000, + weakMagnitude: 1.0, + strongMagnitude: 1.0, + }, + ], + }, + /* Dotted, 0.1s, 0.1s */ + { + name: 'Dotted Weak', + type: 'Simple', + pattern: [ + { + startDelay: 100, + duration: 100, + weakMagnitude: 1.0, + strongMagnitude: 0.0, + }, + ], + }, + { + name: 'Dotted Strong', + type: 'Simple', + pattern: [ + { + startDelay: 100, + duration: 100, + weakMagnitude: 0.0, + strongMagnitude: 1.0, + }, + ], + }, + { + name: 'Dotted Max', + type: 'Simple', + pattern: [ + { + startDelay: 0, + duration: 100, + weakMagnitude: 1.0, + strongMagnitude: 1.0, + }, + ], + }, + /* Short Dashed, 0.1s, 0.25s */ + { + name: 'Short Dashed Weak', + type: 'Simple', + pattern: [ + { + startDelay: 100, + duration: 250, + weakMagnitude: 1.0, + strongMagnitude: 0.0, + }, + ], + }, + { + name: 'Short Dashed Strong', + type: 'Simple', + pattern: [ + { + startDelay: 100, + duration: 250, + weakMagnitude: 0.0, + strongMagnitude: 1.0, + }, + ], + }, + { + name: 'Short Dashed Max', + type: 'Simple', + pattern: [ + { + startDelay: 0, + duration: 250, + weakMagnitude: 1.0, + strongMagnitude: 1.0, + }, + ], + }, + /* Long Dashed, 0.1s, 0.5s */ + { + name: 'Long Dashed Weak', + type: 'Simple', + pattern: [ + { + startDelay: 100, + duration: 500, + weakMagnitude: 1.0, + strongMagnitude: 0.0, + }, + ], + }, + { + name: 'Long Dashed Strong', + type: 'Simple', + pattern: [ + { + startDelay: 100, + duration: 500, + weakMagnitude: 0.0, + strongMagnitude: 1.0, + }, + ], + }, + { + name: 'Long Dashed Max', + type: 'Simple', + pattern: [ + { + startDelay: 0, + duration: 500, + weakMagnitude: 1.0, + strongMagnitude: 1.0, + }, + ], + }, +]; + +class Gamepad { + constructor(gamepad, $container, library) { + this.unit = gamepad; + this.$container = $container; + this.library = library; + this.init(); + }; + + init = () => { + this.id = Date.now(); + this.isSelected = false; + this.isVibrating = false; + this.isLocked = false; + this.cooldown = 0; + this.index = 0; + this.pattern = [ + { + startDelay: 0, + duration: 1000, + weakMagnitude: 1.0, + strongMagnitude: 1.0, + } + ]; + this.generateBox(); + }; + + delete = () => { + this.$list_item.parentNode.removeChild(this.$list_item); + }; + generateBox = () => { + const $list_item = document.createElement('div'); + const $info_box = document.createElement('div'); + /*const $button = document.createElement('button');*/ + + $list_item.classList.add('list-item'); + $info_box.classList.add('list-item__info'); + /*$button.innerText = 'Select';*/ + + /*$button.addEventListener('click', () => { + this.isSelected = !this.isSelected; + });*/ + + $list_item.appendChild($info_box); + /*$list_item.appendChild($button);*/ + this.$container.appendChild($list_item); + + this.$list_item = $list_item; + this.$info_box = $info_box; + this.draw(); + }; + draw = () => { + this.$info_box.innerHTML = ` +

#${this.unit.index + 1}. ${this.unit.id}

+ Vibration Actuator: ${this.unit.vibrationActuator ? 'Available' : 'missing'} +
+ Status: + ${this.isVibrating ? 'Vibrating' : 'Idle'} + A / B + Key locked: + ${this.isLocked ? 'Yes' : 'No'} + X + Y + Mode: + ${this.index + 1}. ${this.library[this.index].name} + LB / RB +
`; + if (this.isSelected === true) { + this.$list_item.classList.add('list-item_selected'); + } else { + this.$list_item.classList.remove('list-item_selected'); + }; + }; + + update = () => { + let gamepads = navigator.getGamepads(); + this.unit = gamepads[this.unit.index]; + }; + + reset = () => { + this.isVibrating = false; + this.unit.vibrationActuator.reset(); + }; + vibrate = async () => { + this.isVibrating = true; + this.pattern = this.library[this.index].pattern; + + while (this.isVibrating) { + for (let i = 0; i < this.pattern.length; i++) { + if (this.isVibrating) { + this.unit.vibrationActuator.playEffect('dual-rumble', this.pattern[i]); + await this.sleep(this.pattern[i].startDelay + this.pattern[i].duration + 100); + } else { + return; + }; + }; + }; + }; + sleep = (ms) => { + return new Promise(resolve => setTimeout(resolve, ms)); + }; + previous = () => { + if (Date.now() >= this.cooldown) { + if (this.index === 0) { + this.index = this.library.length - 1; + } else { + this.index--; + }; + this.pattern = this.library[this.index].pattern; + this.cooldown = Date.now() + 500; + }; + }; + next = () => { + if (Date.now() >= this.cooldown) { + if (this.index === this.library.length - 1) { + this.index = 0; + } else { + this.index++; + }; + this.pattern = this.library[this.index].pattern; + this.cooldown = Date.now() + 500; + }; + }; + lock = () => { + if (Date.now() >= this.cooldown) { + this.isLocked = !this.isLocked; + this.cooldown = Date.now() + 500; + }; + }; +}; + +class GamepadMaster { + constructor() { + this.init(); + }; + init = () => { + this.#DOMs(); + + if (!this.checkGamepadSupport()) { + console.log(`This browser does not support of gamepads.`); + this.$MESSAGE.innerText = `This browser does not support of gamepads.`; + return; + } else { + console.log(`Press any gamepad's button or connect new gamepad.`); + this.$MESSAGE.innerText = `Press any gamepad's button or connect new gamepad.`; + }; + + this.gamepads = []; + this.#eventListeners(); + + this.interval = setInterval(this.eventLoop, 1); + }; + + eventLoop = () => { + this.update(); + this.draw(); + this.eventHandler(); + }; + update = () => { + if (this.gamepads.length > 0) { + this.$MESSAGE_BOX.classList.add('hidden'); + this.$DEVICE_BOX.classList.remove('hidden'); + this.gamepads.forEach(gamepad => { + gamepad.update(); + }); + } else { + this.$MESSAGE_BOX.classList.remove('hidden'); + this.$DEVICE_BOX.classList.add('hidden'); + }; + }; + draw = () => { + if (this.gamepads.length > 0) { + this.gamepads.forEach(gamepad => { + gamepad.draw(); + }); + }; + }; + eventHandler = () => { + if (this.gamepads.length > 0) { + this.gamepads.forEach(gamepad => { + if (gamepad.unit.vibrationActuator) { + if (gamepad.unit.buttons[2].pressed === true && + gamepad.unit.buttons[3].pressed === true) { + gamepad.lock(); + }; + if (gamepad.isLocked === false) { + if (gamepad.unit.buttons[0].pressed === true) { + if (gamepad.isVibrating === false) { + gamepad.vibrate(); + }; + }; + if (gamepad.unit.buttons[1].pressed === true) { + gamepad.reset(); + }; + if (gamepad.unit.buttons[4].pressed === true) { + gamepad.previous(); + }; + if (gamepad.unit.buttons[5].pressed === true) { + gamepad.next(); + }; + }; + }; + }); + }; + }; + + checkGamepadSupport = () => { + return 'getGamepads' in window.navigator; + }; + + #DOMs = () => { + this.$MESSAGE = document.querySelector('#message'); + this.$MESSAGE_BOX = document.querySelector('.message-box'); + this.$DEVICE_LIST = document.querySelector('#device-list'); + this.$DEVICE_BOX = document.querySelector('#device-box'); + }; + #eventListeners = () => { + window.addEventListener('gamepadconnected', (event) => { + this.gamepads.push(new Gamepad(event.gamepad, this.$DEVICE_LIST, __PATTERNS)); + }); + window.addEventListener('gamepaddisconnected', (event) => { + this.gamepads.forEach((gamepad, index) => { + if (gamepad.unit.id === event.gamepad.id) { + this.gamepads[index].delete(); + this.gamepads.splice(index, 1); + }; + }); + }); + }; +}; + +/* -------------- */ +/* INITIALIZATION */ +/* -------------- */ + +const GAMEPAD_MASTER = new GamepadMaster(); +