Files

102 lines
3.1 KiB
JavaScript
Raw Permalink Normal View History

'use strict';
/**
* The slice of OSC 1.0 AbleSet speaks — messages and bundles with int32,
* float32, string, blob, true/false/nil — encoded and decoded with Buffer
* alone, so the module needs nothing outside Node.
*/
function pad4(n) {
return (n + 3) & ~3;
}
function encodeString(s) {
const raw = Buffer.from(String(s), 'utf8');
const out = Buffer.alloc(pad4(raw.length + 1));
raw.copy(out);
return out;
}
/**
* One message. Arguments are typed by their JS value: integers are int32,
* other numbers float32, booleans T/F, null nil, everything else a string.
*/
function encodeMessage(address, args = []) {
let tags = ',';
const parts = [];
for (const a of args) {
if (a === null || a === undefined) {
tags += 'N';
} else if (typeof a === 'boolean') {
tags += a ? 'T' : 'F';
} else if (typeof a === 'number' && Number.isInteger(a)) {
tags += 'i';
const b = Buffer.alloc(4);
b.writeInt32BE(a);
parts.push(b);
} else if (typeof a === 'number') {
tags += 'f';
const b = Buffer.alloc(4);
b.writeFloatBE(a);
parts.push(b);
} else {
tags += 's';
parts.push(encodeString(a));
}
}
return Buffer.concat([encodeString(address), encodeString(tags), ...parts]);
}
function readString(buf, at) {
let end = at;
while (end < buf.length && buf[end] !== 0) end += 1;
if (end >= buf.length) throw new Error('unterminated OSC string');
return { value: buf.toString('utf8', at, end), next: pad4(end + 1) };
}
function decodeMessage(buf, out) {
const addr = readString(buf, 0);
let at = addr.next;
const args = [];
if (at < buf.length && buf[at] === 0x2c) {
const tagStr = readString(buf, at);
at = tagStr.next;
for (const t of tagStr.value.slice(1)) {
switch (t) {
case 'i': args.push(buf.readInt32BE(at)); at += 4; break;
case 'f': args.push(buf.readFloatBE(at)); at += 4; break;
case 'd': args.push(buf.readDoubleBE(at)); at += 8; break;
case 'h': args.push(Number(buf.readBigInt64BE(at))); at += 8; break;
case 's': case 'S': { const s = readString(buf, at); args.push(s.value); at = s.next; break; }
case 'b': { const n = buf.readInt32BE(at); args.push(buf.subarray(at + 4, at + 4 + n)); at = pad4(at + 4 + n); break; }
case 'T': args.push(true); break;
case 'F': args.push(false); break;
case 'N': args.push(null); break;
case 'I': args.push(Infinity); break;
case 't': at += 8; break;
default: throw new Error(`unsupported OSC type tag "${t}"`);
}
}
}
out.push({ address: addr.value, args });
}
/** Every message in a packet, bundles flattened, in order. */
function decodePacket(buf, out = []) {
if (buf.length >= 16 && buf.toString('ascii', 0, 8) === '#bundle\0') {
let at = 16; // "#bundle\0" + 8-byte timetag
while (at + 4 <= buf.length) {
const n = buf.readInt32BE(at);
at += 4;
if (n <= 0 || at + n > buf.length) break;
decodePacket(buf.subarray(at, at + n), out);
at += n;
}
return out;
}
decodeMessage(buf, out);
return out;
}
module.exports = { encodeMessage, decodePacket };