Add new frequency graph

This commit is contained in:
Lewis Moten
2024-05-12 04:12:28 -04:00
parent 51a51d7e96
commit 015dd3eae3
8 changed files with 387 additions and 64 deletions

View File

@@ -8,6 +8,7 @@ let AMPLITUDE_THRESHOLD = 50;
let FSK_SETS = []; let FSK_SETS = [];
let SIGNAL_INTERVAL_MS = 30; let SIGNAL_INTERVAL_MS = 30;
let SIGNAL_TIMEOUT_MS = 400; let SIGNAL_TIMEOUT_MS = 400;
let LAST_SIGNAL_BEFORE_TIMEOUT = 0;
let HAS_SIGNAL = false; let HAS_SIGNAL = false;
let SIGNAL_START_MS = -1; let SIGNAL_START_MS = -1;
@@ -17,6 +18,14 @@ let SAMPLE_RATE;
let signalTimeoutId; let signalTimeoutId;
let SAMPLES = []; let SAMPLES = [];
const setTimeoutMilliseconds = (milliseconds) => {
SIGNAL_TIMEOUT_MS = milliseconds;
if(signalTimeoutId) {
// probably a long timeout. let's reset
window.clearTimeout(signalTimeoutId);
signalTimeoutId = window.setTimeout(handleSignalLost, SIGNAL_TIMEOUT_MS, LAST_SIGNAL_BEFORE_TIMEOUT);
}
}
const changeConfiguration = ({ const changeConfiguration = ({
fskSets, fskSets,
signalIntervalMs, signalIntervalMs,
@@ -196,6 +205,7 @@ const handleSignalOn = time => {
} }
const handleSignalOff = time => { const handleSignalOff = time => {
if(HAS_SIGNAL && !signalTimeoutId) { if(HAS_SIGNAL && !signalTimeoutId) {
LAST_SIGNAL_BEFORE_TIMEOUT = time;
signalTimeoutId = window.setTimeout(handleSignalLost, SIGNAL_TIMEOUT_MS, time); signalTimeoutId = window.setTimeout(handleSignalLost, SIGNAL_TIMEOUT_MS, time);
} }
} }
@@ -222,4 +232,5 @@ export {
reset, reset,
addEventListener, addEventListener,
removeEventListener, removeEventListener,
setTimeoutMilliseconds
} }

View File

@@ -116,14 +116,16 @@ export function stopAt(streamEndSeconds) {
); );
} }
export function stop() { export function stop() {
const time = now();
const oscillators = getOscillators(); const oscillators = getOscillators();
if(this.audioContext) {
const time = now();
oscillators.forEach( oscillators.forEach(
oscillator => { oscillator => {
oscillator?.stop(time); oscillator?.stop(time);
oscillator?.disconnect(); oscillator?.disconnect();
} }
) )
}
oscillators.length = 0; oscillators.length = 0;
futureEventIds.forEach(window.clearTimeout); futureEventIds.forEach(window.clearTimeout);
futureEventIds.length = 0; futureEventIds.length = 0;

View File

@@ -5,6 +5,7 @@ class AvailableFskPairsPanel extends BasePanel {
super('Available FSK Pairs'); super('Available FSK Pairs');
this.exclude = []; this.exclude = [];
this.fskPairs = []; this.fskPairs = [];
this.originalSelectedFskPairs = [];
this.sampleRate = 48000; this.sampleRate = 48000;
this.addCanvas('fsk-spectrum', 200, 32); this.addCanvas('fsk-spectrum', 200, 32);
@@ -28,9 +29,25 @@ class AvailableFskPairsPanel extends BasePanel {
} else if(!this.exclude.includes(event.id)) { } else if(!this.exclude.includes(event.id)) {
this.exclude.push(event.id); this.exclude.push(event.id);
} }
this.checkChanges();
this.drawFskSpectrum(); this.drawFskSpectrum();
}; };
checkChanges = () => {
const selected = this.getSelectedFskPairs();
const original = this.originalSelectedFskPairs;
let changed = false;
if(original.length !== selected.length) {
changed = true;
} else {
const hertz = selected.flat();
changed = original.flat().some((hz, i) => hz !== hertz[i]);
}
if(changed) {
this.originalSelectedFskPairs = selected;
this.dispatcher.emit('change', {selected});
}
}
getSelectedFskPairs = () => this.fskPairs getSelectedFskPairs = () => this.fskPairs
.filter(this.isSelected); .filter(this.isSelected);
@@ -38,6 +55,7 @@ class AvailableFskPairsPanel extends BasePanel {
setSelectedIndexes = (values) => { setSelectedIndexes = (values) => {
this.exclude = values; this.exclude = values;
this.setFskPairs(this.fskPairs); this.setFskPairs(this.fskPairs);
this.checkChanges();
} }
setFskPairs = fskPairs => { setFskPairs = fskPairs => {
@@ -51,6 +69,7 @@ class AvailableFskPairsPanel extends BasePanel {
eventName: 'select' eventName: 'select'
})); }));
this.replaceCheckedInputs('checkbox', 'fsk-pairs', items); this.replaceCheckedInputs('checkbox', 'fsk-pairs', items);
this.checkChanges();
this.drawFskSpectrum(); this.drawFskSpectrum();
} }

View File

@@ -195,6 +195,10 @@ class BasePanel {
const element = this.getElement(id); const element = this.getElement(id);
element.innerHTML = html; element.innerHTML = html;
} }
getNumberById = id => {
const value = this.getValueById(id);
return parseFloat(value);
}
getValueById = (id) => { getValueById = (id) => {
const element = this.getElement(id); const element = this.getElement(id);
switch(element.tagName) { switch(element.tagName) {

View File

@@ -0,0 +1,249 @@
import BasePanel from './BasePanel';
class FrequencyGraphPanel extends BasePanel {
constructor() {
super('Frequency Graph');
this.fskPairs = [];
this.sampleRate = 48000;
this.samplingPeriod = 30;
this.signalStart = performance.now();
this.signalEnd = this.signalStart;
this.samples = [];
this.duration = 200;
this.amplitudeThreshold = 0;
this.addButton('toggle', 'Start', 'toggle');
this.addNewLine();
this.addCanvas('frequency-graph', 500, 150);
this.addEventListener('toggle', this.handleToggle);
};
setDurationMilliseconds = (millseconds) => {
this.duration = millseconds;
this.draw(false);
}
setSignalStart = milliseconds => {
this.signalStart = milliseconds;
}
setSignalEnd = milliseconds => {
this.signalEnd = milliseconds;
}
setSamplingPeriod = (milliseconds) => this.samplingPeriod = milliseconds;
setAmplitudeThreshold = value => {
this.amplitudeThreshold = value;
}
setSampleRate = (value) => {
this.sampleRate = value;
}
setFskPairs = fskPairs => {
this.fskPairs = fskPairs;
}
setAnalyser = (analyser) => {
this.analyser = analyser;
}
isRunning = () => !!this.intervalId || !!this.animationFrameId;
handleToggle = () => {
if(this.isRunning()) {
this.stop();
} else {
this.start();
}
}
start = () => {
this.setValueById('toggle', 'Stop');
if(!this.intervalId) {
this.intervalId = window.setInterval(this.collectSamples, 5);
}
if(!this.animationFrameId) {
this.animationFrameId = window.requestAnimationFrame(this.draw);
}
}
stop = () => {
this.setValueById('toggle', 'Start');
if(this.intervalId) {
window.clearInterval(this.intervalId);
this.intervalId = undefined;
}
if(this.animationFrameId) {
window.cancelAnimationFrame(this.animationFrameId);
this.animationFrameId = undefined;
}
// final draw
this.draw(false);
}
collectSamples = () => {
// Nothing to collect
if(this.fskPairs.length === 0) return;
// Nothing to collect with
const analyser = this.analyser;
if(!analyser) return;
const frequencyResolution = this.sampleRate / analyser.fftSize;
const frequencies = new Uint8Array(analyser.frequencyBinCount);
analyser.getByteFrequencyData(frequencies);
const indexOfHz = hz => Math.round(hz/frequencyResolution);
const ampsFromHz = hz => frequencies[indexOfHz(hz)];
const ampsFromManyHz = fsk => fsk.map(ampsFromHz);
const now = performance.now();
this.samples.unshift({
time: now,
fskPairs: this.fskPairs.map(ampsFromManyHz)
});
this.samples = this.samples.filter(sample => now - sample.time < this.duration);
}
draw = () => {
const maxAmps = 280; // inflated for height
const ultimateFrequency = this.sampleRate / 2;
const canvas = this.getElement('frequency-graph');
const ctx = canvas.getContext('2d');
const {height, width} = canvas;
ctx.clearRect(0, 0, width, height);
let now;
if(this.samples.length > 1) {
now = this.samples[0].time;
this.fskPairs.forEach((fsk, fskIndex) => {
fsk.forEach((hz, hzIndex)=> {
ctx.beginPath();
const hue = Math.floor(hz/ultimateFrequency * 360);
if(hzIndex === 0) {
ctx.strokeStyle = `hsla(${hue}, 100%, 50%, 50%)`;
ctx.setLineDash([5, 5]);
} else {
ctx.strokeStyle = `hsl(${hue}, 100%, 50%)`;
ctx.setLineDash([]);
}
this.samples.forEach(({time, fskPairs}, i) => {
fsk = fskPairs[fskIndex];
if(!fsk) return; // configuration changed
let x = ((now - time) / this.duration) * width;
const percent = (fsk[hzIndex] / maxAmps);
let y = (1 - percent) * height;
if(i === 0) {
ctx.moveTo(x, y);
} else {
ctx.lineTo(x, y);
}
});
ctx.lineWidth = 1;
ctx.stroke();
})
})
};
ctx.setLineDash([]);
// Amplitude Threshold
ctx.strokeStyle = 'hsla(0, 0%, 100%, 20%)';
ctx.lineWidth = 1;
ctx.beginPath();
let y = height * (1-(this.amplitudeThreshold * 255) / maxAmps);
ctx.moveTo(0, y);
ctx.lineTo(width, y);
ctx.stroke();
// sampling periods
if(!now) now = performance.now();
let lastPeriodStart = now - ((now - this.signalStart) % this.samplingPeriod);
let lastTextX = -1000;
ctx.lineWidth = 1;
this.lastCountX = -1000;
for(let time = lastPeriodStart; time > now - this.duration; time -= this.samplingPeriod) {
const end = time + this.samplingPeriod;
let rightX = ((now - time) / this.duration) * width;
let leftX = ((now - end) / this.duration) * width;
// Line for when period started
ctx.beginPath();
ctx.moveTo(rightX, 0);
ctx.lineTo(rightX, height);
ctx.strokeStyle = 'hsla(120, 100%, 100%, 50%)';
ctx.stroke();
let samplePeriodWidth = rightX - leftX;
ctx.fontSize = '24px';
// Sample Index
if(time >= this.signalStart && (this.signalEnd < this.signalStart || time < this.signalEnd)) {
const signalIndex = Math.floor((time - this.signalStart) / this.samplingPeriod);
let text = signalIndex.toLocaleString();
let size = ctx.measureText(text);
let textX = leftX + (samplePeriodWidth / 2) - (size.width / 2);
// far enough from prior text?
if(textX - lastTextX > size.width) {
lastTextX = textX;
ctx.lineWidth = 2;
ctx.textBaseline = 'bottom';
let textY = height - 12;
ctx.strokeStyle = 'black';
ctx.strokeText(text, textX, textY);
ctx.fillStyle = 'white';
ctx.fillText(text, textX, textY);
}
}
// sample counts
this.drawSampleCount(ctx, width, height, time, end, leftX, samplePeriodWidth);
}
this.drawSignalStart(ctx, width, height, now);
this.drawSignalEnd(ctx, width, height, now);
if(this.isRunning()) {
this.animationFrameId = requestAnimationFrame(this.draw);
}
}
drawSampleCount = (ctx, width, height, start, end, leftX, samplePeriodWidth) => {
const count = this.samples.filter(sample => {
return sample.time >= start && sample.time < end;
}).length;
let text = count.toLocaleString();
let size = ctx.measureText(text);
let textX = leftX + (samplePeriodWidth / 2) - (size.width / 2);
// far enough from prior text?
if(textX - this.lastCountX > size.width) {
this.lastCountX = textX;
ctx.lineWidth = 2;
ctx.textBaseline = 'bottom';
let textY = 20;
ctx.strokeStyle = 'black';
ctx.strokeText(text, textX, textY);
if(count === 0) {
ctx.fillStyle = 'red';
} else if(count < 3) {
ctx.fillStyle = 'yellow';
} else {
ctx.fillStyle = 'white';
}
ctx.fillText(text, textX, textY);
}
}
drawSignalStart = (ctx, width, height, now) => {
if(now - this.signalStart < this.duration) {
let x = ((now - this.signalStart) / this.duration) * width;
ctx.beginPath();
ctx.moveTo(x, 0);
ctx.lineTo(x, height);
ctx.lineWidth = 3;
ctx.strokeStyle = 'hsla(60, 100%, 50%, 50%)';
ctx.stroke();
}
};
drawSignalEnd = (ctx, width, height, now) => {
if(now - this.signalEnd < this.duration) {
let x = ((now - this.signalEnd) / this.duration) * width;
ctx.beginPath();
ctx.moveTo(x, 0);
ctx.lineTo(x, height);
ctx.lineWidth = 3;
ctx.strokeStyle = 'hsla(60, 100%, 50%, 50%)';
ctx.stroke();
}
}
}
export default FrequencyGraphPanel;

View File

@@ -0,0 +1,26 @@
import BasePanel from './BasePanel';
class GraphConfigurationPanel extends BasePanel {
constructor() {
super('Graphs');
this.addCheckboxes('checkboxes', [
{text: 'Pause after signal ends', id: 'pause-after-end', eventName: 'pauseAfterEndChange'}
])
this.openField('Duration');
this.addInputNumber('duration', 1, {min: 0.03, step: 0.001, eventName: 'durationChange'});
this.addText('s');
this.closeField();
};
getDurationSeconds = () => this.getNumberById('duration');
setDurationSeconds = (seconds) => this.setValueById('duration', seconds);
getDurationMilliseconds = () => this.getDurationSeconds() * 1000;
setDurationMilliseconds = (milliseconds) => this.setDurationSeconds(milliseconds / 1000);
getPauseAfterEnd = () => this.getCheckedById('pause-after-end');
setPauseAfterEnd = (value) => this.setCheckedById('pause-after-end', value);
}
export default GraphConfigurationPanel;

View File

@@ -28,12 +28,19 @@ class SignalPanel extends BasePanel {
this.addText('%'); this.addText('%');
this.closeField(); this.closeField();
this.openField('Timeout');
this.addInputNumber('timeout', 30, {min: 30, max: 1000, eventName: 'timeoutChange'});
this.addText('ms');
this.closeField();
this.openField('Smoothing Time Constant'); this.openField('Smoothing Time Constant');
this.addInputNumber('smoothing-time-constant', 0, {min: 0, max: 100, eventName: 'smothingTimeConstantChange', translation: 'percent'}); this.addInputNumber('smoothing-time-constant', 0, {min: 0, max: 100, eventName: 'smothingTimeConstantChange', translation: 'percent'});
this.addText('%'); this.addText('%');
this.closeField(); this.closeField();
}; };
getTimeoutMilliseconds = () => this.getNumberById('timeout');
setTimeoutMilliseconds = (milliseconds) => this.setValueById('timeout', milliseconds);
getWaveform = () => this.getValueById('wave-form'); getWaveform = () => this.getValueById('wave-form');
setWaveform = (value) => this.setValueById('wave-form', value); setWaveform = (value) => this.setValueById('wave-form', value);

119
index.js
View File

@@ -14,7 +14,8 @@ import FrequencyPanel from "./Panels/FrequencyPanel";
import SignalPanel from "./Panels/SignalPanel"; import SignalPanel from "./Panels/SignalPanel";
import PacketizationPanel from "./Panels/PacketizationPanel"; import PacketizationPanel from "./Panels/PacketizationPanel";
import AvailableFskPairsPanel from "./Panels/AvailableFskPairsPanel"; import AvailableFskPairsPanel from "./Panels/AvailableFskPairsPanel";
import FrequencyGraphPanel from "./Panels/FrequencyGraphPanel";
import GraphConfigurationPanel from './Panels/GraphConfigurationPanel'
var audioContext; var audioContext;
var microphoneStream; var microphoneStream;
var microphoneNode; var microphoneNode;
@@ -48,10 +49,6 @@ let SAMPLES = [];
var bitStart = []; var bitStart = [];
var PAUSE = false; var PAUSE = false;
var PAUSE_AFTER_END = true;
let USED_FSK = [];
let AVAILABLE_FSK = [];
const communicationsPanel = new CommunicationsPanel(); const communicationsPanel = new CommunicationsPanel();
const messagePanel = new MessagePanel(); const messagePanel = new MessagePanel();
@@ -61,9 +58,13 @@ const frequencyPanel = new FrequencyPanel();
const signalPanel = new SignalPanel(); const signalPanel = new SignalPanel();
const packetizationPanel = new PacketizationPanel(); const packetizationPanel = new PacketizationPanel();
const availableFskPairsPanel = new AvailableFskPairsPanel(); const availableFskPairsPanel = new AvailableFskPairsPanel();
const frequencyGraphPanel = new FrequencyGraphPanel();
const graphConfigurationPanel = new GraphConfigurationPanel();
function handleWindowLoad() { function handleWindowLoad() {
const panelContainer = document.getElementById('panel-container'); const panelContainer = document.getElementById('panel-container');
panelContainer.prepend(graphConfigurationPanel.getDomElement());
panelContainer.prepend(frequencyGraphPanel.getDomElement());
panelContainer.prepend(availableFskPairsPanel.getDomElement()); panelContainer.prepend(availableFskPairsPanel.getDomElement());
panelContainer.prepend(packetizationPanel.getDomElement()); panelContainer.prepend(packetizationPanel.getDomElement());
panelContainer.prepend(signalPanel.getDomElement()); panelContainer.prepend(signalPanel.getDomElement());
@@ -96,6 +97,7 @@ function handleWindowLoad() {
signalPanel.setSegmentDuration(30); signalPanel.setSegmentDuration(30);
signalPanel.setAmplitudeThreshold(0.78); signalPanel.setAmplitudeThreshold(0.78);
signalPanel.setSmoothingTimeConstant(0); signalPanel.setSmoothingTimeConstant(0);
signalPanel.setTimeoutMilliseconds(60);
packetizationPanel.setSizePower(5); packetizationPanel.setSizePower(5);
packetizationPanel.setErrorCorrection(true); packetizationPanel.setErrorCorrection(true);
@@ -103,7 +105,17 @@ function handleWindowLoad() {
availableFskPairsPanel.setFskPairs(frequencyPanel.getFskPairs()); availableFskPairsPanel.setFskPairs(frequencyPanel.getFskPairs());
// Communications Events graphConfigurationPanel.setDurationMilliseconds(signalPanel.getSegmentDuration() * 20);
graphConfigurationPanel.setPauseAfterEnd(true);
frequencyGraphPanel.setFskPairs(availableFskPairsPanel.getSelectedFskPairs());
frequencyGraphPanel.setAmplitudeThreshold(signalPanel.getAmplitudeThreshold());
frequencyGraphPanel.setDurationMilliseconds(graphConfigurationPanel.getDurationMilliseconds());
AudioReceiver.setTimeoutMilliseconds(signalPanel.getTimeoutMilliseconds());
// Events
communicationsPanel.addEventListener('listeningChange', handleChangeListening); communicationsPanel.addEventListener('listeningChange', handleChangeListening);
communicationsPanel.addEventListener('sendSpeakersChange', handleChangeSendSpeakers); communicationsPanel.addEventListener('sendSpeakersChange', handleChangeSendSpeakers);
communicationsPanel.addEventListener('sendAnalyzerChange', handleChangeSendAnalyzer); communicationsPanel.addEventListener('sendAnalyzerChange', handleChangeSendAnalyzer);
@@ -124,9 +136,18 @@ function handleWindowLoad() {
}); });
signalPanel.addEventListener('waveformChange', updateAudioSender); signalPanel.addEventListener('waveformChange', updateAudioSender);
signalPanel.addEventListener('segmentDurationChange', configurationChanged); signalPanel.addEventListener('segmentDurationChange', (event) => {
signalPanel.addEventListener('amplitudeThresholdChange', configurationChanged); frequencyGraphPanel.setSamplingPeriod(event.value);
configurationChanged();
});
signalPanel.addEventListener('amplitudeThresholdChange', ({value}) => {
frequencyGraphPanel.setAmplitudeThreshold(value);
configurationChanged();
});
signalPanel.addEventListener('smoothingConstantChange', configurationChanged); signalPanel.addEventListener('smoothingConstantChange', configurationChanged);
signalPanel.addEventListener('timeoutChange', () => {
AudioReceiver.setTimeoutMilliseconds(signalPanel.getTimeoutMilliseconds());
})
packetizationPanel.addEventListener('sizePowerChange', configurationChanged); packetizationPanel.addEventListener('sizePowerChange', configurationChanged);
packetizationPanel.addEventListener('interleavingChange', () => { packetizationPanel.addEventListener('interleavingChange', () => {
@@ -137,6 +158,18 @@ function handleWindowLoad() {
}); });
packetizationPanel.addEventListener('errorCorrectionChange', configurationChanged); packetizationPanel.addEventListener('errorCorrectionChange', configurationChanged);
availableFskPairsPanel.addEventListener('change', (event) => {
frequencyGraphPanel.setFskPairs(event.selected);
});
graphConfigurationPanel.addEventListener('pauseAfterEndChange', (event) => {
if(!frequencyGraphPanel.isRunning()) {
frequencyGraphPanel.start();
}
})
graphConfigurationPanel.addEventListener('durationChange', event => {
frequencyGraphPanel.setDurationMilliseconds(graphConfigurationPanel.getDurationMilliseconds());
});
// Setup audio sender // Setup audio sender
AudioSender.addEventListener('begin', () => messagePanel.setSendButtonText('Stop')); AudioSender.addEventListener('begin', () => messagePanel.setSendButtonText('Stop'));
AudioSender.addEventListener('send', handleAudioSenderSend); AudioSender.addEventListener('send', handleAudioSenderSend);
@@ -156,11 +189,6 @@ function handleWindowLoad() {
receivedChannelGraph.addEventListener('mouseout', handleReceivedChannelGraphMouseout); receivedChannelGraph.addEventListener('mouseout', handleReceivedChannelGraphMouseout);
receivedChannelGraph.addEventListener('mousemove', handleReceivedChannelGraphMousemove); receivedChannelGraph.addEventListener('mousemove', handleReceivedChannelGraphMousemove);
receivedChannelGraph.addEventListener('click', handleReceivedChannelGraphClick); receivedChannelGraph.addEventListener('click', handleReceivedChannelGraphClick);
document.getElementById('pause-after-end').checked = PAUSE_AFTER_END;
document.getElementById('pause-after-end').addEventListener('change', event => {
PAUSE_AFTER_END = event.target.checked;
if(!PAUSE_AFTER_END) resumeGraph();
})
document.getElementById('max-bits-displayed-on-graph').value= MAX_BITS_DISPLAYED_ON_GRAPH; document.getElementById('max-bits-displayed-on-graph').value= MAX_BITS_DISPLAYED_ON_GRAPH;
document.getElementById('max-bits-displayed-on-graph').addEventListener('input', (event) => { document.getElementById('max-bits-displayed-on-graph').addEventListener('input', (event) => {
MAX_BITS_DISPLAYED_ON_GRAPH = parseInt(event.target.value); MAX_BITS_DISPLAYED_ON_GRAPH = parseInt(event.target.value);
@@ -185,8 +213,6 @@ function handleAudioSenderSend({bits}) {
} }
function configurationChanged() { function configurationChanged() {
if(analyser) analyser.fftSize = frequencyPanel.getFftSize(); if(analyser) analyser.fftSize = frequencyPanel.getFftSize();
USED_FSK = calculateMultiFrequencyShiftKeying(false);
AVAILABLE_FSK = calculateMultiFrequencyShiftKeying(true);
updatePacketUtils(); updatePacketUtils();
updateStreamManager(); updateStreamManager();
updateAudioSender(); updateAudioSender();
@@ -197,16 +223,14 @@ function configurationChanged() {
} }
function updateAudioSender() { function updateAudioSender() {
AudioSender.changeConfiguration({ AudioSender.changeConfiguration({
channels: USED_FSK, channels: availableFskPairsPanel.getSelectedFskPairs(),
destination: SEND_VIA_SPEAKER ? audioContext.destination : getAnalyser(), destination: SEND_VIA_SPEAKER ? audioContext.destination : getAnalyser(),
waveForm: signalPanel.getWaveform() waveForm: signalPanel.getWaveform()
}); });
} }
const logFn = text => (...args) => {
// console.log(text, ...args);
}
const handleAudioReceiverStart = ({signalStart}) => { const handleAudioReceiverStart = ({signalStart}) => {
StreamManager.reset(); StreamManager.reset();
frequencyGraphPanel.setSignalStart(signalStart);
RECEIVED_STREAM_START_MS = signalStart; RECEIVED_STREAM_START_MS = signalStart;
} }
const handleAudioReceiverReceive = ({signalStart, signalIndex, indexStart, bits}) => { const handleAudioReceiverReceive = ({signalStart, signalIndex, indexStart, bits}) => {
@@ -216,15 +240,17 @@ const handleAudioReceiverReceive = ({signalStart, signalIndex, indexStart, bits}
// console.log(signalIndex, packetIndex, segmentIndex, bits.join('')); // console.log(signalIndex, packetIndex, segmentIndex, bits.join(''));
StreamManager.addBits(packetIndex, segmentIndex, bits); StreamManager.addBits(packetIndex, segmentIndex, bits);
} }
const handleAudioReceiverEnd = () => { const handleAudioReceiverEnd = (e) => {
if(PAUSE_AFTER_END) { frequencyGraphPanel.setSignalEnd(e.signalEnd);
if(graphConfigurationPanel.getPauseAfterEnd()) {
stopGraph(); stopGraph();
frequencyGraphPanel.stop();
AudioSender.stop(); AudioSender.stop();
} }
} }
function updateAudioReceiver() { function updateAudioReceiver() {
AudioReceiver.changeConfiguration({ AudioReceiver.changeConfiguration({
fskSets: USED_FSK, fskSets: availableFskPairsPanel.getSelectedFskPairs(),
amplitudeThreshold: Math.floor(signalPanel.getAmplitudeThreshold() * 255), amplitudeThreshold: Math.floor(signalPanel.getAmplitudeThreshold() * 255),
analyser: getAnalyser(), analyser: getAnalyser(),
signalIntervalMs: signalPanel.getSegmentDuration(), signalIntervalMs: signalPanel.getSegmentDuration(),
@@ -238,7 +264,7 @@ function updateStreamManager() {
StreamManager.changeConfiguration({ StreamManager.changeConfiguration({
bitsPerPacket: PacketUtils.getPacketMaxBitCount(), bitsPerPacket: PacketUtils.getPacketMaxBitCount(),
segmentsPerPacket: PacketUtils.getPacketSegmentCount(), segmentsPerPacket: PacketUtils.getPacketSegmentCount(),
bitsPerSegment: USED_FSK.length, bitsPerSegment: availableFskPairsPanel.getSelectedFskPairs().length,
streamHeaders: { streamHeaders: {
'transfer byte count': { 'transfer byte count': {
index: 0, index: 0,
@@ -255,7 +281,7 @@ function updatePacketUtils() {
PacketUtils.setEncoding( PacketUtils.setEncoding(
packetizationPanel.getErrorCorrection() ? HammingEncoding : undefined packetizationPanel.getErrorCorrection() ? HammingEncoding : undefined
); );
const bitsPerSegment = USED_FSK.length; const bitsPerSegment = availableFskPairsPanel.getSelectedFskPairs().length;
PacketUtils.changeConfiguration({ PacketUtils.changeConfiguration({
segmentDurationMilliseconds: signalPanel.getSegmentDuration(), segmentDurationMilliseconds: signalPanel.getSegmentDuration(),
packetSizeBitCount: packetizationPanel.getSizePower(), packetSizeBitCount: packetizationPanel.getSizePower(),
@@ -314,7 +340,7 @@ function drawChannels() {
const sampleRate = getAudioContext().sampleRate; const sampleRate = getAudioContext().sampleRate;
const fftSize = frequencyPanel.getFftSize(); const fftSize = frequencyPanel.getFftSize();
const frequencyResolution = sampleRate / fftSize; const frequencyResolution = sampleRate / fftSize;
const channels = USED_FSK; const channels = availableFskPairsPanel.getSelectedFskPairs();
const channelCount = channels.length; const channelCount = channels.length;
const canvas = document.getElementById('channel-frequency-graph'); const canvas = document.getElementById('channel-frequency-graph');
const ctx = canvas.getContext('2d'); const ctx = canvas.getContext('2d');
@@ -356,30 +382,7 @@ function percentInFrequency(hz, frequencyResolution) {
const percent = hzInSegement / frequencyResolution; const percent = hzInSegement / frequencyResolution;
return percent; return percent;
} }
function calculateMultiFrequencyShiftKeying(includeExcluded = false) {
var audioContext = getAudioContext();
const sampleRate = audioContext.sampleRate;
const fftSize = frequencyPanel.getFftSize();
const frequencyResolution = sampleRate / fftSize;
const channels = [];
const pairStep = frequencyResolution * (2 + frequencyPanel.getMultiFskPadding()) * frequencyPanel.getFskPadding();
let channelId = -1;
const minimumFrequency = frequencyPanel.getMinimumFrequency();
const maximumFrequency = frequencyPanel.getMaximumFrequency();
for(let hz = minimumFrequency; hz < maximumFrequency; hz+= pairStep) {
const low = hz;
const high = hz + frequencyResolution * frequencyPanel.getFskPadding();
if(low < minimumFrequency) continue;
if(high > maximumFrequency) break;
channelId++;
if(!includeExcluded) {
if(EXCLUDED_CHANNELS.includes(channelId)) continue;
}
channels.push([low, high]);
}
return channels;
}
function logSent(text) { function logSent(text) {
// display what is being sent // display what is being sent
sentDataTextArea.value += text + '\n'; sentDataTextArea.value += text + '\n';
@@ -447,7 +450,7 @@ function sendBytes(bytes) {
resumeGraph(); resumeGraph();
} }
function showSentBits() { function showSentBits() {
const channelCount = USED_FSK.length; const channelCount = availableFskPairsPanel.getSelectedFskPairs().length;
// original bits // original bits
document.getElementById('sent-data').innerHTML = document.getElementById('sent-data').innerHTML =
@@ -474,7 +477,7 @@ function showSentBits() {
), '')); ), ''));
} }
function sendPacket(bits, packetStartSeconds) { function sendPacket(bits, packetStartSeconds) {
const channels = USED_FSK; const channels = availableFskPairsPanel.getSelectedFskPairs();
const channelCount = channels.length; const channelCount = channels.length;
let bitCount = bits.length; let bitCount = bits.length;
const segmentDurationSeconds = PacketUtils.getSegmentDurationSeconds(); const segmentDurationSeconds = PacketUtils.getSegmentDurationSeconds();
@@ -542,7 +545,7 @@ function getTransferredCorrectedBits() {
} }
function handleStreamManagerChange() { function handleStreamManagerChange() {
const channelCount = USED_FSK.length; const channelCount = availableFskPairsPanel.getSelectedFskPairs().length;
let allRawBits = StreamManager.getStreamBits(); let allRawBits = StreamManager.getStreamBits();
let allEncodedBits = StreamManager.getAllPacketBits(); let allEncodedBits = StreamManager.getAllPacketBits();
let allDecodedBits = getTransferredCorrectedBits(); let allDecodedBits = getTransferredCorrectedBits();
@@ -642,7 +645,7 @@ function parseTotalBitsTransferring() {
const dataByteCount = parseTransmissionByteCount(); const dataByteCount = parseTransmissionByteCount();
const bitCount = PacketUtils.getPacketizationBitCountFromByteCount(dataByteCount); const bitCount = PacketUtils.getPacketizationBitCountFromByteCount(dataByteCount);
const segments = getTotalSegmentCount(bitCount); const segments = getTotalSegmentCount(bitCount);
return segments * USED_FSK.length; return segments * availableFskPairsPanel.getSelectedFskPairs().length;
} }
function parseTransmissionByteCountCrc() { function parseTransmissionByteCountCrc() {
let decodedBits = getTransferredCorrectedBits(); let decodedBits = getTransferredCorrectedBits();
@@ -841,6 +844,7 @@ function handleSendButtonClick() {
} else { } else {
AudioReceiver.reset(); AudioReceiver.reset();
StreamManager.reset(); StreamManager.reset();
frequencyGraphPanel.start();
const text = messagePanel.getMessage(); const text = messagePanel.getMessage();
sendBytes(textToBytes(text)); sendBytes(textToBytes(text));
} }
@@ -848,6 +852,7 @@ function handleSendButtonClick() {
function getAnalyser() { function getAnalyser() {
if(analyser) return analyser; if(analyser) return analyser;
analyser = audioContext.createAnalyser(); analyser = audioContext.createAnalyser();
frequencyGraphPanel.setAnalyser(analyser);
analyser.smoothingTimeConstant = signalPanel.getSmoothingTimeConstant(); analyser.smoothingTimeConstant = signalPanel.getSmoothingTimeConstant();
analyser.fftSize = frequencyPanel.getFftSize(); analyser.fftSize = frequencyPanel.getFftSize();
return analyser; return analyser;
@@ -1122,7 +1127,7 @@ function drawChannelData() {
// ended too long ago? // ended too long ago?
if(lastStreamEnded < graphEarliest) return; if(lastStreamEnded < graphEarliest) return;
const channels = USED_FSK; const channels = availableFskPairsPanel.getSelectedFskPairs();
const channelCount = channels.length; const channelCount = channels.length;
const canvas = document.getElementById('received-channel-graph'); const canvas = document.getElementById('received-channel-graph');
@@ -1316,7 +1321,7 @@ function drawSelectedChannel(ctx, channelCount, width, height) {
} }
function drawChannelNumbers(ctx, channelCount, width, height) { function drawChannelNumbers(ctx, channelCount, width, height) {
const offset = 0; const offset = 0;
const channels = USED_FSK; const channels = availableFskPairsPanel.getSelectedFskPairs();
const channelHeight = height / channelCount; const channelHeight = height / channelCount;
const segmentWidth = width / MAX_BITS_DISPLAYED_ON_GRAPH; const segmentWidth = width / MAX_BITS_DISPLAYED_ON_GRAPH;
let fontHeight = Math.min(24, channelHeight, segmentWidth); let fontHeight = Math.min(24, channelHeight, segmentWidth);
@@ -1364,7 +1369,7 @@ function drawFrequencyData(forcedDraw) {
ctx.stroke(); ctx.stroke();
drawBitDurationLines(ctx, 'rgba(255, 255, 0, .25)'); drawBitDurationLines(ctx, 'rgba(255, 255, 0, .25)');
drawBitStart(ctx, 'green'); drawBitStart(ctx, 'green');
const frequencies = USED_FSK; const frequencies = availableFskPairsPanel.getSelectedFskPairs();
const high = 1; const high = 1;
const low = 0 const low = 0
const isSelectedOrOver = CHANNEL_OVER !== -1 || CHANNEL_SELECTED !== -1; const isSelectedOrOver = CHANNEL_OVER !== -1 || CHANNEL_SELECTED !== -1;
@@ -1453,7 +1458,7 @@ function handleReceivedChannelGraphClick(e) {
const {channelIndex, segmentIndex} = getChannelAndSegment(e); const {channelIndex, segmentIndex} = getChannelAndSegment(e);
CHANNEL_SELECTED = channelIndex; CHANNEL_SELECTED = channelIndex;
SEGMENT_SELECTED = segmentIndex; SEGMENT_SELECTED = segmentIndex;
const channels = USED_FSK; const channels = availableFskPairsPanel.getSelectedFskPairs();
const channelCount = channels.length; const channelCount = channels.length;
const selectedSamples = document.getElementById('selected-samples'); const selectedSamples = document.getElementById('selected-samples');
@@ -1561,7 +1566,7 @@ function getChannelAndSegment(e) {
segmentIndex: -1 segmentIndex: -1
}; };
// what channel are we over? // what channel are we over?
const channels = USED_FSK; const channels = availableFskPairsPanel.getSelectedFskPairs();
const channelCount = channels.length; const channelCount = channels.length;
let channelIndex = Math.floor((y / height) * channelCount); let channelIndex = Math.floor((y / height) * channelCount);
if(channelIndex === channelCount) channelIndex--; if(channelIndex === channelCount) channelIndex--;