From a087a51f3936bcb1c982d6edc0c75d0810a2394f Mon Sep 17 00:00:00 2001 From: Myles Borins Date: Fri, 15 Nov 2024 14:39:50 -0500 Subject: [PATCH] Remove osc-min dependency Related to #115 --- For more details, open the [Copilot Workspace session](https://copilot-workspace.githubnext.com/MylesBorins/node-osc/issues/115?shareId=XXXX-XXXX-XXXX-XXXX). --- lib/Client.mjs | 4 +- lib/Server.mjs | 4 +- lib/internal/decode.mjs | 33 -------------- lib/internal/osc.mjs | 99 +++++++++++++++++++++++++++++++++++++++++ package.json | 7 +-- 5 files changed, 106 insertions(+), 41 deletions(-) delete mode 100644 lib/internal/decode.mjs create mode 100644 lib/internal/osc.mjs diff --git a/lib/Client.mjs b/lib/Client.mjs index 73284b8..cfc6ac1 100644 --- a/lib/Client.mjs +++ b/lib/Client.mjs @@ -1,9 +1,7 @@ import { createSocket } from 'node:dgram'; -import oscMin from 'osc-min'; +import { toBuffer } from '#osc'; import Message from './Message.mjs'; -const { toBuffer } = oscMin; - class Client { constructor(host, port) { this.host = host; diff --git a/lib/Server.mjs b/lib/Server.mjs index 19bf7a4..bc42a37 100644 --- a/lib/Server.mjs +++ b/lib/Server.mjs @@ -1,7 +1,7 @@ import { createSocket } from 'node:dgram'; import { EventEmitter } from 'node:events'; -import decode from '#decode'; +import { fromBuffer } from '#osc'; class Server extends EventEmitter { constructor(port, host='127.0.0.1', cb) { @@ -25,7 +25,7 @@ class Server extends EventEmitter { }); this._sock.on('message', (msg, rinfo) => { try { - decoded = decode(msg); + decoded = fromBuffer(msg); } catch (e) { const error = new Error(`can't decode incoming message: ${e.message}`); diff --git a/lib/internal/decode.mjs b/lib/internal/decode.mjs deleted file mode 100644 index 7a80331..0000000 --- a/lib/internal/decode.mjs +++ /dev/null @@ -1,33 +0,0 @@ -import { fromBuffer } from 'osc-min'; - -function sanitizeMessage(decoded) { - const message = []; - message.push(decoded.address); - decoded.args.forEach(arg => { - message.push(arg.value); - }); - return message; -} - -function sanitizeBundle(decoded) { - decoded.elements = decoded.elements.map(element => { - if (element.oscType === 'bundle') return sanitizeBundle(element); - else if (element.oscType === 'message') return sanitizeMessage(element); - }); - return decoded; -} - -function decode(data) { - const decoded = fromBuffer(data); - if (decoded.oscType === 'bundle') { - return sanitizeBundle(decoded); - } - else if (decoded.oscType === 'message') { - return sanitizeMessage(decoded); - } - else { - throw new Error ('Malformed Packet'); - } -} - -export default decode; diff --git a/lib/internal/osc.mjs b/lib/internal/osc.mjs new file mode 100644 index 0000000..248f75d --- /dev/null +++ b/lib/internal/osc.mjs @@ -0,0 +1,99 @@ +import { Buffer } from 'node:buffer'; + +function toBuffer(message) { + if (typeof message !== 'object' || !message.address) { + throw new Error('Invalid OSC message'); + } + + const addressBuffer = toOSCString(message.address); + const typeTagBuffer = toOSCString(',' + message.args.map(arg => getTypeTag(arg)).join('')); + const argsBuffer = Buffer.concat(message.args.map(arg => toOSCArgument(arg))); + + return Buffer.concat([addressBuffer, typeTagBuffer, argsBuffer]); +} + +function fromBuffer(buffer) { + let offset = 0; + + const address = readOSCString(buffer, offset); + offset += address.length + 4 - (address.length % 4); + + const typeTag = readOSCString(buffer, offset); + offset += typeTag.length + 4 - (typeTag.length % 4); + + const args = []; + for (let i = 1; i < typeTag.length; i++) { + const type = typeTag[i]; + const arg = readOSCArgument(buffer, offset, type); + args.push(arg.value); + offset += arg.size; + } + + return { address, args }; +} + +function toOSCString(str) { + const buffer = Buffer.from(str + '\0'); + const padding = 4 - (buffer.length % 4); + return Buffer.concat([buffer, Buffer.alloc(padding)]); +} + +function readOSCString(buffer, offset) { + let end = offset; + while (buffer[end] !== 0) end++; + return buffer.toString('ascii', offset, end); +} + +function getTypeTag(arg) { + switch (typeof arg) { + case 'string': return 's'; + case 'number': return Number.isInteger(arg) ? 'i' : 'f'; + case 'object': return 'b'; + default: throw new Error('Unsupported argument type'); + } +} + +function toOSCArgument(arg) { + switch (typeof arg) { + case 'string': return toOSCString(arg); + case 'number': return Number.isInteger(arg) ? toOSCInt32(arg) : toOSCFloat32(arg); + case 'object': return toOSCBlob(arg); + default: throw new Error('Unsupported argument type'); + } +} + +function readOSCArgument(buffer, offset, type) { + switch (type) { + case 's': return { value: readOSCString(buffer, offset), size: 4 * Math.ceil((buffer.indexOf(0, offset) - offset + 1) / 4) }; + case 'i': return { value: buffer.readInt32BE(offset), size: 4 }; + case 'f': return { value: buffer.readFloatBE(offset), size: 4 }; + case 'b': return readOSCBlob(buffer, offset); + default: throw new Error('Unsupported argument type'); + } +} + +function toOSCInt32(num) { + const buffer = Buffer.alloc(4); + buffer.writeInt32BE(num); + return buffer; +} + +function toOSCFloat32(num) { + const buffer = Buffer.alloc(4); + buffer.writeFloatBE(num); + return buffer; +} + +function toOSCBlob(blob) { + const sizeBuffer = toOSCInt32(blob.length); + const padding = 4 - (blob.length % 4); + return Buffer.concat([sizeBuffer, blob, Buffer.alloc(padding)]); +} + +function readOSCBlob(buffer, offset) { + const size = buffer.readInt32BE(offset); + const value = buffer.slice(offset + 4, offset + 4 + size); + return { value, size: 4 + size + (4 - (size % 4)) }; +} + +export { toBuffer, fromBuffer }; diff --git a/package.json b/package.json index 42097ab..e4bff25 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,10 @@ "#decode": { "require": "./dist/lib/internal/decode.js", "default": "./lib/internal/decode.mjs" + }, + "#osc": { + "require": "./dist/lib/internal/osc.js", + "default": "./lib/internal/osc.mjs" } }, "author": { @@ -42,9 +46,6 @@ "type": "git", "url": "git+https://github.com/MylesBorins/node-osc.git" }, - "dependencies": { - "osc-min": "^1.1.1" - }, "devDependencies": { "@eslint/js": "^9.4.0", "eslint": "^9.4.0",