├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .npmignore ├── COPYING ├── eslint.config.mjs ├── examples ├── osc-float-to-int.mjs ├── osc-redirect.mjs ├── oscbundle_heartbeat.mjs ├── oscheartbeat.mjs └── printosc.mjs ├── lib ├── index.ts └── osc-utilities.ts ├── package-lock.json ├── package.json ├── readme.md ├── test └── osc-utilities.test.ts └── tsconfig.json /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | run-name: CI 3 | 4 | on: 5 | push: 6 | branches: 7 | - "*" 8 | 9 | jobs: 10 | ci: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v4 14 | - name: Use Node.js 15 | uses: actions/setup-node@v4 16 | with: 17 | node-version: "20.x" 18 | - run: npm ci 19 | - run: npm run lint 20 | - run: npm test 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist/ 3 | 4 | 5 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | lib/ 3 | test/ 4 | examples/ 5 | eslint.config.mjs 6 | tsconfig.json 7 | .github/ -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | This software is provided 'as-is', without any express or implied 2 | warranty. In no event will the authors be held liable for any damages 3 | arising from the use of this software. 4 | 5 | Permission is granted to anyone to use this software for any purpose, 6 | including commercial applications, and to alter it and redistribute it 7 | freely, subject to the following restrictions: 8 | 9 | 1. The origin of this software must not be misrepresented; you must not 10 | claim that you wrote the original software. If you use this software 11 | in a product, an acknowledgement in the product documentation would be 12 | appreciated but is not required. 13 | 2. Altered source versions must be plainly marked as such, and must not be 14 | misrepresented as being the original software. 15 | 3. This notice may not be removed or altered from any source distribution. 16 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import preferArrowFunctions from "eslint-plugin-prefer-arrow-functions"; 2 | import eslint from "@eslint/js"; 3 | import tseslint from "typescript-eslint"; 4 | import globals from "globals"; 5 | export default [ 6 | { 7 | ignores: ["**/dist", "**/test", "**/eslint.config.mjs"], 8 | }, 9 | eslint.configs.recommended, 10 | ...tseslint.configs.strict, 11 | ...tseslint.configs.stylistic, 12 | { 13 | plugins: { 14 | "prefer-arrow-functions": preferArrowFunctions, 15 | }, 16 | languageOptions: { 17 | parserOptions: { 18 | projectService: true, 19 | tsconfigRootDir: import.meta.dirname, 20 | }, 21 | }, 22 | rules: { 23 | "@typescript-eslint/no-unused-vars": [ 24 | "error", 25 | { 26 | argsIgnorePattern: "^_", 27 | caughtErrorsIgnorePattern: "^_", 28 | }, 29 | ], 30 | 31 | "@typescript-eslint/no-non-null-assertion": "off", 32 | 33 | "@typescript-eslint/restrict-template-expressions": [ 34 | "error", 35 | { 36 | allowNumber: true, 37 | }, 38 | ], 39 | 40 | "@typescript-eslint/no-unnecessary-condition": [ 41 | "error", 42 | { 43 | allowConstantLoopConditions: true, 44 | }, 45 | ], 46 | 47 | "prefer-arrow-functions/prefer-arrow-functions": [ 48 | "error", 49 | { 50 | returnStyle: "implicit", 51 | }, 52 | ], 53 | 54 | "@typescript-eslint/consistent-type-definitions": ["error", "type"], 55 | "no-warning-comments": "error", 56 | }, 57 | }, 58 | { 59 | files: ["**/examples/*.mjs", "**/examples/*.js"], 60 | ...tseslint.configs.disableTypeChecked, 61 | }, 62 | { 63 | files: ["**/examples/*.mjs", "**/examples/*.js"], 64 | languageOptions: { 65 | globals: globals.node, 66 | }, 67 | }, 68 | ]; 69 | -------------------------------------------------------------------------------- /examples/osc-float-to-int.mjs: -------------------------------------------------------------------------------- 1 | import * as osc from "osc-min"; 2 | import * as dgram from "dgram"; 3 | 4 | const inport = process.argv[2] != null ? parseInt(process.argv[2]) : 41234; 5 | const outport = process.argv[3] != null ? parseInt(process.argv[3]) : 41235; 6 | 7 | const float_to_int = (message) => { 8 | for (const arg of message.args) { 9 | if (arg.type === "float") { 10 | arg.type = "integer"; 11 | } 12 | } 13 | return message; 14 | }; 15 | 16 | const sock = dgram.createSocket("udp4", (msg) => { 17 | try { 18 | const edited = osc.applyMessageTransform(msg, (message) => 19 | float_to_int(message) 20 | ); 21 | return sock.send(edited, 0, edited.byteLength, outport, "localhost"); 22 | } catch (error) { 23 | return console.log("error redirecting", error); 24 | } 25 | }); 26 | 27 | sock.bind(inport); 28 | 29 | console.log("OSC redirecter running at http://localhost:" + inport); 30 | 31 | console.log("translating messages to http://localhost:" + outport); 32 | -------------------------------------------------------------------------------- /examples/osc-redirect.mjs: -------------------------------------------------------------------------------- 1 | import * as osc from "osc-min"; 2 | import * as dgram from "dgram"; 3 | 4 | const inport = process.argv[2] != null ? parseInt(process.argv[2]) : 41234; 5 | const outport = process.argv[3] != null ? parseInt(process.argv[3]) : 41235; 6 | 7 | console.log(`OSC redirecter running at http://localhost:${inport}`); 8 | console.log(`redirecting messages to http://localhost:${outport}`); 9 | 10 | const sock = dgram.createSocket("udp4", (msg) => { 11 | try { 12 | const redirected = osc.applyAddressTransform( 13 | msg, 14 | (address) => `/redirect${address}` 15 | ); 16 | return sock.send( 17 | redirected, 18 | 0, 19 | redirected.byteLength, 20 | outport, 21 | "localhost" 22 | ); 23 | } catch (e) { 24 | return console.log(`error redirecting: ${e}`); 25 | } 26 | }); 27 | 28 | sock.bind(inport); 29 | -------------------------------------------------------------------------------- /examples/oscbundle_heartbeat.mjs: -------------------------------------------------------------------------------- 1 | import * as osc from "osc-min"; 2 | import * as dgram from "dgram"; 3 | 4 | const udp = dgram.createSocket("udp4"); 5 | 6 | const outport = process.argv[2] != null ? parseInt(process.argv[2]) : 41234; 7 | 8 | const sendHeartbeat = () => { 9 | const buf = osc.toBuffer({ 10 | timetag: new Date(new Date().getTime() + 50), 11 | elements: [ 12 | { 13 | address: "/p1", 14 | args: new TextEncoder().encode("beat"), 15 | }, 16 | { 17 | address: "/p2", 18 | args: "string", 19 | }, 20 | { 21 | timetag: new Date(new Date().getTime() + 1000), 22 | elements: [ 23 | { 24 | address: "/p3", 25 | args: 12, 26 | }, 27 | ], 28 | }, 29 | ], 30 | }); 31 | return udp.send(buf, 0, buf.byteLength, outport, "localhost"); 32 | }; 33 | 34 | setInterval(sendHeartbeat, 2000); 35 | console.log(`sending heartbeat messages to http://localhost:${outport}`); 36 | -------------------------------------------------------------------------------- /examples/oscheartbeat.mjs: -------------------------------------------------------------------------------- 1 | import { toBuffer } from "osc-min"; 2 | import * as dgram from "dgram"; 3 | const udp = dgram.createSocket("udp4"); 4 | 5 | const outport = process.argv[2] != null ? parseInt(process.argv[2]) : 41234; 6 | 7 | console.log(`sending heartbeat messages to http://localhost:${outport}`); 8 | 9 | const sendHeartbeat = () => { 10 | const buf = toBuffer({ 11 | address: "/heartbeat", 12 | args: [ 13 | 12, 14 | "sttttring", 15 | new TextEncoder().encode("beat"), 16 | { 17 | type: "integer", 18 | value: 7, 19 | }, 20 | ], 21 | }); 22 | return udp.send(buf, 0, buf.byteLength, outport, "localhost"); 23 | }; 24 | 25 | setInterval(sendHeartbeat, 2000); 26 | -------------------------------------------------------------------------------- /examples/printosc.mjs: -------------------------------------------------------------------------------- 1 | import * as osc from "osc-min"; 2 | import * as udp from "dgram"; 3 | 4 | const inport = process.argv[2] != null ? parseInt(process.argv[2]) : 41234; 5 | 6 | console.log(`OSC listener running at http://localhost:${inport}`); 7 | 8 | const sock = udp.createSocket("udp4", (msg) => { 9 | try { 10 | console.log(osc.fromBuffer(msg)); 11 | } catch (e) { 12 | console.log("invalid OSC packet", e); 13 | } 14 | }); 15 | 16 | sock.bind(inport); 17 | -------------------------------------------------------------------------------- /lib/index.ts: -------------------------------------------------------------------------------- 1 | import * as utils from "./osc-utilities.js"; 2 | 3 | export type OscPacketInput = utils.OscPacketInput; 4 | export type OscBundleInput = utils.OscBundleInput; 5 | export type OscMessageInput = utils.OscMessageInput; 6 | export type OscArgInput = utils.OscArgInput; 7 | export type OscArgOrArrayInput = utils.OscArgOrArrayInput; 8 | export type OscPacketOutput = utils.OscPacketOutput; 9 | export type OscBundleOutput = utils.OscBundleOutput; 10 | export type OscMessageOutput = utils.OscMessageOutput; 11 | export type OscArgOutput = utils.OscArgOutput; 12 | export type OscArgOutputOrArray = utils.OscArgOutputOrArray; 13 | export type BufferInput = utils.BufferInput; 14 | export type TimeTag = utils.TimeTag; 15 | 16 | /** Takes a `TypedArray`, `DataView`, `ArrayBuffer`, of node.js `Buffer` of a complete _OSC Packet_ and 17 | * outputs the javascript representation, or throws if the buffer is ill-formed. 18 | */ 19 | export const fromBuffer = (buffer: BufferInput): OscPacketOutput => 20 | utils.fromOscPacket(buffer); 21 | 22 | /** 23 | * Takes a _OSC packet_ javascript representation as defined below and returns 24 | * a node.js Buffer, or throws if the representation is ill-formed. 25 | * 26 | * See "JavaScript representations of the OSC types" below. 27 | */ 28 | export const toBuffer = (object: OscPacketInput): DataView => 29 | utils.toOscPacket(object); 30 | 31 | /** 32 | * Takes a callback that takes a string and outputs a string, 33 | * and applies that to the address of the message encoded in the buffer, 34 | * and outputs an encoded buffer. 35 | * 36 | * If the buffer encodes an _OSC Bundle_, this applies the function to each address 37 | * in the bundle. 38 | * 39 | * There's two subtle reasons you'd want to use this function rather than 40 | * composing `fromBuffer` and `toBuffer`: 41 | * - Future-proofing - if the OSC message uses an argument typecode that 42 | * we don't understand, calling `fromBuffer` will throw. The only time 43 | * when `applyAddressTranform` might fail is if the address is malformed. 44 | * - Accuracy - javascript represents numbers as 64-bit floats, so some 45 | * OSC types will not be able to be represented accurately. If accuracy 46 | * is important to you, then, you should never convert the OSC message to a 47 | * javascript representation. 48 | */ 49 | export const applyAddressTransform = ( 50 | buffer: BufferInput, 51 | transform: (buffer: string) => string 52 | ): DataView => utils.applyTransform(buffer, utils.addressTransform(transform)); 53 | 54 | /** 55 | * Takes a function that takes and returns a javascript _OSC Message_ representation, 56 | * and applies that to each message encoded in the buffer, 57 | * and outputs a new buffer with the new address. 58 | * 59 | * If the buffer encodes an osc-bundle, this applies the function to each message 60 | * in the bundle. 61 | * 62 | * See notes above for applyAddressTransform for why you might want to use this. 63 | */ 64 | export const applyMessageTransform = ( 65 | buffer: BufferInput, 66 | transform: (buffer: OscMessageOutput) => OscMessageOutput 67 | ): DataView => utils.applyTransform(buffer, utils.messageTransform(transform)); 68 | 69 | /** 70 | * Convert a timetag array to a JavaScript Date object in your local timezone. 71 | * 72 | * Received OSC bundles converted with `fromBuffer` will have a timetag array: 73 | * [secondsSince1970, fractionalSeconds] 74 | * 75 | * This utility is useful for logging. Accuracy is reduced to milliseconds. 76 | */ 77 | export const timetagToDate: ([seconds, fractional]: TimeTag) => Date = 78 | utils.timetagToDate; 79 | 80 | /** 81 | * Convert a JavaScript Date to a NTP timetag array [secondsSince1970, fractionalSeconds]. 82 | * 83 | * `toBuffer` already accepts Dates for timetags so you might not need this function. If you need to schedule bundles with finer than millisecond accuracy then you could use this to help assemble the NTP array. 84 | */ 85 | export const dateToTimetag: (date: Date) => TimeTag = utils.dateToTimetag; 86 | -------------------------------------------------------------------------------- /lib/osc-utilities.ts: -------------------------------------------------------------------------------- 1 | export type TypedBufferLike = { 2 | buffer: ArrayBufferLike; 3 | byteOffset: number; 4 | byteLength: number; 5 | }; 6 | 7 | export type BufferInput = ArrayBuffer | TypedBufferLike; 8 | 9 | const toView = (buffer: BufferInput): DataView => { 10 | if (buffer instanceof ArrayBuffer) { 11 | return new DataView(buffer); 12 | } 13 | return new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength); 14 | }; 15 | 16 | export const toOscString = (str: string): ArrayBuffer => { 17 | if (!(typeof str === "string")) { 18 | throw new OSCError("can't pack a non-string into an osc-string"); 19 | } 20 | if (str.indexOf("\u0000") !== -1) { 21 | throw new OSCError( 22 | "Can't pack an osc-string that contains NULL characters" 23 | ); 24 | } 25 | 26 | const buffer = new TextEncoder().encode(str); 27 | const padding = 4 - (buffer.length % 4); 28 | const ret = new ArrayBuffer(buffer.length + padding); 29 | new Uint8Array(ret).set(buffer); 30 | return ret; 31 | }; 32 | 33 | export const splitOscString = ( 34 | bufferInput: BufferInput 35 | ): { 36 | value: string; 37 | rest: DataView; 38 | } => { 39 | const buffer = toView(bufferInput); 40 | const uint8Array = new Uint8Array( 41 | buffer.buffer, 42 | buffer.byteOffset, 43 | buffer.byteLength 44 | ); 45 | 46 | // First, find the first null character. 47 | const nullIndex = uint8Array.indexOf(0); 48 | if (nullIndex === -1) { 49 | throw new OSCError("All osc-strings must contain a null character"); 50 | } 51 | const stringPart = uint8Array.slice(0, nullIndex); 52 | const padding = 4 - (stringPart.length % 4); 53 | 54 | if (uint8Array.length < nullIndex + padding) { 55 | throw new OSCError(`Not enough padding for osc-string`); 56 | } 57 | // Verify padding is all zeros 58 | for (let i = 0; i < padding; i++) { 59 | if (uint8Array[stringPart.length + i] !== 0) { 60 | throw new OSCError("Corrupt padding for osc-string"); 61 | } 62 | } 63 | return { 64 | value: new TextDecoder().decode(stringPart), 65 | rest: sliceDataView(buffer, nullIndex + padding), 66 | }; 67 | }; 68 | 69 | export type TimeTag = [number, number]; 70 | 71 | const sliceDataView = (dataView: DataView, start: number) => 72 | new DataView( 73 | dataView.buffer, 74 | dataView.byteOffset + start, 75 | dataView.byteLength - start 76 | ); 77 | 78 | export const splitInteger = ( 79 | bufferInput: BufferInput 80 | ): { 81 | value: number; 82 | rest: DataView; 83 | } => { 84 | const buffer = toView(bufferInput); 85 | const bytes = 4; 86 | if (buffer.byteLength < bytes) { 87 | throw new OSCError("buffer is not big enough for integer type"); 88 | } 89 | return { 90 | value: buffer.getInt32(0, false), 91 | rest: sliceDataView(buffer, bytes), 92 | }; 93 | }; 94 | 95 | export const splitTimetag = ( 96 | bufferInput: BufferInput 97 | ): { 98 | value: TimeTag; 99 | rest: DataView; 100 | } => { 101 | const buffer = toView(bufferInput); 102 | const bytes = 4; 103 | if (buffer.byteLength < bytes * 2) { 104 | throw new OSCError("buffer is not big enough to contain a timetag"); 105 | } 106 | const seconds = buffer.getUint32(0, false); 107 | const fractional = buffer.getUint32(bytes, false); 108 | return { 109 | value: [seconds, fractional], 110 | rest: sliceDataView(buffer, bytes * 2), 111 | }; 112 | }; 113 | 114 | const UNIX_EPOCH = 2208988800; 115 | 116 | const TWO_POW_32 = 4294967296; 117 | 118 | export const dateToTimetag = (date: Date): TimeTag => { 119 | const timeStamp = date.getTime() / 1000; 120 | const wholeSecs = Math.floor(timeStamp); 121 | return makeTimetag(wholeSecs, timeStamp - wholeSecs); 122 | }; 123 | 124 | const makeTimetag = (unixseconds: number, fracSeconds: number): TimeTag => { 125 | const ntpSecs = unixseconds + UNIX_EPOCH; 126 | const ntpFracs = Math.round(TWO_POW_32 * fracSeconds); 127 | return [ntpSecs, ntpFracs]; 128 | }; 129 | 130 | export const timetagToDate = ([seconds, fractional]: TimeTag): Date => { 131 | const date = new Date(); 132 | date.setTime( 133 | (seconds - UNIX_EPOCH) * 1000 + (fractional * 1000) / TWO_POW_32 134 | ); 135 | return date; 136 | }; 137 | 138 | export const toTimetagBuffer = (timetag: Date | TimeTag): ArrayBuffer => { 139 | let high, low; 140 | if (typeof timetag === "object" && "getTime" in timetag) { 141 | [high, low] = dateToTimetag(timetag); 142 | } else { 143 | [high, low] = timetag; 144 | } 145 | const ret = new DataView(new ArrayBuffer(8)); 146 | ret.setUint32(0, high, false); 147 | ret.setUint32(4, low, false); 148 | return ret.buffer; 149 | }; 150 | 151 | export const toIntegerBuffer = (number: number): ArrayBuffer => { 152 | const ret = new DataView(new ArrayBuffer(4)); 153 | ret.setInt32(0, number, false); 154 | return ret.buffer; 155 | }; 156 | 157 | const parseOscArg = ( 158 | code: string, 159 | buffer: BufferInput 160 | ): 161 | | { 162 | value: OscArgOutput; 163 | rest: DataView; 164 | } 165 | | undefined => { 166 | switch (code) { 167 | case "s": { 168 | const { value, rest } = splitOscString(buffer); 169 | return { value: { type: "string", value }, rest }; 170 | } 171 | case "S": { 172 | const { value, rest } = splitOscString(buffer); 173 | return { value: { type: "symbol", value }, rest }; 174 | } 175 | case "i": { 176 | const { value, rest } = splitInteger(buffer); 177 | return { value: { type: "integer", value }, rest }; 178 | } 179 | case "t": { 180 | const { value, rest } = splitTimetag(buffer); 181 | return { value: { type: "timetag", value }, rest }; 182 | } 183 | case "f": { 184 | const view = toView(buffer); 185 | return { 186 | value: { type: "float", value: view.getFloat32(0, false) }, 187 | rest: sliceDataView(view, 4), 188 | }; 189 | } 190 | case "d": { 191 | const view = toView(buffer); 192 | return { 193 | value: { type: "double", value: view.getFloat64(0, false) }, 194 | rest: sliceDataView(view, 8), 195 | }; 196 | } 197 | case "b": { 198 | const view = toView(buffer); 199 | const { value: length, rest: data } = splitInteger(view); 200 | // We added padding to make sure the blob's length in the buffer is a multiple of 4 201 | const padding = (4 - (length % 4)) % 4; 202 | 203 | return { 204 | value: { 205 | type: "blob", 206 | value: new DataView(data.buffer, data.byteOffset, length), 207 | }, 208 | rest: sliceDataView(data, length + padding), 209 | }; 210 | } 211 | case "r": { 212 | const view = toView(buffer); 213 | return { 214 | value: { 215 | type: "color", 216 | value: { 217 | red: view.getUint8(0), 218 | green: view.getUint8(1), 219 | blue: view.getUint8(2), 220 | alpha: view.getUint8(3), 221 | }, 222 | }, 223 | rest: sliceDataView(view, 4), 224 | }; 225 | } 226 | case "T": 227 | return { 228 | value: { type: "true", value: true }, 229 | rest: toView(buffer), 230 | }; 231 | case "F": 232 | return { 233 | value: { type: "false", value: false }, 234 | rest: toView(buffer), 235 | }; 236 | case "N": 237 | return { 238 | value: { type: "null", value: null }, 239 | rest: toView(buffer), 240 | }; 241 | case "I": 242 | return { 243 | value: { type: "bang", value: "bang" }, 244 | rest: toView(buffer), 245 | }; 246 | case "c": { 247 | const view = toView(buffer); 248 | const codepoint = view.getUint32(0, false); 249 | return { 250 | value: { type: "character", value: String.fromCodePoint(codepoint) }, 251 | rest: sliceDataView(view, 4), 252 | }; 253 | } 254 | case "h": { 255 | const view = toView(buffer); 256 | const bigint = view.getBigInt64(0, false); 257 | return { 258 | value: { type: "bigint", value: bigint }, 259 | rest: sliceDataView(view, 8), 260 | }; 261 | } 262 | case "m": { 263 | const view = toView(buffer); 264 | if (view.byteLength < 4) { 265 | throw new OSCError("buffer is not big enough to contain a midi packet"); 266 | } 267 | const array = new Uint8Array(view.buffer, view.byteOffset, 4); 268 | return { 269 | value: { type: "midi", value: [...array] as OscMidiPacket }, 270 | rest: sliceDataView(view, 4), 271 | }; 272 | } 273 | } 274 | return undefined; 275 | }; 276 | 277 | const toOscArgument = (arg: OscArgWithType): ArrayBuffer => { 278 | switch (arg.type) { 279 | case "string": 280 | return toOscString(arg.value); 281 | case "symbol": 282 | return toOscString(arg.value); 283 | case "integer": 284 | return toIntegerBuffer(arg.value); 285 | case "timetag": 286 | return toTimetagBuffer(arg.value); 287 | case "character": { 288 | const chars = [...arg.value]; 289 | 290 | if (chars.length !== 1) { 291 | throw new OSCError("Can only send a single character"); 292 | } 293 | 294 | const ret = new DataView(new ArrayBuffer(4)); 295 | // ! is safe here because we checked length === 1 above 296 | ret.setUint32(0, chars[0]!.codePointAt(0) ?? 0, false); 297 | return ret.buffer; 298 | } 299 | case "float": { 300 | const ret = new DataView(new ArrayBuffer(4)); 301 | ret.setFloat32(0, arg.value, false); 302 | return ret.buffer; 303 | } 304 | case "bigint": { 305 | const ret = new DataView(new ArrayBuffer(8)); 306 | ret.setBigInt64(0, arg.value, false); 307 | return ret.buffer; 308 | } 309 | case "double": { 310 | const ret = new DataView(new ArrayBuffer(8)); 311 | ret.setFloat64(0, arg.value, false); 312 | return ret.buffer; 313 | } 314 | case "blob": { 315 | const view = toView(arg.value); 316 | 317 | // Add padding to make the blob's length a multiple of 4 318 | const padding = (4 - (arg.value.byteLength % 4)) % 4; 319 | 320 | const ret = new DataView( 321 | new ArrayBuffer(4 + arg.value.byteLength + padding) 322 | ); 323 | ret.setUint32(0, arg.value.byteLength, false); 324 | new Uint8Array(ret.buffer, ret.byteOffset + 4).set( 325 | new Uint8Array(view.buffer, view.byteOffset, view.byteLength) 326 | ); 327 | return ret.buffer; 328 | } 329 | case "color": { 330 | const ret = new DataView(new ArrayBuffer(4 * 4)); 331 | ret.setUint8(0, arg.value.red); 332 | ret.setUint8(1, arg.value.green); 333 | ret.setUint8(2, arg.value.blue); 334 | ret.setUint8(3, arg.value.alpha); 335 | return ret.buffer; 336 | } 337 | case "midi": { 338 | return new Uint8Array(arg.value).buffer; 339 | } 340 | case "true": 341 | return new ArrayBuffer(0); 342 | case "false": 343 | return new ArrayBuffer(0); 344 | case "null": 345 | return new ArrayBuffer(0); 346 | case "bang": 347 | return new ArrayBuffer(0); 348 | } 349 | }; 350 | 351 | export type OscTypeCode = 352 | | "s" 353 | | "i" 354 | | "t" 355 | | "f" 356 | | "d" 357 | | "b" 358 | | "T" 359 | | "F" 360 | | "N" 361 | | "I" 362 | | "S" 363 | | "c" 364 | | "r" 365 | | "m" 366 | | "h"; 367 | 368 | const RepresentationToTypeCode: { 369 | [key in OscArgWithType["type"]]: OscTypeCode; 370 | } = { 371 | string: "s", 372 | integer: "i", 373 | timetag: "t", 374 | float: "f", 375 | double: "d", 376 | blob: "b", 377 | true: "T", 378 | false: "F", 379 | null: "N", 380 | bang: "I", 381 | symbol: "S", 382 | character: "c", 383 | color: "r", 384 | midi: "m", 385 | bigint: "h", 386 | }; 387 | 388 | export type OscMidiPacket = [number, number, number, number]; 389 | 390 | export type OscArgOutput = 391 | | { 392 | type: "string"; 393 | value: string; 394 | } 395 | | { 396 | type: "symbol"; 397 | value: string; 398 | } 399 | | { 400 | type: "integer"; 401 | value: number; 402 | } 403 | | { 404 | type: "bigint"; 405 | value: bigint; 406 | } 407 | | { 408 | type: "timetag"; 409 | value: TimeTag; 410 | } 411 | | { 412 | type: "float"; 413 | value: number; 414 | } 415 | | { 416 | type: "double"; 417 | value: number; 418 | } 419 | | { 420 | type: "blob"; 421 | value: DataView; 422 | } 423 | | { 424 | type: "midi"; 425 | value: OscMidiPacket; 426 | } 427 | | { 428 | type: "color"; 429 | value: OscColor; 430 | } 431 | | { 432 | type: "true"; 433 | value: true; 434 | } 435 | | { 436 | type: "false"; 437 | value: false; 438 | } 439 | | { 440 | type: "null"; 441 | value: null; 442 | } 443 | | { 444 | type: "bang"; 445 | value: "bang"; 446 | } 447 | | { 448 | type: "character"; 449 | value: string; 450 | }; 451 | 452 | export type OscArgOutputOrArray = 453 | | OscArgOutput 454 | | { type: "array"; value: OscArgOutputOrArray[] }; 455 | 456 | export type OscColor = { 457 | red: number; 458 | green: number; 459 | blue: number; 460 | alpha: number; 461 | }; 462 | 463 | export type OscArgWithType = 464 | | { 465 | type: "string"; 466 | value: string; 467 | } 468 | | { 469 | type: "symbol"; 470 | value: string; 471 | } 472 | | { 473 | type: "integer"; 474 | value: number; 475 | } 476 | | { 477 | type: "bigint"; 478 | value: bigint; 479 | } 480 | | { 481 | type: "timetag"; 482 | value: TimeTag | Date; 483 | } 484 | | { 485 | type: "float"; 486 | value: number; 487 | } 488 | | { 489 | type: "double"; 490 | value: number; 491 | } 492 | | { 493 | type: "blob"; 494 | value: ArrayBuffer | TypedBufferLike; 495 | } 496 | | { 497 | type: "character"; 498 | value: string; 499 | } 500 | | { 501 | type: "color"; 502 | value: OscColor; 503 | } 504 | | { 505 | type: "midi"; 506 | value: OscMidiPacket; 507 | } 508 | | { 509 | type: "true"; 510 | } 511 | | { 512 | type: "false"; 513 | } 514 | | { 515 | type: "null"; 516 | } 517 | | { 518 | type: "bang"; 519 | }; 520 | 521 | export type OscArgInput = 522 | | OscArgWithType 523 | | string 524 | | number 525 | | bigint 526 | | Date 527 | | OscColor 528 | | ArrayBuffer 529 | | TypedBufferLike 530 | | true 531 | | false 532 | | "bang" 533 | | null; 534 | 535 | export type OscArgOrArrayInput = 536 | | OscArgInput 537 | | OscArgOrArrayInput[] 538 | | { type: "array"; value: OscArgOrArrayInput[] }; 539 | 540 | const toOscArgWithType = (arg: OscArgInput): OscArgWithType => { 541 | if (arg === null) { 542 | return { type: "null" }; 543 | } 544 | if (typeof arg === "object" && "type" in arg) { 545 | return arg; 546 | } 547 | if (arg === "bang") { 548 | return { type: "bang" }; 549 | } 550 | if (typeof arg === "string") { 551 | return { type: "string", value: arg }; 552 | } 553 | if (typeof arg === "number") { 554 | return { type: "float", value: arg }; 555 | } 556 | if (arg instanceof Date) { 557 | return { type: "timetag", value: arg }; 558 | } 559 | if (arg instanceof ArrayBuffer) { 560 | return { type: "blob", value: arg }; 561 | } 562 | if (typeof arg === "object" && "buffer" in arg) { 563 | return { type: "blob", value: arg }; 564 | } 565 | if (typeof arg === "object" && "red" in arg) { 566 | return { type: "color", value: arg }; 567 | } 568 | if (typeof arg === "bigint") { 569 | return { type: "bigint", value: arg }; 570 | } 571 | if (arg === true) { 572 | return { type: "true" }; 573 | } 574 | // This unnecessary condition helps exhaustivity checking 575 | // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition 576 | if (arg === false) { 577 | return { type: "false" }; 578 | } 579 | arg satisfies never; 580 | throw new OSCError("Invalid argument: " + arg); 581 | }; 582 | 583 | export type OscMessageOutput = { 584 | address: string; 585 | args: OscArgOutputOrArray[]; 586 | oscType: "message"; 587 | }; 588 | 589 | export const fromOscMessage = (buffer: BufferInput): OscMessageOutput => { 590 | const { value: address, rest } = splitOscString(buffer); 591 | buffer = rest; 592 | if (address[0] !== "/") { 593 | throw new OSCError("addresses must start with /"); 594 | } 595 | if (!buffer.byteLength) { 596 | return { 597 | address: address, 598 | args: [], 599 | oscType: "message", 600 | }; 601 | } 602 | const split = splitOscString(buffer); 603 | const types = split.value; 604 | buffer = split.rest; 605 | if (types[0] !== ",") { 606 | throw new OSCError("Argument lists must begin with ,"); 607 | } 608 | const args: OscArgOutputOrArray[] = []; 609 | const arrayStack = [args]; 610 | for (const parsedType of types.slice(1)) { 611 | if (parsedType === "[") { 612 | arrayStack.push([]); 613 | continue; 614 | } 615 | if (parsedType === "]") { 616 | if (arrayStack.length <= 1) { 617 | throw new OSCError("Mismatched ']' character."); 618 | } else { 619 | const built = arrayStack.pop()!; 620 | arrayStack[arrayStack.length - 1]!.push({ 621 | type: "array", 622 | value: built, 623 | }); 624 | } 625 | continue; 626 | } 627 | const parsed = parseOscArg(parsedType, buffer); 628 | if (parsed === undefined) { 629 | throw new OSCError("I don't understand the argument code " + parsedType); 630 | } 631 | buffer = parsed.rest; 632 | arrayStack[arrayStack.length - 1]!.push(parsed.value); 633 | } 634 | if (arrayStack.length !== 1) { 635 | throw new OSCError("Mismatched '[' character"); 636 | } 637 | return { 638 | address: address, 639 | args: args, 640 | oscType: "message", 641 | }; 642 | }; 643 | 644 | export type OscBundleOutput = { 645 | timetag: TimeTag; 646 | elements: OscPacketOutput[]; 647 | oscType: "bundle"; 648 | }; 649 | 650 | export type OscPacketOutput = OscMessageOutput | OscBundleOutput; 651 | 652 | export const fromOscBundle = (buffer: BufferInput): OscBundleOutput => { 653 | const split1 = splitOscString(buffer); 654 | const bundleTag = split1.value; 655 | buffer = split1.rest; 656 | if (bundleTag !== "#bundle") { 657 | throw new OSCError("osc-bundles must begin with #bundle"); 658 | } 659 | const split2 = splitTimetag(buffer); 660 | const timetag = split2.value; 661 | buffer = split2.rest; 662 | const convertedElems = mapBundleList(buffer, (buffer: BufferInput) => 663 | fromOscPacket(buffer) 664 | ); 665 | return { 666 | timetag: timetag, 667 | elements: convertedElems, 668 | oscType: "bundle", 669 | }; 670 | }; 671 | 672 | export const fromOscPacket = (buffer: BufferInput): OscPacketOutput => { 673 | if (isOscBundleBuffer(buffer)) { 674 | return fromOscBundle(buffer); 675 | } else { 676 | return fromOscMessage(buffer); 677 | } 678 | }; 679 | 680 | const toOscTypeAndArgs = ( 681 | args: OscArgOrArrayInput[] 682 | ): { 683 | type: string; 684 | args: ArrayBuffer[]; 685 | } => { 686 | let osctype = ""; 687 | let oscargs: ArrayBuffer[] = []; 688 | for (const arg of args) { 689 | if ( 690 | arg !== null && 691 | (Array.isArray(arg) || 692 | (typeof arg === "object" && "type" in arg && arg.type === "array")) 693 | ) { 694 | const { type, args: newargs } = toOscTypeAndArgs( 695 | Array.isArray(arg) ? arg : arg.value 696 | ); 697 | osctype += "[" + type + "]"; 698 | oscargs = oscargs.concat(newargs); 699 | } else { 700 | const withType = toOscArgWithType(arg); 701 | const typeCode = RepresentationToTypeCode[withType.type]; 702 | const buff = toOscArgument(withType); 703 | osctype += typeCode; 704 | oscargs.push(buff); 705 | } 706 | } 707 | return { type: osctype, args: oscargs }; 708 | }; 709 | 710 | export const concat = (buffers: BufferInput[]): ArrayBuffer => { 711 | const totalLength = buffers.reduce( 712 | (acc, buffer: BufferInput) => acc + buffer.byteLength, 713 | 0 714 | ); 715 | const result = new Uint8Array(totalLength); 716 | let offset = 0; 717 | for (const buffer of buffers) { 718 | const view = toView(buffer); 719 | result.set( 720 | new Uint8Array(view.buffer, view.byteOffset, view.byteLength), 721 | offset 722 | ); 723 | offset += view.byteLength; 724 | } 725 | return result.buffer; 726 | }; 727 | 728 | export type OscMessageInput = 729 | | string 730 | | { address: string; args?: OscArgOrArrayInput[] | OscArgInput }; 731 | 732 | export const toOscMessage = (message: OscMessageInput): DataView => { 733 | const address = typeof message === "string" ? message : message.address; 734 | const rawArgs = 735 | typeof message === "string" 736 | ? [] 737 | : message.args === undefined 738 | ? [] 739 | : Array.isArray(message.args) 740 | ? message.args 741 | : [message.args]; 742 | const oscaddr = toOscString(address); 743 | const { type, args } = toOscTypeAndArgs(rawArgs); 744 | return new DataView(concat([oscaddr, toOscString("," + type)].concat(args))); 745 | }; 746 | 747 | export type OscBundleInput = { 748 | timetag: TimeTag | Date; 749 | elements?: OscPacketInput[] | OscPacketInput; 750 | }; 751 | 752 | export type OscPacketInput = OscBundleInput | OscMessageInput; 753 | 754 | export const toOscBundle = (bundle: OscBundleInput): DataView => { 755 | const elements = 756 | bundle.elements === undefined 757 | ? [] 758 | : Array.isArray(bundle.elements) 759 | ? bundle.elements 760 | : [bundle.elements]; 761 | const oscBundleTag = toOscString("#bundle"); 762 | const oscTimeTag = toTimetagBuffer(bundle.timetag); 763 | const oscElems = elements.reduce((acc, x) => { 764 | const buffer = toOscPacket(x); 765 | const size = toIntegerBuffer(buffer.byteLength); 766 | return acc.concat([new DataView(size), buffer]); 767 | }, new Array()); 768 | return new DataView(concat([oscBundleTag, oscTimeTag, ...oscElems])); 769 | }; 770 | 771 | export const toOscPacket = (packet: OscPacketInput): DataView => { 772 | if (typeof packet === "object" && "timetag" in packet) { 773 | return toOscBundle(packet); 774 | } else { 775 | return toOscMessage(packet); 776 | } 777 | }; 778 | 779 | export const applyMessageTranformerToBundle = 780 | (transform: (buffer: DataView) => DataView) => 781 | (buffer: DataView): DataView => { 782 | const splitStart = splitOscString(buffer); 783 | buffer = splitStart.rest; 784 | if (splitStart.value !== "#bundle") { 785 | throw new OSCError("osc-bundles must begin with #bundle"); 786 | } 787 | const bundleTagBuffer = toOscString(splitStart.value); 788 | const timetagBuffer = new DataView(buffer.buffer, buffer.byteOffset, 8); 789 | buffer = sliceDataView(buffer, 8); 790 | const elems = mapBundleList(buffer, (buffer) => 791 | applyTransform( 792 | buffer, 793 | transform, 794 | applyMessageTranformerToBundle(transform) 795 | ) 796 | ); 797 | const totalLength = 798 | bundleTagBuffer.byteLength + 799 | timetagBuffer.byteLength + 800 | elems.reduce((acc, elem) => acc + 4 + elem.byteLength, 0); 801 | const outBuffer = new Uint8Array(totalLength); 802 | outBuffer.set(new Uint8Array(bundleTagBuffer), 0); 803 | outBuffer.set( 804 | new Uint8Array( 805 | timetagBuffer.buffer, 806 | timetagBuffer.byteOffset, 807 | timetagBuffer.byteLength 808 | ), 809 | bundleTagBuffer.byteLength 810 | ); 811 | let copyIndex = bundleTagBuffer.byteLength + timetagBuffer.byteLength; 812 | for (const elem of elems) { 813 | outBuffer.set( 814 | new Uint8Array(toIntegerBuffer(elem.byteLength)), 815 | copyIndex 816 | ); 817 | copyIndex += 4; 818 | outBuffer.set( 819 | new Uint8Array(elem.buffer, elem.byteOffset, elem.byteLength), 820 | copyIndex 821 | ); 822 | copyIndex += elem.byteLength; 823 | } 824 | return new DataView( 825 | outBuffer.buffer, 826 | outBuffer.byteOffset, 827 | outBuffer.byteLength 828 | ); 829 | }; 830 | 831 | export const applyTransform = ( 832 | buffer: BufferInput, 833 | mTransform: (buffer: DataView) => DataView, 834 | bundleTransform?: (buffer: DataView) => DataView 835 | ): DataView => { 836 | if (bundleTransform == null) { 837 | bundleTransform = applyMessageTranformerToBundle(mTransform); 838 | } 839 | const view = toView(buffer); 840 | if (isOscBundleBuffer(view)) { 841 | return bundleTransform(view); 842 | } else { 843 | return mTransform(view); 844 | } 845 | }; 846 | 847 | export const addressTransform = 848 | (transform: (string: string) => string) => 849 | (buffer: DataView): DataView => { 850 | const { value, rest } = splitOscString(buffer); 851 | return new DataView(concat([toOscString(transform(value)), rest])); 852 | }; 853 | 854 | export const messageTransform = 855 | (transform: (message: OscMessageOutput) => OscMessageOutput) => 856 | (buffer: DataView): DataView => 857 | toOscMessage(transform(fromOscMessage(buffer))); 858 | 859 | class OSCError extends Error { 860 | constructor(message: string) { 861 | super(message); 862 | this.name = "OSCError"; 863 | } 864 | } 865 | 866 | const isOscBundleBuffer = (buffer: BufferInput) => { 867 | const string = splitOscString(buffer).value; 868 | return string === "#bundle"; 869 | }; 870 | 871 | const mapBundleList = ( 872 | buffer: BufferInput, 873 | func: (buffer: BufferInput) => T 874 | ): T[] => { 875 | let view = toView(buffer); 876 | const results = new Array(); 877 | while (view.byteLength) { 878 | const { value: size, rest } = splitInteger(view); 879 | view = rest; 880 | if (size > view.byteLength) { 881 | throw new OSCError( 882 | "Invalid bundle list: size of element is bigger than buffer" 883 | ); 884 | } 885 | const subView = new DataView(view.buffer, view.byteOffset, size); 886 | try { 887 | // If there's an exception thrown from the map function, just ignore 888 | // this result. 889 | results.push(func(subView)); 890 | } catch (_) { 891 | /* empty */ 892 | } 893 | view = sliceDataView(view, size); 894 | } 895 | return results; 896 | }; 897 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "osc-min", 3 | "version": "2.1.2", 4 | "author": { 5 | "name": "Russell McClellan", 6 | "email": "russell.mcclellan@gmail.com", 7 | "url": "http://www.russellmcc.com" 8 | }, 9 | "description": "Simple utilities for open sound control in node.js", 10 | "keywords": [ 11 | "open sound control", 12 | "OSC", 13 | "music control", 14 | "NIME" 15 | ], 16 | "repository": { 17 | "type": "git", 18 | "url": "http://github.com/russellmcc/node-osc-min.git" 19 | }, 20 | "exports": { 21 | "types": "./dist/index.d.ts", 22 | "import": "./dist/index.js" 23 | }, 24 | "devDependencies": { 25 | "@eslint/js": "^9.12.0", 26 | "@types/jest": "^29.5.13", 27 | "eslint": "^9.12.0", 28 | "eslint-plugin-prefer-arrow-functions": "^3.4.1", 29 | "globals": "^15.11.0", 30 | "jest": "^29.7.0", 31 | "markdown-magic": "^3.3.0", 32 | "ts-jest": "^29.2.5", 33 | "typescript": "^5.6.3", 34 | "typescript-eslint": "^8.8.1" 35 | }, 36 | "directories": { 37 | "lib": "lib", 38 | "examples": "examples" 39 | }, 40 | "license": "Zlib", 41 | "engines": { 42 | "node": ">=0.10.0" 43 | }, 44 | "type": "module", 45 | "scripts": { 46 | "prepublish": "npm run build && npm run docs", 47 | "build": "tsc", 48 | "docs": "md-magic readme.md", 49 | "test": "NODE_OPTIONS=--experimental-vm-modules jest", 50 | "lint": "eslint" 51 | }, 52 | "jest": { 53 | "testEnvironment": "node", 54 | "testRegex": "(/test/.*\\.test\\.ts$)", 55 | "transform": { 56 | "^.+.ts$": [ 57 | "ts-jest", 58 | { 59 | "tsconfig": { 60 | "strict": false, 61 | "strictNullChecks": true 62 | } 63 | } 64 | ] 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # osc-min 2 | 3 | _simple utilities for open sound control in node.js_ 4 | 5 | This package provides some node.js utilities for working with 6 | [OSC](http://opensoundcontrol.org/), a format for sound and systems control. 7 | Here we implement the [OSC 1.1][spec11] specification. OSC is a transport-independent 8 | protocol, so we don't provide any server objects, as you should be able to 9 | use OSC over any transport you like. The most common is probably udp, but tcp 10 | is not unheard of. 11 | 12 | [spec11]: http://opensoundcontrol.org/spec-1_1 13 | 14 | --- 15 | 16 | ## Examples 17 | 18 | Further examples available in the `examples` folder. 19 | 20 | ### A simple OSC server that prints any received messages 21 | 22 | 23 | ```js 24 | const sock = udp.createSocket("udp4", (msg) => { 25 | try { 26 | console.log(osc.fromBuffer(msg)); 27 | } catch (e) { 28 | console.log("invalid OSC packet", e); 29 | } 30 | }); 31 | 32 | sock.bind(inport); 33 | ``` 34 | 35 | 36 | ### Send a message containing multiple arguments every 2 seconds 37 | 38 | 39 | ```js 40 | const sendHeartbeat = () => { 41 | const buf = toBuffer({ 42 | address: "/heartbeat", 43 | args: [ 44 | 12, 45 | "sttttring", 46 | new TextEncoder().encode("beat"), 47 | { 48 | type: "integer", 49 | value: 7, 50 | }, 51 | ], 52 | }); 53 | return udp.send(buf, 0, buf.byteLength, outport, "localhost"); 54 | }; 55 | 56 | setInterval(sendHeartbeat, 2000); 57 | ``` 58 | 59 | 60 | ### A simple OSC re-director 61 | 62 | 63 | ```js 64 | const sock = dgram.createSocket("udp4", (msg) => { 65 | try { 66 | const redirected = osc.applyAddressTransform( 67 | msg, 68 | (address) => `/redirect${address}` 69 | ); 70 | return sock.send( 71 | redirected, 72 | 0, 73 | redirected.byteLength, 74 | outport, 75 | "localhost" 76 | ); 77 | } catch (e) { 78 | return console.log(`error redirecting: ${e}`); 79 | } 80 | }); 81 | 82 | sock.bind(inport); 83 | ``` 84 | 85 | 86 | --- 87 | 88 | ## Javascript representations of the OSC types. 89 | 90 | See the [spec][spec] for more information on the OSC types. 91 | 92 | - An _OSC Packet_ is an _OSC Message_ or an _OSC Bundle_. 93 | 94 | - An _OSC Message_: 95 | 96 | { 97 | oscType : "message" 98 | address : "/address/pattern/might/have/wildcards" 99 | args : [arg1,arg2] 100 | } 101 | 102 | Where args is an array of _OSC Arguments_. `oscType` is optional. 103 | `args` can be a single element. 104 | 105 | - An _OSC Argument_ is represented as a javascript object with the following layout: 106 | 107 | { 108 | type : "string" 109 | value : "value" 110 | } 111 | 112 | Where the `type` is one of the following: 113 | 114 | - `string` - string value 115 | - `float` - numeric value 116 | - `integer` - numeric value 117 | - `color` - JS object containing `red`, `green`, `blue`, `alpha` in range 0-255 118 | - `midi` - four-element array of numbers representing a midi packet of data 119 | - `symbol` - string value 120 | - `character` - a single-character string 121 | - `double` - numeric value 122 | - `bigint` - 64-bit `bigint` value (watch out, this will be truncated to 64 bits!) 123 | - `blob` - `ArrayBuffer`, `DataView`, `TypedArray` or node.js `Buffer` 124 | - `true` - value is boolean true 125 | - `false` - value is boolean false 126 | - `null` - no value 127 | - `bang` - no value (this is the `I` type tag) 128 | - `timetag` - Javascript `Date` 129 | - `array` - array of _OSC Arguments_ 130 | 131 | Note that `type` is always a string - i.e. `"true"` rather than `true`. 132 | 133 | The following non-standard types are also supported: 134 | 135 | - `double` - numeric value (encodes to a float64 value) 136 | 137 | For messages sent to the `toBuffer` function, `type` is optional. 138 | If the argument is not an object, it will be interpreted as either 139 | `string`, `float`, `array` or `blob`, depending on its javascript type 140 | (String, Number, Array, Buffer, respectively) 141 | 142 | - An _OSC Bundle_ is represented as a javascript object with the following fields: 143 | 144 | { 145 | oscType : "bundle" 146 | timetag : 7 147 | elements : [element1, element] 148 | } 149 | 150 | `oscType` "bundle" 151 | 152 | `timetag` is one of: 153 | 154 | - `Date` - a JavaScript Date object 155 | - `Array` - `[numberOfSecondsSince1900, fractionalSeconds]` 156 | Both values are `number`s. This gives full timing accuracy of 1/(2^32) seconds. 157 | 158 | `elements` is an `Array` of either _OSC Message_ or _OSC Bundle_ 159 | 160 | ## [spec]: http://opensoundcontrol.org/spec-1_0 161 | 162 | ## Migrating from 1.0 163 | 164 | There have been a few breaking changes from the 1.0 version: 165 | 166 | - We now provide type declarations for typescript compatibility 167 | - We always enable the previously optional "strict" errors 168 | - Many explicit error messages for passing in data of the wrong type have been removed. We encourage you to use typescript to prevent these sorts of errors. 169 | - Functions that used to return `Buffer` now return `DataView` 170 | - TimeTags must be specified as `Date`s or `[number, number]` arrays, and are always returned as `[number, number]` arrays. To convert between arrays and `Date`s, use `dateToTimetag` and `timetagToDate`. 171 | - The two-argument version of `toBuffer` has been removed. 172 | 173 | ## License 174 | 175 | Licensed under the terms found in COPYING (zlib license) 176 | -------------------------------------------------------------------------------- /test/osc-utilities.test.ts: -------------------------------------------------------------------------------- 1 | import * as osc from "../lib/osc-utilities"; 2 | import * as assert from "assert"; 3 | 4 | const testString = function (str, expected_len) { 5 | return { 6 | str: str, 7 | len: expected_len, 8 | }; 9 | }; 10 | 11 | const testData = [ 12 | testString("abc", 4), 13 | testString("abcd", 8), 14 | testString("abcde", 8), 15 | testString("abcdef", 8), 16 | testString("abcdefg", 8), 17 | ]; 18 | 19 | const testStringLength = function (str, expected_len) { 20 | const oscstr = osc.toOscString(str); 21 | assert.strictEqual(oscstr.byteLength, expected_len); 22 | }; 23 | 24 | test("basic strings length", function () { 25 | var data, k, len; 26 | for (k = 0, len = testData.length; k < len; k++) { 27 | data = testData[k]; 28 | testStringLength(data.str, data.len); 29 | } 30 | }); 31 | 32 | const testStringRoundTrip = function (str) { 33 | assert.strictEqual(str, osc.splitOscString(osc.toOscString(str)).value); 34 | }; 35 | 36 | test("basic strings round trip", function () { 37 | var data, k, len; 38 | for (k = 0, len = testData.length; k < len; k++) { 39 | data = testData[k]; 40 | testStringRoundTrip(data.str); 41 | } 42 | }); 43 | 44 | test("strings with null characters fail toOscString", function () { 45 | assert.throws(function () { 46 | osc.toOscString("\u0000"); 47 | }); 48 | }); 49 | 50 | test("osc buffers with no null characters fail splitOscString", function () { 51 | assert.throws(function () { 52 | osc.splitOscString(new TextEncoder().encode("abc")); 53 | }); 54 | }); 55 | 56 | test("osc buffers with non-null characters after a null character fail fromOscMessage in mode", function () { 57 | assert.throws(function () { 58 | osc.fromOscMessage(new TextEncoder().encode("abc\u0000abcd")); 59 | }); 60 | }); 61 | 62 | test("basic strings pass fromOscString in mode", function () { 63 | var data, k, len; 64 | for (k = 0, len = testData.length; k < len; k++) { 65 | data = testData[k]; 66 | testStringRoundTrip(data.str); 67 | } 68 | }); 69 | 70 | test("osc buffers with non-four length fail in mode", function () { 71 | assert.throws(function () { 72 | osc.fromOscMessage(new TextEncoder().encode("abcd\u0000\u0000")); 73 | }); 74 | }); 75 | 76 | test("splitOscString of an osc-string matches the string", function () { 77 | const { value, rest } = osc.splitOscString(osc.toOscString("testing it")); 78 | assert.strictEqual(value, "testing it"); 79 | assert.strictEqual(rest.byteLength, 0); 80 | }); 81 | 82 | test("splitOscString works with an over-allocated buffer", function () { 83 | const overallocated = new ArrayBuffer(16); 84 | new Uint8Array(overallocated).set( 85 | new Uint8Array(osc.toOscString("testing it")) 86 | ); 87 | const { value, rest } = osc.splitOscString(overallocated); 88 | assert.strictEqual(value, "testing it"); 89 | assert.strictEqual(rest.byteLength, 4); 90 | }); 91 | 92 | test("splitOscString fails for just a string", function () { 93 | assert.throws(function () { 94 | osc.splitOscString(new TextEncoder().encode("testing it")); 95 | }); 96 | }); 97 | 98 | test("splitOscString fails for string with not enough padding", function () { 99 | assert.throws(function () { 100 | osc.splitOscString(new TextEncoder().encode("testing \u0000\u0000")); 101 | }); 102 | }); 103 | 104 | test("splitOscString succeeds for strings with valid padding", function () { 105 | const { value, rest } = osc.splitOscString( 106 | new TextEncoder().encode("testing it\u0000\u0000aaaa") 107 | ); 108 | assert.strictEqual(value, "testing it"); 109 | assert.strictEqual(rest.byteLength, 4); 110 | }); 111 | 112 | test("splitOscString fails for string with invalid padding", function () { 113 | assert.throws(function () { 114 | osc.splitOscString(new TextEncoder().encode("testing it\u0000aaaaa")); 115 | }); 116 | }); 117 | 118 | test("splitInteger fails when sent a buffer that's too small", function () { 119 | assert.throws(function () { 120 | osc.splitInteger(new ArrayBuffer(3)); 121 | }); 122 | }); 123 | 124 | test("fromOscMessage with no type string works", function () { 125 | var translate; 126 | translate = osc.fromOscMessage(osc.toOscString("/stuff")); 127 | assert.strictEqual(translate != null ? translate.address : void 0, "/stuff"); 128 | assert.deepStrictEqual(translate != null ? translate.args : void 0, []); 129 | }); 130 | 131 | test("fromOscMessage with type string and no args works", function () { 132 | const oscaddr = osc.toOscString("/stuff"); 133 | const osctype = osc.toOscString(","); 134 | const oscmessage = new Uint8Array(oscaddr.byteLength + osctype.byteLength); 135 | oscmessage.set(new Uint8Array(oscaddr)); 136 | oscmessage.set(new Uint8Array(osctype), oscaddr.byteLength); 137 | const translate = osc.fromOscMessage(oscmessage); 138 | assert.strictEqual(translate.address, "/stuff"); 139 | assert.deepStrictEqual(translate.args, []); 140 | }); 141 | 142 | test("fromOscMessage with string argument works", function () { 143 | var oscaddr, oscarg, osctype, ref, ref1, ref2, ref3, translate; 144 | oscaddr = osc.toOscString("/stuff"); 145 | osctype = osc.toOscString(",s"); 146 | oscarg = osc.toOscString("argu"); 147 | translate = osc.fromOscMessage(osc.concat([oscaddr, osctype, oscarg])); 148 | assert.strictEqual(translate != null ? translate.address : void 0, "/stuff"); 149 | assert.strictEqual( 150 | translate != null 151 | ? (ref = translate.args) != null 152 | ? (ref1 = ref[0]) != null 153 | ? ref1.type 154 | : void 0 155 | : void 0 156 | : void 0, 157 | "string" 158 | ); 159 | assert.strictEqual( 160 | translate != null 161 | ? (ref2 = translate.args) != null 162 | ? (ref3 = ref2[0]) != null 163 | ? ref3.value 164 | : void 0 165 | : void 0 166 | : void 0, 167 | "argu" 168 | ); 169 | }); 170 | 171 | test("fromOscMessage with true argument works", function () { 172 | var oscaddr, osctype, ref, ref1, ref2, ref3, translate; 173 | oscaddr = osc.toOscString("/stuff"); 174 | osctype = osc.toOscString(",T"); 175 | translate = osc.fromOscMessage(osc.concat([oscaddr, osctype])); 176 | assert.strictEqual(translate != null ? translate.address : void 0, "/stuff"); 177 | assert.strictEqual( 178 | translate != null 179 | ? (ref = translate.args) != null 180 | ? (ref1 = ref[0]) != null 181 | ? ref1.type 182 | : void 0 183 | : void 0 184 | : void 0, 185 | "true" 186 | ); 187 | assert.strictEqual( 188 | translate != null 189 | ? (ref2 = translate.args) != null 190 | ? (ref3 = ref2[0]) != null 191 | ? ref3.value 192 | : void 0 193 | : void 0 194 | : void 0, 195 | true 196 | ); 197 | }); 198 | 199 | test("fromOscMessage with false argument works", function () { 200 | var oscaddr, osctype, ref, ref1, ref2, ref3, translate; 201 | oscaddr = osc.toOscString("/stuff"); 202 | osctype = osc.toOscString(",F"); 203 | translate = osc.fromOscMessage(osc.concat([oscaddr, osctype])); 204 | assert.strictEqual(translate != null ? translate.address : void 0, "/stuff"); 205 | assert.strictEqual( 206 | translate != null 207 | ? (ref = translate.args) != null 208 | ? (ref1 = ref[0]) != null 209 | ? ref1.type 210 | : void 0 211 | : void 0 212 | : void 0, 213 | "false" 214 | ); 215 | assert.strictEqual( 216 | translate != null 217 | ? (ref2 = translate.args) != null 218 | ? (ref3 = ref2[0]) != null 219 | ? ref3.value 220 | : void 0 221 | : void 0 222 | : void 0, 223 | false 224 | ); 225 | }); 226 | 227 | test("fromOscMessage with null argument works", function () { 228 | var oscaddr, osctype, ref, ref1, ref2, ref3, translate; 229 | oscaddr = osc.toOscString("/stuff"); 230 | osctype = osc.toOscString(",N"); 231 | translate = osc.fromOscMessage(osc.concat([oscaddr, osctype])); 232 | assert.strictEqual(translate != null ? translate.address : void 0, "/stuff"); 233 | assert.strictEqual( 234 | translate != null 235 | ? (ref = translate.args) != null 236 | ? (ref1 = ref[0]) != null 237 | ? ref1.type 238 | : void 0 239 | : void 0 240 | : void 0, 241 | "null" 242 | ); 243 | assert.strictEqual( 244 | translate != null 245 | ? (ref2 = translate.args) != null 246 | ? (ref3 = ref2[0]) != null 247 | ? ref3.value 248 | : void 0 249 | : void 0 250 | : void 0, 251 | null 252 | ); 253 | }); 254 | 255 | test("fromOscMessage with bang argument works", function () { 256 | var oscaddr, osctype, ref, ref1, ref2, ref3, translate; 257 | oscaddr = osc.toOscString("/stuff"); 258 | osctype = osc.toOscString(",I"); 259 | translate = osc.fromOscMessage(osc.concat([oscaddr, osctype])); 260 | assert.strictEqual(translate != null ? translate.address : void 0, "/stuff"); 261 | assert.strictEqual( 262 | translate != null 263 | ? (ref = translate.args) != null 264 | ? (ref1 = ref[0]) != null 265 | ? ref1.type 266 | : void 0 267 | : void 0 268 | : void 0, 269 | "bang" 270 | ); 271 | assert.strictEqual( 272 | translate != null 273 | ? (ref2 = translate.args) != null 274 | ? (ref3 = ref2[0]) != null 275 | ? ref3.value 276 | : void 0 277 | : void 0 278 | : void 0, 279 | "bang" 280 | ); 281 | }); 282 | 283 | test("fromOscMessage with blob argument works", function () { 284 | const oscaddr = osc.toOscString("/stuff"); 285 | const osctype = osc.toOscString(",b"); 286 | const oscarg = osc.concat([ 287 | osc.toIntegerBuffer(4), 288 | new TextEncoder().encode("argu"), 289 | ]); 290 | const translate: any = osc.fromOscMessage( 291 | osc.concat([oscaddr, osctype, oscarg]) 292 | ); 293 | assert.strictEqual(translate.address, "/stuff"); 294 | assert.strictEqual(translate.args[0].type, "blob"); 295 | assert.strictEqual(new TextDecoder().decode(translate.args[0].value), "argu"); 296 | }); 297 | 298 | test("fromOscMessage with integer argument works", function () { 299 | var oscaddr, oscarg, osctype, ref, ref1, ref2, ref3, translate; 300 | oscaddr = osc.toOscString("/stuff"); 301 | osctype = osc.toOscString(",i"); 302 | oscarg = osc.toIntegerBuffer(888); 303 | translate = osc.fromOscMessage(osc.concat([oscaddr, osctype, oscarg])); 304 | assert.strictEqual(translate != null ? translate.address : void 0, "/stuff"); 305 | assert.strictEqual( 306 | translate != null 307 | ? (ref = translate.args) != null 308 | ? (ref1 = ref[0]) != null 309 | ? ref1.type 310 | : void 0 311 | : void 0 312 | : void 0, 313 | "integer" 314 | ); 315 | assert.strictEqual( 316 | translate != null 317 | ? (ref2 = translate.args) != null 318 | ? (ref3 = ref2[0]) != null 319 | ? ref3.value 320 | : void 0 321 | : void 0 322 | : void 0, 323 | 888 324 | ); 325 | }); 326 | 327 | test("fromOscMessage with timetag argument works", function () { 328 | var oscaddr, oscarg, osctype, ref, ref1, ref2, ref3, timetag, translate; 329 | oscaddr = osc.toOscString("/stuff"); 330 | osctype = osc.toOscString(",t"); 331 | timetag = [8888, 9999]; 332 | oscarg = osc.toTimetagBuffer(timetag); 333 | translate = osc.fromOscMessage(osc.concat([oscaddr, osctype, oscarg])); 334 | assert.strictEqual(translate != null ? translate.address : void 0, "/stuff"); 335 | assert.strictEqual( 336 | translate != null 337 | ? (ref = translate.args) != null 338 | ? (ref1 = ref[0]) != null 339 | ? ref1.type 340 | : void 0 341 | : void 0 342 | : void 0, 343 | "timetag" 344 | ); 345 | assert.deepStrictEqual( 346 | translate != null 347 | ? (ref2 = translate.args) != null 348 | ? (ref3 = ref2[0]) != null 349 | ? ref3.value 350 | : void 0 351 | : void 0 352 | : void 0, 353 | timetag 354 | ); 355 | }); 356 | 357 | test("fromOscMessage with mismatched array throws", function () { 358 | var oscaddr; 359 | oscaddr = osc.toOscString("/stuff"); 360 | assert.throws(function () { 361 | osc.fromOscMessage(osc.concat([oscaddr, osc.toOscString(",[")])); 362 | }); 363 | assert.throws(function () { 364 | osc.fromOscMessage(osc.concat([oscaddr, osc.toOscString(",]")])); 365 | }); 366 | }); 367 | 368 | test("fromOscMessage with empty array argument works", function () { 369 | var oscaddr, osctype, ref, ref1, ref2, ref3, ref4, ref5, ref6, translate; 370 | oscaddr = osc.toOscString("/stuff"); 371 | osctype = osc.toOscString(",[]"); 372 | translate = osc.fromOscMessage(osc.concat([oscaddr, osctype])); 373 | assert.strictEqual(translate != null ? translate.address : void 0, "/stuff"); 374 | assert.strictEqual( 375 | translate != null 376 | ? (ref = translate.args) != null 377 | ? (ref1 = ref[0]) != null 378 | ? ref1.type 379 | : void 0 380 | : void 0 381 | : void 0, 382 | "array" 383 | ); 384 | assert.strictEqual( 385 | translate != null 386 | ? (ref2 = translate.args) != null 387 | ? (ref3 = ref2[0]) != null 388 | ? (ref4 = ref3.value) != null 389 | ? ref4.length 390 | : void 0 391 | : void 0 392 | : void 0 393 | : void 0, 394 | 0 395 | ); 396 | assert.deepStrictEqual( 397 | translate != null 398 | ? (ref5 = translate.args) != null 399 | ? (ref6 = ref5[0]) != null 400 | ? ref6.value 401 | : void 0 402 | : void 0 403 | : void 0, 404 | [] 405 | ); 406 | }); 407 | 408 | test("fromOscMessage with bang array argument works", function () { 409 | var oscaddr, 410 | osctype, 411 | ref, 412 | ref1, 413 | ref10, 414 | ref11, 415 | ref12, 416 | ref2, 417 | ref3, 418 | ref4, 419 | ref5, 420 | ref6, 421 | ref7, 422 | ref8, 423 | ref9, 424 | translate; 425 | oscaddr = osc.toOscString("/stuff"); 426 | osctype = osc.toOscString(",[I]"); 427 | translate = osc.fromOscMessage(osc.concat([oscaddr, osctype])); 428 | assert.strictEqual(translate != null ? translate.address : void 0, "/stuff"); 429 | assert.strictEqual( 430 | translate != null 431 | ? (ref = translate.args) != null 432 | ? (ref1 = ref[0]) != null 433 | ? ref1.type 434 | : void 0 435 | : void 0 436 | : void 0, 437 | "array" 438 | ); 439 | assert.strictEqual( 440 | translate != null 441 | ? (ref2 = translate.args) != null 442 | ? (ref3 = ref2[0]) != null 443 | ? (ref4 = ref3.value) != null 444 | ? ref4.length 445 | : void 0 446 | : void 0 447 | : void 0 448 | : void 0, 449 | 1 450 | ); 451 | assert.strictEqual( 452 | translate != null 453 | ? (ref5 = translate.args) != null 454 | ? (ref6 = ref5[0]) != null 455 | ? (ref7 = ref6.value) != null 456 | ? (ref8 = ref7[0]) != null 457 | ? ref8.type 458 | : void 0 459 | : void 0 460 | : void 0 461 | : void 0 462 | : void 0, 463 | "bang" 464 | ); 465 | assert.strictEqual( 466 | translate != null 467 | ? (ref9 = translate.args) != null 468 | ? (ref10 = ref9[0]) != null 469 | ? (ref11 = ref10.value) != null 470 | ? (ref12 = ref11[0]) != null 471 | ? ref12.value 472 | : void 0 473 | : void 0 474 | : void 0 475 | : void 0 476 | : void 0, 477 | "bang" 478 | ); 479 | }); 480 | 481 | test("fromOscMessage with string array argument works", function () { 482 | var oscaddr, 483 | oscarg, 484 | osctype, 485 | ref, 486 | ref1, 487 | ref10, 488 | ref11, 489 | ref12, 490 | ref2, 491 | ref3, 492 | ref4, 493 | ref5, 494 | ref6, 495 | ref7, 496 | ref8, 497 | ref9, 498 | translate; 499 | oscaddr = osc.toOscString("/stuff"); 500 | osctype = osc.toOscString(",[s]"); 501 | oscarg = osc.toOscString("argu"); 502 | translate = osc.fromOscMessage(osc.concat([oscaddr, osctype, oscarg])); 503 | assert.strictEqual(translate != null ? translate.address : void 0, "/stuff"); 504 | assert.strictEqual( 505 | translate != null 506 | ? (ref = translate.args) != null 507 | ? (ref1 = ref[0]) != null 508 | ? ref1.type 509 | : void 0 510 | : void 0 511 | : void 0, 512 | "array" 513 | ); 514 | assert.strictEqual( 515 | translate != null 516 | ? (ref2 = translate.args) != null 517 | ? (ref3 = ref2[0]) != null 518 | ? (ref4 = ref3.value) != null 519 | ? ref4.length 520 | : void 0 521 | : void 0 522 | : void 0 523 | : void 0, 524 | 1 525 | ); 526 | assert.strictEqual( 527 | translate != null 528 | ? (ref5 = translate.args) != null 529 | ? (ref6 = ref5[0]) != null 530 | ? (ref7 = ref6.value) != null 531 | ? (ref8 = ref7[0]) != null 532 | ? ref8.type 533 | : void 0 534 | : void 0 535 | : void 0 536 | : void 0 537 | : void 0, 538 | "string" 539 | ); 540 | assert.strictEqual( 541 | translate != null 542 | ? (ref9 = translate.args) != null 543 | ? (ref10 = ref9[0]) != null 544 | ? (ref11 = ref10.value) != null 545 | ? (ref12 = ref11[0]) != null 546 | ? ref12.value 547 | : void 0 548 | : void 0 549 | : void 0 550 | : void 0 551 | : void 0, 552 | "argu" 553 | ); 554 | }); 555 | 556 | test("fromOscMessage with nested array argument works", function () { 557 | var oscaddr, 558 | osctype, 559 | ref, 560 | ref1, 561 | ref10, 562 | ref11, 563 | ref12, 564 | ref13, 565 | ref14, 566 | ref15, 567 | ref16, 568 | ref17, 569 | ref18, 570 | ref19, 571 | ref2, 572 | ref20, 573 | ref21, 574 | ref22, 575 | ref23, 576 | ref24, 577 | ref25, 578 | ref3, 579 | ref4, 580 | ref5, 581 | ref6, 582 | ref7, 583 | ref8, 584 | ref9, 585 | translate; 586 | oscaddr = osc.toOscString("/stuff"); 587 | osctype = osc.toOscString(",[[I]]"); 588 | translate = osc.fromOscMessage(osc.concat([oscaddr, osctype])); 589 | assert.strictEqual(translate != null ? translate.address : void 0, "/stuff"); 590 | assert.strictEqual( 591 | translate != null 592 | ? (ref = translate.args) != null 593 | ? (ref1 = ref[0]) != null 594 | ? ref1.type 595 | : void 0 596 | : void 0 597 | : void 0, 598 | "array" 599 | ); 600 | assert.strictEqual( 601 | translate != null 602 | ? (ref2 = translate.args) != null 603 | ? (ref3 = ref2[0]) != null 604 | ? (ref4 = ref3.value) != null 605 | ? ref4.length 606 | : void 0 607 | : void 0 608 | : void 0 609 | : void 0, 610 | 1 611 | ); 612 | assert.strictEqual( 613 | translate != null 614 | ? (ref5 = translate.args) != null 615 | ? (ref6 = ref5[0]) != null 616 | ? (ref7 = ref6.value) != null 617 | ? (ref8 = ref7[0]) != null 618 | ? ref8.type 619 | : void 0 620 | : void 0 621 | : void 0 622 | : void 0 623 | : void 0, 624 | "array" 625 | ); 626 | assert.strictEqual( 627 | translate != null 628 | ? (ref9 = translate.args) != null 629 | ? (ref10 = ref9[0]) != null 630 | ? (ref11 = ref10.value) != null 631 | ? (ref12 = ref11[0]) != null 632 | ? (ref13 = ref12.value) != null 633 | ? ref13.length 634 | : void 0 635 | : void 0 636 | : void 0 637 | : void 0 638 | : void 0 639 | : void 0, 640 | 1 641 | ); 642 | assert.strictEqual( 643 | translate != null 644 | ? (ref14 = translate.args) != null 645 | ? (ref15 = ref14[0]) != null 646 | ? (ref16 = ref15.value) != null 647 | ? (ref17 = ref16[0]) != null 648 | ? (ref18 = ref17.value) != null 649 | ? (ref19 = ref18[0]) != null 650 | ? ref19.type 651 | : void 0 652 | : void 0 653 | : void 0 654 | : void 0 655 | : void 0 656 | : void 0 657 | : void 0, 658 | "bang" 659 | ); 660 | assert.strictEqual( 661 | translate != null 662 | ? (ref20 = translate.args) != null 663 | ? (ref21 = ref20[0]) != null 664 | ? (ref22 = ref21.value) != null 665 | ? (ref23 = ref22[0]) != null 666 | ? (ref24 = ref23.value) != null 667 | ? (ref25 = ref24[0]) != null 668 | ? ref25.value 669 | : void 0 670 | : void 0 671 | : void 0 672 | : void 0 673 | : void 0 674 | : void 0 675 | : void 0, 676 | "bang" 677 | ); 678 | }); 679 | 680 | test("fromOscMessage with multiple args works", function () { 681 | var oscaddr, oscargs, oscbuffer, osctype, ref, ref1, ref2, ref3, translate; 682 | oscaddr = osc.toOscString("/stuff"); 683 | osctype = osc.toOscString(",sbi"); 684 | oscargs = [ 685 | osc.toOscString("argu"), 686 | osc.concat([osc.toIntegerBuffer(4), new TextEncoder().encode("argu")]), 687 | osc.toIntegerBuffer(888), 688 | ]; 689 | oscbuffer = osc.concat([oscaddr, osctype, osc.concat(oscargs)]); 690 | translate = osc.fromOscMessage(oscbuffer); 691 | assert.strictEqual(translate != null ? translate.address : void 0, "/stuff"); 692 | assert.strictEqual( 693 | translate != null 694 | ? (ref = translate.args) != null 695 | ? (ref1 = ref[0]) != null 696 | ? ref1.type 697 | : void 0 698 | : void 0 699 | : void 0, 700 | "string" 701 | ); 702 | assert.strictEqual( 703 | translate != null 704 | ? (ref2 = translate.args) != null 705 | ? (ref3 = ref2[0]) != null 706 | ? ref3.value 707 | : void 0 708 | : void 0 709 | : void 0, 710 | "argu" 711 | ); 712 | }); 713 | 714 | test("fromOscMessage fails if type string has no comma", function () { 715 | var oscaddr, osctype; 716 | oscaddr = osc.toOscString("/stuff"); 717 | osctype = osc.toOscString("fake"); 718 | assert.throws(function () { 719 | osc.fromOscMessage(osc.concat([oscaddr, osctype])); 720 | }); 721 | }); 722 | 723 | test("fromOscMessage fails if type address doesn't begin with /", function () { 724 | var oscaddr, osctype; 725 | oscaddr = osc.toOscString("stuff"); 726 | osctype = osc.toOscString(","); 727 | assert.throws(function () { 728 | osc.fromOscMessage(osc.concat([oscaddr, osctype])); 729 | }); 730 | }); 731 | 732 | test("fromOscBundle works with no messages", function () { 733 | var buffer, oscbundle, osctimetag, timetag, translate; 734 | oscbundle = osc.toOscString("#bundle"); 735 | timetag = [0, 0]; 736 | osctimetag = osc.toTimetagBuffer(timetag); 737 | buffer = osc.concat([oscbundle, osctimetag]); 738 | translate = osc.fromOscBundle(buffer); 739 | assert.deepStrictEqual( 740 | translate != null ? translate.timetag : void 0, 741 | timetag 742 | ); 743 | assert.deepStrictEqual(translate != null ? translate.elements : void 0, []); 744 | }); 745 | 746 | test("fromOscBundle works with single message", function () { 747 | const oscbundle = osc.toOscString("#bundle"); 748 | const timetag: osc.TimeTag = [0, 0]; 749 | const osctimetag = osc.toTimetagBuffer(timetag); 750 | const oscaddr = osc.toOscString("/addr"); 751 | const osctype = osc.toOscString(","); 752 | const oscmessage = osc.concat([oscaddr, osctype]); 753 | const osclen = osc.toIntegerBuffer(oscmessage.byteLength); 754 | const buffer = osc.concat([oscbundle, osctimetag, osclen, oscmessage]); 755 | const translate: any = osc.fromOscBundle(buffer); 756 | assert.deepStrictEqual(translate.timetag, timetag); 757 | assert.strictEqual(translate.elements.length, 1); 758 | assert.strictEqual(translate.elements[0].address, "/addr"); 759 | }); 760 | 761 | test("fromOscBundle works with multiple messages", function () { 762 | const oscbundle = osc.toOscString("#bundle"); 763 | const timetag: osc.TimeTag = [0, 0]; 764 | const osctimetag = osc.toTimetagBuffer(timetag); 765 | const oscaddr1 = osc.toOscString("/addr"); 766 | const osctype1 = osc.toOscString(","); 767 | const oscmessage1 = osc.concat([oscaddr1, osctype1]); 768 | const osclen1 = osc.toIntegerBuffer(oscmessage1.byteLength); 769 | const oscaddr2 = osc.toOscString("/addr2"); 770 | const osctype2 = osc.toOscString(","); 771 | const oscmessage2 = osc.concat([oscaddr2, osctype2]); 772 | const osclen2 = osc.toIntegerBuffer(oscmessage2.byteLength); 773 | const buffer = osc.concat([ 774 | oscbundle, 775 | osctimetag, 776 | osclen1, 777 | oscmessage1, 778 | osclen2, 779 | oscmessage2, 780 | ]); 781 | const translate: any = osc.fromOscBundle(buffer); 782 | assert.deepStrictEqual(translate.timetag, timetag); 783 | assert.strictEqual(translate.elements.length, 2); 784 | assert.strictEqual(translate.elements[0].address, "/addr"); 785 | assert.strictEqual(translate.elements[1].address, "/addr2"); 786 | }); 787 | 788 | test("fromOscBundle works with nested bundles", function () { 789 | const oscbundle = osc.toOscString("#bundle"); 790 | const timetag: osc.TimeTag = [0, 0]; 791 | const osctimetag = osc.toTimetagBuffer(timetag); 792 | const oscaddr1 = osc.toOscString("/addr"); 793 | const osctype1 = osc.toOscString(","); 794 | const oscmessage1 = osc.concat([oscaddr1, osctype1]); 795 | const osclen1 = osc.toIntegerBuffer(oscmessage1.byteLength); 796 | const oscbundle2 = osc.toOscString("#bundle"); 797 | const timetag2: osc.TimeTag = [0, 0]; 798 | const osctimetag2 = osc.toTimetagBuffer(timetag2); 799 | const oscmessage2 = osc.concat([oscbundle2, osctimetag2]); 800 | const osclen2 = osc.toIntegerBuffer(oscmessage2.byteLength); 801 | const buffer = osc.concat([ 802 | oscbundle, 803 | osctimetag, 804 | osclen1, 805 | oscmessage1, 806 | osclen2, 807 | oscmessage2, 808 | ]); 809 | const translate: any = osc.fromOscBundle(buffer); 810 | assert.deepStrictEqual(translate.timetag, timetag); 811 | assert.strictEqual(translate.elements.length, 2); 812 | assert.strictEqual(translate.elements[0].address, "/addr"); 813 | assert.deepStrictEqual(translate.elements[1].timetag, timetag2); 814 | }); 815 | 816 | test("fromOscBundle works with non-understood messages", function () { 817 | const oscbundle = osc.toOscString("#bundle"); 818 | const timetag: osc.TimeTag = [0, 0]; 819 | const osctimetag = osc.toTimetagBuffer(timetag); 820 | const oscaddr1 = osc.toOscString("/addr"); 821 | const osctype1 = osc.toOscString(","); 822 | const oscmessage1 = osc.concat([oscaddr1, osctype1]); 823 | const osclen1 = osc.toIntegerBuffer(oscmessage1.byteLength); 824 | const oscaddr2 = osc.toOscString("/addr2"); 825 | const osctype2 = osc.toOscString(",α"); 826 | const oscmessage2 = osc.concat([oscaddr2, osctype2]); 827 | const osclen2 = osc.toIntegerBuffer(oscmessage2.byteLength); 828 | const buffer = osc.concat([ 829 | oscbundle, 830 | osctimetag, 831 | osclen1, 832 | oscmessage1, 833 | osclen2, 834 | oscmessage2, 835 | ]); 836 | const translate: any = osc.fromOscBundle(buffer); 837 | assert.deepStrictEqual(translate.timetag, timetag); 838 | assert.strictEqual(translate.elements.length, 1); 839 | assert.strictEqual(translate.elements[0].address, "/addr"); 840 | }); 841 | 842 | test("fromOscBundle fails with bad bundle ID", function () { 843 | var oscbundle; 844 | oscbundle = osc.toOscString("#blunder"); 845 | assert.throws(function () { 846 | osc.fromOscBundle(oscbundle); 847 | }); 848 | }); 849 | 850 | test("fromOscBundle fails with ridiculous sizes", function () { 851 | var oscbundle, timetag; 852 | timetag = [0, 0]; 853 | oscbundle = osc.concat([ 854 | osc.toOscMessage("#bundle"), 855 | osc.toTimetagBuffer(timetag), 856 | osc.toIntegerBuffer(999999), 857 | ]); 858 | assert.throws(function () { 859 | osc.fromOscBundle(oscbundle); 860 | }); 861 | }); 862 | 863 | const checkRoundTrip = function ( 864 | args: osc.OscArgOrArrayInput[], 865 | roundTrips: any 866 | ) { 867 | assert.strictEqual(roundTrips.length, args.length); 868 | 869 | for (let i = 0; i < args.length; i++) { 870 | const arg: any = args[i]; 871 | const roundTrip: any = roundTrips[i]; 872 | if (arg !== null && typeof arg === "object" && "type" in arg) { 873 | assert.strictEqual(roundTrip.type, arg.type); 874 | } 875 | if ( 876 | Array.isArray(arg) || 877 | (arg !== null && 878 | typeof arg === "object" && 879 | "type" in arg && 880 | arg.type === "array") 881 | ) { 882 | checkRoundTrip(Array.isArray(arg) ? arg : arg.value, roundTrip.value); 883 | } else if (arg !== null && typeof arg === "object" && "value" in arg) { 884 | assert.deepStrictEqual(roundTrip.value, arg.value); 885 | } else if (arg instanceof ArrayBuffer) { 886 | assert.deepStrictEqual(roundTrip.value, new DataView(arg)); 887 | } else if (arg !== null && typeof arg === "object" && "buffer" in arg) { 888 | assert.deepStrictEqual( 889 | roundTrip.value, 890 | new DataView(arg.buffer, arg.byteOffset, arg.byteLength) 891 | ); 892 | } else { 893 | assert.deepStrictEqual(roundTrip.value, arg); 894 | } 895 | } 896 | }; 897 | 898 | const roundTripMessage = function (args: osc.OscArgOrArrayInput[]) { 899 | const oscMessage = { 900 | address: "/addr", 901 | args: args, 902 | }; 903 | const roundTrip = osc.fromOscMessage(osc.toOscMessage(oscMessage)); 904 | assert.strictEqual(roundTrip.address, "/addr"); 905 | checkRoundTrip(args, roundTrip.args); 906 | }; 907 | 908 | test("toOscMessage with no args works", function () { 909 | roundTripMessage([]); 910 | }); 911 | 912 | test("toOscMessage with null argument", function () { 913 | roundTripMessage([null]); 914 | }); 915 | 916 | test("toOscMessage with string argument works", function () { 917 | roundTripMessage(["strr"]); 918 | }); 919 | 920 | test("toOscMessage with empty array argument works", function () { 921 | roundTripMessage([[]]); 922 | }); 923 | 924 | test("toOscMessage with array value works", function () { 925 | roundTripMessage([["strr"]]); 926 | }); 927 | 928 | test("toOscMessage with string array argument works", function () { 929 | roundTripMessage(["hello", "goodbye"]); 930 | }); 931 | 932 | test("toOscMessage with multi-type array argument works", function () { 933 | roundTripMessage([["hello", 7]]); 934 | }); 935 | 936 | test("toOscMessage with nested array argument works", function () { 937 | roundTripMessage([ 938 | [ 939 | { 940 | type: "array", 941 | value: [ 942 | { 943 | type: "string", 944 | value: "hello", 945 | }, 946 | ], 947 | }, 948 | ], 949 | ]); 950 | }); 951 | 952 | test("toOscMessage with bad layout works", function () { 953 | const oscMessage = { 954 | address: "/addr", 955 | args: ["strr"], 956 | }; 957 | const roundTrip: any = osc.fromOscMessage(osc.toOscMessage(oscMessage)); 958 | assert.strictEqual(roundTrip.address, "/addr"); 959 | assert.strictEqual(roundTrip.args.length, 1); 960 | assert.strictEqual(roundTrip.args[0].value, "strr"); 961 | }); 962 | 963 | test("toOscMessage with single numeric argument works", function () { 964 | const oscMessage = { 965 | address: "/addr", 966 | args: 13, 967 | }; 968 | const roundTrip: any = osc.fromOscMessage(osc.toOscMessage(oscMessage)); 969 | assert.strictEqual(roundTrip.address, "/addr"); 970 | assert.strictEqual(roundTrip.args.length, 1); 971 | assert.strictEqual(roundTrip.args[0].value, 13); 972 | assert.strictEqual(roundTrip.args[0].type, "float"); 973 | }); 974 | 975 | test("toOscMessage with args shortcut works", function () { 976 | var oscMessage, ref, ref1, ref2, ref3, ref4, roundTrip; 977 | oscMessage = { 978 | address: "/addr", 979 | args: 13, 980 | }; 981 | roundTrip = osc.fromOscMessage(osc.toOscMessage(oscMessage)); 982 | assert.strictEqual(roundTrip != null ? roundTrip.address : void 0, "/addr"); 983 | assert.strictEqual( 984 | roundTrip != null 985 | ? (ref = roundTrip.args) != null 986 | ? ref.length 987 | : void 0 988 | : void 0, 989 | 1 990 | ); 991 | assert.strictEqual( 992 | roundTrip != null 993 | ? (ref1 = roundTrip.args) != null 994 | ? (ref2 = ref1[0]) != null 995 | ? ref2.value 996 | : void 0 997 | : void 0 998 | : void 0, 999 | 13 1000 | ); 1001 | assert.strictEqual( 1002 | roundTrip != null 1003 | ? (ref3 = roundTrip.args) != null 1004 | ? (ref4 = ref3[0]) != null 1005 | ? ref4.type 1006 | : void 0 1007 | : void 0 1008 | : void 0, 1009 | "float" 1010 | ); 1011 | }); 1012 | 1013 | test("toOscMessage with single blob argument works", function () { 1014 | const buff = new ArrayBuffer(18); 1015 | const oscMessage = { 1016 | address: "/addr", 1017 | args: buff, 1018 | }; 1019 | const roundTrip: any = osc.fromOscMessage(osc.toOscMessage(oscMessage)); 1020 | assert.strictEqual(roundTrip.address, "/addr"); 1021 | assert.strictEqual(roundTrip.args.length, 1); 1022 | assert.strictEqual(roundTrip.args[0].type, "blob"); 1023 | assert.deepStrictEqual(roundTrip.args[0].value, new DataView(buff)); 1024 | }); 1025 | 1026 | test("toOscMessage with single string argument works", function () { 1027 | var oscMessage, ref, ref1, ref2, ref3, ref4, roundTrip; 1028 | oscMessage = { 1029 | address: "/addr", 1030 | args: "strr", 1031 | }; 1032 | roundTrip = osc.fromOscMessage(osc.toOscMessage(oscMessage)); 1033 | assert.strictEqual(roundTrip != null ? roundTrip.address : void 0, "/addr"); 1034 | assert.strictEqual( 1035 | roundTrip != null 1036 | ? (ref = roundTrip.args) != null 1037 | ? ref.length 1038 | : void 0 1039 | : void 0, 1040 | 1 1041 | ); 1042 | assert.strictEqual( 1043 | roundTrip != null 1044 | ? (ref1 = roundTrip.args) != null 1045 | ? (ref2 = ref1[0]) != null 1046 | ? ref2.value 1047 | : void 0 1048 | : void 0 1049 | : void 0, 1050 | "strr" 1051 | ); 1052 | assert.strictEqual( 1053 | roundTrip != null 1054 | ? (ref3 = roundTrip.args) != null 1055 | ? (ref4 = ref3[0]) != null 1056 | ? ref4.type 1057 | : void 0 1058 | : void 0 1059 | : void 0, 1060 | "string" 1061 | ); 1062 | }); 1063 | 1064 | test("toOscMessage with integer argument works", function () { 1065 | roundTripMessage([8]); 1066 | }); 1067 | 1068 | test("toOscMessage with buffer argument works", function () { 1069 | roundTripMessage([new TextEncoder().encode("testing 123")]); 1070 | }); 1071 | 1072 | test("toOscMessage with type true works", function () { 1073 | var roundTrip; 1074 | roundTrip = osc.fromOscMessage( 1075 | osc.toOscMessage({ 1076 | address: "/addr", 1077 | args: true, 1078 | }) 1079 | ); 1080 | assert.strictEqual(roundTrip.args.length, 1); 1081 | assert.strictEqual(roundTrip.args[0].value, true); 1082 | assert.strictEqual(roundTrip.args[0].type, "true"); 1083 | }); 1084 | 1085 | test("toOscMessage with type false works", function () { 1086 | var roundTrip; 1087 | roundTrip = osc.fromOscMessage( 1088 | osc.toOscMessage({ 1089 | address: "/addr", 1090 | args: false, 1091 | }) 1092 | ); 1093 | assert.strictEqual(roundTrip.args.length, 1); 1094 | assert.strictEqual(roundTrip.args[0].value, false); 1095 | assert.strictEqual(roundTrip.args[0].type, "false"); 1096 | }); 1097 | 1098 | test("toOscMessage with type bang argument works", function () { 1099 | var roundTrip; 1100 | roundTrip = osc.fromOscMessage( 1101 | osc.toOscMessage({ 1102 | address: "/addr", 1103 | args: { 1104 | type: "bang", 1105 | }, 1106 | }) 1107 | ); 1108 | assert.strictEqual(roundTrip.args.length, 1); 1109 | assert.strictEqual(roundTrip.args[0].value, "bang"); 1110 | assert.strictEqual(roundTrip.args[0].type, "bang"); 1111 | }); 1112 | 1113 | test("toOscMessage with type timetag argument works", function () { 1114 | roundTripMessage([ 1115 | { 1116 | type: "timetag", 1117 | value: [8888, 9999], 1118 | }, 1119 | ]); 1120 | }); 1121 | 1122 | test("toOscMessage with type double argument works", function () { 1123 | roundTripMessage([ 1124 | { 1125 | type: "double", 1126 | value: 8888, 1127 | }, 1128 | ]); 1129 | }); 1130 | 1131 | test("toOscMessage with type null works", function () { 1132 | var roundTrip; 1133 | roundTrip = osc.fromOscMessage( 1134 | osc.toOscMessage({ 1135 | address: "/addr", 1136 | args: null, 1137 | }) 1138 | ); 1139 | assert.strictEqual(roundTrip.args.length, 1); 1140 | assert.strictEqual(roundTrip.args[0].value, null); 1141 | assert.strictEqual(roundTrip.args[0].type, "null"); 1142 | }); 1143 | 1144 | test("toOscMessage with float argument works", function () { 1145 | roundTripMessage([ 1146 | { 1147 | value: 6, 1148 | type: "float", 1149 | }, 1150 | ]); 1151 | }); 1152 | 1153 | test("toOscMessage just a string works", function () { 1154 | var message; 1155 | message = osc.fromOscMessage(osc.toOscMessage("/bleh")); 1156 | assert.strictEqual(message.address, "/bleh"); 1157 | assert.strictEqual(message.args.length, 0); 1158 | }); 1159 | 1160 | test("roundtrip symbol works", () => { 1161 | roundTripMessage([ 1162 | { 1163 | type: "symbol", 1164 | value: "bleh", 1165 | }, 1166 | ]); 1167 | }); 1168 | 1169 | test("roundtrip character works", () => { 1170 | roundTripMessage([ 1171 | { 1172 | type: "character", 1173 | value: "b", 1174 | }, 1175 | ]); 1176 | }); 1177 | 1178 | test("roundtrip emoji works", () => { 1179 | // Note this isn't actually standard but feels like it should work 1180 | roundTripMessage([ 1181 | { 1182 | type: "character", 1183 | value: "🎛", 1184 | }, 1185 | ]); 1186 | }); 1187 | 1188 | test("Trying to send multiple characters fails", () => { 1189 | assert.throws(() => { 1190 | osc.toOscMessage({ 1191 | address: "/addr", 1192 | args: { type: "character", value: "ab" }, 1193 | }); 1194 | }); 1195 | }); 1196 | 1197 | test("Trying to send zero characters fails", () => { 1198 | assert.throws(() => { 1199 | osc.toOscMessage({ 1200 | address: "/addr", 1201 | args: { type: "character", value: "" }, 1202 | }); 1203 | }); 1204 | }); 1205 | 1206 | test("Roundtrip color works", () => { 1207 | roundTripMessage([ 1208 | { 1209 | red: 255, 1210 | green: 0, 1211 | blue: 175, 1212 | alpha: 255, 1213 | }, 1214 | ]); 1215 | }); 1216 | 1217 | test("Roundtrip bigint works", () => { 1218 | roundTripMessage([1234567891234n]); 1219 | }); 1220 | 1221 | test("Roundtrip midi works", () => { 1222 | roundTripMessage([ 1223 | { 1224 | type: "midi", 1225 | value: [1, 2, 3, 4], 1226 | }, 1227 | ]); 1228 | }); 1229 | 1230 | test("toOscMessage with multiple args works", function () { 1231 | roundTripMessage(["str", 7, new ArrayBuffer(30), 6]); 1232 | }); 1233 | 1234 | test("toOscMessage with integer argument works", function () { 1235 | roundTripMessage([ 1236 | { 1237 | value: 7, 1238 | type: "integer", 1239 | }, 1240 | ]); 1241 | }); 1242 | 1243 | const roundTripBundle = function (elems) { 1244 | var i, 1245 | k, 1246 | length, 1247 | oscMessage, 1248 | ref, 1249 | ref1, 1250 | ref2, 1251 | ref3, 1252 | ref4, 1253 | ref5, 1254 | ref6, 1255 | ref7, 1256 | roundTrip; 1257 | oscMessage = { 1258 | timetag: [0, 0], 1259 | elements: elems, 1260 | }; 1261 | roundTrip = osc.fromOscBundle(osc.toOscBundle(oscMessage)); 1262 | assert.deepStrictEqual( 1263 | roundTrip != null ? roundTrip.timetag : void 0, 1264 | [0, 0] 1265 | ); 1266 | length = typeof elems === "object" ? elems.length : 1; 1267 | assert.strictEqual( 1268 | roundTrip != null 1269 | ? (ref = roundTrip.elements) != null 1270 | ? ref.length 1271 | : void 0 1272 | : void 0, 1273 | length 1274 | ); 1275 | for ( 1276 | i = k = 0, ref1 = length; 1277 | 0 <= ref1 ? k < ref1 : k > ref1; 1278 | i = 0 <= ref1 ? ++k : --k 1279 | ) { 1280 | if (typeof elems === "object") { 1281 | assert.deepStrictEqual( 1282 | roundTrip != null 1283 | ? (ref2 = roundTrip.elements) != null 1284 | ? (ref3 = ref2[i]) != null 1285 | ? ref3.timetag 1286 | : void 0 1287 | : void 0 1288 | : void 0, 1289 | elems[i].timetag 1290 | ); 1291 | assert.strictEqual( 1292 | roundTrip != null 1293 | ? (ref4 = roundTrip.elements) != null 1294 | ? (ref5 = ref4[i]) != null 1295 | ? ref5.address 1296 | : void 0 1297 | : void 0 1298 | : void 0, 1299 | elems[i].address 1300 | ); 1301 | } else { 1302 | assert.strictEqual( 1303 | roundTrip != null 1304 | ? (ref6 = roundTrip.elements) != null 1305 | ? (ref7 = ref6[i]) != null 1306 | ? ref7.address 1307 | : void 0 1308 | : void 0 1309 | : void 0, 1310 | elems 1311 | ); 1312 | } 1313 | } 1314 | }; 1315 | 1316 | test("toOscBundle with no elements works", function () { 1317 | roundTripBundle([]); 1318 | }); 1319 | 1320 | test("toOscBundle with just a string works", function () { 1321 | roundTripBundle("/address"); 1322 | }); 1323 | 1324 | test("toOscBundle with just a number fails", function () { 1325 | assert.throws(function () { 1326 | roundTripBundle(78); 1327 | }); 1328 | }); 1329 | 1330 | test("toOscBundle with one message works", function () { 1331 | roundTripBundle([ 1332 | { 1333 | address: "/addr", 1334 | }, 1335 | ]); 1336 | }); 1337 | 1338 | test("toOscBundle with nested bundles works", function () { 1339 | roundTripBundle([ 1340 | { 1341 | address: "/addr", 1342 | }, 1343 | { 1344 | timetag: [8888, 9999], 1345 | }, 1346 | ]); 1347 | }); 1348 | 1349 | test("identity applyTransform works with single message", function () { 1350 | var testBuffer; 1351 | testBuffer = osc.toOscString("/message"); 1352 | assert.deepStrictEqual( 1353 | osc.applyTransform(testBuffer, function (a) { 1354 | return a; 1355 | }), 1356 | new DataView(testBuffer) 1357 | ); 1358 | }); 1359 | 1360 | test("nullary applyTransform works with single message", function () { 1361 | var testBuffer; 1362 | testBuffer = osc.toOscString("/message"); 1363 | assert.strictEqual( 1364 | osc.applyTransform(testBuffer, function () { 1365 | return new DataView(new ArrayBuffer(0)); 1366 | }).byteLength, 1367 | 0 1368 | ); 1369 | }); 1370 | 1371 | test("toOscPacket works when explicitly set to bundle", function () { 1372 | var roundTrip; 1373 | roundTrip = osc.fromOscBundle( 1374 | osc.toOscPacket({ 1375 | timetag: new Date(), 1376 | elements: [], 1377 | }) 1378 | ); 1379 | assert.strictEqual(roundTrip.elements.length, 0); 1380 | }); 1381 | 1382 | test("toOscPacket works when explicitly set to message", function () { 1383 | var roundTrip; 1384 | roundTrip = osc.fromOscPacket( 1385 | osc.toOscPacket({ 1386 | address: "/bleh", 1387 | args: [], 1388 | }) 1389 | ); 1390 | assert.strictEqual(roundTrip.args.length, 0); 1391 | assert.strictEqual(roundTrip.address, "/bleh"); 1392 | }); 1393 | 1394 | test("identity applyTransform works with a simple bundle", function () { 1395 | const base: any = { 1396 | timetag: [0, 0], 1397 | elements: [ 1398 | { 1399 | address: "/test1", 1400 | }, 1401 | { 1402 | address: "/test2", 1403 | }, 1404 | ], 1405 | }; 1406 | const transformed: any = osc.fromOscPacket( 1407 | osc.applyTransform(osc.toOscPacket(base), function (a) { 1408 | return a; 1409 | }) 1410 | ); 1411 | assert.deepStrictEqual(transformed.timetag, [0, 0]); 1412 | assert.strictEqual(transformed.elements.length, base.elements.length); 1413 | for (let i = 0; i < base.elements.length; i++) { 1414 | assert.equal(transformed.elements[i].timetag, base.elements[i].timetag); 1415 | assert.strictEqual( 1416 | transformed.elements[i].address, 1417 | base.elements[i].address 1418 | ); 1419 | } 1420 | }); 1421 | 1422 | test("applyMessageTranformerToBundle fails on bundle without tag", function () { 1423 | var func; 1424 | func = osc.applyMessageTranformerToBundle(function (a) { 1425 | return a; 1426 | }); 1427 | assert.throws(function () { 1428 | func(osc.concat([osc.toOscMessage("#grundle"), osc.toIntegerBuffer(0)])); 1429 | }); 1430 | }); 1431 | 1432 | test("addressTransform works with identity", function () { 1433 | var i, k, ref, testBuffer, transformed; 1434 | testBuffer = osc.concat([ 1435 | osc.toOscMessage("/message"), 1436 | new TextEncoder().encode("gobblegobblewillsnever\u0000parse blah lbha"), 1437 | ]); 1438 | transformed = osc.applyTransform( 1439 | testBuffer, 1440 | osc.addressTransform(function (a) { 1441 | return a; 1442 | }) 1443 | ); 1444 | for ( 1445 | i = k = 0, ref = testBuffer.length; 1446 | 0 <= ref ? k < ref : k > ref; 1447 | i = 0 <= ref ? ++k : --k 1448 | ) { 1449 | assert.equal(transformed[i], testBuffer[i]); 1450 | } 1451 | }); 1452 | 1453 | test("addressTransform works with bundles", function () { 1454 | var base, i, k, ref, ref1, ref2, ref3, ref4, ref5, transformed; 1455 | base = { 1456 | timetag: [0, 0], 1457 | elements: [ 1458 | { 1459 | address: "test1", 1460 | }, 1461 | { 1462 | address: "test2", 1463 | }, 1464 | ], 1465 | }; 1466 | transformed = osc.fromOscPacket( 1467 | osc.applyTransform( 1468 | osc.toOscPacket(base), 1469 | osc.addressTransform(function (a) { 1470 | return "/prelude/" + a; 1471 | }) 1472 | ) 1473 | ); 1474 | assert.deepStrictEqual( 1475 | transformed != null ? transformed.timetag : void 0, 1476 | [0, 0] 1477 | ); 1478 | assert.strictEqual( 1479 | transformed != null 1480 | ? (ref = transformed.elements) != null 1481 | ? ref.length 1482 | : void 0 1483 | : void 0, 1484 | base.elements.length 1485 | ); 1486 | for ( 1487 | i = k = 0, ref1 = base.elements.length; 1488 | 0 <= ref1 ? k < ref1 : k > ref1; 1489 | i = 0 <= ref1 ? ++k : --k 1490 | ) { 1491 | assert.equal( 1492 | transformed != null 1493 | ? (ref2 = transformed.elements) != null 1494 | ? (ref3 = ref2[i]) != null 1495 | ? ref3.timetag 1496 | : void 0 1497 | : void 0 1498 | : void 0, 1499 | base.elements[i].timetag 1500 | ); 1501 | assert.strictEqual( 1502 | transformed != null 1503 | ? (ref4 = transformed.elements) != null 1504 | ? (ref5 = ref4[i]) != null 1505 | ? ref5.address 1506 | : void 0 1507 | : void 0 1508 | : void 0, 1509 | "/prelude/" + base.elements[i].address 1510 | ); 1511 | } 1512 | }); 1513 | 1514 | test("messageTransform works with identity function for single message", function () { 1515 | var buff, message; 1516 | message = { 1517 | address: "/addr", 1518 | args: [], 1519 | }; 1520 | buff = osc.toOscPacket(message); 1521 | assert.deepStrictEqual( 1522 | osc.applyTransform( 1523 | buff, 1524 | osc.messageTransform(function (a) { 1525 | return a; 1526 | }) 1527 | ), 1528 | buff 1529 | ); 1530 | }); 1531 | 1532 | test("messageTransform works with bundles", function () { 1533 | const message: osc.OscPacketInput = { 1534 | timetag: [0, 0], 1535 | elements: [ 1536 | { 1537 | address: "/test1", 1538 | }, 1539 | { 1540 | address: "/test2", 1541 | }, 1542 | ], 1543 | }; 1544 | const buff = osc.toOscPacket(message); 1545 | assert.deepStrictEqual( 1546 | osc.applyTransform( 1547 | buff, 1548 | osc.messageTransform(function (a) { 1549 | return a; 1550 | }) 1551 | ), 1552 | buff 1553 | ); 1554 | }); 1555 | 1556 | const assertDatesEqual = function (date1, date2) { 1557 | assert( 1558 | Math.abs(date1.getTime() - date2.getTime()) <= 1, 1559 | "" + date1 + " != " + date2 1560 | ); 1561 | }; 1562 | 1563 | test("timetagToDate converts timetag to a Date", function () { 1564 | var date, date2, timetag; 1565 | date = new Date(); 1566 | timetag = osc.dateToTimetag(date); 1567 | date2 = osc.timetagToDate(timetag); 1568 | assertDatesEqual(date, date2); 1569 | }); 1570 | 1571 | test("dateToTimetag converts date to ntp array", function () { 1572 | var date, date2, timetag; 1573 | date = new Date(); 1574 | timetag = osc.dateToTimetag(date); 1575 | date2 = osc.timetagToDate(timetag); 1576 | assertDatesEqual(date, date2); 1577 | }); 1578 | 1579 | test("splitTimetag returns timetag from a buffer", function () { 1580 | const timetag: osc.TimeTag = [1000, 1001]; 1581 | const rest = "the rest"; 1582 | const buf = osc.concat([ 1583 | osc.toTimetagBuffer(timetag), 1584 | new TextEncoder().encode(rest), 1585 | ]); 1586 | const { value: timetag2 } = osc.splitTimetag(buf); 1587 | assert.deepStrictEqual(timetag2, timetag); 1588 | }); 1589 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": [ 3 | "lib/**/*" 4 | ], 5 | "compilerOptions": { 6 | "target": "es2020", 7 | "useDefineForClassFields": true, 8 | "module": "node16", 9 | "skipLibCheck": true, 10 | "moduleResolution": "node16", 11 | /* Export options */ 12 | "declaration": true, 13 | "outDir": "./dist", 14 | "rootDir": "./lib", 15 | /* Linting */ 16 | "strict": true, 17 | "noUnusedLocals": true, 18 | "noUnusedParameters": true, 19 | "noFallthroughCasesInSwitch": true, 20 | "noUncheckedIndexedAccess": true, 21 | "isolatedDeclarations": true, 22 | "isolatedModules": true 23 | } 24 | } --------------------------------------------------------------------------------