/**
* spc-parser - Thermo Galactic GRAMS SPC files parser
* @version v0.7.1
* @link https://github.com/cheminfo/spc-parser#readme
* @license MIT
*/
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('util')) :
typeof define === 'function' && define.amd ? define(['exports', 'util'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.SPCParser = {}, global.util));
})(this, (function (exports, util) { 'use strict';
/**
* Gives meaning to type codes.
* @param xzwType x, z or w type code.
* @return String corresponding to the code.
*/
function xzwTypes(xzwType) {
switch (xzwType) {
case 1:
return 'Wavenumber (cm-1)';
case 2:
return 'Micrometers (um)';
case 3:
return 'Nanometers (nm)';
case 4:
return 'Seconds';
case 5:
return 'Minutes';
case 6:
return 'Hertz (Hz)';
case 7:
return 'Kilohertz (KHz)';
case 8:
return 'Megahertz (MHz)';
case 9:
return 'Mass (M/z)';
case 10:
return 'Parts per million (PPM)';
case 11:
return 'Days';
case 12:
return 'Years';
case 13:
return 'Raman Shift (cm-1)';
case 14:
return 'eV';
case 15:
return 0;
case 16:
return 'Diode Number';
case 17:
return 'Channel ';
case 18:
return 'Degrees';
case 19:
return 'Temperature (F)';
case 20:
return 'Temperature (C)';
case 21:
return 'Temperature (K)';
case 22:
return 'Data Points';
case 23:
return 'Milliseconds (mSec)';
case 24:
return 'Microseconds (uSec)';
case 25:
return 'Nanoseconds (nSec)';
case 26:
return 'Gigahertz (GHz)';
case 27:
return 'Centimeters (cm)';
case 28:
return 'Meters (m)';
case 29:
return 'Millimeters (mm)';
case 30:
return 'Hours';
case 255:
return 'Double interferogram';
default:
return 'Arbitrary';
}
}
/**
* Gives meaning to y type codes
* @param yType y type code
* @return String corresponding to the code
*/
function yTypes(yType) {
switch (yType) {
case 0:
return 'Arbitrary Intensity';
case 1:
return 'Interferogram';
case 2:
return 'Absorbance';
case 3:
return 'Kubelka-Monk';
case 4:
return 'Counts';
case 5:
return 'Volts';
case 6:
return 'Degrees';
case 7:
return 'Milliamps';
case 8:
return 'Millimeters';
case 9:
return 'Millivolts';
case 10:
return 'Log(1/R)';
case 11:
return 'Percent';
case 12:
return 'Intensity';
case 13:
return 'Relative Intensity';
case 14:
return 'Energy';
case 16:
return 'Decibel';
case 19:
return 'Temperature (F)';
case 20:
return 'Temperature (C)';
case 21:
return 'Temperature (K)';
case 22:
return 'Index of Refraction [N]';
case 23:
return 'Extinction Coeff. [K]';
case 24:
return 'Real';
case 25:
return 'Imaginary';
case 26:
return 'Complex';
case 128:
return 'Transmission';
case 129:
return 'Reflectance';
case 130:
return 'Arbitrary or Single Beam with Valley Peaks';
case 131:
return 'Emission';
default:
return 'Reference Arbitrary Energy';
}
}
/**
* Convert code to experiment type
* @param code
* @returns type of experiment carried out.
*/
function experimentSettings(code) {
switch (code) {
case 1:
return 'Gas Chromatogram';
case 2:
return 'General Chromatogram (same as SPCGEN with TCGRAM)';
case 3:
return 'HPLC Chromatogram';
case 4:
return 'FT-IR, FT-NIR, FT-Raman Spectrum or Igram (Can also be used for scanning IR.)';
case 5:
return 'NIR Spectrum (Usually multi-spectral data sets for calibration.)';
case 7:
return 'UV-VIS Spectrum (Can be used for single scanning UV-VIS-NIR.)';
case 8:
return 'X-ray Diffraction Spectrum';
case 9:
return 'Mass Spectrum (Can be single, GC-MS, Continuum, Centroid or TOF.)';
case 10:
return 'NMR Spectrum or FID';
case 11:
return 'Raman Spectrum (Usually Diode Array, CCD, etc. use SPCFTIR for FT-Raman.)';
case 12:
return 'Fluorescence Spectrum';
case 13:
return 'Atomic Spectrum';
case 14:
return 'Chromatography Diode Array Spectra';
default:
return 'General SPC (could be anything)';
}
}
/**
* Gets the parameter in each bit of the flag
*
* @param flag First byte of the main header.
* @returns The parameters.
*/
class FlagParameters {
constructor(flag) {
this.y16BitPrecision = (flag & 1) !== 0; //Y values are 16 bits instead of 32
this.useExperimentExtension = (flag & 2) !== 0; //Enable experiment mode
this.multiFile = (flag & 4) !== 0; //Multiple spectra (multifile)
this.zValuesRandom = (flag & 8) !== 0; //Z values in random order if multiFile
this.zValuesUneven = (flag & 16) !== 0; //Z values ordered but unevenly spaced if multi
this.customAxisLabels = (flag & 32) !== 0; //Custom labels
this.xyxy = (flag & 64) !== 0; //One X array per subfile, for discontinuous curves
this.xy = (flag & 128) !== 0; // Non-evenly spaced X, X before Y
}
}
/**
* Gets the date encoded in binary in a long number.
* @param long Binary date.
* @return Date formatted to ISO 8601:2019 convention.
*/
function longToDate(long) {
if (long === 0) {
return '0000-00-00T00:00:00.00Z';
}
const date = new Date();
date.setUTCFullYear(long >> 20);
date.setUTCMonth((long >> 16 & 0x0f) - 1);
date.setUTCDate(long >> 11 & 0x1f);
date.setUTCHours(long >> 6 & 0x1f);
date.setUTCMinutes(long & 0x3f);
date.setUTCSeconds(0);
date.setUTCMilliseconds(0);
return date.toISOString();
}
/**
* Old-format File-header parsing.
* @param buffer spc buffer.
* @param prev `{parameters,fileversion}`
* @return file metadata
*/
class TheOldHeader {
constructor(buffer, prev) {
this.fileVersion = prev.fileVersion; //Each bit contains a parameter
this.parameters = prev.parameters; //4B => New format; 4D => LabCalc format
this.exponentY = buffer.readInt16(); //Word (16 bits) instead of byte
this.numberPoints = buffer.readFloat32();
this.startingX = buffer.readFloat32();
this.endingX = buffer.readFloat32();
this.xUnitsType = xzwTypes(buffer.readUint8());
this.yUnitsType = yTypes(buffer.readUint8());
const date = new Date();
const zTypeYear = buffer.readUint16(); //Unrelated to Z axis
date.setUTCFullYear(zTypeYear % 4096); // TODO: might be wrong
date.setUTCMonth(Math.max(buffer.readUint8() - 1, 0));
date.setUTCDate(buffer.readUint8());
date.setUTCHours(buffer.readUint8());
date.setUTCMinutes(buffer.readUint8());
this.date = date.toISOString();
this.resolutionDescription = buffer.readChars(8).replace(/\x00/g, '').trim();
this.peakPointNumber = buffer.readUint16();
this.scans = buffer.readUint16();
this.spare = [];
for (let i = 0; i < 7; i++) {
this.spare.push(buffer.readFloat32());
}
this.memo = buffer.readChars(130).replace(/\x00/g, '').trim();
this.xyzLabels = buffer.readChars(30).replace(/\x00/g, '').trim();
}
}
/**
* New format file-header parsing.
* @param buffer spc buffer.
* @param prev `{parameters,fileversion}`
* @return file metadata
*/
class TheNewHeader {
constructor(buffer, prev) {
this.fileVersion = prev.fileVersion; //Each bit contains a parameter
this.parameters = prev.parameters; //4B => New format; 4D => LabCalc format
this.experimentType = experimentSettings(buffer.readUint8()); //Experiment type code (See SPC.h)
this.exponentY = buffer.readInt8(); //Exponent for Y values (80h = floating point): FloatY = (2^Exp)*IntY/(2^32) 32-bit; FloatY = (2^Exp)*IntY/(2^16) 32-bit
this.numberPoints = buffer.readUint32(); //Number of points (if not XYXY)
this.startingX = buffer.readFloat64(); //First X coordinate
this.endingX = buffer.readFloat64(); //Last X coordinate
this.spectra = buffer.readUint32(); //Number of spectrums
this.xUnitsType = xzwTypes(buffer.readUint8()); //X Units type code (See types.js)
this.yUnitsType = yTypes(buffer.readUint8()); //Y ""
this.zUnitsType = xzwTypes(buffer.readUint8()); //Z ""
this.postingDisposition = buffer.readUint8(); //Posting disposition (See GRAMSDDE.H)
this.date = longToDate(buffer.readUint32()); //Date: minutes = first 6 bits, hours = 5 next bits, days = 5 next, months = 4 next, years = 12 last
this.resolutionDescription = buffer.readChars(9).replace(/\x00/g, '').trim(); //Resolution description text
this.sourceInstrumentDescription = buffer.readChars(9).replace(/\x00/g, '').trim(); // Source Instrument description text
this.peakPointNumber = buffer.readUint16(); //Peak point number for interferograms
this.spare = [];
for (let i = 0; i < 8; i++) {
this.spare.push(buffer.readFloat32());
}
if (this.fileVersion === 0x4c) {
//Untested case because no test files
this.spare.reverse();
}
this.memo = buffer.readChars(130).replace(/\x00/g, '').trim();
this.xyzLabels = buffer.readChars(30).replace(/\x00/g, '').trim();
this.logOffset = buffer.readUint32(); //Byte offset to Log Block
this.modifiedFlag = buffer.readUint32(); //File modification flag (See values in SPC.H)
this.processingCode = buffer.readUint8(); //Processing code (See GRAMSDDE.H)
this.calibrationLevel = buffer.readUint8(); //Calibration level + 1
this.subMethodSampleInjectionNumber = buffer.readUint16(); //Sub-method sample injection number
this.concentrationFactor = buffer.readFloat32(); //Floating data multiplier concentration factor
this.methodFile = buffer.readChars(48).replace(/\x00/g, '').trim(); //Method file
this.zSubIncrement = buffer.readFloat32(); //Z subfile increment for even Z Multifiles
this.wPlanes = buffer.readUint32();
this.wPlaneIncrement = buffer.readFloat32();
this.wAxisUnits = xzwTypes(buffer.readUint8()); //W axis units code
this.reserved = buffer.readChars(187).replace(/\x00/g, '').trim(); //Reserved space (Must be zero)
if (this.xUnitsType === 0) {
this.xUnitsType = this.xyzLabels.substring(0, 10);
}
if (this.zUnitsType === 0) {
this.zUnitsType = this.xyzLabels.substring(20, 30);
}
}
}
/**
* File-header parsing - First 512/256 bytes (new/old format).
* @param buffer SPC buffer.
* @return File-header object
*/
function fileHeader(buffer) {
const parameters = new FlagParameters(buffer.readUint8()); //Each bit contains a parameter
const fileVersion = buffer.readUint8(); //4B => New format; 4D => LabCalc format
const headerOpts = {
parameters,
fileVersion
};
switch (fileVersion) {
case 0x4b:
// new format
break;
case 0x4c:
buffer.setBigEndian();
break;
case 0x4d:
{
// old LabCalc format
return new TheOldHeader(buffer, headerOpts);
}
default:
throw new Error('Unrecognized file format: byte 01 must be either 4B, 4C or 4D');
}
return new TheNewHeader(buffer, headerOpts);
}
/** Get how the data was stored
* @param multiFile - whether there are multiple spectra (subfiles) or not.
* @param xy - uneven x values
* @param xyxy - multifile with separate x axis
* @return the shape of the data as a string
*/
function getDataShape(_ref) {
let {
multiFile,
xy,
xyxy
} = _ref;
/* single file */
if (!multiFile) {
// Y or XY,
return !xy ? 'Y' : xyxy ? 'exception' : 'XY';
}
/* then multifile */
if (!xy) {
/* even X - equidistant */
return 'YY';
} else {
// uneven x
return !xyxy ? 'XYY' : 'XYXY';
}
}
/**
* Inspects properties and tries to classify the spectra
* For the most common spectra types
* @param data the parsed data
* @returns string describing the type of spectra ([[`SpectraType`]]) or "General" if unsure.
*/
function guessSpectraType(meta) {
//function tested with the `fileHeader.test.ts`
const {
xUnitsType: xU,
yUnitsType: yU
} = meta; // for the new file header they define a "experiment type"
if (meta instanceof TheNewHeader) {
// "General SPC" does not give any information
if (!meta.experimentType.startsWith('General SPC')) {
const id = meta.experimentType.split(' ')[0];
switch (id //find all possible ids in `types.ts` file
) {
case 'FT-IR,':
return 'ir';
case 'NIR':
return 'ir';
case 'UV-VIS':
return 'uv';
case 'Mass':
return 'mass';
case 'Raman':
return 'raman';
default:
return 'other';
}
}
} // for old header or General SPC
switch (xU) {
case 'Mass (M/z)':
return 'mass';
case 'Raman Shift (cm-1)':
return 'raman';
case 'Micrometers (um)':
return uvOrIR(meta, 'micrometer');
case 'Wavenumber (cm-1)':
{
return uvOrIR(meta, 'wavenumber');
}
case 'Nanometers (nm)':
if ([
/*'Kubelka-Monk'*/
'Absorbance', 'Log(1/R)', 'Transmission'].includes(yU)) {
return uvOrIR(meta, 'nanometer');
}
return 'other';
default:
return 'other';
}
}
/**
* Further classify an X axis that is using "wavenumber" as uv or ir.
* @param data - the parsed file (a jsonlike object)
* @returns
*/
function uvOrIR(meta, xUnit) {
//tested in "parse" because of the input
const dataShape = getDataShape(meta.parameters); //xyxy and exception won't normally get here anyways (raman or ms normally.)
const analyze = ['Y', 'YY', 'XY', 'XYY'].includes(dataShape);
if (analyze) {
let sX = meta.startingX;
let eX = meta.endingX;
if (xUnit !== 'nanometer') {
sX = unitToNano(sX, xUnit);
eX = unitToNano(eX, xUnit);
}
const lowerBound = sX <= eX ? sX : eX;
return getRegion(lowerBound);
}
return 'other';
}
/**
* @param lb - lower boundary in _nanometers_
* @return type of spectra
*/
function getRegion(lb) {
return lb < 150 ? 'other' : lb < 700 ? 'uv' : 'ir';
}
/**
* Converts micrometers or wavenumber units to nanometers
* This allows a unique way to determine the spectra region (using nanometers).
* @param x - value
* @param from - input unit to convert
* @return equivalent in nanometers
*/
function unitToNano(x, from) {
return from === 'micrometer' ? x * 1000 : 1 / x * 10 ** 7;
}
function decode(bytes) {
let encoding = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'utf8';
const decoder = new util.TextDecoder(encoding);
return decoder.decode(bytes);
}
const encoder = new util.TextEncoder();
function encode(str) {
return encoder.encode(str);
}
const defaultByteLength = 1024 * 8;
class IOBuffer {
/**
* @param data - The data to construct the IOBuffer with.
* If data is a number, it will be the new buffer's length
* If data is `undefined`, the buffer will be initialized with a default length of 8Kb
* If data is an ArrayBuffer, SharedArrayBuffer, an ArrayBufferView (Typed Array), an IOBuffer instance,
* or a Node.js Buffer, a view will be created over the underlying ArrayBuffer.
* @param options
*/
constructor() {
let data = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : defaultByteLength;
let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
let dataIsGiven = false;
if (typeof data === 'number') {
data = new ArrayBuffer(data);
} else {
dataIsGiven = true;
this.lastWrittenByte = data.byteLength;
}
const offset = options.offset ? options.offset >>> 0 : 0;
const byteLength = data.byteLength - offset;
let dvOffset = offset;
if (ArrayBuffer.isView(data) || data instanceof IOBuffer) {
if (data.byteLength !== data.buffer.byteLength) {
dvOffset = data.byteOffset + offset;
}
data = data.buffer;
}
if (dataIsGiven) {
this.lastWrittenByte = byteLength;
} else {
this.lastWrittenByte = 0;
}
this.buffer = data;
this.length = byteLength;
this.byteLength = byteLength;
this.byteOffset = dvOffset;
this.offset = 0;
this.littleEndian = true;
this._data = new DataView(this.buffer, dvOffset, byteLength);
this._mark = 0;
this._marks = [];
}
/**
* Checks if the memory allocated to the buffer is sufficient to store more
* bytes after the offset.
* @param byteLength - The needed memory in bytes.
* @returns `true` if there is sufficient space and `false` otherwise.
*/
available() {
let byteLength = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 1;
return this.offset + byteLength <= this.length;
}
/**
* Check if little-endian mode is used for reading and writing multi-byte
* values.
* @returns `true` if little-endian mode is used, `false` otherwise.
*/
isLittleEndian() {
return this.littleEndian;
}
/**
* Set little-endian mode for reading and writing multi-byte values.
*/
setLittleEndian() {
this.littleEndian = true;
return this;
}
/**
* Check if big-endian mode is used for reading and writing multi-byte values.
* @returns `true` if big-endian mode is used, `false` otherwise.
*/
isBigEndian() {
return !this.littleEndian;
}
/**
* Switches to big-endian mode for reading and writing multi-byte values.
*/
setBigEndian() {
this.littleEndian = false;
return this;
}
/**
* Move the pointer n bytes forward.
* @param n - Number of bytes to skip.
*/
skip() {
let n = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 1;
this.offset += n;
return this;
}
/**
* Move the pointer n bytes backward.
* @param n - Number of bytes to move back.
*/
back() {
let n = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 1;
this.offset -= n;
return this;
}
/**
* Move the pointer to the given offset.
* @param offset
*/
seek(offset) {
this.offset = offset;
return this;
}
/**
* Store the current pointer offset.
* @see {@link IOBuffer#reset}
*/
mark() {
this._mark = this.offset;
return this;
}
/**
* Move the pointer back to the last pointer offset set by mark.
* @see {@link IOBuffer#mark}
*/
reset() {
this.offset = this._mark;
return this;
}
/**
* Push the current pointer offset to the mark stack.
* @see {@link IOBuffer#popMark}
*/
pushMark() {
this._marks.push(this.offset);
return this;
}
/**
* Pop the last pointer offset from the mark stack, and set the current
* pointer offset to the popped value.
* @see {@link IOBuffer#pushMark}
*/
popMark() {
const offset = this._marks.pop();
if (offset === undefined) {
throw new Error('Mark stack empty');
}
this.seek(offset);
return this;
}
/**
* Move the pointer offset back to 0.
*/
rewind() {
this.offset = 0;
return this;
}
/**
* Make sure the buffer has sufficient memory to write a given byteLength at
* the current pointer offset.
* If the buffer's memory is insufficient, this method will create a new
* buffer (a copy) with a length that is twice (byteLength + current offset).
* @param byteLength
*/
ensureAvailable() {
let byteLength = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 1;
if (!this.available(byteLength)) {
const lengthNeeded = this.offset + byteLength;
const newLength = lengthNeeded * 2;
const newArray = new Uint8Array(newLength);
newArray.set(new Uint8Array(this.buffer));
this.buffer = newArray.buffer;
this.length = this.byteLength = newLength;
this._data = new DataView(this.buffer);
}
return this;
}
/**
* Read a byte and return false if the byte's value is 0, or true otherwise.
* Moves pointer forward by one byte.
*/
readBoolean() {
return this.readUint8() !== 0;
}
/**
* Read a signed 8-bit integer and move pointer forward by 1 byte.
*/
readInt8() {
return this._data.getInt8(this.offset++);
}
/**
* Read an unsigned 8-bit integer and move pointer forward by 1 byte.
*/
readUint8() {
return this._data.getUint8(this.offset++);
}
/**
* Alias for {@link IOBuffer#readUint8}.
*/
readByte() {
return this.readUint8();
}
/**
* Read `n` bytes and move pointer forward by `n` bytes.
*/
readBytes() {
let n = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 1;
const bytes = new Uint8Array(n);
for (let i = 0; i < n; i++) {
bytes[i] = this.readByte();
}
return bytes;
}
/**
* Read a 16-bit signed integer and move pointer forward by 2 bytes.
*/
readInt16() {
const value = this._data.getInt16(this.offset, this.littleEndian);
this.offset += 2;
return value;
}
/**
* Read a 16-bit unsigned integer and move pointer forward by 2 bytes.
*/
readUint16() {
const value = this._data.getUint16(this.offset, this.littleEndian);
this.offset += 2;
return value;
}
/**
* Read a 32-bit signed integer and move pointer forward by 4 bytes.
*/
readInt32() {
const value = this._data.getInt32(this.offset, this.littleEndian);
this.offset += 4;
return value;
}
/**
* Read a 32-bit unsigned integer and move pointer forward by 4 bytes.
*/
readUint32() {
const value = this._data.getUint32(this.offset, this.littleEndian);
this.offset += 4;
return value;
}
/**
* Read a 32-bit floating number and move pointer forward by 4 bytes.
*/
readFloat32() {
const value = this._data.getFloat32(this.offset, this.littleEndian);
this.offset += 4;
return value;
}
/**
* Read a 64-bit floating number and move pointer forward by 8 bytes.
*/
readFloat64() {
const value = this._data.getFloat64(this.offset, this.littleEndian);
this.offset += 8;
return value;
}
/**
* Read a 64-bit signed integer number and move pointer forward by 8 bytes.
*/
readBigInt64() {
const value = this._data.getBigInt64(this.offset, this.littleEndian);
this.offset += 8;
return value;
}
/**
* Read a 64-bit unsigned integer number and move pointer forward by 8 bytes.
*/
readBigUint64() {
const value = this._data.getBigUint64(this.offset, this.littleEndian);
this.offset += 8;
return value;
}
/**
* Read a 1-byte ASCII character and move pointer forward by 1 byte.
*/
readChar() {
return String.fromCharCode(this.readInt8());
}
/**
* Read `n` 1-byte ASCII characters and move pointer forward by `n` bytes.
*/
readChars() {
let n = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 1;
let result = '';
for (let i = 0; i < n; i++) {
result += this.readChar();
}
return result;
}
/**
* Read the next `n` bytes, return a UTF-8 decoded string and move pointer
* forward by `n` bytes.
*/
readUtf8() {
let n = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 1;
return decode(this.readBytes(n));
}
/**
* Read the next `n` bytes, return a string decoded with `encoding` and move pointer
* forward by `n` bytes.
* If no encoding is passed, the function is equivalent to @see {@link IOBuffer#readUtf8}
*/
decodeText() {
let n = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 1;
let encoding = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'utf-8';
return decode(this.readBytes(n), encoding);
}
/**
* Write 0xff if the passed value is truthy, 0x00 otherwise and move pointer
* forward by 1 byte.
*/
writeBoolean(value) {
this.writeUint8(value ? 0xff : 0x00);
return this;
}
/**
* Write `value` as an 8-bit signed integer and move pointer forward by 1 byte.
*/
writeInt8(value) {
this.ensureAvailable(1);
this._data.setInt8(this.offset++, value);
this._updateLastWrittenByte();
return this;
}
/**
* Write `value` as an 8-bit unsigned integer and move pointer forward by 1
* byte.
*/
writeUint8(value) {
this.ensureAvailable(1);
this._data.setUint8(this.offset++, value);
this._updateLastWrittenByte();
return this;
}
/**
* An alias for {@link IOBuffer#writeUint8}.
*/
writeByte(value) {
return this.writeUint8(value);
}
/**
* Write all elements of `bytes` as uint8 values and move pointer forward by
* `bytes.length` bytes.
*/
writeBytes(bytes) {
this.ensureAvailable(bytes.length);
for (let i = 0; i < bytes.length; i++) {
this._data.setUint8(this.offset++, bytes[i]);
}
this._updateLastWrittenByte();
return this;
}
/**
* Write `value` as a 16-bit signed integer and move pointer forward by 2
* bytes.
*/
writeInt16(value) {
this.ensureAvailable(2);
this._data.setInt16(this.offset, value, this.littleEndian);
this.offset += 2;
this._updateLastWrittenByte();
return this;
}
/**
* Write `value` as a 16-bit unsigned integer and move pointer forward by 2
* bytes.
*/
writeUint16(value) {
this.ensureAvailable(2);
this._data.setUint16(this.offset, value, this.littleEndian);
this.offset += 2;
this._updateLastWrittenByte();
return this;
}
/**
* Write `value` as a 32-bit signed integer and move pointer forward by 4
* bytes.
*/
writeInt32(value) {
this.ensureAvailable(4);
this._data.setInt32(this.offset, value, this.littleEndian);
this.offset += 4;
this._updateLastWrittenByte();
return this;
}
/**
* Write `value` as a 32-bit unsigned integer and move pointer forward by 4
* bytes.
*/
writeUint32(value) {
this.ensureAvailable(4);
this._data.setUint32(this.offset, value, this.littleEndian);
this.offset += 4;
this._updateLastWrittenByte();
return this;
}
/**
* Write `value` as a 32-bit floating number and move pointer forward by 4
* bytes.
*/
writeFloat32(value) {
this.ensureAvailable(4);
this._data.setFloat32(this.offset, value, this.littleEndian);
this.offset += 4;
this._updateLastWrittenByte();
return this;
}
/**
* Write `value` as a 64-bit floating number and move pointer forward by 8
* bytes.
*/
writeFloat64(value) {
this.ensureAvailable(8);
this._data.setFloat64(this.offset, value, this.littleEndian);
this.offset += 8;
this._updateLastWrittenByte();
return this;
}
/**
* Write `value` as a 64-bit signed bigint and move pointer forward by 8
* bytes.
*/
writeBigInt64(value) {
this.ensureAvailable(8);
this._data.setBigInt64(this.offset, value, this.littleEndian);
this.offset += 8;
this._updateLastWrittenByte();
return this;
}
/**
* Write `value` as a 64-bit unsigned bigint and move pointer forward by 8
* bytes.
*/
writeBigUint64(value) {
this.ensureAvailable(8);
this._data.setBigUint64(this.offset, value, this.littleEndian);
this.offset += 8;
this._updateLastWrittenByte();
return this;
}
/**
* Write the charCode of `str`'s first character as an 8-bit unsigned integer
* and move pointer forward by 1 byte.
*/
writeChar(str) {
return this.writeUint8(str.charCodeAt(0));
}
/**
* Write the charCodes of all `str`'s characters as 8-bit unsigned integers
* and move pointer forward by `str.length` bytes.
*/
writeChars(str) {
for (let i = 0; i < str.length; i++) {
this.writeUint8(str.charCodeAt(i));
}
return this;
}
/**
* UTF-8 encode and write `str` to the current pointer offset and move pointer
* forward according to the encoded length.
*/
writeUtf8(str) {
return this.writeBytes(encode(str));
}
/**
* Export a Uint8Array view of the internal buffer.
* The view starts at the byte offset and its length
* is calculated to stop at the last written byte or the original length.
*/
toArray() {
return new Uint8Array(this.buffer, this.byteOffset, this.lastWrittenByte);
}
/**
* Update the last written byte offset
* @private
*/
_updateLastWrittenByte() {
if (this.offset > this.lastWrittenByte) {
this.lastWrittenByte = this.offset;
}
}
}
/**
* Create an array with numbers between "from" and "to" of length "length"
*
* @param options - options
* @return - array of distributed numbers between "from" and "to"
*/
function createFromToArray() {
let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
let {
from = 0,
to = 1,
length = 1000,
includeFrom = true,
includeTo = true,
distribution = 'uniform'
} = options;
const array = new Float64Array(length);
let div = length;
if (includeFrom && includeTo) {
div = length - 1;
} else if (!includeFrom && includeTo || includeFrom && !includeTo) {
div = length;
} else if (!includeFrom && !includeTo) {
div = length + 1;
}
let delta = (to - from) / div;
if (distribution === 'uniform') {
if (includeFrom) {
let index = 0;
while (index < length) {
array[index] = from + delta * index;
index++;
}
} else {
let index = 0;
while (index < length) {
array[index] = from + delta * (index + 1);
index++;
}
}
} else if (distribution === 'log') {
let base = (to / from) ** (1 / div);
let firstExponent = Math.log(from) / Math.log(base);
if (includeFrom) {
let index = 0;
while (index < length) {
array[index] = base ** (firstExponent + index);
index++;
}
} else {
let index = 0;
while (index < length) {
array[index] = base ** (firstExponent + index + 1);
index++;
}
}
} else {
throw new Error('Please choose for the distribution either uniform or log. By default the distribution chosen is uniform.');
}
return array;
}
/** Ensures x-values are increasing in magnitude. It reverses y if x was reversed.
*
* * Does not mutate arrays
* * Assumes that X is either increasing or decreasing, not any random order.
* * Expects x and y to be the same length
* @param x - array of x values
* @param y - array of y values
* @returns [x,y] tuple
*/
function ensureIncreasingXValues(x, y) {
const xL = x.length;
if (xL !== 0) {
if (y.length !== xL) {
//wouldn't really make sense for x and y to be !==
throw new RangeError('x and y length must be the same');
}
const firstX = x[0];
const lastX = x[x.length - 1];
if (firstX > lastX) {
//apparently slice(0) faster than slice()
return [x.slice(0).reverse(), y.slice(0).reverse()];
}
}
return [x, y];
}
/**
* Gets the Subfile flags.
*
* @param flag First byte of the subheader.
* @return The parameters.
*/
class SubFlagParameters {
constructor(flag) {
this.changed = (flag & 1) !== 0;
this.noPeakTable = (flag & 8) !== 0;
this.modifiedArithmetic = (flag & 128) !== 0;
}
}
/**
* Parses the subheader (header of the subfile)
*
* @param buffer SPC buffer.
* @return subheader object
*/
class SubHeader {
constructor(buffer) {
this.parameters = new SubFlagParameters(buffer.readUint8());
this.exponentY = buffer.readInt8();
this.indexNumber = buffer.readUint16();
this.startingZ = buffer.readFloat32();
this.endingZ = buffer.readFloat32();
this.noiseValue = buffer.readFloat32();
this.numberPoints = buffer.readUint32();
this.numberCoAddedScans = buffer.readUint32();
this.wAxisValue = buffer.readFloat32();
this.reserved = buffer.readChars(4).replace(/\x00/g, '').trim();
}
}
/**
* Set the X and Y axis (object with labels, values etc.)
* @param x
* @param y
* @param fileHeader
* @return object with x and y as axis.
*/
function setXYAxis(x, y, fileHeader) {
var _a, _b, _c, _d;
const xAxis = /(?