mirror of
https://github.com/lewismoten/data-over-audio.git
synced 2026-02-01 15:55:50 +08:00
add hamming error correction
This commit is contained in:
95
index.html
95
index.html
@@ -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>
|
||||
95
index.js
95
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;
|
||||
|
||||
Reference in New Issue
Block a user