add hamming error correction

This commit is contained in:
Lewis Moten
2024-05-03 16:50:22 -04:00
parent cee611f5cb
commit 53efd951d9
2 changed files with 143 additions and 47 deletions

View File

@@ -1,52 +1,67 @@
<!DOCTYPE html>
<html>
<head>
<title>Data Over Audio</title>
<link rel="stylesheet" type="text/css" href="style.css" />
<script src="index.js" type="text/javascript"></script>
</head>
<body>
<h1>Data Over Audio</h1>
<div class="panels">
<head>
<title>Data Over Audio</title>
<link rel="stylesheet" type="text/css" href="style.css" />
<script src="index.js" type="text/javascript"></script>
</head>
<body>
<h1>Data Over Audio</h1>
<div class="panels">
<div>
Segment Duration: <input id="bit-duration-text" type="number" min="0" max="1000" value="190">ms<br>
Amplitude Threshold: <input id="amplitude-threshold-text" type="number" min="0" max="100" value="75"><br>
Minimum Frequency: <input id="minimum-frequency" type="number" min="20" max="20000" value="900"><br>
Maximum Frequency: <input id="maximum-frequency" type="number" min="20" max="20000" value="1200"><br>
Last Segment Percent: <input id="last-bit-percent" type="number" min="0" max="100" value="90">%<br>
FFT Size: 2^<input id="fft-size-power-text" type="number" min="5" max="15" value="90"><br>
Frequency Resolution: <span id="frequency-resolution">N/A</span><br>
Frequency Count: <span id="frequency-count">N/A</span><br>
Frequency Resolution Multiplier: <input id="frequency-resolution-multiplier" type="number" min="1" max="10"
value="2"><br>
Smoothing Time Constant: <input id="smoothing-time-constant-text" type="number" min="0.00" max="1.00" step="0.01"
value="0.00"><br>
<label>
<input type="checkbox" id="error-correction-hamming" checked>Hamming Code Error Correction
</label><br>
<input type="text" id="text-to-send">
<button id="send-button">Send</button>
<h2>Sent</h2>
<textarea id="sent-data" rows="10" cols="40"></textarea><br />
</div>
<div>
<label>
<input type="checkbox" id="is-listening-checkbox">Listening
</label>
<h2>Received</h2>
<textarea id="received-data" rows="10" cols="40"></textarea><br />
Samples Per Bit: <span id="samples-per-bit">0</span><br>
Sample Rate: <span id="audio-context-sample-rate">N/A</span> per second.<br />
Segments per second: <span id="durations-per-second">N/A</span><br />
Bits per segment: <span id="bits-per-duration">N/A</span><br />
Speed: <span id="data-transfer-speed-bits-per-second">N/A</span> Baud<br>
<span id="data-transfer-speed-bytes-per-second">N/A</span> Bytes/second<br />
Effective Speed: <span id="effective-speed-bits-per-second">N/A</span> Baud<br>
<span id="effective-speed-bytes-per-second">N/A</span> Bytes/second<br />
</div>
<canvas id="received-graph" width="550" height="100"></canvas><br>
<div>
<div>
Segment Duration: <input id="bit-duration-text" type="number" min="0" max="1000" value="190">ms<br>
Amplitude Threshold: <input id="amplitude-threshold-text" type="number" min="0" max="100" value="75"><br>
Minimum Frequency: <input id="minimum-frequency" type="number" min="20" max="20000" value="900"><br>
Maximum Frequency: <input id="maximum-frequency" type="number" min="20" max="20000" value="1200"><br>
Last Segment Percent: <input id="last-bit-percent" type="number" min="0" max="100" value="90">%<br>
FFT Size: 2^<input id="fft-size-power-text" type="number" min="5" max="15" value="90"><br>
Frequency Resolution: <span id="frequency-resolution">N/A</span><br>
Frequency Count: <span id="frequency-count">N/A</span><br>
Frequency Resolution Multiplier: <input id="frequency-resolution-multiplier" type="number" min="1" max="10" value="2"><br >
Smoothing Time Constant: <input id="smoothing-time-constant-text" type="number" min="0.00" max="1.00" step="0.01" value="0.00"><br>
<input type="text" id="text-to-send">
<button id="send-button">Send</button>
<h2>Sent</h2>
<textarea id="sent-data" rows="10" cols="40"></textarea><br />
<h2>Decoded</h2>
<textarea id="decoded-data" rows="10" cols="40"></textarea><br />
Data Byte Count: <span id="decoded-byte-count">N/A</span><br />
</div>
<div>
<label>
<input type="checkbox" id="is-listening-checkbox">Listening
</label>
<h2>Received</h2>
<textarea id="received-data" rows="10" cols="40"></textarea><br />
Samples Per Bit: <span id="samples-per-bit">0</span><br>
Sample Rate: <span id="audio-context-sample-rate">N/A</span> per second.<br />
Segments per second: <span id="durations-per-second">N/A</span><br />
Bits per segment: <span id="bits-per-duration">N/A</span><br />
Speed: <span id="data-transfer-speed-bits-per-second">N/A</span> Baud<br>
<span id="data-transfer-speed-bytes-per-second">N/A</span> Bytes/second<br />
</div>
<div>
<canvas id="received-graph" width="550" height="100"></canvas>
</div>
</div>
<div>
<label>
<input type="checkbox" id="pause-after-end" checked>Pause after end
</label><br >
</label><br>
Max Bits Displayed: <input id="max-bits-displayed-on-graph" type="number" min="1" max="2000"><br>
</div>
</div>
</body>
</body>
</html>

View File

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