Files
osc-js/packages/osc/src/message.ts
T
2025-01-20 10:24:48 -06:00

149 lines
4.3 KiB
TypeScript

import { OSCMessage, OSCArg, OSCType } from './models';
import { oscTypeConverterMap } from './osc-types';
function argsToBuffer(args: OSCArg[]) {
const argBuffers: Uint8Array[] = [];
let argBuffersTotalLength = 0;
for (let index = 0; index < args.length; index += 1) {
const arg = args[index];
const typeConverter = oscTypeConverterMap[arg.type];
if (typeConverter === undefined) {
throw new TypeError('unknown type '.concat(arg.type));
}
const buffer = typeConverter.toBuffer(arg.value);
if (buffer !== undefined) {
argBuffers.push(buffer);
argBuffersTotalLength += buffer.length;
}
}
const buffer = new Uint8Array(argBuffersTotalLength);
let offset = 0;
argBuffers.forEach((argBuffer) => {
buffer.set(argBuffer, offset);
offset += argBuffer.length;
});
return buffer;
}
export function messageToBuffer(message: OSCMessage): Uint8Array {
if (!message.address.startsWith('/')) {
throw new Error('osc message must start with a /');
}
const addressBuffer = oscTypeConverterMap.s.toBuffer(message.address);
if (addressBuffer === undefined) {
throw new Error('problem encoding address');
}
let typeString = ',';
const flatOSCArgs: OSCArg[] = [];
message.args.forEach((oscArg) => {
if (Array.isArray(oscArg)) {
typeString += '[';
oscArg.forEach((oscArg) => {
typeString += oscArg.type;
flatOSCArgs.push(oscArg);
});
typeString += ']';
} else {
typeString += oscArg.type;
flatOSCArgs.push(oscArg);
}
});
const typesBuffer = oscTypeConverterMap.s.toBuffer(typeString);
if (typesBuffer === undefined) {
throw new Error('problem encoding types');
}
const argsBuffer = argsToBuffer(flatOSCArgs);
const buffer = new Uint8Array(addressBuffer.length + typesBuffer.length + argsBuffer.length);
buffer.set(addressBuffer, 0);
buffer.set(typesBuffer, addressBuffer.length);
buffer.set(argsBuffer, addressBuffer.length + typesBuffer.length);
return buffer;
}
export function messageFromBuffer(bytes: Uint8Array): [OSCMessage | undefined, Uint8Array | undefined] {
if (bytes[0] !== 47) {
throw new Error('osc message must start with a /');
}
const oscArgs: (OSCArg | OSCArg[])[] = [];
const [address, bytesAfterAddress] = oscTypeConverterMap.s.fromBuffer(bytes);
if (typeof address === 'string') {
if (bytesAfterAddress.length === 0) {
// NOTE(jwetzell): OSC 1.0 spec says that messages without a type string should be allowed
return [
{
address,
args: [],
},
bytesAfterAddress,
];
}
const [typeString, bytesAfterType] = oscTypeConverterMap.s.fromBuffer(bytesAfterAddress);
if (typeof typeString === 'string') {
if (!typeString.startsWith(',')) {
throw new Error('osc type string must start with a ,');
}
let argsBuffer = bytesAfterType;
let oscArrayArg: OSCArg[] = [];
let insideArgArray = false;
for (let index = 1; index < typeString.length; index++) {
const argType = typeString.charAt(index) as OSCType;
if (argType === '[') {
if (insideArgArray) {
throw new Error('osc arg array opened without closing previous arg array');
}
oscArrayArg = [];
insideArgArray = true;
continue;
}
if (argType === ']') {
if (!insideArgArray) {
throw new Error('osc arg array closed without opening arg array');
}
oscArgs.push(oscArrayArg);
oscArrayArg = [];
insideArgArray = false;
continue;
}
const oscTypeConverter = oscTypeConverterMap[argType];
if (oscTypeConverter === undefined) {
throw new Error('unknown OSC type');
}
const [value, remainingBytes] = oscTypeConverter.fromBuffer(argsBuffer);
if (value !== undefined) {
const arg: OSCArg = {
type: argType,
value: value,
};
if (insideArgArray) {
oscArrayArg.push(arg);
} else {
oscArgs.push(arg);
}
}
argsBuffer = remainingBytes;
}
return [
{
address,
args: oscArgs,
},
argsBuffer,
];
}
}
return [undefined, undefined];
}