'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 };