mirror of https://github.com/nealey/vail.git
Add keys for vband compatibility
This commit is contained in:
parent
fc213369c1
commit
ad596546bb
134
static/vail.js
134
static/vail.js
|
@ -8,8 +8,8 @@ const DIT = 1
|
||||||
const DAH = 3
|
const DAH = 3
|
||||||
|
|
||||||
// iOS kludge
|
// iOS kludge
|
||||||
if (! window.AudioContext) {
|
if (!window.AudioContext) {
|
||||||
window.AudioContext = window.webkitAudioContext
|
window.AudioContext = window.webkitAudioContext
|
||||||
}
|
}
|
||||||
|
|
||||||
function toast(msg) {
|
function toast(msg) {
|
||||||
|
@ -58,7 +58,7 @@ class Iambic {
|
||||||
this.interval = null
|
this.interval = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
stateDit() {
|
stateDit() {
|
||||||
// Send a dit
|
// Send a dit
|
||||||
this.beginTxFunc()
|
this.beginTxFunc()
|
||||||
|
@ -100,7 +100,7 @@ class Iambic {
|
||||||
}
|
}
|
||||||
this.state()
|
this.state()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Edge trigger on key press or release
|
* Edge trigger on key press or release
|
||||||
|
@ -114,9 +114,9 @@ class Iambic {
|
||||||
} else if (key == DAH) {
|
} else if (key == DAH) {
|
||||||
this.dahDown = down
|
this.dahDown = down
|
||||||
}
|
}
|
||||||
|
|
||||||
// Not pulsing yet? Start right away!
|
// Not pulsing yet? Start right away!
|
||||||
if (! this.interval) {
|
if (!this.interval) {
|
||||||
this.interval = setInterval(e => this.pulse(), this.intervalDuration)
|
this.interval = setInterval(e => this.pulse(), this.intervalDuration)
|
||||||
this.pulse()
|
this.pulse()
|
||||||
}
|
}
|
||||||
|
@ -132,7 +132,7 @@ class Buzzer {
|
||||||
// in order to avoid "pops" (square wave overtones)
|
// in order to avoid "pops" (square wave overtones)
|
||||||
// that happen with instant changes in gain.
|
// that happen with instant changes in gain.
|
||||||
|
|
||||||
constructor(txGain=0.6) {
|
constructor(txGain = 0.6) {
|
||||||
this.txGain = txGain
|
this.txGain = txGain
|
||||||
|
|
||||||
this.ac = new AudioContext()
|
this.ac = new AudioContext()
|
||||||
|
@ -143,13 +143,13 @@ class Buzzer {
|
||||||
this.noiseGain = this.whiteNoise()
|
this.noiseGain = this.whiteNoise()
|
||||||
|
|
||||||
this.ac.resume()
|
this.ac.resume()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
document.querySelector("#muted").classList.add("hidden")
|
document.querySelector("#muted").classList.add("hidden")
|
||||||
})
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
create(frequency, type="sine") {
|
create(frequency, type = "sine") {
|
||||||
let gain = this.ac.createGain()
|
let gain = this.ac.createGain()
|
||||||
gain.connect(this.ac.destination)
|
gain.connect(this.ac.destination)
|
||||||
gain.gain.value = 0
|
gain.gain.value = 0
|
||||||
|
@ -160,7 +160,7 @@ class Buzzer {
|
||||||
osc.start()
|
osc.start()
|
||||||
return gain
|
return gain
|
||||||
}
|
}
|
||||||
|
|
||||||
whiteNoise() {
|
whiteNoise() {
|
||||||
let bufferSize = 17 * this.ac.sampleRate
|
let bufferSize = 17 * this.ac.sampleRate
|
||||||
let noiseBuffer = this.ac.createBuffer(1, bufferSize, this.ac.sampleRate)
|
let noiseBuffer = this.ac.createBuffer(1, bufferSize, this.ac.sampleRate)
|
||||||
|
@ -168,7 +168,7 @@ class Buzzer {
|
||||||
for (let i = 0; i < bufferSize; i++) {
|
for (let i = 0; i < bufferSize; i++) {
|
||||||
output[i] = Math.random() * 2 - 1;
|
output[i] = Math.random() * 2 - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
let whiteNoise = this.ac.createBufferSource();
|
let whiteNoise = this.ac.createBufferSource();
|
||||||
whiteNoise.buffer = noiseBuffer;
|
whiteNoise.buffer = noiseBuffer;
|
||||||
whiteNoise.loop = true;
|
whiteNoise.loop = true;
|
||||||
|
@ -177,16 +177,16 @@ class Buzzer {
|
||||||
let filter = this.ac.createBiquadFilter()
|
let filter = this.ac.createBiquadFilter()
|
||||||
filter.type = "lowpass"
|
filter.type = "lowpass"
|
||||||
filter.frequency.value = 100
|
filter.frequency.value = 100
|
||||||
|
|
||||||
let gain = this.ac.createGain()
|
let gain = this.ac.createGain()
|
||||||
gain.gain.value = 0.1
|
gain.gain.value = 0.1
|
||||||
|
|
||||||
whiteNoise.connect(filter)
|
whiteNoise.connect(filter)
|
||||||
filter.connect(gain)
|
filter.connect(gain)
|
||||||
gain.connect(this.ac.destination)
|
gain.connect(this.ac.destination)
|
||||||
|
|
||||||
return gain
|
return gain
|
||||||
}
|
}
|
||||||
|
|
||||||
gain(high) {
|
gain(high) {
|
||||||
if (high) {
|
if (high) {
|
||||||
|
@ -203,11 +203,11 @@ class Buzzer {
|
||||||
* @return {number} AudioContext offset time
|
* @return {number} AudioContext offset time
|
||||||
*/
|
*/
|
||||||
acTime(when) {
|
acTime(when) {
|
||||||
if (! when) {
|
if (!when) {
|
||||||
return this.ac.currentTime
|
return this.ac.currentTime
|
||||||
}
|
}
|
||||||
|
|
||||||
let acOffset = Date.now() - this.ac.currentTime*1000
|
let acOffset = Date.now() - this.ac.currentTime * 1000
|
||||||
let acTime = (when - acOffset) / 1000
|
let acTime = (when - acOffset) / 1000
|
||||||
return acTime
|
return acTime
|
||||||
}
|
}
|
||||||
|
@ -235,8 +235,8 @@ class Buzzer {
|
||||||
* @param {boolean} tx Transmit or receive tone
|
* @param {boolean} tx Transmit or receive tone
|
||||||
* @param {number} when Time to begin, in ms (null=now)
|
* @param {number} when Time to begin, in ms (null=now)
|
||||||
*/
|
*/
|
||||||
Buzz(tx, when=null) {
|
Buzz(tx, when = null) {
|
||||||
if (! tx) {
|
if (!tx) {
|
||||||
let recv = document.querySelector("#recv")
|
let recv = document.querySelector("#recv")
|
||||||
let ms = when - Date.now()
|
let ms = when - Date.now()
|
||||||
setTimeout(e => {
|
setTimeout(e => {
|
||||||
|
@ -247,9 +247,9 @@ class Buzzer {
|
||||||
let gain = this.gain(tx)
|
let gain = this.gain(tx)
|
||||||
let acWhen = this.acTime(when)
|
let acWhen = this.acTime(when)
|
||||||
this.ac.resume()
|
this.ac.resume()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
gain.setTargetAtTime(this.txGain, acWhen, 0.001)
|
gain.setTargetAtTime(this.txGain, acWhen, 0.001)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -258,8 +258,8 @@ class Buzzer {
|
||||||
* @param {boolean} tx Transmit or receive tone
|
* @param {boolean} tx Transmit or receive tone
|
||||||
* @param {number} when Time to end, in ms (null=now)
|
* @param {number} when Time to end, in ms (null=now)
|
||||||
*/
|
*/
|
||||||
Silence(tx, when=null) {
|
Silence(tx, when = null) {
|
||||||
if (! tx) {
|
if (!tx) {
|
||||||
let recv = document.querySelector("#recv")
|
let recv = document.querySelector("#recv")
|
||||||
let ms = when - Date.now()
|
let ms = when - Date.now()
|
||||||
setTimeout(e => {
|
setTimeout(e => {
|
||||||
|
@ -282,7 +282,7 @@ class Buzzer {
|
||||||
*/
|
*/
|
||||||
BuzzDuration(high, when, duration) {
|
BuzzDuration(high, when, duration) {
|
||||||
this.Buzz(high, when)
|
this.Buzz(high, when)
|
||||||
this.Silence(high, when+duration)
|
this.Silence(high, when + duration)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -300,7 +300,7 @@ class Vail {
|
||||||
|
|
||||||
// Listen to HTML buttons
|
// Listen to HTML buttons
|
||||||
for (let e of document.querySelectorAll("button.key")) {
|
for (let e of document.querySelectorAll("button.key")) {
|
||||||
e.addEventListener("contextmenu", e => {e.preventDefault(); return false})
|
e.addEventListener("contextmenu", e => { e.preventDefault(); return false })
|
||||||
e.addEventListener("touchstart", e => this.keyButton(e))
|
e.addEventListener("touchstart", e => this.keyButton(e))
|
||||||
e.addEventListener("touchend", e => this.keyButton(e))
|
e.addEventListener("touchend", e => this.keyButton(e))
|
||||||
e.addEventListener("mousedown", e => this.keyButton(e))
|
e.addEventListener("mousedown", e => this.keyButton(e))
|
||||||
|
@ -320,22 +320,22 @@ class Vail {
|
||||||
|
|
||||||
// Listen for slider values
|
// Listen for slider values
|
||||||
this.inputInit("#iambic-duration", e => this.iambic.SetIntervalDuration(e.target.value))
|
this.inputInit("#iambic-duration", e => this.iambic.SetIntervalDuration(e.target.value))
|
||||||
this.inputInit("#rx-delay", e => {this.rxDelay = Number(e.target.value)})
|
this.inputInit("#rx-delay", e => { this.rxDelay = Number(e.target.value) })
|
||||||
|
|
||||||
// Show what repeater we're on
|
// Show what repeater we're on
|
||||||
let repeater = (new URL(location)).searchParams.get("repeater") || "General Chaos"
|
let repeater = (new URL(location)).searchParams.get("repeater") || "General Chaos"
|
||||||
document.querySelector("#repeater").textContent = repeater
|
document.querySelector("#repeater").textContent = repeater
|
||||||
|
|
||||||
// Request MIDI access
|
// Request MIDI access
|
||||||
if (navigator.requestMIDIAccess) {
|
if (navigator.requestMIDIAccess) {
|
||||||
navigator.requestMIDIAccess()
|
navigator.requestMIDIAccess()
|
||||||
.then(a => this.midiInit(a))
|
.then(a => this.midiInit(a))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set up for gamepad input
|
// Set up for gamepad input
|
||||||
window.addEventListener("gamepadconnected", e => this.gamepadConnected(e))
|
window.addEventListener("gamepadconnected", e => this.gamepadConnected(e))
|
||||||
}
|
}
|
||||||
|
|
||||||
openSocket() {
|
openSocket() {
|
||||||
// Set up WebSocket
|
// Set up WebSocket
|
||||||
let wsUrl = new URL(window.location)
|
let wsUrl = new URL(window.location)
|
||||||
|
@ -362,23 +362,23 @@ class Vail {
|
||||||
})
|
})
|
||||||
element.dispatchEvent(new Event("input"))
|
element.dispatchEvent(new Event("input"))
|
||||||
}
|
}
|
||||||
|
|
||||||
midiInit(access) {
|
midiInit(access) {
|
||||||
this.midiAccess = access
|
this.midiAccess = access
|
||||||
for (let input of this.midiAccess.inputs.values()) {
|
for (let input of this.midiAccess.inputs.values()) {
|
||||||
input.addEventListener("midimessage", e => this.midiMessage(e))
|
input.addEventListener("midimessage", e => this.midiMessage(e))
|
||||||
}
|
}
|
||||||
this.midiAccess.addEventListener("statechange", e => this.midiStateChange(e))
|
this.midiAccess.addEventListener("statechange", e => this.midiStateChange(e))
|
||||||
}
|
}
|
||||||
|
|
||||||
midiStateChange(event) {
|
midiStateChange(event) {
|
||||||
// XXX: it's not entirely clear how to handle new devices showing up.
|
// XXX: it's not entirely clear how to handle new devices showing up.
|
||||||
// XXX: possibly we go through this.midiAccess.inputs and somehow only listen on new things
|
// XXX: possibly we go through this.midiAccess.inputs and somehow only listen on new things
|
||||||
}
|
}
|
||||||
|
|
||||||
midiMessage(event) {
|
midiMessage(event) {
|
||||||
let data = Array.from(event.data)
|
let data = Array.from(event.data)
|
||||||
|
|
||||||
let begin
|
let begin
|
||||||
let cmd = data[0] >> 4
|
let cmd = data[0] >> 4
|
||||||
let chan = data[0] & 0xf
|
let chan = data[0] & 0xf
|
||||||
|
@ -392,7 +392,7 @@ class Vail {
|
||||||
default:
|
default:
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (data[1] % 12) {
|
switch (data[1] % 12) {
|
||||||
case 0: // C
|
case 0: // C
|
||||||
this.straightKey(begin)
|
this.straightKey(begin)
|
||||||
|
@ -405,9 +405,9 @@ class Vail {
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
error(msg) {
|
error(msg) {
|
||||||
toast(msg)
|
toast(msg)
|
||||||
this.buzzer.ErrorTone()
|
this.buzzer.ErrorTone()
|
||||||
|
@ -434,8 +434,8 @@ class Vail {
|
||||||
}
|
}
|
||||||
|
|
||||||
updateReadings() {
|
updateReadings() {
|
||||||
let avgLag = this.lagTimes.reduce((a,b) => (a+b)) / this.lagTimes.length
|
let avgLag = this.lagTimes.reduce((a, b) => (a + b)) / this.lagTimes.length
|
||||||
let longestRx = this.rxDurations.reduce((a,b) => Math.max(a,b))
|
let longestRx = this.rxDurations.reduce((a, b) => Math.max(a, b))
|
||||||
let suggestedDelay = (avgLag + longestRx) * 1.2
|
let suggestedDelay = (avgLag + longestRx) * 1.2
|
||||||
|
|
||||||
this.updateReading("#lag-value", avgLag.toFixed())
|
this.updateReading("#lag-value", avgLag.toFixed())
|
||||||
|
@ -474,22 +474,22 @@ class Vail {
|
||||||
try {
|
try {
|
||||||
msg = JSON.parse(jmsg)
|
msg = JSON.parse(jmsg)
|
||||||
}
|
}
|
||||||
catch(err) {
|
catch (err) {
|
||||||
console.log(err, msg)
|
console.log(err, msg)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let beginTxTime = msg[0]
|
let beginTxTime = msg[0]
|
||||||
let durations = msg.slice(1)
|
let durations = msg.slice(1)
|
||||||
|
|
||||||
if (this.debug) {
|
if (this.debug) {
|
||||||
console.log("recv", beginTxTime, durations)
|
console.log("recv", beginTxTime, durations)
|
||||||
}
|
}
|
||||||
|
|
||||||
let sent = this.sent.filter(e => e != jmsg)
|
let sent = this.sent.filter(e => e != jmsg)
|
||||||
if (sent.length < this.sent.length) {
|
if (sent.length < this.sent.length) {
|
||||||
// We're getting our own message back, which tells us our lag.
|
// We're getting our own message back, which tells us our lag.
|
||||||
// We shouldn't emit a tone, though.
|
// We shouldn't emit a tone, though.
|
||||||
let totalDuration = durations.reduce((a,b) => a+b)
|
let totalDuration = durations.reduce((a, b) => a + b)
|
||||||
this.sent = sent
|
this.sent = sent
|
||||||
this.addLagReading(now - beginTxTime - totalDuration)
|
this.addLagReading(now - beginTxTime - totalDuration)
|
||||||
return
|
return
|
||||||
|
@ -509,9 +509,9 @@ class Vail {
|
||||||
if (beginTxTime == 0) {
|
if (beginTxTime == 0) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add rxDelay
|
// Add rxDelay
|
||||||
let adjustedTxTime = beginTxTime+this.rxDelay
|
let adjustedTxTime = beginTxTime + this.rxDelay
|
||||||
if (adjustedTxTime < now) {
|
if (adjustedTxTime < now) {
|
||||||
console.log("adjustedTxTime: ", adjustedTxTime, " now: ", now)
|
console.log("adjustedTxTime: ", adjustedTxTime, " now: ", now)
|
||||||
this.error("Packet requested playback " + (now - adjustedTxTime) + "ms in the past. Increase receive delay!")
|
this.error("Packet requested playback " + (now - adjustedTxTime) + "ms in the past. Increase receive delay!")
|
||||||
|
@ -546,7 +546,7 @@ class Vail {
|
||||||
iambicDah(begin) {
|
iambicDah(begin) {
|
||||||
this.iambic.Key(begin, DAH)
|
this.iambic.Key(begin, DAH)
|
||||||
}
|
}
|
||||||
|
|
||||||
keyboard(event) {
|
keyboard(event) {
|
||||||
if (event.repeat) {
|
if (event.repeat) {
|
||||||
// Ignore key repeats generated by the OS, we do this ourselves
|
// Ignore key repeats generated by the OS, we do this ourselves
|
||||||
|
@ -554,20 +554,28 @@ class Vail {
|
||||||
}
|
}
|
||||||
|
|
||||||
let begin = event.type.endsWith("down")
|
let begin = event.type.endsWith("down")
|
||||||
|
|
||||||
if ((event.code == "KeyX") || (event.code == "Period")) {
|
if ((event.code == "KeyX") ||
|
||||||
|
(event.code == "Period") ||
|
||||||
|
(event.code == "ControlLeft") ||
|
||||||
|
(event.code == "BracketLeft") ||
|
||||||
|
(event.key == "[")) {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
this.iambicDit(begin)
|
this.iambicDit(begin)
|
||||||
}
|
}
|
||||||
if ((event.code == "KeyZ") || (event.code == "Slash")) {
|
if ((event.code == "KeyZ") ||
|
||||||
|
(event.code == "Slash") ||
|
||||||
|
(event.code == "ControlRight") ||
|
||||||
|
(event.code == "BracketRight") ||
|
||||||
|
(event.key == "]")) {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
this.iambicDah(begin)
|
this.iambicDah(begin)
|
||||||
}
|
}
|
||||||
if ((event.code == "KeyC") ||
|
if ((event.code == "KeyC") ||
|
||||||
(event.code == "Comma") ||
|
(event.code == "Comma") ||
|
||||||
(event.key == "Shift") ||
|
(event.key == "Shift") ||
|
||||||
(event.key == "Enter") ||
|
(event.key == "Enter") ||
|
||||||
(event.key == "NumpadEnter")) {
|
(event.key == "NumpadEnter")) {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
this.straightKey(begin)
|
this.straightKey(begin)
|
||||||
}
|
}
|
||||||
|
@ -596,12 +604,12 @@ class Vail {
|
||||||
// Polling could be computationally expensive,
|
// Polling could be computationally expensive,
|
||||||
// especially on devices with a power budget, like phones.
|
// especially on devices with a power budget, like phones.
|
||||||
// To be considerate, we only start polling if a gamepad appears.
|
// To be considerate, we only start polling if a gamepad appears.
|
||||||
if (! this.gamepadButtons) {
|
if (!this.gamepadButtons) {
|
||||||
this.gamepadButtons = {}
|
this.gamepadButtons = {}
|
||||||
this.gamepadPoll(event.timeStamp)
|
this.gamepadPoll(event.timeStamp)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
gamepadPoll(timestamp) {
|
gamepadPoll(timestamp) {
|
||||||
let currentButtons = {}
|
let currentButtons = {}
|
||||||
for (let gp of navigator.getGamepads()) {
|
for (let gp of navigator.getGamepads()) {
|
||||||
|
@ -654,9 +662,9 @@ class Vail {
|
||||||
|
|
||||||
maximize(e) {
|
maximize(e) {
|
||||||
let element = e.target
|
let element = e.target
|
||||||
while (! element.classList.contains("mdl-card")) {
|
while (!element.classList.contains("mdl-card")) {
|
||||||
element = element.parentElement
|
element = element.parentElement
|
||||||
if (! element) {
|
if (!element) {
|
||||||
console.log("Maximize button: couldn't find parent card")
|
console.log("Maximize button: couldn't find parent card")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -664,7 +672,7 @@ class Vail {
|
||||||
element.classList.toggle("maximized")
|
element.classList.toggle("maximized")
|
||||||
console.log(element)
|
console.log(element)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue