@neurosity/sdk
Version:
Neurosity SDK
117 lines (116 loc) • 4.83 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.encodedSampleSize = exports.decode = exports.binaryBufferToSamples = exports.binaryBufferToEpoch = void 0;
const rxjs_1 = require("rxjs");
const operators_1 = require("rxjs/operators");
const index_js_1 = require("buffer/index.js"); // not including /index.js causes typescript to uses Node's native Buffer built-in and we want to use this npm package for both node and the browser
const pipes_1 = require("../../../utils/pipes");
const EPOCH_BUFFER_SIZE = 16;
const SAMPLING_RATE_FALLBACK = 256; // Crown's sampling rate
/** Size in bytes for each channel's payload. */
const TimestampSize = 8; // UInt64
const MarkerSize = 2; // UInt16
const ChannelDataSize = 8; // Double
/** Size in bytes for the static payload of every sample (Timestamp + Marker) */
const SampleFixedSize = TimestampSize + MarkerSize;
/**
* @hidden
*/
function binaryBufferToEpoch(deviceInfo) {
var _a;
if (!(deviceInfo === null || deviceInfo === void 0 ? void 0 : deviceInfo.samplingRate)) {
console.warn(`Didn't receive a sampling rate, defaulting to ${SAMPLING_RATE_FALLBACK}`);
}
return (0, rxjs_1.pipe)(binaryBufferToSamples(deviceInfo.channels), (0, pipes_1.epoch)({
duration: EPOCH_BUFFER_SIZE,
interval: EPOCH_BUFFER_SIZE,
samplingRate: (_a = deviceInfo === null || deviceInfo === void 0 ? void 0 : deviceInfo.samplingRate) !== null && _a !== void 0 ? _a : SAMPLING_RATE_FALLBACK
}), (0, pipes_1.addInfo)({
channelNames: deviceInfo.channelNames,
samplingRate: deviceInfo.samplingRate
}));
}
exports.binaryBufferToEpoch = binaryBufferToEpoch;
/**
* @hidden
*/
function binaryBufferToSamples(channelCount) {
return (0, rxjs_1.pipe)((0, operators_1.mergeMap)((arrayBuffer) => {
const buffer = index_js_1.Buffer.from(arrayBuffer);
const decoded = decode(buffer, channelCount);
return (0, rxjs_1.from)(decoded); // `from` creates an Observable emission from each item (Sample) in the array
}));
}
exports.binaryBufferToSamples = binaryBufferToSamples;
/**
* @hidden
*
* Decode the supplied Buffer as a list of Sample.
*
* Supplied buffer's length must be multiple of
* `encodedSampleSize(channelCount)`.
*
* NB: This method does not guarantee validity of decoded samples. When
* supplied with a buffer of appropriate length, it will always return a
* matching number of Sample8. Since the encoding protocol defines no
* metadata/checksum, correctness must be guaranteed via test coverage.
*
* @param buffer Buffer with binary payload to decode.
* @param channelCount Number of expected channels in each sample.
*
* @returns List of decoded Samples present in buffer.
*/
function decode(buffer, channelCount) {
let sampleLen = encodedSampleSize(channelCount);
// Alternative: relax this check, process sampleLen at a time, discard remainder?
if (buffer.length % sampleLen != 0) {
throw new Error(`buffer.length (${buffer.length}) for ${channelCount} channels must be multiple of ${sampleLen}B)`);
}
let sampleCount = buffer.length / sampleLen;
let samples = new Array(sampleCount);
for (let i = 0; i < sampleCount; i++) {
let offset = i * sampleLen;
let channelData = new Array(channelCount);
// Read 8 bytes for timestamp & advance offset
let ts = buffer.readBigUInt64BE(offset);
offset += TimestampSize;
// Read 1 byte for marker & advance offset
let marker = buffer.readUInt16BE(offset);
offset += MarkerSize;
// Read 8 bytes for each channel & advance offset
for (let i = 0; i < channelCount; i++) {
channelData[i] = buffer.readDoubleBE(offset);
offset += ChannelDataSize;
}
samples[i] = {
timestamp: Number(ts),
// TODO: uncomment when ready
// marker: marker,
data: channelData
};
}
return samples;
}
exports.decode = decode;
/**
* @hidden
*
* Calculate the size of each sample based on the number of channels.
*
* Each sample has the following 3 segments:
* - Timestamp: 8 bytes (UInt64); contains current time in millis since epoch)
* - Marker: 2 bytes (UInt16); for classifier data
* - Data: N * 8 bytes (Double), each entry representing data from a different
* electrode.
*
* +-----------+--------+------------------+
* | timestamp | marker | data (e1 ... eN) |
* +-----------+--------+------------------+
*
* The number of entries for Data varies per hardware model. It can be assumed
* to remain constant for the lifetime of the program.
*/
function encodedSampleSize(channelCount) {
return SampleFixedSize + channelCount * ChannelDataSize;
}
exports.encodedSampleSize = encodedSampleSize;