mockband

Firmware for Wii Rock Band guitar and drum kit
git clone https://git.woozle.org/neale/mockband.git

mockband / docs
Neale Pickett  ·  2024-01-04

gamepad.html

  1<!--
  2/*
  3 * Copyright 2019 Gregg Tavares
  4 *
  5 * Permission is hereby granted, free of charge, to any person obtaining a
  6 * copy of this software and associated documentation files (the "Software"),
  7 * to deal in the Software without restriction, including without limitation
  8 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
  9 * and/or sell copies of the Software, and to permit persons to whom the
 10 * Software is furnished to do so, subject to the following conditions:
 11 *
 12 * The above copyright notice and this permission notice shall be included in
 13 * all copies or substantial portions of the Software.
 14 *
 15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 16 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 18 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 19 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 20 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 21 * DEALINGS IN THE SOFTWARE.
 22 */
 23-->
 24<style>
 25body {
 26    background: #444;
 27    color: white;
 28    font-family: monospace;
 29}
 30#gamepads>* {
 31    background: #333;
 32    padding: 1em;
 33    margin: 10px 5px 0 0;
 34}
 35#gamepads pre {
 36    white-space: pre-wrap;
 37}
 38
 39.head {
 40  display: flex;
 41}
 42.head .id {
 43  flex: 1 1 auto;
 44}
 45.head .index,
 46.head .id {
 47  display: inline-block;
 48  background: #222;
 49  padding: 0.5em;
 50}
 51.head .index {
 52}
 53
 54.info .label {
 55  width: 7em;
 56  display: inline-block;
 57}
 58.info>div {
 59  padding: 0.25em;
 60  background: #222;
 61  margin: 0.25em 0.25em 0 0;
 62}
 63
 64.inputs {
 65  display: flex;
 66}
 67.axes {
 68  display: flex;
 69  align-items: flex-start;
 70}
 71
 72.svg text {
 73  color: #CCC;
 74  font-family: monospace;
 75}
 76
 77.axes svg text {
 78  font-size: 0.6px;
 79}
 80.buttons svg text {
 81  font-size: 1.2px;
 82}
 83.axes>div, .buttons>div {
 84  display: inline-block;
 85  background: #222;
 86}
 87.axes>div {
 88  margin: 2px 5px 0 0;
 89}
 90.buttons>div {
 91  margin: 2px 2px 0 0;
 92}
 93</style>
 94<body>
 95
 96<h1>HTML5 Gamepad Test</h1>
 97<div>running: <span id="running"></span></div>
 98<div id="gamepads"></div>
 99</body>
100<script>
101
102const fudgeFactor = 2;  // because of bug in Chrome related to svg text alignment font sizes can not be < 1
103const runningElem = document.querySelector('#running');
104const gamepadsElem = document.querySelector('#gamepads');
105const gamepadsByIndex = {};
106
107const controllerTemplate = `
108<div>
109  <div class="head"><div class="index"></div><div class="id"></div></div>
110  <div class="info"><div class="label">connected:</div><span class="connected"></span></div>
111  <div class="info"><div class="label">mapping:</div><span class="mapping"></span></div>
112  <div class="inputs">
113    <div class="axes"></div>
114    <div class="buttons"></div>
115  </div>
116</div>
117`;
118const axisTemplate = `
119<svg viewBox="-2.2 -2.2 4.4 4.4" width="128" height="128">
120    <circle cx="0" cy="0" r="2" fill="none" stroke="#888" stroke-width="0.04" />
121    <path d="M0,-2L0,2M-2,0L2,0" stroke="#888" stroke-width="0.04" />
122    <circle cx="0" cy="0" r="0.22" fill="red" class="axis" />
123    <text text-anchor="middle" fill="#CCC" x="0" y="2">0</text>
124</svg>
125`
126
127const buttonTemplate = `
128<svg viewBox="-2.2 -2.2 4.4 4.4" width="64" height="64">
129  <circle cx="0" cy="0" r="2" fill="none" stroke="#888" stroke-width="0.1" />
130  <circle cx="0" cy="0" r="0" fill="none" fill="red" class="button" />
131  <text class="value" dominant-baseline="middle" text-anchor="middle" fill="#CCC" x="0" y="0">0.00</text>
132  <text class="index" alignment-baseline="hanging" dominant-baseline="hanging" text-anchor="start" fill="#CCC" x="-2" y="-2">0</text>
133</svg>
134`;
135
136function addGamepad(gamepad) {
137  console.log('add:', gamepad.index);
138  const elem = document.createElement('div');
139  elem.innerHTML = controllerTemplate;
140
141  const axesElem = elem.querySelector('.axes');
142  const buttonsElem = elem.querySelector('.buttons');
143  
144  const axes = [];
145  for (let ndx = 0; ndx < gamepad.axes.length; ndx += 2) {
146    const div = document.createElement('div');
147    div.innerHTML = axisTemplate;
148    axesElem.appendChild(div);
149    axes.push({
150      axis: div.querySelector('.axis'),
151      value: div.querySelector('text'),
152    });
153  }
154
155  const buttons = [];
156  for (let ndx = 0; ndx < gamepad.buttons.length; ++ndx) {
157    const div = document.createElement('div');
158    div.innerHTML = buttonTemplate;
159    buttonsElem.appendChild(div);
160    div.querySelector('.index').textContent = ndx;
161    buttons.push({
162      circle: div.querySelector('.button'),
163      value: div.querySelector('.value'),
164    });
165  }
166
167  gamepadsByIndex[gamepad.index] = {
168    gamepad,
169    elem,
170    axes,
171    buttons,
172    index: elem.querySelector('.index'),
173    id: elem.querySelector('.id'),
174    mapping: elem.querySelector('.mapping'),
175    connected: elem.querySelector('.connected'),
176  };
177  gamepadsElem.appendChild(elem);
178}
179
180function removeGamepad(gamepad) {
181  const info = gamepadsByIndex[gamepad.index];
182  if (info) {
183    delete gamepadsByIndex[gamepad.index];
184    info.elem.parentElement.removeChild(info.elem);
185  }
186}
187
188function addGamepadIfNew(gamepad) {
189  const info = gamepadsByIndex[gamepad.index];
190  if (!info) {
191    addGamepad(gamepad);
192  } else {
193    // This broke sometime in the past. It used to be
194    // the same gamepad object was returned forever.
195    // Then Chrome only changed to a new gamepad object
196    // is returned every frame.
197    info.gamepad = gamepad;
198  }
199}
200
201function handleConnect(e) {
202  console.log('connect');
203  addGamepadIfNew(e.gamepad);
204}
205
206function handleDisconnect(e) {
207  console.log('disconnect');
208  removeGamepad(e.gamepad);
209}
210
211const t = String.fromCharCode(0x26AA);
212const f = String.fromCharCode(0x26AB);
213function onOff(v) {
214  return v ? t : f;
215}
216
217const keys = ['index', 'id', 'connected', 'mapping', /*'timestamp'*/];
218function processController(info) {
219  const {elem, gamepad, axes, buttons} = info;
220  const lines = [`gamepad  : ${gamepad.index}`];
221  for (const key of keys) {
222    info[key].textContent = gamepad[key];
223  }
224  axes.forEach(({axis, value}, ndx) => {
225    const off = ndx * 2;
226    axis.setAttributeNS(null, 'cx', gamepad.axes[off    ] * fudgeFactor);
227    axis.setAttributeNS(null, 'cy', gamepad.axes[off + 1] * fudgeFactor);
228    value.textContent = `${Math.floor(127*gamepad.axes[off]+127)},${Math.floor(127*gamepad.axes[off+1]+127)}`;
229  });
230  buttons.forEach(({circle, value}, ndx) => {
231    const button = gamepad.buttons[ndx];
232    circle.setAttributeNS(null, 'r', button.value * fudgeFactor);
233    circle.setAttributeNS(null, 'fill', button.pressed ? 'red' : 'gray');
234    value.textContent = `${button.value.toFixed(2)}`;
235  });
236  
237//  lines.push(`axes     : [${gamepad.axes.map((v, ndx) => `${ndx}: ${v.toFixed(2).padStart(5)}`).join(', ')} ]`);
238//  lines.push(`buttons  : [${gamepad.buttons.map((v, ndx) => `${ndx}: ${onOff(v.pressed)} ${v.value.toFixed(2)}`).join(', ')} ]`);
239 // elem.textContent = lines.join('\n');
240}
241
242function addNewPads() {
243  const gamepads = navigator.getGamepads();
244  for (let i = 0; i < gamepads.length; i++) {
245    const gamepad = gamepads[i]
246    if (gamepad) {
247      addGamepadIfNew(gamepad);
248    }
249  }
250}
251
252window.addEventListener("gamepadconnected", handleConnect);
253window.addEventListener("gamepaddisconnected", handleDisconnect);
254
255function process() {
256  runningElem.textContent = ((performance.now() * 0.001 * 60 | 0) % 100).toString().padStart(2, '0');
257  addNewPads();  // some browsers add by polling, others by event
258
259  Object.values(gamepadsByIndex).forEach(processController);
260  requestAnimationFrame(process);
261}
262requestAnimationFrame(process);
263
264</script>