mirror of https://github.com/nealey/vail.git
Closer to working again
This commit is contained in:
parent
8fcbb05183
commit
3282401e50
|
@ -88,15 +88,15 @@
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
<p>
|
<p>
|
||||||
dit length
|
dit length:
|
||||||
|
<output id="iambic-duration-value"></output>ms
|
||||||
<input
|
<input
|
||||||
id="duration"
|
id="iambic-duration"
|
||||||
class="mdl-slider mdl-js-slider"
|
class="mdl-slider mdl-js-slider"
|
||||||
type="range"
|
type="range"
|
||||||
min="40"
|
min="40"
|
||||||
max="255"
|
max="255"
|
||||||
value="80">
|
value="80">
|
||||||
<output id="duration-value"></output>
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="mdl-tabs__panel" id="straight">
|
<div class="mdl-tabs__panel" id="straight">
|
||||||
|
|
|
@ -5,6 +5,10 @@
|
||||||
justify-content: space-around;
|
justify-content: space-around;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mdl-card {
|
||||||
|
margin: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
.key {
|
.key {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 6em;
|
height: 6em;
|
||||||
|
|
292
static/vail.js
292
static/vail.js
|
@ -1,22 +1,26 @@
|
||||||
// jshint asi:true
|
// jshint asi:true
|
||||||
|
|
||||||
var recFreq = 660
|
const lowFreq = 660
|
||||||
var sendFreq = recFreq * 6 / 5 // Perfect minor third
|
const highFreq = lowFreq * 6 / 5 // Perfect minor third
|
||||||
|
|
||||||
const DIT = 1
|
const DIT = 1
|
||||||
const DAH = 3
|
const DAH = 3
|
||||||
|
|
||||||
class Iambic {
|
class Iambic {
|
||||||
constructor(BeginTxFunc, endTxFunc) {
|
constructor(beginTxFunc, endTxFunc) {
|
||||||
this.beginTxFunc = beginTxFunc
|
this.beginTxFunc = beginTxFunc
|
||||||
this.endTxFunc = endTxFunc
|
this.endTxFunc = endTxFunc
|
||||||
this.interval = null
|
this.interval = null
|
||||||
this.state = this.space
|
this.state = this.stateSpace
|
||||||
this.keyFunc = null
|
this.keyState = null
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set a new interval (transmission rate)
|
/**
|
||||||
setInterval(duration) {
|
* Set a new interval (transmission rate)
|
||||||
|
*
|
||||||
|
* @param {number} duration New interval duration, in ms
|
||||||
|
*/
|
||||||
|
SetInterval(duration) {
|
||||||
clearInterval(this.interval)
|
clearInterval(this.interval)
|
||||||
this.interval = setInterval(e => this.pulse(), duration)
|
this.interval = setInterval(e => this.pulse(), duration)
|
||||||
}
|
}
|
||||||
|
@ -26,111 +30,158 @@ class Iambic {
|
||||||
this.state()
|
this.state()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
stateSpace() {
|
||||||
// Don't transmit for one interval.
|
// Don't transmit for one interval.
|
||||||
// If a transmission was requested, start transmitting at the next interval
|
this.state = this.keyState || this.stateSpace
|
||||||
space() {
|
|
||||||
if (this.keyFunc) {
|
|
||||||
this.state = this.keyFunc
|
|
||||||
}
|
}
|
||||||
}
|
stateDit() {
|
||||||
|
|
||||||
// Send a dit
|
// Send a dit
|
||||||
dit() {
|
|
||||||
this.beginTxFunc()
|
this.beginTxFunc()
|
||||||
this.state = this.end
|
this.state = this.stateEnd
|
||||||
}
|
}
|
||||||
|
stateDah() {
|
||||||
// Send a dah
|
// Send a dah
|
||||||
dah() {
|
|
||||||
this.beginTxFunc()
|
this.beginTxFunc()
|
||||||
this.state = this.dah2
|
this.state = this.stateDah2
|
||||||
}
|
}
|
||||||
dah2() {
|
stateDah2() {
|
||||||
this.state = this.dah3
|
this.state = this.stateDah3
|
||||||
}
|
}
|
||||||
dah3() {
|
stateDah3() {
|
||||||
this.state = this.end
|
this.state = this.stateEnd
|
||||||
}
|
}
|
||||||
|
stateEnd() {
|
||||||
// Stop sending
|
// Stop sending
|
||||||
end() {
|
|
||||||
this.endTxFunc()
|
this.endTxFunc()
|
||||||
this.state = this.space
|
this.state = this.stateSpace
|
||||||
this.state()
|
this.state()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Edge trigger on key press
|
/**
|
||||||
KeyDown(key) {
|
* Edge trigger on key press or release
|
||||||
|
*
|
||||||
|
* @param {boolean} down True if key was pressed, false if released
|
||||||
|
* @param {number} key DIT or DAH
|
||||||
|
*/
|
||||||
|
Key(down, key) {
|
||||||
|
// By setting keyState we request this state transition,
|
||||||
|
// the next time the transition is possible.
|
||||||
|
let keyState = null
|
||||||
if (key == DIT) {
|
if (key == DIT) {
|
||||||
this.keyFunc = this.dit
|
keyState = this.stateDit
|
||||||
} else if (key == DAH) {
|
} else if (key == DAH) {
|
||||||
this.keyFunc = this.dah
|
keyState = this.stateDah
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Edge trigger on key release
|
if (down) {
|
||||||
KeyUp() {
|
this.keyState = keyState
|
||||||
// Only clear the keyFunc if the key released is the same one that we think is pressed
|
} else if (keyState == this.keyState) {
|
||||||
if ((key == DIT) && (this.keyFunc == this.dit)) {
|
// Only stop when we've released the right key
|
||||||
this.keyFunc = null
|
this.keyState = null
|
||||||
} else if ((key == DAH) && (this.keyFunc = this.dah)) {
|
|
||||||
this.keyFunc = null
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Buzzer {
|
class Buzzer {
|
||||||
constructor(txGain=0.1) {
|
// Buzzers keep two oscillators: one high and one low.
|
||||||
|
// They generate a continuous waveform,
|
||||||
|
// and we change the gain to turn the pitches off and on.
|
||||||
|
//
|
||||||
|
// This also implements a very quick ramp-up and ramp-down in gain,
|
||||||
|
// in order to avoid "pops" (square wave overtones)
|
||||||
|
// that happen with instant changes in gain.
|
||||||
|
|
||||||
|
constructor(txGain=0.5) {
|
||||||
this.txGain = txGain
|
this.txGain = txGain
|
||||||
|
|
||||||
this.ac = new AudioContext()
|
this.ac = new AudioContext()
|
||||||
|
|
||||||
this.lowGain = this.ac.createGain()
|
this.lowGain = this.ac.createGain()
|
||||||
this.lowGain.connect(ac.destination)
|
this.lowGain.connect(this.ac.destination)
|
||||||
this.lowGain.gain.value = 0
|
this.lowGain.gain.value = 0
|
||||||
this.lowOsc = this.ac.createOscillator()
|
this.lowOsc = this.ac.createOscillator()
|
||||||
this.lowOsc.connect(recGain)
|
this.lowOsc.connect(this.lowGain)
|
||||||
this.lowOsc.frequency.value = recFreq
|
this.lowOsc.frequency.value = lowFreq
|
||||||
|
this.lowOsc.start()
|
||||||
|
|
||||||
this.highGain = this.ac.createGain()
|
this.highGain = this.ac.createGain()
|
||||||
this.highGain.connect(ac.destination)
|
this.highGain.connect(this.ac.destination)
|
||||||
this.highGain.gain.value = 0
|
this.highGain.gain.value = 0
|
||||||
this.highOsc = this.ac.createOscillator()
|
this.highOsc = this.ac.createOscillator()
|
||||||
this.highOsc.connect(sendGain)
|
this.highOsc.connect(this.highGain)
|
||||||
this.highOsc.frequency.value = recFreq
|
this.highOsc.frequency.value = highFreq
|
||||||
|
this.highOsc.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
gain(high) {
|
gain(high) {
|
||||||
if (high) {
|
if (high) {
|
||||||
return this.highGain
|
return this.highGain.gain
|
||||||
} else {
|
} else {
|
||||||
return this.lowGain
|
return this.lowGain.gain
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Begin buzzing at time (null = now)
|
/**
|
||||||
Buzz(high=false, when=null) {
|
* Convert clock time to AudioContext time
|
||||||
if (when === null) {
|
*
|
||||||
when = this.ac.currentTime
|
* @param {number} when Clock time in ms
|
||||||
}
|
* @return {number} AudioContext offset time
|
||||||
this.gain(high).linearRampToValueAtTime(this.txGain, when + 0.1)
|
*/
|
||||||
|
acTime(when) {
|
||||||
|
if (! when) {
|
||||||
|
return this.ac.currentTime
|
||||||
}
|
}
|
||||||
|
|
||||||
// End buzzing at time (null = now)
|
let acOffset = Date.now() - this.ac.currentTime*1000
|
||||||
Silence(high=false, when=null) {
|
return (when - acOffset) / 1000
|
||||||
if (when === null) {
|
|
||||||
when = this.ac.currentTime
|
|
||||||
}
|
|
||||||
this.gain(high).linearRampToValueAtTime(0, when + 0.1)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Buzz for a duration at time
|
/**
|
||||||
BuzzDuration(high, when, duration) {
|
* Set gain
|
||||||
|
*
|
||||||
|
* @param {number} gain Value (0-1)
|
||||||
|
*/
|
||||||
|
SetGain(gain) {
|
||||||
|
this.txGain = gain
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Begin buzzing at time
|
||||||
|
*
|
||||||
|
* @param {boolean} high High or low pitched tone
|
||||||
|
* @param {number} when Time to begin (null=now)
|
||||||
|
*/
|
||||||
|
Buzz(high, when=null) {
|
||||||
let gain = this.gain(high)
|
let gain = this.gain(high)
|
||||||
gain.setValueAtTime(0, when)
|
let acWhen = this.acTime(when)
|
||||||
gain.linearRampToValueAtTime(this.txGain, when+0.1)
|
|
||||||
gain.setValueAtTime(this.txGain, when+duration)
|
this.ac.resume()
|
||||||
gain.linearRampToValueAtTime(this.txGain, when+duration+0.1)
|
gain.setTargetAtTime(this.txGain, acWhen, 0.001)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* End buzzing at time
|
||||||
|
*
|
||||||
|
* @param {boolean} high High or low pitched tone
|
||||||
|
* @param {number} when Time to begin (null=now)
|
||||||
|
*/
|
||||||
|
Silence(high, when=null) {
|
||||||
|
let gain = this.gain(high)
|
||||||
|
let acWhen = this.acTime(when)
|
||||||
|
|
||||||
|
gain.setTargetAtTime(0, acWhen, 0.001)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Buzz for a duration at time
|
||||||
|
*
|
||||||
|
* @param {boolean} high High or low pitched tone
|
||||||
|
* @param {number} when Time to begin (ms since 1970-01-01Z, null=now)
|
||||||
|
* @param {number} duration Duration of buzz (ms)
|
||||||
|
*/
|
||||||
|
BuzzDuration(high, when, duration) {
|
||||||
|
this.Buzz(high, when)
|
||||||
|
this.Silence(high, when+duration)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -147,7 +198,7 @@ class Vail {
|
||||||
|
|
||||||
// Listen for right clicks on dit button
|
// Listen for right clicks on dit button
|
||||||
let dit = document.querySelector("#dit")
|
let dit = document.querySelector("#dit")
|
||||||
dit.addEventListener("contextmenu", e => this.canWeJustNot(e))
|
dit.addEventListener("contextmenu", e => {e.preventDefault(); return false})
|
||||||
dit.addEventListener("mousedown", e => this.ditMouse(e))
|
dit.addEventListener("mousedown", e => this.ditMouse(e))
|
||||||
dit.addEventListener("mouseup", e => this.ditMouse(e))
|
dit.addEventListener("mouseup", e => this.ditMouse(e))
|
||||||
|
|
||||||
|
@ -155,99 +206,72 @@ class Vail {
|
||||||
document.addEventListener("keydown", e => this.key(e))
|
document.addEventListener("keydown", e => this.key(e))
|
||||||
document.addEventListener("keyup", e => this.key(e))
|
document.addEventListener("keyup", e => this.key(e))
|
||||||
|
|
||||||
// Make an Iambic input device
|
// Make helpers
|
||||||
this.iambic = new Iambic(() => this.beginTx(), () => this.endTx())
|
this.iambic = new Iambic(() => this.beginTx(), () => this.endTx())
|
||||||
|
this.buzzer = new Buzzer()
|
||||||
|
|
||||||
|
// Listen for slider values
|
||||||
let durationElement = document.querySelector("#duration")
|
this.inputListen("#iambic-duration", e => this.setIambicDuration(e))
|
||||||
durationElement.addEventListener("input", e => this.changeDuration(e))
|
|
||||||
this.durationInterval = setInterval(e => this.durationElapsed(e), durationElement.value)
|
|
||||||
|
|
||||||
this.keyDownTime = 0
|
|
||||||
this.durationsLeft = 0
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
keyDown() {
|
inputListen(selector, func) {
|
||||||
this.keyDownTime = new Date()
|
let element = document.querySelector(selector)
|
||||||
this.sendGain.linearRampToValueAtTime(0.1, this.ac.currentTime + 0.1)
|
element.addEventListener("input", func)
|
||||||
|
element.dispatchEvent(new Event("input"))
|
||||||
}
|
}
|
||||||
|
|
||||||
keyUp() {
|
setIambicDuration(event) {
|
||||||
let keyUpTime = new Date()
|
console.log(this)
|
||||||
let duration = keyUpTime - keyDownTime
|
this.iambic.SetInterval(event.target.value)
|
||||||
this.sendGain.linearRampToValueAtTime(0.0, this.ac.currentTime + 0.1)
|
document.querySelector("#iambic-duration-value").value = event.target.value
|
||||||
this.send(keyDownTime, [duration])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
durationElapsed(e) {
|
beginTx() {
|
||||||
if (this.durationsLeft === 0) {
|
this.beginTxTime = Date.now()
|
||||||
this.durationsLeft = this.keyDown + 1
|
this.buzzer.Buzz(true)
|
||||||
}
|
|
||||||
if (this.keyDown == 2) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
beep(gain, duration) {
|
endTx() {
|
||||||
let now = ac.currentTime
|
let endTxTime = Date.now()
|
||||||
let end = now + duration
|
let duration = endTxTime - this.beginTxTime
|
||||||
if (now === 0) {
|
this.buzzer.Silence(true)
|
||||||
return
|
|
||||||
|
let msg = JSON.stringify([this.beginTxTime, duration])
|
||||||
|
window.socket.send(msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ramping in and out prevents square wave overtones
|
wsMessage(event) {
|
||||||
gain.linearRampToValueAtTime(0.1, now + 0.1)
|
let msg = JSON.parse(event.data)
|
||||||
gain.setValueAtTime(0.1, end - 0.1)
|
let beginTxTime = msg[0]
|
||||||
gain.linearRampToValueAtTime(0.0, end)
|
let duration = msg[1]
|
||||||
}
|
|
||||||
|
|
||||||
message(event) {
|
|
||||||
let duration = Number(event.data) || 0
|
|
||||||
duration = Math.min(duration, long)
|
|
||||||
beep(this.recGain.gain, duration)
|
|
||||||
}
|
|
||||||
|
|
||||||
send(duration) {
|
|
||||||
window.socket.send(duration)
|
|
||||||
beep(this.sendGain.gain, duration)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
key(event) {
|
key(event) {
|
||||||
let duration = 0
|
|
||||||
|
|
||||||
ac.resume()
|
|
||||||
|
|
||||||
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
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((event.button === 0) || (event.code == "Period") || (event.key == "Shift")) {
|
let begin = event.type.endsWith("down")
|
||||||
duration = short
|
|
||||||
}
|
|
||||||
if ((event.button === 2) || (event.code == "Slash") || (event.code == "KeyZ")) {
|
|
||||||
duration = long
|
|
||||||
}
|
|
||||||
if (duration === 0) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (repeatInterval) {
|
if ((event.code == "Period") || (event.key == "KeyZ")) {
|
||||||
clearInterval(repeatInterval)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event.type.endsWith("down")) {
|
|
||||||
send(duration)
|
|
||||||
repeatInterval = setInterval(() => {send(duration)}, duration + short)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
canWeJustNot(event) {
|
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
return false
|
this.iambic.Key(begin, DIT)
|
||||||
|
}
|
||||||
|
if ((event.code == "Slash") || (event.code == "KeyX")) {
|
||||||
|
event.preventDefault()
|
||||||
|
this.iambic.Key(begin, DAH)
|
||||||
|
}
|
||||||
|
if ((event.key == "Shift")) {
|
||||||
|
event.preventDefault()
|
||||||
|
if (begin) {
|
||||||
|
this.beginTx()
|
||||||
|
} else {
|
||||||
|
this.endTx()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function vailInit() {
|
function vailInit() {
|
||||||
|
|
Loading…
Reference in New Issue