mirror of https://github.com/nealey/vail.git
Pull some JS functionality into classes
This commit is contained in:
parent
7ba5ee836e
commit
8fcbb05183
17
main.go
17
main.go
|
@ -4,6 +4,7 @@ import (
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"golang.org/x/net/websocket"
|
"golang.org/x/net/websocket"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
var book Book
|
var book Book
|
||||||
|
@ -17,13 +18,23 @@ func (c Client) Handle(ws *websocket.Conn) {
|
||||||
book.Join(c.repeaterName, ws)
|
book.Join(c.repeaterName, ws)
|
||||||
defer book.Part(c.repeaterName, ws)
|
defer book.Part(c.repeaterName, ws)
|
||||||
|
|
||||||
|
offset := int64{0}
|
||||||
for {
|
for {
|
||||||
|
var m Message
|
||||||
buf := make([]byte, ws.MaxPayloadBytes)
|
buf := make([]byte, ws.MaxPayloadBytes)
|
||||||
n, err := ws.Read(buf)
|
|
||||||
if err != nil {
|
if n, err := ws.Read(buf); err != nil {
|
||||||
break
|
break
|
||||||
|
} else {
|
||||||
|
buf = buf[:n]
|
||||||
}
|
}
|
||||||
buf = buf[:n]
|
|
||||||
|
if err := m.UnmarshalBinary(buf); err != nil {
|
||||||
|
log("Unmarshal error:", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
book.Send(c.repeaterName, buf)
|
book.Send(c.repeaterName, buf)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -68,12 +68,12 @@
|
||||||
<table style="width: 100%; text-align: center;">
|
<table style="width: 100%; text-align: center;">
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<button class="key mdl-button mdl-js-button mdl-button--raised mdl-button--colored">
|
<button id="dit" class="key mdl-button mdl-js-button mdl-button--raised mdl-button--colored">
|
||||||
dit
|
dit
|
||||||
</button>
|
</button>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<button class="key mdl-button mdl-js-button mdl-button--raised mdl-button--colored">
|
<button id="dah" class="key mdl-button mdl-js-button mdl-button--raised mdl-button--colored">
|
||||||
dah
|
dah
|
||||||
</button>
|
</button>
|
||||||
</td>
|
</td>
|
||||||
|
@ -87,6 +87,17 @@
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
<p>
|
||||||
|
dit length
|
||||||
|
<input
|
||||||
|
id="duration"
|
||||||
|
class="mdl-slider mdl-js-slider"
|
||||||
|
type="range"
|
||||||
|
min="40"
|
||||||
|
max="255"
|
||||||
|
value="80">
|
||||||
|
<output id="duration-value"></output>
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="mdl-tabs__panel" id="straight">
|
<div class="mdl-tabs__panel" id="straight">
|
||||||
<button class="key mdl-button mdl-js-button mdl-button--raised mdl-button--colored">
|
<button class="key mdl-button mdl-js-button mdl-button--raised mdl-button--colored">
|
||||||
|
|
332
static/vail.js
332
static/vail.js
|
@ -1,114 +1,262 @@
|
||||||
// jshint asi:true
|
// jshint asi:true
|
||||||
|
|
||||||
var short = 80
|
var recFreq = 660
|
||||||
var long = 200
|
var sendFreq = recFreq * 6 / 5 // Perfect minor third
|
||||||
var audioFreq = 660
|
|
||||||
var audioFreqMe = audioFreq * 6 / 5 // I think this works out to a minor third
|
|
||||||
|
|
||||||
var ac = new AudioContext()
|
const DIT = 1
|
||||||
|
const DAH = 3
|
||||||
|
|
||||||
var mygain = ac.createGain()
|
class Iambic {
|
||||||
mygain.connect(ac.destination)
|
constructor(BeginTxFunc, endTxFunc) {
|
||||||
mygain.gain.value = 0
|
this.beginTxFunc = beginTxFunc
|
||||||
|
this.endTxFunc = endTxFunc
|
||||||
var myosc = ac.createOscillator()
|
this.interval = null
|
||||||
myosc.connect(mygain)
|
this.state = this.space
|
||||||
myosc.frequency.value = audioFreqMe
|
this.keyFunc = null
|
||||||
myosc.start()
|
|
||||||
|
|
||||||
var theirgain = ac.createGain()
|
|
||||||
theirgain.connect(ac.destination)
|
|
||||||
theirgain.gain.value = 0
|
|
||||||
|
|
||||||
var theirosc = ac.createOscillator()
|
|
||||||
theirosc.connect(theirgain)
|
|
||||||
theirosc.frequency.value = audioFreq
|
|
||||||
theirosc.start()
|
|
||||||
|
|
||||||
var repeatInterval
|
|
||||||
|
|
||||||
|
|
||||||
function message(event) {
|
|
||||||
let now = ac.currentTime
|
|
||||||
let duration = Number(event.data) || 0
|
|
||||||
duration = Math.min(duration, long)
|
|
||||||
|
|
||||||
if (now === 0) {
|
|
||||||
// Audio Context hasn't started, we can't make sound yet
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
theirgain.gain.linearRampToValueAtTime(0.1, now + 0.01)
|
// Set a new interval (transmission rate)
|
||||||
mygain.gain.setValueAtTime(0.1, now + duration/1000)
|
setInterval(duration) {
|
||||||
theirgain.gain.linearRampToValueAtTime(0.0, now + 0.01 + duration/1000)
|
clearInterval(this.interval)
|
||||||
}
|
this.interval = setInterval(e => this.pulse(), duration)
|
||||||
|
|
||||||
function send(duration) {
|
|
||||||
let now = ac.currentTime
|
|
||||||
window.socket.send(duration)
|
|
||||||
|
|
||||||
if (now === 0) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
mygain.gain.linearRampToValueAtTime(0.1, now + 0.01)
|
|
||||||
mygain.gain.setValueAtTime(0.1, now + duration/1000)
|
|
||||||
mygain.gain.linearRampToValueAtTime(0.0, now + 0.01 + duration/1000)
|
|
||||||
}
|
|
||||||
|
|
||||||
function key(event) {
|
|
||||||
let duration = 0
|
|
||||||
|
|
||||||
ac.resume()
|
|
||||||
|
|
||||||
if (event.repeat) {
|
|
||||||
// Ignore key repeats generated by the OS, we do this ourselves
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((event.button === 0) || (event.code == "Period") || (event.key == "Shift")) {
|
|
||||||
duration = short
|
|
||||||
}
|
|
||||||
if ((event.button === 2) || (event.code == "Slash") || (event.code == "KeyZ")) {
|
|
||||||
duration = long
|
|
||||||
}
|
|
||||||
if (duration === 0) {
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (repeatInterval) {
|
// An interval has passed, call whatever the current state function is
|
||||||
clearInterval(repeatInterval)
|
pulse(event) {
|
||||||
|
this.state()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't transmit for one interval.
|
||||||
|
// If a transmission was requested, start transmitting at the next interval
|
||||||
|
space() {
|
||||||
|
if (this.keyFunc) {
|
||||||
|
this.state = this.keyFunc
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event.type.endsWith("down")) {
|
// Send a dit
|
||||||
send(duration)
|
dit() {
|
||||||
repeatInterval = setInterval(() => {send(duration)}, duration + short)
|
this.beginTxFunc()
|
||||||
|
this.state = this.end
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send a dah
|
||||||
|
dah() {
|
||||||
|
this.beginTxFunc()
|
||||||
|
this.state = this.dah2
|
||||||
|
}
|
||||||
|
dah2() {
|
||||||
|
this.state = this.dah3
|
||||||
|
}
|
||||||
|
dah3() {
|
||||||
|
this.state = this.end
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop sending
|
||||||
|
end() {
|
||||||
|
this.endTxFunc()
|
||||||
|
this.state = this.space
|
||||||
|
this.state()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Edge trigger on key press
|
||||||
|
KeyDown(key) {
|
||||||
|
if (key == DIT) {
|
||||||
|
this.keyFunc = this.dit
|
||||||
|
} else if (key == DAH) {
|
||||||
|
this.keyFunc = this.dah
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Edge trigger on key release
|
||||||
|
KeyUp() {
|
||||||
|
// Only clear the keyFunc if the key released is the same one that we think is pressed
|
||||||
|
if ((key == DIT) && (this.keyFunc == this.dit)) {
|
||||||
|
this.keyFunc = null
|
||||||
|
} else if ((key == DAH) && (this.keyFunc = this.dah)) {
|
||||||
|
this.keyFunc = null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function canWeJustNot(event) {
|
class Buzzer {
|
||||||
event.preventDefault()
|
constructor(txGain=0.1) {
|
||||||
return false
|
this.txGain = txGain
|
||||||
|
|
||||||
|
this.ac = new AudioContext()
|
||||||
|
|
||||||
|
this.lowGain = this.ac.createGain()
|
||||||
|
this.lowGain.connect(ac.destination)
|
||||||
|
this.lowGain.gain.value = 0
|
||||||
|
this.lowOsc = this.ac.createOscillator()
|
||||||
|
this.lowOsc.connect(recGain)
|
||||||
|
this.lowOsc.frequency.value = recFreq
|
||||||
|
|
||||||
|
this.highGain = this.ac.createGain()
|
||||||
|
this.highGain.connect(ac.destination)
|
||||||
|
this.highGain.gain.value = 0
|
||||||
|
this.highOsc = this.ac.createOscillator()
|
||||||
|
this.highOsc.connect(sendGain)
|
||||||
|
this.highOsc.frequency.value = recFreq
|
||||||
|
}
|
||||||
|
|
||||||
|
gain(high) {
|
||||||
|
if (high) {
|
||||||
|
return this.highGain
|
||||||
|
} else {
|
||||||
|
return this.lowGain
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Begin buzzing at time (null = now)
|
||||||
|
Buzz(high=false, when=null) {
|
||||||
|
if (when === null) {
|
||||||
|
when = this.ac.currentTime
|
||||||
|
}
|
||||||
|
this.gain(high).linearRampToValueAtTime(this.txGain, when + 0.1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// End buzzing at time (null = now)
|
||||||
|
Silence(high=false, when=null) {
|
||||||
|
if (when === null) {
|
||||||
|
when = this.ac.currentTime
|
||||||
|
}
|
||||||
|
this.gain(high).linearRampToValueAtTime(0, when + 0.1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Buzz for a duration at time
|
||||||
|
BuzzDuration(high, when, duration) {
|
||||||
|
let gain = this.gain(high)
|
||||||
|
gain.setValueAtTime(0, when)
|
||||||
|
gain.linearRampToValueAtTime(this.txGain, when+0.1)
|
||||||
|
gain.setValueAtTime(this.txGain, when+duration)
|
||||||
|
gain.linearRampToValueAtTime(this.txGain, when+duration+0.1)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function init() {
|
class Vail {
|
||||||
let wsUrl = new URL(window.location)
|
constructor() {
|
||||||
wsUrl.protocol = "ws:"
|
this.beginTxTime = null // Time when we began transmitting
|
||||||
wsUrl.pathname += "chat"
|
|
||||||
window.socket = new WebSocket(wsUrl)
|
|
||||||
window.socket.addEventListener("message", message)
|
|
||||||
|
|
||||||
// disable RMB context menu
|
// Set up WebSocket
|
||||||
document.addEventListener("contextmenu", e => canWeJustNot(e))
|
let wsUrl = new URL(window.location)
|
||||||
document.addEventListener("mousedown", e => key(e))
|
wsUrl.protocol = "ws:"
|
||||||
document.addEventListener("mouseup", e => key(e))
|
wsUrl.pathname += "chat"
|
||||||
document.addEventListener("keydown", e => key(e))
|
window.socket = new WebSocket(wsUrl)
|
||||||
document.addEventListener("keyup", e => key(e))
|
window.socket.addEventListener("message", this.wsMessage)
|
||||||
|
|
||||||
|
// Listen for right clicks on dit button
|
||||||
|
let dit = document.querySelector("#dit")
|
||||||
|
dit.addEventListener("contextmenu", e => this.canWeJustNot(e))
|
||||||
|
dit.addEventListener("mousedown", e => this.ditMouse(e))
|
||||||
|
dit.addEventListener("mouseup", e => this.ditMouse(e))
|
||||||
|
|
||||||
|
// Listen for keystrokes
|
||||||
|
document.addEventListener("keydown", e => this.key(e))
|
||||||
|
document.addEventListener("keyup", e => this.key(e))
|
||||||
|
|
||||||
|
// Make an Iambic input device
|
||||||
|
this.iambic = new Iambic(() => this.beginTx(), () => this.endTx())
|
||||||
|
|
||||||
|
|
||||||
|
let durationElement = document.querySelector("#duration")
|
||||||
|
durationElement.addEventListener("input", e => this.changeDuration(e))
|
||||||
|
this.durationInterval = setInterval(e => this.durationElapsed(e), durationElement.value)
|
||||||
|
|
||||||
|
this.keyDownTime = 0
|
||||||
|
this.durationsLeft = 0
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
keyDown() {
|
||||||
|
this.keyDownTime = new Date()
|
||||||
|
this.sendGain.linearRampToValueAtTime(0.1, this.ac.currentTime + 0.1)
|
||||||
|
}
|
||||||
|
|
||||||
|
keyUp() {
|
||||||
|
let keyUpTime = new Date()
|
||||||
|
let duration = keyUpTime - keyDownTime
|
||||||
|
this.sendGain.linearRampToValueAtTime(0.0, this.ac.currentTime + 0.1)
|
||||||
|
this.send(keyDownTime, [duration])
|
||||||
|
}
|
||||||
|
|
||||||
|
durationElapsed(e) {
|
||||||
|
if (this.durationsLeft === 0) {
|
||||||
|
this.durationsLeft = this.keyDown + 1
|
||||||
|
}
|
||||||
|
if (this.keyDown == 2) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
beep(gain, duration) {
|
||||||
|
let now = ac.currentTime
|
||||||
|
let end = now + duration
|
||||||
|
if (now === 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ramping in and out prevents square wave overtones
|
||||||
|
gain.linearRampToValueAtTime(0.1, now + 0.1)
|
||||||
|
gain.setValueAtTime(0.1, end - 0.1)
|
||||||
|
gain.linearRampToValueAtTime(0.0, end)
|
||||||
|
}
|
||||||
|
|
||||||
|
message(event) {
|
||||||
|
let duration = Number(event.data) || 0
|
||||||
|
duration = Math.min(duration, long)
|
||||||
|
beep(this.recGain.gain, duration)
|
||||||
|
}
|
||||||
|
|
||||||
|
send(duration) {
|
||||||
|
window.socket.send(duration)
|
||||||
|
beep(this.sendGain.gain, duration)
|
||||||
|
}
|
||||||
|
|
||||||
|
key(event) {
|
||||||
|
let duration = 0
|
||||||
|
|
||||||
|
ac.resume()
|
||||||
|
|
||||||
|
if (event.repeat) {
|
||||||
|
// Ignore key repeats generated by the OS, we do this ourselves
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((event.button === 0) || (event.code == "Period") || (event.key == "Shift")) {
|
||||||
|
duration = short
|
||||||
|
}
|
||||||
|
if ((event.button === 2) || (event.code == "Slash") || (event.code == "KeyZ")) {
|
||||||
|
duration = long
|
||||||
|
}
|
||||||
|
if (duration === 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (repeatInterval) {
|
||||||
|
clearInterval(repeatInterval)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.type.endsWith("down")) {
|
||||||
|
send(duration)
|
||||||
|
repeatInterval = setInterval(() => {send(duration)}, duration + short)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
canWeJustNot(event) {
|
||||||
|
event.preventDefault()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function vailInit() {
|
||||||
|
window.app = new Vail()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (document.readyState === "loading") {
|
if (document.readyState === "loading") {
|
||||||
document.addEventListener("DOMContentLoaded", init)
|
document.addEventListener("DOMContentLoaded", vailInit)
|
||||||
} else {
|
} else {
|
||||||
init()
|
vailInit()
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue