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

@ -44,7 +44,7 @@
<a class="mdl-navigation__link" href="?repeater=21-99+WPM">21-99 WPM</a>
</nav>
<hr>
<nav class="mdl-navigation">
<nav class="mdl-navigation">
<a class="mdl-navigation__link" href="https://morse.withgoogle.com/learn/">Learn Morse Code</a>
<a class="mdl-navigation__link" href="https://github.com/nealey/vail-adapter">Use a physical key</a>
</nav>
@ -58,15 +58,23 @@
<main class="mdl-layout__content">
<div class="flex">
<div class="mdl-card mdl-shadow--4dp input-methods">
<div id="recv">
<!-- This div appears as a little light that turns on when someone's sending -->
<i class="material-icons" id="muted">volume_off</i>
</div>
<div class="mdl-card__title">
<h2 class="mdl-card__title-text">
<span id="repeater"></span>
Repeater
<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 id="recv">
<!-- This div appears as a little light that turns on when someone's sending -->
<i class="material-icons" id="muted">volume_off</i>
</div>
</div>
<div class="mdl-tabs mdl-js-tabs mdl-js-ripple-effect">
<div class="mdl-tabs__tab-bar">
@ -91,7 +99,6 @@
<kbd>c</kbd>
<kbd>,</kbd>
<kbd>Enter</kbd>
<kbd>⇧ Shift</kbd>
</td>
</tr>
<tr>
@ -354,7 +361,7 @@
value="100">
</p>
<p>
Recieve delay:
Receive delay:
<output id="rx-delay-value"></output>ms
<input
id="rx-delay"
@ -406,139 +413,123 @@
<div class="mdl-card mdl-shadow--4dp">
<div class="mdl-card__title">
<h2 class="mdl-card__title-text">Vail</h2>
<h2 class="mdl-card__title-text">Documentation</h2>
</div>
<div class="mdl-card__supporting-text">
<p>
This is a CW repeater,
named after Alfred Vail,
who may or may not have invented what's called "Morse code",
but clearly had some role in it.
</p>
<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>
This is a CW repeater,
named after Alfred Vail,
who may or may not have invented what's called "Morse code",
but clearly had some role in it.
</p>
<p>
Just like a radio repeater,
anybody can connect and start transmitting stuff,
and this will broadcast it to everyone connected.
</p>
<p>
Just like a radio repeater,
anybody can connect and start transmitting stuff,
and this will broadcast it to everyone connected.
</p>
</div>
<h3 class="mdl-card__title-text">Why Does This Exist?</h3>
<div class="mdl-tabs__panel mdl-card__supporting-text long" id="doc-faq">
<h3 class="mdl-card__title-text">Why Does This Exist?</h3>
<p>
I needed a place to practice CW with actual human beings,
and I wanted it to be as close as possible to what I'd experience on a radio.
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.
</p>
<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>
<p>
I need 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.
Also, I don't want to make people buy a bunch of radio hardware.
Nothing else like this exists on the Internet, as far as I can tell.
</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 class="mdl-tabs__panel mdl-card__supporting-text long" id="doc-geek">
<p>
The Internet isn't exactly like radio waves:
it still goes at near the speed of light,
but there are multiple hops between endpoints,
which buffer up transmissions, and multiplex them onto a single uplink connection.
These repeaters (routers)
are also allowed to just drop things if they need to.
It's the responsibility of the communicating parties
to work out whether something needs to be retransmitted.
Because of this,
there's no telling how long it will take for a transmission to get to a destination.
</p>
<p>
Each Vail transmission (packet) consists of:
</p>
<ul>
<li>timestamp (milliseconds since 1 Jan 1970, 00:00:00 in Reykjavík)</li>
<li>transmission duration (milliseconds)</li>
</ul>
<p>
The repeater does nothing but broadcast everything it gets
to every connected Vail client,
including the one that sent the packet.
When your client gets back the exact same thing it sent,
it compares the current time to the time in the packet.
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.
</p>
<p>
When the client gets a packet it didn't send,
it adds the <i>receive delay</i> to the timestamp,
and schedules to play the tones and silences in the packet
at that time.
</p>
<p>
By adding the maximum round-trip time to the <i>longest recent transmission</i>
(the length of a dah, hopefully),
your client can make a guess about how much time needs to be added to a received timestamp,
in order to have it play back in the future at the time it comes in.
This is just a guess.
If you're communicating with somebody with a higher round-trip time than you have,
you'll need to raise your receive delay to account for it.
</p>
</div>
</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">
<p>
The Internet isn't exactly like radio waves:
it still goes at near the speed of light,
but there are multiple hops between endpoints,
which buffer up transmissions, and multiplex them onto a single uplink connection.
These repeaters (routers)
are also allowed to just drop things if they need to.
It's the responsibility of the communicating parties
to work out whether something needs to be retransmitted.
Because of this,
there's no telling how long it will take for a transmission to get to a destination.
</p>
<p>
Each Vail transmission (packet) consists of:
</p>
<ul>
<li>timestamp (milliseconds since 1 Jan 1970, 00:00:00 in Reykjavík)</li>
<li>transmission duration (milliseconds)</li>
</ul>
<p>
The repeater does nothing but broadcast everything it gets
to every connected Vail client,
including the one that sent the packet.
When your client gets back the exact same thing it sent,
it compares the current time to the time in the packet.
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.
</p>
<p>
When the client gets a packet it didn't send,
it adds the <i>receive delay</i> to the timestamp,
and schedules to play the tones and silences in the packet
at that time.
</p>
<p>
By adding the maximum round-trip time to the <i>longest recent transmission</i>
(the length of a dah, hopefully),
your client can make a guess about how much time needs to be added to a received timestamp,
in order to have it play back in the future at the time it comes in.
This is just a guess.
If you're communicating with somebody with a higher round-trip time than you have,
you'll need to raise your receive delay to account for it.
</p>
</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 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>
</main>
</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) {
console.log(event)
event.waitUntil(Promise.resolve(true))
event.waitUntil(
caches.open(cacheName)
.then(cache => {
return cache.addAll(
[
"/",
]
)
})
)
}
self.addEventListener("fetch", fetcher)
function fetcher(event) {
event.respondWith(fetch(event.request))
self.addEventListener("fetch", e => cacheFetch(e))
function cacheFetch(event) {
event.respondWith(
fetch(event.request)
.catch(() => {
return caches.match(event.request)
})
)
}

View File

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

View File

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