mirror of https://github.com/nealey/vail.git
Merge branch 'main' of https://github.com/nealey/vail
This commit is contained in:
commit
9df4c09229
|
@ -8,6 +8,6 @@ case "$1" in
|
||||||
docker -H ssh://melville.woozle.org service update --image ghcr.io/nealey/vail:main melville_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 --delete -va static/ melville.woozle.org:/srv/vail/testing/
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
* @file Provides some base audio tools.
|
* @file Provides some base audio tools.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import * as time from "./time.mjs"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compute the special "Audio Context" time
|
* Compute the special "Audio Context" time
|
||||||
*
|
*
|
||||||
|
@ -13,8 +15,8 @@
|
||||||
*/
|
*/
|
||||||
function AudioContextTime(context, when) {
|
function AudioContextTime(context, when) {
|
||||||
if (!when) return 0
|
if (!when) return 0
|
||||||
let acOffset = Date.now() - (context.currentTime * Second)
|
let acOffset = Date.now() - (context.currentTime * time.Second)
|
||||||
return Math.max(when - acOffset, 0) / Second
|
return Math.max(when - acOffset, 0) / time.Second
|
||||||
}
|
}
|
||||||
|
|
||||||
class AudioSource {
|
class AudioSource {
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
/** @typedef {Number} Duration */
|
import * as time from "./time.mjs"
|
||||||
const Millisecond = 1
|
|
||||||
const Second = 1000 * Millisecond
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A chart of historical values.
|
* A chart of historical values.
|
||||||
|
@ -14,7 +12,7 @@ class HistoryChart {
|
||||||
* @param {string} style style to draw in; falls back to the `data-style` attribute
|
* @param {string} style style to draw in; falls back to the `data-style` attribute
|
||||||
* @param {Duration} duration Time to display history for
|
* @param {Duration} duration Time to display history for
|
||||||
*/
|
*/
|
||||||
constructor(canvas, style=null, duration=20*Second) {
|
constructor(canvas, style=null, duration=20*time.Second) {
|
||||||
this.canvas = canvas
|
this.canvas = canvas
|
||||||
this.ctx = canvas.getContext("2d")
|
this.ctx = canvas.getContext("2d")
|
||||||
this.duration = duration
|
this.duration = duration
|
||||||
|
@ -22,7 +20,7 @@ class HistoryChart {
|
||||||
this.data = []
|
this.data = []
|
||||||
|
|
||||||
// One canvas pixel = 20ms
|
// One canvas pixel = 20ms
|
||||||
canvas.width = duration / (20 * Millisecond)
|
canvas.width = duration / (20 * time.Millisecond)
|
||||||
|
|
||||||
// Set origin to lower-left corner
|
// Set origin to lower-left corner
|
||||||
this.ctx.scale(1, -1)
|
this.ctx.scale(1, -1)
|
||||||
|
@ -113,7 +111,7 @@ class HistoryChart {
|
||||||
* @param duration duration of chart window
|
* @param duration duration of chart window
|
||||||
* @returns new chart, or null if it couldn't find a canvas
|
* @returns new chart, or null if it couldn't find a canvas
|
||||||
*/
|
*/
|
||||||
function FromSelector(selector, style, duration=20*Second) {
|
function FromSelector(selector, style, duration=20*time.Second) {
|
||||||
let canvas = document.querySelector(selector)
|
let canvas = document.querySelector(selector)
|
||||||
if (canvas) {
|
if (canvas) {
|
||||||
return new HistoryChart(canvas, style, duration)
|
return new HistoryChart(canvas, style, duration)
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as RoboKeyer from "./robokeyer.mjs"
|
import * as RoboKeyer from "./robokeyer.mjs"
|
||||||
|
import * as time from "./time.mjs"
|
||||||
|
|
||||||
/** Silent period between dits and dash */
|
/** Silent period between dits and dash */
|
||||||
const PAUSE = -1
|
const PAUSE = -1
|
||||||
|
@ -15,24 +16,6 @@ const DIT = 1
|
||||||
/** Length of a dah */
|
/** Length of a dah */
|
||||||
const DAH = 3
|
const DAH = 3
|
||||||
|
|
||||||
/**
|
|
||||||
* A time duration.
|
|
||||||
*
|
|
||||||
* JavaScript uses milliseconds in most (but not all) places.
|
|
||||||
* I've found it helpful to be able to multiply by a unit, so it's clear what's going on.
|
|
||||||
*
|
|
||||||
* @typedef {number} Duration
|
|
||||||
*/
|
|
||||||
/** @type {Duration} */
|
|
||||||
const Millisecond = 1
|
|
||||||
/** @type {Duration} */
|
|
||||||
const Second = 1000 * Millisecond
|
|
||||||
/** @type {Duration} */
|
|
||||||
const Minute = 60 * Second
|
|
||||||
/** @type {Duration} */
|
|
||||||
const Hour = 60 * Minute
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Queue Set: A Set you can shift and pop.
|
* Queue Set: A Set you can shift and pop.
|
||||||
*
|
*
|
||||||
|
@ -185,7 +168,7 @@ class BugKeyer extends StraightKeyer {
|
||||||
|
|
||||||
Reset() {
|
Reset() {
|
||||||
super.Reset()
|
super.Reset()
|
||||||
this.SetDitDuration(100 * Millisecond)
|
this.SetDitDuration(100 * time.Millisecond)
|
||||||
if (this.pulseTimer) {
|
if (this.pulseTimer) {
|
||||||
clearInterval(this.pulseTimer)
|
clearInterval(this.pulseTimer)
|
||||||
this.pulseTimer = null
|
this.pulseTimer = null
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import {AudioSource, AudioContextTime} from "./audio.mjs"
|
import {AudioSource, AudioContextTime} from "./audio.mjs"
|
||||||
|
import * as time from "./time.mjs"
|
||||||
|
|
||||||
const HIGH_FREQ = 555
|
const HIGH_FREQ = 555
|
||||||
const LOW_FREQ = 444
|
const LOW_FREQ = 444
|
||||||
|
@ -19,36 +20,33 @@ const LOW_FREQ = 444
|
||||||
* @typedef {number} Date
|
* @typedef {number} Date
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const Millisecond = 1
|
|
||||||
const Second = 1000 * Millisecond
|
|
||||||
|
|
||||||
/** The amount of time it should take an oscillator to ramp to and from zero gain
|
/** The amount of time it should take an oscillator to ramp to and from zero gain
|
||||||
*
|
*
|
||||||
* @constant {Duration}
|
* @constant {Duration}
|
||||||
*/
|
*/
|
||||||
const OscillatorRampDuration = 5*Millisecond
|
const OscillatorRampDuration = 5*time.Millisecond
|
||||||
|
|
||||||
|
|
||||||
class Oscillator extends AudioSource {
|
class Oscillator extends AudioSource {
|
||||||
/**
|
/**
|
||||||
* Create a new oscillator, and encase it in a Gain for control.
|
* Create a new oscillator, and encase it in a Gain for control.
|
||||||
*
|
*
|
||||||
* @param {AudioContext} context Audio context
|
* @param {AudioContext} context Audio context
|
||||||
* @param {number} frequency Oscillator frequency (Hz)
|
* @param {number} frequency Oscillator frequency (Hz)
|
||||||
* @param {number} maxGain Maximum gain (volume) of this oscillator (0.0 - 1.0)
|
* @param {number} maxGain Maximum gain (volume) of this oscillator (0.0 - 1.0)
|
||||||
* @param {string} type Oscillator type
|
* @param {string} type Oscillator type
|
||||||
*/
|
*/
|
||||||
constructor(context, frequency, maxGain = 0.5, type = "sine") {
|
constructor(context, frequency, maxGain = 0.5, type = "sine") {
|
||||||
super(context)
|
super(context)
|
||||||
this.maxGain = maxGain
|
this.maxGain = maxGain
|
||||||
|
|
||||||
// Start quiet
|
// Start quiet
|
||||||
this.masterGain.gain.value = 0
|
this.masterGain.gain.value = 0
|
||||||
|
|
||||||
this.osc = new OscillatorNode(this.context)
|
this.osc = new OscillatorNode(this.context)
|
||||||
this.osc.type = type
|
this.osc.type = type
|
||||||
this.osc.connect(this.masterGain)
|
this.osc.connect(this.masterGain)
|
||||||
this.setFrequency(frequency)
|
this.setFrequency(frequency)
|
||||||
this.osc.start()
|
this.osc.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -88,7 +86,7 @@ class Oscillator extends AudioSource {
|
||||||
this.masterGain.gain.setTargetAtTime(
|
this.masterGain.gain.setTargetAtTime(
|
||||||
target,
|
target,
|
||||||
AudioContextTime(this.context, when),
|
AudioContextTime(this.context, when),
|
||||||
timeConstant/Second,
|
timeConstant/time.Second,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -227,7 +225,7 @@ class AudioBuzzer extends Buzzer {
|
||||||
Error() {
|
Error() {
|
||||||
let now = Date.now()
|
let now = Date.now()
|
||||||
this.errorTone.SoundAt(now)
|
this.errorTone.SoundAt(now)
|
||||||
this.errorTone.HushAt(now + 200*Millisecond)
|
this.errorTone.HushAt(now + 200*time.Millisecond)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,5 @@
|
||||||
import {GetFortune} from "./fortunes.mjs"
|
import {GetFortune} from "./fortunes.mjs"
|
||||||
|
import * as time from "./time.mjs"
|
||||||
const Millisecond = 1
|
|
||||||
const Second = 1000 * Millisecond
|
|
||||||
const Minute = 60 * Second
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compare two messages
|
* Compare two messages
|
||||||
|
@ -61,7 +58,7 @@ export class Vail {
|
||||||
msg => {
|
msg => {
|
||||||
this.rx(0, 0, {connected: false, notice: `Repeater disconnected: ${msg.reason}`})
|
this.rx(0, 0, {connected: false, notice: `Repeater disconnected: ${msg.reason}`})
|
||||||
console.error("Repeater connection dropped:", msg.reason)
|
console.error("Repeater connection dropped:", msg.reason)
|
||||||
setTimeout(() => this.reopen(), 2*Second)
|
setTimeout(() => this.reopen(), 2*time.Second)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -157,7 +154,7 @@ export class Vail {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Null {
|
export class Null {
|
||||||
constructor(rx, interval=3*Second) {
|
constructor(rx, interval=3*time.Second) {
|
||||||
this.rx = rx
|
this.rx = rx
|
||||||
this.init()
|
this.init()
|
||||||
}
|
}
|
||||||
|
@ -219,11 +216,11 @@ export class Fortune extends Null {
|
||||||
if (this.timeout) {
|
if (this.timeout) {
|
||||||
clearTimeout(this.timeout)
|
clearTimeout(this.timeout)
|
||||||
}
|
}
|
||||||
this.timeout = setTimeout(() => this.pulse(), 3 * Second)
|
this.timeout = setTimeout(() => this.pulse(), 3 * time.Second)
|
||||||
}
|
}
|
||||||
|
|
||||||
Close() {
|
Close() {
|
||||||
this.keyer.Flush()
|
this.keyer.Flush()
|
||||||
super.Close()
|
super.Close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,15 +6,13 @@
|
||||||
*
|
*
|
||||||
* @typedef {number} Duration
|
* @typedef {number} Duration
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/** @type {Duration} */
|
/** @type {Duration} */
|
||||||
export const Millisecond = 1
|
const Millisecond = 1
|
||||||
|
|
||||||
/** @type {Duration} */
|
/** @type {Duration} */
|
||||||
export const Second = 1000 * Millisecond
|
const Second = 1000 * Millisecond
|
||||||
|
|
||||||
/** @type {Duration} */
|
/** @type {Duration} */
|
||||||
export const Minute = 60 * Second
|
const Minute = 60 * Second
|
||||||
|
|
||||||
/** @type {Duration} */
|
/** @type {Duration} */
|
||||||
export const Hour = 60 * Minute
|
const Hour = 60 * Minute
|
||||||
|
|
||||||
|
export {Millisecond, Second, Minute, Hour}
|
|
@ -4,12 +4,10 @@ import * as Inputs from "./inputs.mjs"
|
||||||
import * as Repeaters from "./repeaters.mjs"
|
import * as Repeaters from "./repeaters.mjs"
|
||||||
import * as Chart from "./chart.mjs"
|
import * as Chart from "./chart.mjs"
|
||||||
import * as I18n from "./i18n.mjs"
|
import * as I18n from "./i18n.mjs"
|
||||||
|
import * as time from "./time.mjs"
|
||||||
import * as Music from "./music.mjs"
|
import * as Music from "./music.mjs"
|
||||||
|
|
||||||
const DefaultRepeater = "General"
|
const DefaultRepeater = "General"
|
||||||
const Millisecond = 1
|
|
||||||
const Second = 1000 * Millisecond
|
|
||||||
const Minute = 60 * Second
|
|
||||||
|
|
||||||
console.warn("Chrome will now complain about an AudioContext not being allowed to start. This is normal, and there is no way to make Chrome stop complaining about this.")
|
console.warn("Chrome will now complain about an AudioContext not being allowed to start. This is normal, and there is no way to make Chrome stop complaining about this.")
|
||||||
const globalAudioContext = new AudioContext({
|
const globalAudioContext = new AudioContext({
|
||||||
|
@ -21,7 +19,7 @@ const globalAudioContext = new AudioContext({
|
||||||
*
|
*
|
||||||
* @param {string} msg Message to display
|
* @param {string} msg Message to display
|
||||||
*/
|
*/
|
||||||
function toast(msg, timeout=4*Second) {
|
function toast(msg, timeout=4*time.Second) {
|
||||||
console.info(msg)
|
console.info(msg)
|
||||||
|
|
||||||
let errors = document.querySelector("#errors")
|
let errors = document.querySelector("#errors")
|
||||||
|
@ -41,7 +39,7 @@ class VailClient {
|
||||||
this.lagTimes = [0]
|
this.lagTimes = [0]
|
||||||
this.rxDurations = [0]
|
this.rxDurations = [0]
|
||||||
this.clockOffset = null // How badly our clock is off of the server's
|
this.clockOffset = null // How badly our clock is off of the server's
|
||||||
this.rxDelay = 0 * Millisecond // Time to add to incoming timestamps
|
this.rxDelay = 0 * time.Millisecond // Time to add to incoming timestamps
|
||||||
this.beginTxTime = null // Time when we began transmitting
|
this.beginTxTime = null // Time when we began transmitting
|
||||||
|
|
||||||
// Outputs
|
// Outputs
|
||||||
|
@ -76,7 +74,7 @@ class VailClient {
|
||||||
this.inputInit("#keyer-mode", e => this.setKeyer(e.target.value))
|
this.inputInit("#keyer-mode", e => this.setKeyer(e.target.value))
|
||||||
this.inputInit("#keyer-rate", e => {
|
this.inputInit("#keyer-rate", e => {
|
||||||
let rate = e.target.value
|
let rate = e.target.value
|
||||||
this.ditDuration = Math.round(Minute / rate / 50)
|
this.ditDuration = Math.round(time.Minute / rate / 50)
|
||||||
for (let e of document.querySelectorAll("[data-fill='keyer-ms']")) {
|
for (let e of document.querySelectorAll("[data-fill='keyer-ms']")) {
|
||||||
e.textContent = this.ditDuration
|
e.textContent = this.ditDuration
|
||||||
}
|
}
|
||||||
|
@ -85,7 +83,7 @@ class VailClient {
|
||||||
this.inputs.SetDitDuration(this.ditDuration)
|
this.inputs.SetDitDuration(this.ditDuration)
|
||||||
})
|
})
|
||||||
this.inputInit("#rx-delay", e => {
|
this.inputInit("#rx-delay", e => {
|
||||||
this.rxDelay = e.target.value * Second
|
this.rxDelay = e.target.value * time.Second
|
||||||
})
|
})
|
||||||
this.inputInit("#masterGain", e => {
|
this.inputInit("#masterGain", e => {
|
||||||
this.outputs.SetGain(e.target.value / 100)
|
this.outputs.SetGain(e.target.value / 100)
|
||||||
|
@ -464,7 +462,7 @@ class VailClient {
|
||||||
|
|
||||||
function init() {
|
function init() {
|
||||||
if (navigator.serviceWorker) {
|
if (navigator.serviceWorker) {
|
||||||
navigator.serviceWorker.register("sw.js")
|
navigator.serviceWorker.register("scripts/sw.js")
|
||||||
}
|
}
|
||||||
I18n.Setup()
|
I18n.Setup()
|
||||||
try {
|
try {
|
||||||
|
|
Loading…
Reference in New Issue