Split configuration into frequencies and signal
This commit is contained in:
@@ -27,45 +27,93 @@ class BasePanel {
|
|||||||
addCheckboxes = (name, items) => {
|
addCheckboxes = (name, items) => {
|
||||||
this.addCheckedInputs('checkbox', name, items);
|
this.addCheckedInputs('checkbox', name, items);
|
||||||
};
|
};
|
||||||
|
openField = name => this.addText(`${name}: `);
|
||||||
|
addText = text => this.append(document.createTextNode(text));
|
||||||
|
addDynamicText = (id, text) => {
|
||||||
|
const span = document.createElement('span');
|
||||||
|
span.id = this.childId(id);
|
||||||
|
span.innerText = text;
|
||||||
|
return this.append(span);
|
||||||
|
}
|
||||||
|
closeField = () => this.addNewLine();
|
||||||
|
addNewLine = () => this.append(document.createElement('br'));
|
||||||
|
addDropdown = (id, items, eventName = 'change') => {
|
||||||
|
const select = document.createElement('select');
|
||||||
|
select.id = this.childId(id);
|
||||||
|
select.addEventListener('change', (e) => {
|
||||||
|
const values = [...select.selectedOptions].map(option => option.value);
|
||||||
|
this.dispatcher.emit(eventName, {
|
||||||
|
id,
|
||||||
|
values
|
||||||
|
});
|
||||||
|
});
|
||||||
|
items.forEach((item, i) => {
|
||||||
|
const option = document.createElement('option');
|
||||||
|
option.value = item.value;
|
||||||
|
option.text = item.text;
|
||||||
|
if(item.selected) {
|
||||||
|
if(select.selectedIndex === -1) {
|
||||||
|
select.selectedIndex = i;
|
||||||
|
}
|
||||||
|
option.selected = item.selected;
|
||||||
|
}
|
||||||
|
select.append(option);
|
||||||
|
});
|
||||||
|
if(select.selectedIndex === -1 && items.length !== 0) {
|
||||||
|
select.selectedIndex = 0;
|
||||||
|
}
|
||||||
|
this.append(select);
|
||||||
|
}
|
||||||
addCheckedInputs = (type, name, items, value) => {
|
addCheckedInputs = (type, name, items, value) => {
|
||||||
items.forEach(({id, text, checked = false, eventName = 'change'}, index)=> {
|
items.forEach(({id, text, checked = false, eventName = 'change'})=> {
|
||||||
const label = document.createElement('label');
|
const label = document.createElement('label');
|
||||||
const input = document.createElement('input');
|
label.for = this.childId(id);
|
||||||
input.type = type;
|
const input = this.createInput(id, value, {name, checked, type, eventName});
|
||||||
input.name = name;
|
|
||||||
input.checked = checked;
|
|
||||||
input.value = value;
|
|
||||||
input.id = this.childId(id);
|
|
||||||
input.addEventListener('change', e => {
|
|
||||||
this.dispatcher.emit(eventName, {
|
|
||||||
name,
|
|
||||||
id,
|
|
||||||
index,
|
|
||||||
checked: e.target.checked,
|
|
||||||
value
|
|
||||||
});
|
|
||||||
})
|
|
||||||
label.appendChild(input);
|
label.appendChild(input);
|
||||||
const textNode = document.createTextNode(text);
|
const textNode = document.createTextNode(text);
|
||||||
label.append(textNode);
|
label.append(textNode);
|
||||||
this.append(label);
|
this.append(label);
|
||||||
const br = document.createElement('br');
|
this.addNewLine();
|
||||||
this.append(br);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
addInputText = (id, value, eventName = 'input') => {
|
addInputText = (id, value, options = {}) => {
|
||||||
|
this.append(this.createInput(id, value, {...options, type: 'text'}));
|
||||||
|
}
|
||||||
|
addInputNumber = (id, value, options = {}) => {
|
||||||
|
this.append(this.createInput(id, value, {...options, type: 'number'}));
|
||||||
|
}
|
||||||
|
createInput = (id, value = '', options = {}) => {
|
||||||
|
const {
|
||||||
|
eventName = 'input',
|
||||||
|
type = 'text',
|
||||||
|
translation,
|
||||||
|
...attr
|
||||||
|
} = options;
|
||||||
const input = document.createElement('input');
|
const input = document.createElement('input');
|
||||||
input.type = 'text';
|
|
||||||
input.value = value;
|
input.value = value;
|
||||||
input.id = this.childId(id);
|
input.id = this.childId(id);
|
||||||
input.addEventListener('input', e => {
|
input.type = type;
|
||||||
this.dispatcher.emit(eventName, {
|
if(['radio', 'checkbox'].includes(type)) {
|
||||||
panel: this.id,
|
input.addEventListener('change', e => {
|
||||||
id,
|
this.dispatcher.emit(eventName, {
|
||||||
value
|
id,
|
||||||
|
checked: e.target.checked,
|
||||||
|
value
|
||||||
|
});
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
input.addEventListener('input', e => {
|
||||||
|
this.dispatcher.emit(eventName, {
|
||||||
|
panel: this.id,
|
||||||
|
id,
|
||||||
|
value: translateValue(e.target.value, translation)
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
}
|
||||||
this.append(input);
|
Object.keys(attr).forEach(key => {
|
||||||
|
input[key] = options[key];
|
||||||
|
})
|
||||||
|
return input;
|
||||||
}
|
}
|
||||||
addButton = (id, text, eventName = 'click') => {
|
addButton = (id, text, eventName = 'click') => {
|
||||||
const button = document.createElement('button');
|
const button = document.createElement('button');
|
||||||
@@ -80,6 +128,13 @@ class BasePanel {
|
|||||||
});
|
});
|
||||||
this.append(button);
|
this.append(button);
|
||||||
}
|
}
|
||||||
|
addCanvas = (id, width, height) => {
|
||||||
|
const canvas = document.createElement('canvas');
|
||||||
|
canvas.id = this.childId(id);
|
||||||
|
canvas.width = width;
|
||||||
|
canvas.height = height;
|
||||||
|
return this.append(canvas);
|
||||||
|
}
|
||||||
addProgressBar = (id, percent) => {
|
addProgressBar = (id, percent) => {
|
||||||
const progressBar = document.createElement('div');
|
const progressBar = document.createElement('div');
|
||||||
progressBar.className = 'progress-container';
|
progressBar.className = 'progress-container';
|
||||||
@@ -96,8 +151,10 @@ class BasePanel {
|
|||||||
element.style.width = `${clamp(percent, 0, 1) * 100}%`;
|
element.style.width = `${clamp(percent, 0, 1) * 100}%`;
|
||||||
}
|
}
|
||||||
childId = id => `${this.id}-${id}`;
|
childId = id => `${this.id}-${id}`;
|
||||||
|
|
||||||
getElement = id => {
|
getElement = id => {
|
||||||
const element = document.getElementById(this.childId(id));
|
const element = this.getDomElement().querySelector(`#${this.childId(id)}`);
|
||||||
|
// const element = document.getElementById(this.childId(id));
|
||||||
if(!element) throw new Error(`Unable to find ${id}`);
|
if(!element) throw new Error(`Unable to find ${id}`);
|
||||||
return element;
|
return element;
|
||||||
}
|
}
|
||||||
@@ -142,5 +199,15 @@ class BasePanel {
|
|||||||
addEventListener = (eventName, callback) => this.dispatcher.addListener(eventName, callback);
|
addEventListener = (eventName, callback) => this.dispatcher.addListener(eventName, callback);
|
||||||
removeEventListener = (eventName, callback) => this.dispatcher.removeListener(eventName, callback);
|
removeEventListener = (eventName, callback) => this.dispatcher.removeListener(eventName, callback);
|
||||||
}
|
}
|
||||||
|
const translateValue = (value, translation) => {
|
||||||
|
if(!translation) return value;
|
||||||
|
if(translation === 'percent') {
|
||||||
|
return parseInt(value) / 100;
|
||||||
|
} else if(translation === 'power of 2') {
|
||||||
|
return 2 ** parseInt(value);
|
||||||
|
}
|
||||||
|
console.warn('Unknown translation', translation)
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
export default BasePanel;
|
export default BasePanel;
|
||||||
176
Panels/FrequencyPanel.js
Normal file
176
Panels/FrequencyPanel.js
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
import BasePanel from './BasePanel';
|
||||||
|
|
||||||
|
class FrequencyPanel extends BasePanel {
|
||||||
|
constructor(sampleRate = 48000) {
|
||||||
|
super('Frequencies');
|
||||||
|
|
||||||
|
this.sampleRate = sampleRate;
|
||||||
|
const ultimateFrequency = sampleRate / 2;
|
||||||
|
|
||||||
|
this.openField('Minimum');
|
||||||
|
this.addInputNumber('minimum-frequency', 0, {min: 0, max: ultimateFrequency, eventName: 'minimumFrequencyChange'});
|
||||||
|
this.closeField();
|
||||||
|
|
||||||
|
this.openField('Maximum');
|
||||||
|
this.addInputNumber('maximum-frequency', ultimateFrequency, {min: 0, max: ultimateFrequency, eventName: 'maximumFrequencyChange'});
|
||||||
|
this.closeField();
|
||||||
|
|
||||||
|
this.openField('FFT Size');
|
||||||
|
this.addText('2^');
|
||||||
|
this.addInputNumber('fft-power', 10, {min: 5, max: 15, eventName: 'fftSizeChange', translation: 'power of 2'});
|
||||||
|
this.addText(' ');
|
||||||
|
this.addDynamicText('fft-size', 'N/A');
|
||||||
|
this.closeField();
|
||||||
|
|
||||||
|
this.openField('Frequency Resolution');
|
||||||
|
this.addDynamicText('frequency-resolution-size', 'N/A');
|
||||||
|
this.addText(' Hz');
|
||||||
|
this.closeField();
|
||||||
|
|
||||||
|
this.openField('FSK Padding');
|
||||||
|
this.addInputNumber('fsk-padding', 1, {min: 1, max: 20, eventName: 'fskPaddingChange'});
|
||||||
|
this.closeField();
|
||||||
|
|
||||||
|
this.openField('Multi-FSK Padding');
|
||||||
|
this.addInputNumber('multi-fsk-padding', 0, {min: 0, max: 20, eventName: 'multiFskPaddingChange'});
|
||||||
|
this.closeField();
|
||||||
|
|
||||||
|
this.addCanvas('frequency-spectrum', 200, 32);
|
||||||
|
this.addNewLine();
|
||||||
|
|
||||||
|
this.openField('FSK Pairs Available');
|
||||||
|
this.addDynamicText('fsk-count', 'N/A');
|
||||||
|
this.closeField();
|
||||||
|
|
||||||
|
this.addEventListener('multiFskPaddingChange', this.checkFskPairsChanged);
|
||||||
|
this.addEventListener('fskPaddingChange', this.checkFskPairsChanged);
|
||||||
|
this.addEventListener('fftSizeChange', this.checkFskPairsChanged);
|
||||||
|
this.addEventListener('fftSizeChange', this.handleFftSizeChanged);
|
||||||
|
this.addEventListener('maximumFrequencyChange', this.checkFskPairsChanged);
|
||||||
|
this.addEventListener('minimumFrequencyChange', this.checkFskPairsChanged);
|
||||||
|
this.originalFskPairs = this.getFskPairs();
|
||||||
|
this.drawFrequencySpectrum();
|
||||||
|
};
|
||||||
|
getMinimumFrequency = () => parseInt(this.getValueById('minimum-frequency'));
|
||||||
|
setMinimumFrequency = value => {
|
||||||
|
this.setValueById('minimum-frequency', value);
|
||||||
|
this.checkFskPairsChanged();
|
||||||
|
};
|
||||||
|
|
||||||
|
getMaximumFrequency = () => parseInt(this.getValueById('maximum-frequency'));
|
||||||
|
setMaximumFrequency = value => {
|
||||||
|
this.setValueById('maximum-frequency', value);
|
||||||
|
this.checkFskPairsChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
getFftSize = () => 2 ** parseInt(this.getValueById('fft-power'));
|
||||||
|
setFftSize = (value) => {
|
||||||
|
this.setValueById('fft-power', Math.log2(value));
|
||||||
|
this.handleFftSizeChanged();
|
||||||
|
this.checkFskPairsChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
handleFftSizeChanged = () => {
|
||||||
|
const fftSize = this.getFftSize();
|
||||||
|
this.setValueById('fft-size', fftSize.toLocaleString());
|
||||||
|
const resolution = this.sampleRate / fftSize;
|
||||||
|
this.setValueById('frequency-resolution-size', parseFloat(resolution.toFixed(1)).toLocaleString());
|
||||||
|
}
|
||||||
|
|
||||||
|
getFskPadding = () => parseInt(this.getValueById('fsk-padding'));
|
||||||
|
setFskPadding = (value) => {
|
||||||
|
this.setValueById('fsk-padding', value);
|
||||||
|
this.checkFskPairsChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
getMultiFskPadding = () => parseInt(this.getValueById('multi-fsk-padding'));
|
||||||
|
setMultiFskPadding = (value) => {
|
||||||
|
this.setValueById('multi-fsk-padding', value);
|
||||||
|
this.checkFskPairsChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
checkFskPairsChanged = () => {
|
||||||
|
const original = this.originalFskPairs;
|
||||||
|
const current = this.getFskPairs();
|
||||||
|
let changed = true;
|
||||||
|
if(original.length !== current.length) {
|
||||||
|
changed = true;
|
||||||
|
} else {
|
||||||
|
changed = original.some(
|
||||||
|
(fsk, fskIndex) => {
|
||||||
|
return fsk.some((hz, hzIndex) => hz !== original[fskIndex][hzIndex]);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if(changed) {
|
||||||
|
this.originalFskPairs = current;
|
||||||
|
this.setValueById('fsk-count', current.length.toLocaleString());
|
||||||
|
this.drawFrequencySpectrum();
|
||||||
|
this.dispatcher.emit('fskPairsChange', {value: current});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
getFskPairs = () => {
|
||||||
|
const sampleRate = this.sampleRate;
|
||||||
|
const fftSize = this.getFftSize();
|
||||||
|
const fskPadding = this.getFskPadding();
|
||||||
|
const multiFskPadding = this.getMultiFskPadding();
|
||||||
|
const frequencyResolution = sampleRate / fftSize;
|
||||||
|
const fskPairs = [];
|
||||||
|
const multiFskStep = frequencyResolution * (2 + multiFskPadding) * fskPadding;
|
||||||
|
const minimumFrequency = this.getMinimumFrequency();
|
||||||
|
const maximumFrequency = this.getMaximumFrequency();
|
||||||
|
for(let hz = minimumFrequency; hz < maximumFrequency; hz+= multiFskStep) {
|
||||||
|
const lowHz = hz;
|
||||||
|
const highHz = hz + frequencyResolution * fskPadding;
|
||||||
|
if(lowHz < minimumFrequency) continue;
|
||||||
|
if(highHz > maximumFrequency) break;
|
||||||
|
fskPairs.push([lowHz, highHz]);
|
||||||
|
}
|
||||||
|
return fskPairs;
|
||||||
|
}
|
||||||
|
drawFrequencySpectrum = () => {
|
||||||
|
const ultimateFrequency = this.sampleRate / 2;
|
||||||
|
const fskPairs = this.getFskPairs();
|
||||||
|
const canvas = this.getElement('frequency-spectrum');
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
const {height, width} = canvas;
|
||||||
|
ctx.clearRect(0, 0, width, height);
|
||||||
|
|
||||||
|
// Human Hearing
|
||||||
|
let x1 = (20/ultimateFrequency) * width;
|
||||||
|
let x2 = (20000/ultimateFrequency) * width;
|
||||||
|
ctx.fillStyle = 'hsla(0, 0%, 100%, 20%)';
|
||||||
|
ctx.fillRect(
|
||||||
|
x1,
|
||||||
|
0,
|
||||||
|
x2 - x1,
|
||||||
|
height
|
||||||
|
);
|
||||||
|
// Telephone
|
||||||
|
x1 = (300/ultimateFrequency) * width;
|
||||||
|
x2 = (3400/ultimateFrequency) * width;
|
||||||
|
ctx.fillStyle = 'hsla(60, 50%, 50%, 20%)';
|
||||||
|
ctx.fillRect(
|
||||||
|
x1,
|
||||||
|
0,
|
||||||
|
x2 - x1,
|
||||||
|
height
|
||||||
|
);
|
||||||
|
|
||||||
|
ctx.lineWith = 1;
|
||||||
|
const plotHz = hz => {
|
||||||
|
const percent = (hz / ultimateFrequency);
|
||||||
|
const hue = Math.floor(percent * 360);
|
||||||
|
ctx.strokeStyle = `hsl(${hue}, 100%, 50%)`;
|
||||||
|
const x = percent * width;
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(x, 0);
|
||||||
|
ctx.lineTo(x, height);
|
||||||
|
ctx.stroke();
|
||||||
|
}
|
||||||
|
|
||||||
|
fskPairs.forEach(fsk => fsk.forEach(plotHz));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default FrequencyPanel;
|
||||||
50
Panels/SignalPanel.js
Normal file
50
Panels/SignalPanel.js
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
import BasePanel from './BasePanel';
|
||||||
|
|
||||||
|
const clamp = (value, min, max) => Math.max(min, Math.min(value, max));
|
||||||
|
|
||||||
|
class SignalPanel extends BasePanel {
|
||||||
|
constructor() {
|
||||||
|
super('Signal');
|
||||||
|
this.openField('Segment Duration');
|
||||||
|
this.addInputNumber('segment-duration', 100, {min: 0, max: 1000, eventName: 'segmentDurationChange'});
|
||||||
|
this.addText('ms');
|
||||||
|
this.closeField();
|
||||||
|
|
||||||
|
this.addSection('Sending');
|
||||||
|
|
||||||
|
this.openField('Wave Form');
|
||||||
|
this.addDropdown('wave-form', [
|
||||||
|
{ text: 'Sine Wave', value: 'sine'},
|
||||||
|
{ text: 'Square Wave', value: 'square'},
|
||||||
|
{ text: 'Sawtooth Wave', value: 'sawtooth'},
|
||||||
|
{ text: 'Triangle Wave', value: 'triangle'},
|
||||||
|
], {eventName: 'waveformChange'});
|
||||||
|
this.closeField();
|
||||||
|
|
||||||
|
this.addSection('Receiving');
|
||||||
|
|
||||||
|
this.openField('Amplitude Threshold');
|
||||||
|
this.addInputNumber('amplitude-threshold', 50, {min: 0, max: 100, eventName: 'amplitudeThresholdChange', translation: 'percent'});
|
||||||
|
this.addText('%');
|
||||||
|
this.closeField();
|
||||||
|
|
||||||
|
this.openField('Smoothing Time Constant');
|
||||||
|
this.addInputNumber('smoothing-time-constant', 0, {min: 0, max: 100, eventName: 'smothingTimeConstantChange', translation: 'percent'});
|
||||||
|
this.addText('%');
|
||||||
|
this.closeField();
|
||||||
|
};
|
||||||
|
|
||||||
|
getWaveform = () => this.getValueById('wave-form');
|
||||||
|
setWaveform = (value) => this.setValueById('wave-form', value);
|
||||||
|
|
||||||
|
getSegmentDuration = () => parseInt(this.getValueById('segment-duration'));
|
||||||
|
setSegmentDuration = value => this.setValueById('segment-duration', value);
|
||||||
|
|
||||||
|
getAmplitudeThreshold = () => parseInt(this.getValueById('amplitude-threshold')) / 100;
|
||||||
|
setAmplitudeThreshold = value => this.setValueById('amplitude-threshold', clamp(value * 100, 0, 100));
|
||||||
|
|
||||||
|
getSmoothingTimeConstant = () => parseInt(this.getValueById('smoothing-time-constant')) / 100;
|
||||||
|
setSmoothingTimeConstant = value => this.setValueById('smoothing-time-constant', clamp(value * 100, 0, 100));
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SignalPanel;
|
||||||
24
index.html
24
index.html
@@ -11,34 +11,12 @@
|
|||||||
<h1>Data Over Audio</h1>
|
<h1>Data Over Audio</h1>
|
||||||
<div class="panels" id="panel-container">
|
<div class="panels" id="panel-container">
|
||||||
<div>
|
<div>
|
||||||
<h2>Configuration</h2>
|
<h2>Packetizaton</h2>
|
||||||
<div>
|
<div>
|
||||||
<h4>Audio</h4>
|
|
||||||
Wave Form: <select id="wave-form" value="sine">
|
|
||||||
<option value="sine">Sine Wave</option>
|
|
||||||
<option value="square">Square Wave</option>
|
|
||||||
<option value="sawtooth">Sawtooth Wave</option>
|
|
||||||
<option value="triangle">Triangle Wave</option>
|
|
||||||
</select><br>
|
|
||||||
Minimum Frequency: <input id="minimum-frequency" type="number" min="20" max="20000" value="900"><br>
|
|
||||||
Maximum Frequency: <input id="maximum-frequency" type="number" min="20" max="20000" value="1200"><br>
|
|
||||||
Segment Duration: <input id="bit-duration-text" type="number" min="0" max="1000" value="190">ms<br>
|
|
||||||
FFT Size: 2^<input id="fft-size-power-text" type="number" min="5" max="15" value="90"><br>
|
|
||||||
Frequency Resolution Multiplier: <input id="frequency-resolution-multiplier" type="number" min="1" max="20"
|
|
||||||
value="2"><br>
|
|
||||||
Channel Frequency Resolution Padding: <input id="channel-frequency-resolution-padding" type="number" min="0"
|
|
||||||
max="20"><br>
|
|
||||||
|
|
||||||
<h4>Packetization</h4>
|
|
||||||
Packet Size:
|
Packet Size:
|
||||||
2^<input id="packet-size-power" type="number" min="0" max="16">
|
2^<input id="packet-size-power" type="number" min="0" max="16">
|
||||||
<span id="packet-size"></span>
|
<span id="packet-size"></span>
|
||||||
<br>
|
<br>
|
||||||
<h4>Receiving</h4>
|
|
||||||
Amplitude Threshold: <input id="amplitude-threshold-text" type="number" min="0" max="100" value="75"><br>
|
|
||||||
Last Segment Percent: <input id="last-bit-percent" type="number" min="0" max="100" value="90">%<br>
|
|
||||||
Smoothing Time Constant: <input id="smoothing-time-constant-text" type="number" min="0.00" max="1.00"
|
|
||||||
step="0.01" value="0.00"><br>
|
|
||||||
<h4>Encoding</h4>
|
<h4>Encoding</h4>
|
||||||
<label>
|
<label>
|
||||||
<input type="checkbox" id="periodic-interleaving" checked>Interleaving
|
<input type="checkbox" id="periodic-interleaving" checked>Interleaving
|
||||||
|
|||||||
235
index.js
235
index.js
@@ -6,20 +6,20 @@ import * as Humanize from './Humanize';
|
|||||||
import * as Randomizer from './Randomizer';
|
import * as Randomizer from './Randomizer';
|
||||||
import * as AudioSender from './AudioSender';
|
import * as AudioSender from './AudioSender';
|
||||||
import * as AudioReceiver from './AudioReceiver';
|
import * as AudioReceiver from './AudioReceiver';
|
||||||
import * as CRC from './CRC.js';
|
import * as CRC from './CRC';
|
||||||
import CommunicationsPanel from './Panels/CommunicationsPanel';
|
import CommunicationsPanel from './Panels/CommunicationsPanel';
|
||||||
import MessagePanel from "./Panels/MessagePanel.js";
|
import MessagePanel from "./Panels/MessagePanel";
|
||||||
import CodePanel from "./Panels/CodePanel.js";
|
import CodePanel from "./Panels/CodePanel";
|
||||||
|
import FrequencyPanel from "./Panels/FrequencyPanel";
|
||||||
|
import SignalPanel from "./Panels/SignalPanel";
|
||||||
|
|
||||||
var audioContext;
|
var audioContext;
|
||||||
var microphoneStream;
|
var microphoneStream;
|
||||||
var microphoneNode;
|
var microphoneNode;
|
||||||
var analyser;
|
var analyser;
|
||||||
var receivedDataTextarea;
|
|
||||||
var sentDataTextArea;
|
var sentDataTextArea;
|
||||||
var receivedGraph;
|
var receivedGraph;
|
||||||
var receivedData = [];
|
var receivedData = [];
|
||||||
var MAX_AMPLITUDE = 300; // Higher than 255 to give us space
|
|
||||||
const MAXIMUM_PACKETIZATION_SIZE_BITS = 16;
|
const MAXIMUM_PACKETIZATION_SIZE_BITS = 16;
|
||||||
const CRC_BIT_COUNT = 8;
|
const CRC_BIT_COUNT = 8;
|
||||||
|
|
||||||
@@ -32,19 +32,8 @@ let SENT_TRANSFER_BITS = []; // bits sent in the transfer
|
|||||||
let EXCLUDED_CHANNELS = [];
|
let EXCLUDED_CHANNELS = [];
|
||||||
|
|
||||||
var MAX_BITS_DISPLAYED_ON_GRAPH = 79;
|
var MAX_BITS_DISPLAYED_ON_GRAPH = 79;
|
||||||
var SEGMENT_DURATION = 30;
|
|
||||||
var AMPLITUDE_THRESHOLD_PERCENT = .75;
|
|
||||||
var AMPLITUDE_THRESHOLD = 160;
|
|
||||||
var MINIMUM_FREQUENCY = 9000;
|
|
||||||
var MAXIMUM_FREQUENCY = 15000;
|
|
||||||
var LAST_SEGMENT_PERCENT = 0.6;
|
|
||||||
var FFT_SIZE_POWER = 9;
|
|
||||||
var FREQUENCY_RESOLUTION_MULTIPLIER = 3;
|
|
||||||
let CHANNEL_FREQUENCY_RESOLUTION_PADDING = 1;
|
|
||||||
var SMOOTHING_TIME_CONSTANT = 0;
|
|
||||||
var HAMMING_ERROR_CORRECTION = true;
|
var HAMMING_ERROR_CORRECTION = true;
|
||||||
let PERIODIC_INTERLEAVING = true;
|
let PERIODIC_INTERLEAVING = true;
|
||||||
let WAVE_FORM = "triangle";
|
|
||||||
|
|
||||||
const ERROR_CORRECTION_BLOCK_SIZE = 7;
|
const ERROR_CORRECTION_BLOCK_SIZE = 7;
|
||||||
const ERROR_CORRECTION_DATA_SIZE = 4;
|
const ERROR_CORRECTION_DATA_SIZE = 4;
|
||||||
@@ -62,13 +51,20 @@ var PAUSE = false;
|
|||||||
var PAUSE_AFTER_END = true;
|
var PAUSE_AFTER_END = true;
|
||||||
var PACKET_SIZE_BITS = 5; // 32 bytes, 256 bits
|
var PACKET_SIZE_BITS = 5; // 32 bytes, 256 bits
|
||||||
|
|
||||||
|
let USED_FSK = [];
|
||||||
|
let AVAILABLE_FSK = [];
|
||||||
|
|
||||||
const communicationsPanel = new CommunicationsPanel();
|
const communicationsPanel = new CommunicationsPanel();
|
||||||
const messagePanel = new MessagePanel();
|
const messagePanel = new MessagePanel();
|
||||||
const bitsSentPanel = new CodePanel('Bits Sent');
|
const bitsSentPanel = new CodePanel('Bits Sent');
|
||||||
const bitsReceivedPanel = new CodePanel('Bits Received');
|
const bitsReceivedPanel = new CodePanel('Bits Received');
|
||||||
|
const frequencyPanel = new FrequencyPanel();
|
||||||
|
const signalPanel = new SignalPanel();
|
||||||
|
|
||||||
function handleWindowLoad() {
|
function handleWindowLoad() {
|
||||||
const panelContainer = document.getElementById('panel-container');
|
const panelContainer = document.getElementById('panel-container');
|
||||||
|
panelContainer.prepend(signalPanel.getDomElement());
|
||||||
|
panelContainer.prepend(frequencyPanel.getDomElement());
|
||||||
panelContainer.prepend(bitsReceivedPanel.getDomElement());
|
panelContainer.prepend(bitsReceivedPanel.getDomElement());
|
||||||
panelContainer.prepend(bitsSentPanel.getDomElement());
|
panelContainer.prepend(bitsSentPanel.getDomElement());
|
||||||
panelContainer.prepend(messagePanel.getDomElement());
|
panelContainer.prepend(messagePanel.getDomElement());
|
||||||
@@ -87,6 +83,17 @@ function handleWindowLoad() {
|
|||||||
bitsSentPanel.setCode('');
|
bitsSentPanel.setCode('');
|
||||||
bitsReceivedPanel.setCode('');
|
bitsReceivedPanel.setCode('');
|
||||||
|
|
||||||
|
frequencyPanel.setMinimumFrequency(9000);
|
||||||
|
frequencyPanel.setMaximumFrequency(15000);
|
||||||
|
frequencyPanel.setFftSize(2 ** 9);
|
||||||
|
frequencyPanel.setFskPadding(1);
|
||||||
|
frequencyPanel.setMultiFskPadding(1);
|
||||||
|
|
||||||
|
signalPanel.setWaveform('triangle');
|
||||||
|
signalPanel.setSegmentDuration(30);
|
||||||
|
signalPanel.setAmplitudeThreshold(0.78);
|
||||||
|
signalPanel.setSmoothingTimeConstant(0);
|
||||||
|
|
||||||
// Communications Events
|
// Communications Events
|
||||||
communicationsPanel.addEventListener('listeningChange', handleChangeListening);
|
communicationsPanel.addEventListener('listeningChange', handleChangeListening);
|
||||||
communicationsPanel.addEventListener('sendSpeakersChange', handleChangeSendSpeakers);
|
communicationsPanel.addEventListener('sendSpeakersChange', handleChangeSendSpeakers);
|
||||||
@@ -95,6 +102,20 @@ function handleWindowLoad() {
|
|||||||
messagePanel.addEventListener('messageChange', configurationChanged);
|
messagePanel.addEventListener('messageChange', configurationChanged);
|
||||||
messagePanel.addEventListener('send', handleSendButtonClick);
|
messagePanel.addEventListener('send', handleSendButtonClick);
|
||||||
|
|
||||||
|
frequencyPanel.addEventListener('minimumFrequencyChange', configurationChanged);
|
||||||
|
frequencyPanel.addEventListener('maximumFrequencyChange', configurationChanged);
|
||||||
|
frequencyPanel.addEventListener('fftSizeChange', ({value}) => {
|
||||||
|
configurationChanged();
|
||||||
|
resetGraphData();
|
||||||
|
});
|
||||||
|
frequencyPanel.addEventListener('fskPaddingChange', configurationChanged);
|
||||||
|
frequencyPanel.addEventListener('multiFskPaddingChange', configurationChanged);
|
||||||
|
|
||||||
|
signalPanel.addEventListener('waveformChange', updateAudioSender);
|
||||||
|
signalPanel.addEventListener('segmentDurationChange', configurationChanged);
|
||||||
|
signalPanel.addEventListener('amplitudeThresholdChange', configurationChanged);
|
||||||
|
signalPanel.addEventListener('smoothingConstantChange', configurationChanged);
|
||||||
|
|
||||||
// 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);
|
||||||
@@ -107,7 +128,6 @@ function handleWindowLoad() {
|
|||||||
StreamManager.addEventListener('change', handleStreamManagerChange);
|
StreamManager.addEventListener('change', handleStreamManagerChange);
|
||||||
|
|
||||||
// grab dom elements
|
// grab dom elements
|
||||||
receivedDataTextarea = document.getElementById('received-data');
|
|
||||||
receivedGraph = document.getElementById('received-graph');
|
receivedGraph = document.getElementById('received-graph');
|
||||||
sentDataTextArea = document.getElementById('sent-data');
|
sentDataTextArea = document.getElementById('sent-data');
|
||||||
const receivedChannelGraph = document.getElementById('received-channel-graph');
|
const receivedChannelGraph = document.getElementById('received-channel-graph');
|
||||||
@@ -115,11 +135,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('wave-form').value = WAVE_FORM;
|
|
||||||
document.getElementById('wave-form').addEventListener('change', (event) => {
|
|
||||||
WAVE_FORM = event.target.value;
|
|
||||||
configurationChanged();
|
|
||||||
});
|
|
||||||
document.getElementById('packet-size-power').value = PACKET_SIZE_BITS;
|
document.getElementById('packet-size-power').value = PACKET_SIZE_BITS;
|
||||||
document.getElementById('packet-size').innerText = Humanize.byteSize(2 ** PACKET_SIZE_BITS);
|
document.getElementById('packet-size').innerText = Humanize.byteSize(2 ** PACKET_SIZE_BITS);
|
||||||
document.getElementById('packet-size-power').addEventListener('input', event => {
|
document.getElementById('packet-size-power').addEventListener('input', event => {
|
||||||
@@ -145,59 +160,10 @@ function handleWindowLoad() {
|
|||||||
PAUSE_AFTER_END = event.target.checked;
|
PAUSE_AFTER_END = event.target.checked;
|
||||||
if(!PAUSE_AFTER_END) resumeGraph();
|
if(!PAUSE_AFTER_END) resumeGraph();
|
||||||
})
|
})
|
||||||
document.getElementById('frequency-resolution-multiplier').value = FREQUENCY_RESOLUTION_MULTIPLIER;
|
|
||||||
document.getElementById('frequency-resolution-multiplier').addEventListener('input', event => {
|
|
||||||
FREQUENCY_RESOLUTION_MULTIPLIER = parseInt(event.target.value);
|
|
||||||
configurationChanged();
|
|
||||||
})
|
|
||||||
document.getElementById('channel-frequency-resolution-padding').value = CHANNEL_FREQUENCY_RESOLUTION_PADDING;
|
|
||||||
document.getElementById('channel-frequency-resolution-padding').addEventListener('input', event => {
|
|
||||||
CHANNEL_FREQUENCY_RESOLUTION_PADDING = parseInt(event.target.value);
|
|
||||||
configurationChanged();
|
|
||||||
})
|
|
||||||
document.getElementById('bit-duration-text').addEventListener('input', (event) => {
|
|
||||||
SEGMENT_DURATION = parseInt(event.target.value);
|
|
||||||
configurationChanged();
|
|
||||||
});
|
|
||||||
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);
|
||||||
})
|
})
|
||||||
document.getElementById('bit-duration-text').value = SEGMENT_DURATION;
|
|
||||||
document.getElementById('amplitude-threshold-text').value = Math.floor(AMPLITUDE_THRESHOLD_PERCENT * 100);
|
|
||||||
AMPLITUDE_THRESHOLD = Math.floor(AMPLITUDE_THRESHOLD_PERCENT * 255);
|
|
||||||
document.getElementById('maximum-frequency').value = MAXIMUM_FREQUENCY;
|
|
||||||
document.getElementById('minimum-frequency').value = MINIMUM_FREQUENCY;
|
|
||||||
document.getElementById('last-bit-percent').value = Math.floor(LAST_SEGMENT_PERCENT * 100);
|
|
||||||
document.getElementById('fft-size-power-text').value = FFT_SIZE_POWER;
|
|
||||||
document.getElementById('smoothing-time-constant-text').value = SMOOTHING_TIME_CONSTANT.toFixed(2);
|
|
||||||
|
|
||||||
document.getElementById('amplitude-threshold-text').addEventListener('input', (event) => {
|
|
||||||
AMPLITUDE_THRESHOLD_PERCENT = parseInt(event.target.value) / 100;
|
|
||||||
AMPLITUDE_THRESHOLD = Math.floor(AMPLITUDE_THRESHOLD_PERCENT * 255);
|
|
||||||
configurationChanged();
|
|
||||||
});
|
|
||||||
document.getElementById('maximum-frequency').addEventListener('input', (event) => {
|
|
||||||
MAXIMUM_FREQUENCY = parseInt(event.target.value);
|
|
||||||
configurationChanged();
|
|
||||||
});
|
|
||||||
document.getElementById('minimum-frequency').addEventListener('input', (event) => {
|
|
||||||
MINIMUM_FREQUENCY = parseInt(event.target.value);
|
|
||||||
configurationChanged();
|
|
||||||
});
|
|
||||||
document.getElementById('last-bit-percent').addEventListener('input', (event) => {
|
|
||||||
LAST_SEGMENT_PERCENT = parseInt(event.target.value) / 100;
|
|
||||||
});
|
|
||||||
document.getElementById('fft-size-power-text').addEventListener('input', (event) => {
|
|
||||||
FFT_SIZE_POWER = parseInt(event.target.value);
|
|
||||||
if(analyser) analyser.fftSize = 2 ** FFT_SIZE_POWER;
|
|
||||||
configurationChanged();
|
|
||||||
resetGraphData();
|
|
||||||
});
|
|
||||||
document.getElementById('smoothing-time-constant-text').addEventListener('input', event => {
|
|
||||||
SMOOTHING_TIME_CONSTANT = parseFloat(event.target.value);
|
|
||||||
if(analyser) analyser.smoothingTimeConstant = SMOOTHING_TIME_CONSTANT;
|
|
||||||
});
|
|
||||||
document.getElementById('audio-context-sample-rate').innerText = getAudioContext().sampleRate.toLocaleString();
|
document.getElementById('audio-context-sample-rate').innerText = getAudioContext().sampleRate.toLocaleString();
|
||||||
// wire up events
|
// wire up events
|
||||||
configurationChanged();
|
configurationChanged();
|
||||||
@@ -205,7 +171,7 @@ function handleWindowLoad() {
|
|||||||
|
|
||||||
function updateFrequencyResolution() {
|
function updateFrequencyResolution() {
|
||||||
const sampleRate = getAudioContext().sampleRate;
|
const sampleRate = getAudioContext().sampleRate;
|
||||||
const fftSize = 2 ** FFT_SIZE_POWER;
|
const fftSize = frequencyPanel.getFftSize();
|
||||||
const frequencyResolution = sampleRate / fftSize;
|
const frequencyResolution = sampleRate / fftSize;
|
||||||
const frequencyCount = (sampleRate/2) / frequencyResolution;
|
const frequencyCount = (sampleRate/2) / frequencyResolution;
|
||||||
document.getElementById('frequency-resolution').innerText = frequencyResolution.toFixed(2);
|
document.getElementById('frequency-resolution').innerText = frequencyResolution.toFixed(2);
|
||||||
@@ -213,7 +179,7 @@ function updateFrequencyResolution() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function showChannelList() {
|
function showChannelList() {
|
||||||
const allChannels = getChannels(true);
|
const allChannels = AVAILABLE_FSK;
|
||||||
const channelList = document.getElementById('channel-list');
|
const channelList = document.getElementById('channel-list');
|
||||||
channelList.innerHTML = "";
|
channelList.innerHTML = "";
|
||||||
allChannels.forEach(([low, high], i) => {
|
allChannels.forEach(([low, high], i) => {
|
||||||
@@ -244,6 +210,9 @@ function handleAudioSenderSend({bits}) {
|
|||||||
showSentBits();
|
showSentBits();
|
||||||
}
|
}
|
||||||
function configurationChanged() {
|
function configurationChanged() {
|
||||||
|
if(analyser) analyser.fftSize = frequencyPanel.getFftSize();
|
||||||
|
USED_FSK = calculateMultiFrequencyShiftKeying(false);
|
||||||
|
AVAILABLE_FSK = calculateMultiFrequencyShiftKeying(true);
|
||||||
updatePacketUtils();
|
updatePacketUtils();
|
||||||
updateStreamManager();
|
updateStreamManager();
|
||||||
updateAudioSender();
|
updateAudioSender();
|
||||||
@@ -254,9 +223,9 @@ function configurationChanged() {
|
|||||||
}
|
}
|
||||||
function updateAudioSender() {
|
function updateAudioSender() {
|
||||||
AudioSender.changeConfiguration({
|
AudioSender.changeConfiguration({
|
||||||
channels: getChannels(),
|
channels: USED_FSK,
|
||||||
destination: SEND_VIA_SPEAKER ? audioContext.destination : getAnalyser(),
|
destination: SEND_VIA_SPEAKER ? audioContext.destination : getAnalyser(),
|
||||||
waveForm: WAVE_FORM
|
waveForm: signalPanel.getWaveform()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const logFn = text => (...args) => {
|
const logFn = text => (...args) => {
|
||||||
@@ -281,11 +250,10 @@ const handleAudioReceiverEnd = () => {
|
|||||||
}
|
}
|
||||||
function updateAudioReceiver() {
|
function updateAudioReceiver() {
|
||||||
AudioReceiver.changeConfiguration({
|
AudioReceiver.changeConfiguration({
|
||||||
fskSets: getChannels(),
|
fskSets: USED_FSK,
|
||||||
segmentDurationMs: SEGMENT_DURATION,
|
amplitudeThreshold: Math.floor(signalPanel.getAmplitudeThreshold() * 255),
|
||||||
amplitudeThreshold: AMPLITUDE_THRESHOLD,
|
|
||||||
analyser: getAnalyser(),
|
analyser: getAnalyser(),
|
||||||
signalIntervalMs: SEGMENT_DURATION,
|
signalIntervalMs: signalPanel.getSegmentDuration(),
|
||||||
sampleRate: getAudioContext().sampleRate
|
sampleRate: getAudioContext().sampleRate
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -296,7 +264,7 @@ function updateStreamManager() {
|
|||||||
StreamManager.changeConfiguration({
|
StreamManager.changeConfiguration({
|
||||||
bitsPerPacket: PacketUtils.getPacketMaxBitCount(),
|
bitsPerPacket: PacketUtils.getPacketMaxBitCount(),
|
||||||
segmentsPerPacket: PacketUtils.getPacketSegmentCount(),
|
segmentsPerPacket: PacketUtils.getPacketSegmentCount(),
|
||||||
bitsPerSegment: getChannels().length,
|
bitsPerSegment: USED_FSK.length,
|
||||||
streamHeaders: {
|
streamHeaders: {
|
||||||
'transfer byte count': {
|
'transfer byte count': {
|
||||||
index: 0,
|
index: 0,
|
||||||
@@ -313,9 +281,9 @@ function updatePacketUtils() {
|
|||||||
PacketUtils.setEncoding(
|
PacketUtils.setEncoding(
|
||||||
HAMMING_ERROR_CORRECTION ? HammingEncoding : undefined
|
HAMMING_ERROR_CORRECTION ? HammingEncoding : undefined
|
||||||
);
|
);
|
||||||
const bitsPerSegment = getChannels().length;
|
const bitsPerSegment = USED_FSK.length;
|
||||||
PacketUtils.changeConfiguration({
|
PacketUtils.changeConfiguration({
|
||||||
segmentDurationMilliseconds: SEGMENT_DURATION,
|
segmentDurationMilliseconds: signalPanel.getSegmentDuration(),
|
||||||
packetSizeBitCount: PACKET_SIZE_BITS,
|
packetSizeBitCount: PACKET_SIZE_BITS,
|
||||||
dataSizeBitCount: MAXIMUM_PACKETIZATION_SIZE_BITS,
|
dataSizeBitCount: MAXIMUM_PACKETIZATION_SIZE_BITS,
|
||||||
dataSizeCrcBitCount: CRC_BIT_COUNT,
|
dataSizeCrcBitCount: CRC_BIT_COUNT,
|
||||||
@@ -370,9 +338,9 @@ function updatePacketStats() {
|
|||||||
|
|
||||||
function drawChannels() {
|
function drawChannels() {
|
||||||
const sampleRate = getAudioContext().sampleRate;
|
const sampleRate = getAudioContext().sampleRate;
|
||||||
const fftSize = 2 ** FFT_SIZE_POWER;
|
const fftSize = frequencyPanel.getFftSize();
|
||||||
const frequencyResolution = sampleRate / fftSize;
|
const frequencyResolution = sampleRate / fftSize;
|
||||||
const channels = getChannels();
|
const channels = USED_FSK;
|
||||||
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');
|
||||||
@@ -414,19 +382,21 @@ function percentInFrequency(hz, frequencyResolution) {
|
|||||||
const percent = hzInSegement / frequencyResolution;
|
const percent = hzInSegement / frequencyResolution;
|
||||||
return percent;
|
return percent;
|
||||||
}
|
}
|
||||||
function getChannels(includeExcluded = false) {
|
function calculateMultiFrequencyShiftKeying(includeExcluded = false) {
|
||||||
var audioContext = getAudioContext();
|
var audioContext = getAudioContext();
|
||||||
const sampleRate = audioContext.sampleRate;
|
const sampleRate = audioContext.sampleRate;
|
||||||
const fftSize = 2 ** FFT_SIZE_POWER;
|
const fftSize = frequencyPanel.getFftSize();
|
||||||
const frequencyResolution = sampleRate / fftSize;
|
const frequencyResolution = sampleRate / fftSize;
|
||||||
const channels = [];
|
const channels = [];
|
||||||
const pairStep = frequencyResolution * (2 + CHANNEL_FREQUENCY_RESOLUTION_PADDING) * FREQUENCY_RESOLUTION_MULTIPLIER;
|
const pairStep = frequencyResolution * (2 + frequencyPanel.getMultiFskPadding()) * frequencyPanel.getFskPadding();
|
||||||
let channelId = -1;
|
let channelId = -1;
|
||||||
for(let hz = MINIMUM_FREQUENCY; hz < MAXIMUM_FREQUENCY; hz+= pairStep) {
|
const minimumFrequency = frequencyPanel.getMinimumFrequency();
|
||||||
|
const maximumFrequency = frequencyPanel.getMaximumFrequency();
|
||||||
|
for(let hz = minimumFrequency; hz < maximumFrequency; hz+= pairStep) {
|
||||||
const low = hz;
|
const low = hz;
|
||||||
const high = hz + frequencyResolution * FREQUENCY_RESOLUTION_MULTIPLIER;
|
const high = hz + frequencyResolution * frequencyPanel.getFskPadding();
|
||||||
if(low < MINIMUM_FREQUENCY) continue;
|
if(low < minimumFrequency) continue;
|
||||||
if(high > MAXIMUM_FREQUENCY) continue;
|
if(high > maximumFrequency) break;
|
||||||
channelId++;
|
channelId++;
|
||||||
|
|
||||||
if(!includeExcluded) {
|
if(!includeExcluded) {
|
||||||
@@ -479,7 +449,6 @@ function sendBytes(bytes) {
|
|||||||
const packetCount = PacketUtils.getPacketCount(bitCount);
|
const packetCount = PacketUtils.getPacketCount(bitCount);
|
||||||
const totalDurationSeconds = PacketUtils.getDataTransferDurationSeconds(bitCount);
|
const totalDurationSeconds = PacketUtils.getDataTransferDurationSeconds(bitCount);
|
||||||
|
|
||||||
const channelCount = getChannels().length;
|
|
||||||
const errorCorrectionBits = [];
|
const errorCorrectionBits = [];
|
||||||
|
|
||||||
AudioSender.beginAt(startSeconds);
|
AudioSender.beginAt(startSeconds);
|
||||||
@@ -504,7 +473,7 @@ function sendBytes(bytes) {
|
|||||||
resumeGraph();
|
resumeGraph();
|
||||||
}
|
}
|
||||||
function showSentBits() {
|
function showSentBits() {
|
||||||
const channelCount = getChannels().length;
|
const channelCount = USED_FSK.length;
|
||||||
|
|
||||||
// original bits
|
// original bits
|
||||||
document.getElementById('sent-data').innerHTML =
|
document.getElementById('sent-data').innerHTML =
|
||||||
@@ -531,7 +500,7 @@ function showSentBits() {
|
|||||||
), ''));
|
), ''));
|
||||||
}
|
}
|
||||||
function sendPacket(bits, packetStartSeconds) {
|
function sendPacket(bits, packetStartSeconds) {
|
||||||
const channels = getChannels();
|
const channels = USED_FSK;
|
||||||
const channelCount = channels.length;
|
const channelCount = channels.length;
|
||||||
let bitCount = bits.length;
|
let bitCount = bits.length;
|
||||||
const segmentDurationSeconds = PacketUtils.getSegmentDurationSeconds();
|
const segmentDurationSeconds = PacketUtils.getSegmentDurationSeconds();
|
||||||
@@ -599,7 +568,7 @@ function getTransferredCorrectedBits() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function handleStreamManagerChange() {
|
function handleStreamManagerChange() {
|
||||||
const channelCount = getChannels().length;
|
const channelCount = USED_FSK.length;
|
||||||
let allRawBits = StreamManager.getStreamBits();
|
let allRawBits = StreamManager.getStreamBits();
|
||||||
let allEncodedBits = StreamManager.getAllPacketBits();
|
let allEncodedBits = StreamManager.getAllPacketBits();
|
||||||
let allDecodedBits = getTransferredCorrectedBits();
|
let allDecodedBits = getTransferredCorrectedBits();
|
||||||
@@ -699,7 +668,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 * getChannels().length;
|
return segments * USED_FSK.length;
|
||||||
}
|
}
|
||||||
function parseTransmissionByteCountCrc() {
|
function parseTransmissionByteCountCrc() {
|
||||||
let decodedBits = getTransferredCorrectedBits();
|
let decodedBits = getTransferredCorrectedBits();
|
||||||
@@ -903,8 +872,8 @@ function handleSendButtonClick() {
|
|||||||
function getAnalyser() {
|
function getAnalyser() {
|
||||||
if(analyser) return analyser;
|
if(analyser) return analyser;
|
||||||
analyser = audioContext.createAnalyser();
|
analyser = audioContext.createAnalyser();
|
||||||
analyser.smoothingTimeConstant = SMOOTHING_TIME_CONSTANT;
|
analyser.smoothingTimeConstant = signalPanel.getSmoothingTimeConstant();
|
||||||
analyser.fftSize = 2 ** FFT_SIZE_POWER;
|
analyser.fftSize = frequencyPanel.getFftSize();
|
||||||
return analyser;
|
return analyser;
|
||||||
}
|
}
|
||||||
function handleChangeSendAnalyzer({checked}) {
|
function handleChangeSendAnalyzer({checked}) {
|
||||||
@@ -960,27 +929,6 @@ function handleChangeListening({checked}) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function received(value) {
|
|
||||||
receivedDataTextarea.value += value;
|
|
||||||
receivedDataTextarea.scrollTop = receivedDataTextarea.scrollHeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
function canHear(hz, {frequencies, length}) {
|
|
||||||
var i = Math.round(hz / length);
|
|
||||||
return frequencies[i] > AMPLITUDE_THRESHOLD;
|
|
||||||
}
|
|
||||||
function amplitude(hz, {frequencies, length}) {
|
|
||||||
var i = Math.round(hz / length);
|
|
||||||
return frequencies[i];
|
|
||||||
}
|
|
||||||
function sum(total, value) {
|
|
||||||
return total + value;
|
|
||||||
}
|
|
||||||
function avgLabel(array) {
|
|
||||||
const values = array.filter(v => v > 0);
|
|
||||||
if(values.length === 0) return 'N/A';
|
|
||||||
return (values.reduce((t, v) => t + v, 0) / values.length).toFixed(2)
|
|
||||||
}
|
|
||||||
function drawSegmentIndexes(ctx, width, height) {
|
function drawSegmentIndexes(ctx, width, height) {
|
||||||
// Do/did we have a stream?
|
// Do/did we have a stream?
|
||||||
if(!RECEIVED_STREAM_START_MS) return;
|
if(!RECEIVED_STREAM_START_MS) return;
|
||||||
@@ -991,7 +939,7 @@ function drawSegmentIndexes(ctx, width, height) {
|
|||||||
const transferDuration = parseDataTransferDurationMilliseconds();
|
const transferDuration = parseDataTransferDurationMilliseconds();
|
||||||
const lastStreamEnded = RECEIVED_STREAM_START_MS + transferDuration;
|
const lastStreamEnded = RECEIVED_STREAM_START_MS + transferDuration;
|
||||||
|
|
||||||
const graphDuration = SEGMENT_DURATION * MAX_BITS_DISPLAYED_ON_GRAPH;
|
const graphDuration = signalPanel.getSegmentDuration() * MAX_BITS_DISPLAYED_ON_GRAPH;
|
||||||
const graphEarliest = latest - graphDuration;
|
const graphEarliest = latest - graphDuration;
|
||||||
// ended too long ago?
|
// ended too long ago?
|
||||||
if(lastStreamEnded < graphEarliest) return;
|
if(lastStreamEnded < graphEarliest) return;
|
||||||
@@ -1000,7 +948,7 @@ function drawSegmentIndexes(ctx, width, height) {
|
|||||||
|
|
||||||
const latestSegmentEnded = Math.min(latest, lastStreamEnded);
|
const latestSegmentEnded = Math.min(latest, lastStreamEnded);
|
||||||
|
|
||||||
for(let time = latestSegmentEnded; time > graphEarliest; time -= SEGMENT_DURATION) {
|
for(let time = latestSegmentEnded; time > graphEarliest; time -= signalPanel.getSegmentDuration()) {
|
||||||
// too far back?
|
// too far back?
|
||||||
if(time < RECEIVED_STREAM_START_MS) break;
|
if(time < RECEIVED_STREAM_START_MS) break;
|
||||||
|
|
||||||
@@ -1063,7 +1011,7 @@ function drawSegmentIndexes(ctx, width, height) {
|
|||||||
function drawBitDurationLines(ctx, color) {
|
function drawBitDurationLines(ctx, color) {
|
||||||
const { width, height } = receivedGraph;
|
const { width, height } = receivedGraph;
|
||||||
const newest = SAMPLES[0].time;
|
const newest = SAMPLES[0].time;
|
||||||
const duration = SEGMENT_DURATION * MAX_BITS_DISPLAYED_ON_GRAPH;
|
const duration = signalPanel.getSegmentDuration() * MAX_BITS_DISPLAYED_ON_GRAPH;
|
||||||
|
|
||||||
const streamTimes = SAMPLES.filter(({
|
const streamTimes = SAMPLES.filter(({
|
||||||
streamStarted
|
streamStarted
|
||||||
@@ -1081,7 +1029,7 @@ function drawBitDurationLines(ctx, color) {
|
|||||||
|
|
||||||
ctx.strokeStyle = color;
|
ctx.strokeStyle = color;
|
||||||
streamTimes.forEach(({ streamStarted, streamEnded = newest}) => {
|
streamTimes.forEach(({ streamStarted, streamEnded = newest}) => {
|
||||||
for(let time = streamStarted; time < streamEnded; time += SEGMENT_DURATION) {
|
for(let time = streamStarted; time < streamEnded; time += signalPanel.getSegmentDuration()) {
|
||||||
if(newest - time > duration) continue;
|
if(newest - time > duration) continue;
|
||||||
const x = ((newest - time) / duration) * width;
|
const x = ((newest - time) / duration) * width;
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
@@ -1101,7 +1049,7 @@ function drawBitDurationLines(ctx, color) {
|
|||||||
function drawBitStart(ctx, color) {
|
function drawBitStart(ctx, color) {
|
||||||
const { width, height } = receivedGraph;
|
const { width, height } = receivedGraph;
|
||||||
const newest = SAMPLES[0].time;
|
const newest = SAMPLES[0].time;
|
||||||
const duration = SEGMENT_DURATION * MAX_BITS_DISPLAYED_ON_GRAPH;
|
const duration = signalPanel.getSegmentDuration() * MAX_BITS_DISPLAYED_ON_GRAPH;
|
||||||
ctx.strokeStyle = color;
|
ctx.strokeStyle = color;
|
||||||
for(let i = 0; i < bitStart.length; i++) {
|
for(let i = 0; i < bitStart.length; i++) {
|
||||||
if(!bitStart[i]) continue;
|
if(!bitStart[i]) continue;
|
||||||
@@ -1121,7 +1069,7 @@ function getPercentY(percent) {
|
|||||||
function drawFrequencyLineGraph(ctx, channel, highLowIndex, color, lineWidth, dashed) {
|
function drawFrequencyLineGraph(ctx, channel, highLowIndex, color, lineWidth, dashed) {
|
||||||
const { width, height } = receivedGraph;
|
const { width, height } = receivedGraph;
|
||||||
const newest = SAMPLES[0].time;
|
const newest = SAMPLES[0].time;
|
||||||
const duration = SEGMENT_DURATION * MAX_BITS_DISPLAYED_ON_GRAPH;
|
const duration = signalPanel.getSegmentDuration() * MAX_BITS_DISPLAYED_ON_GRAPH;
|
||||||
const isSelected = channel === CHANNEL_SELECTED;
|
const isSelected = channel === CHANNEL_SELECTED;
|
||||||
const isOver = channel === CHANNEL_OVER;
|
const isOver = channel === CHANNEL_OVER;
|
||||||
if(dashed) {
|
if(dashed) {
|
||||||
@@ -1134,7 +1082,7 @@ function drawFrequencyLineGraph(ctx, channel, highLowIndex, color, lineWidth, da
|
|||||||
if(x === -1) continue;
|
if(x === -1) continue;
|
||||||
if(channel >= pairs.length) continue;
|
if(channel >= pairs.length) continue;
|
||||||
const amplitude = pairs[channel][highLowIndex];
|
const amplitude = pairs[channel][highLowIndex];
|
||||||
const y = getPercentY(amplitude / MAX_AMPLITUDE);
|
const y = getPercentY(amplitude / 300);
|
||||||
if(i === 0) ctx.moveTo(x, y); else ctx.lineTo(x, y);
|
if(i === 0) ctx.moveTo(x, y); else ctx.lineTo(x, y);
|
||||||
}
|
}
|
||||||
if(isSelected || isOver) {
|
if(isSelected || isOver) {
|
||||||
@@ -1162,7 +1110,7 @@ function drawFrequencyDots(ctx, channel, highLowIndex, color) {
|
|||||||
const x = getTimeX(time, newest);
|
const x = getTimeX(time, newest);
|
||||||
if(x === -1) continue;
|
if(x === -1) continue;
|
||||||
const amplitude = pairs[channel][highLowIndex];
|
const amplitude = pairs[channel][highLowIndex];
|
||||||
const y = getPercentY(amplitude / MAX_AMPLITUDE);
|
const y = getPercentY(amplitude / 300);
|
||||||
|
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
ctx.arc(x, y, radius, 0, fullCircle);
|
ctx.arc(x, y, radius, 0, fullCircle);
|
||||||
@@ -1177,16 +1125,11 @@ function getTimeX(time, newest) {
|
|||||||
return getTimePercent(time, newest) * receivedGraph.width;
|
return getTimePercent(time, newest) * receivedGraph.width;
|
||||||
}
|
}
|
||||||
function getTimePercent(time, newest) {
|
function getTimePercent(time, newest) {
|
||||||
const duration = SEGMENT_DURATION * MAX_BITS_DISPLAYED_ON_GRAPH;
|
const duration = signalPanel.getSegmentDuration() * MAX_BITS_DISPLAYED_ON_GRAPH;
|
||||||
if(newest - time > duration) return -1;
|
if(newest - time > duration) return -1;
|
||||||
return ((newest - time) / duration);
|
return ((newest - time) / duration);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getPacketSizeSegmentCount() {
|
|
||||||
const totalBits = PacketUtils.getPacketMaxBitCount();
|
|
||||||
const channelCount = getChannels().length;
|
|
||||||
return Math.ceil(totalBits / channelCount);
|
|
||||||
}
|
|
||||||
function drawChannelData() {
|
function drawChannelData() {
|
||||||
// Do/did we have a stream?
|
// Do/did we have a stream?
|
||||||
if(!RECEIVED_STREAM_START_MS) return;
|
if(!RECEIVED_STREAM_START_MS) return;
|
||||||
@@ -1198,12 +1141,12 @@ function drawChannelData() {
|
|||||||
|
|
||||||
const packetDuration = PacketUtils.getPacketDurationMilliseconds();
|
const packetDuration = PacketUtils.getPacketDurationMilliseconds();
|
||||||
const lastStreamEnded = RECEIVED_STREAM_START_MS + packetDuration;
|
const lastStreamEnded = RECEIVED_STREAM_START_MS + packetDuration;
|
||||||
const graphDuration = SEGMENT_DURATION * MAX_BITS_DISPLAYED_ON_GRAPH;
|
const graphDuration = signalPanel.getSegmentDuration() * MAX_BITS_DISPLAYED_ON_GRAPH;
|
||||||
const graphEarliest = latest - graphDuration;
|
const graphEarliest = latest - graphDuration;
|
||||||
// ended too long ago?
|
// ended too long ago?
|
||||||
if(lastStreamEnded < graphEarliest) return;
|
if(lastStreamEnded < graphEarliest) return;
|
||||||
|
|
||||||
const channels = getChannels();
|
const channels = USED_FSK;
|
||||||
const channelCount = channels.length;
|
const channelCount = channels.length;
|
||||||
|
|
||||||
const canvas = document.getElementById('received-channel-graph');
|
const canvas = document.getElementById('received-channel-graph');
|
||||||
@@ -1214,7 +1157,7 @@ function drawChannelData() {
|
|||||||
|
|
||||||
// Loop through visible segments
|
// Loop through visible segments
|
||||||
const latestSegmentEnded = Math.min(latest, lastStreamEnded);
|
const latestSegmentEnded = Math.min(latest, lastStreamEnded);
|
||||||
for(let time = latestSegmentEnded; time > graphEarliest; time -= SEGMENT_DURATION) {
|
for(let time = latestSegmentEnded; time > graphEarliest; time -= signalPanel.getSegmentDuration()) {
|
||||||
// too far back?
|
// too far back?
|
||||||
if(time < RECEIVED_STREAM_START_MS) break;
|
if(time < RECEIVED_STREAM_START_MS) break;
|
||||||
|
|
||||||
@@ -1397,7 +1340,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 = getChannels();
|
const channels = USED_FSK;
|
||||||
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);
|
||||||
@@ -1437,7 +1380,7 @@ function drawFrequencyData(forcedDraw) {
|
|||||||
ctx.fillStyle = 'black';
|
ctx.fillStyle = 'black';
|
||||||
ctx.fillRect(0, 0, width, height);
|
ctx.fillRect(0, 0, width, height);
|
||||||
|
|
||||||
const thresholdY = (1 - (AMPLITUDE_THRESHOLD/MAX_AMPLITUDE)) * height;
|
const thresholdY = (1 - ((signalPanel.getAmplitudeThreshold() * 255)/300)) * height;
|
||||||
ctx.strokeStyle = 'grey';
|
ctx.strokeStyle = 'grey';
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
ctx.moveTo(0, thresholdY);
|
ctx.moveTo(0, thresholdY);
|
||||||
@@ -1445,7 +1388,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 = getChannels();
|
const frequencies = USED_FSK;
|
||||||
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;
|
||||||
@@ -1534,7 +1477,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 = getChannels();
|
const channels = USED_FSK;
|
||||||
const channelCount = channels.length;
|
const channelCount = channels.length;
|
||||||
|
|
||||||
const selectedSamples = document.getElementById('selected-samples');
|
const selectedSamples = document.getElementById('selected-samples');
|
||||||
@@ -1642,7 +1585,7 @@ function getChannelAndSegment(e) {
|
|||||||
segmentIndex: -1
|
segmentIndex: -1
|
||||||
};
|
};
|
||||||
// what channel are we over?
|
// what channel are we over?
|
||||||
const channels = getChannels();
|
const channels = USED_FSK;
|
||||||
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--;
|
||||||
@@ -1659,7 +1602,7 @@ function getChannelAndSegment(e) {
|
|||||||
// will any of the stream appear?
|
// will any of the stream appear?
|
||||||
const packetDuration = PacketUtils.getPacketDurationMilliseconds();
|
const packetDuration = PacketUtils.getPacketDurationMilliseconds();
|
||||||
const lastStreamEnded = RECEIVED_STREAM_START_MS + packetDuration;
|
const lastStreamEnded = RECEIVED_STREAM_START_MS + packetDuration;
|
||||||
const graphDuration = SEGMENT_DURATION * MAX_BITS_DISPLAYED_ON_GRAPH;
|
const graphDuration = signalPanel.getSegmentDuration() * MAX_BITS_DISPLAYED_ON_GRAPH;
|
||||||
const graphEarliest = latest - graphDuration;
|
const graphEarliest = latest - graphDuration;
|
||||||
// ended too long ago?
|
// ended too long ago?
|
||||||
if(lastStreamEnded < graphEarliest) {
|
if(lastStreamEnded < graphEarliest) {
|
||||||
@@ -1673,7 +1616,7 @@ function getChannelAndSegment(e) {
|
|||||||
|
|
||||||
const latestSegmentEnded = Math.min(latest, lastStreamEnded);
|
const latestSegmentEnded = Math.min(latest, lastStreamEnded);
|
||||||
|
|
||||||
for(let time = latestSegmentEnded; time > graphEarliest; time -= SEGMENT_DURATION) {
|
for(let time = latestSegmentEnded; time > graphEarliest; time -= signalPanel.getSegmentDuration()) {
|
||||||
// too far back?
|
// too far back?
|
||||||
if(time < RECEIVED_STREAM_START_MS) {
|
if(time < RECEIVED_STREAM_START_MS) {
|
||||||
return {
|
return {
|
||||||
@@ -1683,11 +1626,11 @@ function getChannelAndSegment(e) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// which segment are we looking at?
|
// which segment are we looking at?
|
||||||
const segmentIndex = Math.floor(((time - RECEIVED_STREAM_START_MS) / SEGMENT_DURATION));
|
const segmentIndex = Math.floor(((time - RECEIVED_STREAM_START_MS) / signalPanel.getSegmentDuration()));
|
||||||
|
|
||||||
// when did the segment begin/end
|
// when did the segment begin/end
|
||||||
const segmentStart = RECEIVED_STREAM_START_MS + (segmentIndex * SEGMENT_DURATION);
|
const segmentStart = RECEIVED_STREAM_START_MS + (segmentIndex * signalPanel.getSegmentDuration());
|
||||||
const segmentEnd = segmentStart + SEGMENT_DURATION;
|
const segmentEnd = segmentStart + signalPanel.getSegmentDuration();
|
||||||
|
|
||||||
// where is the segments left x coordinate?
|
// where is the segments left x coordinate?
|
||||||
const leftX = ((latest - segmentEnd) / graphDuration) * width;
|
const leftX = ((latest - segmentEnd) / graphDuration) * width;
|
||||||
|
|||||||
Reference in New Issue
Block a user