diff --git a/static/index.html b/static/index.html index 431bead..9fb7bc7 100644 --- a/static/index.html +++ b/static/index.html @@ -24,22 +24,21 @@
+
- Title + Repeaters
+
@@ -54,12 +53,23 @@ Iambic
- + + + + + + + +
+ +
+ +
- +
- . or + . or z - / or z + / or x
-

- Dit length: - ms - -

+ +
+
+

+ Knobs +

+
+
+ + + + + + + + + + + + + + + + + +
+ Average round-trip time: + + 0ms +
+ Longest recent transmission: + + 0ms +
+ Suggested receive delay: + + 0ms +
+
+

+ Recieve delay: + ms + +

+

+ Dit length (iambic): + ms + +

+
+
@@ -125,62 +187,44 @@ Just like a radio repeater, anybody can connect and start transmitting stuff, and this will broadcast it to everyone connected. - If there's enough interest, - I'll add something like channels.

+ +

Why Does This Exist?

-

- If you need this to work on a cell phone, - let me know and I'll come up with something for you. -

-
-
- -
-
-

- Why Does This Exist? -

-
-

I need a place to practice CW with actual human beings, and I want it to be as close as possible to what I'd experience on a radio. Also, I don't want to make people buy a bunch of radio hardware. Nothing else like this exists on the Internet, as far as I can tell.

- -

Who made it?

- -

- Neale Pickett kd7oqi -

-
-

Future plans

+

Future plans

-
+
  • Move to a more permanent URL
  • Make this page less ugly
  • Arduino program to let you hook up an iambic paddle over USB
  • Document the protocol
  • Support multiple channels/frequencies
  • -
  • Sensible way to make this work on a cell phone
  • -
  • Make this page less ugly (I really hate it right now)
-

How can I help?

+

How can I help?

  • Improve the source code
  • -
  • Email me and let me know you're using it
  • +
  • Email me and let me know you're using it
+ +

+ Neale Pickett kd7oqi +

+
diff --git a/static/vail.css b/static/vail.css index 597fe0b..83d9d0c 100644 --- a/static/vail.css +++ b/static/vail.css @@ -14,6 +14,14 @@ height: 6em; } +.center { + text-align: center; +} + +.wide { + width: 100%; +} + code { background-color: #333; color: #fff; diff --git a/static/vail.js b/static/vail.js index c6a8e40..53dc1b2 100644 --- a/static/vail.js +++ b/static/vail.js @@ -2,6 +2,7 @@ const lowFreq = 660 const highFreq = lowFreq * 6 / 5 // Perfect minor third +const errorFreq = 30 const DIT = 1 const DAH = 3 @@ -91,26 +92,26 @@ class Buzzer { // in order to avoid "pops" (square wave overtones) // that happen with instant changes in gain. - constructor(txGain=0.5) { + constructor(txGain=0.3) { this.txGain = txGain this.ac = new AudioContext() - this.lowGain = this.ac.createGain() - this.lowGain.connect(this.ac.destination) - this.lowGain.gain.value = 0 - this.lowOsc = this.ac.createOscillator() - this.lowOsc.connect(this.lowGain) - this.lowOsc.frequency.value = lowFreq - this.lowOsc.start() - - this.highGain = this.ac.createGain() - this.highGain.connect(this.ac.destination) - this.highGain.gain.value = 0 - this.highOsc = this.ac.createOscillator() - this.highOsc.connect(this.highGain) - this.highOsc.frequency.value = highFreq - this.highOsc.start() + this.lowGain = this.create(lowFreq) + this.highGain = this.create(highFreq) + this.errorGain = this.create(errorFreq, "square") + } + + create(frequency, type="sine") { + let gain = this.ac.createGain() + gain.connect(this.ac.destination) + gain.gain.value = 0 + let osc = this.ac.createOscillator() + osc.type = type + osc.connect(gain) + osc.frequency.value = frequency + osc.start() + return gain } gain(high) { @@ -145,6 +146,14 @@ class Buzzer { this.txGain = gain } + /** + * Play an error tone + */ + ErrorTone() { + this.errorGain.gain.setTargetAtTime(this.txGain * 0.5, this.ac.currentTime, 0.001) + this.errorGain.gain.setTargetAtTime(0, this.ac.currentTime + 0.2, 0.001) + } + /** * Begin buzzing at time * @@ -187,6 +196,10 @@ class Buzzer { class Vail { constructor() { + this.sent = [] + this.lagTimes = [0] + this.rxDurations = [0] + this.rxDelay = 0 // Milliseconds to add to incoming timestamps this.beginTxTime = null // Time when we began transmitting // Set up WebSocket @@ -194,7 +207,7 @@ class Vail { wsUrl.protocol = "ws:" wsUrl.pathname += "chat" window.socket = new WebSocket(wsUrl) - window.socket.addEventListener("message", this.wsMessage) + window.socket.addEventListener("message", e => this.wsMessage(e)) // Listen to HTML buttons for (let e of document.querySelectorAll("button.key")) { @@ -213,6 +226,7 @@ class Vail { // Listen for slider values this.inputInit("#iambic-duration", e => this.iambic.SetInterval(e.target.value)) + this.inputInit("#rx-delay", e => {this.rxDelay = e.target.value}) } inputInit(selector, func) { @@ -241,17 +255,69 @@ class Vail { let endTxTime = Date.now() let duration = endTxTime - this.beginTxTime this.buzzer.Silence(true) + this.wsSend(this.beginTxTime, duration) + this.beginTxTime = null + } + + updateReading(selector, value) { + let e = document.querySelector(selector) + if (e) { + e.value = value + } + } + + updateReadings() { + let avgLag = this.lagTimes.reduce((a,b) => (a+b)) / this.lagTimes.length + let longestRx = this.rxDurations.reduce(Math.max) + let suggestedDelay = (avgLag + longestRx) * 1.2 - let msg = JSON.stringify([this.beginTxTime, duration]) - window.socket.send(msg) + this.updateReading("#lag-value", avgLag.toFixed()) + this.updateReading("#longest-rx-value", longestRx) + this.updateReading("#suggested-delay-value", suggestedDelay.toFixed()) + } + + addLagReading(duration) { + this.lagTimes.push(duration) + if (this.lagTimes.length > 20) { + this.lagTimes.shift() + } + this.updateReadings() + } + + addRxDuration(duration) { + this.rxDurations.push(duration) + if (this.rxDurations.length > 20) { + this.rxDurations.shift() + } + this.updateReadings() + } + + wsSend(time, duration) { + let msg = [time, duration] + let jmsg = JSON.stringify(msg) + window.socket.send(jmsg) + this.sent.push(jmsg) } wsMessage(event) { - let msg = JSON.parse(event.data) + let jmsg = event.data + let msg = JSON.parse(jmsg) let beginTxTime = msg[0] let duration = msg[1] + + let sent = this.sent.filter(e => e != jmsg) + if (sent.length < this.sent.length) { + // We're getting our own message back, which tells us our lag. + // We shouldn't emit a tone, though. + this.sent = sent + this.addLagReading(Date.now() - beginTxTime - duration) + return + } - console.log(msg) + // Beep! + this.buzzer.BuzzDuration(false, beginTxTime+this.rxDelay, duration) + + this.addRxDuration(duration) } key(event) { @@ -262,11 +328,11 @@ class Vail { let begin = event.type.endsWith("down") - if ((event.code == "Period") || (event.key == "KeyZ")) { + if ((event.code == "KeyZ") || (event.code == "Period")) { event.preventDefault() this.iambic.Key(begin, DIT) } - if ((event.code == "Slash") || (event.code == "KeyX")) { + if ((event.code == "KeyX") || (event.code == "Slash")) { event.preventDefault() this.iambic.Key(begin, DAH) }