mirror of https://github.com/nealey/vail.git
Charts, pare down UI
This commit is contained in:
parent
2290c2ff02
commit
01ed64ad2d
|
@ -14,7 +14,7 @@ class HistoryChart {
|
|||
* @param {string} strokeStyle strokeStyle to draw in
|
||||
* @param {Duration} duration Time to display history for
|
||||
*/
|
||||
constructor(canvas, strokeStyle, duration) {
|
||||
constructor(canvas, strokeStyle="black", duration=20*Second) {
|
||||
this.canvas = canvas
|
||||
this.ctx = canvas.getContext("2d")
|
||||
this.duration = duration
|
||||
|
@ -29,8 +29,10 @@ class HistoryChart {
|
|||
this.ctx.translate(0, -canvas.height)
|
||||
|
||||
this.ctx.strokeStyle = strokeStyle
|
||||
this.ctx.fillStyle = strokeStyle
|
||||
this.ctx.lineWdith = 2
|
||||
|
||||
this.running=true
|
||||
this.draw()
|
||||
}
|
||||
|
||||
|
@ -42,8 +44,8 @@ class HistoryChart {
|
|||
* This also cleans up the event list,
|
||||
* purging anything that is too old to be displayed.
|
||||
*
|
||||
* @param when Time the event happened
|
||||
* @param value Value for the event
|
||||
* @param {Number} when Time the event happened
|
||||
* @param {Number} value Value for the event
|
||||
*/
|
||||
Add(when, value) {
|
||||
let now = Date.now()
|
||||
|
@ -54,8 +56,10 @@ class HistoryChart {
|
|||
while ((this.data.length > 1) && (this.data[1][0] < earliest)) {
|
||||
this.data.shift()
|
||||
}
|
||||
}
|
||||
|
||||
console.log(this.data)
|
||||
Stop() {
|
||||
this.running = false
|
||||
}
|
||||
|
||||
draw() {
|
||||
|
@ -64,43 +68,29 @@ class HistoryChart {
|
|||
let xScale = this.canvas.width / this.duration
|
||||
let yScale = this.canvas.height * 0.95
|
||||
let y = 0
|
||||
let x = 0
|
||||
|
||||
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height)
|
||||
|
||||
this.ctx.moveTo(0, 0)
|
||||
this.ctx.beginPath()
|
||||
for (let point of this.data) {
|
||||
let x = (point[0] - earliest) * xScale
|
||||
this.ctx.lineTo(x, y)
|
||||
y = point[1] * yScale
|
||||
this.ctx.lineTo(x, y)
|
||||
}
|
||||
this.ctx.lineTo(this.canvas.width, y)
|
||||
this.ctx.stroke()
|
||||
let x2 = (point[0] - earliest) * xScale
|
||||
let y2 = point[1] * yScale
|
||||
|
||||
requestAnimationFrame(() => this.draw())
|
||||
if (y > 0) {
|
||||
this.ctx.fillRect(x, 0, x2-x, y)
|
||||
}
|
||||
|
||||
x=x2
|
||||
y=y2
|
||||
}
|
||||
if (y > 0) {
|
||||
this.ctx.fillRect(x, 0, this.canvas.width, y)
|
||||
}
|
||||
|
||||
if (this.running) {
|
||||
requestAnimationFrame(() => this.draw())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export {HistoryChart}
|
||||
|
||||
|
||||
// XXX: remove after testing
|
||||
let chart
|
||||
|
||||
function init() {
|
||||
let canvas = document.querySelector("#chart")
|
||||
chart = new HistoryChart(canvas, "red", 20 * Second)
|
||||
setInterval(update, 500 * Millisecond)
|
||||
}
|
||||
|
||||
function update() {
|
||||
let now = Date.now()
|
||||
chart.Add(now, Math.sin(now/Second)/2 + 0.5)
|
||||
}
|
||||
|
||||
if (document.readyState === "loading") {
|
||||
document.addEventListener("DOMContentLoaded", init)
|
||||
} else {
|
||||
init()
|
||||
}
|
|
@ -27,9 +27,6 @@
|
|||
<div class="mdl-layout-spacer"></div>
|
||||
<!-- Navigation -->
|
||||
<nav class="mdl-navigation">
|
||||
<a class="mdl-navigation__link" href="https://discord.gg/GBzj8cBat7">Discord</a>
|
||||
<a class="mdl-navigation__link" href="https://morse.withgoogle.com/learn/">Learn Morse</a>
|
||||
<a class="mdl-navigation__link" href="https://github.com/nealey/vail-adapter">USB Adapter</a>
|
||||
<a class="mdl-navigation__link" href="https://github.com/nealey/vail">Source Code</a>
|
||||
</nav>
|
||||
</div>
|
||||
|
@ -54,10 +51,10 @@
|
|||
<a class="mdl-navigation__link" href="#Fortunes: Pauses ×10">Fortunes (crazy slow)</a>
|
||||
</nav>
|
||||
<hr>
|
||||
<span class="mdl-layout-title">Resources</span>
|
||||
<nav class="mdl-navigation">
|
||||
<a class="mdl-navigation__link" href="https://github.com/nealey/vail/wiki">Wiki</a>
|
||||
<a class="mdl-navigation__link" href="https://discord.gg/GBzj8cBat7">Discord</a>
|
||||
<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>
|
||||
</div>
|
||||
|
||||
|
@ -96,6 +93,13 @@
|
|||
</h2>
|
||||
</div>
|
||||
<output id="note"></output>
|
||||
|
||||
<div id="charts">
|
||||
<canvas class="chart" id="txChart"></canvas>
|
||||
<canvas class="chart" id="ditChart"></canvas>
|
||||
<canvas class="chart" id="dahChart"></canvas>
|
||||
</div>
|
||||
|
||||
<div class="mdl-tabs mdl-js-tabs mdl-js-ripple-effect">
|
||||
<div class="mdl-tabs__tab-bar">
|
||||
<a href="#straight" class="mdl-tabs__tab is-active" data-singlekey="straight">Straight Key</a>
|
||||
|
@ -217,6 +221,7 @@
|
|||
</div>
|
||||
<div class="mdl-card__supporting-text">
|
||||
<textarea class="notes" placeholder="Enter your own notes here"></textarea>
|
||||
<a href="https://github.com/nealey/vail/wiki">Vail Wiki</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -265,6 +270,12 @@
|
|||
<span class="mdl-switch__label">Telegraph sounds</span>
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label class="mdl-switch mdl-js-switch mdl-js-ripple-effect" for="timing-chart">
|
||||
<input type="checkbox" id="timing-chart" class="mdl-switch__input">
|
||||
<span class="mdl-switch__label">Timing chart</span>
|
||||
</label>
|
||||
</p>
|
||||
<hr>
|
||||
<table>
|
||||
<tbody>
|
||||
|
@ -305,269 +316,6 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mdl-card mdl-shadow--4dp">
|
||||
<div class="mdl-card__title">
|
||||
<h2 class="mdl-card__title-text">Alphabet</h2>
|
||||
</div>
|
||||
<div class="mdl-tabs mdl-js-tabs mdl-js-ripple-effect">
|
||||
<div class="mdl-tabs__tab-bar">
|
||||
<a href="#morse-tree" class="mdl-tabs__tab is-active">Dichotomous Key</a>
|
||||
<a href="#morse-list" class="mdl-tabs__tab">List</a>
|
||||
</div>
|
||||
<div class="mdl-tabs__panel mdl-card__supporting-text long is-active" id="morse-tree">
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td rowspan="8">E .</td>
|
||||
<td rowspan="4">I ..</td>
|
||||
<td rowspan="2">S ...</td>
|
||||
<td rowspan="1">H ....</td>
|
||||
<td class="dah">4 ....-</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="1">V ...-</td>
|
||||
<td class="dah">3 ...--</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="2">U ..-</td>
|
||||
<td rowspan="1">F ..-.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="1"></td>
|
||||
<td class="dah">2 ..---</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="4">A .-</td>
|
||||
<td rowspan="2">R .-.</td>
|
||||
<td rowspan="1">L .-..</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="1"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="2">W .--</td>
|
||||
<td rowspan="1">P .--.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="1">J .---</td>
|
||||
<td class="dah">1 .----</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td rowspan="8">T -</td>
|
||||
<td rowspan="4">N -.</td>
|
||||
<td rowspan="2">D -..</td>
|
||||
<td rowspan="1">B -...</td>
|
||||
<td>6 -....</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="1">X -..-</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="2">K -.-</td>
|
||||
<td rowspan="1">C -.-.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="1">Y -.--</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="4">M --</td>
|
||||
<td rowspan="2">G --.</td>
|
||||
<td rowspan="1">Z --..</td>
|
||||
<td>7 --...</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="1">Q --.-</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="2">O ---</td>
|
||||
<td rowspan="1"></td>
|
||||
<td>8 ---..</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="1"></td>
|
||||
<td>9 ----.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="mdl-tabs__panel mdl-card__supporting-text long" id="morse-list">
|
||||
<span>A .-</span>
|
||||
<span>B -...</span>
|
||||
<span>C -.-.</span>
|
||||
<span>D -..</span>
|
||||
<span>E .</span>
|
||||
<span>F ..-.</span>
|
||||
<span>G --.</span>
|
||||
<span>H ....</span>
|
||||
<span>I ..</span>
|
||||
<span>J .---</span>
|
||||
<span>K -.-</span>
|
||||
<span>L .-..</span>
|
||||
<span>M --</span>
|
||||
<span>N -.</span>
|
||||
<span>O ---</span>
|
||||
<span>P .--.</span>
|
||||
<span>Q --.-</span>
|
||||
<span>R .-.</span>
|
||||
<span>S ...</span>
|
||||
<span>T -</span>
|
||||
<span>U ..-</span>
|
||||
<span>V ...-</span>
|
||||
<span>W .--</span>
|
||||
<span>X -..-</span>
|
||||
<span>Y -.--</span>
|
||||
<span>Z --..</span>
|
||||
<br>
|
||||
<span>0 -----</span>
|
||||
<span>1 .----</span>
|
||||
<span>2 ..---</span>
|
||||
<span>3 ...--</span>
|
||||
<span>4 ....-</span>
|
||||
<span>5 .....</span>
|
||||
<span>6 -....</span>
|
||||
<span>7 --...</span>
|
||||
<span>8 ---..</span>
|
||||
<span>9 ----.</span>
|
||||
<br>
|
||||
<span>End of transmission .-.-.</span>
|
||||
<span>Over -.-</span>
|
||||
<span>Correction ........</span>
|
||||
<span>? / Say Again ..--..</span>
|
||||
<span>Speak Slower --.- .-. ...</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mdl-card mdl-shadow--4dp">
|
||||
<div class="mdl-card__title">
|
||||
<h2 class="mdl-card__title-text">Documentation</h2>
|
||||
</div>
|
||||
<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>
|
||||
</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>
|
||||
<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">What does "local" mean next to the repeater name?</h3>
|
||||
<p>
|
||||
It means this repeater doesn't repeat anything:
|
||||
nothing you key in will be sent anywhere.
|
||||
These are to help people practice and learn,
|
||||
without worrying about anyone else hearing them fumble around.
|
||||
</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>
|
||||
Vail attempts to correct for clock differences,
|
||||
but making sure your computer has correct time,
|
||||
down to the millisecond,
|
||||
can help with reliability.
|
||||
</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>
|
||||
</main>
|
||||
</div>
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import * as Chart from "./chart.mjs"
|
||||
|
||||
/** Silent period between words */
|
||||
const PAUSE_WORD = -7
|
||||
/** Silent period between letters */
|
||||
|
@ -145,10 +147,16 @@ class Keyer {
|
|||
next *= this.pauseMultiplier
|
||||
} else {
|
||||
this.endTxFunc()
|
||||
if (this.txChart) {
|
||||
this.txChart.Add(Date.now(), 0)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.last = next
|
||||
this.beginTxFunc()
|
||||
if (this.txChart) {
|
||||
this.txChart.Add(Date.now(), 1)
|
||||
}
|
||||
}
|
||||
this.pulseTimer = setTimeout(() => this.pulse(), next * this.intervalDuration)
|
||||
}
|
||||
|
@ -180,6 +188,25 @@ class Keyer {
|
|||
return this.last
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up various charts by providing canvases for them.
|
||||
*
|
||||
* @param {Element} txCanvas
|
||||
* @param {Element} straightCanvas
|
||||
* @param {Element} ditCanvas
|
||||
* @param {Element} dahCanvas
|
||||
*/
|
||||
SetCanvas(txCanvas=null, straightCanvas=null, ditCanvas=null, dahCanvas=null) {
|
||||
for (let c of [this.txChart, this.straightChart, this.ditChart, this.dahChart]) {
|
||||
if (c) c.Stop()
|
||||
}
|
||||
|
||||
this.txChart = txCanvas?new Chart.HistoryChart(txCanvas, "red"):null
|
||||
this.straightChart =straightCanvas?new Chart.HistoryChart(straightCanvas, "teal"):null
|
||||
this.ditChart =ditCanvas?new Chart.HistoryChart(ditCanvas, "olive"):null
|
||||
this.dahChart =dahCanvas?new Chart.HistoryChart(dahCanvas, "purple"):null
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if we are currently playing out something
|
||||
*/
|
||||
|
@ -324,6 +351,9 @@ class Keyer {
|
|||
} else {
|
||||
this.endTxFunc()
|
||||
}
|
||||
if (this.straightChart) {
|
||||
this.straightChart.Add(Date.now(), down?1:0)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -340,6 +370,9 @@ class Keyer {
|
|||
this.Enqueue(DIT)
|
||||
}
|
||||
}
|
||||
if (this.ditChart) {
|
||||
this.ditChart.Add(Date.now(), down?1:0)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -356,6 +389,9 @@ class Keyer {
|
|||
this.Enqueue(DAH)
|
||||
}
|
||||
}
|
||||
if (this.dahChart) {
|
||||
this.dahChart.Add(Date.now(), down?1:0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -153,3 +153,11 @@ img {
|
|||
padding: 0.4em;
|
||||
min-width: 4em;
|
||||
}
|
||||
|
||||
#charts {
|
||||
line-height: 0;
|
||||
}
|
||||
#charts canvas {
|
||||
height: 0.5em;
|
||||
width: 100%;
|
||||
}
|
|
@ -87,6 +87,10 @@ class VailClient {
|
|||
this.inputInit("#telegraph-buzzer", e => {
|
||||
this.setTelegraphBuzzer(e.target.checked)
|
||||
})
|
||||
this.inputInit("#timing-chart", e => {
|
||||
console.log("moo")
|
||||
this.setTimingCharts(e.target.checked)
|
||||
})
|
||||
|
||||
// Fill in the name of our repeater
|
||||
document.querySelector("#repeater").addEventListener("change", e => this.setRepeater(e.target.value.trim()))
|
||||
|
@ -101,6 +105,30 @@ class VailClient {
|
|||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle timing charts.
|
||||
*
|
||||
* @param enable True to enable charts
|
||||
*/
|
||||
setTimingCharts(enable) {
|
||||
// XXX: UI code shouldn't be in the Keyer class.
|
||||
// Actually, the charts calls should be in vail
|
||||
let chartsContainer = document.querySelector("#charts")
|
||||
if (enable) {
|
||||
chartsContainer.classList.remove("hidden")
|
||||
this.keyer.SetCanvas(
|
||||
document.querySelector("#txChart"),
|
||||
document.querySelector("#straightChart"),
|
||||
document.querySelector("#ditChart"),
|
||||
document.querySelector("#dahChart"),
|
||||
)
|
||||
} else {
|
||||
chartsContainer.classList.add("hidden")
|
||||
this.keyer.SetCanvas()
|
||||
}
|
||||
console.log("timing chart", enable)
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle the clicktastic buzzer, instead of the beeptastic one.
|
||||
*
|
||||
|
@ -210,6 +238,7 @@ class VailClient {
|
|||
inputInit(selector, callback) {
|
||||
let element = document.querySelector(selector)
|
||||
if (!element) {
|
||||
console.warn("Unable to find an input to init", selector)
|
||||
return
|
||||
}
|
||||
let storedValue = localStorage[element.id]
|
||||
|
@ -234,6 +263,7 @@ class VailClient {
|
|||
outputWpmElement.value = (1200 / value).toFixed(1)
|
||||
}
|
||||
if (callback) {
|
||||
console.log("callback", selector)
|
||||
callback(e)
|
||||
}
|
||||
})
|
||||
|
|
Loading…
Reference in New Issue