From 6647e5b51d9476a191a57180e102b7554c39be30 Mon Sep 17 00:00:00 2001 From: Lewis Moten Date: Sat, 11 May 2024 03:33:40 -0400 Subject: [PATCH] setup audio receiver and event dispatcher --- AudioReceiver.js | 225 +++++++++++++++++++++++++++++++++ AudioSender.js | 16 ++- Dispatcher.js | 45 +++++++ StreamManager.js | 26 ++++ index.html | 25 ++-- index.js | 318 ++++++++--------------------------------------- 6 files changed, 376 insertions(+), 279 deletions(-) create mode 100644 AudioReceiver.js create mode 100644 Dispatcher.js diff --git a/AudioReceiver.js b/AudioReceiver.js new file mode 100644 index 0000000..443dfde --- /dev/null +++ b/AudioReceiver.js @@ -0,0 +1,225 @@ +import Dispatcher from "./Dispatcher"; + +const dispatcher = new Dispatcher('AudioReceiver', ['begin', 'end', 'receive']); + +let sampleIntervalIds = []; +let SAMPLE_LAST_COLLECTED = 0; +let AMPLITUDE_THRESHOLD = 50; +let FSK_SETS = []; +let SIGNAL_INTERVAL_MS = 30; +let SIGNAL_TIMEOUT_MS = 400; + +let HAS_SIGNAL = false; +let SIGNAL_START_MS = -1; +let ANALYSER; +let SAMPLE_RATE; + +let signalTimeoutId; +let SAMPLES = []; + +const changeConfiguration = ({ + fskSets, + signalIntervalMs, + amplitudeThreshold, + analyser, + sampleRate +}) => { + FSK_SETS = fskSets; + AMPLITUDE_THRESHOLD = amplitudeThreshold; + SIGNAL_INTERVAL_MS = signalIntervalMs; + ANALYSER = analyser; + SAMPLE_RATE = sampleRate; +} + +function start() { + // Browsers generally do not run any less than 3 milliseconds + const MINIMUM_INTERVAL_MS = 3; + // Running two intervals gives us a small increase in sample rate + // Running more than two intervals was negligible + const SAMPLING_INTERVAL_COUNT = 2; + for(let i = 0; i < SAMPLING_INTERVAL_COUNT; i++) { + // already started? + if(sampleIntervalIds[i]) continue; + // set interval + sampleIntervalIds[i] = window.setInterval( + collectSample, + MINIMUM_INTERVAL_MS + (i/SAMPLING_INTERVAL_COUNT) + ); + } +} +function stop() { + sampleIntervalIds.forEach(window.clearInterval); + sampleIntervalIds = sampleIntervalIds.map(() => {}); +} +const reset =() => { + HAS_SIGNAL = false; + SIGNAL_START_MS = -1; + SAMPLES.length = 0; + SAMPLE_LAST_COLLECTED = -1; +} +const getAnalyser = () => ANALYSER; +const getFrequencyShiftKeyingSets = () => FSK_SETS; + +function analyzeAudioFrequenciesAsBits() { + const analyser = getAnalyser(); + const frequencyResolution = SAMPLE_RATE / 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); + return getFrequencyShiftKeyingSets().map(ampsFromManyHz); +} +const ampMeetsTheshold = amp => amp > AMPLITUDE_THRESHOLD; +const anyAmpMeetsThreshold = amps => amps.some(ampMeetsTheshold); +const anySetOfAmpsMeetsThreshold = bitStates => bitStates.some(anyAmpMeetsThreshold); + +function collectSample() { + const time = performance.now(); + // Do nothing if we already collected the sample + if(time === SAMPLE_LAST_COLLECTED) return; + SAMPLE_LAST_COLLECTED = time; + // Get amplitude of each channels set of frequencies + const bitStates = analyzeAudioFrequenciesAsBits(); + const hasSignal = anySetOfAmpsMeetsThreshold(bitStates); + handleSignalState(time, hasSignal); + if(hasSignal) { + const duration = time - SIGNAL_START_MS; + const index = Math.floor(duration / SIGNAL_INTERVAL_MS); + const start = SIGNAL_START_MS + (index * SIGNAL_INTERVAL_MS); + const end = start + SIGNAL_INTERVAL_MS; + SAMPLES.unshift({ + signalStart: SIGNAL_START_MS, + index, + start, + time, + end, + bitStates, + bs: bitStates.map(ss => '[' + ss.join(':') + ']').join('') + }); + } + processSamples(time); + removeSamples(time); +} +const isExpiredSample = time => { + const duration = Math.max(30, SIGNAL_INTERVAL_MS * 2); + const expired = time - duration; + return sample => sample.time < expired; +}; + +const removeSamples = time => { + // remove expired samples + let length = SAMPLES.findIndex(isExpiredSample(time)); + if(length !== -1) SAMPLES.length = length; + // Don't let long signal intervals take over memory + if(SAMPLES.length > 1024) SAMPLES.length = 1024; +} + +const uniqueSamplesReady = time => (all, { + signalStart, + index, + end +}) => { + // still collecting samples? + if(end > time) return all; + const isSameSample = sample => sample.signalStart === signalStart && sample.index === index; + // sample exists? + if(!all.some(isSameSample)) { + all.push({ signalStart, index }); + } + return all; +} + +function processSamples(time) { + SAMPLES + .reduce(uniqueSamplesReady(time), []) + .every(processSample); +} +function processSample({ signalStart, index }) { + + const isSegment = sample => ( + sample.signalStart === signalStart && + sample.index === index + ); + const samples = SAMPLES.filter(isSegment); + if(samples.length === 0) return; + + let bits = evaluateBits(samples); + const { start, end } = samples[0]; + dispatcher.emit('receive', { + signalStart, + signalIndex: index, + indexStart: start, + indexEnd: end, + bits, + }); + + // remove processed samples + const isNotSegment = sample => !isSegment(sample); + SAMPLES = SAMPLES.filter(isNotSegment) +} +const newSingleBitState = () => new Array(2).fill(0); +const newMultiBitStates = count => new Array(count).fill(0).map(newSingleBitState); +const mapBitValue = (bitStates) => bitStates[0] > bitStates[1] ? 0 : 1 +const evaluateBits = (samples) => { + if(samples.length === 0) return; + const {bitStates: { length: bitCount }} = SAMPLES[0]; + if(bitCount === 0) return; + const bitSums = newMultiBitStates(bitCount); + samples.forEach(({bitStates}) => { + bitStates.forEach((strength, bitIndex) => { + strength.forEach((value, bitState) => { + bitSums[bitIndex][bitState] += value; + }); + }); + }); + return bitSums.map(mapBitValue); +} +const handleSignalState = (time, hasSignal) => { + if(hasSignal) { + handleSignalOn(time); + } else { + handleSignalOff(time); + } +} +const handleSignalOn = time => { + if(signalTimeoutId) { + window.clearTimeout(signalTimeoutId); + signalTimeoutId = undefined; + } + if(!HAS_SIGNAL) { + HAS_SIGNAL = true; + SIGNAL_START_MS = time; + dispatcher.emit('begin', { signalStart: time }); + } +} +const handleSignalOff = time => { + if(HAS_SIGNAL && !signalTimeoutId) { + signalTimeoutId = window.setTimeout(handleSignalLost, SIGNAL_TIMEOUT_MS, time); + } +} +const handleSignalLost = time => { + if(signalTimeoutId) { + window.clearTimeout(signalTimeoutId); + signalTimeoutId = undefined; + } + if(HAS_SIGNAL) { + HAS_SIGNAL = false; + dispatcher.emit('end', { + signalStart: SIGNAL_START_MS, + signalEnd: time + }); + } +} +const addEventListener = dispatcher.addListener; +const removeEventListener = dispatcher.removeListener; + +export { + changeConfiguration, + start, + stop, + reset, + addEventListener, + removeEventListener, +} diff --git a/AudioSender.js b/AudioSender.js index b68cae9..833bdb8 100644 --- a/AudioSender.js +++ b/AudioSender.js @@ -1,3 +1,7 @@ +import Dispatcher from "./Dispatcher"; + +const dispatcher = new Dispatcher('AudioSender', ['begin', 'end', 'send']); + let audioContext; let CHANNELS = []; let DESTINATION; @@ -9,6 +13,9 @@ let WAVE_FORM; let stopOscillatorsTimeoutId; +export const addEventListener = dispatcher.addListener; +export const removeEventListener = dispatcher.removeListener; + export const changeConfiguration = ({ channels, destination, @@ -58,7 +65,7 @@ export function beginAt(streamStartSeconds) { oscillator.start(streamStartSeconds); oscillators.push(oscillator); } - callFn(ON_START); + dispatcher.emit('begin'); return oscillators; } function getOscillators() { @@ -66,10 +73,11 @@ function getOscillators() { } export function send(bits, startSeconds) { const oscillators = getOscillators(); + const sentBits = []; getChannels().forEach((channel, i) => { // send missing bits as zero const isHigh = bits[i] ?? 0; - callFn(ON_SEND, isHigh); + sentBits.push(isHigh); const oscillator = oscillators[i]; // already at correct frequency if(oscillator.on === isHigh) return; @@ -77,6 +85,7 @@ export function send(bits, startSeconds) { const hz = channel[isHigh ? 1 : 0]; oscillator.frequency.setValueAtTime(hz, startSeconds); }); + dispatcher.emit('send', {bits: sentBits, startSeconds}); } const stopTimeout = () => { if(stopOscillatorsTimeoutId) { @@ -109,7 +118,6 @@ export function stop() { } ) oscillators.length = 0; - callFn(ON_STOP); + dispatcher.emit('end'); stopTimeout(); } -const callFn = (fn, ...args) => typeof fn === 'function' ? fn(...args) : 0; diff --git a/Dispatcher.js b/Dispatcher.js new file mode 100644 index 0000000..d26a87c --- /dev/null +++ b/Dispatcher.js @@ -0,0 +1,45 @@ +class Dispatcher { + constructor(domain, allowedEvents=[]) { + this.LISTENERS = {}; + this.allowedEvents = allowedEvents; + this.domain = domain; + } + emit = (eventName, ...args) => { + // console.log(`${this.domain}.${eventName}`, ...args); + if(!this.LISTENERS[eventName]) return; + this.LISTENERS[eventName].forEach(callback => callback(...args)); + } + addListener = (eventName, callback) => { + if(this.allowedEvents.length !== 0) { + if(!this.allowedEvents.includes(eventName)) { + throw new Error(`Event "${eventName}" is not allowed for ${this.domain}.`) + } + } + if(typeof callback !== 'function') + throw new Error('Must provide a function'); + + if(!this.LISTENERS[eventName]) { + this.LISTENERS[eventName] = []; + } + if(this.LISTENERS[eventName].includes(callback)) return; + this.LISTENERS[eventName].push(callback); + } + removeListener = (eventName, callback) => { + if(!this.LISTENERS[eventName]) return; + const i = this.LISTENERS[eventName].indexOf(callback); + if(i === -1) return; + this.LISTENERS[eventName].splice(i, 1); + } + clearEventListeners = eventName => { + if(!this.LISTENERS[eventName]) return; + delete this.LISTENERS[eventName]; + } + clearAllEventListeners = () => { + Object + .keys(this.LISTENERS) + .forEach( + eventName => this.clearEventListeners(eventName) + ); + } +} +export default Dispatcher; \ No newline at end of file diff --git a/StreamManager.js b/StreamManager.js index 4714b0e..4f9a5aa 100644 --- a/StreamManager.js +++ b/StreamManager.js @@ -1,5 +1,8 @@ +import Dispatcher from "./Dispatcher"; import { bitsToInt } from "./converters"; +const dispatcher = new Dispatcher('StreamManager', ['change']); + const BITS = []; let BITS_PER_PACKET = 0; let SEGMENTS_PER_PACKET = 0; @@ -14,6 +17,16 @@ let PACKET_ENCODING = { decode: bits => bits } +export const addEventListener = dispatcher.addListener; +export const removeEventListener = dispatcher.removeListener; + +export const reset = () => { + if(BITS.length !== 0) { + BITS.length = 0; + dispatcher.emit('change'); + } +} + export const changeConfiguration = ({ segmentsPerPacket, bitsPerPacket, @@ -42,7 +55,20 @@ export const addBits = ( if(BITS[packetIndex] === undefined) { BITS[packetIndex] = []; } + const oldBits = BITS[packetIndex][segmentIndex]; BITS[packetIndex][segmentIndex] = bits; + if(hasNewBits(oldBits, bits)) + dispatcher.emit('change'); +} +const hasNewBits = (oldBits = [], bits = []) => { + if(oldBits.length === 0 && bits.length === BITS_PER_SEGMENT) + return true; + for(let i = 0; i < BITS_PER_SEGMENT; i++) { + let a = oldBits[i] ?? 0; + let b = bits[i] ?? 0; + if(a !== b) return true; + } + return false; } export const getPacketReceivedCount = () => { if(BITS.length === 0) return 1; diff --git a/index.html b/index.html index bc7bdf7..a9761c4 100644 --- a/index.html +++ b/index.html @@ -40,6 +40,19 @@ +
+

Encoded segments to send

+
+

+
+
+
+

Encoded Segments Received

+
+

+
+
+

Configuration

@@ -154,12 +167,6 @@
-
-

Encoded segments to send

-
-

-
-

Decoded

@@ -171,12 +178,6 @@ Decoded Packets: N/A%
-
-

Encoded Segments Received

-
-

-
-

Encoded Packets Received

diff --git a/index.js b/index.js index 6c2fee5..c924660 100644 --- a/index.js +++ b/index.js @@ -5,6 +5,7 @@ import * as PacketUtils from './PacketUtils'; import * as Humanize from './Humanize'; import * as Randomizer from './Randomizer'; import * as AudioSender from './AudioSender'; +import * as AudioReceiver from './AudioReceiver'; import * as CRC from './CRC.js'; var audioContext; @@ -32,9 +33,7 @@ let SENT_ENCODED_BITS = []; // bits with error encoding let SENT_TRANSFER_BITS = []; // bits sent in the transfer // interval and timeout ids -var pauseGraphId; let stopOscillatorsTimeoutId; -var sampleIntervalIds = []; let EXCLUDED_CHANNELS = []; @@ -63,11 +62,7 @@ let SEGMENT_SELECTED = -1; var SEND_VIA_SPEAKER = false; var RECEIVED_STREAM_START_MS = -1; -let RECEIVED_STREAM_END_MS = -1; -var MINIMUM_INTERVAL_MS = 3; // DO NOT SET THIS BELOW THE BROWSERS MINIMUM "real" INTERVAL -const SAMPLING_INTERVAL_COUNT = 2; let SAMPLES = []; -let SAMPLE_LAST_COLLECTED = 0; // time when sample was last collected var bitStart = []; var PAUSE = false; @@ -76,6 +71,16 @@ var PACKET_SIZE_BITS = 5; // 32 bytes, 256 bits function handleWindowLoad() { TEXT_TO_SEND = Randomizer.text(5); + // Setup audio sender + AudioSender.addEventListener('begin', () => sendButton.innerText = 'Stop'); + AudioSender.addEventListener('send', handleAudioSenderSend); + AudioSender.addEventListener('end', () => sendButton.innerText = 'Send'); + // Setup audio receiver + AudioReceiver.addEventListener('begin', handleAudioReceiverStart); + AudioReceiver.addEventListener('receive', handleAudioReceiverReceive); + AudioReceiver.addEventListener('end', handleAudioReceiverEnd); + // Setup stream manager + StreamManager.addEventListener('change', handleStreamManagerChange); // grab dom elements sendButton = document.getElementById('send-button'); @@ -222,19 +227,14 @@ function showChannelList() { drawChannels(); } -function handleAudioSenderStart() { - sendButton.innerText = 'Stop'; -} -function handleAudioSenderStop() { - sendButton.innerText = 'Send'; -} -function handleAudioSenderSend(bit) { - SENT_TRANSFER_BITS.push(bit); +function handleAudioSenderSend({bits}) { + SENT_TRANSFER_BITS.push(...bits); } function configurationChanged() { updatePacketUtils(); updateStreamManager(); updateAudioSender(); + updateAudioReceiver(); showChannelList(); updateFrequencyResolution(); updatePacketStats(); @@ -243,12 +243,39 @@ function updateAudioSender() { AudioSender.changeConfiguration({ channels: getChannels(), destination: SEND_VIA_SPEAKER ? audioContext.destination : getAnalyser(), - startCallback: handleAudioSenderStart, - stopCallback: handleAudioSenderStop, - sendCallback: handleAudioSenderSend, waveForm: WAVE_FORM }); } +const logFn = text => (...args) => { + // console.log(text, ...args); +} +const handleAudioReceiverStart = ({signalStart}) => { + StreamManager.reset(); + RECEIVED_STREAM_START_MS = signalStart; +} +const handleAudioReceiverReceive = ({signalStart, signalIndex, indexStart, bits}) => { + const packetIndex = PacketUtils.getPacketIndex(signalStart, indexStart); + const segmentIndex = PacketUtils.getPacketSegmentIndex(signalStart, indexStart); + // Getting all 1's for only the first 5 segments? + // console.log(signalIndex, packetIndex, segmentIndex, bits.join('')); + StreamManager.addBits(packetIndex, segmentIndex, bits); +} +const handleAudioReceiverEnd = () => { + if(PAUSE_AFTER_END) { + stopGraph(); + AudioSender.stop(); + } +} +function updateAudioReceiver() { + AudioReceiver.changeConfiguration({ + fskSets: getChannels(), + segmentDurationMs: SEGMENT_DURATION, + amplitudeThreshold: AMPLITUDE_THRESHOLD, + analyser: getAnalyser(), + signalIntervalMs: SEGMENT_DURATION, + sampleRate: getAudioContext().sampleRate + }); +} function updateStreamManager() { StreamManager.setPacketEncoding( HAMMING_ERROR_CORRECTION ? HammingEncoding : undefined @@ -522,26 +549,14 @@ function padArray(values, length, value) { function stopGraph() { PAUSE = true; - stopCollectingSamples(); -} -function startCollectingSamples() { - for(let i = 0; i < SAMPLING_INTERVAL_COUNT; i++) { - if(sampleIntervalIds[i]) continue; - sampleIntervalIds[i] = window.setInterval( - collectSample, - MINIMUM_INTERVAL_MS + (i/SAMPLING_INTERVAL_COUNT) - ); - } -} -function stopCollectingSamples() { - sampleIntervalIds.forEach(window.clearInterval); - sampleIntervalIds = sampleIntervalIds.map(() => {}); + AudioReceiver.stop(); } + function resumeGraph() { if(isListeningCheckbox.checked) { if(PAUSE) { PAUSE = false; - startCollectingSamples(); + AudioReceiver.start(); resetGraphData(); requestAnimationFrame(drawFrequencyData); } else { @@ -551,196 +566,6 @@ function resumeGraph() { PAUSE = false; } } -function collectSample() { - const time = performance.now(); - // Do nothing if we already collected the sample - if(time === SAMPLE_LAST_COLLECTED) return; - SAMPLE_LAST_COLLECTED = time; - const frequencies = new Uint8Array(analyser.frequencyBinCount); - analyser.getByteFrequencyData(frequencies); - const length = audioContext.sampleRate / analyser.fftSize; - // Get amplitude of each channels set of frequencies - const channelAmps = getChannels().map(hzSet => hzSet.map(hz => frequencies[Math.round(hz / length)])); - const hasSignal = channelAmps.some(amps => amps.some(amp => amp > AMPLITUDE_THRESHOLD)); - if(hasSignal) { - abandonPauseAfterLastSignalEnded(); - if(time > RECEIVED_STREAM_END_MS) { - resetReceivedData(); - // New stream - RECEIVED_STREAM_START_MS = time; - // Assume at least 1 full packet arriving - RECEIVED_STREAM_END_MS = getPacketEndMilliseconds(time); - } - } else { - pauseAfterSignalEnds(); - } - if(time >= RECEIVED_STREAM_START_MS && time <= RECEIVED_STREAM_END_MS) { - // determine packet/segment index based on time as well as start/end times for packet - const packetIndex = PacketUtils.getPacketIndex(RECEIVED_STREAM_START_MS, time); - const segmentIndex = PacketUtils.getPacketSegmentIndex(RECEIVED_STREAM_START_MS, time); - SAMPLES.unshift({ - time, - pairs: channelAmps, - packetIndex, - segmentIndex, - streamStarted: RECEIVED_STREAM_START_MS, - }); - } - processSamples(); - truncateGraphData(); -} -function abandonPauseAfterLastSignalEnded() { - if(pauseGraphId) { - window.clearTimeout(pauseGraphId); - pauseGraphId = undefined; - } -} -function pauseAfterSignalEnds() { - // If we never had a signal, do nothing. - if(RECEIVED_STREAM_START_MS === -1) return; - // If we continue after a signal ends, do nothing. - if(!PAUSE_AFTER_END) return; - // If we are already setup to pause, do nothing - if(pauseGraphId) return; - - // pause after waiting for 2 segments to come through - let delay = PacketUtils.getSegmentDurationMilliseconds() * 2; - - // Long segments? Pause for no more than 400 milliseconds - delay = Math.min(400, delay); - - // we haven't paused yet. Let's prepare to pause - pauseGraphId = window.setTimeout(() => { - pauseGraphId = undefined; - // if user still wants to pause, stop the graph - if(PAUSE_AFTER_END) { - stopGraph(); - - // are we the sender as well? - // Stop sending the signal. - AudioSender.stop(); - } - }, delay); -} - -function hasSampleSegmentCompleted(now) { - return ({streamStarted, packetIndex, segmentIndex}) => now > - PacketUtils.getPacketSegmentEndMilliseconds(streamStarted, packetIndex, segmentIndex); -} -function hasSamplePacketCompleted(now) { - return ({streamStarted, packetIndex}) => now > - getPacketIndexEndMilliseconds(streamStarted, packetIndex); -} -function consolidateFotPackets(all, {streamStarted, packetIndex}) { - const isMatch = (fot) => { - fot.streamStarted === streamStarted && - fot.packetIndex === packetIndex - }; - - if(!all.some(isMatch)) - all.push({streamStarted, packetIndex}); - return all; -} -const consolidateUnprocessedSampleSegments = now => (all, { - streamStarted, - packetIndex, - segmentIndex, - processedSegment -}) => { - - const isMatch = (sample) => { - sample.streamStarted === streamStarted && - sample.packetIndex === packetIndex && - sample.segmentIndex === segmentIndex - }; - - if(!processedSegment) { - if(!all.some(isMatch)) { - const end = PacketUtils.getPacketSegmentEndMilliseconds(streamStarted, packetIndex, segmentIndex); - if(end < now) - all.push({ - streamStarted, - packetIndex, - segmentIndex - }); - } - } - return all; -} -const markSampleSegmentProcessed = sample => sample.processedSegment = true; -const hasNotProcessedPacket = sample => sample.processedPacket; -const markSamplePacketProcessed = sample => sample.processedPacket = true; - -function processSamples() { - const now = performance.now(); - // Process completed segments - SAMPLES - .reduce(consolidateUnprocessedSampleSegments(now), []) - .every(({ - streamStarted, packetIndex, segmentIndex - }) => { - processSegmentReceived(streamStarted, packetIndex, segmentIndex); - }); - - // Process completed packets - SAMPLES - .filter(hasNotProcessedPacket) - .reduce(consolidateFotPackets, []) - .filter(hasSamplePacketCompleted(now)) - .every(({ - streamStarted, packetIndex - }) => { - processPacketReceived(streamStarted, packetIndex); - }); -} - -function GET_SEGMENT_BITS(streamStarted, segmentIndex, packetIndex, originalOrder = false) { - - const samples = SAMPLES.filter(f => - f.segmentIndex === segmentIndex && - f.packetIndex === packetIndex && - f.streamStarted === streamStarted - ); - const channelCount = SAMPLES[0].pairs.length; - const channelFrequencyCount = 2; - const sums = new Array(channelCount) - .fill(0) - .map(() => - new Array(channelFrequencyCount) - .fill(0) - ); - samples.forEach(({pairs}) => { - pairs.forEach((amps, channel) => { - amps.forEach((amp, i) => { - sums[channel][i] += amp; - }); - }); - }); - const bitValues = sums.map((amps) => amps[0] > amps[1] ? 0 : 1); - // if(packetIndex === 0 && segmentIndex === 1) { - // console.log(packetIndex, segmentIndex, bitValues.join('')) - // } - return originalOrder ? bitValues : InterleaverEncoding.decode(bitValues); -} -function resetReceivedData() { - resetStream(); - resetPacket(); -} -function resetStream() { - RECEIVED_STREAM_END_MS = -1; - RECEIVED_STREAM_START_MS = -1; - RECEIVED_SEGMENT_BITS.length = 0; -} -function resetPacket() { -} -function processPacketReceived(streamStarted, packetIndex) { - SAMPLES.filter( - fot => fot.streamStarted === streamStarted && - fot.packetIndex === packetIndex - ).forEach(markSamplePacketProcessed); - resetPacket(); - updateReceivedData(); -} function getTransferredCorrectedBits() { const bits = []; @@ -755,25 +580,8 @@ function getTransferredCorrectedBits() { } return bits; } -function processSegmentReceived(streamStarted, packetIndex, segmentIndex) { - const isSegment = sample => ( - sample.streamStarted === streamStarted && - sample.packetIndex === packetIndex && - sample.segmentIndex === segmentIndex - ); - - let bitValues = GET_SEGMENT_BITS(streamStarted, segmentIndex, packetIndex, segmentIndex, true); - - StreamManager.addBits(packetIndex, segmentIndex, bitValues); - - // mark samples as processed - SAMPLES.filter(isSegment).every(markSampleSegmentProcessed); - - updateReceivedData(); -} - -function updateReceivedData() { +function handleStreamManagerChange() { const channelCount = getChannels().length; let allRawBits = StreamManager.getStreamBits(); let allEncodedBits = StreamManager.getAllPacketBits(); @@ -1005,22 +813,6 @@ function resetGraphData() { SAMPLES.length = 0; bitStart.length = 0; } -function truncateGraphData() { - const duration = SEGMENT_DURATION * MAX_BITS_DISPLAYED_ON_GRAPH; - const now = performance.now(); - let length = SAMPLES.length; - while(length !== 0) { - const time = SAMPLES[length-1].time; - if(now - time > duration) length--; - else break; - } - if(length !== SAMPLES.length) { - SAMPLES.length = length; - bitStart.length = length; - } - // remove processed segments - SAMPLES = SAMPLES.filter(s => !s.segmentProcessed); -} function getAudioContext() { if(!audioContext) { audioContext = new (window.AudioContext || webkitAudioContext)(); @@ -1081,14 +873,14 @@ function bitsToText(bits) { return bytesToText(bytes.buffer); } function handleSendButtonClick() { - if(stopOscillatorsTimeoutId) { - disconnectOscillators(); - return; + if(sendButton.innerText === 'Stop') { + AudioSender.stop(); + } else { + AudioReceiver.reset(); + StreamManager.reset(); + const text = document.getElementById('text-to-send').value; + sendBytes(textToBytes(text)); } - resetReceivedData(); - - const text = document.getElementById('text-to-send').value; - sendBytes(textToBytes(text)); } function getAnalyser() { if(analyser) return analyser;