Closer to working again

This commit is contained in:
Neale Pickett 2020-04-26 15:46:37 -06:00
parent 8fcbb05183
commit 3282401e50
3 changed files with 176 additions and 148 deletions

View File

@ -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">

View File

@ -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;

View File

@ -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() {