diff --git a/Panels/BasePanel.js b/Panels/BasePanel.js index 821dc68..57bb0d6 100644 --- a/Panels/BasePanel.js +++ b/Panels/BasePanel.js @@ -27,7 +27,16 @@ class BasePanel { addCheckboxes = (name, items) => { this.addCheckedInputs('checkbox', name, items); }; - openField = name => this.addText(`${name}: `); + openField = (name, id) => { + if(id) { + if(this._field) throw `Unable to add field ${id} when prior field was not closed.`; + const field = document.createElement('span'); + field.id = this.childId(id); + this.append(field); + this._field = field; + } + this.addText(`${name}: `) + }; addText = text => this.append(document.createTextNode(text)); addDynamicText = (id, text) => { const span = document.createElement('span'); @@ -35,7 +44,10 @@ class BasePanel { span.innerText = text; return this.append(span); } - closeField = () => this.addNewLine(); + closeField = () => { + this._field = undefined; + this.addNewLine() + }; addNewLine = () => this.append(document.createElement('br')); addDropdown = (id, items, eventName = 'change') => { const select = document.createElement('select'); @@ -169,7 +181,6 @@ class BasePanel { getElement = id => { const element = this.getDomElement().querySelector(`#${this.childId(id)}`); - // const element = document.getElementById(this.childId(id)); if(!element) throw new Error(`Unable to find ${id}`); return element; } @@ -180,6 +191,15 @@ class BasePanel { code.className = type === '' ? 'raw-data' : `raw-data-${type}`; this.append(code); } + addImage = (id, src, options) => { + const image = document.createElement('img'); + image.id = this.childId(id); + image.src = src; + Object.keys(options).forEach(key => { + image[key] = options[key]; + }) + this.append(image); + } setValueById = (id, value) => { const element = this.getElement(id); switch(element.tagName) { @@ -187,6 +207,10 @@ class BasePanel { case 'SELECT': element.value = value; break; + case 'IMG': + element.src = value; + element.onload = () => URL.revokeObjectURL(value); + break; default: element.innerText = value; } @@ -209,11 +233,21 @@ class BasePanel { return element.innerText; } } + display = (id, isDisplayed) => { + const element = this.getElement(id); + element.style.display = isDisplayed ? 'block' : 'none'; + } setCheckedById = (id, checked = true) => { this.getElement(id).checked = !!checked; } getCheckedById = (id) => !!(this.getElement(id).checked); - append = (element) => this.container.appendChild(element); + append = (element) => { + if(this._field) { + return this._field.appendChild(element); + } else { + return this.container.appendChild(element); + } + } clear = () => this.container.innerHTML = ''; addEventListener = (eventName, callback) => this.dispatcher.addListener(eventName, callback); removeEventListener = (eventName, callback) => this.dispatcher.removeListener(eventName, callback); diff --git a/Panels/MessagePanel.js b/Panels/MessagePanel.js index 8252e3f..3ba7270 100644 --- a/Panels/MessagePanel.js +++ b/Panels/MessagePanel.js @@ -1,20 +1,82 @@ +import { bytesToText, textToBytes, urlToBytes, bytesToUrl } from '../converters'; +import { byteSize } from '../Humanize'; import BasePanel from './BasePanel'; class MessagePanel extends BasePanel { constructor() { super('Message'); - this.addInputText('text-to-send', '', 'messageChange'); + + this.openField('Data Type'); + this.addDropdown('data-type', [ + {text: 'Text', value: 'text', selected: true}, + {text: 'Image', value: 'image'} + ], 'dataTypeChange'); + this.closeField(); + + this.openField('Text', 'field-text'); + this.addInputText('text-to-send', '', {eventName: 'messageChange'}); + this.closeField(); + + this.openField('Image', 'field-image'); + this.addImage('image-to-send', 'interlaced-sample.gif', {eventName: 'messageChange'}); + this.closeField(); + this.addButton('send-button', 'Send', 'send'); + this.addNewLine(); + + this.openField('Bytes'); + this.addDynamicText('bytes', 0); + this.closeField(); + this.addSection('Received'); - this.addProgressBar('received-progress', .50); + this.addCode('decoded-text', '', 'small'); + this.addImage('decoded-image', undefined, {width: 32, height: 32}); + + this.addProgressBar('received-progress', .50); + + this.addEventListener('dataTypeChange', ({values: [value]}) => { + this.display('field-text', value === 'text'); + this.display('field-image', value === 'image'); + this.display('decoded-image', value === 'image'); + this.display('decoded-text', value==='text'); + // should be 487 bytes + this.setValueById('bytes', byteSize(this.getMessageBytes().length)); + }); + this.addEventListener('messageChange', e => { + this.setValueById('bytes', byteSize(this.getMessageBytes().length)); + }); + this.dispatcher.emit('dataTypeChange', {values: [this.getDataType()]}); } getSendButtonText = () => this.getValueById('send-button'); setSendButtonText = text => this.setValueById('send-button', text); - setMessage = text => this.setValueById('text-to-send', text); - getMessage = () => this.getValueById('text-to-send'); + setMessageText = text => { + this.setValueById('text-to-send', text); + this.setValueById('bytes', byteSize(textToBytes(text).length)); + } + getMessageText = () => this.getValueById('text-to-send'); + getMessageBytes = () => { + if(this.getDataType() === 'text') { + return textToBytes(this.getMessageText()); + } else { + return urlToBytes(this.getElement('image-to-send').src); + } + } setProgress = percent => this.setProgressById('received-progress', percent); setReceived = (html) => this.setHtmlById('decoded-text', html); + setReceivedBytes = bytes => { + if(this.getDataType() === 'text') { + this.setReceived(bytesToText(bytes)); + } else { + this.setValueById('decoded-image', bytesToUrl(bytes)); + } + } + + getDataType = () => this.getValueById('data-type'); + setDataType = (value) => { + this.setValueById('data-type', value); + this.dispatcher.emit('dataTypeChange', {values: [value]}); + } } export default MessagePanel; \ No newline at end of file diff --git a/Panels/SpeedPanel.js b/Panels/SpeedPanel.js index ade17a0..4618c22 100644 --- a/Panels/SpeedPanel.js +++ b/Panels/SpeedPanel.js @@ -20,18 +20,13 @@ class SpeedPanel extends BasePanel { this.addDynamicText('transfer-duration', 'n/a'); this.closeField(); - this.addSection('Maximum'); - - this.openField('Packets'); - this.addDynamicText('max-packets', 'n/a'); - this.closeField(); + this.addSection('Maximum Data'); this.openField('Duration'); this.addDynamicText('max-duration', 'n/a'); this.closeField(); }; - setMaximumPackets = (count) => this.setValueById('max-packets', count.toLocaleString()); setMaximumDurationMilliseconds = (milliseconds) => this.setValueById('max-duration', Humanize.durationMilliseconds(milliseconds)); setPacketizationBitsPerSecond = (bps) => this.setValueById('bps-packetization', Humanize.bitsPerSecond(bps)); setDataBitsPerSecond = (bps) => this.setValueById('bps-data', Humanize.bitsPerSecond(bps)); diff --git a/converters.js b/converters.js index 2e22d82..47ad700 100644 --- a/converters.js +++ b/converters.js @@ -61,3 +61,20 @@ export const bitsToInt = (bits, bitLength) => { 2 ); } +export const urlToBytes = src => { + const xhr = new XMLHttpRequest(); + // we need a synchronous response. + xhr.open('GET', src, false); + xhr.overrideMimeType('text/plain; charset=x-user-defined'); + xhr.send(null); + if(xhr.status !== 200) return []; + let bytes = []; + for(let i = 0; i < xhr.response.length; i++) { + bytes.push(xhr.response.charCodeAt(i) & 0xFF); + } + return bytes; +} +export const bytesToUrl = bytes => { + const blob = new Blob([new Uint8Array(bytes)]); + return URL.createObjectURL(blob); +} \ No newline at end of file diff --git a/index.html b/index.html index 326d05d..88d4968 100644 --- a/index.html +++ b/index.html @@ -16,26 +16,6 @@ - -
-

Packet Configuration

-
- Bytes per packet:
- Bits per Packet:
-

Encoding

- Packet Encoding:
- Bits per block:
- Blocks per packet:
- Encoding bits per Packet:
-

Utilization

- Data Bits per Packet:
- Unused bits per packet:
-

Segments

- Segments Per Packet:
- Bits per segment: N/A
- Last segment unused bits:
-
-

Data

diff --git a/index.js b/index.js index fe36de7..ca0a93d 100644 --- a/index.js +++ b/index.js @@ -74,10 +74,10 @@ function handleWindowLoad() { panelContainer.prepend(speedPanel.getDomElement()); panelContainer.prepend(graphConfigurationPanel.getDomElement()); panelContainer.prepend(frequencyGraphPanel.getDomElement()); - panelContainer.prepend(availableFskPairsPanel.getDomElement()); panelContainer.prepend(packetizationPanel.getDomElement()); - panelContainer.prepend(signalPanel.getDomElement()); + panelContainer.prepend(availableFskPairsPanel.getDomElement()); panelContainer.prepend(frequencyPanel.getDomElement()); + panelContainer.prepend(signalPanel.getDomElement()); panelContainer.prepend(bitsReceivedPanel.getDomElement()); panelContainer.prepend(bitsSentPanel.getDomElement()); panelContainer.prepend(messagePanel.getDomElement()); @@ -88,19 +88,20 @@ function handleWindowLoad() { communicationsPanel.setSendSpeakers(false); communicationsPanel.setSendAnalyzer(true); - messagePanel.setMessage(Randomizer.text(5)); + messagePanel.setMessageText(Randomizer.text(5)); messagePanel.setProgress(0); messagePanel.setReceived(''); + messagePanel.setDataType('image'); messagePanel.setSendButtonText('Send'); bitsSentPanel.setCode(''); bitsReceivedPanel.setCode(''); - frequencyPanel.setMinimumFrequency(9000); - frequencyPanel.setMaximumFrequency(15000); + frequencyPanel.setMinimumFrequency(2500); + frequencyPanel.setMaximumFrequency(23000); frequencyPanel.setFftSize(2 ** 9); - frequencyPanel.setFskPadding(1); - frequencyPanel.setMultiFskPadding(1); + frequencyPanel.setFskPadding(3); + frequencyPanel.setMultiFskPadding(4); signalPanel.setWaveform('triangle'); signalPanel.setSegmentDurationMilliseconds(30); @@ -125,7 +126,6 @@ function handleWindowLoad() { frequencyGraphPanel.setAmplitudeThreshold(signalPanel.getAmplitudeThreshold()); frequencyGraphPanel.setDurationMilliseconds(graphConfigurationPanel.getDurationMilliseconds()); - speedPanel.setMaximumPackets(0); speedPanel.setMaximumDurationMilliseconds(0); speedPanel.setDataBitsPerSecond(0); speedPanel.setPacketizationBitsPerSecond(0); @@ -319,43 +319,29 @@ function updatePacketUtils() { packetEncodingBitCount: ERROR_CORRECTION_BLOCK_SIZE, packetDecodingBitCount: ERROR_CORRECTION_DATA_SIZE, }); - speedPanel.setMaximumPackets(PacketUtils.getMaxPackets()); speedPanel.setMaximumDurationMilliseconds(PacketUtils.getMaxDurationMilliseconds()); speedPanel.setDataBitsPerSecond(PacketUtils.getEffectiveBaud()); speedPanel.setPacketizationBitsPerSecond(PacketUtils.getBaud()); speedPanel.setTransferDurationMilliseconds(PacketUtils.getDataTransferDurationMillisecondsFromByteCount( - textToBytes(messagePanel.getMessage()).length + messagePanel.getMessageBytes().length )); } function updatePacketStats() { - const text = messagePanel.getMessage(); - const bits = textToBits(text); - const byteCount = text.length; + const bytes = messagePanel.getMessageBytes(); + const bits = bytesToBits(bytes); + const byteCount = bytes.length; const bitCount = PacketUtils.getPacketizationBitCountFromBitCount(bits.length);; // Data - document.getElementById('original-byte-count').innerText = textToBytes(text).length.toLocaleString(); + document.getElementById('original-byte-count').innerText = byteCount.toLocaleString(); document.getElementById('packetization-byte-count').innerText = PacketUtils.getPacketizationByteCountFromBitCount(bits.length).toLocaleString(); document.getElementById('packetization-bit-count').innerText = bitCount.toLocaleString(); document.getElementById('packet-count').innerText = PacketUtils.getPacketCount(bitCount).toLocaleString(); - // # Packet Config - document.getElementById('bits-per-packet').innerText = PacketUtils.getPacketMaxBitCount().toLocaleString(); - document.getElementById('bytes-per-packet').innerText = Humanize.byteSize(PacketUtils.getPacketMaxByteCount()); // ## Packet Encoding - document.getElementById('packet-encoding').innerText = PacketUtils.isPacketEncoded() ? 'Yes' : 'No'; - document.getElementById('packet-encoding-block-count').innerText = PacketUtils.getPacketEncodingBlockCount().toLocaleString(); - document.getElementById('packet-encoding-bits-per-block').innerText = PacketUtils.packetEncodingBlockSize().toLocaleString(); - document.getElementById('packet-encoding-bit-count').innerText = PacketUtils.getEncodedPacketDataBitCount().toLocaleString(); - - document.getElementById('bits-per-segment').innerText = PacketUtils.getBitsPerSegment(); // Data - document.getElementById('packet-data-bit-count').innerText = PacketUtils.getPacketDataBitCount().toLocaleString(); - document.getElementById('packet-unused-bit-count').innerText = PacketUtils.getPacketUnusedBitCount().toLocaleString(); document.getElementById('last-packet-unused-bit-count').innerText = PacketUtils.fromByteCountGetPacketLastUnusedBitCount(byteCount).toLocaleString(); - document.getElementById('last-segment-unused-bit-count').innerText = PacketUtils.getPacketLastSegmentUnusedBitCount().toLocaleString() - document.getElementById('segments-per-packet').innerText = PacketUtils.getPacketSegmentCount().toLocaleString(); frequencyGraphPanel.setSamplePeriodsPerGroup(PacketUtils.getPacketSegmentCount()); document.getElementById('total-segments').innerText = getTotalSegmentCount(bitCount).toLocaleString(); } @@ -673,9 +659,13 @@ function handleStreamManagerChange() { const bytes = StreamManager.getDataBytes(); const receivedText = bytesToText(bytes); - messagePanel.setReceived( - receivedText.split('').reduce(textExpectorReducer(SENT_ORIGINAL_TEXT), '') - ); + if(messagePanel.getDataType() === 'text') { + messagePanel.setReceived( + receivedText.split('').reduce(textExpectorReducer(SENT_ORIGINAL_TEXT), '') + ); + } else { + messagePanel.setReceivedBytes(bytes); + } } function parseTotalBitsTransferring() { const dataByteCount = StreamManager.getTransferByteCount(); @@ -850,8 +840,7 @@ function handleSendButtonClick() { if(messagePanel.getSendButtonText() === 'Stop') { AudioSender.stop(); } else { - const text = messagePanel.getMessage(); - sendBytes(textToBytes(text)); + sendBytes(messagePanel.getMessageBytes()); } } function getAnalyser() { diff --git a/interlaced-sample.gif b/interlaced-sample.gif new file mode 100644 index 0000000..33a7672 Binary files /dev/null and b/interlaced-sample.gif differ