vail/static/inputs.mjs

245 lines
5.7 KiB
JavaScript
Raw Normal View History

class Input {
2021-04-27 17:30:16 -06:00
constructor(keyer) {
this.keyer = keyer
}
2022-05-08 11:33:25 -06:00
SetDitDuration(delay) {
// Nothing
}
}
export class HTML extends Input{
constructor(keyer) {
super(keyer)
2021-04-27 17:30:16 -06:00
// Listen to HTML buttons
for (let e of document.querySelectorAll("button.key")) {
// Chrome is going to suggest you use passive events here.
// I tried that and it screws up Safari mobile,
// making it so that hitting the button selects text on the page.
e.addEventListener("contextmenu", e => { e.preventDefault(); return false }, {passive: false})
2022-04-26 11:55:43 -06:00
e.addEventListener("touchstart", e => this.keyButton(e), {passive: false})
e.addEventListener("touchend", e => this.keyButton(e), {passive: false})
e.addEventListener("mousedown", e => this.keyButton(e), {passive: false})
e.addEventListener("mouseup", e => this.keyButton(e), {passive: false})
e.contentEditable = false
2021-04-27 17:30:16 -06:00
}
}
keyButton(event) {
2022-05-15 10:46:51 -06:00
let down = event.type.endsWith("down") || event.type.endsWith("start")
let key = Number(event.target.dataset.key)
2022-05-14 18:51:05 -06:00
// Button 2 does the other key (assuming 2 keys)
if (event.button == 2) {
key = 1 - key
2022-04-26 11:55:43 -06:00
}
2022-05-15 10:46:51 -06:00
this.keyer.Key(key, down)
2022-05-14 18:51:05 -06:00
2022-04-26 11:55:43 -06:00
if (event.cancelable) {
event.preventDefault()
2021-04-27 17:30:16 -06:00
}
}
}
export class Keyboard extends Input{
2021-04-27 17:30:16 -06:00
constructor(keyer) {
super(keyer)
2021-04-27 17:30:16 -06:00
// Listen for keystrokes
document.addEventListener("keydown", e => this.keyboard(e))
document.addEventListener("keyup", e => this.keyboard(e))
// VBand: the keyboard input needs to know whether vband's "left" should be dit or straight
this.iambic = false
2021-04-27 17:30:16 -06:00
}
keyboard(event) {
2021-07-16 10:10:50 -06:00
if (["INPUT", "TEXTAREA"].includes(document.activeElement.tagName)) {
2021-04-27 17:30:16 -06:00
// Ignore everything if the user is entering text somewhere
return
}
let down = event.type.endsWith("down")
if (
(event.code == "KeyX")
|| (event.code == "Period")
|| (event.code == "BracketLeft")
2022-05-14 21:17:44 -06:00
|| (event.code == "ControlLeft")
|| (event.key == "[")
) {
// Dit
if (this.ditDown != down) {
2022-05-14 18:51:05 -06:00
this.keyer.Key(0, down)
this.ditDown = down
}
2021-04-27 17:30:16 -06:00
}
if (
(event.code == "KeyZ")
|| (event.code == "Slash")
|| (event.code == "BracketRight")
2022-05-14 21:17:44 -06:00
|| (event.code == "ControlRight")
|| (event.key == "]")
) {
if (this.dahDown != down) {
2022-05-14 18:51:05 -06:00
this.keyer.Key(1, down)
this.dahDown = down
}
2021-04-27 17:30:16 -06:00
}
if (
(event.code == "KeyC")
|| (event.code == "Comma")
|| (event.key == "Enter")
|| (event.key == "NumpadEnter")
) {
if (this.straightDown != down) {
this.keyer.Straight(down)
this.straightDown = down
}
2021-04-27 17:30:16 -06:00
}
}
}
export class MIDI extends Input{
2021-04-27 17:30:16 -06:00
constructor(keyer) {
super(keyer)
2021-04-27 17:30:16 -06:00
this.midiAccess = {outputs: []} // stub while we wait for async stuff
2021-04-27 17:30:16 -06:00
if (navigator.requestMIDIAccess) {
this.midiInit()
}
}
async midiInit(access) {
this.inputs = []
2021-04-27 17:30:16 -06:00
this.midiAccess = await navigator.requestMIDIAccess()
this.midiAccess.addEventListener("statechange", e => this.midiStateChange(e))
this.midiStateChange()
2021-04-27 17:30:16 -06:00
}
SetIntervalDuration(delay) {
// Send the Vail adapter the current iambic delay setting
for (let output of this.midiAccess.outputs.values()) {
// MIDI only supports 7-bit values, so we have to divide it by two
output.send([0x8B, 0x01, delay/2])
}
}
2021-04-27 17:30:16 -06:00
midiStateChange(event) {
// Go through this.midiAccess.inputs and only listen on new things
for (let input of this.midiAccess.inputs.values()) {
if (!this.inputs.includes(input)) {
input.addEventListener("midimessage", e => this.midiMessage(e))
this.inputs.push(input)
}
}
// Tell the Vail adapter to disable keyboard events: we can do MIDI!
for (let output of this.midiAccess.outputs.values()) {
output.send([0x8B, 0x00, 0x00]) // Turn off keyboard mode
}
2021-04-27 17:30:16 -06:00
}
midiMessage(event) {
let data = Array.from(event.data)
let begin
let cmd = data[0] >> 4
let chan = data[0] & 0xf
switch (cmd) {
case 9:
begin = true
break
case 8:
begin = false
break
default:
return
}
switch (data[1] % 12) {
case 0: // C
this.keyer.Straight(begin)
break
case 1: // C#
2022-05-14 18:51:05 -06:00
this.keyer.Key(0, begin)
2021-04-27 17:30:16 -06:00
break
case 2: // D
2022-05-14 18:51:05 -06:00
this.keyer.Key(1, begin)
2021-04-27 17:30:16 -06:00
break
default:
return
}
}
}
export class Gamepad extends Input{
2021-04-27 17:30:16 -06:00
constructor(keyer) {
super(keyer)
2021-04-27 17:30:16 -06:00
// Set up for gamepad input
window.addEventListener("gamepadconnected", e => this.gamepadConnected(e))
}
/**
* Gamepads must be polled, usually at 60fps.
* This could be really expensive,
* especially on devices with a power budget, like phones.
* To be considerate, we only start polling if a gamepad appears.
*
* @param event Gamepad Connected event
*/
gamepadConnected(event) {
if (!this.gamepadButtons) {
this.gamepadButtons = {}
this.gamepadPoll(event.timeStamp)
}
}
gamepadPoll(timestamp) {
let currentButtons = {}
for (let gp of navigator.getGamepads()) {
if (gp == null) {
continue
}
for (let i in gp.buttons) {
let pressed = gp.buttons[i].pressed
if (i < 2) {
currentButtons.key |= pressed
} else if (i % 2 == 0) {
currentButtons.dit |= pressed
} else {
currentButtons.dah |= pressed
}
}
}
if (currentButtons.key != this.gamepadButtons.key) {
this.keyer.Straight(currentButtons.key)
}
if (currentButtons.dit != this.gamepadButtons.dit) {
2022-05-14 18:51:05 -06:00
this.keyer.Key(0, currentButtons.dit)
2021-04-27 17:30:16 -06:00
}
if (currentButtons.dah != this.gamepadButtons.dah) {
2022-05-14 18:51:05 -06:00
this.keyer.Key(1, currentButtons.dah)
2021-04-27 17:30:16 -06:00
}
this.gamepadButtons = currentButtons
requestAnimationFrame(e => this.gamepadPoll(e))
}
}
/**
* Set up all input methods
*
* @param keyer Keyer object for everyone to use
*/
export function SetupAll(keyer) {
return {
HTML: new HTML(keyer),
Keyboard: new Keyboard(keyer),
MIDI: new MIDI(keyer),
Gamepad: new Gamepad(keyer),
}
}