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:
Neale Pickett 2022-04-21 22:05:51 -06:00
parent 870c318d96
commit fc537d7341
3 changed files with 169 additions and 86 deletions

View File

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

View File

@ -130,8 +130,9 @@ class Keyer {
if (next > 1) {
// Don't adjust spacing within a letter
next *= this.pauseMultiplier
} else {
this.endTxFunc()
}
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}

View File

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