diff --git a/AudioReceiver.js b/AudioReceiver.js index 443dfde..13fc39f 100644 --- a/AudioReceiver.js +++ b/AudioReceiver.js @@ -8,6 +8,7 @@ let AMPLITUDE_THRESHOLD = 50; let FSK_SETS = []; let SIGNAL_INTERVAL_MS = 30; let SIGNAL_TIMEOUT_MS = 400; +let LAST_SIGNAL_BEFORE_TIMEOUT = 0; let HAS_SIGNAL = false; let SIGNAL_START_MS = -1; @@ -17,6 +18,14 @@ let SAMPLE_RATE; let signalTimeoutId; 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 = ({ fskSets, signalIntervalMs, @@ -196,6 +205,7 @@ const handleSignalOn = time => { } const handleSignalOff = time => { if(HAS_SIGNAL && !signalTimeoutId) { + LAST_SIGNAL_BEFORE_TIMEOUT = time; signalTimeoutId = window.setTimeout(handleSignalLost, SIGNAL_TIMEOUT_MS, time); } } @@ -222,4 +232,5 @@ export { reset, addEventListener, removeEventListener, + setTimeoutMilliseconds } diff --git a/AudioSender.js b/AudioSender.js index 154074d..23391d7 100644 --- a/AudioSender.js +++ b/AudioSender.js @@ -116,14 +116,16 @@ export function stopAt(streamEndSeconds) { ); } export function stop() { - const time = now(); const oscillators = getOscillators(); - oscillators.forEach( - oscillator => { - oscillator?.stop(time); - oscillator?.disconnect(); - } - ) + if(this.audioContext) { + const time = now(); + oscillators.forEach( + oscillator => { + oscillator?.stop(time); + oscillator?.disconnect(); + } + ) + } oscillators.length = 0; futureEventIds.forEach(window.clearTimeout); futureEventIds.length = 0; diff --git a/Panels/AvailableFskPairsPanel.js b/Panels/AvailableFskPairsPanel.js index 55a45c4..bd5590d 100644 --- a/Panels/AvailableFskPairsPanel.js +++ b/Panels/AvailableFskPairsPanel.js @@ -5,6 +5,7 @@ class AvailableFskPairsPanel extends BasePanel { super('Available FSK Pairs'); this.exclude = []; this.fskPairs = []; + this.originalSelectedFskPairs = []; this.sampleRate = 48000; this.addCanvas('fsk-spectrum', 200, 32); @@ -28,9 +29,25 @@ class AvailableFskPairsPanel extends BasePanel { } else if(!this.exclude.includes(event.id)) { this.exclude.push(event.id); } + this.checkChanges(); 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 .filter(this.isSelected); @@ -38,6 +55,7 @@ class AvailableFskPairsPanel extends BasePanel { setSelectedIndexes = (values) => { this.exclude = values; this.setFskPairs(this.fskPairs); + this.checkChanges(); } setFskPairs = fskPairs => { @@ -51,6 +69,7 @@ class AvailableFskPairsPanel extends BasePanel { eventName: 'select' })); this.replaceCheckedInputs('checkbox', 'fsk-pairs', items); + this.checkChanges(); this.drawFskSpectrum(); } diff --git a/Panels/BasePanel.js b/Panels/BasePanel.js index c83d775..821dc68 100644 --- a/Panels/BasePanel.js +++ b/Panels/BasePanel.js @@ -195,6 +195,10 @@ class BasePanel { const element = this.getElement(id); element.innerHTML = html; } + getNumberById = id => { + const value = this.getValueById(id); + return parseFloat(value); + } getValueById = (id) => { const element = this.getElement(id); switch(element.tagName) { diff --git a/Panels/FrequencyGraphPanel.js b/Panels/FrequencyGraphPanel.js new file mode 100644 index 0000000..a930b5d --- /dev/null +++ b/Panels/FrequencyGraphPanel.js @@ -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; \ No newline at end of file diff --git a/Panels/GraphConfigurationPanel.js b/Panels/GraphConfigurationPanel.js new file mode 100644 index 0000000..69a1dd7 --- /dev/null +++ b/Panels/GraphConfigurationPanel.js @@ -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; \ No newline at end of file diff --git a/Panels/SignalPanel.js b/Panels/SignalPanel.js index 654dbf7..ff3222e 100644 --- a/Panels/SignalPanel.js +++ b/Panels/SignalPanel.js @@ -28,12 +28,19 @@ class SignalPanel extends BasePanel { this.addText('%'); 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.addInputNumber('smoothing-time-constant', 0, {min: 0, max: 100, eventName: 'smothingTimeConstantChange', translation: 'percent'}); this.addText('%'); this.closeField(); }; + getTimeoutMilliseconds = () => this.getNumberById('timeout'); + setTimeoutMilliseconds = (milliseconds) => this.setValueById('timeout', milliseconds); getWaveform = () => this.getValueById('wave-form'); setWaveform = (value) => this.setValueById('wave-form', value); diff --git a/index.js b/index.js index 1848832..04f90ba 100644 --- a/index.js +++ b/index.js @@ -14,7 +14,8 @@ import FrequencyPanel from "./Panels/FrequencyPanel"; import SignalPanel from "./Panels/SignalPanel"; import PacketizationPanel from "./Panels/PacketizationPanel"; import AvailableFskPairsPanel from "./Panels/AvailableFskPairsPanel"; - +import FrequencyGraphPanel from "./Panels/FrequencyGraphPanel"; +import GraphConfigurationPanel from './Panels/GraphConfigurationPanel' var audioContext; var microphoneStream; var microphoneNode; @@ -48,10 +49,6 @@ let SAMPLES = []; var bitStart = []; var PAUSE = false; -var PAUSE_AFTER_END = true; - -let USED_FSK = []; -let AVAILABLE_FSK = []; const communicationsPanel = new CommunicationsPanel(); const messagePanel = new MessagePanel(); @@ -61,9 +58,13 @@ const frequencyPanel = new FrequencyPanel(); const signalPanel = new SignalPanel(); const packetizationPanel = new PacketizationPanel(); const availableFskPairsPanel = new AvailableFskPairsPanel(); +const frequencyGraphPanel = new FrequencyGraphPanel(); +const graphConfigurationPanel = new GraphConfigurationPanel(); function handleWindowLoad() { const panelContainer = document.getElementById('panel-container'); + panelContainer.prepend(graphConfigurationPanel.getDomElement()); + panelContainer.prepend(frequencyGraphPanel.getDomElement()); panelContainer.prepend(availableFskPairsPanel.getDomElement()); panelContainer.prepend(packetizationPanel.getDomElement()); panelContainer.prepend(signalPanel.getDomElement()); @@ -96,6 +97,7 @@ function handleWindowLoad() { signalPanel.setSegmentDuration(30); signalPanel.setAmplitudeThreshold(0.78); signalPanel.setSmoothingTimeConstant(0); + signalPanel.setTimeoutMilliseconds(60); packetizationPanel.setSizePower(5); packetizationPanel.setErrorCorrection(true); @@ -103,7 +105,17 @@ function handleWindowLoad() { 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('sendSpeakersChange', handleChangeSendSpeakers); communicationsPanel.addEventListener('sendAnalyzerChange', handleChangeSendAnalyzer); @@ -124,9 +136,18 @@ function handleWindowLoad() { }); signalPanel.addEventListener('waveformChange', updateAudioSender); - signalPanel.addEventListener('segmentDurationChange', configurationChanged); - signalPanel.addEventListener('amplitudeThresholdChange', configurationChanged); + signalPanel.addEventListener('segmentDurationChange', (event) => { + frequencyGraphPanel.setSamplingPeriod(event.value); + configurationChanged(); + }); + signalPanel.addEventListener('amplitudeThresholdChange', ({value}) => { + frequencyGraphPanel.setAmplitudeThreshold(value); + configurationChanged(); + }); signalPanel.addEventListener('smoothingConstantChange', configurationChanged); + signalPanel.addEventListener('timeoutChange', () => { + AudioReceiver.setTimeoutMilliseconds(signalPanel.getTimeoutMilliseconds()); + }) packetizationPanel.addEventListener('sizePowerChange', configurationChanged); packetizationPanel.addEventListener('interleavingChange', () => { @@ -137,6 +158,18 @@ function handleWindowLoad() { }); 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 AudioSender.addEventListener('begin', () => messagePanel.setSendButtonText('Stop')); AudioSender.addEventListener('send', handleAudioSenderSend); @@ -156,11 +189,6 @@ function handleWindowLoad() { receivedChannelGraph.addEventListener('mouseout', handleReceivedChannelGraphMouseout); receivedChannelGraph.addEventListener('mousemove', handleReceivedChannelGraphMousemove); 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').addEventListener('input', (event) => { MAX_BITS_DISPLAYED_ON_GRAPH = parseInt(event.target.value); @@ -185,8 +213,6 @@ function handleAudioSenderSend({bits}) { } function configurationChanged() { if(analyser) analyser.fftSize = frequencyPanel.getFftSize(); - USED_FSK = calculateMultiFrequencyShiftKeying(false); - AVAILABLE_FSK = calculateMultiFrequencyShiftKeying(true); updatePacketUtils(); updateStreamManager(); updateAudioSender(); @@ -197,16 +223,14 @@ function configurationChanged() { } function updateAudioSender() { AudioSender.changeConfiguration({ - channels: USED_FSK, + channels: availableFskPairsPanel.getSelectedFskPairs(), destination: SEND_VIA_SPEAKER ? audioContext.destination : getAnalyser(), waveForm: signalPanel.getWaveform() }); } -const logFn = text => (...args) => { - // console.log(text, ...args); -} const handleAudioReceiverStart = ({signalStart}) => { StreamManager.reset(); + frequencyGraphPanel.setSignalStart(signalStart); RECEIVED_STREAM_START_MS = signalStart; } const handleAudioReceiverReceive = ({signalStart, signalIndex, indexStart, bits}) => { @@ -216,15 +240,17 @@ const handleAudioReceiverReceive = ({signalStart, signalIndex, indexStart, bits} // console.log(signalIndex, packetIndex, segmentIndex, bits.join('')); StreamManager.addBits(packetIndex, segmentIndex, bits); } -const handleAudioReceiverEnd = () => { - if(PAUSE_AFTER_END) { +const handleAudioReceiverEnd = (e) => { + frequencyGraphPanel.setSignalEnd(e.signalEnd); + if(graphConfigurationPanel.getPauseAfterEnd()) { stopGraph(); + frequencyGraphPanel.stop(); AudioSender.stop(); } } function updateAudioReceiver() { AudioReceiver.changeConfiguration({ - fskSets: USED_FSK, + fskSets: availableFskPairsPanel.getSelectedFskPairs(), amplitudeThreshold: Math.floor(signalPanel.getAmplitudeThreshold() * 255), analyser: getAnalyser(), signalIntervalMs: signalPanel.getSegmentDuration(), @@ -238,7 +264,7 @@ function updateStreamManager() { StreamManager.changeConfiguration({ bitsPerPacket: PacketUtils.getPacketMaxBitCount(), segmentsPerPacket: PacketUtils.getPacketSegmentCount(), - bitsPerSegment: USED_FSK.length, + bitsPerSegment: availableFskPairsPanel.getSelectedFskPairs().length, streamHeaders: { 'transfer byte count': { index: 0, @@ -255,7 +281,7 @@ function updatePacketUtils() { PacketUtils.setEncoding( packetizationPanel.getErrorCorrection() ? HammingEncoding : undefined ); - const bitsPerSegment = USED_FSK.length; + const bitsPerSegment = availableFskPairsPanel.getSelectedFskPairs().length; PacketUtils.changeConfiguration({ segmentDurationMilliseconds: signalPanel.getSegmentDuration(), packetSizeBitCount: packetizationPanel.getSizePower(), @@ -314,7 +340,7 @@ function drawChannels() { const sampleRate = getAudioContext().sampleRate; const fftSize = frequencyPanel.getFftSize(); const frequencyResolution = sampleRate / fftSize; - const channels = USED_FSK; + const channels = availableFskPairsPanel.getSelectedFskPairs(); const channelCount = channels.length; const canvas = document.getElementById('channel-frequency-graph'); const ctx = canvas.getContext('2d'); @@ -356,30 +382,7 @@ function percentInFrequency(hz, frequencyResolution) { const percent = hzInSegement / frequencyResolution; 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) { // display what is being sent sentDataTextArea.value += text + '\n'; @@ -447,7 +450,7 @@ function sendBytes(bytes) { resumeGraph(); } function showSentBits() { - const channelCount = USED_FSK.length; + const channelCount = availableFskPairsPanel.getSelectedFskPairs().length; // original bits document.getElementById('sent-data').innerHTML = @@ -474,7 +477,7 @@ function showSentBits() { ), '')); } function sendPacket(bits, packetStartSeconds) { - const channels = USED_FSK; + const channels = availableFskPairsPanel.getSelectedFskPairs(); const channelCount = channels.length; let bitCount = bits.length; const segmentDurationSeconds = PacketUtils.getSegmentDurationSeconds(); @@ -542,7 +545,7 @@ function getTransferredCorrectedBits() { } function handleStreamManagerChange() { - const channelCount = USED_FSK.length; + const channelCount = availableFskPairsPanel.getSelectedFskPairs().length; let allRawBits = StreamManager.getStreamBits(); let allEncodedBits = StreamManager.getAllPacketBits(); let allDecodedBits = getTransferredCorrectedBits(); @@ -642,7 +645,7 @@ function parseTotalBitsTransferring() { const dataByteCount = parseTransmissionByteCount(); const bitCount = PacketUtils.getPacketizationBitCountFromByteCount(dataByteCount); const segments = getTotalSegmentCount(bitCount); - return segments * USED_FSK.length; + return segments * availableFskPairsPanel.getSelectedFskPairs().length; } function parseTransmissionByteCountCrc() { let decodedBits = getTransferredCorrectedBits(); @@ -841,6 +844,7 @@ function handleSendButtonClick() { } else { AudioReceiver.reset(); StreamManager.reset(); + frequencyGraphPanel.start(); const text = messagePanel.getMessage(); sendBytes(textToBytes(text)); } @@ -848,6 +852,7 @@ function handleSendButtonClick() { function getAnalyser() { if(analyser) return analyser; analyser = audioContext.createAnalyser(); + frequencyGraphPanel.setAnalyser(analyser); analyser.smoothingTimeConstant = signalPanel.getSmoothingTimeConstant(); analyser.fftSize = frequencyPanel.getFftSize(); return analyser; @@ -1122,7 +1127,7 @@ function drawChannelData() { // ended too long ago? if(lastStreamEnded < graphEarliest) return; - const channels = USED_FSK; + const channels = availableFskPairsPanel.getSelectedFskPairs(); const channelCount = channels.length; const canvas = document.getElementById('received-channel-graph'); @@ -1316,7 +1321,7 @@ function drawSelectedChannel(ctx, channelCount, width, height) { } function drawChannelNumbers(ctx, channelCount, width, height) { const offset = 0; - const channels = USED_FSK; + const channels = availableFskPairsPanel.getSelectedFskPairs(); const channelHeight = height / channelCount; const segmentWidth = width / MAX_BITS_DISPLAYED_ON_GRAPH; let fontHeight = Math.min(24, channelHeight, segmentWidth); @@ -1364,7 +1369,7 @@ function drawFrequencyData(forcedDraw) { ctx.stroke(); drawBitDurationLines(ctx, 'rgba(255, 255, 0, .25)'); drawBitStart(ctx, 'green'); - const frequencies = USED_FSK; + const frequencies = availableFskPairsPanel.getSelectedFskPairs(); const high = 1; const low = 0 const isSelectedOrOver = CHANNEL_OVER !== -1 || CHANNEL_SELECTED !== -1; @@ -1453,7 +1458,7 @@ function handleReceivedChannelGraphClick(e) { const {channelIndex, segmentIndex} = getChannelAndSegment(e); CHANNEL_SELECTED = channelIndex; SEGMENT_SELECTED = segmentIndex; - const channels = USED_FSK; + const channels = availableFskPairsPanel.getSelectedFskPairs(); const channelCount = channels.length; const selectedSamples = document.getElementById('selected-samples'); @@ -1561,7 +1566,7 @@ function getChannelAndSegment(e) { segmentIndex: -1 }; // what channel are we over? - const channels = USED_FSK; + const channels = availableFskPairsPanel.getSelectedFskPairs(); const channelCount = channels.length; let channelIndex = Math.floor((y / height) * channelCount); if(channelIndex === channelCount) channelIndex--;