mirror of
https://github.com/jwetzell/ddp-js.git
synced 2026-08-13 10:43:38 +00:00
191 lines
4.7 KiB
TypeScript
191 lines
4.7 KiB
TypeScript
export enum DDPDataType {
|
|
UNDEFINED = 0,
|
|
RGB = 1,
|
|
HSL = 2,
|
|
RGBW = 3,
|
|
GRAYSCALE = 4,
|
|
}
|
|
|
|
export enum DDPID {
|
|
JSON_CONTROL = 246,
|
|
JSON_CONFIG = 250,
|
|
JSON_STATUS = 251,
|
|
DMX_TRANSIT = 254,
|
|
ALL = 255,
|
|
}
|
|
|
|
export type DDPHeader = {
|
|
flags: {
|
|
version: number;
|
|
timecode: boolean;
|
|
storage: boolean;
|
|
reply: boolean;
|
|
query: boolean;
|
|
push: boolean;
|
|
};
|
|
sequenceNumber: number;
|
|
dataType: {
|
|
standard: boolean;
|
|
type: number;
|
|
bitsPerPixel: number;
|
|
};
|
|
sourceOrDestinationID: number;
|
|
dataOffset: number;
|
|
dataLength: number;
|
|
timecode?: {
|
|
seconds: number;
|
|
fractionalSeconds: number;
|
|
};
|
|
};
|
|
|
|
export type DDPPacket = {
|
|
header: DDPHeader;
|
|
data: Uint8Array;
|
|
};
|
|
|
|
export function decode(bytes: Uint8Array): DDPPacket {
|
|
if (bytes.length < 10) {
|
|
throw new Error('DDP packet must be at least 10 bytes');
|
|
}
|
|
|
|
const flagsByte = bytes[0];
|
|
|
|
const flags = {
|
|
version: (flagsByte & 0xc0) >> 6,
|
|
timecode: (flagsByte & 0x10) >> 4 === 1,
|
|
storage: (flagsByte & 0x08) >> 3 === 1,
|
|
reply: (flagsByte & 0x04) >> 2 === 1,
|
|
query: (flagsByte & 0x02) >> 1 === 1,
|
|
push: (flagsByte & 0x01) === 1,
|
|
};
|
|
const sequenceNumber = bytes[1] & 0x0f;
|
|
|
|
const dataTypeByte = bytes[2];
|
|
|
|
const dataType = {
|
|
standard: dataTypeByte >> 7 === 0,
|
|
type: (dataTypeByte & 0x38) >> 3,
|
|
bitsPerPixel: dataTypeByte & 0x07,
|
|
};
|
|
|
|
if (dataType.bitsPerPixel > 1) {
|
|
if (dataType.bitsPerPixel === 2) {
|
|
dataType.bitsPerPixel = 4;
|
|
} else if (dataType.bitsPerPixel === 3) {
|
|
dataType.bitsPerPixel = 8;
|
|
} else if (dataType.bitsPerPixel === 4) {
|
|
dataType.bitsPerPixel = 16;
|
|
} else if (dataType.bitsPerPixel === 5) {
|
|
dataType.bitsPerPixel = 24;
|
|
} else if (dataType.bitsPerPixel === 6) {
|
|
dataType.bitsPerPixel = 32;
|
|
} else {
|
|
throw new Error('unknown DDP size');
|
|
}
|
|
}
|
|
|
|
const sourceOrDestinationID = bytes[3];
|
|
const dataOffset = (bytes[4] << 24) + (bytes[5] << 16) + (bytes[6] << 8) + bytes[7];
|
|
const dataLength = (bytes[8] << 8) + bytes[9];
|
|
|
|
const header: DDPHeader = {
|
|
flags,
|
|
sequenceNumber,
|
|
dataType,
|
|
sourceOrDestinationID,
|
|
dataOffset,
|
|
dataLength,
|
|
};
|
|
|
|
let dataStart = 10;
|
|
if (flags.timecode) {
|
|
if (bytes.length < 14) {
|
|
throw new Error('DDP packet with timecode must be at least 14 bytes');
|
|
}
|
|
dataStart = 14;
|
|
header.timecode = {
|
|
seconds: (bytes[10] << 8) + bytes[11],
|
|
fractionalSeconds: (bytes[12] << 8) + bytes[13],
|
|
};
|
|
}
|
|
|
|
return {
|
|
header,
|
|
data: bytes.subarray(dataStart),
|
|
};
|
|
}
|
|
|
|
export function encode(packet: DDPPacket): Uint8Array {
|
|
let flagByte = packet.header.flags.version << 6;
|
|
|
|
let headerLength = 10;
|
|
|
|
if (packet.header.flags.timecode) {
|
|
flagByte += 16;
|
|
headerLength = 14;
|
|
}
|
|
|
|
if (packet.header.flags.storage) {
|
|
flagByte += 8;
|
|
}
|
|
if (packet.header.flags.reply) {
|
|
flagByte += 4;
|
|
}
|
|
if (packet.header.flags.query) {
|
|
flagByte += 2;
|
|
}
|
|
if (packet.header.flags.push) {
|
|
flagByte += 1;
|
|
}
|
|
|
|
let dataTypeByte = 0;
|
|
|
|
if (!packet.header.dataType.standard) {
|
|
dataTypeByte += 128;
|
|
}
|
|
|
|
dataTypeByte += packet.header.dataType.type << 3;
|
|
|
|
if (packet.header.dataType.bitsPerPixel === 1) {
|
|
dataTypeByte += 1;
|
|
} else if (packet.header.dataType.bitsPerPixel === 4) {
|
|
dataTypeByte += 2;
|
|
} else if (packet.header.dataType.bitsPerPixel === 8) {
|
|
dataTypeByte += 3;
|
|
} else if (packet.header.dataType.bitsPerPixel === 16) {
|
|
dataTypeByte += 4;
|
|
} else if (packet.header.dataType.bitsPerPixel === 24) {
|
|
dataTypeByte += 5;
|
|
} else if (packet.header.dataType.bitsPerPixel === 32) {
|
|
dataTypeByte += 6;
|
|
} else if (packet.header.dataType.bitsPerPixel === 0) {
|
|
dataTypeByte += 0;
|
|
} else {
|
|
throw new Error('DDP packet has unsupported bits per pixel value');
|
|
}
|
|
|
|
const bytes = new Uint8Array(headerLength);
|
|
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
|
|
view.setUint8(0, flagByte);
|
|
view.setUint8(1, packet.header.sequenceNumber);
|
|
view.setUint8(2, dataTypeByte);
|
|
view.setUint8(3, packet.header.sourceOrDestinationID);
|
|
view.setUint32(4, packet.header.dataOffset);
|
|
view.setUint16(8, packet.header.dataLength);
|
|
|
|
if (packet.header.flags.timecode) {
|
|
if (packet.header.timecode !== undefined) {
|
|
view.setUint16(10, packet.header.timecode?.seconds);
|
|
view.setUint16(12, packet.header.timecode?.fractionalSeconds);
|
|
} else {
|
|
throw new Error('DDP packet had timecode flag set but no timecode value supplied');
|
|
}
|
|
}
|
|
|
|
// const bytes = new Uint8Array([flagByte, sequenceNumberByte, dataTypeByte, packet.header.sourceOrDestinationID])
|
|
const headerBytes = new Uint8Array(view.buffer, view.byteOffset, view.byteLength);
|
|
|
|
return new Uint8Array([...headerBytes, ...packet.data]);
|
|
}
|