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
+
+
+
-
-
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;