mirror of https://github.com/nealey/vail.git
I think it works? Maybe?
This commit is contained in:
parent
b202ab6968
commit
308131f901
|
@ -24,22 +24,21 @@
|
||||||
<div class="mdl-layout-spacer"></div>
|
<div class="mdl-layout-spacer"></div>
|
||||||
<!-- Navigation -->
|
<!-- Navigation -->
|
||||||
<nav class="mdl-navigation">
|
<nav class="mdl-navigation">
|
||||||
<a class="mdl-navigation__link" href="">Link</a>
|
<a class="mdl-navigation__link" href="https://github.com/nealey/vail">Source Code</a>
|
||||||
<a class="mdl-navigation__link" href="">Link</a>
|
<a class="mdl-navigation__link" href="https://github.com/nealey/vail/issues/new">Bug Report</a>
|
||||||
<a class="mdl-navigation__link" href="">Link</a>
|
|
||||||
<a class="mdl-navigation__link" href="">Link</a>
|
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div class="mdl-layout__drawer">
|
<div class="mdl-layout__drawer">
|
||||||
<span class="mdl-layout-title">Title</span>
|
<span class="mdl-layout-title">Repeaters</span>
|
||||||
<nav class="mdl-navigation">
|
<nav class="mdl-navigation">
|
||||||
<a class="mdl-navigation__link" href="">Link</a>
|
<a class="mdl-navigation__link" href="?repeater=">1-15 WPM</a>
|
||||||
<a class="mdl-navigation__link" href="">Link</a>
|
<a class="mdl-navigation__link" href="?repeater=int">16-20 WPM</a>
|
||||||
<a class="mdl-navigation__link" href="">Link</a>
|
<a class="mdl-navigation__link" href="?repeater=adv">21-99 WPM</a>
|
||||||
<a class="mdl-navigation__link" href="">Link</a>
|
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<main class="mdl-layout__content">
|
<main class="mdl-layout__content">
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<div class="mdl-card mdl-shadow--4dp">
|
<div class="mdl-card mdl-shadow--4dp">
|
||||||
|
@ -54,12 +53,23 @@
|
||||||
<a href="#iambic" class="mdl-tabs__tab">Iambic</a>
|
<a href="#iambic" class="mdl-tabs__tab">Iambic</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="mdl-tabs__panel is-active" id="straight">
|
<div class="mdl-tabs__panel is-active" id="straight">
|
||||||
<button id="key" class="key mdl-button mdl-js-button mdl-button--raised mdl-button--colored">
|
<table class="center wide">
|
||||||
Key
|
<tr>
|
||||||
</button>
|
<td>
|
||||||
|
<button id="key" class="key mdl-button mdl-js-button mdl-button--raised mdl-button--colored">
|
||||||
|
Key
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<code>⇧</code>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<div class="mdl-tabs__panel" id="iambic">
|
<div class="mdl-tabs__panel" id="iambic">
|
||||||
<table style="width: 100%; text-align: center;">
|
<table class="center wide">
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<button id="dit" class="key mdl-button mdl-js-button mdl-button--raised mdl-button--colored">
|
<button id="dit" class="key mdl-button mdl-js-button mdl-button--raised mdl-button--colored">
|
||||||
|
@ -74,28 +84,80 @@
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<code>.</code> or <code>⇧</code>
|
<code>.</code> or <code>z</code>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<code>/</code> or <code>z</code>
|
<code>/</code> or <code>x</code>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
<p>
|
|
||||||
Dit length:
|
|
||||||
<output id="iambic-duration-value"></output>ms
|
|
||||||
<input
|
|
||||||
id="iambic-duration"
|
|
||||||
class="mdl-slider mdl-js-slider"
|
|
||||||
type="range"
|
|
||||||
min="40"
|
|
||||||
max="255"
|
|
||||||
value="80">
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="mdl-card mdl-shadow--4dp">
|
||||||
|
<div class="mdl-card__title">
|
||||||
|
<h2 class="mdl-card__title-text">
|
||||||
|
Knobs
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
<div class="mdl-card__supporting-text">
|
||||||
|
<table>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
Average round-trip time:
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<output id="lag-value">0</output>ms
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
Longest recent transmission:
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<output id="longest-rx-value">0</output>ms
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
Suggested receive delay:
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<output id="suggested-delay-value">0</output>ms
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<hr>
|
||||||
|
<p>
|
||||||
|
Recieve delay:
|
||||||
|
<output id="rx-delay-value"></output>ms
|
||||||
|
<input
|
||||||
|
id="rx-delay"
|
||||||
|
class="mdl-slider mdl-js-slider"
|
||||||
|
type="range"
|
||||||
|
min="0"
|
||||||
|
max="5000"
|
||||||
|
value="300">
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Dit length (iambic):
|
||||||
|
<output id="iambic-duration-value"></output>ms
|
||||||
|
<input
|
||||||
|
id="iambic-duration"
|
||||||
|
class="mdl-slider mdl-js-slider"
|
||||||
|
type="range"
|
||||||
|
min="40"
|
||||||
|
max="255"
|
||||||
|
value="80">
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<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">
|
<h2 class="mdl-card__title-text">
|
||||||
|
@ -125,32 +187,39 @@
|
||||||
Just like a radio repeater,
|
Just like a radio repeater,
|
||||||
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.
|
||||||
If there's enough interest,
|
|
||||||
I'll add something like channels.
|
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>
|
<h3 class="mdl-card__title-text">Why Does This Exist?</h3>
|
||||||
If you need this to work on a cell phone,
|
|
||||||
let me know and I'll come up with something for you.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mdl-card mdl-shadow--4dp">
|
|
||||||
<div class="mdl-card__title">
|
|
||||||
<h2 class="mdl-card__title-text">
|
|
||||||
Why Does This Exist?
|
|
||||||
</h2>
|
|
||||||
</div>
|
|
||||||
<div class="mdl-card__supporting-text">
|
|
||||||
<p>
|
<p>
|
||||||
I need a place to practice CW with actual human beings,
|
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.
|
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.
|
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.
|
Nothing else like this exists on the Internet, as far as I can tell.
|
||||||
</p>
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<h3>Who made it?</h3>
|
<div class="mdl-card mdl-shadow--4dp">
|
||||||
|
<div class="mdl-card__title">
|
||||||
|
<h2 class="mdl-card__title-text">Future plans</h2>
|
||||||
|
</div>
|
||||||
|
<div class="mdl-card__supporting-text">
|
||||||
|
<ul>
|
||||||
|
<li>Move to a more permanent URL</li>
|
||||||
|
<li>Make this page less ugly</li>
|
||||||
|
<li>Arduino program to let you hook up an iambic paddle over USB</li>
|
||||||
|
<li>Document the protocol</li>
|
||||||
|
<li>Support multiple channels/frequencies</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
|
||||||
|
<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>
|
||||||
|
</ul>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
<a href="mailto:neale@woozle.org">Neale Pickett</a> kd7oqi
|
<a href="mailto:neale@woozle.org">Neale Pickett</a> kd7oqi
|
||||||
|
@ -158,31 +227,6 @@
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mdl-card mdl-shadow--4dp">
|
|
||||||
<div class="mdl-card__title">
|
|
||||||
<h3 class="mdl-card__title-text">Future plans</h3>
|
|
||||||
</div>
|
|
||||||
<div class="mdl-card__supporting_text">
|
|
||||||
<ul>
|
|
||||||
<li>Move to a more permanent URL</li>
|
|
||||||
<li>Make this page less ugly</li>
|
|
||||||
<li>Arduino program to let you hook up an iambic paddle over USB</li>
|
|
||||||
<li>Document the protocol</li>
|
|
||||||
<li>Support multiple channels/frequencies</li>
|
|
||||||
<li>Sensible way to make this work on a cell phone</li>
|
|
||||||
<li>Make this page less ugly (I really hate it right now)</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
|
|
||||||
<h3>How can I help?</h3>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>Improve the <a href="https://github.com/nealey/vail/">source code</a></li>
|
|
||||||
<li><a href="mailto:neale@woozle.org">Email me</a> and let me know you're using it</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -14,6 +14,14 @@
|
||||||
height: 6em;
|
height: 6em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.center {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wide {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
code {
|
code {
|
||||||
background-color: #333;
|
background-color: #333;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
|
|
110
static/vail.js
110
static/vail.js
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
const lowFreq = 660
|
const lowFreq = 660
|
||||||
const highFreq = lowFreq * 6 / 5 // Perfect minor third
|
const highFreq = lowFreq * 6 / 5 // Perfect minor third
|
||||||
|
const errorFreq = 30
|
||||||
|
|
||||||
const DIT = 1
|
const DIT = 1
|
||||||
const DAH = 3
|
const DAH = 3
|
||||||
|
@ -91,26 +92,26 @@ class Buzzer {
|
||||||
// in order to avoid "pops" (square wave overtones)
|
// in order to avoid "pops" (square wave overtones)
|
||||||
// that happen with instant changes in gain.
|
// that happen with instant changes in gain.
|
||||||
|
|
||||||
constructor(txGain=0.5) {
|
constructor(txGain=0.3) {
|
||||||
this.txGain = txGain
|
this.txGain = txGain
|
||||||
|
|
||||||
this.ac = new AudioContext()
|
this.ac = new AudioContext()
|
||||||
|
|
||||||
this.lowGain = this.ac.createGain()
|
this.lowGain = this.create(lowFreq)
|
||||||
this.lowGain.connect(this.ac.destination)
|
this.highGain = this.create(highFreq)
|
||||||
this.lowGain.gain.value = 0
|
this.errorGain = this.create(errorFreq, "square")
|
||||||
this.lowOsc = this.ac.createOscillator()
|
}
|
||||||
this.lowOsc.connect(this.lowGain)
|
|
||||||
this.lowOsc.frequency.value = lowFreq
|
|
||||||
this.lowOsc.start()
|
|
||||||
|
|
||||||
this.highGain = this.ac.createGain()
|
create(frequency, type="sine") {
|
||||||
this.highGain.connect(this.ac.destination)
|
let gain = this.ac.createGain()
|
||||||
this.highGain.gain.value = 0
|
gain.connect(this.ac.destination)
|
||||||
this.highOsc = this.ac.createOscillator()
|
gain.gain.value = 0
|
||||||
this.highOsc.connect(this.highGain)
|
let osc = this.ac.createOscillator()
|
||||||
this.highOsc.frequency.value = highFreq
|
osc.type = type
|
||||||
this.highOsc.start()
|
osc.connect(gain)
|
||||||
|
osc.frequency.value = frequency
|
||||||
|
osc.start()
|
||||||
|
return gain
|
||||||
}
|
}
|
||||||
|
|
||||||
gain(high) {
|
gain(high) {
|
||||||
|
@ -145,6 +146,14 @@ class Buzzer {
|
||||||
this.txGain = gain
|
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
|
* Begin buzzing at time
|
||||||
*
|
*
|
||||||
|
@ -187,6 +196,10 @@ class Buzzer {
|
||||||
|
|
||||||
class Vail {
|
class Vail {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
this.sent = []
|
||||||
|
this.lagTimes = [0]
|
||||||
|
this.rxDurations = [0]
|
||||||
|
this.rxDelay = 0 // Milliseconds to add to incoming timestamps
|
||||||
this.beginTxTime = null // Time when we began transmitting
|
this.beginTxTime = null // Time when we began transmitting
|
||||||
|
|
||||||
// Set up WebSocket
|
// Set up WebSocket
|
||||||
|
@ -194,7 +207,7 @@ class Vail {
|
||||||
wsUrl.protocol = "ws:"
|
wsUrl.protocol = "ws:"
|
||||||
wsUrl.pathname += "chat"
|
wsUrl.pathname += "chat"
|
||||||
window.socket = new WebSocket(wsUrl)
|
window.socket = new WebSocket(wsUrl)
|
||||||
window.socket.addEventListener("message", this.wsMessage)
|
window.socket.addEventListener("message", e => this.wsMessage(e))
|
||||||
|
|
||||||
// Listen to HTML buttons
|
// Listen to HTML buttons
|
||||||
for (let e of document.querySelectorAll("button.key")) {
|
for (let e of document.querySelectorAll("button.key")) {
|
||||||
|
@ -213,6 +226,7 @@ class Vail {
|
||||||
|
|
||||||
// Listen for slider values
|
// Listen for slider values
|
||||||
this.inputInit("#iambic-duration", e => this.iambic.SetInterval(e.target.value))
|
this.inputInit("#iambic-duration", e => this.iambic.SetInterval(e.target.value))
|
||||||
|
this.inputInit("#rx-delay", e => {this.rxDelay = e.target.value})
|
||||||
}
|
}
|
||||||
|
|
||||||
inputInit(selector, func) {
|
inputInit(selector, func) {
|
||||||
|
@ -241,17 +255,69 @@ class Vail {
|
||||||
let endTxTime = Date.now()
|
let endTxTime = Date.now()
|
||||||
let duration = endTxTime - this.beginTxTime
|
let duration = endTxTime - this.beginTxTime
|
||||||
this.buzzer.Silence(true)
|
this.buzzer.Silence(true)
|
||||||
|
this.wsSend(this.beginTxTime, duration)
|
||||||
|
this.beginTxTime = null
|
||||||
|
}
|
||||||
|
|
||||||
let msg = JSON.stringify([this.beginTxTime, duration])
|
updateReading(selector, value) {
|
||||||
window.socket.send(msg)
|
let e = document.querySelector(selector)
|
||||||
|
if (e) {
|
||||||
|
e.value = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateReadings() {
|
||||||
|
let avgLag = this.lagTimes.reduce((a,b) => (a+b)) / this.lagTimes.length
|
||||||
|
let longestRx = this.rxDurations.reduce(Math.max)
|
||||||
|
let suggestedDelay = (avgLag + longestRx) * 1.2
|
||||||
|
|
||||||
|
this.updateReading("#lag-value", avgLag.toFixed())
|
||||||
|
this.updateReading("#longest-rx-value", longestRx)
|
||||||
|
this.updateReading("#suggested-delay-value", suggestedDelay.toFixed())
|
||||||
|
}
|
||||||
|
|
||||||
|
addLagReading(duration) {
|
||||||
|
this.lagTimes.push(duration)
|
||||||
|
if (this.lagTimes.length > 20) {
|
||||||
|
this.lagTimes.shift()
|
||||||
|
}
|
||||||
|
this.updateReadings()
|
||||||
|
}
|
||||||
|
|
||||||
|
addRxDuration(duration) {
|
||||||
|
this.rxDurations.push(duration)
|
||||||
|
if (this.rxDurations.length > 20) {
|
||||||
|
this.rxDurations.shift()
|
||||||
|
}
|
||||||
|
this.updateReadings()
|
||||||
|
}
|
||||||
|
|
||||||
|
wsSend(time, duration) {
|
||||||
|
let msg = [time, duration]
|
||||||
|
let jmsg = JSON.stringify(msg)
|
||||||
|
window.socket.send(jmsg)
|
||||||
|
this.sent.push(jmsg)
|
||||||
}
|
}
|
||||||
|
|
||||||
wsMessage(event) {
|
wsMessage(event) {
|
||||||
let msg = JSON.parse(event.data)
|
let jmsg = event.data
|
||||||
|
let msg = JSON.parse(jmsg)
|
||||||
let beginTxTime = msg[0]
|
let beginTxTime = msg[0]
|
||||||
let duration = msg[1]
|
let duration = msg[1]
|
||||||
|
|
||||||
console.log(msg)
|
let sent = this.sent.filter(e => e != jmsg)
|
||||||
|
if (sent.length < this.sent.length) {
|
||||||
|
// We're getting our own message back, which tells us our lag.
|
||||||
|
// We shouldn't emit a tone, though.
|
||||||
|
this.sent = sent
|
||||||
|
this.addLagReading(Date.now() - beginTxTime - duration)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Beep!
|
||||||
|
this.buzzer.BuzzDuration(false, beginTxTime+this.rxDelay, duration)
|
||||||
|
|
||||||
|
this.addRxDuration(duration)
|
||||||
}
|
}
|
||||||
|
|
||||||
key(event) {
|
key(event) {
|
||||||
|
@ -262,11 +328,11 @@ class Vail {
|
||||||
|
|
||||||
let begin = event.type.endsWith("down")
|
let begin = event.type.endsWith("down")
|
||||||
|
|
||||||
if ((event.code == "Period") || (event.key == "KeyZ")) {
|
if ((event.code == "KeyZ") || (event.code == "Period")) {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
this.iambic.Key(begin, DIT)
|
this.iambic.Key(begin, DIT)
|
||||||
}
|
}
|
||||||
if ((event.code == "Slash") || (event.code == "KeyX")) {
|
if ((event.code == "KeyX") || (event.code == "Slash")) {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
this.iambic.Key(begin, DAH)
|
this.iambic.Key(begin, DAH)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue