wip: big refactor
This commit is contained in:
115
AudioSender.js
Normal file
115
AudioSender.js
Normal 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
103
CRC.js
Normal 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
53
HammingEncoding.js
Normal 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
58
Humanize.js
Normal 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
54
InterleaverEncoding.js
Normal 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
147
PacketUtils.js
Normal 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
72
Randomizer.js
Normal 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
111
StreamManager.js
Normal 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
10
converters.js
Normal 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')
|
||||||
|
);
|
||||||
|
}
|
||||||
304
index.html
304
index.html
@@ -4,80 +4,89 @@
|
|||||||
<head>
|
<head>
|
||||||
<title>Data Over Audio</title>
|
<title>Data Over Audio</title>
|
||||||
<link rel="stylesheet" type="text/css" href="style.css" />
|
<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>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<h1>Data Over Audio</h1>
|
<h1>Data Over Audio</h1>
|
||||||
<div class="panels">
|
<div class="panels">
|
||||||
<div>
|
<div>
|
||||||
<h2>Message</h2>
|
<h2>Communications</h2>
|
||||||
<label>
|
<div>
|
||||||
<input type="checkbox" id="send-via-speaker">Speakers
|
<h4>Send</h4>
|
||||||
</label><br />
|
<label>
|
||||||
<input type="text" id="text-to-send">
|
<input type="radio" name="send-via" id="send-via-analyzer" checked>Analyzer
|
||||||
<button id="send-button">Send</button><br />
|
</label><br />
|
||||||
<div class="raw-data" id="sent-data"></div><br />
|
<label>
|
||||||
</div>
|
<input type="radio" name="send-via" id="send-via-speaker">Speakers
|
||||||
<div>
|
</label><br />
|
||||||
<h2>Data</h2>
|
<h4>Receive</h4>
|
||||||
Original Bytes: <span id="original-byte-count"></span><br>
|
<label>
|
||||||
Packetization Bytes: <span id="packetization-byte-count"></span><br>
|
<input type="checkbox" id="is-listening-checkbox">Listening
|
||||||
Packetization Bits: <span id="packetization-bit-count"></span><br>
|
</label>
|
||||||
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="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>
|
|
||||||
<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>
|
|
||||||
<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>
|
|
||||||
<div class="progress-container">
|
|
||||||
<div id="received-progress" class="progress-bar"></div>
|
|
||||||
</div>
|
</div>
|
||||||
Text:
|
</div>
|
||||||
<div class="raw-data" id="decoded-text"></div><br />
|
<div>
|
||||||
</div>
|
<h2>Message</h2>
|
||||||
<div>
|
<div>
|
||||||
|
<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>
|
||||||
|
<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>
|
<h2>Frequency Graph</h2>
|
||||||
|
<div>
|
||||||
<canvas id="received-graph" width="800" height="150"></canvas><br>
|
<canvas id="received-graph" width="800" height="150"></canvas><br>
|
||||||
<canvas id="received-channel-graph" width="800" height="300"></canvas><br>
|
<canvas id="received-channel-graph" width="800" height="300"></canvas><br>
|
||||||
<label>
|
<label>
|
||||||
@@ -85,80 +94,131 @@
|
|||||||
</label><br>
|
</label><br>
|
||||||
Max Segments Displayed: <input id="max-bits-displayed-on-graph" type="number" min="1" max="2000"><br>
|
Max Segments Displayed: <input id="max-bits-displayed-on-graph" type="number" min="1" max="2000"><br>
|
||||||
</div>
|
</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>
|
<div>
|
||||||
<h2>Selected</h2>
|
<h2>Selected</h2>
|
||||||
|
<div>
|
||||||
Channel: <span id="selected-channel">N/A</span><br>
|
Channel: <span id="selected-channel">N/A</span><br>
|
||||||
Segment: <span id="selected-segment">N/A</span><br>
|
Segment: <span id="selected-segment">N/A</span><br>
|
||||||
Bit: <span id="selected-bit">N/A</span><br>
|
Bit: <span id="selected-bit">N/A</span><br>
|
||||||
<div id="selected-samples"></div>
|
<div id="selected-samples"></div>
|
||||||
</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>
|
||||||
<div>
|
<div>
|
||||||
<h2>Data Integrity</h2>
|
<h2>Audio Spectrum</h2>
|
||||||
<label>
|
<div>
|
||||||
<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>
|
|
||||||
<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 Resolution: <span id="frequency-resolution">N/A</span><br>
|
||||||
Frequency Count: <span id="frequency-count">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 />
|
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 />
|
</div>
|
||||||
Bits per segment: <span id="bits-per-duration">N/A</span><br />
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h2>Speed</h2>
|
<h2>Receiving</h2>
|
||||||
Baud: <span id="data-transfer-speed-bits-per-second">N/A</span><br>
|
<div>
|
||||||
Bytes/s: <span id="data-transfer-speed-bytes-per-second">N/A</span><br />
|
Samples Per Bit: <span id="samples-per-bit">0</span><br>
|
||||||
Effective Baud: <span id="effective-speed-bits-per-second">N/A</span><br>
|
</div>
|
||||||
Effective Bytes/s: <span id="effective-speed-bytes-per-second">N/A</span><br />
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h2>Channels</h2>
|
<h2>Channel Tuning</h2>
|
||||||
<div class="panels">
|
<div>
|
||||||
<ol id="channel-list" start="0"></ol>
|
<canvas id="channel-frequency-graph" width="160" height="160"></canvas>
|
||||||
<canvas id="channel-frequency-graph" width="200" height="200"></canvas>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
|
|||||||
55
style.css
55
style.css
@@ -4,19 +4,53 @@ body {
|
|||||||
.panels {
|
.panels {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
font-family: sans-serif;
|
||||||
}
|
}
|
||||||
.panels > div {
|
.panels > div {
|
||||||
border: 1px solid black;
|
border: 1px solid black;
|
||||||
background-color: darkslategray;
|
background-color: darkslategray;
|
||||||
margin: 10px;
|
margin: 10px;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
|
padding-top: 0;
|
||||||
|
padding-left: 0;
|
||||||
|
padding-right: 0;
|
||||||
border-radius: 10px;
|
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;
|
background-color: gray;
|
||||||
border-bottom: 4px solid darkolivegreen;
|
border-bottom: 4px solid darkolivegreen;
|
||||||
color: black;
|
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 {
|
canvas {
|
||||||
background-color: black;
|
background-color: black;
|
||||||
@@ -34,6 +68,7 @@ canvas {
|
|||||||
position: relative;
|
position: relative;
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
left: 0;
|
left: 0;
|
||||||
|
width: 0;
|
||||||
/* transition: width 0.3s ease; */
|
/* transition: width 0.3s ease; */
|
||||||
}
|
}
|
||||||
.xprogress-container::after {
|
.xprogress-container::after {
|
||||||
@@ -48,7 +83,17 @@ canvas {
|
|||||||
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: 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;
|
font-size: x-small;
|
||||||
border: 1px solid grey;
|
border: 1px solid grey;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
@@ -68,8 +113,8 @@ canvas {
|
|||||||
color: green;
|
color: green;
|
||||||
}
|
}
|
||||||
ol {
|
ol {
|
||||||
overflow: auto;
|
/* overflow: auto; */
|
||||||
height: 75px;
|
/* height: 75px; */
|
||||||
background-color: rgb(41, 59, 10);
|
background-color: rgb(41, 59, 10);
|
||||||
color: rgb(75, 185, 75);
|
color: rgb(75, 185, 75);
|
||||||
border: 1px solid grey;
|
border: 1px solid grey;
|
||||||
|
|||||||
Reference in New Issue
Block a user