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 {
|
class Lamp extends Buzzer {
|
||||||
constructor() {
|
constructor(element) {
|
||||||
super()
|
super()
|
||||||
this.lamp = document.querySelector("#recv")
|
this.element = element
|
||||||
}
|
}
|
||||||
|
|
||||||
Buzz(tx, when=0) {
|
Buzz(tx, when=0) {
|
||||||
if (tx) return
|
if (tx) return
|
||||||
|
|
||||||
let ms = when - Date.now()
|
let ms = when?when - Date.now():0
|
||||||
setTimeout(e => {
|
setTimeout(
|
||||||
recv.classList.add("rx")
|
() =>{
|
||||||
}, ms)
|
this.element.classList.add("rx")
|
||||||
|
},
|
||||||
|
ms,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
Silence(tx, when=0) {
|
Silence(tx, when=0) {
|
||||||
if (tx) return
|
if (tx) return
|
||||||
|
|
||||||
let recv = document.querySelector("#recv")
|
let ms = when?when - Date.now():0
|
||||||
let ms = when - Date.now()
|
setTimeout(() => this.element.classList.remove("rx"), ms)
|
||||||
setTimeout(e => {
|
|
||||||
recv.classList.remove("rx")
|
|
||||||
}, ms)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,296 +8,247 @@
|
||||||
<!-- Bulma CSS -->
|
<!-- 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/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">
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@mdi/font@6.5.95/css/materialdesignicons.min.css">
|
||||||
|
|
||||||
<!-- Vail stuff -->
|
<!-- Vail stuff -->
|
||||||
<link rel="manifest" href="manifest.json">
|
<link rel="manifest" href="manifest.json">
|
||||||
<link rel="icon" href="vail.png" sizes="256x256" type="image/png">
|
<link rel="icon" href="vail.png" sizes="256x256" type="image/png">
|
||||||
<link rel="icon" href="vail.svg" sizes="any" type="image/svg+xml">
|
<link rel="icon" href="vail.svg" sizes="any" type="image/svg+xml">
|
||||||
<script type="module" src="vail.mjs"></script>
|
<script type="module" src="vail.mjs"></script>
|
||||||
|
<script type="module" src="ui.mjs"></script>
|
||||||
<link rel="stylesheet" href="vail.css">
|
<link rel="stylesheet" href="vail.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<nav class="navbar">
|
<nav class="navbar is-dark">
|
||||||
<div class="navbar-brand">
|
<div class="navbar-brand">
|
||||||
<a class="navbar-item">
|
<a class="navbar-item">
|
||||||
<img src="vail.svg" alt="">
|
<img class="" src="vail.svg" alt="">
|
||||||
Vail
|
<div class="block">Vail</div>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="navbar-menu">
|
<div class="navbar-menu">
|
||||||
<div class="navbar-end">
|
<div class="navbar-end">
|
||||||
<div class="navbar-item">
|
<a class="navbar-item" href="https://github.com/nealey/vail/">Source Code</a>
|
||||||
<a href="https://github.com/nealey/vail/">Source Code</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<section class="section">
|
<section class="section">
|
||||||
<div class="container">
|
<div class="columns">
|
||||||
<div class="field">
|
<div class="column">
|
||||||
<label class="mdl-textfield__label" for="repeater">Repeater</label>
|
<div class="box" id="transciever">
|
||||||
<div class="control">
|
<h1 class="title">Repeater</h1>
|
||||||
<input class="mdl-textfield__input" type="text" id="repeater" list="repeater-list">
|
<div class="level">
|
||||||
<datalist id="repeater-list">
|
<div class="level-left">
|
||||||
<option>General</option>
|
<div class="level-item">
|
||||||
<option value="1">Channel 1</option>
|
|
||||||
<option value="2">Channel 2</option>
|
<div class="field is-horizontal">
|
||||||
<option value="3">Channel 3</option>
|
<div class="field-label is-hidden">
|
||||||
<option value="Null">No transmit</option>
|
<label class="label" for="repeater">Repeater</label>
|
||||||
<option>Echo</option>
|
</div>
|
||||||
<option>Echo 5s</option>
|
<div class="field-body">
|
||||||
<option>Echo 10s</option>
|
<div class="field">
|
||||||
<option>Fortunes</option>
|
<div class="control">
|
||||||
<option>Fortunes: Pauses ×2</option>
|
<input class="input" type="text" id="repeater" list="repeater-list">
|
||||||
<option>Fortunes: Pauses ×4</option>
|
<datalist id="repeater-list">
|
||||||
<option>Fortunes: Pauses ×8</option>
|
<option>General</option>
|
||||||
</datalist>
|
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</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="columns is-centered bottom">
|
||||||
<div class="flex">
|
<div class="column is-half" id="errors"></div>
|
||||||
<div class="mdl-card mdl-shadow--4dp input-methods mashable-area">
|
</div>
|
||||||
<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>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
<!-- vim: set noet ts=2 sw=2 : -->
|
<!-- vim: set noet ts=2 sw=2 : -->
|
||||||
|
|
|
@ -27,24 +27,14 @@ export class HTML extends Input{
|
||||||
|
|
||||||
keyButton(event) {
|
keyButton(event) {
|
||||||
let begin = event.type.endsWith("down") || event.type.endsWith("start")
|
let begin = event.type.endsWith("down") || event.type.endsWith("start")
|
||||||
|
let key = event.target.dataset.key
|
||||||
if (event.target.id == "dah") {
|
|
||||||
if (event.button == 2) {
|
// Button 2 does the other key (assuming 2 keys)
|
||||||
this.keyer.Dit(begin)
|
if (event.button == 2) {
|
||||||
} else {
|
key = 1 - key
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
this.keyer.Key(key, begin)
|
||||||
|
|
||||||
if (event.cancelable) {
|
if (event.cancelable) {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
}
|
}
|
||||||
|
@ -80,7 +70,7 @@ export class Keyboard extends Input{
|
||||||
) {
|
) {
|
||||||
// Dit
|
// Dit
|
||||||
if (this.ditDown != down) {
|
if (this.ditDown != down) {
|
||||||
this.keyer.Dit(down)
|
this.keyer.Key(0, down)
|
||||||
this.ditDown = down
|
this.ditDown = down
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -92,7 +82,7 @@ export class Keyboard extends Input{
|
||||||
|| (event.key == "]")
|
|| (event.key == "]")
|
||||||
) {
|
) {
|
||||||
if (this.dahDown != down) {
|
if (this.dahDown != down) {
|
||||||
this.keyer.Dah(down)
|
this.keyer.Key(1, down)
|
||||||
this.dahDown = down
|
this.dahDown = down
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -173,10 +163,10 @@ export class MIDI extends Input{
|
||||||
this.keyer.Straight(begin)
|
this.keyer.Straight(begin)
|
||||||
break
|
break
|
||||||
case 1: // C#
|
case 1: // C#
|
||||||
this.keyer.Dit(begin)
|
this.keyer.Key(0, begin)
|
||||||
break
|
break
|
||||||
case 2: // D
|
case 2: // D
|
||||||
this.keyer.Dah(begin)
|
this.keyer.Key(1, begin)
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
return
|
return
|
||||||
|
@ -229,10 +219,10 @@ export class Gamepad extends Input{
|
||||||
this.keyer.Straight(currentButtons.key)
|
this.keyer.Straight(currentButtons.key)
|
||||||
}
|
}
|
||||||
if (currentButtons.dit != this.gamepadButtons.dit) {
|
if (currentButtons.dit != this.gamepadButtons.dit) {
|
||||||
this.keyer.Dit(currentButtons.dit)
|
this.keyer.Key(0, currentButtons.dit)
|
||||||
}
|
}
|
||||||
if (currentButtons.dah != this.gamepadButtons.dah) {
|
if (currentButtons.dah != this.gamepadButtons.dah) {
|
||||||
this.keyer.Dah(currentButtons.dah)
|
this.keyer.Key(1, currentButtons.dah)
|
||||||
}
|
}
|
||||||
this.gamepadButtons = currentButtons
|
this.gamepadButtons = currentButtons
|
||||||
|
|
||||||
|
|
|
@ -6,10 +6,8 @@
|
||||||
* advanced than the bug keyer.
|
* advanced than the bug keyer.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/** Silent period between words */
|
import * as RoboKeyer from "./robokeyer.mjs"
|
||||||
const PAUSE_WORD = -7
|
|
||||||
/** Silent period between letters */
|
|
||||||
const PAUSE_LETTER = -3
|
|
||||||
/** Silent period between dits and dash */
|
/** Silent period between dits and dash */
|
||||||
const PAUSE = -1
|
const PAUSE = -1
|
||||||
/** Length of a dit */
|
/** Length of a dit */
|
||||||
|
@ -34,78 +32,6 @@ const Minute = 60 * Second
|
||||||
/** @type {Duration} */
|
/** @type {Duration} */
|
||||||
const Hour = 60 * Minute
|
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.
|
* Queue Set: A Set you can shift and pop.
|
||||||
|
@ -167,6 +93,20 @@ class StraightKeyer {
|
||||||
this.txRelays = []
|
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.
|
* Returns the state of a single transmit relay.
|
||||||
*
|
*
|
||||||
|
@ -235,7 +175,7 @@ class CootieKeyer extends StraightKeyer {
|
||||||
*/
|
*/
|
||||||
class BugKeyer extends StraightKeyer {
|
class BugKeyer extends StraightKeyer {
|
||||||
KeyNames() {
|
KeyNames() {
|
||||||
return ["· ", "Key"]
|
return ["Dit ", "Key"]
|
||||||
}
|
}
|
||||||
|
|
||||||
Reset() {
|
Reset() {
|
||||||
|
@ -248,15 +188,6 @@ class BugKeyer extends StraightKeyer {
|
||||||
this.keyPressed = [false, false]
|
this.keyPressed = [false, false]
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the duration of dit.
|
|
||||||
*
|
|
||||||
* @param {Duration} d New dit duration
|
|
||||||
*/
|
|
||||||
SetDitDuration(d) {
|
|
||||||
this.ditDuration = d
|
|
||||||
}
|
|
||||||
|
|
||||||
Key(key, pressed) {
|
Key(key, pressed) {
|
||||||
this.keyPressed[key] = pressed
|
this.keyPressed[key] = pressed
|
||||||
if (key == 0) {
|
if (key == 0) {
|
||||||
|
@ -302,7 +233,7 @@ class BugKeyer extends StraightKeyer {
|
||||||
*/
|
*/
|
||||||
class ElBugKeyer extends BugKeyer {
|
class ElBugKeyer extends BugKeyer {
|
||||||
KeyNames() {
|
KeyNames() {
|
||||||
return ["· ", "−"]
|
return ["Dit ", "Dah"]
|
||||||
}
|
}
|
||||||
|
|
||||||
Reset() {
|
Reset() {
|
||||||
|
@ -499,281 +430,47 @@ class IambicBKeyer extends IambicKeyer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
class KeyaheadKeyer extends ElBugKeyer {
|
||||||
* Keyer class. This handles iambic and straight key input.
|
Reset() {
|
||||||
*
|
super.Reset()
|
||||||
* 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
|
|
||||||
this.queue = []
|
this.queue = []
|
||||||
this.pulseTimer = null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pulse() {
|
Key(key, pressed) {
|
||||||
if (this.queue.length == 0) {
|
if (pressed) {
|
||||||
let next = this.typematic()
|
this.queue.push(key)
|
||||||
if (next) {
|
|
||||||
// Barkeep! Another round!
|
|
||||||
this.Enqueue(next)
|
|
||||||
} else {
|
|
||||||
// Nothing left on the queue, stop the machine
|
|
||||||
this.pulseTimer = null
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
super.Key(key, pressed)
|
||||||
|
}
|
||||||
|
|
||||||
|
nextTx() {
|
||||||
let next = this.queue.shift()
|
let next = this.queue.shift()
|
||||||
if (next < 0) {
|
if (next != null) {
|
||||||
next *= -1
|
return next
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
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) {
|
* A dictionary of all available keyers
|
||||||
this.modeBQueue = this.last
|
*/
|
||||||
this.last = not(this.last)
|
const Keyers = {
|
||||||
} else if (this.ditDown) {
|
straight: StraightKeyer,
|
||||||
this.modeBQueue = null
|
cootie: CootieKeyer,
|
||||||
this.last = DIT
|
bug: BugKeyer,
|
||||||
} else if (this.dahDown) {
|
elbug: ElBugKeyer,
|
||||||
this.modeBQueue = null
|
singledot: SingleDotKeyer,
|
||||||
this.last = DAH
|
ultimatic: UltimaticKeyer,
|
||||||
} else if (this.modeBQueue && this.iambicModeB) {
|
iambic: IambicKeyer,
|
||||||
this.last = this.modeBQueue
|
iambica: IambicAKeyer,
|
||||||
this.modeBQueue = null
|
iambicb: IambicBKeyer,
|
||||||
} else {
|
keyahead: KeyaheadKeyer,
|
||||||
this.last = null
|
|
||||||
this.modeBQueue = null
|
|
||||||
}
|
|
||||||
return this.last
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
robo: RoboKeyer.Keyer,
|
||||||
* 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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
StraightKeyer,
|
Keyers,
|
||||||
CootieKeyer, BugKeyer, ElBugKeyer,
|
|
||||||
SingleDotKeyer, UltimaticKeyer,
|
|
||||||
IambicKeyer, IambicAKeyer, IambicBKeyer,
|
|
||||||
}
|
}
|
|
@ -124,11 +124,10 @@ export class Vail {
|
||||||
export class Null {
|
export class Null {
|
||||||
constructor(rx) {
|
constructor(rx) {
|
||||||
this.rx = rx
|
this.rx = rx
|
||||||
this.interval = setInterval(() => this.pulse(), 1 * Second)
|
this.interval = setInterval(() => this.pulse(), 3 * Second)
|
||||||
}
|
}
|
||||||
|
|
||||||
pulse() {
|
pulse() {
|
||||||
console.log("pulse")
|
|
||||||
this.rx(0, 0, {note: "local"})
|
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 {
|
.key {
|
||||||
width: 100%;
|
width: 95%;
|
||||||
height: 6em;
|
height: 6em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wide {
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mashable-area {
|
.mashable-area {
|
||||||
|
@ -8,43 +16,39 @@
|
||||||
-webkit-user-select: none; /* 2022-04-26 Safari still needs this */
|
-webkit-user-select: none; /* 2022-04-26 Safari still needs this */
|
||||||
}
|
}
|
||||||
|
|
||||||
.center {
|
#recv.rx {
|
||||||
text-align: center;
|
background-color: orange;
|
||||||
}
|
}
|
||||||
|
|
||||||
.maximize {
|
input[type=range] {
|
||||||
position: absolute;
|
height: 25px;
|
||||||
|
margin: 10px 0;
|
||||||
|
width: 100%;
|
||||||
|
accent-color: #00d1b2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottom {
|
||||||
|
position: fixed;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
right: 0;
|
width: 100vw;
|
||||||
}
|
}
|
||||||
.maximized .key {
|
|
||||||
height: 90vh;
|
|
||||||
}
|
|
||||||
.maximized {
|
|
||||||
width: 90vw;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wide {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hidden {
|
|
||||||
visibility: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
#errors {
|
#errors {
|
||||||
color: rgba(127, 0, 0, .54);
|
max-height: 10em;
|
||||||
max-height: 5em;
|
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes yellow-fade {
|
@keyframes yellow-fade {
|
||||||
0% {background: yellow;}
|
0% {background-color: orange;}
|
||||||
100% {background: none;}
|
100% {background-color: default;}
|
||||||
}
|
}
|
||||||
#errors p {
|
#errors p {
|
||||||
margin: 0;
|
background-color: #444;
|
||||||
animation: yellow-fade 2s ease-in 1;
|
color: white;
|
||||||
|
margin: 0.5em;
|
||||||
|
padding: 0.2em;
|
||||||
|
border-radius: 4px;
|
||||||
|
text-align: center;
|
||||||
|
animation: yellow-fade 0.3s ease-in 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
kbd {
|
kbd {
|
||||||
|
@ -72,78 +76,6 @@ code {
|
||||||
padding: 0.1em;
|
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 {
|
#charts {
|
||||||
line-height: 0;
|
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 Buzzer from "./buzzer.mjs"
|
||||||
import * as Inputs from "./inputs.mjs"
|
import * as Inputs from "./inputs.mjs"
|
||||||
import * as Repeaters from "./repeaters.mjs"
|
import * as Repeaters from "./repeaters.mjs"
|
||||||
|
@ -7,22 +7,20 @@ import * as Chart from "./chart.mjs"
|
||||||
const DefaultRepeater = "General"
|
const DefaultRepeater = "General"
|
||||||
const Millisecond = 1
|
const Millisecond = 1
|
||||||
const Second = 1000 * Millisecond
|
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
|
* @param {string} msg Message to display
|
||||||
*/
|
*/
|
||||||
function toast(msg) {
|
function toast(msg, timeout=4*Second) {
|
||||||
console.info(msg)
|
console.info(msg)
|
||||||
let el = document.querySelector("#snackbar")
|
|
||||||
if (!el || !el.MaterialSnackbar) {
|
let errors = document.querySelector("#errors")
|
||||||
return
|
let p = errors.appendChild(document.createElement("p"))
|
||||||
}
|
p.textContent = msg
|
||||||
el.MaterialSnackbar.showSnackbar({
|
setTimeout(() => p.remove(), timeout)
|
||||||
message: msg,
|
|
||||||
timeout: 2000
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// iOS kludge
|
// iOS kludge
|
||||||
|
@ -40,21 +38,16 @@ class VailClient {
|
||||||
this.beginTxTime = null // Time when we began transmitting
|
this.beginTxTime = null // Time when we began transmitting
|
||||||
|
|
||||||
// Make helpers
|
// Make helpers
|
||||||
this.lamp = new Buzzer.Lamp()
|
this.lamp = new Buzzer.Lamp(document.querySelector("#recv"))
|
||||||
this.buzzer = new Buzzer.ToneBuzzer()
|
this.buzzer = new Buzzer.ToneBuzzer()
|
||||||
this.straightKeyer = new Keyer.StraightKeyer(() => this.beginTx(), () => this.endTx())
|
this.straightKeyer = new Keyers.straight(() => this.beginTx(), () => this.endTx())
|
||||||
this.keyer = new Keyer.SingleDotKeyer(() => this.beginTx(), () => this.endTx())
|
this.keyer = new Keyers.straight(() => this.beginTx(), () => this.endTx())
|
||||||
this.roboKeyer = new Keyer.ElBugKeyer(() => this.Buzz(), () => this.Silence())
|
this.roboKeyer = new Keyers.robo(() => this.Buzz(), () => this.Silence())
|
||||||
|
|
||||||
// Set up various input methods
|
// Set up various input methods
|
||||||
// Send this as the keyer so we can intercept dit and dah events for charts
|
// Send this as the keyer so we can intercept dit and dah events for charts
|
||||||
this.inputs = Inputs.SetupAll(this)
|
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
|
// Maximize button
|
||||||
for (let e of document.querySelectorAll("button.maximize")) {
|
for (let e of document.querySelectorAll("button.maximize")) {
|
||||||
e.addEventListener("click", e => this.maximize(e))
|
e.addEventListener("click", e => this.maximize(e))
|
||||||
|
@ -67,39 +60,36 @@ class VailClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set up inputs
|
// Set up inputs
|
||||||
this.inputInit("#iambic-duration", e => {
|
this.inputInit("#keyer-mode", e => this.setKeyer(e.target.value))
|
||||||
this.keyer.SetDitDuration(e.target.value)
|
this.inputInit("#keyer-rate", e => {
|
||||||
this.roboKeyer.SetDitDuration(e.target.value)
|
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)) {
|
for (let i of Object.values(this.inputs)) {
|
||||||
i.SetDitDuration(e.target.value)
|
i.SetDitDuration(ditDuration)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
this.inputInit("#rx-delay", e => {
|
this.inputInit("#rx-delay", e => {
|
||||||
this.rxDelay = Number(e.target.value)
|
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.inputInit("#telegraph-buzzer", e => {
|
||||||
this.setTelegraphBuzzer(e.target.checked)
|
this.setTelegraphBuzzer(e.target.checked)
|
||||||
})
|
})
|
||||||
this.inputInit("#timing-chart", e => {
|
this.inputInit("#notes")
|
||||||
this.setTimingCharts(e.target.checked)
|
|
||||||
})
|
|
||||||
|
|
||||||
// Fill in the name of our repeater
|
// Fill in the name of our repeater
|
||||||
document.querySelector("#repeater").addEventListener("change", e => this.setRepeater(e.target.value.trim()))
|
document.querySelector("#repeater").addEventListener("change", e => this.setRepeater(e.target.value.trim()))
|
||||||
window.addEventListener("hashchange", () => this.hashchange())
|
window.addEventListener("hashchange", () => this.hashchange())
|
||||||
this.hashchange()
|
this.hashchange()
|
||||||
|
|
||||||
|
this.setTimingCharts(true)
|
||||||
|
|
||||||
// Turn off the "muted" symbol when we can start making noise
|
// Turn off the "muted" symbol when we can start making noise
|
||||||
Buzzer.Ready()
|
Buzzer.Ready()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
console.log("Audio context ready")
|
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) {
|
Straight(down) {
|
||||||
this.straightKeyer.Key(0, 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) {
|
Key(key, down) {
|
||||||
this.keyer.Key(0, down)
|
this.keyer.Key(key, down)
|
||||||
if (this.ditChart) this.ditChart.Set(down?1:0)
|
if (this.keyCharts) this.keyCharts[key].Set(down?1:0)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
setKeyer(keyerName) {
|
||||||
* Dah key change (keyer shim)
|
let newKeyerClass = Keyers[keyerName]
|
||||||
*
|
if (!newKeyerClass) {
|
||||||
* @param down If the key has been depressed
|
console.error("Keyer not found", keyerName)
|
||||||
*/
|
return
|
||||||
Dah(down) {
|
}
|
||||||
this.keyer.Key(1, down)
|
let newKeyer = new newKeyerClass(() => this.beginTx(), () => this.endTx())
|
||||||
if (this.dahChart) this.dahChart.Set(down?1:0)
|
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() {
|
Buzz() {
|
||||||
|
@ -196,14 +195,15 @@ class VailClient {
|
||||||
let chartsContainer = document.querySelector("#charts")
|
let chartsContainer = document.querySelector("#charts")
|
||||||
if (enable) {
|
if (enable) {
|
||||||
chartsContainer.classList.remove("hidden")
|
chartsContainer.classList.remove("hidden")
|
||||||
this.ditChart = Chart.FromSelector("#ditChart")
|
this.keyCharts = [
|
||||||
this.dahChart = Chart.FromSelector("#dahChart")
|
Chart.FromSelector("#key0Chart"),
|
||||||
|
Chart.FromSelector("#key1Chart")
|
||||||
|
]
|
||||||
this.txChart = Chart.FromSelector("#txChart")
|
this.txChart = Chart.FromSelector("#txChart")
|
||||||
this.rxChart = Chart.FromSelector("#rxChart")
|
this.rxChart = Chart.FromSelector("#rxChart")
|
||||||
} else {
|
} else {
|
||||||
chartsContainer.classList.add("hidden")
|
chartsContainer.classList.add("hidden")
|
||||||
this.ditChart = null
|
this.keyCharts = []
|
||||||
this.dahChart = null
|
|
||||||
this.txChart = null
|
this.txChart = null
|
||||||
this.rxChart = null
|
this.rxChart = null
|
||||||
}
|
}
|
||||||
|
@ -232,19 +232,6 @@ class VailClient {
|
||||||
this.setRepeater(decodeURIComponent(hashParts[1] || ""))
|
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.
|
* Connect to a repeater by name.
|
||||||
*
|
*
|
||||||
|
@ -326,8 +313,8 @@ class VailClient {
|
||||||
element.value = storedValue
|
element.value = storedValue
|
||||||
element.checked = (storedValue == "on")
|
element.checked = (storedValue == "on")
|
||||||
}
|
}
|
||||||
let outputElement = document.querySelector(selector + "-value")
|
let id = element.id
|
||||||
let outputWpmElement = document.querySelector(selector + "-wpm")
|
let outputElement = document.querySelector(`[for="${id}"]`)
|
||||||
|
|
||||||
element.addEventListener("input", e => {
|
element.addEventListener("input", e => {
|
||||||
let value = element.value
|
let value = element.value
|
||||||
|
@ -339,9 +326,6 @@ class VailClient {
|
||||||
if (outputElement) {
|
if (outputElement) {
|
||||||
outputElement.value = value
|
outputElement.value = value
|
||||||
}
|
}
|
||||||
if (outputWpmElement) {
|
|
||||||
outputWpmElement.value = (1200 / value).toFixed(1)
|
|
||||||
}
|
|
||||||
if (callback) {
|
if (callback) {
|
||||||
callback(e)
|
callback(e)
|
||||||
}
|
}
|
||||||
|
@ -388,7 +372,7 @@ class VailClient {
|
||||||
let longestRxDuration = this.rxDurations.reduce((a,b) => Math.max(a,b))
|
let longestRxDuration = this.rxDurations.reduce((a,b) => Math.max(a,b))
|
||||||
let suggestedDelay = ((averageLag + longestRxDuration) * 1.2).toFixed(0)
|
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("#lag-value", averageLag)
|
||||||
this.updateReading("#longest-rx-value", longestRxDuration)
|
this.updateReading("#longest-rx-value", longestRxDuration)
|
||||||
this.updateReading("#suggested-delay-value", suggestedDelay)
|
this.updateReading("#suggested-delay-value", suggestedDelay)
|
||||||
|
@ -457,7 +441,7 @@ class VailClient {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function vailInit() {
|
function init() {
|
||||||
if (navigator.serviceWorker) {
|
if (navigator.serviceWorker) {
|
||||||
navigator.serviceWorker.register("sw.js")
|
navigator.serviceWorker.register("sw.js")
|
||||||
}
|
}
|
||||||
|
@ -471,9 +455,9 @@ function vailInit() {
|
||||||
|
|
||||||
|
|
||||||
if (document.readyState === "loading") {
|
if (document.readyState === "loading") {
|
||||||
document.addEventListener("DOMContentLoaded", vailInit)
|
document.addEventListener("DOMContentLoaded", init)
|
||||||
} else {
|
} else {
|
||||||
vailInit()
|
init()
|
||||||
}
|
}
|
||||||
|
|
||||||
// vim: noet sw=2 ts=2
|
// vim: noet sw=2 ts=2
|
||||||
|
|
Loading…
Reference in New Issue