mirror of https://github.com/nealey/vail.git
Working up to single dot
This commit is contained in:
parent
af21b30afc
commit
4950042e6c
|
@ -0,0 +1,20 @@
|
||||||
|
/**
|
||||||
|
* A time duration.
|
||||||
|
*
|
||||||
|
* JavaScript uses milliseconds in most (but not all) places.
|
||||||
|
* I've found it helpful to be able to multiply by a unit, so it's clear what's going on.
|
||||||
|
*
|
||||||
|
* @typedef {number} Duration
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** @type {Duration} */
|
||||||
|
export const Millisecond = 1
|
||||||
|
|
||||||
|
/** @type {Duration} */
|
||||||
|
export const Second = 1000 * Millisecond
|
||||||
|
|
||||||
|
/** @type {Duration} */
|
||||||
|
export const Minute = 60 * Second
|
||||||
|
|
||||||
|
/** @type {Duration} */
|
||||||
|
export const Hour = 60 * Minute
|
|
@ -5,10 +5,9 @@
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
|
||||||
<!-- Material Design Lite -->
|
<!-- Bulma CSS -->
|
||||||
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.3/css/bulma.min.css">
|
||||||
<link rel="stylesheet" href="https://code.getmdl.io/1.3.0/material.teal-purple.min.css">
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@mdi/font@6.5.95/css/materialdesignicons.min.css">
|
||||||
<script defer src="https://code.getmdl.io/1.3.0/material.min.js"></script>
|
|
||||||
|
|
||||||
<!-- Vail stuff -->
|
<!-- Vail stuff -->
|
||||||
<link rel="manifest" href="manifest.json">
|
<link rel="manifest" href="manifest.json">
|
||||||
|
@ -18,50 +17,46 @@
|
||||||
<link rel="stylesheet" href="vail.css">
|
<link rel="stylesheet" href="vail.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="mdl-layout mdl-js-layout">
|
<nav class="navbar">
|
||||||
<header class="mdl-layout__header mdl-layout__header--scroll">
|
<div class="navbar-brand">
|
||||||
<div class="mdl-layout__header-row">
|
<a class="navbar-item">
|
||||||
<!-- Title -->
|
<img src="vail.svg" alt="">
|
||||||
<span class="mdl-layout-title">Vail</span>
|
Vail
|
||||||
<!-- Add spacer, to align navigation to the right -->
|
</a>
|
||||||
<div class="mdl-layout-spacer"></div>
|
</div>
|
||||||
<!-- Navigation -->
|
<div class="navbar-menu">
|
||||||
<nav class="mdl-navigation">
|
<div class="navbar-end">
|
||||||
<a class="mdl-navigation__link" href="https://github.com/nealey/vail">Source Code</a>
|
<div class="navbar-item">
|
||||||
</nav>
|
<a href="https://github.com/nealey/vail/">Source Code</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
|
||||||
|
|
||||||
<div class="mdl-layout__drawer">
|
|
||||||
<span class="mdl-layout-title">Repeaters</span>
|
|
||||||
<nav class="mdl-navigation">
|
|
||||||
<a class="mdl-navigation__link" href="#">General</a>
|
|
||||||
<a class="mdl-navigation__link" href="#1">Channel 1</a>
|
|
||||||
<a class="mdl-navigation__link" href="#2">Channel 2</a>
|
|
||||||
<a class="mdl-navigation__link" href="#3">Channel 3</a>
|
|
||||||
</nav>
|
|
||||||
<hr>
|
|
||||||
<span class="mdl-layout-title">Local Practice</span>
|
|
||||||
<nav class="mdl-navigation">
|
|
||||||
<a class="mdl-navigation__link" href="#Echo">Echo</a>
|
|
||||||
<a class="mdl-navigation__link" href="#Fortunes">Fortunes</a>
|
|
||||||
<a class="mdl-navigation__link" href="#Fortunes: Pauses ×2">Fortunes (slow)</a>
|
|
||||||
<a class="mdl-navigation__link" href="#Fortunes: Pauses ×4">Fortunes (very slow)</a>
|
|
||||||
<a class="mdl-navigation__link" href="#Fortunes: Pauses ×6">Fortunes (very very slow)</a>
|
|
||||||
<a class="mdl-navigation__link" href="#Fortunes: Pauses ×10">Fortunes (crazy slow)</a>
|
|
||||||
</nav>
|
|
||||||
<hr>
|
|
||||||
<span class="mdl-layout-title">Resources</span>
|
|
||||||
<nav class="mdl-navigation">
|
|
||||||
<a class="mdl-navigation__link" href="https://github.com/nealey/vail/wiki" target="_blank">Wiki</a>
|
|
||||||
<a class="mdl-navigation__link" href="https://discord.gg/GBzj8cBat7" target="_blank">Discord</a>
|
|
||||||
</nav>
|
|
||||||
</div>
|
</div>
|
||||||
|
</nav>
|
||||||
<div id="snackbar" class="mdl-js-snackbar mdl-snackbar">
|
<section class="section">
|
||||||
<div class="mdl-snackbar__text"></div>
|
<div class="container">
|
||||||
<button class="mdl-snackbar__action" type="button"></button>
|
<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>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
|
||||||
<main class="mdl-layout__content">
|
<main class="mdl-layout__content">
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
|
@ -73,22 +68,6 @@
|
||||||
<div class="mdl-card__title">
|
<div class="mdl-card__title">
|
||||||
<h2 class="mdl-card__title-text">
|
<h2 class="mdl-card__title-text">
|
||||||
<div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label">
|
<div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label">
|
||||||
<input class="mdl-textfield__input" type="text" id="repeater" list="repeater-list">
|
|
||||||
<datalist id="repeater-list">
|
|
||||||
<option value="">General</option>
|
|
||||||
<option value="1">Channel 1</option>
|
|
||||||
<option value="2">Channel 2</option>
|
|
||||||
<option value="3">Channel 3</option>
|
|
||||||
<option value="Null">Null (dummy load)</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>
|
|
||||||
<label class="mdl-textfield__label" for="repeater">Repeater</label>
|
|
||||||
</div>
|
</div>
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
|
@ -118,7 +97,7 @@
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<i class="material-icons" role="presentation">keyboard</i>
|
<i class="mdi mdi-keyboard" title="Keyboard"></i>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<kbd>c</kbd>
|
<kbd>c</kbd>
|
||||||
|
@ -128,11 +107,11 @@
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<i class="material-icons" role="presentation">gamepad</i>
|
<i class="mdi mdi-controller-classic"></i>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<img class="gamepad b0" title="Gamepad Bottom Button" src="b0.svg" alt="Bottom button">
|
<i class="mdi mdi-gamepad-circle-down" title="Gamepad Bottom Button"></i>
|
||||||
<img class="gamepad b1" title="Gamepad Right Button" src="b1.svg" alt="Right button">
|
<i class="mdi mdi-gamepad-circle-right" title="Gamepad Right Button"></i>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
@ -153,14 +132,14 @@
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<i class="material-icons" role="presentation">keyboard</i>
|
<i class="mdi mdi-keyboard" title="Keyboard"></i>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<kbd>.</kbd>
|
<kbd>.</kbd>
|
||||||
<kbd>x</kbd>
|
<kbd>x</kbd>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<i class="material-icons" role="presentation">keyboard</i>
|
<i class="mdi mdi-keyboard" title="Keyboard"></i>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<kbd>/</kbd>
|
<kbd>/</kbd>
|
||||||
|
@ -169,17 +148,17 @@
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<i class="material-icons" role="presentation">gamepad</i>
|
<i class="mdi mdi-controller-classic"></i>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<img class="gamepad b2" title="Gamepad Left Button" src="b2.svg" alt="Left Button">
|
<i class="mdi mdi-gamepad-circle-left" title="Gamepad Left Button"></i>
|
||||||
<kbd class="gamepad" title="Gamepad Left Shoulder Button">L1</kbd>
|
<kbd class="gamepad" title="Gamepad Left Shoulder Button">L1</kbd>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<i class="material-icons" role="presentation">gamepad</i>
|
<i class="mdi mdi-controller-classic"></i>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<img class="gamepad b3" title="Gamepad Top Button" src="b3.svg" alt="Top Button">
|
<i class="mdi mdi-gamepad-circle-up" title="Gamepad Top Button"></i>
|
||||||
<kbd class="gamepad" title="Gamepad Right Shoulder Button">R1</kbd>
|
<kbd class="gamepad" title="Gamepad Right Shoulder Button">R1</kbd>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -319,7 +298,6 @@
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
</div>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
<!-- vim: set noet ts=2 sw=2 : -->
|
<!-- vim: set noet ts=2 sw=2 : -->
|
||||||
|
|
|
@ -2,7 +2,7 @@ class Input {
|
||||||
constructor(keyer) {
|
constructor(keyer) {
|
||||||
this.keyer = keyer
|
this.keyer = keyer
|
||||||
}
|
}
|
||||||
SetIntervalDuration(delay) {
|
SetDitDuration(delay) {
|
||||||
// Nothing
|
// Nothing
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
296
static/keyer.mjs
296
static/keyer.mjs
|
@ -1,3 +1,11 @@
|
||||||
|
/**
|
||||||
|
* A number of keyers.
|
||||||
|
*
|
||||||
|
* The document "All About Squeeze-Keying" by Karl Fischer, DJ5IL, was
|
||||||
|
* absolutely instrumental in correctly (I hope) implementing everything more
|
||||||
|
* advanced than the bug keyer.
|
||||||
|
*/
|
||||||
|
|
||||||
/** Silent period between words */
|
/** Silent period between words */
|
||||||
const PAUSE_WORD = -7
|
const PAUSE_WORD = -7
|
||||||
/** Silent period between letters */
|
/** Silent period between letters */
|
||||||
|
@ -9,6 +17,23 @@ const DIT = 1
|
||||||
/** Duration of a dah */
|
/** Duration of a dah */
|
||||||
const DAH = 3
|
const DAH = 3
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A time duration.
|
||||||
|
*
|
||||||
|
* JavaScript uses milliseconds in most (but not all) places.
|
||||||
|
* I've found it helpful to be able to multiply by a unit, so it's clear what's going on.
|
||||||
|
*
|
||||||
|
* @typedef {number} Duration
|
||||||
|
*/
|
||||||
|
/** @type {Duration} */
|
||||||
|
const Millisecond = 1
|
||||||
|
/** @type {Duration} */
|
||||||
|
const Second = 1000 * Millisecond
|
||||||
|
/** @type {Duration} */
|
||||||
|
const Minute = 60 * Second
|
||||||
|
/** @type {Duration} */
|
||||||
|
const Hour = 60 * Minute
|
||||||
|
|
||||||
const MorseMap = {
|
const MorseMap = {
|
||||||
"\x04": ".-.-.", // End Of Transmission
|
"\x04": ".-.-.", // End Of Transmission
|
||||||
"\x18": "........", // Cancel
|
"\x18": "........", // Cancel
|
||||||
|
@ -68,11 +93,6 @@ const MorseMap = {
|
||||||
"@": ".--.-.",
|
"@": ".--.-.",
|
||||||
}
|
}
|
||||||
|
|
||||||
// iOS kludge
|
|
||||||
if (!window.AudioContext) {
|
|
||||||
window.AudioContext = window.webkitAudioContext
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the inverse of the input.
|
* Return the inverse of the input.
|
||||||
* If you give it dit, it returns dah, and vice-versa.
|
* If you give it dit, it returns dah, and vice-versa.
|
||||||
|
@ -80,7 +100,7 @@ if (!window.AudioContext) {
|
||||||
* @param ditdah What to invert
|
* @param ditdah What to invert
|
||||||
* @returns The inverse of ditdah
|
* @returns The inverse of ditdah
|
||||||
*/
|
*/
|
||||||
function morseNot(ditdah) {
|
function not(ditdah) {
|
||||||
if (ditdah == DIT) {
|
if (ditdah == DIT) {
|
||||||
return DAH
|
return DAH
|
||||||
}
|
}
|
||||||
|
@ -93,6 +113,264 @@ function morseNot(ditdah) {
|
||||||
* @callback TxControl
|
* @callback TxControl
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A straight keyer.
|
||||||
|
*
|
||||||
|
* This is one or more relays wired in parallel. Each relay has an associated
|
||||||
|
* input key. You press any key, and it starts transmitting until all keys are
|
||||||
|
* released.
|
||||||
|
*/
|
||||||
|
class StraightKeyer {
|
||||||
|
/**
|
||||||
|
* @param {TxControl} beginTxFunc Callback to begin transmitting
|
||||||
|
* @param {TxControl} endTxFunc Callback to end transmitting
|
||||||
|
*/
|
||||||
|
constructor(beginTxFunc, endTxFunc) {
|
||||||
|
this.beginTxFunc = beginTxFunc
|
||||||
|
this.endTxFunc = endTxFunc
|
||||||
|
this.Reset()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list of names for keys supported by this keyer.
|
||||||
|
*
|
||||||
|
* @returns {Array.<string>} A list of key names
|
||||||
|
*/
|
||||||
|
KeyNames() {
|
||||||
|
return ["Key"]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset state and stop all transmissions.
|
||||||
|
*/
|
||||||
|
Reset() {
|
||||||
|
this.endTxFunc()
|
||||||
|
this.txRelays = []
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the state of a single transmit relay.
|
||||||
|
*
|
||||||
|
* If n is not provided, return the state of all relays wired in parallel.
|
||||||
|
*
|
||||||
|
* @param {number} n Relay number
|
||||||
|
* @returns {bool} True if relay is closed
|
||||||
|
*/
|
||||||
|
TxClosed(n=null) {
|
||||||
|
if (n == null) {
|
||||||
|
return this.txRelays.some(Boolean)
|
||||||
|
}
|
||||||
|
return this.txRelays[n]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close a transmit relay.
|
||||||
|
*
|
||||||
|
* In most of these keyers, you have multiple things that can transmit. In
|
||||||
|
* the circuit, they'd all be wired together in parallel. We instead keep
|
||||||
|
* track of relay state here, and start or stop transmitting based on the
|
||||||
|
* logical of of all relays.
|
||||||
|
*
|
||||||
|
* @param {number} n Relay number
|
||||||
|
* @param {bool} closed True if relay should be closed
|
||||||
|
*/
|
||||||
|
Tx(n, closed) {
|
||||||
|
let wasClosed = this.TxClosed()
|
||||||
|
this.txRelays[n] = closed
|
||||||
|
let nowClosed = this.TxClosed()
|
||||||
|
|
||||||
|
if (wasClosed != nowClosed) {
|
||||||
|
if (nowClosed) {
|
||||||
|
this.beginTxFunc()
|
||||||
|
} else {
|
||||||
|
this.endTxFunc()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* React to a key being pressed.
|
||||||
|
*
|
||||||
|
* @param {number} key Which key was pressed
|
||||||
|
* @param {bool} pressed True if the key was pressed
|
||||||
|
*/
|
||||||
|
Key(key, pressed) {
|
||||||
|
this.Tx(key, pressed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A "Cootie" or "Double Speed Key" is just two straight keys in parallel.
|
||||||
|
*/
|
||||||
|
class CootieKeyer extends StraightKeyer {
|
||||||
|
KeyNames() {
|
||||||
|
return ["Key", "Key"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Vibroplex "Bug".
|
||||||
|
*
|
||||||
|
* Left key send dits over and over until you let go.
|
||||||
|
* Right key works just like a stright key.
|
||||||
|
*/
|
||||||
|
class BugKeyer extends StraightKeyer {
|
||||||
|
KeyNames() {
|
||||||
|
return ["· ", "Key"]
|
||||||
|
}
|
||||||
|
|
||||||
|
Reset() {
|
||||||
|
super.Reset()
|
||||||
|
this.SetDitDuration(100 * Millisecond)
|
||||||
|
if (this.pulseTimer) {
|
||||||
|
clearInterval(this.pulseTimer)
|
||||||
|
this.pulseTimer = null
|
||||||
|
}
|
||||||
|
this.keyPressed = []
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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) {
|
||||||
|
this.beginPulsing()
|
||||||
|
} else {
|
||||||
|
super.Key(key, pressed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Begin a pulse if it hasn't already begun
|
||||||
|
*/
|
||||||
|
beginPulsing() {
|
||||||
|
if (!this.pulseTimer) {
|
||||||
|
this.pulse()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pulse() {
|
||||||
|
if (this.TxClosed(0)) {
|
||||||
|
// If we were transmitting, pause
|
||||||
|
this.Tx(0, false)
|
||||||
|
} else if (this.keyPressed[0]) {
|
||||||
|
// If the key was pressed, transmit
|
||||||
|
this.Tx(0, true)
|
||||||
|
} else {
|
||||||
|
// If the key wasn't pressed, stop pulsing
|
||||||
|
this.pulseTimer = null
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.pulseTimer = setTimeout(() => this.pulse(), this.ditDuration)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Electronic Bug Keyer
|
||||||
|
*
|
||||||
|
* Repeats both dits and dahs, ensuring proper pauses.
|
||||||
|
*
|
||||||
|
* I think the original ElBug Keyers did not have two paddles, so I've taken the
|
||||||
|
* liberty of making it so that whatever you pressed last is what gets repeated,
|
||||||
|
* similar to a modern computer keyboard.
|
||||||
|
*/
|
||||||
|
class ElBugKeyer extends BugKeyer {
|
||||||
|
KeyNames() {
|
||||||
|
return ["· ", "−"]
|
||||||
|
}
|
||||||
|
|
||||||
|
Reset() {
|
||||||
|
super.Reset()
|
||||||
|
this.lastPressed = -1
|
||||||
|
}
|
||||||
|
|
||||||
|
Key(key, pressed) {
|
||||||
|
this.keyPressed[key] = pressed
|
||||||
|
if (pressed) {
|
||||||
|
this.lastPressed = key
|
||||||
|
} else {
|
||||||
|
this.lastPressed = this.keyPressed.findIndex(Boolean)
|
||||||
|
}
|
||||||
|
this.beginPulsing()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the duration of the next transmission to send.
|
||||||
|
*
|
||||||
|
* If there is nothing to send, returns 0.
|
||||||
|
*
|
||||||
|
* @returns {Duration} Duration of next transmission
|
||||||
|
*/
|
||||||
|
nextTxDuration() {
|
||||||
|
switch (this.lastPressed) {
|
||||||
|
case 0:
|
||||||
|
return this.ditDuration * DIT
|
||||||
|
case 1:
|
||||||
|
return this.ditDuration * DAH
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
pulse() {
|
||||||
|
let nextPulse = 0
|
||||||
|
|
||||||
|
// This keyer only drives one transmit relay
|
||||||
|
if (this.TxClosed()) {
|
||||||
|
// If we're transmitting at all, pause
|
||||||
|
this.Tx(0, false)
|
||||||
|
nextPulse = this.ditDuration
|
||||||
|
} else if (this.keyPressed.some(Boolean)) {
|
||||||
|
// If there's a key down, transmit.
|
||||||
|
//
|
||||||
|
// Wait until here to ask for next duration, so things with memories
|
||||||
|
// don't flush that memory for a pause.
|
||||||
|
this.Tx(0, true)
|
||||||
|
nextPulse = this.nextTxDuration()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nextPulse) {
|
||||||
|
this.pulseTimer = setTimeout(() => this.pulse(), nextPulse)
|
||||||
|
} else {
|
||||||
|
this.pulseTimer = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Single dot memory keyer.
|
||||||
|
*
|
||||||
|
* If you tap dit while a dah is sending, it queues up a dit to send, even if
|
||||||
|
* the dit key is no longer being held at the start of the next cycle.
|
||||||
|
*/
|
||||||
|
class SingleDotKeyer extends ElBugKeyer {
|
||||||
|
Reset() {
|
||||||
|
super.Reset()
|
||||||
|
this.queue = []
|
||||||
|
}
|
||||||
|
|
||||||
|
Key(key, pressed) {
|
||||||
|
super.Key(key, pressed)
|
||||||
|
if (pressed && (key == 0) && this.keyPressed[1]) {
|
||||||
|
this.queue = [DIT]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
nextTxDuration() {
|
||||||
|
if (this.queue.length) {
|
||||||
|
let dits = this.queue.shift()
|
||||||
|
return dits * this.ditDuration
|
||||||
|
}
|
||||||
|
return super.nextTxDuration()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Keyer class. This handles iambic and straight key input.
|
* Keyer class. This handles iambic and straight key input.
|
||||||
*
|
*
|
||||||
|
@ -101,7 +379,7 @@ function morseNot(ditdah) {
|
||||||
* - Typematic: you hold the key down and it repeats evenly-spaced tones
|
* - 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
|
* - Typeahead: if you hit a key while it's still transmitting the last-entered one, it queues up your next entered one
|
||||||
*/
|
*/
|
||||||
class Keyer {
|
class OldKeyer {
|
||||||
/**
|
/**
|
||||||
* Create a Keyer
|
* Create a Keyer
|
||||||
*
|
*
|
||||||
|
@ -169,7 +447,7 @@ class Keyer {
|
||||||
typematic() {
|
typematic() {
|
||||||
if (this.ditDown && this.dahDown) {
|
if (this.ditDown && this.dahDown) {
|
||||||
this.modeBQueue = this.last
|
this.modeBQueue = this.last
|
||||||
this.last = morseNot(this.last)
|
this.last = not(this.last)
|
||||||
} else if (this.ditDown) {
|
} else if (this.ditDown) {
|
||||||
this.modeBQueue = null
|
this.modeBQueue = null
|
||||||
this.last = DIT
|
this.last = DIT
|
||||||
|
@ -365,4 +643,4 @@ class Keyer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export {Keyer}
|
export {StraightKeyer, CootieKeyer, BugKeyer, ElBugKeyer, SingleDotKeyer}
|
||||||
|
|
|
@ -67,12 +67,9 @@ export class Vail {
|
||||||
this.lagDurations.unshift(now - this.clockOffset - beginTxTime - totalDuration)
|
this.lagDurations.unshift(now - this.clockOffset - beginTxTime - totalDuration)
|
||||||
this.lagDurations.splice(20, 2)
|
this.lagDurations.splice(20, 2)
|
||||||
this.rx(0, 0, this.stats())
|
this.rx(0, 0, this.stats())
|
||||||
console.debug("Vail.wsMessage() SQUELCH", msg)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
console.debug("Vail.wsMessage()", this.socket, msg)
|
|
||||||
|
|
||||||
// The very first packet is the server telling us the current time
|
// The very first packet is the server telling us the current time
|
||||||
if (durations.length == 0) {
|
if (durations.length == 0) {
|
||||||
if (this.clockOffset == 0) {
|
if (this.clockOffset == 0) {
|
||||||
|
@ -112,7 +109,6 @@ export class Vail {
|
||||||
console.error("Not connected, dropping", jmsg)
|
console.error("Not connected, dropping", jmsg)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
console.debug("Transmit", this.socket, msg)
|
|
||||||
this.socket.send(jmsg)
|
this.socket.send(jmsg)
|
||||||
if (squelch) {
|
if (squelch) {
|
||||||
this.sent.push(jmsg)
|
this.sent.push(jmsg)
|
||||||
|
|
|
@ -1,14 +1,3 @@
|
||||||
.flex {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
align-items: flex-start;
|
|
||||||
justify-content: space-around;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mdl-card {
|
|
||||||
margin: 0.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.key {
|
.key {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 6em;
|
height: 6em;
|
||||||
|
@ -62,10 +51,10 @@ kbd {
|
||||||
background-color: #eee;
|
background-color: #eee;
|
||||||
border: 1px solid #bbb;
|
border: 1px solid #bbb;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
font-size: 9pt;
|
font-size: 66%;
|
||||||
padding: .1em .6em;
|
padding: .1em .6em;
|
||||||
cursor: default;
|
cursor: default;
|
||||||
vertical-align: top;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
|
||||||
kbd.gamepad {
|
kbd.gamepad {
|
||||||
|
@ -76,10 +65,6 @@ kbd.gamepad {
|
||||||
height: 10px;
|
height: 10px;
|
||||||
width: 10px;
|
width: 10px;
|
||||||
}
|
}
|
||||||
img.gamepad {
|
|
||||||
height: 1.5em;
|
|
||||||
vertical-align: baseline;
|
|
||||||
}
|
|
||||||
|
|
||||||
code {
|
code {
|
||||||
background-color: #333;
|
background-color: #333;
|
||||||
|
|
|
@ -25,6 +25,11 @@ function toast(msg) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// iOS kludge
|
||||||
|
if (!window.AudioContext) {
|
||||||
|
window.AudioContext = window.webkitAudioContext
|
||||||
|
}
|
||||||
|
|
||||||
class VailClient {
|
class VailClient {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.sent = []
|
this.sent = []
|
||||||
|
@ -37,8 +42,9 @@ class VailClient {
|
||||||
// Make helpers
|
// Make helpers
|
||||||
this.lamp = new Buzzer.Lamp()
|
this.lamp = new Buzzer.Lamp()
|
||||||
this.buzzer = new Buzzer.ToneBuzzer()
|
this.buzzer = new Buzzer.ToneBuzzer()
|
||||||
this.keyer = new Keyer.Keyer(() => this.beginTx(), () => this.endTx())
|
this.straightKeyer = new Keyer.StraightKeyer(() => this.beginTx(), () => this.endTx())
|
||||||
this.roboKeyer = new Keyer.Keyer(() => this.Buzz(), () => this.Silence())
|
this.keyer = new Keyer.SingleDotKeyer(() => this.beginTx(), () => this.endTx())
|
||||||
|
this.roboKeyer = new Keyer.ElBugKeyer(() => 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
|
||||||
|
@ -62,10 +68,10 @@ class VailClient {
|
||||||
|
|
||||||
// Set up inputs
|
// Set up inputs
|
||||||
this.inputInit("#iambic-duration", e => {
|
this.inputInit("#iambic-duration", e => {
|
||||||
this.keyer.SetIntervalDuration(e.target.value)
|
this.keyer.SetDitDuration(e.target.value)
|
||||||
this.roboKeyer.SetIntervalDuration(e.target.value)
|
this.roboKeyer.SetDitDuration(e.target.value)
|
||||||
for (let i of Object.values(this.inputs)) {
|
for (let i of Object.values(this.inputs)) {
|
||||||
i.SetIntervalDuration(e.target.value)
|
i.SetDitDuration(e.target.value)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
this.inputInit("#rx-delay", e => {
|
this.inputInit("#rx-delay", e => {
|
||||||
|
@ -103,7 +109,7 @@ class VailClient {
|
||||||
* @param down If key has been depressed
|
* @param down If key has been depressed
|
||||||
*/
|
*/
|
||||||
Straight(down) {
|
Straight(down) {
|
||||||
this.keyer.Straight(down)
|
this.straightKeyer.Key(0, down)
|
||||||
if (this.straightChart) this.straightChart.Set(down?1:0)
|
if (this.straightChart) this.straightChart.Set(down?1:0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -113,7 +119,7 @@ class VailClient {
|
||||||
* @param down If the key has been depressed
|
* @param down If the key has been depressed
|
||||||
*/
|
*/
|
||||||
Dit(down) {
|
Dit(down) {
|
||||||
this.keyer.Dit(down)
|
this.keyer.Key(0, down)
|
||||||
if (this.ditChart) this.ditChart.Set(down?1:0)
|
if (this.ditChart) this.ditChart.Set(down?1:0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -123,7 +129,7 @@ class VailClient {
|
||||||
* @param down If the key has been depressed
|
* @param down If the key has been depressed
|
||||||
*/
|
*/
|
||||||
Dah(down) {
|
Dah(down) {
|
||||||
this.keyer.Dah(down)
|
this.keyer.Key(1, down)
|
||||||
if (this.dahChart) this.dahChart.Set(down?1:0)
|
if (this.dahChart) this.dahChart.Set(down?1:0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue