mirror of https://github.com/nealey/vail.git
seems to work
This commit is contained in:
parent
9a37907945
commit
f23ea76a4f
|
@ -267,27 +267,27 @@ class TelegraphBuzzer extends AudioBuzzer{
|
|||
}
|
||||
|
||||
class Lamp extends Buzzer {
|
||||
constructor() {
|
||||
constructor(element) {
|
||||
super()
|
||||
this.lamp = document.querySelector("#recv")
|
||||
this.element = element
|
||||
}
|
||||
|
||||
Buzz(tx, when=0) {
|
||||
if (tx) return
|
||||
|
||||
let ms = when - Date.now()
|
||||
setTimeout(e => {
|
||||
recv.classList.add("rx")
|
||||
}, ms)
|
||||
let ms = when?when - Date.now():0
|
||||
setTimeout(
|
||||
() =>{
|
||||
this.element.classList.add("rx")
|
||||
},
|
||||
ms,
|
||||
)
|
||||
}
|
||||
Silence(tx, when=0) {
|
||||
if (tx) return
|
||||
|
||||
let recv = document.querySelector("#recv")
|
||||
let ms = when - Date.now()
|
||||
setTimeout(e => {
|
||||
recv.classList.remove("rx")
|
||||
}, ms)
|
||||
let ms = when?when - Date.now():0
|
||||
setTimeout(() => this.element.classList.remove("rx"), ms)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -8,296 +8,247 @@
|
|||
<!-- Bulma CSS -->
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.3/css/bulma.min.css">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@mdi/font@6.5.95/css/materialdesignicons.min.css">
|
||||
|
||||
|
||||
<!-- Vail stuff -->
|
||||
<link rel="manifest" href="manifest.json">
|
||||
<link rel="icon" href="vail.png" sizes="256x256" type="image/png">
|
||||
<link rel="icon" href="vail.svg" sizes="any" type="image/svg+xml">
|
||||
<script type="module" src="vail.mjs"></script>
|
||||
<script type="module" src="ui.mjs"></script>
|
||||
<link rel="stylesheet" href="vail.css">
|
||||
</head>
|
||||
<body>
|
||||
<nav class="navbar">
|
||||
<nav class="navbar is-dark">
|
||||
<div class="navbar-brand">
|
||||
<a class="navbar-item">
|
||||
<img src="vail.svg" alt="">
|
||||
Vail
|
||||
<img class="" src="vail.svg" alt="">
|
||||
<div class="block">Vail</div>
|
||||
</a>
|
||||
</div>
|
||||
<div class="navbar-menu">
|
||||
<div class="navbar-end">
|
||||
<div class="navbar-item">
|
||||
<a href="https://github.com/nealey/vail/">Source Code</a>
|
||||
</div>
|
||||
<a class="navbar-item" href="https://github.com/nealey/vail/">Source Code</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
<div class="field">
|
||||
<label class="mdl-textfield__label" for="repeater">Repeater</label>
|
||||
<div class="control">
|
||||
<input class="mdl-textfield__input" type="text" id="repeater" list="repeater-list">
|
||||
<datalist id="repeater-list">
|
||||
<option>General</option>
|
||||
<option value="1">Channel 1</option>
|
||||
<option value="2">Channel 2</option>
|
||||
<option value="3">Channel 3</option>
|
||||
<option value="Null">No transmit</option>
|
||||
<option>Echo</option>
|
||||
<option>Echo 5s</option>
|
||||
<option>Echo 10s</option>
|
||||
<option>Fortunes</option>
|
||||
<option>Fortunes: Pauses ×2</option>
|
||||
<option>Fortunes: Pauses ×4</option>
|
||||
<option>Fortunes: Pauses ×8</option>
|
||||
</datalist>
|
||||
<div class="columns">
|
||||
<div class="column">
|
||||
<div class="box" id="transciever">
|
||||
<h1 class="title">Repeater</h1>
|
||||
<div class="level">
|
||||
<div class="level-left">
|
||||
<div class="level-item">
|
||||
|
||||
<div class="field is-horizontal">
|
||||
<div class="field-label is-hidden">
|
||||
<label class="label" for="repeater">Repeater</label>
|
||||
</div>
|
||||
<div class="field-body">
|
||||
<div class="field">
|
||||
<div class="control">
|
||||
<input class="input" type="text" id="repeater" list="repeater-list">
|
||||
<datalist id="repeater-list">
|
||||
<option>General</option>
|
||||
<option value="1">Channel 1</option>
|
||||
<option value="2">Channel 2</option>
|
||||
<option value="3">Channel 3</option>
|
||||
<option value="Null">No transmit</option>
|
||||
<option>Echo</option>
|
||||
<option>Echo 5s</option>
|
||||
<option>Echo 10s</option>
|
||||
<option>Fortunes</option>
|
||||
<option>Fortunes: Pauses ×2</option>
|
||||
<option>Fortunes: Pauses ×4</option>
|
||||
<option>Fortunes: Pauses ×8</option>
|
||||
</datalist>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="level-right">
|
||||
<div class="level-item">
|
||||
<!-- This appears as a little light that turns on when someone's sending -->
|
||||
<span class="tag" id="recv">
|
||||
<output class="has-text-info" id="note"></output>
|
||||
<i class="mdi mdi-volume-off" id="muted"></i>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="block">
|
||||
<div class="" id="charts">
|
||||
<canvas class="chart" id="rxChart" data-color="orange"></canvas>
|
||||
<canvas class="chart" id="txChart" data-color="teal"></canvas>
|
||||
<canvas class="chart" id="key0Chart" data-color="olive"></canvas>
|
||||
<canvas class="chart" id="key1Chart" data-color="purple"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="block">
|
||||
<table class="wide">
|
||||
<tr>
|
||||
<td>
|
||||
<button class="button key is-primary" data-key="0" title="right click for Key">
|
||||
Key
|
||||
</button>
|
||||
<div class="shortcuts">
|
||||
<kbd title="keyboard button">.</kbd>
|
||||
<kbd title="keyboard button">x</kbd>
|
||||
<i class="mdi mdi-gamepad-circle-left" title="Gamepad Left Button"></i>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<button class="button key is-primary" data-key="1" title="right click for Key">
|
||||
Key
|
||||
</button>
|
||||
<div class="shortcuts">
|
||||
<kbd title="keyboard button">/</kbd>
|
||||
<kbd title="keyboard button">z</kbd>
|
||||
<i class="mdi mdi-gamepad-circle-up" title="Gamepad Top Button"></i>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="field is-horizontal">
|
||||
<div class="field-label">
|
||||
<label class="label">Mode</label>
|
||||
</div>
|
||||
<div class="field-body">
|
||||
<div class="field">
|
||||
<div class="control">
|
||||
<div class="select">
|
||||
<select id="keyer-mode">
|
||||
<option value="cootie">Straight Key / Cootie</option>
|
||||
<option value="bug">Bug</option>
|
||||
<option value="elbug">ElBug</option>
|
||||
<option value="singledot">Single Dot</option>
|
||||
<option value="ultimatic">Ultimatic</option>
|
||||
<option value="iambic">Iambic (Plain)</option>
|
||||
<option value="iambica">Iambic A</option>
|
||||
<option value="iambicb">Iambic B</option>
|
||||
<option value="keyahead">Keyahead</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field is-horizontal">
|
||||
<div class="field-label">
|
||||
<label class="label">
|
||||
<output for="keyer-rate"></output> WPM
|
||||
</label>
|
||||
</div>
|
||||
<div class="field-body">
|
||||
<div class="field">
|
||||
<div class="control">
|
||||
<input
|
||||
id="keyer-rate"
|
||||
type="range"
|
||||
min="5"
|
||||
max="40"
|
||||
step="1"
|
||||
value="12">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="column">
|
||||
<div class="box">
|
||||
<h2 class="title">Knobs</h2>
|
||||
<div class="block">
|
||||
<div class="control">
|
||||
<button id="ck" class="button is-primary">
|
||||
CK
|
||||
</button>
|
||||
</div>
|
||||
<div class="">
|
||||
Send <code>CK</code> (check) to the repeater, and play when it comes back.
|
||||
</div>
|
||||
</div>
|
||||
<div class="block">
|
||||
<div class="control">
|
||||
<button id="reset" class="button">
|
||||
Reset
|
||||
</button>
|
||||
</div>
|
||||
<div>
|
||||
Reset all Vail preferences to default.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="field is-horizontal">
|
||||
<div class="field-label">
|
||||
<label class="label">
|
||||
<output for="rx-delay"></output>s
|
||||
rx delay
|
||||
</label>
|
||||
</div>
|
||||
<div class="field-body">
|
||||
<div class="field">
|
||||
<div class="control">
|
||||
<input
|
||||
id="rx-delay"
|
||||
type="range"
|
||||
min="0"
|
||||
max="10"
|
||||
value="4"
|
||||
step="0.1"
|
||||
list="rx-delays">
|
||||
<datalist id="rx-delays">
|
||||
<option value="0"></option>
|
||||
<option value="1"></option>
|
||||
<option value="2" label="2s"></option>
|
||||
<option value="3"></option>
|
||||
<option value="4" label="4s"></option>
|
||||
<option value="5"></option>
|
||||
<option value="6" label="6s"></option>
|
||||
<option value="7"></option>
|
||||
<option value="8" label="8s"></option>
|
||||
<option value="9"></option>
|
||||
<option value="10"></option>
|
||||
</datalist>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p>
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" id="telegraph-buzzer">
|
||||
Telegraph sounds
|
||||
</label>
|
||||
</p>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="box">
|
||||
<h2 class="title">Notes</h2>
|
||||
<textarea class="textarea" placeholder="Enter your own notes here" id="notes"></textarea>
|
||||
<a href="https://github.com/nealey/vail/wiki" target="_blank">Vail Wiki</a>
|
||||
</div>
|
||||
|
||||
<main class="mdl-layout__content">
|
||||
<div class="flex">
|
||||
<div class="mdl-card mdl-shadow--4dp input-methods mashable-area">
|
||||
<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">
|
||||
<div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label">
|
||||
</div>
|
||||
</h2>
|
||||
</div>
|
||||
<output id="note"></output>
|
||||
|
||||
<div id="charts">
|
||||
<canvas class="chart" id="rxChart" data-color="orange"></canvas>
|
||||
<canvas class="chart" id="txChart" data-color="teal"></canvas>
|
||||
<canvas class="chart" id="ditChart" data-color="olive"></canvas>
|
||||
<canvas class="chart" id="dahChart" data-color="purple"></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>
|
||||
<a href="#iambic" class="mdl-tabs__tab" data-singlekey="iambic">Iambic</a>
|
||||
<a href="#tools" class="mdl-tabs__tab">Tools</a>
|
||||
</div>
|
||||
<div class="mdl-tabs__panel is-active" id="straight">
|
||||
<table class="center wide">
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<button id="key" class="key mdl-button mdl-js-button mdl-button--raised mdl-button--colored">
|
||||
Key
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<i class="mdi mdi-keyboard" title="Keyboard"></i>
|
||||
</td>
|
||||
<td>
|
||||
<kbd>c</kbd>
|
||||
<kbd>,</kbd>
|
||||
<kbd>Enter</kbd>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<i class="mdi mdi-controller-classic"></i>
|
||||
</td>
|
||||
<td>
|
||||
<i class="mdi mdi-gamepad-circle-down" title="Gamepad Bottom Button"></i>
|
||||
<i class="mdi mdi-gamepad-circle-right" title="Gamepad Right Button"></i>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div class="mdl-tabs__panel" id="iambic">
|
||||
<table class="center wide">
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<button id="dit" class="key mdl-button mdl-js-button mdl-button--raised mdl-button--colored">
|
||||
Dit
|
||||
</button>
|
||||
</td>
|
||||
<td colspan="2">
|
||||
<button id="dah" class="key mdl-button mdl-js-button mdl-button--raised mdl-button--colored">
|
||||
Dah
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<i class="mdi mdi-keyboard" title="Keyboard"></i>
|
||||
</td>
|
||||
<td>
|
||||
<kbd>.</kbd>
|
||||
<kbd>x</kbd>
|
||||
</td>
|
||||
<td>
|
||||
<i class="mdi mdi-keyboard" title="Keyboard"></i>
|
||||
</td>
|
||||
<td>
|
||||
<kbd>/</kbd>
|
||||
<kbd>z</kbd>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<i class="mdi mdi-controller-classic"></i>
|
||||
</td>
|
||||
<td>
|
||||
<i class="mdi mdi-gamepad-circle-left" title="Gamepad Left Button"></i>
|
||||
<kbd class="gamepad" title="Gamepad Left Shoulder Button">L1</kbd>
|
||||
</td>
|
||||
<td>
|
||||
<i class="mdi mdi-controller-classic"></i>
|
||||
</td>
|
||||
<td>
|
||||
<i class="mdi mdi-gamepad-circle-up" title="Gamepad Top Button"></i>
|
||||
<kbd class="gamepad" title="Gamepad Right Shoulder Button">R1</kbd>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="4" class="mdl-card__supporting-text" style="text-align: center;">
|
||||
Second mouse button switches dah and dit
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div class="mdl-tabs__panel" id="tools">
|
||||
<div class="flex mdl-card__supporting-text">
|
||||
<button id="ck" class="mdl-button mdl-js-button mdl-button--raised mdl-button--colored">
|
||||
CK
|
||||
</button>
|
||||
<p>
|
||||
Send <code>CK</code> (check) to the repeater, and play when it comes back.
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex mdl-card__supporting-text">
|
||||
<button id="reset" class="mdl-button mdl-js-button mdl-button--raised mdl-button--colored">
|
||||
Reset
|
||||
</button>
|
||||
<p>
|
||||
Reset all Vail preferences to default.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mdl-card__actions">
|
||||
<button class="mdl-button mdl-js-button mdl-button--fab mdl-button--mini-fab mdl-button--colored maximize" title="maximize">
|
||||
<i class="material-icons mdl-color-text--white" role="presentation">aspect_ratio</i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mdl-card mdl-shadow--4dp">
|
||||
<div class="mdl-card__title">
|
||||
<h2 class="mdl-card__title-text">Notes</h2>
|
||||
</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" target="_blank">Vail Wiki</a>
|
||||
</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">
|
||||
<p>
|
||||
Iambic Dit length:
|
||||
<output id="iambic-duration-value"></output>ms
|
||||
/
|
||||
<output id="iambic-duration-wpm"></output> WPM
|
||||
<input
|
||||
id="iambic-duration"
|
||||
class="mdl-slider mdl-js-slider"
|
||||
type="range"
|
||||
min="40"
|
||||
max="255"
|
||||
value="100">
|
||||
</p>
|
||||
<p>
|
||||
<label class="mdl-switch mdl-js-switch mdl-js-ripple-effect" for="iambic-mode-b">
|
||||
<input type="checkbox" id="iambic-mode-b" class="mdl-switch__input">
|
||||
<span class="mdl-switch__label">Iambic mode B</span>
|
||||
</label>
|
||||
<label class="mdl-switch mdl-js-switch mdl-js-ripple-effect" for="iambic-typeahead">
|
||||
<input type="checkbox" id="iambic-typeahead" class="mdl-switch__input">
|
||||
<span class="mdl-switch__label">Iambic typeahead</span>
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
Receive delay:
|
||||
<output id="rx-delay-value"></output>ms
|
||||
<input
|
||||
id="rx-delay"
|
||||
class="mdl-slider mdl-js-slider"
|
||||
type="range"
|
||||
min="0"
|
||||
max="9999"
|
||||
value="4000">
|
||||
</p>
|
||||
<p>
|
||||
<label class="mdl-switch mdl-js-switch mdl-js-ripple-effect" for="telegraph-buzzer">
|
||||
<input type="checkbox" id="telegraph-buzzer" class="mdl-switch__input">
|
||||
<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>
|
||||
<tr>
|
||||
<td>
|
||||
Suggested receive delay:
|
||||
</td>
|
||||
<td>
|
||||
<output id="suggested-delay-value">0</output>ms
|
||||
</td>
|
||||
</tr>
|
||||
<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>
|
||||
Your clock is off by:
|
||||
</td>
|
||||
<td>
|
||||
<output id="clock-off-value">??</output>ms
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</main>
|
||||
<div class="columns is-centered bottom">
|
||||
<div class="column is-half" id="errors"></div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
<!-- vim: set noet ts=2 sw=2 : -->
|
||||
|
|
|
@ -27,24 +27,14 @@ export class HTML extends Input{
|
|||
|
||||
keyButton(event) {
|
||||
let begin = event.type.endsWith("down") || event.type.endsWith("start")
|
||||
|
||||
if (event.target.id == "dah") {
|
||||
if (event.button == 2) {
|
||||
this.keyer.Dit(begin)
|
||||
} else {
|
||||
this.keyer.Dah(begin)
|
||||
}
|
||||
} else if (event.target.id == "dit") {
|
||||
if (event.button == 2) {
|
||||
this.keyer.Dah(begin)
|
||||
} else {
|
||||
this.keyer.Dit(begin)
|
||||
}
|
||||
} else if (event.target.id == "key") {
|
||||
this.keyer.Straight(begin)
|
||||
} else {
|
||||
return
|
||||
let key = event.target.dataset.key
|
||||
|
||||
// Button 2 does the other key (assuming 2 keys)
|
||||
if (event.button == 2) {
|
||||
key = 1 - key
|
||||
}
|
||||
this.keyer.Key(key, begin)
|
||||
|
||||
if (event.cancelable) {
|
||||
event.preventDefault()
|
||||
}
|
||||
|
@ -80,7 +70,7 @@ export class Keyboard extends Input{
|
|||
) {
|
||||
// Dit
|
||||
if (this.ditDown != down) {
|
||||
this.keyer.Dit(down)
|
||||
this.keyer.Key(0, down)
|
||||
this.ditDown = down
|
||||
}
|
||||
}
|
||||
|
@ -92,7 +82,7 @@ export class Keyboard extends Input{
|
|||
|| (event.key == "]")
|
||||
) {
|
||||
if (this.dahDown != down) {
|
||||
this.keyer.Dah(down)
|
||||
this.keyer.Key(1, down)
|
||||
this.dahDown = down
|
||||
}
|
||||
}
|
||||
|
@ -173,10 +163,10 @@ export class MIDI extends Input{
|
|||
this.keyer.Straight(begin)
|
||||
break
|
||||
case 1: // C#
|
||||
this.keyer.Dit(begin)
|
||||
this.keyer.Key(0, begin)
|
||||
break
|
||||
case 2: // D
|
||||
this.keyer.Dah(begin)
|
||||
this.keyer.Key(1, begin)
|
||||
break
|
||||
default:
|
||||
return
|
||||
|
@ -229,10 +219,10 @@ export class Gamepad extends Input{
|
|||
this.keyer.Straight(currentButtons.key)
|
||||
}
|
||||
if (currentButtons.dit != this.gamepadButtons.dit) {
|
||||
this.keyer.Dit(currentButtons.dit)
|
||||
this.keyer.Key(0, currentButtons.dit)
|
||||
}
|
||||
if (currentButtons.dah != this.gamepadButtons.dah) {
|
||||
this.keyer.Dah(currentButtons.dah)
|
||||
this.keyer.Key(1, currentButtons.dah)
|
||||
}
|
||||
this.gamepadButtons = currentButtons
|
||||
|
||||
|
|
|
@ -6,10 +6,8 @@
|
|||
* advanced than the bug keyer.
|
||||
*/
|
||||
|
||||
/** Silent period between words */
|
||||
const PAUSE_WORD = -7
|
||||
/** Silent period between letters */
|
||||
const PAUSE_LETTER = -3
|
||||
import * as RoboKeyer from "./robokeyer.mjs"
|
||||
|
||||
/** Silent period between dits and dash */
|
||||
const PAUSE = -1
|
||||
/** Length of a dit */
|
||||
|
@ -34,78 +32,6 @@ const Minute = 60 * Second
|
|||
/** @type {Duration} */
|
||||
const Hour = 60 * Minute
|
||||
|
||||
const MorseMap = {
|
||||
"\x04": ".-.-.", // End Of Transmission
|
||||
"\x18": "........", // Cancel
|
||||
"0": "-----",
|
||||
"1": ".----",
|
||||
"2": "..---",
|
||||
"3": "...--",
|
||||
"4": "....-",
|
||||
"5": ".....",
|
||||
"6": "-....",
|
||||
"7": "--...",
|
||||
"8": "---..",
|
||||
"9": "----.",
|
||||
"a": ".-",
|
||||
"b": "-...",
|
||||
"c": "-.-.",
|
||||
"d": "-..",
|
||||
"e": ".",
|
||||
"f": "..-.",
|
||||
"g": "--.",
|
||||
"h": "....",
|
||||
"i": "..",
|
||||
"j": ".---",
|
||||
"k": "-.-",
|
||||
"l": ".-..",
|
||||
"m": "--",
|
||||
"n": "-.",
|
||||
"o": "---",
|
||||
"p": ".--.",
|
||||
"q": "--.-",
|
||||
"r": ".-.",
|
||||
"s": "...",
|
||||
"t": "-",
|
||||
"u": "..-",
|
||||
"v": "...-",
|
||||
"w": ".--",
|
||||
"x": "-..-",
|
||||
"y": "-.--",
|
||||
"z": "--..",
|
||||
".": ".-.-.-",
|
||||
",": "--..--",
|
||||
"?": "..--..",
|
||||
"'": ".----.",
|
||||
"!": "-.-.--",
|
||||
"/": "-..-.",
|
||||
"(": "-.--.",
|
||||
")": "-.--.-",
|
||||
"&": ".-...",
|
||||
":": "---...",
|
||||
";": "---...",
|
||||
"=": "-...-",
|
||||
"+": ".-.-.",
|
||||
"-": "-....-",
|
||||
"_": "--..-.",
|
||||
"\"": ".-..-.",
|
||||
"$": "...-..-",
|
||||
"@": ".--.-.",
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the inverse of the input.
|
||||
* If you give it dit, it returns dah, and vice-versa.
|
||||
*
|
||||
* @param ditdah What to invert
|
||||
* @returns The inverse of ditdah
|
||||
*/
|
||||
function not(ditdah) {
|
||||
if (ditdah == DIT) {
|
||||
return DAH
|
||||
}
|
||||
return DIT
|
||||
}
|
||||
|
||||
/**
|
||||
* Queue Set: A Set you can shift and pop.
|
||||
|
@ -167,6 +93,20 @@ class StraightKeyer {
|
|||
this.txRelays = []
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the duration of dit.
|
||||
*
|
||||
* @param {Duration} d New dit duration
|
||||
*/
|
||||
SetDitDuration(d) {
|
||||
this.ditDuration = d
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up all timers, etc.
|
||||
*/
|
||||
Release() {}
|
||||
|
||||
/**
|
||||
* Returns the state of a single transmit relay.
|
||||
*
|
||||
|
@ -235,7 +175,7 @@ class CootieKeyer extends StraightKeyer {
|
|||
*/
|
||||
class BugKeyer extends StraightKeyer {
|
||||
KeyNames() {
|
||||
return ["· ", "Key"]
|
||||
return ["Dit ", "Key"]
|
||||
}
|
||||
|
||||
Reset() {
|
||||
|
@ -248,15 +188,6 @@ class BugKeyer extends StraightKeyer {
|
|||
this.keyPressed = [false, false]
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the duration of dit.
|
||||
*
|
||||
* @param {Duration} d New dit duration
|
||||
*/
|
||||
SetDitDuration(d) {
|
||||
this.ditDuration = d
|
||||
}
|
||||
|
||||
Key(key, pressed) {
|
||||
this.keyPressed[key] = pressed
|
||||
if (key == 0) {
|
||||
|
@ -302,7 +233,7 @@ class BugKeyer extends StraightKeyer {
|
|||
*/
|
||||
class ElBugKeyer extends BugKeyer {
|
||||
KeyNames() {
|
||||
return ["· ", "−"]
|
||||
return ["Dit ", "Dah"]
|
||||
}
|
||||
|
||||
Reset() {
|
||||
|
@ -499,281 +430,47 @@ class IambicBKeyer extends IambicKeyer {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Keyer class. This handles iambic and straight key input.
|
||||
*
|
||||
* This will handle the following things that people appear to want with iambic input:
|
||||
*
|
||||
* - Typematic: you hold the key down and it repeats evenly-spaced tones
|
||||
* - Typeahead: if you hit a key while it's still transmitting the last-entered one, it queues up your next entered one
|
||||
*/
|
||||
class OldKeyer {
|
||||
/**
|
||||
* Create a Keyer
|
||||
*
|
||||
* @param {TxControl} beginTxFunc Callback to begin transmitting
|
||||
* @param {TxControl} endTxFunc Callback to end transmitting
|
||||
* @param {number} intervalDuration Dit duration (milliseconds)
|
||||
* @param {number} pauseMultiplier How long to stretch out inter-letter and inter-word pauses
|
||||
*/
|
||||
constructor(beginTxFunc, endTxFunc, {intervalDuration=100, pauseMultiplier=1}={}) {
|
||||
this.beginTxFunc = beginTxFunc
|
||||
this.endTxFunc = endTxFunc
|
||||
this.intervalDuration = intervalDuration
|
||||
this.pauseMultiplier = pauseMultiplier
|
||||
this.ditDown = false
|
||||
this.dahDown = false
|
||||
this.typeahead = false
|
||||
this.iambicModeB = true
|
||||
this.last = null
|
||||
class KeyaheadKeyer extends ElBugKeyer {
|
||||
Reset() {
|
||||
super.Reset()
|
||||
this.queue = []
|
||||
this.pulseTimer = null
|
||||
}
|
||||
|
||||
pulse() {
|
||||
if (this.queue.length == 0) {
|
||||
let next = this.typematic()
|
||||
if (next) {
|
||||
// Barkeep! Another round!
|
||||
this.Enqueue(next)
|
||||
} else {
|
||||
// Nothing left on the queue, stop the machine
|
||||
this.pulseTimer = null
|
||||
return
|
||||
}
|
||||
Key(key, pressed) {
|
||||
if (pressed) {
|
||||
this.queue.push(key)
|
||||
}
|
||||
super.Key(key, pressed)
|
||||
}
|
||||
|
||||
nextTx() {
|
||||
let next = this.queue.shift()
|
||||
if (next < 0) {
|
||||
next *= -1
|
||||
if (next > 1) {
|
||||
// Don't adjust spacing within a letter
|
||||
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)
|
||||
}
|
||||
if (next != null) {
|
||||
return next
|
||||
}
|
||||
this.pulseTimer = setTimeout(() => this.pulse(), next * this.intervalDuration)
|
||||
return super.nextTx()
|
||||
}
|
||||
}
|
||||
|
||||
maybePulse() {
|
||||
// If there's no timer running right now, restart the pulse
|
||||
if (!this.pulseTimer) {
|
||||
this.pulse()
|
||||
}
|
||||
}
|
||||
|
||||
typematic() {
|
||||
if (this.ditDown && this.dahDown) {
|
||||
this.modeBQueue = this.last
|
||||
this.last = not(this.last)
|
||||
} else if (this.ditDown) {
|
||||
this.modeBQueue = null
|
||||
this.last = DIT
|
||||
} else if (this.dahDown) {
|
||||
this.modeBQueue = null
|
||||
this.last = DAH
|
||||
} else if (this.modeBQueue && this.iambicModeB) {
|
||||
this.last = this.modeBQueue
|
||||
this.modeBQueue = null
|
||||
} else {
|
||||
this.last = null
|
||||
this.modeBQueue = null
|
||||
}
|
||||
return this.last
|
||||
}
|
||||
/**
|
||||
* A dictionary of all available keyers
|
||||
*/
|
||||
const Keyers = {
|
||||
straight: StraightKeyer,
|
||||
cootie: CootieKeyer,
|
||||
bug: BugKeyer,
|
||||
elbug: ElBugKeyer,
|
||||
singledot: SingleDotKeyer,
|
||||
ultimatic: UltimaticKeyer,
|
||||
iambic: IambicKeyer,
|
||||
iambica: IambicAKeyer,
|
||||
iambicb: IambicBKeyer,
|
||||
keyahead: KeyaheadKeyer,
|
||||
|
||||
/**
|
||||
* Return true if we are currently playing out something
|
||||
*/
|
||||
Busy() {
|
||||
return this.pulseTimer
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a new dit interval (transmission rate)
|
||||
*
|
||||
* @param {number} duration Dit duration (milliseconds)
|
||||
*/
|
||||
SetIntervalDuration(duration) {
|
||||
this.intervalDuration = duration
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a new pause multiplier.
|
||||
*
|
||||
* This slows down the inter-letter and inter-word pauses,
|
||||
* which can aid in learning.
|
||||
*
|
||||
* @param {number} multiplier Pause multiplier
|
||||
*/
|
||||
SetPauseMultiplier(multiplier) {
|
||||
this.pauseMultiplier = multiplier
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Iambic mode B.
|
||||
*
|
||||
* Near as I can tell, B sends one more tone than was entered, when
|
||||
* both keys are held down.
|
||||
* This logic happens in the typematic code.
|
||||
*
|
||||
* ▁▁▔▔▔▔▔▔▔▁▁▁▁ Dit key
|
||||
*
|
||||
* ▁▔▔▔▔▔▔▔▔▁▁▁▁ Dah key
|
||||
*
|
||||
* ▁▔▔▔▁▔▁▔▔▔▁▁▁ Mode A output
|
||||
*
|
||||
* ▁▔▔▔▁▔▁▔▔▔▁▔▁ Mode B output
|
||||
*
|
||||
* @param {boolean} value True to set mode to B
|
||||
*/
|
||||
SetIambicModeB(value) {
|
||||
this.iambicModeB = Boolean(value)
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable/disable typeahead.
|
||||
*
|
||||
* Typeahead maintains a key buffer, so you can key in dits and dahs faster than the
|
||||
* Iambic keyer can play them out.
|
||||
*
|
||||
* Some people apparently expect this behavior, and have trouble if it isn't enabled.
|
||||
* For others, having this enabled makes it feel like they have a "phantom keyer"
|
||||
* entering keys they did not send.
|
||||
*
|
||||
* @param value True to enable typeahead
|
||||
*/
|
||||
SetTypeahead(value) {
|
||||
this.typeahead = value
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete anything left on the queue.
|
||||
*/
|
||||
Flush() {
|
||||
this.queue.splice(0)
|
||||
}
|
||||
|
||||
/**
|
||||
* Add to the output queue, and start processing the queue if it's not currently being processed.
|
||||
*
|
||||
* @param {number} key A duration, in dits. Negative durations are silent.
|
||||
*/
|
||||
Enqueue(key) {
|
||||
this.queue.push(key)
|
||||
if (key > 0) {
|
||||
this.queue.push(PAUSE)
|
||||
}
|
||||
this.maybePulse()
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue a morse code string (eg "... --- ...")
|
||||
*
|
||||
* @param {string} ms String to enqueue
|
||||
*/
|
||||
EnqueueMorseString(ms) {
|
||||
for (let mc of ms) {
|
||||
switch (mc) {
|
||||
case ".":
|
||||
this.Enqueue(DIT)
|
||||
break
|
||||
case "-":
|
||||
this.Enqueue(DAH)
|
||||
break
|
||||
case " ":
|
||||
this.Enqueue(PAUSE_LETTER)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue an ASCII string (eg "SOS help")
|
||||
*
|
||||
* @param {string} s String to enqueue
|
||||
*/
|
||||
EnqueueAsciiString(s, {pauseLetter = PAUSE_LETTER, pauseWord = PAUSE_WORD} = {}) {
|
||||
for (let c of s.toLowerCase()) {
|
||||
let m = MorseMap[c]
|
||||
if (m) {
|
||||
this.EnqueueMorseString(m)
|
||||
this.Enqueue(pauseLetter)
|
||||
continue
|
||||
}
|
||||
|
||||
switch (c) {
|
||||
case " ":
|
||||
case "\n":
|
||||
case "\t":
|
||||
this.Enqueue(pauseWord)
|
||||
break
|
||||
default:
|
||||
console.warn("Unable to encode '" + c + "'!")
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Do something to the straight key
|
||||
*
|
||||
* @param down True if key was pressed
|
||||
*/
|
||||
Straight(down) {
|
||||
if (down) {
|
||||
this.beginTxFunc()
|
||||
} else {
|
||||
this.endTxFunc()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Do something to the dit key
|
||||
*
|
||||
* @param down True if key was pressed
|
||||
*/
|
||||
Dit(down) {
|
||||
this.ditDown = down
|
||||
if (down) {
|
||||
if (this.typeahead
|
||||
|| !this.Busy()
|
||||
|| (this.iambicModeB && (this.last == DAH))) {
|
||||
this.Enqueue(DIT)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Do something to the dah key
|
||||
*
|
||||
* @param down True if key was pressed
|
||||
*/
|
||||
Dah(down) {
|
||||
this.dahDown = down
|
||||
if (down) {
|
||||
if (this.typeahead
|
||||
|| !this.Busy()
|
||||
|| (this.iambicModeB && (this.last == DIT))) {
|
||||
this.Enqueue(DAH)
|
||||
}
|
||||
}
|
||||
}
|
||||
robo: RoboKeyer.Keyer,
|
||||
}
|
||||
|
||||
export {
|
||||
StraightKeyer,
|
||||
CootieKeyer, BugKeyer, ElBugKeyer,
|
||||
SingleDotKeyer, UltimaticKeyer,
|
||||
IambicKeyer, IambicAKeyer, IambicBKeyer,
|
||||
Keyers,
|
||||
}
|
|
@ -124,11 +124,10 @@ export class Vail {
|
|||
export class Null {
|
||||
constructor(rx) {
|
||||
this.rx = rx
|
||||
this.interval = setInterval(() => this.pulse(), 1 * Second)
|
||||
this.interval = setInterval(() => this.pulse(), 3 * Second)
|
||||
}
|
||||
|
||||
pulse() {
|
||||
console.log("pulse")
|
||||
this.rx(0, 0, {note: "local"})
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,217 @@
|
|||
/** Silent period between words */
|
||||
const PAUSE_WORD = -7
|
||||
/** Silent period between letters */
|
||||
const PAUSE_LETTER = -3
|
||||
/** Silent period between dits and dash */
|
||||
const PAUSE = -1
|
||||
/** Length of a dit */
|
||||
const DIT = 1
|
||||
/** Length of a dah */
|
||||
const DAH = 3
|
||||
|
||||
const MorseMap = {
|
||||
"\x04": ".-.-.", // End Of Transmission
|
||||
"\x18": "........", // Cancel
|
||||
"0": "-----",
|
||||
"1": ".----",
|
||||
"2": "..---",
|
||||
"3": "...--",
|
||||
"4": "....-",
|
||||
"5": ".....",
|
||||
"6": "-....",
|
||||
"7": "--...",
|
||||
"8": "---..",
|
||||
"9": "----.",
|
||||
"a": ".-",
|
||||
"b": "-...",
|
||||
"c": "-.-.",
|
||||
"d": "-..",
|
||||
"e": ".",
|
||||
"f": "..-.",
|
||||
"g": "--.",
|
||||
"h": "....",
|
||||
"i": "..",
|
||||
"j": ".---",
|
||||
"k": "-.-",
|
||||
"l": ".-..",
|
||||
"m": "--",
|
||||
"n": "-.",
|
||||
"o": "---",
|
||||
"p": ".--.",
|
||||
"q": "--.-",
|
||||
"r": ".-.",
|
||||
"s": "...",
|
||||
"t": "-",
|
||||
"u": "..-",
|
||||
"v": "...-",
|
||||
"w": ".--",
|
||||
"x": "-..-",
|
||||
"y": "-.--",
|
||||
"z": "--..",
|
||||
".": ".-.-.-",
|
||||
",": "--..--",
|
||||
"?": "..--..",
|
||||
"'": ".----.",
|
||||
"!": "-.-.--",
|
||||
"/": "-..-.",
|
||||
"(": "-.--.",
|
||||
")": "-.--.-",
|
||||
"&": ".-...",
|
||||
":": "---...",
|
||||
";": "---...",
|
||||
"=": "-...-",
|
||||
"+": ".-.-.",
|
||||
"-": "-....-",
|
||||
"_": "--..-.",
|
||||
"\"": ".-..-.",
|
||||
"$": "...-..-",
|
||||
"@": ".--.-.",
|
||||
}
|
||||
|
||||
/**
|
||||
* Robo Keyer. It sends morse code so you don't have to!
|
||||
*/
|
||||
class Keyer {
|
||||
/**
|
||||
* Create a Keyer
|
||||
*
|
||||
* @param {TxControl} beginTxFunc Callback to begin transmitting
|
||||
* @param {TxControl} endTxFunc Callback to end transmitting
|
||||
*/
|
||||
constructor(beginTxFunc, endTxFunc) {
|
||||
this.beginTxFunc = beginTxFunc
|
||||
this.endTxFunc = endTxFunc
|
||||
this.ditDuration = 100
|
||||
this.pauseMultiplier = 1
|
||||
this.queue = []
|
||||
this.pulseTimer = null
|
||||
}
|
||||
|
||||
pulse() {
|
||||
let next = this.queue.shift()
|
||||
|
||||
if (next == null) {
|
||||
// Nothing left on the queue, stop the machine
|
||||
this.pulseTimer = null
|
||||
return
|
||||
}
|
||||
|
||||
if (next < 0) {
|
||||
next *= -1
|
||||
if (next > 1) {
|
||||
// Don't adjust spacing within a letter
|
||||
next *= this.pauseMultiplier
|
||||
} else {
|
||||
this.endTxFunc()
|
||||
}
|
||||
} else {
|
||||
this.beginTxFunc()
|
||||
}
|
||||
this.pulseTimer = setTimeout(() => this.pulse(), next * this.ditDuration)
|
||||
}
|
||||
|
||||
maybePulse() {
|
||||
// If there's no timer running right now, restart the pulse
|
||||
if (!this.pulseTimer) {
|
||||
this.pulse()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if we are currently playing out something
|
||||
*/
|
||||
Busy() {
|
||||
return Boolean(this.pulseTimer)
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a new dit interval (transmission rate)
|
||||
*
|
||||
* @param {number} duration Dit duration (milliseconds)
|
||||
*/
|
||||
SetDitDuration(duration) {
|
||||
this.ditDuration = duration
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a new pause multiplier.
|
||||
*
|
||||
* This slows down the inter-letter and inter-word pauses,
|
||||
* which can aid in learning.
|
||||
*
|
||||
* @param {number} multiplier Pause multiplier
|
||||
*/
|
||||
SetPauseMultiplier(multiplier) {
|
||||
this.pauseMultiplier = multiplier
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete anything left on the queue.
|
||||
*/
|
||||
Flush() {
|
||||
this.queue.splice(0)
|
||||
}
|
||||
|
||||
/**
|
||||
* Add to the output queue, and start processing the queue if it's not currently being processed.
|
||||
*
|
||||
* @param {number} key A duration, in dits. Negative durations are silent.
|
||||
*/
|
||||
Enqueue(key) {
|
||||
this.queue.push(key)
|
||||
if (key > 0) {
|
||||
this.queue.push(PAUSE)
|
||||
}
|
||||
this.maybePulse()
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue a morse code string (eg "... --- ...")
|
||||
*
|
||||
* @param {string} ms String to enqueue
|
||||
*/
|
||||
EnqueueMorseString(ms) {
|
||||
for (let mc of ms) {
|
||||
switch (mc) {
|
||||
case ".":
|
||||
this.Enqueue(DIT)
|
||||
break
|
||||
case "-":
|
||||
this.Enqueue(DAH)
|
||||
break
|
||||
case " ":
|
||||
this.Enqueue(PAUSE_LETTER)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue an ASCII string (eg "SOS help")
|
||||
*
|
||||
* @param {string} s String to enqueue
|
||||
*/
|
||||
EnqueueAsciiString(s, {pauseLetter = PAUSE_LETTER, pauseWord = PAUSE_WORD} = {}) {
|
||||
for (let c of s.toLowerCase()) {
|
||||
let m = MorseMap[c]
|
||||
if (m) {
|
||||
this.EnqueueMorseString(m)
|
||||
this.Enqueue(pauseLetter)
|
||||
continue
|
||||
}
|
||||
|
||||
switch (c) {
|
||||
case " ":
|
||||
case "\n":
|
||||
case "\t":
|
||||
this.Enqueue(pauseWord)
|
||||
break
|
||||
default:
|
||||
console.warn("Unable to encode '" + c + "'!")
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export {Keyer}
|
|
@ -0,0 +1,39 @@
|
|||
/**
|
||||
* If the user clicked on the little down arrow,
|
||||
* clear the input field so all autocomplete options are shown.
|
||||
*
|
||||
* This kludge may not work properly on every browser.
|
||||
*
|
||||
* @param event Triggering event
|
||||
*/
|
||||
function maybeDropdown(event) {
|
||||
let el = event.target
|
||||
switch (event.type) {
|
||||
case "click":
|
||||
let offset = el.clientWidth + el.offsetLeft - event.clientX;
|
||||
if (el.value) {
|
||||
el.dataset.value = el.value
|
||||
}
|
||||
if (offset < 0) {
|
||||
el.value = ""
|
||||
}
|
||||
break
|
||||
case "mouseleave":
|
||||
if (!el.value) {
|
||||
el.value = el.dataset.value
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function init() {
|
||||
let rep = document.querySelector("#repeater")
|
||||
rep.addEventListener("click", maybeDropdown)
|
||||
rep.addEventListener("mouseleave", maybeDropdown)
|
||||
}
|
||||
|
||||
if (document.readyState === "loading") {
|
||||
document.addEventListener("DOMContentLoaded", init)
|
||||
} else {
|
||||
init()
|
||||
}
|
132
static/vail.css
132
static/vail.css
|
@ -1,6 +1,14 @@
|
|||
.navbar-item img {
|
||||
margin-right: 1em;
|
||||
}
|
||||
|
||||
.key {
|
||||
width: 100%;
|
||||
height: 6em;
|
||||
width: 95%;
|
||||
height: 6em;
|
||||
}
|
||||
|
||||
.wide {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.mashable-area {
|
||||
|
@ -8,43 +16,39 @@
|
|||
-webkit-user-select: none; /* 2022-04-26 Safari still needs this */
|
||||
}
|
||||
|
||||
.center {
|
||||
text-align: center;
|
||||
#recv.rx {
|
||||
background-color: orange;
|
||||
}
|
||||
|
||||
.maximize {
|
||||
position: absolute;
|
||||
input[type=range] {
|
||||
height: 25px;
|
||||
margin: 10px 0;
|
||||
width: 100%;
|
||||
accent-color: #00d1b2;
|
||||
}
|
||||
|
||||
.bottom {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
width: 100vw;
|
||||
}
|
||||
.maximized .key {
|
||||
height: 90vh;
|
||||
}
|
||||
.maximized {
|
||||
width: 90vw;
|
||||
}
|
||||
|
||||
.wide {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
visibility: none;
|
||||
}
|
||||
|
||||
#errors {
|
||||
color: rgba(127, 0, 0, .54);
|
||||
max-height: 5em;
|
||||
max-height: 10em;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
@keyframes yellow-fade {
|
||||
0% {background: yellow;}
|
||||
100% {background: none;}
|
||||
0% {background-color: orange;}
|
||||
100% {background-color: default;}
|
||||
}
|
||||
#errors p {
|
||||
margin: 0;
|
||||
animation: yellow-fade 2s ease-in 1;
|
||||
background-color: #444;
|
||||
color: white;
|
||||
margin: 0.5em;
|
||||
padding: 0.2em;
|
||||
border-radius: 4px;
|
||||
text-align: center;
|
||||
animation: yellow-fade 0.3s ease-in 1;
|
||||
}
|
||||
|
||||
kbd {
|
||||
|
@ -72,78 +76,6 @@ code {
|
|||
padding: 0.1em;
|
||||
}
|
||||
|
||||
textarea.notes {
|
||||
width: 100%;
|
||||
min-height: 10em;
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 20em;
|
||||
}
|
||||
|
||||
.mdl-card__supporting-text {
|
||||
max-height: 20em;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
.mdl-card__supporting-text.long {
|
||||
max-height: inherit;
|
||||
}
|
||||
|
||||
#recv {
|
||||
width: 2em;
|
||||
height: 1em;
|
||||
line-height: 1em;
|
||||
position: absolute;
|
||||
top: 0.5em;
|
||||
right: 1em;
|
||||
border-radius: 0.3em;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
}
|
||||
#recv.rx {
|
||||
background-color: orange;
|
||||
}
|
||||
|
||||
#note {
|
||||
position: absolute;
|
||||
top: 0.5em;
|
||||
right: 5em;
|
||||
font-size: 80%;
|
||||
color: #888;
|
||||
}
|
||||
|
||||
.input-methods td {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
|
||||
#morse-tree table {
|
||||
width: 100%;
|
||||
}
|
||||
#morse-tree tr {
|
||||
height: 1.4em;
|
||||
text-align: center;
|
||||
font-family: monospace;
|
||||
}
|
||||
#morse-tree tr,
|
||||
#morse-tree td:nth-child(n+2) {
|
||||
background: #eee;
|
||||
}
|
||||
#morse-tree tr:nth-child(n+2),
|
||||
#morse-tree td.dah {
|
||||
background: #ddd;
|
||||
}
|
||||
|
||||
#morse-list span {
|
||||
font-family: monospace;
|
||||
display: inline-block;
|
||||
background: #eee;
|
||||
margin: 1px;
|
||||
padding: 0.4em;
|
||||
min-width: 4em;
|
||||
}
|
||||
|
||||
#charts {
|
||||
line-height: 0;
|
||||
}
|
||||
|
|
134
static/vail.mjs
134
static/vail.mjs
|
@ -1,4 +1,4 @@
|
|||
import * as Keyer from "./keyer.mjs"
|
||||
import {Keyers} from "./keyers.mjs"
|
||||
import * as Buzzer from "./buzzer.mjs"
|
||||
import * as Inputs from "./inputs.mjs"
|
||||
import * as Repeaters from "./repeaters.mjs"
|
||||
|
@ -7,22 +7,20 @@ import * as Chart from "./chart.mjs"
|
|||
const DefaultRepeater = "General"
|
||||
const Millisecond = 1
|
||||
const Second = 1000 * Millisecond
|
||||
const Minute = 60 * Second
|
||||
|
||||
/**
|
||||
* Pop up a message, using an MDL snackbar.
|
||||
* Pop up a message, using an notification..
|
||||
*
|
||||
* @param {string} msg Message to display
|
||||
*/
|
||||
function toast(msg) {
|
||||
function toast(msg, timeout=4*Second) {
|
||||
console.info(msg)
|
||||
let el = document.querySelector("#snackbar")
|
||||
if (!el || !el.MaterialSnackbar) {
|
||||
return
|
||||
}
|
||||
el.MaterialSnackbar.showSnackbar({
|
||||
message: msg,
|
||||
timeout: 2000
|
||||
})
|
||||
|
||||
let errors = document.querySelector("#errors")
|
||||
let p = errors.appendChild(document.createElement("p"))
|
||||
p.textContent = msg
|
||||
setTimeout(() => p.remove(), timeout)
|
||||
}
|
||||
|
||||
// iOS kludge
|
||||
|
@ -40,21 +38,16 @@ class VailClient {
|
|||
this.beginTxTime = null // Time when we began transmitting
|
||||
|
||||
// Make helpers
|
||||
this.lamp = new Buzzer.Lamp()
|
||||
this.lamp = new Buzzer.Lamp(document.querySelector("#recv"))
|
||||
this.buzzer = new Buzzer.ToneBuzzer()
|
||||
this.straightKeyer = new Keyer.StraightKeyer(() => this.beginTx(), () => this.endTx())
|
||||
this.keyer = new Keyer.SingleDotKeyer(() => this.beginTx(), () => this.endTx())
|
||||
this.roboKeyer = new Keyer.ElBugKeyer(() => this.Buzz(), () => this.Silence())
|
||||
this.straightKeyer = new Keyers.straight(() => this.beginTx(), () => this.endTx())
|
||||
this.keyer = new Keyers.straight(() => this.beginTx(), () => this.endTx())
|
||||
this.roboKeyer = new Keyers.robo(() => this.Buzz(), () => this.Silence())
|
||||
|
||||
// Set up various input methods
|
||||
// Send this as the keyer so we can intercept dit and dah events for charts
|
||||
this.inputs = Inputs.SetupAll(this)
|
||||
|
||||
// VBand: Keep track of how the user wants the single key to behave
|
||||
for (let e of document.querySelectorAll("[data-singlekey]")) {
|
||||
e.addEventListener("click", e => this.singlekeyChange(e))
|
||||
}
|
||||
|
||||
// Maximize button
|
||||
for (let e of document.querySelectorAll("button.maximize")) {
|
||||
e.addEventListener("click", e => this.maximize(e))
|
||||
|
@ -67,39 +60,36 @@ class VailClient {
|
|||
}
|
||||
|
||||
// Set up inputs
|
||||
this.inputInit("#iambic-duration", e => {
|
||||
this.keyer.SetDitDuration(e.target.value)
|
||||
this.roboKeyer.SetDitDuration(e.target.value)
|
||||
this.inputInit("#keyer-mode", e => this.setKeyer(e.target.value))
|
||||
this.inputInit("#keyer-rate", e => {
|
||||
let rate = e.target.value
|
||||
let ditDuration = Minute / rate / 50
|
||||
this.keyer.SetDitDuration(ditDuration)
|
||||
this.roboKeyer.SetDitDuration(ditDuration)
|
||||
for (let i of Object.values(this.inputs)) {
|
||||
i.SetDitDuration(e.target.value)
|
||||
i.SetDitDuration(ditDuration)
|
||||
}
|
||||
})
|
||||
this.inputInit("#rx-delay", e => {
|
||||
this.rxDelay = Number(e.target.value)
|
||||
})
|
||||
this.inputInit("#iambic-mode-b", e => {
|
||||
this.keyer.SetIambicModeB(e.target.checked)
|
||||
})
|
||||
this.inputInit("#iambic-typeahead", e => {
|
||||
this.keyer.SetTypeahead(e.target.checked)
|
||||
})
|
||||
this.inputInit("#telegraph-buzzer", e => {
|
||||
this.setTelegraphBuzzer(e.target.checked)
|
||||
})
|
||||
this.inputInit("#timing-chart", e => {
|
||||
this.setTimingCharts(e.target.checked)
|
||||
})
|
||||
|
||||
this.inputInit("#notes")
|
||||
|
||||
// Fill in the name of our repeater
|
||||
document.querySelector("#repeater").addEventListener("change", e => this.setRepeater(e.target.value.trim()))
|
||||
window.addEventListener("hashchange", () => this.hashchange())
|
||||
this.hashchange()
|
||||
|
||||
this.setTimingCharts(true)
|
||||
|
||||
// Turn off the "muted" symbol when we can start making noise
|
||||
Buzzer.Ready()
|
||||
.then(() => {
|
||||
console.log("Audio context ready")
|
||||
document.querySelector("#muted").classList.add("hidden")
|
||||
document.querySelector("#muted").classList.add("is-hidden")
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -110,27 +100,36 @@ class VailClient {
|
|||
*/
|
||||
Straight(down) {
|
||||
this.straightKeyer.Key(0, down)
|
||||
if (this.straightChart) this.straightChart.Set(down?1:0)
|
||||
}
|
||||
|
||||
/**
|
||||
* Dit key change (keyer shim)
|
||||
* Key/paddle change
|
||||
*
|
||||
* @param down If the key has been depressed
|
||||
* @param {Number} key Key which was pressed
|
||||
* @param {Boolean} down True if key was pressed
|
||||
*/
|
||||
Dit(down) {
|
||||
this.keyer.Key(0, down)
|
||||
if (this.ditChart) this.ditChart.Set(down?1:0)
|
||||
Key(key, down) {
|
||||
this.keyer.Key(key, down)
|
||||
if (this.keyCharts) this.keyCharts[key].Set(down?1:0)
|
||||
}
|
||||
|
||||
/**
|
||||
* Dah key change (keyer shim)
|
||||
*
|
||||
* @param down If the key has been depressed
|
||||
*/
|
||||
Dah(down) {
|
||||
this.keyer.Key(1, down)
|
||||
if (this.dahChart) this.dahChart.Set(down?1:0)
|
||||
setKeyer(keyerName) {
|
||||
let newKeyerClass = Keyers[keyerName]
|
||||
if (!newKeyerClass) {
|
||||
console.error("Keyer not found", keyerName)
|
||||
return
|
||||
}
|
||||
let newKeyer = new newKeyerClass(() => this.beginTx(), () => this.endTx())
|
||||
let i = 0
|
||||
for (let keyName of newKeyer.KeyNames()) {
|
||||
let e = document.querySelector(`.key[data-key="${i}"]`)
|
||||
e.textContent = keyName
|
||||
i += 1
|
||||
}
|
||||
this.keyer.Release()
|
||||
this.keyer = newKeyer
|
||||
|
||||
document.querySelector("#keyer-rate").dispatchEvent(new Event("input"))
|
||||
}
|
||||
|
||||
Buzz() {
|
||||
|
@ -196,14 +195,15 @@ class VailClient {
|
|||
let chartsContainer = document.querySelector("#charts")
|
||||
if (enable) {
|
||||
chartsContainer.classList.remove("hidden")
|
||||
this.ditChart = Chart.FromSelector("#ditChart")
|
||||
this.dahChart = Chart.FromSelector("#dahChart")
|
||||
this.keyCharts = [
|
||||
Chart.FromSelector("#key0Chart"),
|
||||
Chart.FromSelector("#key1Chart")
|
||||
]
|
||||
this.txChart = Chart.FromSelector("#txChart")
|
||||
this.rxChart = Chart.FromSelector("#rxChart")
|
||||
} else {
|
||||
chartsContainer.classList.add("hidden")
|
||||
this.ditChart = null
|
||||
this.dahChart = null
|
||||
this.keyCharts = []
|
||||
this.txChart = null
|
||||
this.rxChart = null
|
||||
}
|
||||
|
@ -232,19 +232,6 @@ class VailClient {
|
|||
this.setRepeater(decodeURIComponent(hashParts[1] || ""))
|
||||
}
|
||||
|
||||
/**
|
||||
* VBand: Called when something happens to change what a single key does
|
||||
*
|
||||
* @param {Event} event What caused this
|
||||
*/
|
||||
singlekeyChange(event) {
|
||||
for (let e of event.composedPath()) {
|
||||
if (e.dataset && e.dataset.singlekey) {
|
||||
this.inputs.Keyboard.iambic = (e.dataset.singlekey == "iambic")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect to a repeater by name.
|
||||
*
|
||||
|
@ -326,8 +313,8 @@ class VailClient {
|
|||
element.value = storedValue
|
||||
element.checked = (storedValue == "on")
|
||||
}
|
||||
let outputElement = document.querySelector(selector + "-value")
|
||||
let outputWpmElement = document.querySelector(selector + "-wpm")
|
||||
let id = element.id
|
||||
let outputElement = document.querySelector(`[for="${id}"]`)
|
||||
|
||||
element.addEventListener("input", e => {
|
||||
let value = element.value
|
||||
|
@ -339,9 +326,6 @@ class VailClient {
|
|||
if (outputElement) {
|
||||
outputElement.value = value
|
||||
}
|
||||
if (outputWpmElement) {
|
||||
outputWpmElement.value = (1200 / value).toFixed(1)
|
||||
}
|
||||
if (callback) {
|
||||
callback(e)
|
||||
}
|
||||
|
@ -388,7 +372,7 @@ class VailClient {
|
|||
let longestRxDuration = this.rxDurations.reduce((a,b) => Math.max(a,b))
|
||||
let suggestedDelay = ((averageLag + longestRxDuration) * 1.2).toFixed(0)
|
||||
|
||||
this.updateReading("#note", stats.note || "")
|
||||
this.updateReading("#note", stats.note || "☁")
|
||||
this.updateReading("#lag-value", averageLag)
|
||||
this.updateReading("#longest-rx-value", longestRxDuration)
|
||||
this.updateReading("#suggested-delay-value", suggestedDelay)
|
||||
|
@ -457,7 +441,7 @@ class VailClient {
|
|||
}
|
||||
}
|
||||
|
||||
function vailInit() {
|
||||
function init() {
|
||||
if (navigator.serviceWorker) {
|
||||
navigator.serviceWorker.register("sw.js")
|
||||
}
|
||||
|
@ -471,9 +455,9 @@ function vailInit() {
|
|||
|
||||
|
||||
if (document.readyState === "loading") {
|
||||
document.addEventListener("DOMContentLoaded", vailInit)
|
||||
document.addEventListener("DOMContentLoaded", init)
|
||||
} else {
|
||||
vailInit()
|
||||
init()
|
||||
}
|
||||
|
||||
// vim: noet sw=2 ts=2
|
||||
|
|
Loading…
Reference in New Issue