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(); +