mirror of
https://github.com/jwetzell/osc-js.git
synced 2026-08-18 13:33:56 +00:00
+11
-4
@@ -1,7 +1,14 @@
|
|||||||
export type OSCType = 's' | 'i' | 'f' | 'b' | 'T' | 'F';
|
export type OSCType = 's' | 'i' | 'f' | 'b' | 'T' | 'F' | 't';
|
||||||
export type OSCArg = {
|
export type OSCArg = {
|
||||||
type: OSCType;
|
type: OSCType;
|
||||||
value: string | number | Buffer | boolean;
|
value: string | number | Buffer | boolean | OSCTimeTag;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type OSCTimeTag = [number, number];
|
||||||
|
|
||||||
|
export type OSCBundle = {
|
||||||
|
timeTag: OSCTimeTag;
|
||||||
|
contents: (OSCBundle | OSCMessage)[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type OSCMessage = {
|
export type OSCMessage = {
|
||||||
@@ -10,6 +17,6 @@ export type OSCMessage = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type OSCTypeConverter = {
|
export type OSCTypeConverter = {
|
||||||
toBuffer: (value: string | number | Buffer | boolean) => Buffer | undefined;
|
toBuffer: (value: string | number | Buffer | boolean | OSCTimeTag) => Buffer | undefined;
|
||||||
fromBuffer: (buffer: Buffer) => [string | number | Buffer | boolean | undefined, Buffer];
|
fromBuffer: (buffer: Buffer) => [string | number | Buffer | boolean | OSCTimeTag | undefined, Buffer];
|
||||||
};
|
};
|
||||||
|
|||||||
+128
-1
@@ -1,4 +1,4 @@
|
|||||||
import { OSCType, OSCArg, OSCMessage, OSCTypeConverter } from './models';
|
import { OSCType, OSCArg, OSCMessage, OSCTypeConverter, OSCBundle, OSCTimeTag } from './models';
|
||||||
|
|
||||||
const oscTypeConverterMap: { [key: string]: OSCTypeConverter } = {
|
const oscTypeConverterMap: { [key: string]: OSCTypeConverter } = {
|
||||||
s: {
|
s: {
|
||||||
@@ -111,6 +111,46 @@ const oscTypeConverterMap: { [key: string]: OSCTypeConverter } = {
|
|||||||
return [false, buffer];
|
return [false, buffer];
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
t: {
|
||||||
|
toBuffer: (timetag) => {
|
||||||
|
if (!Array.isArray(timetag)) {
|
||||||
|
throw new TypeError('osc type t toBuffer called with non array value');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (timetag.length != 2) {
|
||||||
|
throw new TypeError('osc type t array should have exactly 2 elements');
|
||||||
|
}
|
||||||
|
|
||||||
|
const seconds = timetag[0];
|
||||||
|
if (typeof seconds !== 'number') {
|
||||||
|
throw new TypeError('osc type t seconds part should be a number');
|
||||||
|
}
|
||||||
|
|
||||||
|
const fractional = timetag[1];
|
||||||
|
if (typeof fractional !== 'number') {
|
||||||
|
throw new TypeError('osc type t fractional part should be a number');
|
||||||
|
}
|
||||||
|
|
||||||
|
const secondsBuffer = oscTypeConverterMap.i.toBuffer(seconds);
|
||||||
|
const fractionalBuffer = oscTypeConverterMap.i.toBuffer(fractional);
|
||||||
|
|
||||||
|
if (secondsBuffer && fractionalBuffer) {
|
||||||
|
return Buffer.concat([secondsBuffer, fractionalBuffer]);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fromBuffer: (buffer) => {
|
||||||
|
if (buffer.length < 8) {
|
||||||
|
throw new Error('osc time tag must be greater than 8 bytes');
|
||||||
|
}
|
||||||
|
const [seconds, bytesAfterSeconds] = oscTypeConverterMap.i.fromBuffer(buffer);
|
||||||
|
const [fractional, bytesAfterFractional] = oscTypeConverterMap.i.fromBuffer(bytesAfterSeconds);
|
||||||
|
if (typeof seconds === 'number' && typeof fractional === 'number') {
|
||||||
|
const timeTag: OSCTimeTag = [seconds, fractional];
|
||||||
|
return [timeTag, bytesAfterFractional];
|
||||||
|
}
|
||||||
|
return [undefined, bytesAfterFractional];
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
function argsToBuffer(args: OSCArg[]) {
|
function argsToBuffer(args: OSCArg[]) {
|
||||||
@@ -186,3 +226,90 @@ export function messageFromBuffer(bytes: Buffer): OSCMessage | undefined {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function bundleFromBuffer(bytes: Buffer): OSCBundle | undefined {
|
||||||
|
if (bytes.length < 8) {
|
||||||
|
throw new Error('bundle has to be at least 20 bytes');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bytes.subarray(0, 7).toString('ascii') !== '#bundle') {
|
||||||
|
throw new Error('bundle must start with #bundle');
|
||||||
|
}
|
||||||
|
|
||||||
|
const [bundleHeader, bytesAfterHeader] = oscTypeConverterMap.s.fromBuffer(bytes);
|
||||||
|
let [timeTag, bytesAfterTimeTag] = oscTypeConverterMap.t.fromBuffer(bytesAfterHeader);
|
||||||
|
|
||||||
|
if (!Array.isArray(timeTag)) {
|
||||||
|
throw new Error('problem getting bundle time tag');
|
||||||
|
}
|
||||||
|
|
||||||
|
const bundleContents = [];
|
||||||
|
|
||||||
|
let endOfBundle = false;
|
||||||
|
|
||||||
|
let [contentSize, remainingBytes] = oscTypeConverterMap.i.fromBuffer(bytesAfterTimeTag);
|
||||||
|
if (typeof contentSize === 'number') {
|
||||||
|
while (!endOfBundle) {
|
||||||
|
if (remainingBytes.length < contentSize) {
|
||||||
|
throw new Error('bundle does not contain enough data');
|
||||||
|
}
|
||||||
|
|
||||||
|
const bundleContentBytes = remainingBytes.subarray(0, contentSize);
|
||||||
|
console.log(bundleContentBytes);
|
||||||
|
|
||||||
|
if (bundleContentBytes[0] === 35) {
|
||||||
|
// # character indicating contents is a bundle
|
||||||
|
const content = bundleFromBuffer(bundleContentBytes);
|
||||||
|
if (content) {
|
||||||
|
bundleContents.push(content);
|
||||||
|
}
|
||||||
|
} else if (bundleContentBytes[0] === 47) {
|
||||||
|
const content = messageFromBuffer(bundleContentBytes);
|
||||||
|
if (content) {
|
||||||
|
bundleContents.push(content);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new Error('bundle contents does not look like a OSC message or bundle');
|
||||||
|
}
|
||||||
|
|
||||||
|
remainingBytes = remainingBytes.subarray(contentSize);
|
||||||
|
if (remainingBytes.length === 0) {
|
||||||
|
endOfBundle = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
timeTag,
|
||||||
|
contents: bundleContents,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function bundleToBuffer(bundle: OSCBundle): Buffer {
|
||||||
|
const headerBuffer = oscTypeConverterMap.s.toBuffer('#bundle');
|
||||||
|
|
||||||
|
if (headerBuffer === undefined) {
|
||||||
|
throw new Error('problem encoding buffer header');
|
||||||
|
}
|
||||||
|
|
||||||
|
const timeTagBuffer = oscTypeConverterMap.t.toBuffer(bundle.timeTag);
|
||||||
|
|
||||||
|
if (timeTagBuffer === undefined) {
|
||||||
|
throw new Error('problem encoding buffer time tag');
|
||||||
|
}
|
||||||
|
|
||||||
|
const contentsBuffers: Buffer[] = [];
|
||||||
|
|
||||||
|
bundle.contents.forEach((bundleContent) => {
|
||||||
|
if ('address' in bundleContent) {
|
||||||
|
const contentBuffer = messageToBuffer(bundleContent);
|
||||||
|
const contentSizeBuffer = oscTypeConverterMap.i.toBuffer(contentBuffer.length);
|
||||||
|
|
||||||
|
if (contentBuffer && contentSizeBuffer) {
|
||||||
|
contentsBuffers.push(Buffer.concat([contentSizeBuffer, contentBuffer]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return Buffer.concat([headerBuffer, timeTagBuffer, ...contentsBuffers]);
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
const { deepEqual } = require('assert');
|
||||||
|
const { describe, it } = require('node:test');
|
||||||
|
const osc = require('../dist/index');
|
||||||
|
|
||||||
|
const tests = [
|
||||||
|
{
|
||||||
|
description: 'simple contents single message',
|
||||||
|
expected: {
|
||||||
|
timeTag: [32, 0],
|
||||||
|
contents: [{ address: '/oscillator/4/frequency', args: [{ type: 'f', value: 440 }] }],
|
||||||
|
},
|
||||||
|
bundle: Buffer.concat([
|
||||||
|
Buffer.from('#bundle', 'ascii'),
|
||||||
|
Buffer.from([0x00]),
|
||||||
|
Buffer.from([0, 0, 0, 32, 0, 0, 0, 0]),
|
||||||
|
Buffer.from('00000020', 'hex'),
|
||||||
|
Buffer.from('2f6f7363696c6c61746f722f342f6672657175656e6379002c66000043dc0000', 'hex'),
|
||||||
|
]),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
describe('OSC Bundle Decoding', () => {
|
||||||
|
tests.forEach((bundleTest) => {
|
||||||
|
it(bundleTest.description, () => {
|
||||||
|
const encoded = osc.bundleFromBuffer(bundleTest.bundle);
|
||||||
|
deepEqual(encoded, bundleTest.expected);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
const { deepEqual, throws } = require('assert');
|
||||||
|
const { describe, it } = require('node:test');
|
||||||
|
const osc = require('../dist/index');
|
||||||
|
|
||||||
|
const tests = [
|
||||||
|
{
|
||||||
|
description: 'simple contents single message',
|
||||||
|
bundle: { timeTag: [32, 0], contents: [{ address: '/oscillator/4/frequency', args: [{ type: 'f', value: 440 }] }] },
|
||||||
|
expected: Buffer.concat([
|
||||||
|
Buffer.from('#bundle', 'ascii'),
|
||||||
|
Buffer.from([0x00]),
|
||||||
|
Buffer.from([0, 0, 0, 32, 0, 0, 0, 0]),
|
||||||
|
Buffer.from('00000020', 'hex'),
|
||||||
|
Buffer.from('2f6f7363696c6c61746f722f342f6672657175656e6379002c66000043dc0000', 'hex'),
|
||||||
|
]),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
describe('OSC Bundle Encoding', () => {
|
||||||
|
tests.forEach((bundleTest) => {
|
||||||
|
it(bundleTest.description, () => {
|
||||||
|
const encoded = osc.bundleToBuffer(bundleTest.bundle);
|
||||||
|
deepEqual(encoded, bundleTest.expected);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user