diff --git a/index.html b/index.html index 0becc98..046775d 100644 --- a/index.html +++ b/index.html @@ -1,52 +1,67 @@ - - Data Over Audio - - - - -

Data Over Audio

-
+ + + Data Over Audio + + + + + +

Data Over Audio

+
+
+ Segment Duration: ms
+ Amplitude Threshold:
+ Minimum Frequency:
+ Maximum Frequency:
+ Last Segment Percent: %
+ FFT Size: 2^
+ Frequency Resolution: N/A
+ Frequency Count: N/A
+ Frequency Resolution Multiplier:
+ Smoothing Time Constant:
+
+ + +

Sent

+
+
+
+ +

Received

+
+ Samples Per Bit: 0
+ Sample Rate: N/A per second.
+ Segments per second: N/A
+ Bits per segment: N/A
+ Speed: N/A Baud
+ N/A Bytes/second
+ Effective Speed: N/A Baud
+ N/A Bytes/second
+
+
+
- Segment Duration: ms
- Amplitude Threshold:
- Minimum Frequency:
- Maximum Frequency:
- Last Segment Percent: %
- FFT Size: 2^
- Frequency Resolution: N/A
- Frequency Count: N/A
- Frequency Resolution Multiplier:
- Smoothing Time Constant:
- - -

Sent

-
+

Decoded

+
+ Data Byte Count: N/A
-
- -

Received

-
- Samples Per Bit: 0
- Sample Rate: N/A per second.
- Segments per second: N/A
- Bits per segment: N/A
- Speed: N/A Baud
- N/A Bytes/second
-
-
- -
+

+
Max Bits Displayed:
- + + \ No newline at end of file diff --git a/index.js b/index.js index 07ca2f6..7122175 100644 --- a/index.js +++ b/index.js @@ -14,6 +14,7 @@ var MAX_BITS_DISPLAYED_ON_GRAPH = 9; var MAX_DATA = 300; var pauseTimeoutId; var sampleIntervalId; +var HAMMING_ERROR_CORRECTION = true; // 20 to 20,000 - human var TEXT_TO_SEND = "Hello World!"; @@ -36,6 +37,7 @@ var PAUSE_AFTER_END = true; var PACKET_SIZE_BITS = 10; const packetBits = []; +const packetDecodedBits = []; let packetDataByteCount = -1; function handleWindowLoad() { @@ -49,6 +51,11 @@ function handleWindowLoad() { sentDataTextArea = document.getElementById('sent-data'); samplesPerBitLabel = document.getElementById('samples-per-bit'); document.getElementById('pause-after-end').checked = PAUSE_AFTER_END; + document.getElementById('error-correction-hamming').checked = HAMMING_ERROR_CORRECTION; + document.getElementById('error-correction-hamming').addEventListener('change', event => { + HAMMING_ERROR_CORRECTION = event.target.checked; + showSpeed(); + }) document.getElementById('pause-after-end').addEventListener('change', event => { PAUSE_AFTER_END = event.target.checked; if(!PAUSE_AFTER_END) resumeGraph(); @@ -131,6 +138,47 @@ function showSpeed() { document.getElementById('bits-per-duration').innerText = bitsPerSegment; document.getElementById('data-transfer-speed-bits-per-second').innerText = baud.toFixed(2); document.getElementById('data-transfer-speed-bytes-per-second').innerText = bytes.toFixed(2); + if(HAMMING_ERROR_CORRECTION) { + const effectiveBaud = baud * 4 / 7; + const effectiveBytes = effectiveBaud / 8; + document.getElementById('effective-speed-bits-per-second').innerText = effectiveBaud.toFixed(2); + document.getElementById('effective-speed-bytes-per-second').innerText = effectiveBytes.toFixed(2); + } else { + const effectiveBaud = baud; + const effectiveBytes = effectiveBaud / 8; + document.getElementById('effective-speed-bits-per-second').innerText = effectiveBaud.toFixed(2); + document.getElementById('effective-speed-bytes-per-second').innerText = effectiveBytes.toFixed(2); + } +} +function nibbleToHamming(nibble) { + if(nibble.length !== 4) return []; + return [ + nibble[0] ^ nibble[1] ^ nibble[3], + nibble[0] ^ nibble[2] ^ nibble[3], + nibble[0], + nibble[1] ^ nibble[2] ^ nibble[3], + nibble[1], + nibble[2], + nibble[3] + ] +} +function hammingToNibble(hamming) { + if(hamming.length !== 7) return []; + const error_1 = hamming[0] ^ hamming[2] ^ hamming[4] ^ hamming[6]; + const error_2 = hamming[1] ^ hamming[2] ^ hamming[5] ^ hamming[6]; + const error_3 = hamming[3] ^ hamming[4] ^ hamming[5] ^ hamming[6]; + let error = (error_3 << 2) | (error_2 << 1) | error_1; + if(error !== 0) { + // don't mutate the array + hamming = hamming.slice(); + hamming[error - 1] ^= 1; // flip + } + return [ + hamming[2], + hamming[4], + hamming[5], + hamming[6] + ]; } function getFrequency(bit) { @@ -175,9 +223,19 @@ function sendBits(bits) { .padStart(PACKET_SIZE_BITS, '0') .split('') .map(Number); + bits.unshift(...packetLength); logSent(bits.join('')); + if(HAMMING_ERROR_CORRECTION) { + const encodedBits = []; + for(let i = 0; i < bits.length; i+= 4) { + const nibble = bits.slice(i, i + 4); + while(nibble.length < 4) nibble.push(0); + encodedBits.push(...nibbleToHamming(bits.slice(i, i + 4))); + } + bits = encodedBits; + } var audioContext = getAudioContext(); const channels = getChannels(); const oscillators = []; @@ -387,21 +445,36 @@ function processSegmentReceived() { packetBits.push(...bitValues); - if(packetBits.length >= 10) { + const encodingRatio = HAMMING_ERROR_CORRECTION ? 7/4 : 1; + if(HAMMING_ERROR_CORRECTION) { + packetDecodedBits.length = 0; + for(let i = 0; i < packetBits.length; i += 7) { + const hamming = packetBits.slice(i, i + 7); + const nibble = hammingToNibble(hamming); + packetDecodedBits.push(...nibble); + console.log(i, hamming, nibble); + } + } else { + packetDecodedBits = packetBits; + } + + if(packetDecodedBits.length >= 10) { // we can evaluate how many bytes are comming - const dataLengthIndex = Math.floor(10 / channelCount); + const encodedBitsNeeded = Math.ceil(10 * encodingRatio); + const dataLengthIndex = Math.floor(encodedBitsNeeded / channelCount); if(dataLengthIndex === segmentIndex) { // we just got the bits we needed - packetDataByteCount = 1 + packetBits + packetDataByteCount = 1 + packetDecodedBits .slice(0, PACKET_SIZE_BITS) .reduce((value, bit) => (value << 1) | bit); + document.getElementById('decoded-byte-count').innerText = packetDataByteCount; // let's get the end time - const totalBits = (packetDataByteCount * 8) + PACKET_SIZE_BITS; + const totalBits = Math.ceil(((packetDataByteCount * 8) + PACKET_SIZE_BITS) * encodingRatio); const segments = Math.ceil(totalBits / channelCount); const duration = segments * FREQUENCY_DURATION; const streamEnded = streamStarted + duration; console.log({ - tenBitNum: packetBits + tenBitNum: packetDecodedBits .slice(0, PACKET_SIZE_BITS).join(''), packetDataByteCount, PACKET_SIZE_BITS, @@ -419,7 +492,7 @@ function processSegmentReceived() { }); } // remove phantom bits - const totalBits = (packetDataByteCount * 8) + PACKET_SIZE_BITS; + const totalBits = Math.ceil(((packetDataByteCount * 8) + PACKET_SIZE_BITS) * encodingRatio); if(packetBits.length > totalBits) { const excess = packetBits.length % totalBits; packetBits.length = totalBits; @@ -427,7 +500,15 @@ function processSegmentReceived() { } } - received(bitValues.join('') + '\n'); + document.getElementById('decoded-data').value = packetDecodedBits.reduce((all, bit, i) => { + if(i !== 0 && i % 8 === 0) return all + ' ' + bit; + return all + bit; + }, ''); + document.getElementById('received-data').value = packetBits.reduce((all, bit, i) => { + if(i !== 0 && i % 8 === 0) return all + ' ' + bit; + return all + bit; + }, ''); + } function resetGraphData() { frequencyOverTime.length = 0;