wip: big refactor

This commit is contained in:
Lewis Moten
2024-05-10 04:42:51 -04:00
parent 513aa67875
commit fdb6723e87
12 changed files with 1384 additions and 993 deletions

115
AudioSender.js Normal file
View File

@@ -0,0 +1,115 @@
let audioContext;
let CHANNELS = [];
let DESTINATION;
let ON_START;
let ON_STOP;
let ON_SEND;
let CHANNEL_OSCILLATORS = [];
let WAVE_FORM;
let stopOscillatorsTimeoutId;
export const changeConfiguration = ({
channels,
destination,
startCallback,
stopCallback,
sendCallback,
waveForm
}) => {
CHANNELS = channels;
DESTINATION = destination;
ON_START = startCallback;
ON_STOP = stopCallback;
ON_SEND = sendCallback;
WAVE_FORM = waveForm;
}
export const setAudioContext = ctx => audioContext = ctx;
function getAudioContext() {
if(!audioContext) {
throw 'Audio context not provided.';
}
if(audioContext.state === 'suspended') {
audioContext.resume();
}
return audioContext;
}
const getChannels = () => CHANNELS;
const getDestination = () => DESTINATION;
export const now = () => getAudioContext().currentTime;
export function beginAt(streamStartSeconds) {
stopTimeout();
const oscillators = getOscillators();
if(oscillators.length !== 0) stop();
const audioContext = getAudioContext();
const channels = getChannels();
const channelCount = channels.length;
const destination = getDestination();;
// create our oscillators
for(let i = 0; i < channelCount; i++) {
const oscillator = audioContext.createOscillator();
oscillator.connect(destination);
oscillator.type = WAVE_FORM;
oscillator.start(streamStartSeconds);
oscillators.push(oscillator);
}
callFn(ON_START);
return oscillators;
}
function getOscillators() {
return CHANNEL_OSCILLATORS;
}
export function send(bits, startSeconds) {
const oscillators = getOscillators();
getChannels().forEach((channel, i) => {
// send missing bits as zero
const isHigh = bits[i] ?? 0;
callFn(ON_SEND, isHigh);
const oscillator = oscillators[i];
// already at correct frequency
if(oscillator.on === isHigh) return;
oscillator.on = isHigh;
const hz = channel[isHigh ? 1 : 0];
oscillator.frequency.setValueAtTime(hz, startSeconds);
});
}
const stopTimeout = () => {
if(stopOscillatorsTimeoutId) {
window.setTimeout(stopOscillatorsTimeoutId);
stopOscillatorsTimeoutId = undefined;
}
}
export function stopAt(streamEndSeconds) {
const channels = getChannels();
const oscillators = getOscillators();
const channelCount = channels.length;
// silence oscillators when done
for(let channel = 0; channel < channelCount; channel++) {
const oscillator = oscillators[channel];
oscillator?.stop(streamEndSeconds);
}
stopTimeout();
stopOscillatorsTimeoutId = window.setTimeout(
stop,
(streamEndSeconds - now()) * 1000
);
}
export function stop() {
const time = now();
const oscillators = getOscillators();
oscillators.forEach(
oscillator => {
oscillator?.stop(time);
oscillator?.disconnect();
}
)
oscillators.length = 0;
callFn(ON_STOP);
stopTimeout();
}
const callFn = (fn, ...args) => typeof fn === 'function' ? fn(...args) : 0;

103
CRC.js Normal file
View File

@@ -0,0 +1,103 @@
function calcCrc(
bytes,
size,
polynomial,
{
initialization = 0,
reflectIn = false,
reflectOut = false,
xorOut = 0
} = {}
) {
if(bytes.length === 0) return 0;
const validBits = (1 << size) - 1;
const mostSignificantBit = 1 << size - 1;
const bitsBeforeLastByte = size - 8;
// setup our initial value
let crc = initialization;
function reverseBits(value, size) {
let reversed = 0;
for(let i = 0; i < size; i++) {
// if bit position is on
if(value & (1<<i)) {
// turn on bit in reverse order
reversed |= 1 << (size - 1 - i);
}
}
return reversed;
}
for(let byte of bytes) {
// reflect incoming bits?
if(reflectIn){
byte = reverseBits(byte, 8);
}
// xor current byte against first byte of crc
crc ^= byte << bitsBeforeLastByte;
// loop through the first 8 bits of the crc
for(let i = 0; i < 8; i++) {
// is first bit 1?
const isFlagged = crc & mostSignificantBit;
// if flagged, xor the first bit to prevent overflow
if(isFlagged) crc ^= mostSignificantBit;
// shift bits left
crc <<= 1;
// remove invalid bits
crc &= validBits;
// xor the polynomial
if(isFlagged) crc ^= polynomial;
}
}
// We only want the last [size] bits
crc &= validBits;
// reflect final bits?
if(reflectOut) crc = reverseBits(crc, size);
// xor the final value going out
crc ^= xorOut;
// remove sign
if(size >= 32 && crc & mostSignificantBit) {
crc >>>= 0;
}
return crc;
}
export function check(bytes, bitCount = 8) {
switch(bitCount) {
case 8: return crc8(bytes);
case 16: return crc16(bytes);
case 32: return crc32(bytes);
default: return 0;
}
}
function crc8(bytes) { return calcCrc(bytes, 8, 0x07); }
function crc16(bytes) {
return calcCrc(
bytes,
16,
0x8005,
{
initialization: 0,
reflectIn: true,
reflectOut: true,
xorOut: 0
}
);
}
function crc32(bytes) {
return calcCrc(
bytes,
32,
0x04C11DB7,
{
initialization: 0xFFFFFFFF,
reflectIn: true,
reflectOut: true,
xorOut: 0x0
}
);
}

53
HammingEncoding.js Normal file
View File

@@ -0,0 +1,53 @@
// Encoding to encode/decode data with Hamming Error Correction
export const DECODED_SIZE = 4;
export const ENCODED_SIZE = 7;
export const blockSize = () => ({
encoded: ENCODED_SIZE,
decoded: DECODED_SIZE
});
export const encode = (bits) => {
const encodedBits = [];
for(let i = 0; i < bits.length; i+= DECODED_SIZE) {
const block = bits.slice(i, i + DECODED_SIZE);
encodedBits.push(...encodeBlock(block));
}
return encodedBits;
}
export const decode = bits => {
const decodedBits = [];
for(let i = 0; i < bits.length; i += ENCODED_SIZE) {
const block = bits.slice(i, i + ENCODED_SIZE);
decodedBits.push(...decodeBlock(block));
}
return decodedBits;
}
const encodeBlock = bits => {
if(bits.length !== DECODED_SIZE) return [];
return [
bits[0] ^ bits[1] ^ bits[3],
bits[0] ^ bits[2] ^ bits[3],
bits[0],
bits[1] ^ bits[2] ^ bits[3],
bits[1],
bits[2],
bits[3]
]
}
const decodeBlock = bits => {
if(bits.length !== ENCODED_SIZE) return [];
const error_1 = bits[0] ^ bits[2] ^ bits[4] ^ bits[6];
const error_2 = bits[1] ^ bits[2] ^ bits[5] ^ bits[6];
const error_3 = bits[3] ^ bits[4] ^ bits[5] ^ bits[6];
let error = (error_3 << 2) | (error_2 << 1) | error_1;
if(error !== 0) bits[error - 1] ^= 1;
return [
bits[2],
bits[4],
bits[5],
bits[6]
];
}

58
Humanize.js Normal file
View File

@@ -0,0 +1,58 @@
export function byteSize(count) {
let unitIndex = 0;
const units = ['bytes', 'kb', 'mb', 'gb', 'tb', 'pb'];
while(count > 999) {
count /= 1024;
unitIndex++;
if(unitIndex === units.length - 1) break;
}
count = Math.floor(count * 10) * 0.1
return `${count.toLocaleString()} ${units[unitIndex]}`
}
export function bitsPerSecond(bps) {
let unitIndex = 0;
const units = ['baud', 'Kbps', 'Mbps', 'Gbps', 'Tbps', 'Pbps'];
while(bps > 999) {
bps /= 1024;
unitIndex++;
if(unitIndex === units.length - 1) break;
}
bps = Math.floor(bps * 10) * 0.1
return `${bps.toLocaleString()} ${units[unitIndex]}`
}
export function durationMilliseconds(milliseconds) {
const lookup = [
{mod: 1000, singular: 'ms', plural: 'ms'},
{mod: 60, singular: 's', plural: 's'},
{mod: 60, singular: 'm', plural: 'm'},
{mod: 24, singular: 'h', plural: 'h'},
{mod: 7, singular: 'd', plural: 'd'},
{mod: 53, singular: 'w', plural: 'w'},
{mod: 10, singular: 'y', plural: 'y'},
{mod: 10, singular: 'd', plural: 'd'},
{mod: 10, singular: 'c', plural: 'c'},
{mod: 10, singular: 'm', plural: 'm'},
]
const units = [];
let remaining = Math.floor(milliseconds);
for(let i = 0; i < lookup.length; i++) {
const {mod, singular, plural} = lookup[i];
const value = remaining % mod;
units.push({value, singular, plural});
remaining -= value;
if(remaining < mod) break;
remaining = remaining / mod;
}
return units
// largest units first
.reverse()
// top 2 largest values
.filter((_, i) => i < 2)
// drop values of zero
.filter(({value}) => value > 0)
// humanize unit
.map(({value, singular, plural}) => value + (value === 1 ? singular : plural))
// combine
.join(' ');
}

54
InterleaverEncoding.js Normal file
View File

@@ -0,0 +1,54 @@
// InterleaverEncoding
// Rolls / shifts elements of an array so that each
// block of N data elements contains the minimal
// amount of original data possible.
// Elements toward the end of an array are wrapped
// around to the beginning
// This is primarily used for data transfer where
// multiple channels representing various bits in
// the same block may be subsceptable to noise and
// provide the wrong value for one or more bits in
// a block
let BLOCK_SIZE = 1;
export const blockSize = () => ({
encoded: BLOCK_SIZE,
decoded: BLOCK_SIZE
});
export const changeConfiguration = ({
blockSize
}) => {
BLOCK_SIZE = blockSize ?? 1;
}
export const decode = bits => encode(bits, true);
export const encode = (bits, undo = false) => {
// Block sizes of 1 or less are unable to move bits
if(BLOCK_SIZE <= 1) return bits;
// We need at least 1 extra bit for one bit to escape the block
if(bits.length <= BLOCK_SIZE ) return bits;
// loop through indexes of a block
for(let blockMovement = 1; blockMovement < BLOCK_SIZE; blockMovement++) {
// Move every N bit N blocks over...
bits.filter((_, i) =>
// values to be moved to different blocks
i % BLOCK_SIZE === blockMovement
).map((_,i,a) => {
// values moved N blocks
if(undo) i -= blockMovement; else i += blockMovement;
i = ((i % a.length) + a.length) % a.length;
return a[i];
}).forEach((v, i) => {
// replace with new values
bits[blockMovement + (i * BLOCK_SIZE)] = v;
})
};
return bits;
}

147
PacketUtils.js Normal file
View File

@@ -0,0 +1,147 @@
let SEGMENT_DURATION = 30;
let PACKET_SIZE_BITS = 8;
let DATA_SIZE_BITS = 8;
let DATA_SIZE_CRC_BITS = 8;
let BITS_PER_SEGMENT = 1;
let PACKET_ENCODING = false;
let PACKET_ENCODING_SIZE = 7;
let PACKET_DECODING_SIZE = 4;
let ENCODING;
export const changeConfiguration = (config) => {
const {
segmentDurationMilliseconds,
packetSizeBitCount,
dataSizeBitCount,
dataSizeCrcBitCount,
bitsPerSegment,
packetEncoding,
packetEncodingBitCount,
packetDecodingBitCount
} = config;
SEGMENT_DURATION = segmentDurationMilliseconds;
PACKET_SIZE_BITS = packetSizeBitCount;
DATA_SIZE_BITS = dataSizeBitCount;
DATA_SIZE_CRC_BITS = dataSizeCrcBitCount;
BITS_PER_SEGMENT = bitsPerSegment;
PACKET_ENCODING = packetEncoding;
PACKET_ENCODING_SIZE = packetEncodingBitCount;
PACKET_DECODING_SIZE = packetDecodingBitCount;
}
export const setEncoding = (encoding) => {
ENCODING = encoding;
}
const encodePacket = (bits) => ENCODING.encode(bits);
export const getSegmentDurationMilliseconds = () => SEGMENT_DURATION;
export const getPacketMaxByteCount = () => 2 ** PACKET_SIZE_BITS;
export const getDataMaxByteCount = () => 2 ** DATA_SIZE_BITS;
export const getBitsPerSegment = () => BITS_PER_SEGMENT;
export const isPacketEncoded = () => PACKET_ENCODING;
export const packetEncodingBlockSize = () => isPacketEncoded() ? PACKET_ENCODING_SIZE : 0;
export const packetDecodingBlockSize = () => isPacketEncoded() ? PACKET_DECODING_SIZE : 0;
export const getPacketDataBitCount = () => {
if(isPacketEncoded()) return getPacketEncodingBlockCount() * PACKET_DECODING_SIZE;
return getPacketMaxBitCount();
}
export function fromByteCountGetPacketLastUnusedBitCount(byteCount) {
const bitCount = byteCount * 8;
const availableBits = getPacketMaxBitCount();
const dataBitsPerPacket = getPacketDataBitCount();
let bitsInLastPacket = bitCount % dataBitsPerPacket;
let usedBits = bitsInLastPacket;
if(isPacketEncoded()) {
const blocks = Math.ceil(usedBits / packetDecodingBlockSize())
usedBits = blocks * packetEncodingBlockSize();
}
return availableBits - usedBits;
}
export function getPacketLastSegmentUnusedBitCount() {
return (BITS_PER_SEGMENT - (getPacketMaxBitCount() % BITS_PER_SEGMENT));
}
export const getBaud = () => {
return Math.floor(getBitsPerSegment() / getSegmentDurationSeconds());
}
export const getEffectiveBaud = () => {
return Math.floor(getPacketDataBitCount() / getPacketDurationSeconds());
}
export const getEncodedPacketDataBitCount = () => {
return isPacketEncoded() ? getPacketEncodingBitCount() : 0;
}
export const getPacketUsedBitCount = () =>
isPacketEncoded() ? getPacketEncodingBitCount() : getPacketMaxBitCount();
export const getPacketUnusedBitCount = () => getPacketMaxBitCount() - getPacketUsedBitCount();
export const getMaxPackets = () =>
Math.ceil((getDataMaxByteCount() * 8) / getPacketUsedBitCount());
export const getMaxDurationMilliseconds = () => getMaxPackets() * getPacketDurationMilliseconds();
export const getPacketEncodingBitCount = () => getPacketEncodingBlockCount() * packetEncodingBlockSize();
export const canSendPacket = () => {
const maxBits = getPacketMaxBitCount();
if(maxBits < 1) return false;
return isPacketEncoded() ? maxBits >= packetEncodingBlockSize() : true;
}
export const getPacketEncodingBlockCount = () =>
isPacketEncoded() ? Math.floor(getPacketMaxBitCount() / packetEncodingBlockSize()) : 0;
export const getPacketizationHeaderBitCount = () => DATA_SIZE_BITS + DATA_SIZE_CRC_BITS;
export const getPacketizationBitCountFromBitCount = (bitCount) => bitCount + getPacketizationHeaderBitCount();
export const getPacketizationBitCountFromByteCount = (byteCount) =>
getPacketizationBitCountFromBitCount(byteCount * 8);
export const getPacketizationByteCountFromByteCount = (byteCount) =>
Math.ceil(getPacketizationBitCountFromByteCount(byteCount) / 8);
export const getPacketizationByteCountFromBitCount = bitCount =>
Math.ceil(getPacketizationBitCountFromBitCount(bitCount) / 8);
export const getDataTransferDurationMillisecondsFromByteCount = (byteCount) =>
getDataTransferDurationMilliseconds(getPacketizationBitCountFromByteCount(byteCount));
export const getDataTransferDurationSeconds = (bitCount) =>
getDataTransferDurationMilliseconds(bitCount) / 1000;
export const getPacketCount = (bitCount) =>
canSendPacket() ? Math.ceil(bitCount / getPacketDataBitCount()) : 0;
export const getDataTransferDurationMilliseconds = (bitCount) =>
getPacketCount(bitCount) * getPacketDurationMilliseconds();
export const getPacketDurationSeconds = () => getPacketDurationMilliseconds() / 1000;
export const getSegmentDurationSeconds = () => getSegmentDurationMilliseconds() / 1000;
export const getPacketMaxBitCount = () => getPacketMaxByteCount() * 8;
export const getPacketSegmentCount = () => Math.ceil(getPacketMaxBitCount() / getBitsPerSegment());
export const getPacketDurationMilliseconds = () =>
getPacketSegmentCount() * getSegmentDurationMilliseconds();
export const getPacketIndex = (transferStartedMilliseconds, time) =>
Math.floor((time - transferStartedMilliseconds) / getPacketDurationMilliseconds());
export function getPacketUsedBits(bits, packetIndex) {
if(!canSendPacket()) return [];
// How many data bits will be in our packet?
const dataBitCount = getPacketDataBitCount();
// grab our data
const startIndex = packetIndex * dataBitCount;
const endIndex = startIndex + dataBitCount;
let packetBits = bits.slice(startIndex, endIndex);
return isPacketEncoded() ? encodePacket(packetBits) : packetBits;
}
export const getPacketBits = (bits, packetIndex) =>
canSendPacket() ? getPacketUsedBits(bits, packetIndex) : [];
export function getPacketSegmentIndex(transferStartedMilliseconds, time) {
return getTranserSegmentIndex(transferStartedMilliseconds, time) % getPacketSegmentCount();
}
export function getTranserSegmentIndex(transferStartedMilliseconds, time) {
const transferMs = time - transferStartedMilliseconds;
const segmentMs = getSegmentDurationMilliseconds();
return Math.floor(transferMs / segmentMs);
}
export function getPacketSegmentStartMilliseconds(transferStartedMilliseconds, packetIndex, segmentIndex) {
const packetStart = getPacketStartMilliseconds(transferStartedMilliseconds, packetIndex);
const segmentOffset = segmentIndex * getSegmentDurationMilliseconds();
return packetStart + segmentOffset;
}
export function getPacketStartMilliseconds(transferStartedMilliseconds, packetIndex) {
if(packetIndex < 0) return 0;
if(packetIndex === 0) return transferStartedMilliseconds;
return transferStartedMilliseconds + (packetIndex * getPacketDurationMilliseconds());
}
export function getPacketSegmentEndMilliseconds(transferStartedMilliseconds, packetIndex, segmentIndex) {
return getPacketSegmentStartMilliseconds(transferStartedMilliseconds, packetIndex, segmentIndex + 1) - 0.1;
}

72
Randomizer.js Normal file
View File

@@ -0,0 +1,72 @@
const UNICODE_TEXT = [
'\u0041', // Latin
'\u0410', // Cyrillic
'\u0391', // Greek
'\u05D0', // Hebrew
'\u0627', // Arabic
'\u0905', // Devanagari
'\u0E01', // Thai
'\u3042', // Japanese Hiragana
'\u30A2', // Japanese Katakana
'\uAC00', // Korean Hangul
'\u10D0', // Georgian
'\u0531', // Armenian
'\u4E00', // Chinese
'\u0F40', // Tibetan
'\u0985', // Bengali
'\u0A85', // Gujarati
'\u0A05', // Gurmukhi
'\u0C85', // Kannada
'\u1780', // Khmer
'\u0E81', // Lao
'\u0D05', // Malayalam
'\u1000', // Myanmar
'\u0D85', // Sinhala
'\u0B85', // Tamil
'\u0C05', // Telugu
'\u1200', // Amharic
'\u1000', // Burmese
'\u0C85', // Kannada
'\u0B05', // Oriya
'\u0D85' // Sinhala
];
const UNICODE_EMOJI = [
"\u{1F600}", "\u{1F601}", "\u{1F602}", "\u{1F923}", "\u{1F603}", "\u{1F604}", "\u{1F605}", "\u{1F606}",
"\u{1F609}", "\u{1F60A}", "\u{1F60B}", "\u{1F60E}", "\u{1F60D}", "\u{1F618}", "\u{1F617}", "\u{1F619}",
"\u{1F61A}", "\u{1F61B}", "\u{263A}", "\u{1F642}", "\u{1F60F}", "\u{1F60C}", "\u{1F61C}", "\u{1F61D}",
"\u{1F61E}", "\u{1F61F}", "\u{1F612}", "\u{1F613}", "\u{1F614}", "\u{1F615}", "\u{1F643}", "\u{1F610}",
"\u{1F611}", "\u{1F636}", "\u{1F607}", "\u{1F60F}", "\u{1F623}", "\u{1F625}", "\u{1F62E}", "\u{1F62F}",
"\u{1F62A}", "\u{1F62B}", "\u{1F634}", "\u{1F60D}", "\u{1F615}", "\u{1F625}", "\u{1F622}", "\u{1F62D}",
"\u{1F631}", "\u{1F616}", "\u{1F623}", "\u{1F624}", "\u{1F630}", "\u{1F621}", "\u{1F620}", "\u{1F637}",
"\u{1F912}", "\u{1F915}", "\u{1F922}", "\u{1F92A}", "\u{1F605}", "\u{1F624}", "\u{1F62C}", "\u{1F687}",
"\u{1F636}", "\u{1F610}", "\u{1F611}", "\u{1F974}", "\u{1F612}", "\u{1F644}", "\u{1F913}", "\u{1F615}",
"\u{1F62C}", "\u{1F636}", "\u{1F922}", "\u{1F927}", "\u{1F974}", "\u{1F975}", "\u{1F976}", "\u{1F92E}",
"\u{1F927}", "\u{1F976}", "\u{1F925}", "\u{1F92F}", "\u{1F975}", "\u{1F976}", "\u{1F92E}", "\u{1F925}",
"\u{1F924}", "\u{1F631}", "\u{1F634}", "\u{1F62C}", "\u{1F91E}", "\u{1F621}", "\u{1F608}", "\u{1F47F}",
"\u{1F480}", "\u{1F47B}", "\u{1F47D}", "\u{1F916}", "\u{1F608}", "\u{1F47A}", "\u{1F479}", "\u{1F47C}",
"\u{1F47E}", "\u{1F916}", "\u{1F4A9}", "\u{1F608}", "\u{1F4A4}", "\u{1F525}", "\u{1F4A3}", "\u{1F52E}",
"\u{1F4A2}", "\u{1F4A1}", "\u{1F6A8}", "\u{1F3B6}", "\u{1F519}", "\u{1F5E8}", "\u{1F4F3}", "\u{1F4F1}",
"\u{1F4F2}", "\u{1F514}", "\u{1F3A4}", "\u{1F4F9}", "\u{1F4F7}", "\u{1F4F8}", "\u{1F4F4}", "\u{1F4F6}",
"\u{1F3AF}", "\u{1F4FD}", "\u{1F4FC}", "\u{1F4E5}"
];
const ASCII_CHARS = new Array(255)
.fill(0)
.map((_, code) => String.fromCharCode(code))
.filter((_, code) =>
// printable ascii
(code >= 32 && code <= 126) ||
// extended ascii (accented letters, currency, etc.)
(code >= 160 && code <= 255)
);
const PRINTABLE_CHARS = [
...UNICODE_EMOJI,
...ASCII_CHARS,
...UNICODE_TEXT
];
function randomCharacter() {
const index = Math.floor(Math.random() * PRINTABLE_CHARS.length);
return PRINTABLE_CHARS[index];
}
export const text = (length) => new Array(length).fill(0).map(randomCharacter).join('');

111
StreamManager.js Normal file
View File

@@ -0,0 +1,111 @@
import { bitsToInt } from "./converters";
const BITS = [];
let BITS_PER_PACKET = 0;
let SEGMENTS_PER_PACKET = 0;
let BITS_PER_SEGMENT = 0;
let STREAM_HEADERS = [];
let SEGMENT_ENCODING = {
encode: bits => bits,
decode: bits => bits
};
let PACKET_ENCODING = {
encode: bits => bits,
decode: bits => bits
}
export const changeConfiguration = ({
segmentsPerPacket,
bitsPerPacket,
bitsPerSegment,
streamHeaders
}) => {
BITS_PER_PACKET = bitsPerPacket;
SEGMENTS_PER_PACKET = segmentsPerPacket;
BITS_PER_SEGMENT = bitsPerSegment;
STREAM_HEADERS = streamHeaders;
}
const noEncoding = bits => bits;
export const setSegmentEncoding = ({ encode, decode } = {}) => {
SEGMENT_ENCODING.encode = encode ?? noEncoding;
SEGMENT_ENCODING.decode = decode ?? noEncoding;
}
export const setPacketEncoding = ({ encode, decode } = {}) => {
PACKET_ENCODING.encode = encode ?? noEncoding;
PACKET_ENCODING.decode = decode ?? noEncoding;
}
export const addBits = (
packetIndex,
segmentIndex,
bits
) => {
if(BITS[packetIndex] === undefined) {
BITS[packetIndex] = [];
}
BITS[packetIndex][segmentIndex] = bits;
}
export const getPacketReceivedCount = () => {
if(BITS.length === 0) return 1;
return BITS.length;
}
export const getStreamBits = () => {
const bits = [];
const packetCount = getPacketReceivedCount();
for(let packetIndex = 0; packetIndex < packetCount; packetIndex++) {
const packet = BITS[packetIndex] ?? [];
for(let segmentIndex = 0; segmentIndex < SEGMENTS_PER_PACKET; segmentIndex++) {
let segment = packet[segmentIndex] ?? [];
for(let bitIndex = 0; bitIndex < BITS_PER_SEGMENT; bitIndex++) {
const bit = segment[bitIndex];
bits.push(bit ?? 0);
}
}
}
return bits;
}
export const getPacketSegmentBits = (packetIndex, segmentIndex) => BITS[packetIndex]?.[segmentIndex];
export const getAllPacketBits = () => {
const packetCount = getPacketReceivedCount();
const bits = [];
for(let packetIndex = 0; packetIndex < packetCount; packetIndex++) {
bits.push(...getPacketBits(packetIndex));
}
return bits;
}
export const getPacketBits = (packetIndex, defaultBit = 0) => {
const bits = [];
const packet = BITS[packetIndex] ?? [];
for(let segmentIndex = 0; segmentIndex < SEGMENTS_PER_PACKET; segmentIndex++) {
let segment = packet[segmentIndex] ?? [];
segment = SEGMENT_ENCODING.decode(segment);
for(let bitIndex = 0; bitIndex < BITS_PER_SEGMENT; bitIndex++) {
const bit = segment[bitIndex];
bits.push(bit ?? defaultBit);
if(bits.length === BITS_PER_PACKET) return bits;
}
}
while(bits.length < BITS_PER_PACKET) bits.push(defaultBit);
return bits;
}
export const getPacketBitsDecoded = (packetIndex, defaultBit = 0) => {
const bits = getPacketBits(packetIndex, defaultBit);
return PACKET_ENCODING.decode(bits);
}
const getStreamHeaderBits = name => {
const header = STREAM_HEADERS[name];
if(!header) return [];
const { index, length } = header;
const packetCount = getPacketReceivedCount();
const bits = [];
for(let packetIndex = 0; packetIndex < packetCount; packetIndex++) {
bits.push(...getPacketBitsDecoded(packetIndex, 1));
if(bits.length >= index + length) break;
}
return bits.slice(index, index + length);
}
export const getTransferByteCount = () => {
const name = 'transfer byte count';
const length = STREAM_HEADERS[name].length;
const bits = getStreamHeaderBits(name);
return bitsToInt(bits, length);
}

10
converters.js Normal file
View File

@@ -0,0 +1,10 @@
export const bitsToInt = (bits, bitLength) => {
parseInt(bits
// only grab the bits we need
.slice(0, bitLength)
// combine into string
.join('')
// Assume missing bits were zeros
.padEnd(bitLength, '0')
);
}

View File

@@ -4,80 +4,89 @@
<head>
<title>Data Over Audio</title>
<link rel="stylesheet" type="text/css" href="style.css" />
<script src="index.js" type="text/javascript"></script>
<script src="index.js" type="module" type="text/javascript"></script>
</head>
<body>
<h1>Data Over Audio</h1>
<div class="panels">
<div>
<h2>Message</h2>
<h2>Communications</h2>
<div>
<h4>Send</h4>
<label>
<input type="checkbox" id="send-via-speaker">Speakers
<input type="radio" name="send-via" id="send-via-analyzer" checked>Analyzer
</label><br />
<input type="text" id="text-to-send">
<button id="send-button">Send</button><br />
<div class="raw-data" id="sent-data"></div><br />
</div>
<div>
<h2>Data</h2>
Original Bytes: <span id="original-byte-count"></span><br>
Packetization Bytes: <span id="packetization-byte-count"></span><br>
Packetization Bits: <span id="packetization-bit-count"></span><br>
Bits per Packet: <span id="packet-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 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 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>
Packet Count: <span id="packet-count"></span><br>
Segments Per Packet: <span id="segments-per-packet"></span><br>
Total Segments: <span id="total-segments"></span><br>
Segment Duration: <span id="segment-transfer-duration"></span><br>
Packet Duration: <span id="packet-transfer-duration"></span><br>
Total Duration: <span id="data-transfer-duration"></span><br>
</div>
<div>
<h2>Error Correcting</h2>
<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>
<label>
<input type="radio" name="send-via" id="send-via-speaker">Speakers
</label><br />
<h4>Receive</h4>
<label>
<input type="checkbox" id="is-listening-checkbox">Listening
</label>
<h2>Bits Received</h2>
<div class="raw-data" id="received-raw-bits"></div><br />
Error Percent: <span id="received-raw-bits-error-percent">N/A</span>%
</div>
</div>
<div>
<h2>Encoded Bits Received</h2>
<div class="raw-data" id="received-encoded-bits"></div><br />
Error Percent: <span id="received-encoded-bits-error-percent">N/A</span>%
</div>
<h2>Message</h2>
<div>
<h2>Decoded Bits Received</h2>
<div class="raw-data" id="received-decoded-bits"></div><br />
Error Percent: <span id="received-decoded-bits-error-percent">N/A</span>%<br />
</div>
<div>
<h2>Decoded</h2>
Bytes: <span id="received-packet-original-bytes">N/A</span><br>
Length CRC-8: <span id="received-packet-original-bytes-crc">N/A</span><br>
<input type="text" id="text-to-send">
<button id="send-button">Send</button><br />
<h4>Received</h4>
<div class="progress-container">
<div id="received-progress" class="progress-bar"></div>
</div>
Text:
<div class="raw-data" id="decoded-text"></div><br />
<div class="raw-data-small" id="decoded-text"></div><br />
</div>
</div>
<div>
<h2>Configuration</h2>
<div>
<h4>Audio</h4>
Wave Form: <select id="wave-form" value="sine">
<option value="sine">Sine Wave</option>
<option value="square">Square Wave</option>
<option value="sawtooth">Sawtooth Wave</option>
<option value="triangle">Triangle Wave</option>
</select><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>
Segment Duration: <input id="bit-duration-text" type="number" min="0" max="1000" value="190">ms<br>
FFT Size: 2^<input id="fft-size-power-text" type="number" min="5" max="15" value="90"><br>
Frequency Resolution Multiplier: <input id="frequency-resolution-multiplier" type="number" min="1" max="20"
value="2"><br>
Channel Frequency Resolution Padding: <input id="channel-frequency-resolution-padding" type="number" min="0"
max="20"><br>
<h4>Packetization</h4>
Packet Size:
2^<input id="packet-size-power" type="number" min="0" max="16">
<span id="packet-size"></span>
<br>
<h4>Receiving</h4>
Amplitude Threshold: <input id="amplitude-threshold-text" type="number" min="0" max="100" value="75"><br>
Last Segment Percent: <input id="last-bit-percent" type="number" min="0" max="100" value="90">%<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>
<h4>Encoding</h4>
<label>
<input type="checkbox" id="periodic-interleaving" checked>Interleaving
</label><br />
<label>
<input type="checkbox" id="error-correction-hamming" checked>Error Correction
</label><br>
</div>
</div>
<div>
<h2>Channels</h2>
<div>
<ol id="channel-list" start="0"></ol>
</div>
</div>
<div class="chart">
<h2>Frequency Graph</h2>
<div>
<canvas id="received-graph" width="800" height="150"></canvas><br>
<canvas id="received-channel-graph" width="800" height="300"></canvas><br>
<label>
@@ -85,80 +94,131 @@
</label><br>
Max Segments Displayed: <input id="max-bits-displayed-on-graph" type="number" min="1" max="2000"><br>
</div>
</div>
<div>
<h2>Packetization Configuration</h2>
<div>
Maximum Data: <span id="packetization-max-bytes"></span><br>
Maximum Packets: <span id="packetization-max-packets"></span><br>
Maximum Duration: <span id="packetization-max-duration"></span><br>
<h4>Speed</h4>
Packetization: <span id="packetization-speed-bits-per-second"></span><br>
Data Only: <span id="packetization-speed-effective-bits-per-second"></span><br>
</div>
</div>
<div>
<h2>Packet Configuration</h2>
<div>
Bytes per packet: <span id="bytes-per-packet"></span><br>
Bits per Packet: <span id="bits-per-packet"></span><br>
<h4>Encoding</h4>
Packet Encoding: <span id="packet-encoding"></span><br>
Bits per block: <span id="packet-encoding-bits-per-block"></span><br>
Blocks per packet: <span id="packet-encoding-block-count"></span><br>
Encoding bits per Packet: <span id="packet-encoding-bit-count"></span><br>
<h4>Utilization</h4>
Data Bits per Packet: <span id="packet-data-bit-count"></span><br>
Unused bits per packet: <span id="packet-unused-bit-count"></span><br>
<h4>Segments</h4>
Segments Per Packet: <span id="segments-per-packet"></span><br>
Bits per segment: <span id="bits-per-segment">N/A</span><br />
Last segment unused bits: <span id="last-segment-unused-bit-count"></span><br>
</div>
</div>
<div>
<h2>Data</h2>
<div>
Original Bytes: <span id="original-byte-count"></span><br>
Packetization Bytes: <span id="packetization-byte-count"></span><br>
Packetization Bits: <span id="packetization-bit-count"></span><br>
Packet Count: <span id="packet-count"></span><br>
Total Segments: <span id="total-segments"></span><br>
<h3>Transfer Time</h3>
Per Segment: <span id="segment-transfer-duration"></span><br>
Per Packet: <span id="packet-transfer-duration"></span><br>
Total: <span id="data-transfer-duration"></span><br>
<h3>Utilization</h3>
Unused bits in last packet: <span id="last-packet-unused-bit-count"></span><br>
</div>
</div>
<div>
<h2>Original Data to send</h2>
<div>
<div class="raw-data" id="sent-data"></div><br />
</div>
</div>
<div>
<h2>Encoded packets to send</h2>
<div>
<div class="raw-data" id="error-correcting-data"></div>
</div>
</div>
<div>
<h2>Encoded segments to send</h2>
<div>
<div class="raw-data" id="actual-bits-to-send"></div><br />
</div>
</div>
<div>
<h2>Decoded</h2>
<div>
Bytes: <span id="received-packet-original-bytes">N/A</span><br>
Length CRC-8: <span id="received-packet-original-bytes-crc">N/A</span><br>
<h4>Errors</h4>
Encoded Segments: <span id="received-raw-bits-error-percent">N/A</span>%<br>
Encoded Packets: <span id="received-encoded-bits-error-percent">N/A</span>%<br>
Decoded Packets: <span id="received-decoded-bits-error-percent">N/A</span>%<br />
</div>
</div>
<div>
<h2>Encoded Segments Received</h2>
<div>
<div class="raw-data" id="received-encoded-segment-bits"></div><br />
</div>
</div>
<div>
<h2>Encoded Packets Received</h2>
<div>
<div class="raw-data" id="received-encoded-bits"></div><br />
</div>
</div>
<div>
<h2>Decoded Packets Received</h2>
<div>
<div class="raw-data" id="received-decoded-bits"></div><br />
</div>
</div>
<div>
<h2>Selected</h2>
<div>
Channel: <span id="selected-channel">N/A</span><br>
Segment: <span id="selected-segment">N/A</span><br>
Bit: <span id="selected-bit">N/A</span><br>
<div id="selected-samples"></div>
</div>
<div>
<h2>Configuration</h2>
Wave Form: <select id="wave-form" value="sine">
<option value="sine">Sine Wave</option>
<option value="square">Square Wave</option>
<option value="sawtooth">Sawtooth Wave</option>
<option value="triangle">Triangle Wave</option>
</select><br>
Packet Size:
2^<input id="packet-size-power" type="number" min="0" max="16">
<span id="packet-size"></span>
<br>
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 Multiplier: <input id="frequency-resolution-multiplier" type="number" min="1" max="20"
value="2"><br>
Channel Frequency Resolution Padding: <input id="channel-frequency-resolution-padding" type="number" min="0" max="20"><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>
</div>
<div>
<h2>Data Integrity</h2>
<label>
<input type="checkbox" id="periodic-interleaving" checked>Periodic Interleaving
</label><br />
<label>
<input type="checkbox" id="error-correction-hamming" checked>Hamming Code Error Correction
</label><br>
</div>
<h2>Audio Spectrum</h2>
<div>
<h2>Packet</h2>
Data Bits: <span id="data-bits-to-send">N/A</span> Bytes: <span id="data-bytes-to-send">N/A</span><br />
Data Size Header Bits: <span id="data-size-header-bits">N/A</span><br />
Error Correction Bits: <span id="error-correction-bits">N/A</span><br />
Total Bits: <span id="total-bits-to-send">N/A</span> Bytes: <span id="total-bytes-to-send">N/A</span><br />
Channels: <span id="packet-send-channel-count">N/A</span><br />
Segment Count: <span id="packet-send-segment-count">N/A</span><br />
Segment Duration: <span id="packet-send-segment-duration">N/A</span>s<br />
Total Duration: <span id="duration-to-send">N/A</span>s<br />
</div>
<div>
<h2>Information</h2>
Frequency Resolution: <span id="frequency-resolution">N/A</span><br>
Frequency Count: <span id="frequency-count">N/A</span><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 />
</div>
</div>
<div>
<h2>Speed</h2>
Baud: <span id="data-transfer-speed-bits-per-second">N/A</span><br>
Bytes/s: <span id="data-transfer-speed-bytes-per-second">N/A</span><br />
Effective Baud: <span id="effective-speed-bits-per-second">N/A</span><br>
Effective Bytes/s: <span id="effective-speed-bytes-per-second">N/A</span><br />
<h2>Receiving</h2>
<div>
Samples Per Bit: <span id="samples-per-bit">0</span><br>
</div>
</div>
<div>
<h2>Channels</h2>
<div class="panels">
<ol id="channel-list" start="0"></ol>
<canvas id="channel-frequency-graph" width="200" height="200"></canvas>
<h2>Channel Tuning</h2>
<div>
<canvas id="channel-frequency-graph" width="160" height="160"></canvas>
</div>
</div>
</div>
</body>

1281
index.js

File diff suppressed because it is too large Load Diff

View File

@@ -4,19 +4,53 @@ body {
.panels {
display: flex;
flex-wrap: wrap;
font-family: sans-serif;
}
.panels > div {
border: 1px solid black;
background-color: darkslategray;
margin: 10px;
padding: 10px;
padding-top: 0;
padding-left: 0;
padding-right: 0;
border-radius: 10px;
color: rgb(75, 185, 75);
color: rgb(163, 197, 163);
max-height: 200px;
overflow: hidden;
}
.panels > div h2 {
.panels > div.chart {
max-height: unset;
overflow: unset;
}
.panels > div.chart > div {
overflow: unset;
height: unset;
}
.panels > div > h2 {
margin-top: 0;
padding-left: 5px;
padding-right: 5px;
background-color: gray;
border-bottom: 4px solid darkolivegreen;
color: black;
font-size: small;
}
.panels > div > div {
margin-left: 10px;
margin-right: 10px;
font-size: small;
height: 175px;
overflow: auto;
}
.panels > div > div h4 {
font-size: small;
background-color: gray;
border-bottom: 1px solid rgb(58, 58, 58);
color: rgb(58, 58, 58);
margin-top: 3px;
margin-bottom: 3px;
}
canvas {
background-color: black;
@@ -34,6 +68,7 @@ canvas {
position: relative;
z-index: 2;
left: 0;
width: 0;
/* transition: width 0.3s ease; */
}
.xprogress-container::after {
@@ -48,7 +83,17 @@ canvas {
background-color: rgb(41, 59, 10);
color: rgb(75, 185, 75);
width: 250px;
height: 300px;
min-height: 150px;
font-size: x-small;
border: 1px solid grey;
overflow: auto;
font-family: monospace;
}
.raw-data-small {
background-color: rgb(41, 59, 10);
color: rgb(75, 185, 75);
width: 250px;
min-height: 100px;
font-size: x-small;
border: 1px solid grey;
overflow: auto;
@@ -68,8 +113,8 @@ canvas {
color: green;
}
ol {
overflow: auto;
height: 75px;
/* overflow: auto; */
/* height: 75px; */
background-color: rgb(41, 59, 10);
color: rgb(75, 185, 75);
border: 1px solid grey;