UNPKG

vban

Version:
1,315 lines (1,288 loc) 44.4 kB
import { createRequire as topLevelCreateRequire } from 'module'; const require = topLevelCreateRequire(import.meta.url); // src/packets/VBANAudioPacket/EBitsResolutions.ts var EBitsResolutions = /* @__PURE__ */ ((EBitsResolutions2) => { EBitsResolutions2[EBitsResolutions2["VBAN_DATATYPE_BYTE8"] = 0] = "VBAN_DATATYPE_BYTE8"; EBitsResolutions2[EBitsResolutions2["VBAN_DATATYPE_INT16"] = 1] = "VBAN_DATATYPE_INT16"; EBitsResolutions2[EBitsResolutions2["VBAN_DATATYPE_INT24"] = 2] = "VBAN_DATATYPE_INT24"; EBitsResolutions2[EBitsResolutions2["VBAN_DATATYPE_INT32"] = 3] = "VBAN_DATATYPE_INT32"; EBitsResolutions2[EBitsResolutions2["VBAN_DATATYPE_FLOAT32"] = 4] = "VBAN_DATATYPE_FLOAT32"; EBitsResolutions2[EBitsResolutions2["VBAN_DATATYPE_FLOAT64"] = 5] = "VBAN_DATATYPE_FLOAT64"; EBitsResolutions2[EBitsResolutions2["VBAN_DATATYPE_12BITS"] = 6] = "VBAN_DATATYPE_12BITS"; EBitsResolutions2[EBitsResolutions2["VBAN_DATATYPE_10BITS"] = 7] = "VBAN_DATATYPE_10BITS"; return EBitsResolutions2; })(EBitsResolutions || {}); // src/packets/VBANAudioPacket/ECodecs.ts var ECodecs = /* @__PURE__ */ ((ECodecs2) => { ECodecs2[ECodecs2["VBAN_CODEC_PCM"] = 0] = "VBAN_CODEC_PCM"; ECodecs2[ECodecs2["VBAN_CODEC_VBCA"] = 16] = "VBAN_CODEC_VBCA"; ECodecs2[ECodecs2["VBAN_CODEC_VBCV"] = 32] = "VBAN_CODEC_VBCV"; ECodecs2[ECodecs2["VBAN_CODEC_UNDEFINED_1"] = 48] = "VBAN_CODEC_UNDEFINED_1"; ECodecs2[ECodecs2["VBAN_CODEC_UNDEFINED_2"] = 64] = "VBAN_CODEC_UNDEFINED_2"; ECodecs2[ECodecs2["VBAN_CODEC_UNDEFINED_3"] = 80] = "VBAN_CODEC_UNDEFINED_3"; ECodecs2[ECodecs2["VBAN_CODEC_UNDEFINED_4"] = 96] = "VBAN_CODEC_UNDEFINED_4"; ECodecs2[ECodecs2["VBAN_CODEC_UNDEFINED_5"] = 112] = "VBAN_CODEC_UNDEFINED_5"; ECodecs2[ECodecs2["VBAN_CODEC_UNDEFINED_6"] = 128] = "VBAN_CODEC_UNDEFINED_6"; ECodecs2[ECodecs2["VBAN_CODEC_UNDEFINED_7"] = 144] = "VBAN_CODEC_UNDEFINED_7"; ECodecs2[ECodecs2["VBAN_CODEC_UNDEFINED_8"] = 160] = "VBAN_CODEC_UNDEFINED_8"; ECodecs2[ECodecs2["VBAN_CODEC_UNDEFINED_9"] = 176] = "VBAN_CODEC_UNDEFINED_9"; ECodecs2[ECodecs2["VBAN_CODEC_UNDEFINED_10"] = 192] = "VBAN_CODEC_UNDEFINED_10"; ECodecs2[ECodecs2["VBAN_CODEC_UNDEFINED_11"] = 208] = "VBAN_CODEC_UNDEFINED_11"; ECodecs2[ECodecs2["VBAN_CODEC_UNDEFINED_12"] = 224] = "VBAN_CODEC_UNDEFINED_12"; ECodecs2[ECodecs2["VBAN_CODEC_USER"] = 240] = "VBAN_CODEC_USER"; return ECodecs2; })(ECodecs || {}); // src/packets/VBANPacket.ts import { Buffer as Buffer2 } from "buffer"; // src/packets/ESubProtocol.ts var ESubProtocol = /* @__PURE__ */ ((ESubProtocol2) => { ESubProtocol2[ESubProtocol2["AUDIO"] = 0] = "AUDIO"; ESubProtocol2[ESubProtocol2["SERIAL"] = 32] = "SERIAL"; ESubProtocol2[ESubProtocol2["TEXT"] = 64] = "TEXT"; ESubProtocol2[ESubProtocol2["SERVICE"] = 96] = "SERVICE"; return ESubProtocol2; })(ESubProtocol || {}); // src/commons.ts var PACKET_IDENTIFICATION = "VBAN"; var STREAM_NAME_LENGTH = 16; var BITS_SPEEDS = { 0: 0, 1: 110, 2: 150, 3: 300, 4: 600, 5: 1200, 6: 2400, 7: 4800, 8: 9600, 9: 14400, 10: 19200, 11: 31250, 12: 38400, 13: 57600, 14: 115200, 15: 128e3, 16: 230400, 17: 25e4, 18: 256e3, 19: 460800, 20: 921600, 21: 1e6, 22: 15e5, 23: 2e6, 24: 3e6, 25: 0, 26: 0, 27: 0, 28: 0, 29: 0, 30: 0, 31: 0 }; var EFormatBit = /* @__PURE__ */ ((EFormatBit2) => { EFormatBit2[EFormatBit2["VBAN_DATATYPE_BYTE8"] = 0] = "VBAN_DATATYPE_BYTE8"; return EFormatBit2; })(EFormatBit || {}); var serialStopModes = [ { mode: 0, stop: 1 }, { mode: 1, stop: 1.5 }, { mode: 2, stop: 2 }, { mode: 3, stop: null } ]; function dec2bin(dec) { return ((dec >>> 0).toString(2) || "").padStart(8, "0"); } function bufferToHex(buffer) { if (!Buffer.isBuffer(buffer)) { throw new Error("need to be a buffer"); } let hexString = ""; for (let i = 0; i < buffer.length; i++) { const hex = buffer[i].toString(16).padStart(2, "0"); hexString += hex; if (i < buffer.length - 1) { hexString += " "; } } return hexString; } function prepareStringForPacket(str, maxLength) { return str.slice(0, maxLength).padEnd(maxLength, "\0"); } function cleanPacketString(str) { return str.replace(/\0/g, ""); } var sampleRates = { 0: 6e3, 1: 12e3, 2: 24e3, 3: 48e3, 4: 96e3, 5: 192e3, 6: 384e3, 7: 8e3, 8: 16e3, 9: 32e3, 10: 64e3, 11: 128e3, 12: 256e3, 13: 512e3, 14: 11025, 15: 22050, 16: 44100, 17: 88200, 18: 176400, 19: 352800, 20: 705600, 21: 0, 22: 0, 23: 0, 24: 0, 25: 0, 26: 0, 27: 0, 28: 0, 29: 0, 30: 0, 31: 0 }; // src/packets/VBANSpecs.ts var MAX_FRAME_COUNTER = 4294967295; var VBAN_DATA_MAX_SIZE = 1436; var VBAN_PACKET_MAX_SIZE = 1464; // src/packets/VBANPacket.ts var VBANPacket = class { /** * the subProtocol of this packet * {@link ESubProtocol} */ subProtocol = 0 /* AUDIO */; /** * the name of the current stream . * Voicemeeter rely on it to allow a packet or not */ streamName; /** * Sample Rate for this stream */ sr; /** * frameCounter allow checking if you receive frame in order, and without losing them */ frameCounter; static frameCounters = /* @__PURE__ */ new Map(); /** * Extract headers and data from UDPPacket, each Packet will continue the process */ static prepareFromUDPPacket(headersBuffer, checkSR = true) { const headers = {}; const srsp = headersBuffer.readUInt8(PACKET_IDENTIFICATION.length); const srIndex = srsp & 31; if (checkSR && !sampleRates.hasOwnProperty(srIndex) || sampleRates[srIndex] === void 0) { throw new Error(`unknown sample rate ${srIndex}`); } headers.sr = sampleRates[srIndex]; headers.srIndex = srIndex; headers.part1 = headersBuffer.readUInt8(5); headers.part2 = headersBuffer.readUInt8(6); headers.part3 = headersBuffer.readUInt8(7); headers.streamName = cleanPacketString(headersBuffer.toString("ascii", 8, 8 + STREAM_NAME_LENGTH)); headers.frameCounter = headersBuffer.readUInt32LE(24); return headers; } /** * common constructor */ constructor(headers) { this.sr = headers.sr; this.streamName = headers.streamName; this.frameCounter = headers.frameCounter ?? 1; } /** * Convert a VBANPacket to a UDP packet */ static convertToUDPPacket(headers, data, sampleRate) { let bufferStart = 0; const headersBuffer = Buffer2.alloc(28); bufferStart += PACKET_IDENTIFICATION.length; headersBuffer.fill(PACKET_IDENTIFICATION, bufferStart - PACKET_IDENTIFICATION.length, bufferStart, "ascii"); let rate = sampleRate ?? 0; if (sampleRate === void 0) { rate = Number( Object.entries(sampleRates).find(([, sr]) => sr && sr === headers.sr)?.shift() ); if (!rate) { throw new Error(`fail to find index for sample rate ${headers.sr}`); } } headersBuffer.fill(rate & 31 | headers.sp & 224, bufferStart++); headersBuffer.fill(headers.part1, bufferStart++); headersBuffer.fill(headers.part2, bufferStart++); headersBuffer.fill(headers.part3, bufferStart++); headersBuffer.fill(headers.streamName.padEnd(STREAM_NAME_LENGTH, "\0"), bufferStart, bufferStart + STREAM_NAME_LENGTH, "ascii"); bufferStart += STREAM_NAME_LENGTH; headersBuffer.writeUInt32LE(headers.frameCounter ?? 1, bufferStart); if (data.length > VBAN_DATA_MAX_SIZE) { throw new Error( `VBAN DATA MAX SIZE = ${VBAN_DATA_MAX_SIZE} ! You try to send a packet with ${data.length} bytes . You can use the exported var VBAN_DATA_MAX_SIZE to split your datas in packets` ); } return Buffer2.concat([headersBuffer, data.subarray(0, VBAN_DATA_MAX_SIZE)]); } /** * EXPERIMENTAL - DO NOT USE * * @experimental */ static checkFrameCounter(headers) { const frameCounterKey = "str"; const frameCounter = this.frameCounters.get(frameCounterKey); if (!headers.frameCounter) { return; } if (frameCounter && frameCounter > headers.frameCounter && headers.frameCounter > 0) { console.log("frameCounter error"); } else if (frameCounter && headers.frameCounter > 0) { console.log("frame counter", "old", frameCounter, "new", headers.frameCounter, "diff", headers.frameCounter - frameCounter); } else if (headers.frameCounter === 0) { console.log("frame 0"); } this.frameCounters.set(frameCounterKey, headers.frameCounter); } }; // src/packets/VBANAudioPacket/VBANAudioPacket.ts var VBANAudioPacket = class _VBANAudioPacket extends VBANPacket { /** * {@link VBANAudioPacket.subProtocol} */ static subProtocol = 0 /* AUDIO */; subProtocol = _VBANAudioPacket.subProtocol; /** * Number of sample is given by an 8 bits unsigned integer (0 – 255) where 0 means 1 sample and * 255 means 256 samples */ nbSample; /** * Number of channel is given by an 8 bits unsigned integer (0 – 255) where 0 means 1 channel * and 255 means 256 channels. */ nbChannel; /** * Data type used to store audio sample in the packet * Use it to select the correct bitResolution {@link VBANAudioPacket.bitResolutions}, or directly use {@link VBANAudioPacket.bitResolutionObject} */ bitResolution; /** * the bit resolution selected by the id in {@link VBANAudioPacket.bitResolution} */ bitResolutionObject; /** * Audio codec used */ codec; /** * current audio */ data; constructor(headers, data) { super({ ...headers, sp: _VBANAudioPacket.subProtocol }); this.nbSample = headers.nbSample; this.nbChannel = headers.nbChannel; this.bitResolution = headers.bitResolution; if (!_VBANAudioPacket.bitResolutions[headers.bitResolution]) { throw new Error(`fail to found bitResolution with ID ${headers.bitResolution}`); } this.bitResolutionObject = _VBANAudioPacket.bitResolutions[headers.bitResolution]; this.codec = headers.codec; this.data = data; } static toUDPPacket(packet) { return this.convertToUDPPacket( { streamName: packet.streamName, sp: packet.subProtocol, sr: packet.sr, frameCounter: packet.frameCounter, part1: packet.nbSample - 1, part2: packet.nbChannel - 1, part3: packet.bitResolution & 7 | packet.codec & 240 }, packet.data ); } static fromUDPPacket(headersBuffer, dataBuffer) { const headers = this.prepareFromUDPPacket(headersBuffer); const nbSample = headers.part1 + 1; const nbChannel = headers.part2 + 1; const dataFormatAndCodec = headers.part3; const bitResolution = dataFormatAndCodec & 7; if (!EBitsResolutions[bitResolution]) { throw new Error(`unknown bit resolution ${bitResolution}`); } const codec = dataFormatAndCodec & 240; if (!ECodecs[codec]) { throw new Error(`unknown codec ${codec}`); } return new _VBANAudioPacket( { ...headers, nbSample, nbChannel, bitResolution, codec }, dataBuffer ); } static bitResolutions = { 0: { bitDepth: 8, signed: false, float: false }, 1: { bitDepth: 16, signed: true, float: false }, 2: { bitDepth: 24, signed: true, float: false }, 3: { bitDepth: 32, signed: true, float: false }, 4: { bitDepth: 32, signed: true, float: true }, 5: { bitDepth: 64, signed: true, float: true }, 6: { bitDepth: 12, signed: true, float: false }, 7: { bitDepth: 10, signed: true, float: false } }; }; // src/packets/VBANSerialPacket/ESerialStreamType.ts var ESerialStreamType = /* @__PURE__ */ ((ESerialStreamType2) => { ESerialStreamType2[ESerialStreamType2["VBAN_SERIAL_GENERIC"] = 0] = "VBAN_SERIAL_GENERIC"; ESerialStreamType2[ESerialStreamType2["VBAN_SERIAL_MIDI"] = 16] = "VBAN_SERIAL_MIDI"; ESerialStreamType2[ESerialStreamType2["VBAN_SERIAL_UNDEFINED_2"] = 32] = "VBAN_SERIAL_UNDEFINED_2"; ESerialStreamType2[ESerialStreamType2["VBAN_SERIAL_UNDEFINED_3"] = 48] = "VBAN_SERIAL_UNDEFINED_3"; ESerialStreamType2[ESerialStreamType2["VBAN_SERIAL_UNDEFINED_4"] = 64] = "VBAN_SERIAL_UNDEFINED_4"; ESerialStreamType2[ESerialStreamType2["VBAN_SERIAL_UNDEFINED_5"] = 80] = "VBAN_SERIAL_UNDEFINED_5"; ESerialStreamType2[ESerialStreamType2["VBAN_SERIAL_UNDEFINED_6"] = 96] = "VBAN_SERIAL_UNDEFINED_6"; ESerialStreamType2[ESerialStreamType2["VBAN_SERIAL_UNDEFINED_7"] = 112] = "VBAN_SERIAL_UNDEFINED_7"; ESerialStreamType2[ESerialStreamType2["VBAN_SERIAL_UNDEFINED_8"] = 128] = "VBAN_SERIAL_UNDEFINED_8"; ESerialStreamType2[ESerialStreamType2["VBAN_SERIAL_UNDEFINED_9"] = 144] = "VBAN_SERIAL_UNDEFINED_9"; ESerialStreamType2[ESerialStreamType2["VBAN_SERIAL_UNDEFINED_10"] = 160] = "VBAN_SERIAL_UNDEFINED_10"; ESerialStreamType2[ESerialStreamType2["VBAN_SERIAL_UNDEFINED_11"] = 176] = "VBAN_SERIAL_UNDEFINED_11"; ESerialStreamType2[ESerialStreamType2["VBAN_SERIAL_UNDEFINED_12"] = 192] = "VBAN_SERIAL_UNDEFINED_12"; ESerialStreamType2[ESerialStreamType2["VBAN_SERIAL_UNDEFINED_13"] = 208] = "VBAN_SERIAL_UNDEFINED_13"; ESerialStreamType2[ESerialStreamType2["VBAN_SERIAL_UNDEFINED_14"] = 224] = "VBAN_SERIAL_UNDEFINED_14"; ESerialStreamType2[ESerialStreamType2["VBAN_SERIAL_USER"] = 240] = "VBAN_SERIAL_USER"; return ESerialStreamType2; })(ESerialStreamType || {}); // src/packets/VBANSerialPacket/VBANSerialPacket.ts var VBANSerialPacket = class _VBANSerialPacket extends VBANPacket { /** * {@link VBANSerialPacket.subProtocol} */ static subProtocol = 32 /* SERIAL */; subProtocol = _VBANSerialPacket.subProtocol; /** * This field is used to give possible information on COM port and serial transmission mode related * to a Hardware COM port. This is made to possibly emulate COM to COM port connections and * let the receiver configure the physical COM port in the right mode. */ bitMode; /** * Can be used to define a sub channel (sub serial link) and then manage up to 256 different * serial virtual pipes (ZERO by default). */ channelsIdents; /** * SR / bps : Bit rate is given in bps for information only. But it can be useful if serial data come from or go to * a particular COM port. Set to ZERO if there is no particular bit rate. */ bps; /** * not used . Replaced by {@link VBANSerialPacket.bps} */ sr = 0; /** * Data type used to store data in the packet (ZERO per default). The index is stored on 3 first bits. * Bit 3 must be ZERO. Bits 4 to 7 gives additional mode */ formatBit; /** * type of stream . MIDI or SERIAL ... But in practice, only serial is used (MIDI is serial) */ streamType; data; constructor(headers, data) { super({ ...headers, sp: _VBANSerialPacket.subProtocol, sr: 0 }); this.bitMode = headers.bitMode; this.channelsIdents = headers.channelsIdents; this.bps = headers.bps; this.formatBit = headers.formatBit; this.streamType = headers.streamType; this.data = data; this.sr = 0; } static toUDPPacket(packet) { let part1 = 0; const mode = serialStopModes.find((m) => m.stop === packet.bitMode.stop)?.mode; if (mode === void 0) { throw new Error(`fail to found mode for stop ${packet.bitMode.stop}`); } part1 |= mode & 3; if (packet.bitMode.start) { part1 |= 4; } if (packet.bitMode.parity) { part1 |= 8; } if (packet.bitMode.multipart) { part1 |= 128; } const bpsId = Number( Object.entries(BITS_SPEEDS).find(([, bps]) => bps && bps === packet.bps)?.shift() ) || 0; return this.convertToUDPPacket( { streamName: packet.streamName, sp: packet.subProtocol, sr: packet.bps, frameCounter: packet.frameCounter, part1, part2: packet.channelsIdents, part3: packet.formatBit & 7 | packet.streamType & 240 }, packet.data, bpsId ); } static fromUDPPacket(headersBuffer, dataBuffer) { const headers = this.prepareFromUDPPacket(headersBuffer, false); if (headers.srIndex === void 0 || BITS_SPEEDS[headers.srIndex] === void 0) { throw new Error(`unknown bits speed ${headers.srIndex}`); } const bps = BITS_SPEEDS[headers.srIndex]; const bitModeRaw = headers.part1; const stopMode = bitModeRaw & 3; const stop = serialStopModes.find((m) => m.mode === stopMode)?.stop ?? null; const start = (bitModeRaw & 4) === 4; const parity = (bitModeRaw & 8) === 8; const multipart = (bitModeRaw & 128) === 128; const bitMode = { stop, start, parity, multipart }; const channelsIdents = headers.part2; const dataFormat = headers.part3; const formatBit = dataFormat & 7; if (!EFormatBit[formatBit]) { throw new Error(`unknown format bit ${formatBit}`); } const streamType = dataFormat & 240; if (!ESerialStreamType[streamType]) { throw new Error(`unknown stream type ${streamType}`); } return new _VBANSerialPacket( { ...headers, bps, bitMode, channelsIdents, formatBit, streamType }, dataBuffer ); } }; // src/packets/VBANServicePacket/EServiceFunction.ts var EServiceFunction = /* @__PURE__ */ ((EServiceFunction2) => { EServiceFunction2[EServiceFunction2["PING0"] = 0] = "PING0"; EServiceFunction2[EServiceFunction2["REPLY"] = 128] = "REPLY"; return EServiceFunction2; })(EServiceFunction || {}); // src/packets/VBANServicePacket/EServicePINGApplicationType.ts var EServicePINGApplicationType = /* @__PURE__ */ ((EServicePINGApplicationType2) => { EServicePINGApplicationType2[EServicePINGApplicationType2["UNKNOWN"] = 0] = "UNKNOWN"; EServicePINGApplicationType2[EServicePINGApplicationType2["RECEPTOR"] = 1] = "RECEPTOR"; EServicePINGApplicationType2[EServicePINGApplicationType2["TRANSMITTER"] = 2] = "TRANSMITTER"; EServicePINGApplicationType2[EServicePINGApplicationType2["RECEPTORSPOT"] = 4] = "RECEPTORSPOT"; EServicePINGApplicationType2[EServicePINGApplicationType2["TRANSMITTERSPOT"] = 8] = "TRANSMITTERSPOT"; EServicePINGApplicationType2[EServicePINGApplicationType2["VIRTUALDEVICE"] = 16] = "VIRTUALDEVICE"; EServicePINGApplicationType2[EServicePINGApplicationType2["VIRTUALMIXER"] = 32] = "VIRTUALMIXER"; EServicePINGApplicationType2[EServicePINGApplicationType2["MATRIX"] = 64] = "MATRIX"; EServicePINGApplicationType2[EServicePINGApplicationType2["DAW"] = 128] = "DAW"; EServicePINGApplicationType2[EServicePINGApplicationType2["SERVER"] = 16777216] = "SERVER"; return EServicePINGApplicationType2; })(EServicePINGApplicationType || {}); // src/packets/VBANServicePacket/EServicePINGFeatures.ts var EServicePINGFeatures = /* @__PURE__ */ ((EServicePINGFeatures2) => { EServicePINGFeatures2[EServicePINGFeatures2["AUDIO"] = 1] = "AUDIO"; EServicePINGFeatures2[EServicePINGFeatures2["AOIP"] = 2] = "AOIP"; EServicePINGFeatures2[EServicePINGFeatures2["VOIP"] = 4] = "VOIP"; EServicePINGFeatures2[EServicePINGFeatures2["SERIAL"] = 256] = "SERIAL"; EServicePINGFeatures2[EServicePINGFeatures2["MIDI"] = 768] = "MIDI"; EServicePINGFeatures2[EServicePINGFeatures2["FRAME"] = 4096] = "FRAME"; EServicePINGFeatures2[EServicePINGFeatures2["TXT"] = 65536] = "TXT"; return EServicePINGFeatures2; })(EServicePINGFeatures || {}); // src/packets/VBANServicePacket/EServiceType.ts var EServiceType = /* @__PURE__ */ ((EServiceType2) => { EServiceType2[EServiceType2["IDENTIFICATION"] = 0] = "IDENTIFICATION"; EServiceType2[EServiceType2["CHATUTF8"] = 1] = "CHATUTF8"; EServiceType2[EServiceType2["RTPACKETREGISTER"] = 32] = "RTPACKETREGISTER"; EServiceType2[EServiceType2["RTPACKET"] = 33] = "RTPACKET"; return EServiceType2; })(EServiceType || {}); // src/packets/VBANServicePacket/VBANServicePacket.ts import { Buffer as Buffer3 } from "buffer"; var VBANServicePacket = class _VBANServicePacket extends VBANPacket { /** * {@link VBANServicePacket.subProtocol} */ static subProtocol = 96 /* SERVICE */; subProtocol = _VBANServicePacket.subProtocol; /** * Sub Type of the service packet * {@link EServiceType} */ service; /** * current function for this function */ serviceFunction; /** * answer is a reply to another request */ isReply = false; data; /** * not used . */ sr = 0; constructor(headers) { super({ ...headers, sp: _VBANServicePacket.subProtocol, sr: 0 }); this.service = headers.service; this.serviceFunction = headers.serviceFunction; this.isReply = headers.isReply ?? false; this.sr = 0; } toUDPPacket() { return _VBANServicePacket.toUDPPacket(this); } static toUDPPacket(packet) { return _VBANServicePacket.convertToUDPPacket( { streamName: packet.streamName, sp: packet.subProtocol, sr: packet.sr, frameCounter: packet.frameCounter, part1: (packet.isReply ? 128 : 0) & 128 | packet.serviceFunction & 127, part2: packet.service, part3: 0 }, Buffer3.from(""), packet.sr ); } }; // src/packets/VBANServicePacket/VBANChatPacket.ts import { Buffer as Buffer4 } from "buffer"; var VBANChatPacket = class _VBANChatPacket extends VBANServicePacket { data; constructor(headers, data) { super(headers); this.data = data; } static fromUDPPacket(headers, dataBuffer) { const fn = headers.part1; const serviceFunction = fn & 127; const isReply = (fn & 128) >= 1; return new _VBANChatPacket( { ...headers, service: 1 /* CHATUTF8 */, serviceFunction, isReply }, dataBuffer.toString() ); } toUDPPacket() { return _VBANChatPacket.toUDPPacket(this); } static toUDPPacket(packet) { const dataBuffer = Buffer4.alloc(676); dataBuffer.write(prepareStringForPacket(packet.data, 676), 0, "utf8"); return this.convertToUDPPacket( { streamName: packet.streamName, sp: packet.subProtocol, sr: packet.sr, frameCounter: packet.frameCounter, part1: (packet.isReply ? 128 : 0) & 128 | packet.serviceFunction & 127, part2: packet.service, part3: 0 }, dataBuffer, packet.sr ); } }; // src/packets/VBANServicePacket/VBANPingPacket.ts import { Buffer as Buffer5 } from "buffer"; var VBANPingPacket = class _VBANPingPacket extends VBANServicePacket { data; constructor(headers, data) { super(headers); this.data = data; } static fromUDPPacket(headers, dataBuffer) { const fn = headers.part1; const serviceFunction = fn & 127; const isReply = (fn & 128) >= 1; let currentByte = 0; const getXNextBytes = (size) => { const b = dataBuffer.subarray(currentByte, currentByte + size); currentByte += size; return b; }; const bitType = getXNextBytes(4).readUInt32LE(); const applicationType = EServicePINGApplicationType[bitType] ? bitType : 0 /* UNKNOWN */; const bitFeature = getXNextBytes(4).readUInt32LE(); const features = Object.entries(EServicePINGFeatures).filter(([k]) => Number.isNaN(Number(k))).filter(([, v]) => bitFeature & v).map(([, v]) => v); const bitFeatureEx = getXNextBytes(4).readUInt32LE(); const PreferredRate = getXNextBytes(4).readUInt32LE(); const minRate = getXNextBytes(4).readUInt32LE(); const maxRate = getXNextBytes(4).readUInt32LE(); const colorRGB = getXNextBytes(4).readUInt32LE(); const color = { blue: colorRGB & 255, green: colorRGB >> 8 & 255, red: colorRGB >> 16 & 255 }; const nVersion = getXNextBytes(4).readUInt32LE(); const GPSPosition = cleanPacketString(getXNextBytes(8).toString("ascii")); const userPosition = cleanPacketString(getXNextBytes(8).toString("ascii")); const langCode = cleanPacketString(getXNextBytes(8).toString("ascii")); const reservedASCII = cleanPacketString(getXNextBytes(8).toString("ascii")); const reservedEx = cleanPacketString(getXNextBytes(64).toString("ascii")); const reservedEx2 = cleanPacketString(getXNextBytes(36).toString("ascii")); const deviceName = cleanPacketString(getXNextBytes(64).toString("ascii")); const manufacturerName = cleanPacketString(getXNextBytes(64).toString("ascii")); const applicationName = cleanPacketString(getXNextBytes(64).toString("ascii")); const hostnameASCII = cleanPacketString(getXNextBytes(64).toString("ascii")); const userName = cleanPacketString(getXNextBytes(128).toString("utf8")); const userComment = cleanPacketString(getXNextBytes(128).toString("utf8")); const data = { applicationType, features, bitFeatureEx, PreferredRate, minRate, maxRate, color, nVersion, GPSPosition, userPosition, langCode, reservedASCII, reservedEx, reservedEx2, deviceName, manufacturerName, applicationName, hostname: hostnameASCII, userName, userComment }; return new _VBANPingPacket( { ...headers, service: 0 /* IDENTIFICATION */, serviceFunction, isReply }, data ); } toUDPPacket() { return _VBANPingPacket.toUDPPacket(this); } static toUDPPacket(packet) { const dataBuffer = Buffer5.alloc(676); let offset = 0; offset = dataBuffer.writeUInt32LE(packet.data.applicationType, offset); let features = 0; packet.data.features.forEach((feature) => { if (EServicePINGFeatures[feature]) { features = features | feature; } }); offset = dataBuffer.writeUInt32LE(features, offset); offset = dataBuffer.writeUInt32LE(packet.data.bitFeatureEx, offset); offset = dataBuffer.writeUInt32LE(packet.data.PreferredRate, offset); offset = dataBuffer.writeUInt32LE(packet.data.minRate, offset); offset = dataBuffer.writeUInt32LE(packet.data.maxRate, offset); const { red, green, blue } = packet.data.color; offset = dataBuffer.writeUInt32LE((red & 255) << 16 | (green & 255) << 8 | blue & 255, offset); offset = dataBuffer.writeUInt32LE(packet.data.nVersion, offset); offset += dataBuffer.write(prepareStringForPacket(packet.data.GPSPosition, 8), offset, "ascii"); offset += dataBuffer.write(prepareStringForPacket(packet.data.userPosition, 8), offset, "ascii"); offset += dataBuffer.write(prepareStringForPacket(packet.data.langCode, 8), offset, "ascii"); offset += dataBuffer.write(prepareStringForPacket(packet.data.reservedASCII, 8), offset, "ascii"); offset += dataBuffer.write(prepareStringForPacket(packet.data.reservedEx, 64), offset, "ascii"); offset += dataBuffer.write(prepareStringForPacket(packet.data.reservedEx2, 36), offset, "ascii"); offset += dataBuffer.write(prepareStringForPacket(packet.data.deviceName, 64), offset, "ascii"); offset += dataBuffer.write(prepareStringForPacket(packet.data.manufacturerName, 64), offset, "ascii"); offset += dataBuffer.write(prepareStringForPacket(packet.data.applicationName, 64), offset, "ascii"); offset += dataBuffer.write(prepareStringForPacket(packet.data.hostname, 64), offset, "ascii"); offset += dataBuffer.write(prepareStringForPacket(packet.data.userName, 128), offset, "utf8"); dataBuffer.write(prepareStringForPacket(packet.data.userComment, 128), offset, "utf8"); return this.convertToUDPPacket( { streamName: packet.streamName, sp: packet.subProtocol, sr: packet.sr, frameCounter: packet.frameCounter, part1: (packet.isReply ? 128 : 0) & 128 | packet.serviceFunction & 127, part2: packet.service, part3: 0 }, dataBuffer, packet.sr ); } }; // src/packets/VBANServicePacket/VBANRealTimePacket.ts import { Buffer as Buffer6 } from "buffer"; var VBANRealTimePacket = class _VBANRealTimePacket extends VBANServicePacket { /** * not clear about the content of this buffer */ data; constructor(headers, data) { super(headers); this.data = data; } static fromUDPPacket(headers, dataBuffer) { const fn = headers.part1; const serviceFunction = fn & 127; const isReply = (fn & 128) >= 1; return new _VBANRealTimePacket( { ...headers, service: 33 /* RTPACKET */, serviceFunction, isReply }, dataBuffer ); } toUDPPacket() { return _VBANRealTimePacket.toUDPPacket(this); } static toUDPPacket(packet) { return this.convertToUDPPacket( { streamName: packet.streamName, sp: packet.subProtocol, sr: packet.sr, frameCounter: packet.frameCounter, part1: (packet.isReply ? 128 : 0) & 128 | packet.serviceFunction & 127, part2: packet.service, part3: 0 }, Buffer6.from(""), packet.sr ); } }; // src/packets/VBANServicePacket/VBANRealTimeRegisterAnswerPacket.ts import { Buffer as Buffer7 } from "buffer"; var ERegistrationAnswer = /* @__PURE__ */ ((ERegistrationAnswer2) => { ERegistrationAnswer2[ERegistrationAnswer2["NO_RT_PACKET_SERVICE"] = 0] = "NO_RT_PACKET_SERVICE"; ERegistrationAnswer2[ERegistrationAnswer2["RT_PACKET_SERVICE_REGISTERED"] = 1] = "RT_PACKET_SERVICE_REGISTERED"; ERegistrationAnswer2[ERegistrationAnswer2["RT_PACKET_SERVICE_BUSY"] = 2] = "RT_PACKET_SERVICE_BUSY"; return ERegistrationAnswer2; })(ERegistrationAnswer || {}); var VBANRealTimeRegisterAnswerPacket = class _VBANRealTimeRegisterAnswerPacket extends VBANServicePacket { data; constructor(headers, data) { super(headers); this.data = data; } static fromUDPPacket(headers) { const fn = headers.part1; const serviceFunction = fn & 127; const isReply = (fn & 128) >= 1; return new _VBANRealTimeRegisterAnswerPacket( { ...headers, service: 32 /* RTPACKETREGISTER */, serviceFunction, isReply }, { answer: headers.part3 } ); } toUDPPacket() { return _VBANRealTimeRegisterAnswerPacket.toUDPPacket(this); } static toUDPPacket(packet) { return this.convertToUDPPacket( { streamName: packet.streamName, sp: packet.subProtocol, sr: packet.sr, frameCounter: packet.frameCounter, part1: (packet.isReply ? 128 : 0) & 128 | packet.serviceFunction & 127, part2: packet.service, part3: packet.data.answer }, Buffer7.from(""), packet.sr ); } }; // src/packets/VBANServicePacket/VBANRealTimeRegisterPacket.ts import { Buffer as Buffer8 } from "buffer"; var VBANRealTimeRegisterPacket = class _VBANRealTimeRegisterPacket extends VBANServicePacket { data; constructor(headers, data) { super(headers); this.data = data; } static fromUDPPacket(headers) { const fn = headers.part1; const serviceFunction = fn & 127; const isReply = (fn & 128) >= 1; if (isReply) { return VBANRealTimeRegisterAnswerPacket.fromUDPPacket(headers); } return new _VBANRealTimeRegisterPacket( { ...headers, service: 32 /* RTPACKETREGISTER */, serviceFunction, isReply }, { timeout: headers.part3 } ); } toUDPPacket() { return _VBANRealTimeRegisterPacket.toUDPPacket(this); } static toUDPPacket(packet) { if (packet.data.timeout > 255 || packet.data.timeout < 0) { throw new Error("timeout need to be between 0 and 255"); } return this.convertToUDPPacket( { streamName: packet.streamName, sp: packet.subProtocol, sr: packet.sr, frameCounter: packet.frameCounter, part1: (packet.isReply ? 128 : 0) & 128 | packet.serviceFunction & 127, part2: packet.service, part3: packet.data.timeout }, Buffer8.from(""), packet.sr ); } }; // src/packets/VBANServicePacket/VBANServicePacketFactory.ts var VBANServicePacketFactory = class { static fromUDPPacket(headersBuffer, dataBuffer) { const headers = VBANPacket.prepareFromUDPPacket(headersBuffer); const service = headers.part2; return this.getConstructor(service).fromUDPPacket(headers, dataBuffer); } static getConstructor(protocol) { switch (protocol) { case 0 /* IDENTIFICATION */: return VBANPingPacket; case 1 /* CHATUTF8 */: return VBANChatPacket; case 33 /* RTPACKET */: return VBANRealTimePacket; case 32 /* RTPACKETREGISTER */: return VBANRealTimeRegisterPacket; default: throw new Error(`unknown protocol ${protocol}`); } } static toUDPPacket(packet) { return packet.toUDPPacket(); } }; // src/packets/VBANTXTPacket/ETextEncoding.ts var ETextEncoding = /* @__PURE__ */ ((ETextEncoding2) => { ETextEncoding2[ETextEncoding2["VBAN_TXT_ASCII"] = 0] = "VBAN_TXT_ASCII"; ETextEncoding2[ETextEncoding2["VBAN_TXT_UTF8"] = 16] = "VBAN_TXT_UTF8"; ETextEncoding2[ETextEncoding2["VBAN_TXT_WCHAR"] = 32] = "VBAN_TXT_WCHAR"; ETextEncoding2[ETextEncoding2["VBAN_SERIAL_UNDEFINED_3"] = 48] = "VBAN_SERIAL_UNDEFINED_3"; ETextEncoding2[ETextEncoding2["VBAN_SERIAL_UNDEFINED_4"] = 64] = "VBAN_SERIAL_UNDEFINED_4"; ETextEncoding2[ETextEncoding2["VBAN_SERIAL_UNDEFINED_5"] = 80] = "VBAN_SERIAL_UNDEFINED_5"; ETextEncoding2[ETextEncoding2["VBAN_SERIAL_UNDEFINED_6"] = 96] = "VBAN_SERIAL_UNDEFINED_6"; ETextEncoding2[ETextEncoding2["VBAN_SERIAL_UNDEFINED_7"] = 112] = "VBAN_SERIAL_UNDEFINED_7"; ETextEncoding2[ETextEncoding2["VBAN_SERIAL_UNDEFINED_8"] = 128] = "VBAN_SERIAL_UNDEFINED_8"; ETextEncoding2[ETextEncoding2["VBAN_SERIAL_UNDEFINED_9"] = 144] = "VBAN_SERIAL_UNDEFINED_9"; ETextEncoding2[ETextEncoding2["VBAN_SERIAL_UNDEFINED_10"] = 160] = "VBAN_SERIAL_UNDEFINED_10"; ETextEncoding2[ETextEncoding2["VBAN_SERIAL_UNDEFINED_11"] = 176] = "VBAN_SERIAL_UNDEFINED_11"; ETextEncoding2[ETextEncoding2["VBAN_SERIAL_UNDEFINED_12"] = 192] = "VBAN_SERIAL_UNDEFINED_12"; ETextEncoding2[ETextEncoding2["VBAN_SERIAL_UNDEFINED_13"] = 208] = "VBAN_SERIAL_UNDEFINED_13"; ETextEncoding2[ETextEncoding2["VBAN_SERIAL_UNDEFINED_14"] = 224] = "VBAN_SERIAL_UNDEFINED_14"; ETextEncoding2[ETextEncoding2["VBAN_SERIAL_USER"] = 240] = "VBAN_SERIAL_USER"; return ETextEncoding2; })(ETextEncoding || {}); // src/packets/VBANTXTPacket/VBANTEXTPacket.ts import { Buffer as Buffer9 } from "buffer"; var VBANTEXTPacket = class _VBANTEXTPacket extends VBANPacket { /** * {@link VBANTEXTPacket.subProtocol} */ static subProtocol = 64 /* TEXT */; subProtocol = _VBANTEXTPacket.subProtocol; /** * Bit rate is given in bps for information only. But it can be used internally to limit the bandwidth of * the stream and for example gives more priority to audio stream or RT MIDI stream. It can be set * to ZERO if there is no particular bit rate. */ bps; /** * Can be used to define a sub channel (sub text channel) and then manage up to 256 different * virtual pipes (ZERO by default). */ channelsIdents; /** * Data type used to store data in the packet (ZERO/VBAN_DATATYPE_BYTE8 per default). */ formatBit; /** * Text format */ encoding; /** * not used . Replaced by {@link VBANTEXTPacket.bps} */ sr; /** * if data can be decoded, it will be decoded in text */ text; /** * you can access the raw dataBuffer (if available) to try another decoding */ dataBuffer; constructor(headers, txt = "", dataBuffer) { super({ ...headers, sp: _VBANTEXTPacket.subProtocol, sr: 0 }); this.bps = headers.bps ?? BITS_SPEEDS[0]; this.channelsIdents = headers.channelsIdents ?? 0; this.formatBit = headers.formatBit ?? 0 /* VBAN_DATATYPE_BYTE8 */; this.encoding = headers.encoding; this.text = txt; this.dataBuffer = dataBuffer; this.sr = 0; } static toUDPPacket(packet) { const data = packet.text ? Buffer9.from(packet.text, _VBANTEXTPacket.getEncoding(packet.encoding)) : packet.dataBuffer ?? Buffer9.from(""); const bpsId = Number( Object.entries(BITS_SPEEDS).find(([, bps]) => bps && bps === packet.bps)?.shift() ) || 0; return this.convertToUDPPacket( { streamName: packet.streamName, sp: packet.subProtocol, sr: packet.bps, frameCounter: packet.frameCounter, part1: 0, part2: packet.channelsIdents, part3: packet.formatBit & 7 | packet.encoding & 240 }, data, bpsId ); } static fromUDPPacket(headersBuffer, dataBuffer) { const headers = this.prepareFromUDPPacket(headersBuffer); if (headers.srIndex === void 0 || BITS_SPEEDS[headers.srIndex] === void 0) { throw new Error(`unknown bits speed ${headers.srIndex}`); } const bps = BITS_SPEEDS[headers.srIndex]; const channelsIdents = headers.part2; const dataFormat = headers.part3; const formatBit = dataFormat & 7; if (!EFormatBit[formatBit]) { throw new Error(`unknown format bit ${formatBit}`); } const encoding = dataFormat & 240; if (!ETextEncoding[encoding]) { throw new Error(`unknown text stream type ${encoding}`); } const textEncoding = _VBANTEXTPacket.getEncoding(encoding); let text; if (textEncoding) { text = dataBuffer.toString(textEncoding); } return new _VBANTEXTPacket( { ...headers, bps, channelsIdents, formatBit, encoding }, text, dataBuffer ); } static getEncoding(streamType) { let textEncoding; if (streamType === 16 /* VBAN_TXT_UTF8 */) { textEncoding = "utf8"; } else if (streamType === 32 /* VBAN_TXT_WCHAR */) { textEncoding = "utf16le"; } else if (streamType === 0 /* VBAN_TXT_ASCII */) { textEncoding = "ascii"; } return textEncoding; } }; // src/VBANProtocolFactory.ts var VBANProtocolFactory = class _VBANProtocolFactory { static processPacket(packet) { const headerBuffer = packet.subarray(0, 28); const dataBuffer = packet.subarray(28); if (headerBuffer.toString("ascii", 0, PACKET_IDENTIFICATION.length) !== PACKET_IDENTIFICATION) { throw new Error("Invalid Header"); } const header1 = headerBuffer.readUInt8(PACKET_IDENTIFICATION.length); const subProtocol = header1 & 224; return _VBANProtocolFactory.getConstructor(subProtocol).fromUDPPacket(headerBuffer, dataBuffer); } static getConstructor(protocol) { switch (protocol) { case 0 /* AUDIO */: return VBANAudioPacket; case 32 /* SERIAL */: return VBANSerialPacket; case 64 /* TEXT */: return VBANTEXTPacket; case 96 /* SERVICE */: return VBANServicePacketFactory; default: throw new Error(`unknown protocol ${protocol}`); } } static toUDPBuffer(packet) { switch (packet.subProtocol) { case 0 /* AUDIO */: return VBANAudioPacket.toUDPPacket(packet); case 32 /* SERIAL */: return VBANSerialPacket.toUDPPacket(packet); case 64 /* TEXT */: return VBANTEXTPacket.toUDPPacket(packet); case 96 /* SERVICE */: return VBANServicePacketFactory.toUDPPacket(packet); default: throw new Error("unknown packet instance"); } } }; // src/VBANServer.ts import dgram from "node:dgram"; import { EventEmitter } from "events"; import { promisify } from "node:util"; import os from "node:os"; var VBANServer = class extends EventEmitter { UDPServer; options; frameCounter = /* @__PURE__ */ new Map(); isListening = false; constructor(options) { super(); this.UDPServer = dgram.createSocket("udp4"); this.options = options || {}; if (this.options.autoReplyToPing === void 0) { this.options.autoReplyToPing = true; } this.UDPServer.on("listening", (...args) => { this.emit("listening", ...args); this.isListening = true; }); this.UDPServer.on("close", () => { this.isListening = false; }); this.UDPServer.on("error", (...args) => { this.emit("error", ...args); }); this.UDPServer.on("message", this.messageHandler.bind(this)); } address() { return this.UDPServer.address(); } bind(...args) { return new Promise((resolve) => { this.UDPServer.bind(...args, resolve); }); } getFrameCounter(protocol) { let frameCounter = this.frameCounter.get(protocol) ?? 0; if (frameCounter >= MAX_FRAME_COUNTER) { frameCounter = 0; } this.frameCounter.set(protocol, ++frameCounter); return frameCounter; } send(packet, port, address) { return new Promise((resolve, reject) => { packet.frameCounter = this.getFrameCounter(packet.subProtocol); this.UDPServer.send(VBANProtocolFactory.toUDPBuffer(packet), port, address, (error) => { if (error) { reject(error); return; } resolve(); }); }); } sendPing(receiver, isReply = false) { const frameCounter = this.getFrameCounter(96 /* SERVICE */); const defaultApp = { applicationName: "Test application", manufacturerName: "Anonymous", applicationType: 16777216 /* SERVER */, features: [1 /* AUDIO */, 768 /* MIDI */, 65536 /* TXT */, 256 /* SERIAL */], bitFeatureEx: 0, PreferredRate: 0, minRate: 6e3, maxRate: 705600, color: { blue: 0, green: 128, red: 128 }, nVersion: 12345, GPSPosition: "", userPosition: "", langCode: "fr-fr", reservedASCII: "", reservedEx: "", reservedEx2: "", deviceName: "NodeJs Server", userName: "", userComment: "" }; const application = Object.assign(defaultApp, this.options.application); const answerPacket = new VBANPingPacket( { streamName: "VBAN Service", service: 0 /* IDENTIFICATION */, serviceFunction: 0 /* PING0 */, frameCounter, isReply }, { applicationName: application.applicationName, manufacturerName: application.manufacturerName, applicationType: application.applicationType, features: application.features, bitFeatureEx: application.bitFeatureEx, PreferredRate: application.PreferredRate, minRate: application.minRate, maxRate: application.maxRate, color: application.color, nVersion: application.nVersion, GPSPosition: application.GPSPosition, userPosition: application.userPosition, langCode: application.langCode, reservedASCII: application.reservedASCII, reservedEx: application.reservedEx, reservedEx2: application.reservedEx2, deviceName: application.deviceName, hostname: application.hostname ?? os.hostname(), userName: application.userName, userComment: application.userComment } ); return this.send(answerPacket, receiver.port, receiver.address); } messageHandler = async (msg, sender) => { if (this.options.beforeProcessPacket) { if (!this.options.beforeProcessPacket(msg, sender)) { return; } } const packet = VBANProtocolFactory.processPacket(msg); if (this.options.autoReplyToPing && packet instanceof VBANPingPacket && !packet.isReply) { await this.sendPing(sender, true); } this.emit("message", packet, sender); }; async close() { await promisify(this.UDPServer.close)(); this.emit("close"); } }; // src/pkg.ts var pkg = { name: "vban", version: "1.4.1" }; export { BITS_SPEEDS, EBitsResolutions, ECodecs, EFormatBit, ERegistrationAnswer, ESerialStreamType, EServiceFunction, EServicePINGApplicationType, EServicePINGFeatures, EServiceType, ESubProtocol, ETextEncoding, MAX_FRAME_COUNTER, PACKET_IDENTIFICATION, STREAM_NAME_LENGTH, VBANAudioPacket, VBANChatPacket, VBANPacket, VBANPingPacket, VBANProtocolFactory, VBANRealTimePacket, VBANRealTimeRegisterAnswerPacket, VBANRealTimeRegisterPacket, VBANSerialPacket, VBANServer, VBANServicePacket, VBANServicePacketFactory, VBANTEXTPacket, VBAN_DATA_MAX_SIZE, VBAN_PACKET_MAX_SIZE, bufferToHex, cleanPacketString, dec2bin, pkg, prepareStringForPacket, sampleRates, serialStopModes }; //# sourceMappingURL=index.mjs.map