Freetext repeater entry (fixes #17)

This commit is contained in:
Neale Pickett 2021-04-27 12:42:06 -06:00
parent 42b3eb1621
commit 82d0787ef8
4 changed files with 187 additions and 164 deletions

View File

@ -58,15 +58,23 @@
<main class="mdl-layout__content"> <main class="mdl-layout__content">
<div class="flex"> <div class="flex">
<div class="mdl-card mdl-shadow--4dp input-methods"> <div class="mdl-card mdl-shadow--4dp input-methods">
<div class="mdl-card__title">
<h2 class="mdl-card__title-text">
<span id="repeater"></span>
Repeater
</h2>
<div id="recv"> <div id="recv">
<!-- This div appears as a little light that turns on when someone's sending --> <!-- This div appears as a little light that turns on when someone's sending -->
<i class="material-icons" id="muted">volume_off</i> <i class="material-icons" id="muted">volume_off</i>
</div> </div>
<div class="mdl-card__title">
<h2 class="mdl-card__title-text">
<div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label">
<input class="mdl-textfield__input" type="text" id="repeater" list="repeater-list">
<datalist id="repeater-list">
<option>General Chaos</option>
<option>1-15 WPM</option>
<option>16-20 WPM</option>
<option>21+ WPM</option>
</datalist>
<label class="mdl-textfield__label" for="repeater">Repeater</label>
</div>
</h2>
</div> </div>
<div class="mdl-tabs mdl-js-tabs mdl-js-ripple-effect"> <div class="mdl-tabs mdl-js-tabs mdl-js-ripple-effect">
<div class="mdl-tabs__tab-bar"> <div class="mdl-tabs__tab-bar">
@ -91,7 +99,6 @@
<kbd>c</kbd> <kbd>c</kbd>
<kbd>,</kbd> <kbd>,</kbd>
<kbd>Enter</kbd> <kbd>Enter</kbd>
<kbd>⇧ Shift</kbd>
</td> </td>
</tr> </tr>
<tr> <tr>
@ -354,7 +361,7 @@
value="100"> value="100">
</p> </p>
<p> <p>
Recieve delay: Receive delay:
<output id="rx-delay-value"></output>ms <output id="rx-delay-value"></output>ms
<input <input
id="rx-delay" id="rx-delay"
@ -406,9 +413,15 @@
<div class="mdl-card mdl-shadow--4dp"> <div class="mdl-card mdl-shadow--4dp">
<div class="mdl-card__title"> <div class="mdl-card__title">
<h2 class="mdl-card__title-text">Vail</h2> <h2 class="mdl-card__title-text">Documentation</h2>
</div> </div>
<div class="mdl-card__supporting-text"> <div class="mdl-tabs mdl-js-tabs mdl-js-ripple-effect">
<div class="mdl-tabs__tab-bar">
<a href="#doc-about" class="mdl-tabs__tab is-active">About</a>
<a href="#doc-faq" class="mdl-tabs__tab">FAQ</a>
<a href="#doc-geek" class="mdl-tabs__tab">Geek Stuff</a>
</div>
<div class="mdl-tabs__panel mdl-card__supporting-text long is-active" id="doc-about">
<p> <p>
This is a CW repeater, This is a CW repeater,
named after Alfred Vail, named after Alfred Vail,
@ -421,24 +434,55 @@
anybody can connect and start transmitting stuff, anybody can connect and start transmitting stuff,
and this will broadcast it to everyone connected. and this will broadcast it to everyone connected.
</p> </p>
</div>
<div class="mdl-tabs__panel mdl-card__supporting-text long" id="doc-faq">
<h3 class="mdl-card__title-text">Why Does This Exist?</h3> <h3 class="mdl-card__title-text">Why Does This Exist?</h3>
<p> <p>
I need a place to practice CW with actual human beings, I needed a place to practice CW with actual human beings,
and I want it to be as close as possible to what I'd experience on a radio. and I wanted it to be as close as possible to what I'd experience on a radio.
Also, I don't want to make people buy a bunch of radio hardware. I also didn't have a lot of money to spend on equipment, but I did have a computer, phone, and gamepad.
Nothing else like this exists on the Internet, as far as I can tell. Nothing else like this exists on the Internet, as far as I can tell.
</p> </p>
</div>
<h3 class="mdl-card__title-text">Why do I hear a low tone?</h3>
<p>
This is the "drop tone", and will be accompanied by an error.
</p>
<p>
This means the packet arrived so late, it can't be played in time.
In technical terms: the timestamp of the packet plus the receive delay
is less than the current time.
It can't be scheduled to play, because we can't go back in time.
</p>
<p>
This could be happening for three reasons:
</p>
<ol>
<li>You (the person hearing the drop tone) need a larger receive delay</li>
<li>The receiving computer's clock is in the future (running fast)</li>
<li>The sending computer's clock is in the past (running slow)</li>
</ol>
<p>
Make sure your clock is synced with an Internet time server.
Accurate time is very important to how Vail works.
</p>
<h3 class="mdl-card__title-text">How can I help?</h3>
<ul>
<li>Improve the <a href="https://github.com/nealey/vail/">source code</a></li>
<li>Email me and let me know you're using it</li>
<li>Vail costs me 50¢ a year to run: you could buy me a cup of coffee every 5 years or so to offset the expense</li>
</ul>
<h3 class="mdl-card__title-text">Who made this?</h3>
<p>
<a href="mailto:neale@woozle.org">Neale Pickett</a> kd7oqi
</p>
</div> </div>
<div class="mdl-card mdl-shadow--4dp">
<div class="mdl-card__title">
<h2 class="mdl-card__title-text">How It Works</h2>
</div>
<div class="mdl-card__supporting-text">
<div class="mdl-tabs__panel mdl-card__supporting-text long" id="doc-geek">
<p> <p>
The Internet isn't exactly like radio waves: The Internet isn't exactly like radio waves:
it still goes at near the speed of light, it still goes at near the speed of light,
@ -451,16 +495,13 @@
Because of this, Because of this,
there's no telling how long it will take for a transmission to get to a destination. there's no telling how long it will take for a transmission to get to a destination.
</p> </p>
<p> <p>
Each Vail transmission (packet) consists of: Each Vail transmission (packet) consists of:
</p> </p>
<ul> <ul>
<li>timestamp (milliseconds since 1 Jan 1970, 00:00:00 in Reykjavík)</li> <li>timestamp (milliseconds since 1 Jan 1970, 00:00:00 in Reykjavík)</li>
<li>transmission duration (milliseconds)</li> <li>transmission duration (milliseconds)</li>
</ul> </ul>
<p> <p>
The repeater does nothing but broadcast everything it gets The repeater does nothing but broadcast everything it gets
to every connected Vail client, to every connected Vail client,
@ -470,14 +511,12 @@
This is the <i>round-trip time</i>: This is the <i>round-trip time</i>:
the time it takes for a packet to get from your computer to the repeater and back. the time it takes for a packet to get from your computer to the repeater and back.
</p> </p>
<p> <p>
When the client gets a packet it didn't send, When the client gets a packet it didn't send,
it adds the <i>receive delay</i> to the timestamp, it adds the <i>receive delay</i> to the timestamp,
and schedules to play the tones and silences in the packet and schedules to play the tones and silences in the packet
at that time. at that time.
</p> </p>
<p> <p>
By adding the maximum round-trip time to the <i>longest recent transmission</i> By adding the maximum round-trip time to the <i>longest recent transmission</i>
(the length of a dah, hopefully), (the length of a dah, hopefully),
@ -489,56 +528,8 @@
</p> </p>
</div> </div>
</div> </div>
<div class="mdl-card mdl-shadow--4dp">
<div class="mdl-card__title">
<h2 class="mdl-card__title-text">Why do I hear a low tone?</h2>
</div>
<div class="mdl-card__supporting-text">
<p>
This is the "drop tone", and will be accompanied by an error.
</p>
<p>
This means the packet arrived so late, it can't be played in time.
In technical terms: the timestamp of the packet plus the receive delay
is less than the current time.
It can't be scheduled to play, because we can't go back in time.
</p>
<p>
This could be happening for three reasons:
</p>
<ol>
<li>You (the person hearing the drop tone) need a larger receive delay</li>
<li>The receiving computer's clock is in the future (running fast)</li>
<li>The sending computer's clock is in the past (running slow)</li>
</ol>
<p>
Make sure your clock is synced with an Internet time server.
Accurate time is very important to how Vail works.
</p>
</div>
</div> </div>
<div class="mdl-card mdl-shadow--4dp">
<div class="mdl-card__title">
<h2 class="mdl-card__title-text">How can I help?</h2>
</div>
<div class="mdl-card__supporting-text">
<ul>
<li>Improve the <a href="https://github.com/nealey/vail/">source code</a></li>
<li>Email me and let me know you're using it</li>
</ul>
<p>
<a href="mailto:neale@woozle.org">Neale Pickett</a> kd7oqi
</p>
</div>
</div>
</div> </div>
</main> </main>
</div> </div>

View File

@ -1,13 +1,25 @@
// jshint asi:true cacheName = "v1"
self.addEventListener("install", install) self.addEventListener("install", e => install(e))
function install(event) { function install(event) {
console.log(event) event.waitUntil(
event.waitUntil(Promise.resolve(true)) caches.open(cacheName)
.then(cache => {
return cache.addAll(
[
"/",
]
)
})
)
} }
self.addEventListener("fetch", fetcher) self.addEventListener("fetch", e => cacheFetch(e))
function fetcher(event) { function cacheFetch(event) {
event.respondWith(fetch(event.request)) event.respondWith(
fetch(event.request)
.catch(() => {
return caches.match(event.request)
})
)
} }

View File

@ -94,18 +94,14 @@ img {
max-height: inherit; max-height: inherit;
} }
#repeater {
font-style: italic;
margin-right: 0.3em;
}
#recv { #recv {
width: 3em; width: 2em;
height: 2em; height: 1em;
line-height: 2em; line-height: 1em;
position: absolute; position: absolute;
top: 0.5em;
right: 1em; right: 1em;
border-radius: 30%; border-radius: 0.3em;
text-align: center; text-align: center;
vertical-align: middle; vertical-align: middle;
} }

View File

@ -2,6 +2,8 @@ import * as Morse from "./morse.mjs"
import {getFortune} from "./fortunes.mjs" import {getFortune} from "./fortunes.mjs"
import { toast } from "./morse.mjs" import { toast } from "./morse.mjs"
const DefaultRepeater = "General Chaos"
class Vail { class Vail {
constructor() { constructor() {
this.sent = [] this.sent = []
@ -12,8 +14,6 @@ class Vail {
this.beginTxTime = null // Time when we began transmitting this.beginTxTime = null // Time when we began transmitting
this.debug = localStorage.debug this.debug = localStorage.debug
this.openSocket()
// Listen to HTML buttons // Listen to HTML buttons
for (let e of document.querySelectorAll("button.key")) { for (let e of document.querySelectorAll("button.key")) {
e.addEventListener("contextmenu", e => { e.preventDefault(); return false }) e.addEventListener("contextmenu", e => { e.preventDefault(); return false })
@ -33,8 +33,7 @@ class Vail {
// Make helpers // Make helpers
this.buzzer = new Morse.Buzzer() this.buzzer = new Morse.Buzzer()
this.iambic = new Morse.Iambic(() => this.beginTx(), () => this.endTx()) this.iambic = new Morse.Iambic(() => this.beginTx(), () => this.endTx())
this.fortuneBuzzer = new Morse.Buzzer({highFreq: 440}) this.fortuneIambic = new Morse.Iambic(() => this.buzzer.Buzz(), () => this.buzzer.Silence())
this.fortuneIambic = new Morse.Iambic(() => this.fortuneBuzzer.Buzz(true), () => this.fortuneBuzzer.Silence(true))
// Listen for slider values // Listen for slider values
this.inputInit("#iambic-duration", e => { this.inputInit("#iambic-duration", e => {
@ -48,9 +47,21 @@ class Vail {
this.fortuneIambic.SetPauseMultiplier(e.target.value) this.fortuneIambic.SetPauseMultiplier(e.target.value)
}) })
// Show what repeater we're on // Redirect old URLs
let repeater = (new URL(location)).searchParams.get("repeater") || "General Chaos" if (window.location.search) {
document.querySelector("#repeater").textContent = repeater let me = new URL(location)
let repeater = me.searchParams.get("repeater")
me.search = ""
me.hash = repeater
window.location = me
}
// Fill in the name of our repeater
let repeater = decodeURI(window.location.hash.split("#")[1] || "General Chaos")
let repeaterElement = document.querySelector("#repeater")
repeaterElement.addEventListener("change", e => this.setRepeater(e.target.value.trim()))
repeaterElement.value = unescape(repeater)
repeaterElement.dispatchEvent(new Event("change"))
// Request MIDI access // Request MIDI access
if (navigator.requestMIDIAccess) { if (navigator.requestMIDIAccess) {
@ -62,13 +73,24 @@ class Vail {
window.addEventListener("gamepadconnected", e => this.gamepadConnected(e)) window.addEventListener("gamepadconnected", e => this.gamepadConnected(e))
} }
openSocket() { setRepeater(name) {
// Set up WebSocket this.repeaterName = name
// Set window URL
let hash = name
if (name == DefaultRepeater) {
hash = ""
}
if (hash != window.location.hash) {
window.location.hash = hash
}
let wsUrl = new URL("chat", window.location) let wsUrl = new URL("chat", window.location)
wsUrl.protocol = wsUrl.protocol.replace("http", "ws") wsUrl.protocol = wsUrl.protocol.replace("http", "ws")
wsUrl.searchParams.set("repeater", name)
this.socket = new WebSocket(wsUrl) this.socket = new WebSocket(wsUrl)
this.socket.addEventListener("message", e => this.wsMessage(e)) this.socket.addEventListener("message", e => this.wsMessage(e))
this.socket.addEventListener("close", e => this.openSocket()) this.socket.addEventListener("close", () => this.setRepeater(name))
} }
inputInit(selector, func) { inputInit(selector, func) {
@ -273,6 +295,10 @@ class Vail {
} }
keyboard(event) { keyboard(event) {
if (["INPUT"].includes(document.activeElement.tagName)) {
// Ignore everything if the user is entering text somewhere
return
}
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
@ -282,7 +308,6 @@ class Vail {
if ((event.code == "KeyX") || if ((event.code == "KeyX") ||
(event.code == "Period") || (event.code == "Period") ||
(event.code == "ControlLeft") ||
(event.code == "BracketLeft") || (event.code == "BracketLeft") ||
(event.key == "[")) { (event.key == "[")) {
event.preventDefault() event.preventDefault()
@ -290,7 +315,6 @@ class Vail {
} }
if ((event.code == "KeyZ") || if ((event.code == "KeyZ") ||
(event.code == "Slash") || (event.code == "Slash") ||
(event.code == "ControlRight") ||
(event.code == "BracketRight") || (event.code == "BracketRight") ||
(event.key == "]")) { (event.key == "]")) {
event.preventDefault() event.preventDefault()