separate bits into blocks & packets

This commit is contained in:
Lewis Moten
2024-05-08 03:19:50 -04:00
parent 462adc30ee
commit 3a787f02ca
3 changed files with 185 additions and 66 deletions

View File

@@ -17,7 +17,7 @@
</label><br /> </label><br />
<input type="text" id="text-to-send"> <input type="text" id="text-to-send">
<button id="send-button">Send</button><br /> <button id="send-button">Send</button><br />
<textarea id="sent-data" rows="10" cols="40"></textarea><br /> <div class="raw-data" id="sent-data"></div><br />
</div> </div>
<div> <div>
<h2>Data</h2> <h2>Data</h2>
@@ -27,6 +27,8 @@
Data Bits per Packet: <span id="packet-data-bit-count"></span><br> Data Bits per Packet: <span id="packet-data-bit-count"></span><br>
Error Correction: <span id="packet-error-correction"></span><br> Error Correction: <span id="packet-error-correction"></span><br>
Error Blocks per packet: <span id="packet-error-block-count"></span><br> Error Blocks per packet: <span id="packet-error-block-count"></span><br>
Error Bits per block: <span id="packet-error-bits-per-block"></span><br>
Error Correcting Bits per Packet: <span id="packet-error-bit-count"></span><br>
Unused bits per packet: <span id="packet-unused-bit-count"></span><br> Unused bits per packet: <span id="packet-unused-bit-count"></span><br>
Unused bits in last packet: <span id="last-packet-unused-bit-count"></span><br> Unused bits in last packet: <span id="last-packet-unused-bit-count"></span><br>
Last segment unused channels in packet: <span id="last-segment-unused-channel-count"></span><br> Last segment unused channels in packet: <span id="last-segment-unused-channel-count"></span><br>
@@ -38,8 +40,12 @@
Total Duration: <span id="data-transfer-duration"></span><br> Total Duration: <span id="data-transfer-duration"></span><br>
</div> </div>
<div> <div>
<h2>Encoded</h2> <h2>Error Correcting</h2>
<textarea id="encoded-data" rows="10" cols="40"></textarea><br /> <div class="raw-data" id="error-correcting-data"></div><br />
</div>
<div>
<h2 >Bits Sent</h2>
<div class="raw-data" id="actual-bits-to-send"></div><br />
</div> </div>
<div> <div>
<label> <label>

229
index.js
View File

@@ -12,6 +12,11 @@ var receivedGraph;
var receivedData = []; var receivedData = [];
var MAX_AMPLITUDE = 300; // Higher than 255 to give us space var MAX_AMPLITUDE = 300; // Higher than 255 to give us space
let RECEIVED_STREAM_DECODED_BITS = [];
let RECEIVED_PACKET_DECODED_BITS = [];
let RECEIVED_PACKET_BITS = [];
let RECEIVED_STREAM_BITS = [];
const CHANNEL_OSCILLATORS = []; const CHANNEL_OSCILLATORS = [];
// bit stream // bit stream
@@ -301,6 +306,8 @@ function updatePacketStats() {
document.getElementById('data-transfer-duration').innerText = getDataTransferDurationSeconds(bitCount).toLocaleString() + 's'; document.getElementById('data-transfer-duration').innerText = getDataTransferDurationSeconds(bitCount).toLocaleString() + 's';
document.getElementById('segments-per-packet').innerText = getPacketSegmentCount().toLocaleString(); document.getElementById('segments-per-packet').innerText = getPacketSegmentCount().toLocaleString();
document.getElementById('total-segments').innerText = getTotalSegmentCount(bitCount).toLocaleString(); document.getElementById('total-segments').innerText = getTotalSegmentCount(bitCount).toLocaleString();
document.getElementById('packet-error-bits-per-block').innerText = (HAMMING_ERROR_CORRECTION ? ERROR_CORRECTION_BLOCK_SIZE : 0).toLocaleString();
document.getElementById('packet-error-bit-count').innerText = getPacketErrorBitCount();
} }
function drawChannels() { function drawChannels() {
const sampleRate = getAudioContext().sampleRate; const sampleRate = getAudioContext().sampleRate;
@@ -343,14 +350,6 @@ function drawChannels() {
ctx.stroke(); ctx.stroke();
} }
/*
const binWidth = (1 / frequencySegments) * width;
for(let x = 0; x < width; x+= binWidth * 2) {
console.log(x);
ctx.fillStyle = 'rgba(0, 0, 0, 0.25)';
ctx.fillRect(x, 0, binWidth, height);
}
*/
} }
function percentInFrequency(hz, frequencyResolution) { function percentInFrequency(hz, frequencyResolution) {
@@ -505,16 +504,23 @@ function sendBits(bits) {
const totalDurationSeconds = getDataTransferDurationSeconds(bitCount); const totalDurationSeconds = getDataTransferDurationSeconds(bitCount);
const totalDurationMilliseconds = getDataTransferDurationMilliseconds(bitCount); const totalDurationMilliseconds = getDataTransferDurationMilliseconds(bitCount);
const channelCount = getChannels().length;
const errorCorrectionBits = [];
const interleavingBits = [];
createOscillators(startSeconds); createOscillators(startSeconds);
// send all packets // send all packets
for(let i = 0; i < packetCount; i++) { for(let i = 0; i < packetCount; i++) {
let packet = getPacketBits(bits, i); let packet = getPacketBits(bits, i);
errorCorrectionBits.push(...packet);
if(packet.length > packetBitCount) { if(packet.length > packetBitCount) {
console.error('Too many bits in the packet.'); console.error('Too many bits in the packet.');
disconnectOscillators(); disconnectOscillators();
return; return;
} }
packet = padArray(packet, packetBitCount, 0);
packet = applyInterleaving(packet); packet = applyInterleaving(packet);
interleavingBits.push(...packet);
EXPECTED_ENCODED_BITS.push(...packet); EXPECTED_ENCODED_BITS.push(...packet);
sendPacket(packet, startSeconds + (i * packetDurationSeconds)); sendPacket(packet, startSeconds + (i * packetDurationSeconds));
} }
@@ -524,10 +530,31 @@ function sendBits(bits) {
startMilliseconds + totalDurationMilliseconds startMilliseconds + totalDurationMilliseconds
); );
// show what was sent // show what was sent
document.getElementById('sent-data').value =
EXPECTED_BITS.reduce(bitReducer, ''); // original bits
document.getElementById('encoded-data').value = document.getElementById('sent-data').innerHTML =
EXPECTED_ENCODED_BITS.reduce(bitReducer, ''); EXPECTED_BITS.reduce(bitReducer(
getPacketDataBitCount(),
HAMMING_ERROR_CORRECTION ? ERROR_CORRECTION_DATA_SIZE : 8
), '');
// error correcting bits
if(HAMMING_ERROR_CORRECTION) {
document.getElementById('error-correcting-data').innerHTML =
errorCorrectionBits.reduce(bitReducer(
getPacketErrorBitCount(),
ERROR_CORRECTION_BLOCK_SIZE
), '');
} else {
document.getElementById('error-correcting-data').innerHTML = '';
}
document.getElementById('actual-bits-to-send').innerHTML =
interleavingBits.reduce(bitReducer(
getPacketBitCount() + getPacketLastSegmentUnusedChannelCount(),
channelCount,
(packetIndex, blockIndex) => `${blockIndex === 0 ? '' : '<br>'}Segment ${blockIndex}: `
), '');
// start the graph moving again // start the graph moving again
resumeGraph(); resumeGraph();
@@ -583,13 +610,7 @@ function getPacketCount(bitCount) {
} }
function getPacketBits(bits, packetIndex) { function getPacketBits(bits, packetIndex) {
if(!canSendPacket()) return []; if(!canSendPacket()) return [];
const packetBits = getPacketUsedBits(bits, packetIndex); return getPacketUsedBits(bits, packetIndex);
// How many bits expected in our packet?
const packetBitCount = getPacketBitCount();
// pad the array to the entire packet size
return padArray(packetBits, packetBitCount, 0);
} }
function getPacketUsedBits(bits, packetIndex) { function getPacketUsedBits(bits, packetIndex) {
if(!canSendPacket()) return []; if(!canSendPacket()) return [];
@@ -622,6 +643,9 @@ function getPacketErrorBlockCount() {
ERROR_CORRECTION_BLOCK_SIZE ERROR_CORRECTION_BLOCK_SIZE
); );
} }
function getPacketErrorBitCount() {
return getPacketErrorBlockCount() * ERROR_CORRECTION_BLOCK_SIZE;
}
function canSendPacket() { function canSendPacket() {
const max = getPacketBitCount(); const max = getPacketBitCount();
// Need at least 1 bit to send // Need at least 1 bit to send
@@ -639,7 +663,12 @@ function getPacketLastSegmentUnusedChannelCount() {
return (channelCount - (getPacketBitCount() % channelCount)); return (channelCount - (getPacketBitCount() % channelCount));
} }
function getPacketUnusedBitCount() { function getPacketUnusedBitCount() {
return getPacketBitCount() - getPacketDataBitCount(); const bitsAvailable = getPacketBitCount();
let bitsUsed = bitsAvailable;
if(HAMMING_ERROR_CORRECTION) {
bitsUsed = getPacketErrorBitCount();
}
return bitsAvailable - bitsUsed;
} }
function getPacketLastUnusedBitCount(bitCount) { function getPacketLastUnusedBitCount(bitCount) {
const availableBits = getPacketBitCount(); const availableBits = getPacketBitCount();
@@ -764,7 +793,8 @@ function collectSample() {
hasSignal: hadPriorSignal, hasSignal: hadPriorSignal,
streamStarted: initialStreamStart = -1, streamStarted: initialStreamStart = -1,
streamEnded: priorStreamEnded = -1, streamEnded: priorStreamEnded = -1,
segmentIndex: priorSegmentIndex = -1 segmentIndex: priorSegmentIndex = -1,
packetIndex: priorPacketIndex = -1
} = frequencyOverTime[0] ?? {} } = frequencyOverTime[0] ?? {}
const data = { const data = {
time, time,
@@ -775,43 +805,51 @@ function collectSample() {
// Get amplitude of each channels set of frequencies // Get amplitude of each channels set of frequencies
data.pairs = getChannels().map(hzSet => hzSet.map(hz => frequencies[Math.round(hz / length)])); data.pairs = getChannels().map(hzSet => hzSet.map(hz => frequencies[Math.round(hz / length)]));
const hasSignal = data.hasSignal = data.pairs.some(amps => amps.some(amp => amp > AMPLITUDE_THRESHOLD)); const hasSignal = data.hasSignal = data.pairs.some(amps => amps.some(amp => amp > AMPLITUDE_THRESHOLD));
let processPacket = false;
if(hasSignal) { if(hasSignal) {
if(hadPriorSignal) { if(hadPriorSignal) {
// continued bit stream // continued bit stream
data.streamStarted = initialStreamStart; data.streamStarted = initialStreamStart;
// proposed end
data.streamEnded = priorStreamEnded; data.streamEnded = priorStreamEnded;
data.packetIndex = priorPacketIndex;
if(time > priorStreamEnded) {
processPacketReceived(priorPacketIndex, initialStreamStart, priorStreamEnded);
// new packet
data.streamStarted = priorStreamEnded;
data.streamEnded = initialStreamStart + getPacketDurationMilliseconds();
data.packetIndex = priorPacketIndex + 1;
}
} else { } else {
const totalBits = 2 ** PACKET_SIZE_BITS;
const segments = Math.ceil(totalBits / channelCount);
const duration = segments * SEGMENT_DURATION;
if(pauseGraphId) { if(pauseGraphId) {
window.clearTimeout(pauseGraphId); window.clearTimeout(pauseGraphId);
pauseGraphId = undefined; pauseGraphId = undefined;
// recover prior bit stream // recover prior bit stream
data.streamStarted = LAST_STREAM_STARTED; data.streamStarted = LAST_STREAM_STARTED;
data.streamEnded = LAST_STREAM_STARTED + duration; data.streamEnded = LAST_STREAM_STARTED + getPacketDurationMilliseconds();
} else { } else {
// new bit stream // new bit stream
data.streamStarted = time; data.streamStarted = time;
LAST_STREAM_STARTED = time; LAST_STREAM_STARTED = time;
data.packetIndex = 0;
// clear last packet // clear last packet
packetReceivedBits.length = 0; packetReceivedBits.length = 0;
packetUninterlievedBits.length = 0; packetUninterlievedBits.length = 0;
packetDataByteCount = 0; packetDataByteCount = 0;
data.streamEnded = time + duration; data.streamEnded = time + getPacketDurationMilliseconds();
} }
} }
// number of bit in the stream // number of bit in the packet
const segmentIndex = data.segmentIndex = Math.floor((time - data.streamStarted) / SEGMENT_DURATION); const segmentIndex = data.segmentIndex = Math.floor((time - data.streamStarted) / SEGMENT_DURATION);
if(priorSegmentIndex !== segmentIndex && priorSegmentIndex > -1) { if(priorPacketIndex !== data.packetIndex ||
(priorSegmentIndex !== segmentIndex && priorSegmentIndex > -1)
) {
processSegment = true; processSegment = true;
} }
} else { } else {
data.segmentIndex = -1; data.segmentIndex = -1;
data.packetIndex = -1;
if(hadPriorSignal) { if(hadPriorSignal) {
// just stopped // just stopped
data.streamStarted = -1; data.streamStarted = -1;
@@ -828,6 +866,7 @@ function collectSample() {
pauseGraphId = window.setTimeout(() => { pauseGraphId = window.setTimeout(() => {
pauseGraphId = undefined; pauseGraphId = undefined;
if(PAUSE_AFTER_END) stopGraph(); if(PAUSE_AFTER_END) stopGraph();
processPacketReceived(priorPacketIndex, initialStreamStart, priorStreamEnded);
}, SEGMENT_DURATION * 2); }, SEGMENT_DURATION * 2);
} }
} else { } else {
@@ -863,72 +902,140 @@ function GET_SEGMENT_BITS(streamStarted, segmentIndex, originalOrder = false) {
const bitValues = sums.map((amps) => amps[0] > amps[1] ? 0 : 1); const bitValues = sums.map((amps) => amps[0] > amps[1] ? 0 : 1);
return originalOrder ? bitValues : removeInterleaving(bitValues); return originalOrder ? bitValues : removeInterleaving(bitValues);
} }
function processSegmentReceived(streamStarted, segmentIndex) {
const { function processPacketReceived(packetIndex, startMs, endMs) {
pairs: { if(packetIndex === 0) {
length: channelCount // Reset received data from last stream
} RECEIVED_STREAM_BITS.length = 0;
} = frequencyOverTime[0]; RECEIVED_STREAM_DECODED_BITS.length = 0;
}
// append to the stream
RECEIVED_STREAM_BITS.push(...RECEIVED_PACKET_BITS);
RECEIVED_STREAM_DECODED_BITS.push(...RECEIVED_PACKET_DECODED_BITS);
// reset the packet
RECEIVED_PACKET_BITS.length = 0;
RECEIVED_PACKET_DECODED_BITS.length = 0;
packetReceivedBits.length = 0;
packetUninterlievedBits.length = 0;
packetDecodedBits.length = 0;
// display what was received
updateReceivedData();
}
function processSegmentReceived(packetStarted, segmentIndex) {
// is our segment long enough? // is our segment long enough?
const samples = frequencyOverTime.filter( const samples = frequencyOverTime.filter(
fot => fot.streamStarted === streamStarted && fot => fot.streamStarted === packetStarted &&
fot.segmentIndex === segmentIndex fot.segmentIndex === segmentIndex
); );
let bitValues; let bitValues;
if(samples.length === 0) { if(samples.length === 0) {
// nothing collected // nothing collected
// bitValues = new Array(channelCount).fill(0);
return; return;
} else { } else {
const sampleEnd = samples[0].time; const sampleEnd = samples[0].time;
const sampleStart = streamStarted + (segmentIndex * SEGMENT_DURATION); const sampleStart = packetStarted + (segmentIndex * SEGMENT_DURATION);
const sampleDuration = (sampleEnd - sampleStart) + MINIMUM_INTERVAL_MS; const sampleDuration = (sampleEnd - sampleStart) + MINIMUM_INTERVAL_MS;
// not long enough to qualify as a segment // not long enough to qualify as a segment
if((sampleDuration / SEGMENT_DURATION) < LAST_SEGMENT_PERCENT) return; if((sampleDuration / SEGMENT_DURATION) < LAST_SEGMENT_PERCENT) return;
bitValues = GET_SEGMENT_BITS(streamStarted, segmentIndex, true); bitValues = GET_SEGMENT_BITS(packetStarted, segmentIndex, true);
} }
packetReceivedBits.push(...bitValues); packetReceivedBits.push(...bitValues);
packetUninterlievedBits.push(...removeInterleaving(bitValues)); packetUninterlievedBits.push(...removeInterleaving(bitValues));
if(HAMMING_ERROR_CORRECTION) { if(HAMMING_ERROR_CORRECTION) {
const errorBitCount = getPacketErrorBitCount();
const errorBits = packetUninterlievedBits.slice(0, errorBitCount);
packetDecodedBits.length = 0; packetDecodedBits.length = 0;
for(let i = 0; i < packetUninterlievedBits.length; i += ERROR_CORRECTION_BLOCK_SIZE) { for(let i = 0; i < errorBits.length; i += ERROR_CORRECTION_BLOCK_SIZE) {
const hamming = packetUninterlievedBits.slice(i, i + ERROR_CORRECTION_BLOCK_SIZE); const blockBits = errorBits.slice(i, i + ERROR_CORRECTION_BLOCK_SIZE);
const nibble = hammingToNibble(hamming); if(blockBits.length === ERROR_CORRECTION_BLOCK_SIZE) {
packetDecodedBits.push(...nibble); const nibble = hammingToNibble(blockBits);
packetDecodedBits.push(...nibble);
}
} }
} else { } else {
packetDecodedBits.length = 0; packetDecodedBits.length = 0;
packetDecodedBits.push(...packetUninterlievedBits); packetDecodedBits.push(...packetUninterlievedBits);
} }
document.getElementById('decoded-data').innerHTML = packetDecodedBits.reduce(bitExpectorReducer(EXPECTED_BITS), ''); const maxPacketBits = getPacketBitCount();
document.getElementById('received-data').innerHTML = packetReceivedBits.reduce(bitExpectorReducer(EXPECTED_ENCODED_BITS), ''); const maxDataBits = getPacketDataBitCount();
RECEIVED_PACKET_DECODED_BITS = packetDecodedBits.slice(0, maxDataBits);
RECEIVED_PACKET_BITS = packetReceivedBits.slice(0, maxPacketBits);
updateReceivedData();
}
function updateReceivedData() {
const allReceivedBits = [
...RECEIVED_STREAM_DECODED_BITS,
...RECEIVED_PACKET_DECODED_BITS
];
const allDecodedBits = [
...RECEIVED_STREAM_BITS,
...RECEIVED_PACKET_BITS
]
const encodedBitCount = EXPECTED_ENCODED_BITS.length; const encodedBitCount = EXPECTED_ENCODED_BITS.length;
const decodedBitCount = EXPECTED_BITS.length; const decodedBitCount = EXPECTED_BITS.length;
const correctEncodedBits = packetReceivedBits.filter((b, i) => i < encodedBitCount && b === EXPECTED_ENCODED_BITS[i]).length; const correctEncodedBits = allReceivedBits.filter((b, i) => i < encodedBitCount && b === EXPECTED_ENCODED_BITS[i]).length;
const correctedDecodedBits = packetDecodedBits.filter((b, i) => i < decodedBitCount && b === EXPECTED_BITS[i]).length; const correctedDecodedBits = allDecodedBits.filter((b, i) => i < decodedBitCount && b === EXPECTED_BITS[i]).length;
document.getElementById('received-data').innerHTML = allReceivedBits
.reduce(
bitExpectorReducer(
EXPECTED_BITS,
getPacketBitCount(),
HAMMING_ERROR_CORRECTION ? 7 : 8
),
'');
document.getElementById('decoded-data').innerHTML = allDecodedBits
.reduce(
bitExpectorReducer(
EXPECTED_ENCODED_BITS,
getPacketDataBitCount(),
8
),
'');
document.getElementById('received-data-error-percent').innerText = ( document.getElementById('received-data-error-percent').innerText = (
Math.floor((1 - (correctEncodedBits / packetReceivedBits.length)) * 1000) * 0.1 Math.floor((1 - (correctEncodedBits / allReceivedBits.length)) * 10000) * 0.01
).toLocaleString(); ).toLocaleString();
document.getElementById('decoded-data-error-percent').innerText = ( document.getElementById('decoded-data-error-percent').innerText = (
Math.floor((1 - (correctedDecodedBits / packetDecodedBits.length)) * 1000) * 0.1 Math.floor((1 - (correctedDecodedBits / allDecodedBits.length)) * 10000) * 0.01
).toLocaleString(); ).toLocaleString();
document.getElementById('decoded-text').innerHTML = packetDecodedBits.reduce(textExpectorReducer(EXPECTED_TEXT), ''); document.getElementById('decoded-text').innerHTML = allDecodedBits.reduce(textExpectorReducer(EXPECTED_TEXT), '');
} }
function bitReducer(all, bit, i) { const bitReducer = (packetBitSize, blockSize, blockCallback) => (all, bit, i) => {
if(i !== 0 && i % 8 === 0) return all + ' ' + bit; const packetIndex = Math.floor(i / packetBitSize);
if(i % packetBitSize === 0) {
all += `<span class="bit-packet">Packet ${packetIndex}</span>`;
}
const packetBitIndex = i % packetBitSize;
if(packetBitIndex % blockSize === 0) {
if(blockCallback) {
const blockIndex = Math.floor(packetBitIndex / blockSize);
return all + blockCallback(packetIndex, blockIndex) + bit;
}
return all + ' ' + bit;
}
return all + bit; return all + bit;
} }
const bitExpectorReducer = expected => (all, bit, i) => { const bitExpectorReducer = (expected, packetBitSize, blockSize) => (all, bit, i) => {
// if(i === 0) console.log(expected.slice(), all, bit, i); const packetIndex = Math.floor(i / packetBitSize);
if(i % packetBitSize === 0) {
all += `<span class="bit-packet">Packet ${packetIndex}</span>`;
}
const packetBitIndex = i % packetBitSize;
if(i !== 0 && i % 8 === 0) all += ' '; if(packetBitIndex !== 0 && packetBitIndex % blockSize === 0) all += ' ';
if(i >= expected.length) { if(i >= expected.length) {
all += '<span class="bit-unexpected">'; all += '<span class="bit-unexpected">';
} else if(expected[i] !== bit) { } else if(expected[i] !== bit) {

View File

@@ -21,16 +21,22 @@ body {
canvas { canvas {
background-color: black; background-color: black;
} }
textarea, .raw-data { .raw-data {
background-color: rgb(41, 59, 10); background-color: rgb(41, 59, 10);
color: rgb(75, 185, 75); color: rgb(75, 185, 75);
width: 250px; width: 250px;
height: 75px; height: 300px;
font-size: x-small; font-size: x-small;
border: 1px solid grey; border: 1px solid grey;
overflow: auto; overflow: auto;
font-family: monospace; font-family: monospace;
} }
.bit-packet {
display: block;
color: yellow;
text-decoration: underline;
padding: 5px 0 5px 10px
}
.bit-wrong { .bit-wrong {
font-weight: bolder; font-weight: bolder;
color: red; color: red;