Initial commit of my folder

This commit is contained in:
Timothy
2026-03-10 20:41:46 +00:00
parent 37becee76a
commit 7ad88804f3
1225 changed files with 238706 additions and 0 deletions

90
backend/node_modules/node-osc/lib/Bundle.mjs generated vendored Normal file
View File

@ -0,0 +1,90 @@
import Message from './Message.mjs';
/**
* Convert array notation to Message object.
* @private
* @param {Array|Message|Bundle} element - The element to sanitize.
* @returns {Message|Bundle} The sanitized element.
*/
function sanitize(element) {
if (element instanceof Array) element = new Message(element[0], ...element.slice(1));
return element;
}
/**
* Represents an OSC bundle containing multiple messages or nested bundles.
*
* OSC bundles allow multiple messages to be sent together, optionally with
* a timetag indicating when the bundle should be processed.
*
* @class
*
* @example
* // Create a bundle without a timetag
* const bundle = new Bundle(['/one', 1], ['/two', 2]);
*
* @example
* // Create a bundle with a timetag
* const bundle = new Bundle(10, ['/one', 1], ['/two', 2]);
*
* @example
* // Nest bundles
* const bundle1 = new Bundle(['/one', 1]);
* const bundle2 = new Bundle(['/two', 2]);
* bundle1.append(bundle2);
*/
class Bundle {
/**
* Create an OSC Bundle.
*
* @param {number|Message|Bundle|Array} [timetagOrElement=0] - Timetag, or if not a number, the first element and timetag will default to 0.
* @param {...(Message|Bundle|Array)} elements - Messages or bundles to include.
* Arrays will be automatically converted to Message objects.
*
* @example
* // Bundle without timetag
* const bundle = new Bundle(['/test', 1], ['/test2', 2]);
*
* @example
* // Bundle with timetag of 10
* const bundle = new Bundle(10, ['/test', 1]);
*
* @example
* // Bundle with Message objects
* const msg1 = new Message('/one', 1);
* const msg2 = new Message('/two', 2);
* const bundle = new Bundle(msg1, msg2);
*/
constructor(timetag, ...elements) {
if (!(typeof timetag === 'number')) {
elements.unshift(timetag);
timetag = 0;
}
this.oscType = 'bundle';
this.timetag = timetag;
this.elements = elements.map(sanitize);
}
/**
* Append a message or bundle to this bundle.
*
* @param {Message|Bundle|Array} element - The message or bundle to append.
* Arrays will be automatically converted to Message objects.
*
* @example
* const bundle = new Bundle();
* bundle.append(['/test', 1]);
* bundle.append(new Message('/test2', 2));
*
* @example
* // Append a nested bundle
* const bundle1 = new Bundle(['/one', 1]);
* const bundle2 = new Bundle(['/two', 2]);
* bundle1.append(bundle2);
*/
append(element) {
this.elements.push(sanitize(element));
}
}
export default Bundle;

178
backend/node_modules/node-osc/lib/Client.mjs generated vendored Normal file
View File

@ -0,0 +1,178 @@
import { createSocket } from 'node:dgram';
import { EventEmitter } from 'node:events';
import { encode } from './osc.mjs';
import Message from './Message.mjs';
/**
* OSC Client for sending messages and bundles over UDP.
*
* Extends EventEmitter and emits the following events:
* - 'error': Emitted when a socket error occurs
*
* @class
* @extends EventEmitter
* @example
* // Create a client
* const client = new Client('127.0.0.1', 3333);
*
* // Send a message with callback
* client.send('/oscAddress', 200, (err) => {
* if (err) console.error(err);
* client.close();
* });
*
* @example
* // Send a message with async/await
* const client = new Client('127.0.0.1', 3333);
* await client.send('/oscAddress', 200);
* await client.close();
*/
class Client extends EventEmitter {
/**
* Create an OSC Client.
*
* @param {string} host - The hostname or IP address of the OSC server.
* @param {number} port - The port number of the OSC server.
*
* @example
* const client = new Client('127.0.0.1', 3333);
*/
constructor(host, port) {
super();
this.host = host;
this.port = port;
this._sock = createSocket({
type: 'udp4',
reuseAddr: true
});
this._sock.on('error', (err) => {
this.emit('error', err);
});
}
/**
* Close the client socket.
*
* This method can be used with either a callback or as a Promise.
*
* @param {Function} [cb] - Optional callback function called when socket is closed.
* @returns {Promise<void>|undefined} Returns a Promise if no callback is provided.
*
* @example
* // With callback
* client.close((err) => {
* if (err) console.error(err);
* });
*
* @example
* // With async/await
* await client.close();
*/
close(cb) {
if (cb) {
this._sock.close(cb);
} else {
return new Promise((resolve, reject) => {
this._sock.close((err) => {
if (err) reject(err);
else resolve();
});
});
}
}
_performSend(message, args, callback) {
let mes;
let buf;
try {
switch (typeof message) {
case 'object':
buf = encode(message);
this._sock.send(buf, 0, buf.length, this.port, this.host, callback);
break;
case 'string':
mes = new Message(args[0]);
for (let i = 1; i < args.length; i++) {
mes.append(args[i]);
}
buf = encode(mes);
this._sock.send(buf, 0, buf.length, this.port, this.host, callback);
break;
default:
throw new TypeError('That Message Just Doesn\'t Seem Right');
}
}
catch (e) {
if (e.code !== 'ERR_SOCKET_DGRAM_NOT_RUNNING') throw e;
const error = new ReferenceError('Cannot send message on closed socket.');
error.code = e.code;
callback(error);
}
}
/**
* Send an OSC message or bundle to the server.
*
* This method can be used with either a callback or as a Promise.
* Messages can be sent in several formats:
* - As separate arguments: address followed by values
* - As a Message or Bundle object
* - As an array: [address, ...values]
*
* @param {...*} args - The message to send. Can be:
* - (address: string, ...values: any[], callback?: Function)
* - (message: Message|Bundle, callback?: Function)
* - (array: Array, callback?: Function)
* @returns {Promise<void>|undefined} Returns a Promise if no callback is provided.
*
* @throws {TypeError} If the message format is invalid.
* @throws {ReferenceError} If attempting to send on a closed socket.
*
* @example
* // Send with address and arguments
* client.send('/oscAddress', 200, 'hello', (err) => {
* if (err) console.error(err);
* });
*
* @example
* // Send with async/await
* await client.send('/oscAddress', 200, 'hello');
*
* @example
* // Send a Message object
* const msg = new Message('/test', 1, 2, 3);
* await client.send(msg);
*
* @example
* // Send a Bundle object
* const bundle = new Bundle(['/one', 1], ['/two', 2]);
* await client.send(bundle);
*/
send(...args) {
let message = args[0];
let callback;
// Convert array syntax to message object
if (message instanceof Array) {
message = {
address: message[0],
args: message.slice(1)
};
}
if (typeof args[args.length - 1] === 'function') {
callback = args.pop();
this._performSend(message, args, callback);
}
else {
// No callback provided, return a Promise
return new Promise((resolve, reject) => {
callback = (err) => {
if (err) reject(err);
else resolve();
};
this._performSend(message, args, callback);
});
}
}
}
export default Client;

143
backend/node_modules/node-osc/lib/Message.mjs generated vendored Normal file
View File

@ -0,0 +1,143 @@
const typeTags = {
s: 'string',
f: 'float',
i: 'integer',
b: 'blob',
m: 'midi'
};
/**
* Represents a typed argument for an OSC message.
*
* @class
* @private
*/
class Argument {
/**
* @param {string} type - The type of the argument (string, float, integer, blob, boolean).
* @param {*} value - The value of the argument.
*/
constructor(type, value) {
this.type = type;
this.value = value;
}
}
/**
* Represents an OSC message with an address and arguments.
*
* OSC messages consist of an address pattern (string starting with '/')
* and zero or more arguments of various types.
*
* @class
*
* @example
* // Create a message with constructor arguments
* const msg = new Message('/test', 1, 2, 'hello');
*
* @example
* // Create a message and append arguments
* const msg = new Message('/test');
* msg.append(1);
* msg.append('hello');
* msg.append(3.14);
*/
class Message {
/**
* Create an OSC Message.
*
* @param {string} address - The OSC address pattern (e.g., '/oscillator/frequency').
* @param {...*} args - Optional arguments to include in the message.
*
* @example
* const msg = new Message('/test');
*
* @example
* const msg = new Message('/test', 1, 2, 3);
*
* @example
* const msg = new Message('/synth', 'note', 60, 0.5);
*/
constructor(address, ...args) {
this.oscType = 'message';
this.address = address;
this.args = args;
}
/**
* Append an argument to the message.
*
* Automatically detects the type based on the JavaScript type:
* - Integers are encoded as OSC integers
* - Floats are encoded as OSC floats
* - Strings are encoded as OSC strings
* - Booleans are encoded as OSC booleans
* - Buffers are encoded as OSC blobs
* - Arrays are recursively appended
* - Objects with a 'type' property are used as-is
*
* @param {*} arg - The argument to append. Can be:
* - A primitive value (number, string, boolean)
* - A Buffer (encoded as blob)
* - An array of values (will be recursively appended)
* - An object with 'type' and 'value' properties for explicit type control
*
* @throws {Error} If the argument type cannot be encoded.
*
* @example
* const msg = new Message('/test');
* msg.append(42); // Integer
* msg.append(3.14); // Float
* msg.append('hello'); // String
* msg.append(true); // Boolean
*
* @example
* // Append multiple values at once
* msg.append([1, 2, 3]);
*
* @example
* // Explicitly specify type
* msg.append({ type: 'float', value: 42 });
* msg.append({ type: 'blob', value: Buffer.from('data') });
*
* @example
* // MIDI messages (4 bytes: port, status, data1, data2)
* msg.append({ type: 'midi', value: { port: 0, status: 144, data1: 60, data2: 127 } });
* msg.append({ type: 'm', value: Buffer.from([0, 144, 60, 127]) });
*/
append(arg) {
let argOut;
switch (typeof arg) {
case 'object':
if (Buffer.isBuffer(arg)) {
this.args.push(arg);
} else if (arg instanceof Array) {
arg.forEach(a => this.append(a));
} else if (arg.type) {
if (typeTags[arg.type]) arg.type = typeTags[arg.type];
this.args.push(arg);
} else {
throw new Error(`don't know how to encode object ${arg}`);
}
break;
case 'number':
if (Math.floor(arg) === arg) {
argOut = new Argument('integer', arg);
} else {
argOut = new Argument('float', arg);
}
break;
case 'string':
argOut = new Argument('string', arg);
break;
case 'boolean':
argOut = new Argument('boolean', arg);
break;
default:
throw new Error(`don't know how to encode ${arg}`);
}
if (argOut) this.args.push(argOut);
}
}
export default Message;

155
backend/node_modules/node-osc/lib/Server.mjs generated vendored Normal file
View File

@ -0,0 +1,155 @@
import { createSocket } from 'node:dgram';
import { EventEmitter } from 'node:events';
import decode from '#decode';
/**
* OSC Server for receiving messages and bundles over UDP.
*
* Emits the following events:
* - 'listening': Emitted when the server starts listening
* - 'message': Emitted when an OSC message is received (receives msg array and rinfo object)
* - 'bundle': Emitted when an OSC bundle is received (receives bundle object and rinfo object)
* - 'error': Emitted when a socket error or decoding error occurs (receives error and rinfo)
* - Address-specific events: Emitted for each message address (e.g., '/test')
*
* @class
* @extends EventEmitter
*
* @fires Server#listening
* @fires Server#message
* @fires Server#bundle
* @fires Server#error
*
* @example
* // Create and listen for messages
* const server = new Server(3333, '0.0.0.0', () => {
* console.log('Server is listening');
* });
*
* server.on('message', (msg, rinfo) => {
* console.log('Message:', msg);
* console.log('From:', rinfo.address, rinfo.port);
* });
*
* @example
* // Using async/await with events.once
* import { once } from 'node:events';
*
* const server = new Server(3333, '0.0.0.0');
* await once(server, 'listening');
*
* server.on('message', (msg) => {
* console.log('Message:', msg);
* });
*
* @example
* // Listen for specific OSC addresses
* server.on('/note', (msg) => {
* const [address, pitch, velocity] = msg;
* console.log(`Note: ${pitch}, Velocity: ${velocity}`);
* });
*/
class Server extends EventEmitter {
/**
* Create an OSC Server.
*
* @param {number} port - The port to listen on.
* @param {string} [host='127.0.0.1'] - The host address to bind to. Use '0.0.0.0' to listen on all interfaces.
* @param {Function} [cb] - Optional callback function called when server starts listening.
*
* @example
* // Basic server
* const server = new Server(3333);
*
* @example
* // Server on all interfaces with callback
* const server = new Server(3333, '0.0.0.0', () => {
* console.log('Server started');
* });
*
* @example
* // Host parameter can be omitted, callback as second parameter
* const server = new Server(3333, () => {
* console.log('Server started on 127.0.0.1');
* });
*/
constructor(port, host='127.0.0.1', cb) {
super();
if (typeof host === 'function') {
cb = host;
host = '127.0.0.1';
}
let decoded;
this.port = port;
this.host = host;
this._sock = createSocket({
type: 'udp4',
reuseAddr: true
});
this._sock.bind(port, host);
// Update port and emit listening event when socket is ready
this._sock.on('listening', () => {
// Update port with actual bound port (important when using port 0)
this.port = this._sock.address().port;
this.emit('listening');
if (cb) cb();
});
this._sock.on('message', (msg, rinfo) => {
try {
decoded = decode(msg);
}
catch (e) {
const error = new Error(`can't decode incoming message: ${e.message}`);
this.emit('error', error, rinfo);
return;
}
if (decoded.elements) {
this.emit('bundle', decoded, rinfo);
}
else if (decoded) {
this.emit('message', decoded, rinfo);
this.emit(decoded[0], decoded, rinfo);
}
});
this._sock.on('error', (err) => {
this.emit('error', err);
});
}
/**
* Close the server socket.
*
* This method can be used with either a callback or as a Promise.
*
* @param {Function} [cb] - Optional callback function called when socket is closed.
* @returns {Promise<void>|undefined} Returns a Promise if no callback is provided.
*
* @example
* // With callback
* server.close((err) => {
* if (err) console.error(err);
* });
*
* @example
* // With async/await
* await server.close();
*/
close(cb) {
if (cb) {
this._sock.close(cb);
} else {
return new Promise((resolve, reject) => {
this._sock.close((err) => {
if (err) reject(err);
else resolve();
});
});
}
}
}
export default Server;

5
backend/node_modules/node-osc/lib/index.mjs generated vendored Normal file
View File

@ -0,0 +1,5 @@
export { default as Message } from './Message.mjs';
export { default as Bundle } from './Bundle.mjs';
export { default as Server } from './Server.mjs';
export { default as Client } from './Client.mjs';
export { encode, decode } from './osc.mjs';

35
backend/node_modules/node-osc/lib/internal/decode.mjs generated vendored Normal file
View File

@ -0,0 +1,35 @@
import { decode } from '../osc.mjs';
function sanitizeMessage(decoded) {
const message = [];
message.push(decoded.address);
const args = decoded.args ?? [];
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);
throw new Error('Malformed Packet');
});
return decoded;
}
function decodeAndSanitize(data, customDecode = decode) {
const decoded = customDecode(data);
if (decoded.oscType === 'bundle') {
return sanitizeBundle(decoded);
}
else if (decoded.oscType === 'message') {
return sanitizeMessage(decoded);
}
else {
throw new Error ('Malformed Packet');
}
}
export default decodeAndSanitize;

422
backend/node_modules/node-osc/lib/osc.mjs generated vendored Normal file
View File

@ -0,0 +1,422 @@
// OSC 1.0 Protocol Implementation
// Based on http://opensoundcontrol.org/spec-1_0
// Helper functions for OSC encoding/decoding
import { Buffer } from 'node:buffer';
function padString(str) {
const nullTerminated = str + '\0';
const byteLength = Buffer.byteLength(nullTerminated);
const padding = (4 - (byteLength % 4)) % 4;
return nullTerminated + '\0'.repeat(padding);
}
function readString(buffer, offset) {
let end = offset;
while (end < buffer.length && buffer[end] !== 0) {
end++;
}
if (end >= buffer.length) {
throw new Error('Malformed Packet: Missing null terminator for string');
}
const str = buffer.subarray(offset, end).toString('utf8');
// Find next 4-byte boundary
const paddedLength = Math.ceil((end - offset + 1) / 4) * 4;
return { value: str, offset: offset + paddedLength };
}
function writeInt32(value) {
const buffer = Buffer.alloc(4);
buffer.writeInt32BE(value, 0);
return buffer;
}
function readInt32(buffer, offset) {
if (offset + 4 > buffer.length) {
throw new Error('Malformed Packet: Not enough bytes for int32');
}
const value = buffer.readInt32BE(offset);
return { value, offset: offset + 4 };
}
function writeFloat32(value) {
const buffer = Buffer.alloc(4);
buffer.writeFloatBE(value, 0);
return buffer;
}
function readFloat32(buffer, offset) {
if (offset + 4 > buffer.length) {
throw new Error('Malformed Packet: Not enough bytes for float32');
}
const value = buffer.readFloatBE(offset);
return { value, offset: offset + 4 };
}
function writeBlob(value) {
const length = value.length;
const lengthBuffer = writeInt32(length);
const padding = 4 - (length % 4);
const paddingBuffer = Buffer.alloc(padding === 4 ? 0 : padding);
return Buffer.concat([lengthBuffer, value, paddingBuffer]);
}
function readBlob(buffer, offset) {
const lengthResult = readInt32(buffer, offset);
const length = lengthResult.value;
if (length < 0) {
throw new Error('Malformed Packet: Invalid blob length');
}
if (lengthResult.offset + length > buffer.length) {
throw new Error('Malformed Packet: Not enough bytes for blob');
}
const data = buffer.subarray(lengthResult.offset, lengthResult.offset + length);
const padding = 4 - (length % 4);
const nextOffset = lengthResult.offset + length + (padding === 4 ? 0 : padding);
if (nextOffset > buffer.length) {
throw new Error('Malformed Packet: Not enough bytes for blob padding');
}
return { value: data, offset: nextOffset };
}
function writeTimeTag(value) {
// For now, treat timetag as a double (8 bytes)
// OSC timetag is 64-bit: 32-bit seconds since 1900, 32-bit fractional
const buffer = Buffer.alloc(8);
if (value === 0 || value === null || value === undefined) {
// Immediate execution
buffer.writeUInt32BE(0, 0);
buffer.writeUInt32BE(1, 4);
} else if (typeof value === 'number') {
// Convert to OSC timetag format
const seconds = Math.floor(value);
const fraction = Math.floor((value - seconds) * 0x100000000);
buffer.writeUInt32BE(seconds + 2208988800, 0); // Add epoch offset (1900 vs 1970)
buffer.writeUInt32BE(fraction, 4);
} else {
// If not a number, write zeros (immediate execution)
buffer.writeUInt32BE(0, 0);
buffer.writeUInt32BE(1, 4);
}
return buffer;
}
function readTimeTag(buffer, offset) {
if (offset + 8 > buffer.length) {
throw new Error('Malformed Packet: Not enough bytes for timetag');
}
const seconds = buffer.readUInt32BE(offset);
const fraction = buffer.readUInt32BE(offset + 4);
let value;
if (seconds === 0 && fraction === 1) {
// Immediate execution
value = 0;
} else {
// Convert from OSC epoch (1900) to Unix epoch (1970)
const unixSeconds = seconds - 2208988800;
const fractionalSeconds = fraction / 0x100000000;
value = unixSeconds + fractionalSeconds;
}
return { value, offset: offset + 8 };
}
function writeMidi(value) {
// MIDI message is 4 bytes: port id, status byte, data1, data2
const buffer = Buffer.alloc(4);
if (Buffer.isBuffer(value)) {
if (value.length !== 4) {
throw new Error('MIDI message must be exactly 4 bytes');
}
value.copy(buffer);
} else if (typeof value === 'object' && value !== null) {
// Allow object format: { port: 0, status: 144, data1: 60, data2: 127 }
buffer.writeUInt8(value.port || 0, 0);
buffer.writeUInt8(value.status || 0, 1);
buffer.writeUInt8(value.data1 || 0, 2);
buffer.writeUInt8(value.data2 || 0, 3);
} else {
throw new Error('MIDI value must be a 4-byte Buffer or object with port, status, data1, data2 properties');
}
return buffer;
}
function readMidi(buffer, offset) {
if (offset + 4 > buffer.length) {
throw new Error('Not enough bytes for MIDI message');
}
const value = buffer.subarray(offset, offset + 4);
return { value, offset: offset + 4 };
}
function encodeArgument(arg) {
if (typeof arg === 'object' && arg.type && arg.value !== undefined) {
// Explicit type specification
switch (arg.type) {
case 'i':
case 'integer':
return { tag: 'i', data: writeInt32(arg.value) };
case 'f':
case 'float':
return { tag: 'f', data: writeFloat32(arg.value) };
case 's':
case 'string':
return { tag: 's', data: Buffer.from(padString(arg.value)) };
case 'b':
case 'blob':
return { tag: 'b', data: writeBlob(arg.value) };
case 'd':
case 'double':
// For doubles, use float for now (OSC 1.0 doesn't have double)
return { tag: 'f', data: writeFloat32(arg.value) };
case 'T':
return { tag: 'T', data: Buffer.alloc(0) };
case 'F':
return { tag: 'F', data: Buffer.alloc(0) };
case 'boolean':
return arg.value ? { tag: 'T', data: Buffer.alloc(0) } : { tag: 'F', data: Buffer.alloc(0) };
case 'm':
case 'midi':
return { tag: 'm', data: writeMidi(arg.value) };
default:
throw new Error(`Unknown argument type: ${arg.type}`);
}
}
// Infer type from JavaScript type
switch (typeof arg) {
case 'number':
if (Number.isInteger(arg)) {
return { tag: 'i', data: writeInt32(arg) };
} else {
return { tag: 'f', data: writeFloat32(arg) };
}
case 'string':
return { tag: 's', data: Buffer.from(padString(arg)) };
case 'boolean':
return arg ? { tag: 'T', data: Buffer.alloc(0) } : { tag: 'F', data: Buffer.alloc(0) };
default:
if (Buffer.isBuffer(arg)) {
return { tag: 'b', data: writeBlob(arg) };
}
throw new Error(`Don't know how to encode argument: ${arg}`);
}
}
function decodeArgument(tag, buffer, offset) {
switch (tag) {
case 'i':
return readInt32(buffer, offset);
case 'f':
return readFloat32(buffer, offset);
case 's':
return readString(buffer, offset);
case 'b':
return readBlob(buffer, offset);
case 'T':
return { value: true, offset };
case 'F':
return { value: false, offset };
case 'N':
return { value: null, offset };
case 'm':
return readMidi(buffer, offset);
default:
throw new Error(`I don't understand the argument code ${tag}`);
}
}
/**
* Encode an OSC message or bundle to a Buffer.
*
* This low-level function converts OSC messages and bundles into binary format
* for transmission or storage. Useful for sending OSC over custom transports
* (WebSocket, TCP, HTTP), storing to files, or implementing custom OSC routers.
*
* @param {Object} message - OSC message or bundle object with oscType property
* @returns {Buffer} The encoded OSC data ready for transmission
*
* @example
* // Encode a message
* import { Message, encode } from 'node-osc';
*
* const message = new Message('/oscillator/frequency', 440);
* const buffer = encode(message);
* console.log('Encoded bytes:', buffer.length);
*
* @example
* // Encode a bundle
* import { Bundle, encode } from 'node-osc';
*
* const bundle = new Bundle(['/one', 1], ['/two', 2]);
* const buffer = encode(bundle);
*
* @example
* // Send over WebSocket
* const buffer = encode(message);
* websocket.send(buffer);
*/
function encode(message) {
if (message.oscType === 'bundle') {
return encodeBundleToBuffer(message);
} else {
return encodeMessageToBuffer(message);
}
}
function encodeMessageToBuffer(message) {
// OSC Message format:
// Address pattern (padded string)
// Type tag string (padded string starting with ,)
// Arguments (encoded according to type tags)
const address = padString(message.address);
const addressBuffer = Buffer.from(address);
const encodedArgs = message.args.map(encodeArgument);
const typeTags = ',' + encodedArgs.map(arg => arg.tag).join('');
const typeTagsBuffer = Buffer.from(padString(typeTags));
const argumentBuffers = encodedArgs.map(arg => arg.data);
return Buffer.concat([addressBuffer, typeTagsBuffer, ...argumentBuffers]);
}
function encodeBundleToBuffer(bundle) {
// OSC Bundle format:
// "#bundle" (padded string)
// Timetag (8 bytes)
// Elements (each prefixed with size)
const bundleString = padString('#bundle');
const bundleStringBuffer = Buffer.from(bundleString);
const timetagBuffer = writeTimeTag(bundle.timetag);
const elementBuffers = bundle.elements.map(element => {
let elementBuffer;
if (element.oscType === 'bundle') {
elementBuffer = encodeBundleToBuffer(element);
} else {
elementBuffer = encodeMessageToBuffer(element);
}
const sizeBuffer = writeInt32(elementBuffer.length);
return Buffer.concat([sizeBuffer, elementBuffer]);
});
return Buffer.concat([bundleStringBuffer, timetagBuffer, ...elementBuffers]);
}
/**
* Decode a Buffer containing OSC data into a message or bundle object.
*
* This low-level function parses binary OSC data back into JavaScript objects.
* Useful for receiving OSC over custom transports, reading from files,
* or implementing custom OSC routers.
*
* @param {Buffer} buffer - The Buffer containing OSC data
* @returns {Object} The decoded OSC message or bundle. Messages have
* {oscType: 'message', address: string, args: Array}, bundles have
* {oscType: 'bundle', timetag: number, elements: Array}
* @throws {Error} If the buffer contains malformed OSC data
*
* @example
* // Decode received data
* import { decode } from 'node-osc';
*
* const decoded = decode(buffer);
* if (decoded.oscType === 'message') {
* console.log('Address:', decoded.address);
* console.log('Arguments:', decoded.args);
* }
*
* @example
* // Round-trip encode/decode
* import { Message, encode, decode } from 'node-osc';
*
* const original = new Message('/test', 42, 'hello');
* const buffer = encode(original);
* const decoded = decode(buffer);
* console.log(decoded.address); // '/test'
*/
function decode(buffer) {
// Check if it's a bundle or message
if (buffer.length >= 8 && buffer.subarray(0, 8).toString() === '#bundle\0') {
return decodeBundleFromBuffer(buffer);
} else {
return decodeMessageFromBuffer(buffer);
}
}
function decodeMessageFromBuffer(buffer) {
let offset = 0;
// Read address pattern
const addressResult = readString(buffer, offset);
const address = addressResult.value;
offset = addressResult.offset;
// Read type tag string
const typeTagsResult = readString(buffer, offset);
const typeTags = typeTagsResult.value;
offset = typeTagsResult.offset;
if (!typeTags.startsWith(',')) {
throw new Error('Malformed Packet');
}
const tags = typeTags.slice(1); // Remove leading comma
const args = [];
for (const tag of tags) {
const argResult = decodeArgument(tag, buffer, offset);
args.push({ value: argResult.value });
offset = argResult.offset;
}
return {
oscType: 'message',
address,
args
};
}
function decodeBundleFromBuffer(buffer) {
let offset = 8; // Skip "#bundle\0"
// Read timetag
const timetagResult = readTimeTag(buffer, offset);
const timetag = timetagResult.value;
offset = timetagResult.offset;
const elements = [];
while (offset < buffer.length) {
// Read element size
const sizeResult = readInt32(buffer, offset);
const size = sizeResult.value;
offset = sizeResult.offset;
if (size <= 0 || offset + size > buffer.length) {
throw new Error('Malformed Packet');
}
// Read element data
const elementBuffer = buffer.subarray(offset, offset + size);
const element = decode(elementBuffer);
elements.push(element);
offset += size;
}
return {
oscType: 'bundle',
timetag,
elements
};
}
export { encode, decode };