mirror of https://github.com/nealey/vail.git
Multiple fixes
* The little light now shows for telegraph sounds * Telegraph no longer plays the "off" sound constantly * The little light now shows for Fortunes repeaters * MIDI changes * No longer sends note off to disable keyboard mode * Now sends dit length/2 as MIDI control 1
This commit is contained in:
parent
870c318d96
commit
fc537d7341
|
@ -1,7 +1,15 @@
|
|||
|
||||
export class HTML {
|
||||
class Input {
|
||||
constructor(keyer) {
|
||||
this.keyer = keyer
|
||||
}
|
||||
SetIntervalDuration(delay) {
|
||||
// Nothing
|
||||
}
|
||||
}
|
||||
|
||||
export class HTML extends Input{
|
||||
constructor(keyer) {
|
||||
super(keyer)
|
||||
|
||||
// Listen to HTML buttons
|
||||
for (let e of document.querySelectorAll("button.key")) {
|
||||
|
@ -30,9 +38,9 @@ export class HTML {
|
|||
}
|
||||
}
|
||||
|
||||
export class Keyboard {
|
||||
export class Keyboard extends Input{
|
||||
constructor(keyer) {
|
||||
this.keyer = keyer
|
||||
super(keyer)
|
||||
|
||||
// Listen for keystrokes
|
||||
document.addEventListener("keydown", e => this.keyboard(e))
|
||||
|
@ -92,10 +100,11 @@ export class Keyboard {
|
|||
}
|
||||
}
|
||||
|
||||
export class MIDI {
|
||||
export class MIDI extends Input{
|
||||
constructor(keyer) {
|
||||
this.keyer = keyer
|
||||
super(keyer)
|
||||
|
||||
this.midiAccess = {outputs: []} // stub while we wait for async stuff
|
||||
if (navigator.requestMIDIAccess) {
|
||||
this.midiInit()
|
||||
}
|
||||
|
@ -108,6 +117,14 @@ export class MIDI {
|
|||
this.midiStateChange()
|
||||
}
|
||||
|
||||
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])
|
||||
}
|
||||
}
|
||||
|
||||
midiStateChange(event) {
|
||||
// Go through this.midiAccess.inputs and only listen on new things
|
||||
for (let input of this.midiAccess.inputs.values()) {
|
||||
|
@ -119,7 +136,7 @@ export class MIDI {
|
|||
|
||||
// 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
|
||||
output.send([0x8B, 0x00, 0x00]) // Turn off keyboard mode
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -156,9 +173,9 @@ export class MIDI {
|
|||
}
|
||||
}
|
||||
|
||||
export class Gamepad {
|
||||
export class Gamepad extends Input{
|
||||
constructor(keyer) {
|
||||
this.keyer = keyer
|
||||
super(keyer)
|
||||
|
||||
// Set up for gamepad input
|
||||
window.addEventListener("gamepadconnected", e => this.gamepadConnected(e))
|
||||
|
|
193
static/morse.mjs
193
static/morse.mjs
|
@ -130,8 +130,9 @@ class Keyer {
|
|||
if (next > 1) {
|
||||
// Don't adjust spacing within a letter
|
||||
next *= this.pauseMultiplier
|
||||
}
|
||||
} else {
|
||||
this.endTxFunc()
|
||||
}
|
||||
} else {
|
||||
this.last = next
|
||||
this.beginTxFunc()
|
||||
|
@ -329,31 +330,62 @@ class Keyer {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A (mostly) virtual class defining a buzzer.
|
||||
*/
|
||||
class Buzzer {
|
||||
// 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.
|
||||
/**
|
||||
* Signal an error
|
||||
*/
|
||||
Error() {
|
||||
console.log("Error")
|
||||
}
|
||||
|
||||
constructor({txGain=0.6, highFreq=660, lowFreq=550, errorFreq=30} = {}) {
|
||||
this.txGain = txGain
|
||||
/**
|
||||
* Begin buzzing at time
|
||||
*
|
||||
* @param {boolean} tx Transmit or receive tone
|
||||
* @param {number} when Time to begin, in ms (null=now)
|
||||
*/
|
||||
Buzz(tx, when = null) {
|
||||
console.log("Buzz", tx, when)
|
||||
}
|
||||
|
||||
/**
|
||||
* End buzzing at time
|
||||
*
|
||||
* @param {boolean} tx Transmit or receive tone
|
||||
* @param {number} when Time to end, in ms (null=now)
|
||||
*/
|
||||
Silence(tx, when = null) {
|
||||
console.log("Silence", tx, when)
|
||||
}
|
||||
|
||||
/**
|
||||
* Buzz for a duration at time
|
||||
*
|
||||
* @param {boolean} tx Transmit or receive tone
|
||||
* @param {number} when Time to begin (ms since 1970-01-01Z, null=now)
|
||||
* @param {number} duration Duration of buzz (ms)
|
||||
*/
|
||||
BuzzDuration(tx, when, duration) {
|
||||
this.Buzz(tx, when)
|
||||
this.Silence(tx, when + duration)
|
||||
}
|
||||
}
|
||||
|
||||
class AudioBuzzer extends Buzzer {
|
||||
constructor(errorFreq=30) {
|
||||
super()
|
||||
|
||||
this.ac = new AudioContext()
|
||||
this.ramp = 0.005 // Lead-in and lead-out time to avoid popping. This one is in seconds.
|
||||
|
||||
this.lowGain = this.create(lowFreq)
|
||||
this.highGain = this.create(highFreq)
|
||||
this.errorGain = this.create(errorFreq, "square")
|
||||
//this.noiseGain = this.whiteNoise()
|
||||
|
||||
this.ac.resume()
|
||||
.then(() => {
|
||||
document.querySelector("#muted").classList.add("hidden")
|
||||
})
|
||||
|
||||
this.errorGain = this.create(errorFreq, "square")
|
||||
this.errorFreq = errorFreq
|
||||
}
|
||||
|
||||
create(frequency, type = "sine") {
|
||||
|
@ -368,6 +400,53 @@ class Buzzer {
|
|||
return gain
|
||||
}
|
||||
|
||||
ready() {
|
||||
return this.ac.state == "running"
|
||||
}
|
||||
|
||||
Error() {
|
||||
this.errorGain.gain.setTargetAtTime(0.5, this.ac.currentTime, 0.001)
|
||||
this.errorGain.gain.setTargetAtTime(0, this.ac.currentTime + 0.2, 0.001)
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert clock time to AudioContext time
|
||||
*
|
||||
* @param {number} when Clock time in ms
|
||||
* @return {number} AudioContext offset time
|
||||
*/
|
||||
acTime(when) {
|
||||
if (!when) {
|
||||
return this.ac.currentTime
|
||||
}
|
||||
|
||||
let acOffset = Date.now() - this.ac.currentTime * 1000
|
||||
let acTime = (when - acOffset) / 1000
|
||||
return acTime
|
||||
}
|
||||
}
|
||||
|
||||
class ToneBuzzer extends AudioBuzzer {
|
||||
// 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.6, highFreq=660, lowFreq=550} = {}) {
|
||||
super()
|
||||
this.txGain = txGain
|
||||
|
||||
this.ac = new AudioContext()
|
||||
this.ramp = 0.005 // Lead-in and lead-out time to avoid popping. This one is in seconds.
|
||||
|
||||
this.lowGain = this.create(lowFreq)
|
||||
this.highGain = this.create(highFreq)
|
||||
//this.noiseGain = this.whiteNoise()
|
||||
}
|
||||
|
||||
// Generate some noise to prevent the browser from putting us to sleep
|
||||
whiteNoise() {
|
||||
let bufferSize = 17 * this.ac.sampleRate
|
||||
|
@ -404,21 +483,6 @@ class Buzzer {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert clock time to AudioContext time
|
||||
*
|
||||
* @param {number} when Clock time in ms
|
||||
* @return {number} AudioContext offset time
|
||||
*/
|
||||
acTime(when) {
|
||||
if (!when) {
|
||||
return this.ac.currentTime
|
||||
}
|
||||
|
||||
let acOffset = Date.now() - this.ac.currentTime * 1000
|
||||
let acTime = (when - acOffset) / 1000
|
||||
return acTime
|
||||
}
|
||||
|
||||
/**
|
||||
* Set gain
|
||||
|
@ -429,14 +493,6 @@ class Buzzer {
|
|||
this.txGain = gain
|
||||
}
|
||||
|
||||
/**
|
||||
* Play an error tone
|
||||
*/
|
||||
ErrorTone() {
|
||||
this.errorGain.gain.setTargetAtTime(this.txGain * 0.5, this.ac.currentTime, 0.001)
|
||||
this.errorGain.gain.setTargetAtTime(0, this.ac.currentTime + 0.2, 0.001)
|
||||
}
|
||||
|
||||
/**
|
||||
* Begin buzzing at time
|
||||
*
|
||||
|
@ -444,14 +500,6 @@ class Buzzer {
|
|||
* @param {number} when Time to begin, in ms (null=now)
|
||||
*/
|
||||
Buzz(tx, when = null) {
|
||||
if (!tx) {
|
||||
let recv = document.querySelector("#recv")
|
||||
let ms = when - Date.now()
|
||||
setTimeout(e => {
|
||||
recv.classList.add("rx")
|
||||
}, ms)
|
||||
}
|
||||
|
||||
let gain = this.gain(tx)
|
||||
let acWhen = this.acTime(when)
|
||||
this.ac.resume()
|
||||
|
@ -467,34 +515,14 @@ class Buzzer {
|
|||
* @param {number} when Time to end, in ms (null=now)
|
||||
*/
|
||||
Silence(tx, when = null) {
|
||||
if (!tx) {
|
||||
let recv = document.querySelector("#recv")
|
||||
let ms = when - Date.now()
|
||||
setTimeout(e => {
|
||||
recv.classList.remove("rx")
|
||||
}, ms)
|
||||
}
|
||||
|
||||
let gain = this.gain(tx)
|
||||
let acWhen = this.acTime(when)
|
||||
|
||||
gain.setTargetAtTime(0, acWhen, this.ramp)
|
||||
}
|
||||
|
||||
/**
|
||||
* Buzz for a duration at time
|
||||
*
|
||||
* @param {boolean} tx Transmit or receive tone
|
||||
* @param {number} when Time to begin (ms since 1970-01-01Z, null=now)
|
||||
* @param {number} duration Duration of buzz (ms)
|
||||
*/
|
||||
BuzzDuration(tx, when, duration) {
|
||||
this.Buzz(tx, when)
|
||||
this.Silence(tx, when + duration)
|
||||
}
|
||||
}
|
||||
|
||||
class TelegraphBuzzer extends Buzzer{
|
||||
class TelegraphBuzzer extends AudioBuzzer{
|
||||
constructor(gain=0.6) {
|
||||
super()
|
||||
|
||||
|
@ -530,5 +558,30 @@ class TelegraphBuzzer extends Buzzer{
|
|||
}
|
||||
}
|
||||
|
||||
class Lamp extends Buzzer {
|
||||
constructor() {
|
||||
super()
|
||||
this.lamp = document.querySelector("#recv")
|
||||
}
|
||||
|
||||
Buzz(tx, when=0) {
|
||||
if (tx) return
|
||||
|
||||
let ms = when - Date.now()
|
||||
setTimeout(e => {
|
||||
recv.classList.add("rx")
|
||||
}, ms)
|
||||
}
|
||||
Silence(tx, when=0) {
|
||||
if (tx) return
|
||||
|
||||
let recv = document.querySelector("#recv")
|
||||
let ms = when - Date.now()
|
||||
setTimeout(e => {
|
||||
recv.classList.remove("rx")
|
||||
}, ms)
|
||||
}
|
||||
}
|
||||
|
||||
export {DIT, DAH, PAUSE, PAUSE_WORD, PAUSE_LETTER}
|
||||
export {Keyer, Buzzer, TelegraphBuzzer}
|
||||
export {Keyer, ToneBuzzer, TelegraphBuzzer, Lamp}
|
||||
|
|
|
@ -2,10 +2,9 @@ import * as Morse from "./morse.mjs"
|
|||
import * as Inputs from "./inputs.mjs"
|
||||
import * as Repeaters from "./repeaters.mjs"
|
||||
|
||||
const DefaultRepeater = "General Chaos"
|
||||
const DefaultRepeater = "General"
|
||||
const Millisecond = 1
|
||||
const Second = 1000 * Millisecond
|
||||
const Minute = 60 * Second
|
||||
|
||||
/**
|
||||
* Pop up a message, using an MDL snackbar.
|
||||
|
@ -30,14 +29,24 @@ class VailClient {
|
|||
this.lagTimes = [0]
|
||||
this.rxDurations = [0]
|
||||
this.clockOffset = null // How badly our clock is off of the server's
|
||||
this.rxDelay = 0 // Milliseconds to add to incoming timestamps
|
||||
this.rxDelay = 0 * Millisecond // Time to add to incoming timestamps
|
||||
this.beginTxTime = null // Time when we began transmitting
|
||||
this.debug = localStorage.debug
|
||||
|
||||
// Make helpers
|
||||
this.buzzer = new Morse.Buzzer()
|
||||
this.lamp = new Morse.Lamp()
|
||||
this.buzzer = new Morse.ToneBuzzer()
|
||||
this.keyer = new Morse.Keyer(() => this.beginTx(), () => this.endTx())
|
||||
this.roboKeyer = new Morse.Keyer(() => this.buzzer.Buzz(), () => this.buzzer.Silence())
|
||||
this.roboKeyer = new Morse.Keyer(
|
||||
() => {
|
||||
this.buzzer.Buzz()
|
||||
this.lamp.Buzz()
|
||||
},
|
||||
() => {
|
||||
this.buzzer.Silence()
|
||||
this.lamp.Silence()
|
||||
}
|
||||
)
|
||||
|
||||
// Set up various input methods
|
||||
this.inputs = Inputs.SetupAll(this.keyer)
|
||||
|
@ -59,6 +68,9 @@ class VailClient {
|
|||
this.inputInit("#iambic-duration", e => {
|
||||
this.keyer.SetIntervalDuration(e.target.value)
|
||||
this.roboKeyer.SetIntervalDuration(e.target.value)
|
||||
for (let i of Object.values(this.inputs)) {
|
||||
i.SetIntervalDuration(e.target.value)
|
||||
}
|
||||
})
|
||||
this.inputInit("#rx-delay", e => {
|
||||
this.rxDelay = Number(e.target.value)
|
||||
|
@ -88,7 +100,7 @@ class VailClient {
|
|||
if (enable) {
|
||||
this.buzzer = new Morse.TelegraphBuzzer()
|
||||
} else {
|
||||
this.buzzer = new Morse.Buzzer()
|
||||
this.buzzer = new Morse.ToneBuzzer()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -226,7 +238,7 @@ class VailClient {
|
|||
*/
|
||||
error(msg) {
|
||||
toast(msg)
|
||||
this.buzzer.ErrorTone()
|
||||
this.buzzer.Error()
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -267,6 +279,7 @@ class VailClient {
|
|||
}
|
||||
|
||||
this.buzzer.BuzzDuration(false, when, duration)
|
||||
this.lamp.BuzzDuration(false, when, duration)
|
||||
|
||||
this.rxDurations.unshift(duration)
|
||||
this.rxDurations.splice(20, 2)
|
||||
|
|
Loading…
Reference in New Issue