mirror of https://github.com/nealey/vail.git
Many changes
* MIDI * Send MIDI key release for v2 of vail adapter * Now handles hot-plugging MIDI devices! * Iambic input * Typeahead is now a toggle * Implemented iambic mode A, also as a toggle (#31) * If you set the repeater to "debug", it spews out debugging messages * Made it a bit easier on myself to update my instance
This commit is contained in:
parent
ca9f51c621
commit
74ad07174a
|
@ -1,9 +1,11 @@
|
||||||
#! /bin/sh
|
#! /bin/sh
|
||||||
|
|
||||||
|
cd $(dirname $0)
|
||||||
|
|
||||||
case "$1" in
|
case "$1" in
|
||||||
-prod|--prod)
|
-prod|--prod)
|
||||||
echo "Push to main branch, then update stack."
|
echo "Push to main branch, then update stack."
|
||||||
#rsync -va static melville.woozle.org:/srv/vail/
|
docker -H ssh://melville.woozle.org service update --image ghcr.io/nealey/vail:main melville_vail
|
||||||
;;
|
;;
|
||||||
"")
|
"")
|
||||||
rsync -va static/ melville.woozle.org:/srv/vail/testing/
|
rsync -va static/ melville.woozle.org:/srv/vail/testing/
|
||||||
|
|
|
@ -218,7 +218,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="mdl-card__supporting-text">
|
<div class="mdl-card__supporting-text">
|
||||||
<p>
|
<p>
|
||||||
Dit length (iambic):
|
Iambic Dit length:
|
||||||
<output id="iambic-duration-value"></output>ms
|
<output id="iambic-duration-value"></output>ms
|
||||||
/
|
/
|
||||||
<output id="iambic-duration-wpm"></output> WPM
|
<output id="iambic-duration-wpm"></output> WPM
|
||||||
|
@ -230,6 +230,16 @@
|
||||||
max="255"
|
max="255"
|
||||||
value="100">
|
value="100">
|
||||||
</p>
|
</p>
|
||||||
|
<p>
|
||||||
|
<label class="mdl-switch mdl-js-switch mdl-js-ripple-effect" for="iambic-mode-b">
|
||||||
|
<input type="checkbox" id="iambic-mode-b" class="mdl-switch__input" checked>
|
||||||
|
<span class="mdl-switch__label">Iambic mode B</span>
|
||||||
|
</label>
|
||||||
|
<label class="mdl-switch mdl-js-switch mdl-js-ripple-effect" for="iambic-typeahead">
|
||||||
|
<input type="checkbox" id="iambic-typeahead" class="mdl-switch__input" checked>
|
||||||
|
<span class="mdl-switch__label">Iambic typeahead</span>
|
||||||
|
</label>
|
||||||
|
</p>
|
||||||
<p>
|
<p>
|
||||||
Receive delay:
|
Receive delay:
|
||||||
<output id="rx-delay-value"></output>ms
|
<output id="rx-delay-value"></output>ms
|
||||||
|
|
|
@ -102,16 +102,25 @@ export class MIDI {
|
||||||
}
|
}
|
||||||
|
|
||||||
async midiInit(access) {
|
async midiInit(access) {
|
||||||
|
this.inputs = []
|
||||||
this.midiAccess = await navigator.requestMIDIAccess()
|
this.midiAccess = await navigator.requestMIDIAccess()
|
||||||
for (let input of this.midiAccess.inputs.values()) {
|
|
||||||
input.addEventListener("midimessage", e => this.midiMessage(e))
|
|
||||||
}
|
|
||||||
this.midiAccess.addEventListener("statechange", e => this.midiStateChange(e))
|
this.midiAccess.addEventListener("statechange", e => this.midiStateChange(e))
|
||||||
|
this.midiStateChange()
|
||||||
}
|
}
|
||||||
|
|
||||||
midiStateChange(event) {
|
midiStateChange(event) {
|
||||||
// XXX: it's not entirely clear how to handle new devices showing up.
|
// Go through this.midiAccess.inputs and only listen on new things
|
||||||
// XXX: possibly we go through this.midiAccess.inputs and somehow 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([0x80, 0x00, 0x00]) // Stop playing low C
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
midiMessage(event) {
|
midiMessage(event) {
|
||||||
|
|
|
@ -90,7 +90,7 @@ if (!window.AudioContext) {
|
||||||
*/
|
*/
|
||||||
class Keyer {
|
class Keyer {
|
||||||
/**
|
/**
|
||||||
* Create an Keyer
|
* Create a Keyer
|
||||||
*
|
*
|
||||||
* @param {TxControl} beginTxFunc Callback to begin transmitting
|
* @param {TxControl} beginTxFunc Callback to begin transmitting
|
||||||
* @param {TxControl} endTxFunc Callback to end transmitting
|
* @param {TxControl} endTxFunc Callback to end transmitting
|
||||||
|
@ -104,6 +104,8 @@ class Keyer {
|
||||||
this.pauseMultiplier = pauseMultiplier
|
this.pauseMultiplier = pauseMultiplier
|
||||||
this.ditDown = false
|
this.ditDown = false
|
||||||
this.dahDown = false
|
this.dahDown = false
|
||||||
|
this.typeahead = false
|
||||||
|
this.iambicModeB = true
|
||||||
this.last = null
|
this.last = null
|
||||||
this.queue = []
|
this.queue = []
|
||||||
this.pulseTimer = null
|
this.pulseTimer = null
|
||||||
|
@ -146,11 +148,15 @@ class Keyer {
|
||||||
|
|
||||||
typematic() {
|
typematic() {
|
||||||
if (this.ditDown && this.dahDown) {
|
if (this.ditDown && this.dahDown) {
|
||||||
|
if (this.iambicModeB) {
|
||||||
if (this.last == DIT) {
|
if (this.last == DIT) {
|
||||||
this.last = DAH
|
this.last = DAH
|
||||||
} else {
|
} else {
|
||||||
this.last = DIT
|
this.last = DIT
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
this.last = this.last // Mode A = keep on truckin'
|
||||||
|
}
|
||||||
} else if (this.ditDown) {
|
} else if (this.ditDown) {
|
||||||
this.last = DIT
|
this.last = DIT
|
||||||
} else if (this.dahDown) {
|
} else if (this.dahDown) {
|
||||||
|
@ -189,6 +195,34 @@ class Keyer {
|
||||||
this.pauseMultiplier = multiplier
|
this.pauseMultiplier = multiplier
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set Iambic mode B.
|
||||||
|
*
|
||||||
|
* If true, holding both keys will alternate between dit and dah.
|
||||||
|
* If false, holding both keys sends whatever key was depressed first.
|
||||||
|
*
|
||||||
|
* @param {boolean} value True to set mode to B
|
||||||
|
*/
|
||||||
|
SetIambicModeB(value) {
|
||||||
|
this.iambicModeB = Boolean(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enable/disable typeahead.
|
||||||
|
*
|
||||||
|
* Typeahead maintains a key buffer, so you can key in dits and dahs faster than the
|
||||||
|
* Iambic keyer can play them out.
|
||||||
|
*
|
||||||
|
* Some people apparently expect this behavior, and have trouble if it isn't enabled.
|
||||||
|
* For others, having this enabled makes it feel like they have a "phantom keyer"
|
||||||
|
* entering keys they did not send.
|
||||||
|
*
|
||||||
|
* @param value True to enable typeahead
|
||||||
|
*/
|
||||||
|
SetTypeahead(value) {
|
||||||
|
this.typeahead = value
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete anything left on the queue.
|
* Delete anything left on the queue.
|
||||||
*/
|
*/
|
||||||
|
@ -277,8 +311,8 @@ class Keyer {
|
||||||
*/
|
*/
|
||||||
Dit(down) {
|
Dit(down) {
|
||||||
this.ditDown = down
|
this.ditDown = down
|
||||||
if (down) {
|
if (down && this.typeahead || !this.Busy()) {
|
||||||
this.Enqueue(DIT)
|
this.Enqueue(DIT, this.typeahead)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -289,8 +323,8 @@ class Keyer {
|
||||||
*/
|
*/
|
||||||
Dah(down) {
|
Dah(down) {
|
||||||
this.dahDown = down
|
this.dahDown = down
|
||||||
if (down) {
|
if (down && this.typeahead || !this.Busy()) {
|
||||||
this.Enqueue(DAH)
|
this.Enqueue(DAH, this.typeahead)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,6 +45,7 @@ export class Vail {
|
||||||
console.error(err, jmsg)
|
console.error(err, jmsg)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let beginTxTime = msg[0]
|
let beginTxTime = msg[0]
|
||||||
let durations = msg.slice(1)
|
let durations = msg.slice(1)
|
||||||
|
|
||||||
|
@ -62,9 +63,16 @@ export class Vail {
|
||||||
this.lagDurations.unshift(now - this.clockOffset - beginTxTime - totalDuration)
|
this.lagDurations.unshift(now - this.clockOffset - beginTxTime - totalDuration)
|
||||||
this.lagDurations.splice(20, 2)
|
this.lagDurations.splice(20, 2)
|
||||||
this.rx(0, 0, this.stats())
|
this.rx(0, 0, this.stats())
|
||||||
|
if (this.name == "debug") {
|
||||||
|
console.debug("Vail.wsMessage() SQUELCH", msg)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.name == "debug") {
|
||||||
|
console.debug("Vail.wsMessage()", msg)
|
||||||
|
}
|
||||||
|
|
||||||
// The very first packet is the server telling us the current time
|
// The very first packet is the server telling us the current time
|
||||||
if (durations.length == 0) {
|
if (durations.length == 0) {
|
||||||
if (this.clockOffset == 0) {
|
if (this.clockOffset == 0) {
|
||||||
|
|
|
@ -64,15 +64,20 @@ class VailClient {
|
||||||
e.addEventListener("click", e => this.test())
|
e.addEventListener("click", e => this.test())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set up sliders
|
// Set up inputs
|
||||||
this.sliderInit("#iambic-duration", e => {
|
this.inputInit("#iambic-duration", e => {
|
||||||
this.keyer.SetIntervalDuration(e.target.value)
|
this.keyer.SetIntervalDuration(e.target.value)
|
||||||
this.roboKeyer.SetIntervalDuration(e.target.value)
|
this.roboKeyer.SetIntervalDuration(e.target.value)
|
||||||
})
|
})
|
||||||
this.sliderInit("#rx-delay", e => {
|
this.inputInit("#rx-delay", e => {
|
||||||
this.rxDelay = Number(e.target.value)
|
this.rxDelay = Number(e.target.value)
|
||||||
})
|
})
|
||||||
|
this.inputInit("#iambic-mode-b", e => {
|
||||||
|
this.keyer.SetIambicModeB(e.target.checked)
|
||||||
|
})
|
||||||
|
this.inputInit("#iambic-typeahead", e => {
|
||||||
|
this.keyer.SetTypeahead(e.target.checked)
|
||||||
|
})
|
||||||
|
|
||||||
// Fill in the name of our repeater
|
// Fill in the name of our repeater
|
||||||
let repeaterElement = document.querySelector("#repeater").addEventListener("change", e => this.setRepeater(e.target.value.trim()))
|
let repeaterElement = document.querySelector("#repeater").addEventListener("change", e => this.setRepeater(e.target.value.trim()))
|
||||||
|
@ -163,33 +168,40 @@ class VailClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set up a slider.
|
* Set up an input.
|
||||||
*
|
*
|
||||||
* This reads any previously saved value and sets the slider to that.
|
* This reads any previously saved value and sets the input value to that.
|
||||||
* When the slider is updated, it saves the value it's updated to,
|
* When the input is updated, it saves the value it's updated to,
|
||||||
* and calls the provided callback with the new value.
|
* and calls the provided callback with the new value.
|
||||||
*
|
*
|
||||||
* @param {string} selector CSS path to the element
|
* @param {string} selector CSS path to the element
|
||||||
* @param {function} callback Callback to call with any new value that is set
|
* @param {function} callback Callback to call with any new value that is set
|
||||||
*/
|
*/
|
||||||
sliderInit(selector, callback) {
|
inputInit(selector, callback) {
|
||||||
let element = document.querySelector(selector)
|
let element = document.querySelector(selector)
|
||||||
if (!element) {
|
if (!element) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let storedValue = localStorage[element.id]
|
let storedValue = localStorage[element.id]
|
||||||
if (storedValue) {
|
if (storedValue != null) {
|
||||||
element.value = storedValue
|
element.value = storedValue
|
||||||
|
element.checked = JSON.parse(storedValue)
|
||||||
}
|
}
|
||||||
let outputElement = document.querySelector(selector + "-value")
|
let outputElement = document.querySelector(selector + "-value")
|
||||||
let outputWpmElement = document.querySelector(selector + "-wpm")
|
let outputWpmElement = document.querySelector(selector + "-wpm")
|
||||||
|
|
||||||
element.addEventListener("input", e => {
|
element.addEventListener("input", e => {
|
||||||
localStorage[element.id] = element.value
|
let value = element.value
|
||||||
|
if (element.hasAttribute("checked")) {
|
||||||
|
value = element.checked
|
||||||
|
}
|
||||||
|
|
||||||
|
localStorage[element.id] = value
|
||||||
if (outputElement) {
|
if (outputElement) {
|
||||||
outputElement.value = element.value
|
outputElement.value = value
|
||||||
}
|
}
|
||||||
if (outputWpmElement) {
|
if (outputWpmElement) {
|
||||||
outputWpmElement.value = (1200 / element.value).toFixed(1)
|
outputWpmElement.value = (1200 / value).toFixed(1)
|
||||||
}
|
}
|
||||||
if (callback) {
|
if (callback) {
|
||||||
callback(e)
|
callback(e)
|
||||||
|
|
Loading…
Reference in New Issue