├── .babelrc ├── .eslintrc.json ├── .gitignore ├── .nycrc ├── .travis.yml ├── README.md ├── index.js ├── package-lock.json ├── package.json ├── src ├── Reader.js ├── Tag.js ├── Writer.js ├── compile.js ├── decode.js ├── encode.js ├── index.js └── utils.js └── test ├── .eslintrc.json ├── Reader.js ├── Tag.js ├── Writer.js ├── compatibility.js ├── compile.js ├── decode.js ├── encode.js ├── index.js ├── mocha.opts └── utils.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "production": { 4 | "only": [ "src" ], 5 | "presets": [ 6 | "es2015" 7 | ] 8 | }, 9 | "development": { 10 | "only": [ "src", "test" ], 11 | "presets": [ 12 | "power-assert" 13 | ] 14 | }, 15 | "cover": { 16 | "only": [ "src" ], 17 | "sourceMaps": "inline", 18 | "plugins": [ 19 | [ "istanbul" ], 20 | [ "transform-es2015-classes", { "loose": true } ] 21 | ] 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "es6": true, 4 | "node": true 5 | }, 6 | "extends": "eslint:recommended" 7 | } 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_STORE 2 | .nyc_output 3 | npm-debug.log 4 | node_modules/ 5 | coverage/ 6 | lib/ 7 | -------------------------------------------------------------------------------- /.nycrc: -------------------------------------------------------------------------------- 1 | { 2 | "require": [ 3 | "babel-register" 4 | ], 5 | "sourceMap": false, 6 | "instrument": false 7 | } 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | sudo: false 3 | node_js: 4 | - "6.0" 5 | cache: 6 | directories: 7 | - node_modules 8 | script: 9 | - npm run travis 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # osc-msg 2 | [![Build Status](https://img.shields.io/travis/mohayonao/osc-msg.svg?style=flat-square)](https://travis-ci.org/mohayonao/osc-msg) 3 | [![NPM Version](https://img.shields.io/npm/v/osc-msg.svg?style=flat-square)](https://www.npmjs.org/package/osc-msg) 4 | [![License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](https://mohayonao.mit-license.org/) 5 | 6 | > OSC message decoder/encoder with fault tolerant 7 | 8 | ## Features 9 | - Not throw an exception if processing with a broken message 10 | - Useful decoding options, `bundle` and `strip` 11 | - Works in both Node.js and browsers 12 | 13 | ## Installation 14 | 15 | npm: 16 | 17 | ``` 18 | npm install osc-msg 19 | ``` 20 | 21 | ## API 22 | 23 | - `oscmsg.decode(buffer: Buffer, opts={}): object` 24 | - `opts.strict`: strictly validation mode 25 | - `opts.strip`: decode into raw values 26 | - `opts.bundle`: decode as a bundle 27 | - aliases: `fromBuffer`, `toObject` 28 | - `oscmsg.encode(object: object, opts={}): Buffer` 29 | - `opts.strict`: strictly validation mode 30 | - `opts.integer`: use an integer when auto cast 31 | - aliases: `fromObject`, `toBuffer` 32 | 33 | ## Examples 34 | 35 | decode 36 | 37 | ```js 38 | const dgram = require("dgram"); 39 | const oscmsg = require("osc-msg"); 40 | 41 | const socket = dgram.createSocket("udp4"); 42 | 43 | socket.on("message", (buffer) => { 44 | const bundle = oscmsg.decode(buffer, { strict: true, strip: true, bundle: true }); 45 | 46 | if (bundle.error) { 47 | return; 48 | } 49 | 50 | bundle.elements.forEach((message) => { 51 | console.log(JSON.stringify(message)); 52 | }); 53 | }); 54 | 55 | socket.bind(RECV_PORT); 56 | ``` 57 | 58 | encode 59 | 60 | ```js 61 | const dgram = require("dgram"); 62 | const oscmsg = require("osc-msg"); 63 | 64 | const message = { 65 | address: "/foo", 66 | args: [ 67 | { type: "integer", value: 0 }, 68 | { type: "float", value: 1.5 } 69 | ] 70 | }; 71 | const buffer = oscmsg.encode(message); 72 | const socket = dgram.createSocket("udp4"); 73 | 74 | socket.send(buffer, 0, buffer.length, SEND_PORT, "127.0.0.1"); 75 | ``` 76 | 77 | ## Javascript representations of the OSC types 78 | 79 | _compatible interfaces with [osc-min](https://github.com/russellmcc/node-osc-min)_ 80 | 81 | - OSC Message 82 | 83 | ```js 84 | { 85 | "address": string, 86 | "args": [ arg1, arg2, ...argN ], 87 | "oscType": "message" 88 | } 89 | ``` 90 | 91 | Where args is an array of OSC Arguments. `oscType` is optional. `args` can be a single element. 92 | 93 | - OSC Arguments 94 | 95 | ```js 96 | { "type": string, "value": any } 97 | ``` 98 | 99 | Where the `type` is one of the following: 100 | 101 | - `string` - string value 102 | - `float` - numeric value 103 | - `integer` - numeric value 104 | - `blob` - Buffer-like value 105 | - `true` - value is boolean true 106 | - `false` - value is boolean false 107 | - `null` - no value 108 | - `bang` - no value (this is the `I` type tag) 109 | - `timetag` - [ uint32, uint32 ] 110 | - `array` - array of OSC Arguments 111 | 112 | - OSC Bundle 113 | 114 | ```js 115 | { 116 | "timetag": number, 117 | "elements": [ element1, element2, ...elementN ], 118 | "oscType": "bundle" 119 | } 120 | ``` 121 | 122 | Where the timetag is a javascript-native numeric value of the timetag, and elements is an array of either an OSC Bundle or an OSC Message The `oscType` field is optional, but is always returned by api functions. 123 | 124 | ## See also 125 | - [The Open Sound Control 1.0 Specification](https://opensoundcontrol.org/spec-1_0) 126 | - [osc-min / Javascript representations of the OSC types](https://github.com/russellmcc/node-osc-min#javascript-representations-of-the-osc-types) 127 | 128 | ## License 129 | MIT 130 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = require("./lib"); 2 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "osc-msg", 3 | "description": "OSC message decoder/encoder with fault tolerant", 4 | "version": "1.2.0", 5 | "author": "Nao Yonamine ", 6 | "bugs": { 7 | "url": "https://github.com/mohayonao/osc-msg/issues" 8 | }, 9 | "dependencies": { 10 | "dataview2": "^1.0.0" 11 | }, 12 | "devDependencies": { 13 | "babel-cli": "^6.24.1", 14 | "babel-plugin-istanbul": "^4.1.4", 15 | "babel-preset-es2015": "^6.24.1", 16 | "babel-preset-power-assert": "^1.0.0", 17 | "babel-register": "^6.24.1", 18 | "eslint": "^3.19.0", 19 | "lodash.flatten": "^4.4.0", 20 | "mocha": "^3.4.2", 21 | "npm-run-all": "^4.0.2", 22 | "nyc": "^11.0.1", 23 | "osc-min": "^1.1.1", 24 | "power-assert": "^1.4.2" 25 | }, 26 | "files": [ 27 | "package.json", 28 | "index.js", 29 | "lib", 30 | "README.md" 31 | ], 32 | "homepage": "http://mohayonao.github.io/osc-msg/", 33 | "keywords": [ 34 | "message", 35 | "open sound control", 36 | "osc", 37 | "serialize" 38 | ], 39 | "license": "MIT", 40 | "main": "index.js", 41 | "repository": { 42 | "type": "git", 43 | "url": "git://github.com/mohayonao/osc-msg.git" 44 | }, 45 | "scripts": { 46 | "build": "npm-run-all build:*", 47 | "build:to5": "BABEL_ENV=production babel --out-dir=lib src", 48 | "clean": "rm -rf lib coverage .nyc_output npm-debug.log", 49 | "cover": "BABEL_ENV=cover nyc --reporter text --reporter html mocha --require babel-register", 50 | "lint": "eslint src test", 51 | "mocha": "mocha", 52 | "postversion": "git push && git push --tags && npm run clean", 53 | "prepublish": "npm-run-all clean lint test build", 54 | "preversion": "npm-run-all clean lint test", 55 | "test": "mocha --require babel-register", 56 | "travis": "npm-run-all lint mocha", 57 | "version": "npm run build" 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/Reader.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const DataView2 = require("dataview2").DataView2; 4 | const Buffer2 = require("dataview2").Buffer2; 5 | 6 | class Reader { 7 | constructor(buffer) { 8 | this.view = new DataView2(buffer); 9 | 10 | this._index = 0; 11 | this._hasError = false; 12 | } 13 | 14 | read(length) { 15 | length >>>= 0; 16 | 17 | if (this._index + length <= this.view.byteLength) { 18 | const buffer = new Buffer2(length); 19 | const view = new DataView2(buffer); 20 | 21 | for (let i = 0; i < length; i++) { 22 | view.setUint8(i, this.readUInt8()); 23 | } 24 | 25 | return buffer; 26 | } 27 | 28 | this._index += length; 29 | this._hasError = true; 30 | 31 | return new Buffer2(0); 32 | } 33 | 34 | readUInt8() { 35 | this._index += 1; 36 | 37 | if (this._index <= this.view.byteLength) { 38 | return this.view.getUint8(this._index - 1); 39 | } 40 | 41 | this._hasError = true; 42 | 43 | return 0; 44 | } 45 | 46 | readInt32() { 47 | this._index += 4; 48 | 49 | if (this._index <= this.view.byteLength) { 50 | return this.view.getInt32(this._index - 4); 51 | } 52 | 53 | this._hasError = true; 54 | 55 | return 0; 56 | } 57 | 58 | readUInt32() { 59 | this._index += 4; 60 | 61 | if (this._index <= this.view.byteLength) { 62 | return this.view.getUint32(this._index - 4); 63 | } 64 | 65 | this._hasError = true; 66 | 67 | return 0; 68 | } 69 | 70 | readFloat32() { 71 | this._index += 4; 72 | 73 | if (this._index <= this.view.byteLength) { 74 | return this.view.getFloat32(this._index - 4); 75 | } 76 | 77 | this._hasError = true; 78 | 79 | return 0; 80 | } 81 | 82 | readFloat64() { 83 | this._index += 8; 84 | 85 | if (this._index <= this.view.byteLength) { 86 | return this.view.getFloat64(this._index - 8); 87 | } 88 | 89 | this._hasError = true; 90 | 91 | return 0; 92 | } 93 | 94 | readString() { 95 | let result = ""; 96 | let charCode; 97 | 98 | if (this.hasNext()) { 99 | while (this.hasNext() && (charCode = this.readUInt8()) !== 0x00) { 100 | result += String.fromCharCode(charCode); 101 | } 102 | this._align(); 103 | } else { 104 | this._hasError = true; 105 | } 106 | 107 | return result; 108 | } 109 | 110 | readBlob() { 111 | const length = this.readUInt32(); 112 | const buffer = this.read(length); 113 | 114 | this._align(); 115 | 116 | return buffer; 117 | } 118 | 119 | readAddress() { 120 | if (this._index < this.view.byteLength) { 121 | if (this.view.getUint8(this._index) == 0) { 122 | return this.readUInt32(); 123 | } 124 | } 125 | return this.readString(); 126 | } 127 | 128 | readTimeTag() { 129 | return [ this.readUInt32(), this.readUInt32() ]; 130 | } 131 | 132 | hasError() { 133 | return this._hasError; 134 | } 135 | 136 | hasNext() { 137 | return this._index < this.view.byteLength; 138 | } 139 | 140 | _align() { 141 | while (this.hasNext() && this._index % 4 !== 0) { 142 | this._index += 1; 143 | } 144 | } 145 | } 146 | 147 | module.exports = Reader; 148 | -------------------------------------------------------------------------------- /src/Tag.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const utils = require("./utils"); 4 | 5 | const types = {}; 6 | const tags = {}; 7 | 8 | [ 9 | { 10 | type: "integer", 11 | tag: "i", 12 | size: () => 4, 13 | validate: utils.isInteger, 14 | valueOf: value => +value|0, 15 | write(writer, value) { 16 | writer.writeInt32(value); 17 | }, 18 | read: reader => reader.readInt32() 19 | }, 20 | { 21 | type: "float", 22 | tag: "f", 23 | size: () => 4, 24 | validate: utils.isFloat, 25 | valueOf: value => +value || 0, 26 | write(writer, value) { 27 | writer.writeFloat32(value); 28 | }, 29 | read: reader => reader.readFloat32() 30 | }, 31 | { 32 | type: "string", 33 | tag: "s", 34 | size: value => utils.size4(value.length + 1), 35 | validate: utils.isString, 36 | valueOf: value => "" + value, 37 | write(writer, value) { 38 | writer.writeString(value); 39 | }, 40 | read: reader => reader.readString() 41 | }, 42 | { 43 | type: "blob", 44 | tag: "b", 45 | size: value => 4 + utils.size4(value.byteLength || value.length), 46 | validate: utils.isBlob, 47 | valueOf: value => utils.toBlob(value), 48 | write(writer, value) { 49 | writer.writeBlob(value); 50 | }, 51 | read: reader => reader.readBlob() 52 | }, 53 | { 54 | type: "timetag", 55 | tag: "t", 56 | size: () => 8, 57 | validate: utils.isTimeTag, 58 | valueOf: utils.toTimeTag, 59 | write(writer, value) { 60 | writer.writeTimeTag(value); 61 | }, 62 | read: reader => reader.readTimeTag() 63 | }, 64 | { 65 | type: "double", 66 | tag: "d", 67 | size: () => 8, 68 | validate: utils.isDouble, 69 | valueOf: value => +value || 0, 70 | write(writer, value) { 71 | writer.writeFloat64(value); 72 | }, 73 | read: reader => reader.readFloat64() 74 | }, 75 | { 76 | type: "true", 77 | tag: "T", 78 | size: () => 0, 79 | validate: value => value === true, 80 | valueOf: () => true, 81 | write: () => {}, 82 | read: () => true 83 | }, 84 | { 85 | type: "false", 86 | tag: "F", 87 | size: () => 0, 88 | validate: value => value === false, 89 | valueOf: () => false, 90 | write: () => {}, 91 | read: () => false 92 | }, 93 | { 94 | type: "null", 95 | tag: "N", 96 | size: () => 0, 97 | validate: value => value === null, 98 | valueOf: () => null, 99 | write: () => {}, 100 | read: () => null 101 | }, 102 | { 103 | type: "bang", 104 | tag: "I", 105 | size: () => 0, 106 | validate: value => value === "bang", 107 | valueOf: () => "bang", 108 | write: () => {}, 109 | read: () => "bang" 110 | } 111 | ].forEach((params) => { 112 | types[params.type] = params; 113 | tags[params.tag] = params; 114 | }); 115 | 116 | module.exports = { types, tags }; 117 | -------------------------------------------------------------------------------- /src/Writer.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const DataView2 = require("dataview2").DataView2; 4 | 5 | class Writer { 6 | constructor(buffer) { 7 | this.view = new DataView2(buffer); 8 | 9 | this._index = 0; 10 | this._hasError = false; 11 | } 12 | 13 | writeUInt8(value) { 14 | this._index += 1; 15 | 16 | if (this._index <= this.view.byteLength) { 17 | this.view.setUint8(this._index - 1, value); 18 | } else { 19 | this._hasError = true; 20 | } 21 | } 22 | 23 | writeInt32(value) { 24 | this._index += 4; 25 | 26 | if (this._index <= this.view.byteLength) { 27 | this.view.setInt32(this._index - 4, value); 28 | } else { 29 | this._hasError = true; 30 | } 31 | } 32 | 33 | writeUInt32(value) { 34 | this._index += 4; 35 | 36 | if (this._index <= this.view.byteLength) { 37 | this.view.setUint32(this._index - 4, value); 38 | } else { 39 | this._hasError = true; 40 | } 41 | } 42 | 43 | writeFloat32(value) { 44 | this._index += 4; 45 | 46 | if (this._index <= this.view.byteLength) { 47 | this.view.setFloat32(this._index - 4, value); 48 | } else { 49 | this._hasError = true; 50 | } 51 | } 52 | 53 | writeFloat64(value) { 54 | this._index += 8; 55 | 56 | if (this._index <= this.view.byteLength) { 57 | this.view.setFloat64(this._index - 8, value); 58 | } else { 59 | this._hasError = true; 60 | } 61 | } 62 | 63 | writeString(value) { 64 | for (let i = 0; i < value.length; i++) { 65 | this.writeUInt8(value.charCodeAt(i)); 66 | } 67 | 68 | this.writeUInt8(0); 69 | this._align(); 70 | } 71 | 72 | writeBlob(value) { 73 | const view = new DataView2(value); 74 | const length = view.byteLength; 75 | 76 | this.writeUInt32(length); 77 | 78 | for (let i = 0; i < length; i++) { 79 | this.writeUInt8(view.getUint8(i)); 80 | } 81 | 82 | this._align(); 83 | } 84 | 85 | writeAddress(value) { 86 | if (typeof value === "number") { 87 | this.writeUInt32(value); 88 | } else { 89 | this.writeString("" + value); 90 | } 91 | } 92 | 93 | writeTimeTag([ hi, lo ]) { 94 | this.writeUInt32(hi); 95 | this.writeUInt32(lo); 96 | } 97 | 98 | writeRawData(buffer, length) { 99 | if (this._index + length <= this.view.byteLength) { 100 | for (let i = 0; i < length; i++) { 101 | this.view.setUint8(this._index++, buffer[i]); 102 | } 103 | } else { 104 | this._hasError = true; 105 | } 106 | } 107 | 108 | hasError() { 109 | return this._hasError; 110 | } 111 | 112 | hasNext() { 113 | return this._index < this.view.byteLength; 114 | } 115 | 116 | _align() { 117 | while (this._index % 4 !== 0) { 118 | this.writeUInt8(0x00); 119 | } 120 | } 121 | } 122 | 123 | module.exports = Writer; 124 | -------------------------------------------------------------------------------- /src/compile.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Tag = require("./Tag"); 4 | const utils = require("./utils"); 5 | 6 | function compile(object, opts) { 7 | if (object === null || typeof object !== "object") { 8 | object = { args: [ object ] }; 9 | } 10 | if (Array.isArray(object)) { 11 | object = { args: object }; 12 | } 13 | if (Array.isArray(object.elements) || utils.isTimeTag(object.timetag)) { 14 | return compileBundle(object, opts); 15 | } 16 | return compileMessage(object, opts); 17 | } 18 | 19 | function compileBundle(object, opts) { 20 | const timetag = utils.toTimeTag(object.timetag); 21 | const elements = utils.toArray(object.elements).map((element) => { 22 | if (utils.isBlob(element)) { 23 | return compileRawData(element); 24 | } 25 | return compile(element, opts); 26 | }); 27 | const oscType = "bundle"; 28 | 29 | let bufferLength = 0; 30 | // #bundle_ 31 | bufferLength += 8; 32 | // timetag 33 | bufferLength += 8; 34 | // blob size 35 | bufferLength += elements.length * 4; 36 | bufferLength += elements.reduce((bufferLength, element) => bufferLength + element.bufferLength, 0); 37 | 38 | const error = elements.reduce((error, element) => error || element.error, null); 39 | 40 | return { timetag, elements, bufferLength, oscType, error }; 41 | } 42 | 43 | function compileRawData(buffer) { 44 | const oscType = ""; 45 | const bufferLength = buffer.length || buffer.byteLength; 46 | 47 | return { buffer, bufferLength, oscType }; 48 | } 49 | 50 | function compileMessage(object, opts) { 51 | if (opts.strict && typeof object.address !== "string") { 52 | return { 53 | address: "", types: "", values: [], bufferLength: 0, oscType: "message", 54 | error: new TypeError("OSC Message must contain an address") 55 | }; 56 | } 57 | 58 | const address = utils.toAddress(object.address); 59 | const args = utils.toArray(object.args).map(value => convertTypedValue(value, opts)); 60 | const items = build(args, opts); 61 | const types = items.types; 62 | const values = items.values; 63 | const error = items.error; 64 | const oscType = "message"; 65 | 66 | let bufferLength = items.bufferLength; 67 | 68 | if (typeof address === "number") { 69 | bufferLength += 4; 70 | } else { 71 | bufferLength += utils.size4(address.length + 1); 72 | } 73 | bufferLength += utils.size4(types.length + 2); 74 | 75 | return { address, types, values, bufferLength, oscType, error }; 76 | } 77 | 78 | function convertTypedValue(value, opts) { 79 | if (utils.isNone(value)) { 80 | return { type: "null", value: null }; 81 | } 82 | 83 | switch (typeof value) { 84 | case "number": 85 | if (opts.integer) { 86 | return { type: "integer", value }; 87 | } 88 | return { type: "float", value }; 89 | case "string": 90 | return { type: "string", value }; 91 | case "boolean": 92 | return { type: value.toString(), value }; 93 | default: 94 | // do nothing 95 | } 96 | 97 | if (Array.isArray(value)) { 98 | return { type: "array", value: value.map(value => convertTypedValue(value, opts)) }; 99 | } 100 | 101 | if (utils.isBlob(value)) { 102 | return { type: "blob", value }; 103 | } 104 | 105 | if (value instanceof Date) { 106 | return { type: "timetag", value: utils.toTimeTagFromDate(value) }; 107 | } 108 | 109 | return value; 110 | } 111 | 112 | function build(args, opts) { 113 | const values = []; 114 | 115 | let types = ""; 116 | let bufferLength = 0; 117 | let error = null; 118 | 119 | for (let i = 0; i < args.length; i++) { 120 | let value = args[i]; 121 | 122 | if (Tag.types.hasOwnProperty(value.type)) { 123 | const type = Tag.types[value.type]; 124 | 125 | if (opts.strict && !type.validate(value.value)) { 126 | error = new TypeError(`Invalid date: expected ${value.type}, but got ${JSON.stringify(value)}`); 127 | } 128 | 129 | value = { type: value.type, value: type.valueOf(value.value) }; 130 | 131 | values.push(value); 132 | types += type.tag; 133 | bufferLength += type.size(value.value); 134 | } else if (value.type === "array") { 135 | const items = build(value.value, opts); 136 | 137 | values.push.apply(values, items.values); 138 | types += `[${items.types}]`; 139 | bufferLength += items.bufferLength; 140 | error = error || items.error; 141 | } else { 142 | error = new TypeError(`Invalid data: ${value}`); 143 | } 144 | 145 | if (error !== null) { 146 | break; 147 | } 148 | } 149 | 150 | return { types, values, bufferLength, error }; 151 | } 152 | 153 | module.exports = compile; 154 | -------------------------------------------------------------------------------- /src/decode.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Reader = require("./Reader"); 4 | const Tag = require("./Tag"); 5 | const utils = require("./utils"); 6 | 7 | function decode(buffer, opts) { 8 | opts = opts || {}; 9 | 10 | if (!utils.isBlob(buffer)) { 11 | return { error: new TypeError("invalid Buffer for OSCMessage") }; 12 | } 13 | 14 | const reader = new Reader(buffer); 15 | 16 | if (reader.readString() === "#bundle") { 17 | return decodeBundle(buffer, opts); 18 | } 19 | 20 | const msg = decodeMessage(buffer, opts); 21 | 22 | if (opts.bundle) { 23 | const bundle = { timetag: [ 0, 0 ], elements: [ msg ], oscType: "bundle" }; 24 | 25 | if (msg.error) { 26 | bundle.error = msg.error; 27 | } 28 | 29 | return bundle; 30 | } 31 | 32 | return msg; 33 | } 34 | 35 | function decodeBundle(buffer, opts) { 36 | const reader = new Reader(buffer); 37 | 38 | // read '#bundle' 39 | reader.readString(); 40 | 41 | const timetag = reader.readTimeTag(); 42 | const elements = []; 43 | const oscType = "bundle"; 44 | 45 | let error = null; 46 | 47 | while (reader.hasNext()) { 48 | const length = reader.readUInt32(); 49 | const buffer = reader.read(length); 50 | const msg = decode(buffer, opts); 51 | 52 | if (msg.error) { 53 | error = msg.error; 54 | } 55 | 56 | elements.push(msg); 57 | 58 | if (reader.hasError()) { 59 | error = new RangeError("Offset is outside the bounds of the DataView"); 60 | } 61 | 62 | if (error !== null) { 63 | break; 64 | } 65 | } 66 | 67 | const bundle = { timetag, elements, oscType }; 68 | 69 | if (error) { 70 | bundle.error = error; 71 | } 72 | 73 | return bundle; 74 | } 75 | 76 | function decodeMessage(buffer, opts) { 77 | const reader = new Reader(buffer); 78 | const address = reader.readAddress(); 79 | const tags = reader.readString(); 80 | const oscType = "message"; 81 | 82 | let args = []; 83 | let error = null; 84 | 85 | if (tags[0] === ",") { 86 | const stack = []; 87 | 88 | for (let i = 1; i < tags.length; i++) { 89 | const tag = tags[i]; 90 | 91 | switch (tag) { 92 | case "[": 93 | stack.push(args); 94 | args = []; 95 | break; 96 | case "]": 97 | if (stack.length !== 0) { 98 | const pop = stack.pop(); 99 | 100 | if (opts.strip) { 101 | pop.push(args); 102 | } else { 103 | pop.push({ type: "array", value: args }); 104 | } 105 | 106 | args = pop; 107 | } else { 108 | error = new TypeError("Unexpected token ']'"); 109 | } 110 | break; 111 | default: 112 | if (Tag.tags.hasOwnProperty(tag)) { 113 | if (opts.strip) { 114 | args.push(Tag.tags[tag].read(reader)); 115 | } else { 116 | args.push({ type: Tag.tags[tag].type, value: Tag.tags[tag].read(reader) }); 117 | } 118 | } else { 119 | error = new TypeError(`Not supported tag '${tag}'`); 120 | } 121 | } 122 | 123 | if (reader.hasError()) { 124 | error = new RangeError("Offset is outside the bounds of the DataView"); 125 | } 126 | 127 | if (error !== null) { 128 | break; 129 | } 130 | } 131 | 132 | if (error === null && stack.length !== 0) { 133 | error = new TypeError("Unexpected token '['"); 134 | } 135 | } else if (opts.strict) { 136 | error = new TypeError("Missing OSC Type Tag String"); 137 | } 138 | 139 | const msg = { address, args, oscType }; 140 | 141 | if (error) { 142 | msg.error = error; 143 | } 144 | 145 | return msg; 146 | } 147 | 148 | module.exports = decode; 149 | -------------------------------------------------------------------------------- /src/encode.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Buffer2 = require("dataview2").Buffer2; 4 | const compile = require("./compile"); 5 | const Writer = require("./Writer"); 6 | const Tag = require("./Tag"); 7 | 8 | function encode(object, opts) { 9 | opts = opts || {}; 10 | object = compile(object, opts); 11 | 12 | const bufferLength = object.bufferLength; 13 | const buffer = new Buffer2(bufferLength); 14 | const writer = new Writer(buffer); 15 | 16 | if (object.oscType === "bundle") { 17 | encodeBundle(writer, object); 18 | } else { 19 | encodeMessage(writer, object); 20 | } 21 | 22 | if (object.error) { 23 | buffer.error = object.error; 24 | } 25 | 26 | return buffer; 27 | } 28 | 29 | function encodeBundle(writer, object) { 30 | writer.writeString("#bundle"); 31 | writer.writeTimeTag(object.timetag); 32 | 33 | object.elements.forEach((element) => { 34 | writer.writeUInt32(element.bufferLength); 35 | 36 | if (element.oscType === "") { 37 | encodeRawData(writer, element); 38 | } else if (element.oscType === "bundle") { 39 | encodeBundle(writer, element); 40 | } else { 41 | encodeMessage(writer, element); 42 | } 43 | }); 44 | } 45 | 46 | function encodeRawData(writer, element) { 47 | writer.writeRawData(element.buffer, element.bufferLength); 48 | } 49 | 50 | function encodeMessage(writer, object) { 51 | writer.writeAddress(object.address); 52 | writer.writeString("," + object.types); 53 | 54 | object.values.forEach((items) => { 55 | Tag.types[items.type].write(writer, items.value); 56 | }); 57 | } 58 | 59 | module.exports = encode; 60 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const decode = require("./decode"); 4 | const encode = require("./encode"); 5 | 6 | module.exports = { 7 | decode, 8 | encode, 9 | fromBuffer: decode, 10 | fromObject: encode, 11 | toBuffer: encode, 12 | toObject: decode 13 | }; 14 | -------------------------------------------------------------------------------- /src/utils.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const DataView2 = require("dataview2").DataView2; 4 | const Buffer2 = require("dataview2").Buffer2; 5 | 6 | function size4(num) { 7 | return Math.ceil((num|0) / 4) << 2; 8 | } 9 | 10 | function isNone(value) { 11 | return typeof value === "undefined" || value === null; 12 | } 13 | 14 | function isInteger(value) { 15 | return Math.floor(value) === value && value % 1 === 0; 16 | } 17 | 18 | function isFloat(value) { 19 | return !isNaN(value) && typeof value === "number"; 20 | } 21 | 22 | function isDouble(value) { 23 | return !isNaN(value) && typeof value === "number"; 24 | } 25 | 26 | function isTimeTag(value) { 27 | if (Array.isArray(value)) { 28 | return value.length === 2 && isInteger(value[0]) && isInteger(value[1]); 29 | } 30 | if (value instanceof Date) { 31 | return true; 32 | } 33 | return typeof value === "number" && value >= 0 && value % 1 === 0; 34 | } 35 | 36 | function isString(value) { 37 | return typeof value === "string"; 38 | } 39 | 40 | function isBlob(value) { 41 | return (value instanceof ArrayBuffer) || (value instanceof global.Buffer); 42 | } 43 | 44 | function toString(value) { 45 | if (isNone(value)) { 46 | return ""; 47 | } 48 | return "" + value; 49 | } 50 | 51 | function toArray(value) { 52 | if (Array.isArray(value)) { 53 | return value; 54 | } 55 | if (isNone(value)) { 56 | return []; 57 | } 58 | return [ value ]; 59 | } 60 | 61 | function toBlob(value) { 62 | if (isBlob(value)) { 63 | return value; 64 | } 65 | 66 | if (Array.isArray(value)) { 67 | const view = new DataView2(new Buffer2(value.length)); 68 | 69 | for (let i = 0; i < value.length; i++) { 70 | view.setUint8(i, value[i]); 71 | } 72 | 73 | return view.buffer; 74 | } 75 | 76 | if (typeof value === "string") { 77 | const view = new DataView2(new Buffer2(value.length)); 78 | 79 | for (let i = 0; i < value.length; i++) { 80 | view.setUint8(i, value.charCodeAt(i)); 81 | } 82 | 83 | return view.buffer; 84 | } 85 | 86 | if (typeof value === "number") { 87 | return new Buffer2(+value|0); 88 | } 89 | 90 | return new Buffer2(0); 91 | } 92 | 93 | function toAddress(value) { 94 | if (typeof value === "number") { 95 | return value >>> 0; 96 | } 97 | return toString(value); 98 | } 99 | 100 | function toTimeTag(value) { 101 | if (Array.isArray(value)) { 102 | return toTimeTagFromArray(value); 103 | } 104 | if (value instanceof Date) { 105 | return toTimeTagFromDate(value); 106 | } 107 | return toTimeTagFromNumber(value); 108 | } 109 | 110 | function toTimeTagFromArray([ hi, lo ]) { 111 | hi >>>= 0; 112 | lo >>>= 0; 113 | 114 | return [ hi, lo ]; 115 | } 116 | 117 | function toTimeTagFromDate(value) { 118 | const time = (value / 1000) + 2208988800; 119 | const hi = time >>> 0; 120 | const lo = ((time - hi) * 4294967296) >>> 0; 121 | 122 | return [ hi, lo ]; 123 | } 124 | 125 | function toTimeTagFromNumber(value) { 126 | const hi = (value / 4294967296) >>> 0; 127 | const lo = (value % 4294967296) >>> 0; 128 | 129 | return [ hi, lo ]; 130 | } 131 | 132 | module.exports = { 133 | size4, isNone, isInteger, isFloat, isDouble, isTimeTag, isString, isBlob, toString, toArray, toBlob, toAddress, toTimeTag 134 | }; 135 | -------------------------------------------------------------------------------- /test/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "mocha": true 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /test/Reader.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const assert = require("power-assert"); 4 | const Reader = require("../src/Reader"); 5 | 6 | describe("Reader", () => { 7 | describe("constructor(buffer: Buffer|ArrayBuffer)", () => { 8 | it("works", () => { 9 | const reader = new Reader(new Buffer(16)); 10 | 11 | assert(reader instanceof Reader); 12 | }); 13 | }); 14 | describe("#read(length: number): Buffer|ArrayBuffer", () => { 15 | it("works", () => { 16 | const reader = new Reader(new Buffer([ 17 | 0x00, 0x00, 0x03, 0xe8, 18 | 0xff, 0xff, 0xff, 0xff 19 | ])); 20 | 21 | assert.deepEqual(reader.read(5), new Buffer([ 0x00, 0x00, 0x03, 0xe8, 0xff ])); 22 | assert.deepEqual(reader.read(3), new Buffer([ 0xff, 0xff, 0xff ])); 23 | assert(reader.hasError() === false); 24 | assert.deepEqual(reader.read(-1), new Buffer(0)); 25 | assert(reader.hasError() === true); 26 | }); 27 | }); 28 | describe("#readUInt8(): number", () => { 29 | it("works", () => { 30 | const reader = new Reader(new Buffer([ 31 | 0x00, 0x00, 0x03, 0xe8, 32 | 0xff, 0xff, 0xff, 0xff 33 | ])); 34 | 35 | assert(reader.readUInt8() === 0x00); 36 | assert(reader.readUInt8() === 0x00); 37 | assert(reader.readUInt8() === 0x03); 38 | assert(reader.readUInt8() === 0xe8); 39 | assert(reader.readUInt8() === 0xff); 40 | assert(reader.readUInt8() === 0xff); 41 | assert(reader.readUInt8() === 0xff); 42 | assert(reader.readUInt8() === 0xff); // EOD 43 | assert(reader.hasError() === false); 44 | assert(reader.readUInt8() === 0x00); 45 | assert(reader.hasError() === true); 46 | }); 47 | }); 48 | describe("#readInt32(): number", () => { 49 | it("works", () => { 50 | const reader = new Reader(new Buffer([ 51 | 0x00, 0x00, 0x03, 0xe8, // int32 1000 52 | 0xff, 0xff, 0xff, 0xff // int32 -1 53 | ])); 54 | 55 | assert(reader.readInt32() === 1000); 56 | assert(reader.readInt32() === -1); // EOD 57 | assert(reader.hasError() === false); 58 | assert(reader.readInt32() === 0); 59 | assert(reader.hasError() === true); 60 | }); 61 | }); 62 | 63 | describe("#readUInt32(): number", () => { 64 | it("works", () => { 65 | const reader = new Reader(new Buffer([ 66 | 0x00, 0x00, 0x03, 0xe8, // uint32 1000 67 | 0xff, 0xff, 0xff, 0xff // uint32 4294967295 68 | ])); 69 | 70 | assert(reader.readUInt32() === 1000); 71 | assert(reader.readUInt32() === 4294967295); // EOD 72 | assert(reader.hasError() === false); 73 | assert(reader.readUInt32() === 0); 74 | assert(reader.hasError() === true); 75 | }); 76 | }); 77 | describe("#readFloat32(): number", () => { 78 | it("works", () => { 79 | const reader = new Reader(new Buffer([ 80 | 0x3f, 0x9d, 0xf3, 0xb6, // float32 1.234 81 | 0x40, 0xb5, 0xb2, 0x2d // float32 5.678 82 | ])); 83 | 84 | assert(reader.readFloat32() === 1.2339999675750732); 85 | assert(reader.readFloat32() === 5.6779999732971190); // EOD 86 | assert(reader.hasError() === false); 87 | assert(reader.readFloat32() === 0); 88 | assert(reader.hasError() === true); 89 | }); 90 | }); 91 | describe("#readFloat64(): number", () => { 92 | it("works", () => { 93 | const reader = new Reader(new Buffer([ 94 | 0x3f, 0xf3, 0xc0, 0xca, 95 | 0x2a, 0x5b, 0x1d, 0x5d, // float64 1.2345678 96 | 0x40, 0x22, 0x06, 0x52, 97 | 0x29, 0x98, 0x7f, 0xbe // float64 9.0123456 98 | ])); 99 | 100 | assert(reader.readFloat64() === 1.2345678); 101 | assert(reader.readFloat64() === 9.0123456); // EOD 102 | assert(reader.hasError() === false); 103 | assert(reader.readFloat64() === 0); 104 | assert(reader.hasError() === true); 105 | }); 106 | }); 107 | describe("#readString(): string", () => { 108 | it("works", () => { 109 | const reader = new Reader(new Buffer([ 110 | 0x2f, 0x66, 0x6f, 0x6f, // "/foo" 111 | 0x00, 0x00, 0x00, 0x00, 112 | 0x2c, 0x69, 0x69, 0x73, // ",iisff" 113 | 0x66, 0x66, 0x00, 0x00 114 | ])); 115 | 116 | assert(reader.readString() === "/foo"); 117 | assert(reader.readString() === ",iisff"); // EOD 118 | assert(reader.hasError() === false); 119 | assert(reader.readString() === ""); 120 | assert(reader.hasError() === true); 121 | }); 122 | }); 123 | describe("#readBlob(): Buffer|ArrayBuffer", () => { 124 | it("works", () => { 125 | const reader = new Reader(new Buffer([ 126 | 0x00, 0x00, 0x00, 0x06, // size = 6 127 | 0x01, 0x02, 0x03, 0x04, // Buffer([ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 ]) 128 | 0x05, 0x06, 0x00, 0x00, 129 | 0x00, 0x00, 0x00, 0x04, // size = 4 130 | 0x07, 0x08, 0x09, 0x00, // Buffer([ 0x07, 0x08, 0x09, 0x00 ]) 131 | 0x00, 0x00, 0x00, 0x08, // size = 8 132 | 0x0a // Buffer([ 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 ]) 133 | ])); 134 | 135 | assert.deepEqual(reader.readBlob(), new Buffer([ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 ])); 136 | assert.deepEqual(reader.readBlob(), new Buffer([ 0x07, 0x08, 0x09, 0x00 ])); 137 | assert(reader.hasError() === false); 138 | assert.deepEqual(reader.readBlob(), new Buffer(0)); // EOD 139 | assert(reader.hasError() === true); 140 | assert.deepEqual(reader.readBlob(), new Buffer(0)); 141 | }); 142 | }); 143 | describe("#readAddress(): string", () => { 144 | it("works", () => { 145 | const reader = new Reader(new Buffer([ 146 | 0x2f, 0x66, 0x6f, 0x6f, // "/foo" 147 | 0x00, 0x00, 0x00, 0x00, 148 | ])); 149 | 150 | assert(reader.readAddress() === "/foo"); 151 | assert(reader.hasError() === false); 152 | assert(reader.readAddress() === ""); 153 | assert(reader.hasError() === true); 154 | }); 155 | }); 156 | describe("#readAddress(): number", () => { 157 | // It isn't the OSC spec, but SuperCollider allows numeric address. 158 | it("number", () => { 159 | const reader = new Reader(new Buffer([ 160 | 0x00, 0x00, 0x00, 0x01, // uint32 1 161 | ])); 162 | 163 | assert(reader.readAddress() === 1); 164 | assert(reader.hasError() === false); 165 | assert(reader.readAddress() === ""); 166 | assert(reader.hasError() === true); 167 | }); 168 | }); 169 | describe("#readTimeTag(): [ number, number ]", () => { 170 | it("works", () => { 171 | const reader = new Reader(new Buffer([ 172 | 0x83, 0xaa, 0x7e, 0x80, // uint32 2208988800 173 | 0x80, 0x00, 0x00, 0x00, // uint32 2147483648 174 | ])); 175 | 176 | assert.deepEqual(reader.readTimeTag(), [ 2208988800, 2147483648 ]); 177 | assert(reader.hasError() === false); 178 | assert.deepEqual(reader.readTimeTag(), [ 0, 0 ]); 179 | assert(reader.hasError() === true); 180 | }); 181 | }); 182 | describe("#hasError(): boolean", () => { 183 | it("works", () => { 184 | const reader = new Reader(new Buffer(4)); 185 | 186 | assert(reader.hasError() === false); 187 | 188 | reader.readUInt32(); 189 | assert(reader.hasError() === false); 190 | 191 | reader.readUInt32(); 192 | assert(reader.hasError() === true); 193 | }); 194 | }); 195 | describe("#hasNext(): boolean", () => { 196 | it("works", () => { 197 | const reader = new Reader(new Buffer(8)); 198 | 199 | assert(reader.hasNext() === true); 200 | 201 | reader.readInt32(); 202 | assert(reader.hasNext() === true); 203 | 204 | reader.readInt32(); 205 | assert(reader.hasNext() === false); 206 | }); 207 | }); 208 | }); 209 | -------------------------------------------------------------------------------- /test/Tag.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const assert = require("power-assert"); 4 | const Writer = require("../src/Writer"); 5 | const Reader = require("../src/Reader"); 6 | const types = require("../src/Tag").types; 7 | const tags = require("../src/Tag").tags; 8 | 9 | describe("Tag", () => { 10 | let writer, reader; 11 | 12 | beforeEach(() => { 13 | const buffer = new Buffer(16); 14 | 15 | writer = new Writer(buffer); 16 | reader = new Reader(buffer); 17 | }); 18 | 19 | describe('["integer"]', () => { 20 | it("works", () => { 21 | assert(types["integer"].tag === "i"); 22 | assert(types["integer"].size() === 4); 23 | assert(types["integer"].validate(0) === true); 24 | assert(types["integer"].validate(-10) === true); 25 | assert(types["integer"].validate(1.5) === false); 26 | assert(types["integer"].validate(Infinity) === false); 27 | assert(types["integer"].validate(NaN) === false); 28 | assert(types["integer"].valueOf(0) === 0); 29 | assert(types["integer"].valueOf(Math.PI) === 3); 30 | assert(types["integer"].valueOf(Infinity) === 0); 31 | assert(types["integer"].valueOf("3") === 3); 32 | assert(types["integer"].valueOf(NaN) === 0); 33 | 34 | types["integer"].write(writer, 1); 35 | assert(types["integer"].read(reader) === 1); 36 | 37 | assert(tags["i"] === types["integer"]); 38 | }); 39 | }); 40 | describe('["float"]', () => { 41 | it("works", () => { 42 | assert(types["float"].tag === "f"); 43 | assert(types["float"].size() === 4); 44 | assert(types["float"].validate(0) === true); 45 | assert(types["float"].validate(-10) === true); 46 | assert(types["float"].validate(1.5) === true); 47 | assert(types["float"].validate(Infinity) === true); 48 | assert(types["float"].validate(NaN) === false); 49 | assert(types["float"].valueOf(0) === 0); 50 | assert(types["float"].valueOf(Math.PI) === Math.PI); 51 | assert(types["float"].valueOf(Infinity) === Infinity); 52 | assert(types["float"].valueOf("3") === 3); 53 | assert(types["float"].valueOf(NaN) === 0); 54 | 55 | types["float"].write(writer, 1.234); 56 | assert(types["float"].read(reader) === Math.fround(1.234)); 57 | 58 | assert(tags["f"] === types["float"]); 59 | }); 60 | }); 61 | describe('["string"]', () => { 62 | it("works", () => { 63 | assert(types["string"].tag === "s"); 64 | assert(types["string"].size("foo") === 4); 65 | assert(types["string"].size("quux") === 8); 66 | assert(types["string"].validate(0) === false); 67 | assert(types["string"].validate("0") === true); 68 | assert(types["string"].valueOf(0) === "0"); 69 | assert(types["string"].valueOf("3") === "3"); 70 | 71 | types["string"].write(writer, "foo"); 72 | assert(types["string"].read(reader) === "foo"); 73 | 74 | assert(tags["s"] === types["string"]); 75 | }); 76 | }); 77 | describe('["blob"]', () => { 78 | it("works", () => { 79 | assert(types["blob"].tag === "b"); 80 | assert(types["blob"].size(new Buffer(0)) === 4); 81 | assert(types["blob"].size(new Buffer(6)) === 12); 82 | assert(types["blob"].size(new Buffer(8)) === 12); 83 | assert(types["blob"].size(new Uint8Array(0).buffer) === 4); 84 | assert(types["blob"].size(new Uint8Array(6).buffer) === 12); 85 | assert(types["blob"].size(new Uint8Array(8).buffer) === 12); 86 | assert(types["blob"].validate(new Buffer(8)) === true); 87 | assert(types["blob"].validate(new Uint8Array(8).buffer) === true); 88 | assert.deepEqual(types["blob"].valueOf([ 0x01, 0x02, 0x03, 0x04 ]), new Buffer([ 0x01, 0x02, 0x03, 0x04 ])); 89 | 90 | types["blob"].write(writer, new Buffer([ 0x01, 0x02, 0x03, 0x04 ])); 91 | assert.deepEqual(types["blob"].read(reader), new Buffer([ 0x01, 0x02, 0x03, 0x04 ])); 92 | 93 | assert(tags["b"] === types["blob"]); 94 | }); 95 | }); 96 | describe('["timetag"]', () => { 97 | it("works", () => { 98 | assert(types["timetag"].tag === "t"); 99 | assert(types["timetag"].size() === 8); 100 | assert(types["timetag"].validate(0) === true); 101 | assert(types["timetag"].validate(-10) === false); 102 | assert(types["timetag"].validate(1.5) === false); 103 | assert(types["timetag"].validate(Infinity) === false); 104 | assert(types["timetag"].validate(NaN) === false); 105 | assert.deepEqual(types["timetag"].valueOf(0), [ 0, 0 ]); 106 | assert.deepEqual(types["timetag"].valueOf(Math.PI), [ 0, 3 ]); 107 | assert.deepEqual(types["timetag"].valueOf(Infinity), [ 0, 0 ]); 108 | assert.deepEqual(types["timetag"].valueOf("3"), [ 0, 3 ]); 109 | assert.deepEqual(types["timetag"].valueOf(NaN), [ 0, 0 ]); 110 | assert.deepEqual(types["timetag"].valueOf(9487534655377768000), [ 2208988800, 2147483648 ]); 111 | assert.deepEqual(types["timetag"].valueOf([ 2208988800, 0 ]), [ 2208988800, 0 ]); 112 | assert.deepEqual(types["timetag"].valueOf([ 2208988800, 2147483648 ]), [ 2208988800, 2147483648 ]); 113 | 114 | types["timetag"].write(writer, [ 2208988800, 2147483648 ]); 115 | assert.deepEqual(types["timetag"].read(reader), [ 2208988800, 2147483648 ]); 116 | 117 | assert(tags["t"] === types["timetag"]); 118 | }); 119 | }); 120 | describe('["double"]', () => { 121 | it("works", () => { 122 | assert(types["double"].tag === "d"); 123 | assert(types["double"].size() === 8); 124 | assert(types["double"].validate(0) === true); 125 | assert(types["double"].validate(-10) === true); 126 | assert(types["double"].validate(1.5) === true); 127 | assert(types["double"].validate(Infinity) === true); 128 | assert(types["double"].validate(NaN) === false); 129 | assert(types["double"].valueOf(0) === 0); 130 | assert(types["double"].valueOf(Math.PI) === Math.PI); 131 | assert(types["double"].valueOf(Infinity) === Infinity); 132 | assert(types["double"].valueOf("3") === 3); 133 | assert(types["double"].valueOf(NaN) === 0); 134 | 135 | types["double"].write(writer, 1.5); 136 | assert(types["double"].read(reader) === 1.5); 137 | 138 | assert(tags["d"] === types["double"]); 139 | }); 140 | }); 141 | describe('["true"]', () => { 142 | it("works", () => { 143 | assert(types["true"].tag === "T"); 144 | assert(types["true"].size() === 0); 145 | assert(types["true"].validate(true) === true); 146 | assert(types["true"].validate(false) === false); 147 | assert(types["true"].validate(null) === false); 148 | assert(types["true"].valueOf() === true); 149 | 150 | types["true"].write(writer, true); 151 | assert(types["true"].read(reader) === true); 152 | 153 | assert(tags["T"] === types["true"]); 154 | }); 155 | }); 156 | describe('["false"]', () => { 157 | it("works", () => { 158 | assert(types["false"].tag === "F"); 159 | assert(types["false"].size() === 0); 160 | assert(types["false"].validate(true) === false); 161 | assert(types["false"].validate(false) === true); 162 | assert(types["false"].validate(null) === false); 163 | assert(types["false"].valueOf() === false); 164 | 165 | types["false"].write(writer, false); 166 | assert(types["false"].read(reader) === false); 167 | 168 | assert(tags["F"] === types["false"]); 169 | }); 170 | }); 171 | describe('["null"]', () => { 172 | it("works", () => { 173 | assert(types["null"].tag === "N"); 174 | assert(types["null"].size() === 0); 175 | assert(types["null"].validate(true) === false); 176 | assert(types["null"].validate(false) === false); 177 | assert(types["null"].validate(null) === true); 178 | assert(types["null"].valueOf() === null); 179 | 180 | types["null"].write(writer, null); 181 | assert(types["null"].read(reader) === null); 182 | 183 | assert(tags["N"] === types["null"]); 184 | }); 185 | }); 186 | describe('["bang"]', () => { 187 | it("works", () => { 188 | assert(types["bang"].tag === "I"); 189 | assert(types["bang"].size() === 0); 190 | assert(types["bang"].validate("bang") === true); 191 | assert(types["bang"].validate("ding") === false); 192 | assert(types["bang"].valueOf() === "bang"); 193 | 194 | types["bang"].write(writer, "bang"); 195 | assert(types["bang"].read(reader) === "bang"); 196 | 197 | assert(tags["I"] === types["bang"]); 198 | }); 199 | }); 200 | }); 201 | -------------------------------------------------------------------------------- /test/Writer.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const assert = require("power-assert"); 4 | const Writer = require("../src/Writer"); 5 | 6 | describe("Writer", () => { 7 | describe("constructor(buffer: Buffer|ArrayBuffer)", () => { 8 | it("works", () => { 9 | const buffer = new Uint8Array(8).buffer; 10 | const writer = new Writer(buffer); 11 | 12 | assert(writer instanceof Writer); 13 | }); 14 | }); 15 | describe("#writeUInt8(value: number): void", () => { 16 | it("works", () => { 17 | const buffer = new Uint8Array(8).buffer; 18 | const writer = new Writer(buffer); 19 | 20 | writer.writeUInt8(0x00); 21 | writer.writeUInt8(0x00); 22 | writer.writeUInt8(0x03); 23 | writer.writeUInt8(0xe8); 24 | writer.writeUInt8(0xff); 25 | writer.writeUInt8(0xff); 26 | writer.writeUInt8(0xff); 27 | writer.writeUInt8(0xff); // EOD 28 | assert(writer.hasError() === false); 29 | writer.writeUInt8(0x00); 30 | assert(writer.hasError() === true); 31 | 32 | assert(writer.view.getUint8(0) === 0x00); 33 | assert(writer.view.getUint8(1) === 0x00); 34 | assert(writer.view.getUint8(2) === 0x03); 35 | assert(writer.view.getUint8(3) === 0xe8); 36 | assert(writer.view.getUint8(4) === 0xff); 37 | assert(writer.view.getUint8(5) === 0xff); 38 | assert(writer.view.getUint8(6) === 0xff); 39 | assert(writer.view.getUint8(7) === 0xff); 40 | }); 41 | }); 42 | describe("#writeInt32(value: number): void", () => { 43 | it("works", () => { 44 | const buffer = new Uint8Array(8).buffer; 45 | const writer = new Writer(buffer); 46 | 47 | writer.writeInt32(1000); 48 | writer.writeInt32(-1); // EOD 49 | assert(writer.hasError() === false); 50 | writer.writeInt32(0); 51 | assert(writer.hasError() === true); 52 | 53 | assert(writer.view.getInt32(0) === 1000); 54 | assert(writer.view.getInt32(4) === -1); 55 | }); 56 | }); 57 | describe("#writeUInt32(value: number): void", () => { 58 | it("works", () => { 59 | const buffer = new Uint8Array(8).buffer; 60 | const writer = new Writer(buffer); 61 | 62 | writer.writeUInt32(1000); 63 | writer.writeUInt32(4294967295); // EOD 64 | assert(writer.hasError() === false); 65 | writer.writeUInt32(0); 66 | assert(writer.hasError() === true); 67 | 68 | assert(writer.view.getUint32(0) === 1000); 69 | assert(writer.view.getUint32(4) === 4294967295); 70 | }); 71 | }); 72 | describe("#writeFloat32(value: number): void", () => { 73 | it("works", () => { 74 | const buffer = new Uint8Array(8).buffer; 75 | const writer = new Writer(buffer); 76 | 77 | writer.writeFloat32(1.234); 78 | writer.writeFloat32(5.678); // EOD 79 | assert(writer.hasError() === false); 80 | writer.writeFloat32(0); 81 | assert(writer.hasError() === true); 82 | 83 | assert(writer.view.getFloat32(0) === Math.fround(1.234)); 84 | assert(writer.view.getFloat32(4) === Math.fround(5.678)); 85 | }); 86 | }); 87 | describe("#writeFloat64(value: number): void", () => { 88 | it("works", () => { 89 | const buffer = new Uint8Array(16).buffer; 90 | const writer = new Writer(buffer); 91 | 92 | writer.writeFloat64(1.2345678); 93 | writer.writeFloat64(9.0123456); // EOD 94 | assert(writer.hasError() === false); 95 | writer.writeFloat64(0); 96 | assert(writer.hasError() === true); 97 | 98 | assert(writer.view.getFloat64(0) === 1.2345678); 99 | assert(writer.view.getFloat64(8) === 9.0123456); 100 | }); 101 | }); 102 | describe("#writeString(value: string): void", () => { 103 | it("works", () => { 104 | const buffer = new Uint8Array(16).buffer; 105 | const writer = new Writer(buffer); 106 | 107 | writer.writeString("/foo"); 108 | writer.writeString(",iisff"); 109 | assert(writer.hasError() === false); 110 | writer.writeString("foobar"); 111 | assert(writer.hasError() === true); 112 | 113 | assert(writer.view.getUint8(0) === 0x2f); // "/foo" 114 | assert(writer.view.getUint8(1) === 0x66); 115 | assert(writer.view.getUint8(2) === 0x6f); 116 | assert(writer.view.getUint8(3) === 0x6f); 117 | assert(writer.view.getUint8(4) === 0x00); 118 | assert(writer.view.getUint8(5) === 0x00); 119 | assert(writer.view.getUint8(6) === 0x00); 120 | assert(writer.view.getUint8(7) === 0x00); 121 | 122 | assert(writer.view.getUint8(8) === 0x2c); // ",iisff" 123 | assert(writer.view.getUint8(9) === 0x69); 124 | assert(writer.view.getUint8(10) === 0x69); 125 | assert(writer.view.getUint8(11) === 0x73); 126 | assert(writer.view.getUint8(12) === 0x66); 127 | assert(writer.view.getUint8(13) === 0x66); 128 | assert(writer.view.getUint8(14) === 0x00); 129 | assert(writer.view.getUint8(15) === 0x00); 130 | }); 131 | }); 132 | describe("#writeBlob(value: Buffer|ArrayBuffer): void", () => { 133 | it("works", () => { 134 | const buffer = new Uint8Array(32).buffer; 135 | const writer = new Writer(buffer); 136 | 137 | writer.writeBlob(new Buffer([ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 ])); 138 | writer.writeBlob(new Buffer([ 0x07, 0x08, 0x09, 0x00 ])); // EOD 139 | assert(writer.hasError() === false); 140 | writer.writeBlob(new Buffer([ 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 ])); 141 | assert(writer.hasError() === true); 142 | 143 | assert(writer.view.getUint32(0) === 6); // size = 6 144 | assert(writer.view.getUint8(4) === 0x01); 145 | assert(writer.view.getUint8(5) === 0x02); 146 | assert(writer.view.getUint8(6) === 0x03); 147 | assert(writer.view.getUint8(7) === 0x04); 148 | assert(writer.view.getUint8(8) === 0x05); 149 | assert(writer.view.getUint8(9) === 0x06); 150 | assert(writer.view.getUint8(10) === 0x00); 151 | assert(writer.view.getUint8(11) === 0x00); 152 | 153 | assert(writer.view.getUint32(12) === 4); // size = 4 154 | assert(writer.view.getUint8(16) === 0x07); 155 | assert(writer.view.getUint8(17) === 0x08); 156 | assert(writer.view.getUint8(18) === 0x09); 157 | assert(writer.view.getUint8(29) === 0x00); 158 | }); 159 | }); 160 | describe("writeAddress(value: string): void", () => { 161 | it("works", () => { 162 | const buffer = new Uint8Array(8).buffer; 163 | const writer = new Writer(buffer); 164 | 165 | writer.writeString("/foo"); 166 | assert(writer.hasError() === false); 167 | writer.writeString("/bar"); 168 | assert(writer.hasError() === true); 169 | 170 | assert(writer.view.getUint8(0) === 0x2f); // "/foo" 171 | assert(writer.view.getUint8(1) === 0x66); 172 | assert(writer.view.getUint8(2) === 0x6f); 173 | assert(writer.view.getUint8(3) === 0x6f); 174 | assert(writer.view.getUint8(4) === 0x00); 175 | assert(writer.view.getUint8(5) === 0x00); 176 | assert(writer.view.getUint8(6) === 0x00); 177 | assert(writer.view.getUint8(7) === 0x00); 178 | }); 179 | }); 180 | describe("writeAddress(value: number): void", () => { 181 | // It isn't the OSC spec, but SuperCollider allows numeric address. 182 | it("works", () => { 183 | const buffer = new Uint8Array(4).buffer; 184 | const writer = new Writer(buffer); 185 | 186 | writer.writeAddress(1); 187 | assert(writer.hasError() === false); 188 | writer.writeAddress(2); 189 | assert(writer.hasError() === true); 190 | 191 | assert(writer.view.getUint32(0) === 1); 192 | }); 193 | }); 194 | describe("writeTimeTag(value: [ hi, lo ]): void", () => { 195 | it("works", () => { 196 | const buffer = new Uint8Array(8).buffer; 197 | const writer = new Writer(buffer); 198 | 199 | writer.writeTimeTag([ 2208988800, 2147483648 ]); 200 | assert(writer.hasError() === false); 201 | writer.writeTimeTag([ 2208988800, 0 ]); 202 | assert(writer.hasError() === true); 203 | 204 | assert(writer.view.getUint32(0) === 2208988800); 205 | assert(writer.view.getUint32(4) === 2147483648); 206 | }); 207 | }); 208 | describe("#hasNext(): boolean", () => { 209 | it("works", () => { 210 | const buffer = new Uint8Array(4).buffer; 211 | const writer = new Writer(buffer); 212 | 213 | assert(writer.hasError() === false); 214 | 215 | writer.writeInt32(0); 216 | assert(writer.hasError() === false); 217 | 218 | writer.writeInt32(0); 219 | assert(writer.hasError() === true); 220 | }); 221 | }); 222 | describe("#hasNext(): boolean", () => { 223 | it("works", () => { 224 | const buffer = new Uint8Array(8).buffer; 225 | const writer = new Writer(buffer); 226 | 227 | assert(writer.hasNext() === true); 228 | 229 | writer.writeInt32(0); 230 | assert(writer.hasNext() === true); 231 | 232 | writer.writeInt32(0); 233 | assert(writer.hasNext() === false); 234 | }); 235 | }); 236 | }); 237 | -------------------------------------------------------------------------------- /test/compatibility.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const assert = require("power-assert"); 4 | const oscmin = require("osc-min"); 5 | const flatten = require("lodash.flatten"); 6 | const oscmsg = require("../src"); 7 | 8 | function _i(value) { 9 | return Array.from(new Uint8Array(new Int32Array([ value ]).buffer)).reverse(); 10 | } 11 | 12 | function _f(value) { 13 | return Array.from(new Uint8Array(new Float32Array([ value ]).buffer)).reverse(); 14 | } 15 | 16 | function _t(value) { 17 | return Array.from(new Uint8Array(new Int32Array([ value, 0 ]).buffer)).reverse(); 18 | } 19 | 20 | function _s(value) { 21 | const length = Math.ceil((value.length + 1) / 4) * 4; 22 | const list = new Uint8Array(length); 23 | 24 | for (let i = 0; i < value.length; i++) { 25 | list[i] = value.charCodeAt(i); 26 | } 27 | 28 | return [].slice.call(list); 29 | } 30 | 31 | function $i(value) { 32 | return { type: "integer", value: value }; 33 | } 34 | 35 | function $f(value) { 36 | return { type: "float", value: value }; 37 | } 38 | 39 | function $a(value) { 40 | return { type: "array", value: value }; 41 | } 42 | 43 | describe("compatibility with osc-min", () => { 44 | it("#bundle{ /foo /bar }", () => { 45 | const object = { 46 | timetag: [ 0, 1 ], 47 | elements: [ 48 | { address: "/foo", args: [] }, 49 | { address: "/bar", args: [] } 50 | ] 51 | }; 52 | const buffer = new Buffer(flatten([ 53 | _s("#bundle"), _t(1), _i(12), _s("/foo"), _s(","), _i(12), _s("/bar"), _s(",") 54 | ])); 55 | 56 | assert.deepEqual(oscmsg.toBuffer(object), oscmin.toBuffer(object)); 57 | assert.deepEqual(oscmsg.fromBuffer(buffer), oscmin.fromBuffer(buffer)); 58 | }); 59 | it("#bundle{ /foo /bar #bundle{ /baz /qux } }", () => { 60 | const object = { 61 | timetag: [ 0, 1 ], 62 | elements: [ 63 | { address: "/foo", args: [] }, 64 | { address: "/bar", args: [] }, 65 | { 66 | timetag: [ 0, 2 ], 67 | elements: [ 68 | { address: "/baz", args: [] }, 69 | { address: "/qux", args: [] } 70 | ] 71 | } 72 | ] 73 | }; 74 | const buffer = new Buffer(flatten([ 75 | _s("#bundle"), _t(1), _i(12), _s("/foo"), _s(","), _i(12), _s("/bar"), _s(","), 76 | _i(48), _s("#bundle"), _t(2), _i(12), _s("/baz"), _s(","), _i(12), _s("/qux"), _s(",") 77 | ])); 78 | 79 | assert.deepEqual(oscmsg.toBuffer(object), oscmin.toBuffer(object)); 80 | assert.deepEqual(oscmsg.fromBuffer(buffer), oscmin.fromBuffer(buffer)); 81 | }); 82 | it("/address", () => { 83 | const object = { 84 | address: "/address" 85 | }; 86 | const buffer = new Buffer(flatten([ _s("/address"), _s(",") ])); 87 | 88 | assert.deepEqual(oscmsg.toBuffer(object), oscmin.toBuffer(object)); 89 | assert.deepEqual(oscmsg.fromBuffer(buffer), oscmin.fromBuffer(buffer)); 90 | }); 91 | it("/counter 0 1 2 3", () => { 92 | const object = { 93 | address: "/counter", 94 | args: [ $i(0), $i(1), $i(2), $i(3) ] 95 | }; 96 | const buffer = new Buffer(flatten([ 97 | _s("/counter"), _s(",iiii"), _i(0), _i(1), _i(2), _i(3) 98 | ])); 99 | 100 | assert.deepEqual(oscmsg.toBuffer(object), oscmin.toBuffer(object)); 101 | assert.deepEqual(oscmsg.fromBuffer(buffer), oscmin.fromBuffer(buffer)); 102 | }); 103 | it("/matrix [ [ 1. 0. 0. ] [ 0. 1. 0. ] [ 0. 0. 1. ] ]", () => { 104 | const object = { 105 | address: "/matrix", 106 | args: [ 107 | $a([ 108 | $a([ $f(1), $f(0), $f(0) ]), 109 | $a([ $f(0), $f(1), $f(0) ]), 110 | $a([ $f(0), $f(0), $f(1) ]) 111 | ]) 112 | ] 113 | }; 114 | const buffer = new Buffer(flatten([ 115 | _s("/matrix"), _s(",[[fff][fff][fff]]"), _f(1), _f(0), _f(0), _f(0), _f(1), _f(0), _f(0), _f(0), _f(1) 116 | ])); 117 | 118 | assert.deepEqual(oscmsg.toBuffer(object), oscmin.toBuffer(object)); 119 | assert.deepEqual(oscmsg.fromBuffer(buffer), oscmin.fromBuffer(buffer)); 120 | }); 121 | }); 122 | -------------------------------------------------------------------------------- /test/compile.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const assert = require("power-assert"); 4 | const compile = require("../src/compile"); 5 | 6 | function $i(value) { 7 | return { type: "integer", value: value }; 8 | } 9 | 10 | function $f(value) { 11 | return { type: "float", value: value }; 12 | } 13 | 14 | function $s(value) { 15 | return { type: "string", value: value }; 16 | } 17 | 18 | function $b(value) { 19 | return { type: "blob", value: value }; 20 | } 21 | 22 | function $T() { 23 | return { type: "true", value: true }; 24 | } 25 | 26 | function $F() { 27 | return { type: "false", value: false }; 28 | } 29 | 30 | function $N() { 31 | return { type: "null", value: null }; 32 | } 33 | 34 | describe("compile(object: object, opts: object): object", () => { 35 | it("0", () => { 36 | const data = 0; 37 | const result = compile(data, {}); 38 | 39 | assert.deepEqual(result, { 40 | address: "", 41 | types: "f", 42 | values: [ $f(0) ], 43 | bufferLength: 12, 44 | oscType: "message", 45 | error: null 46 | }); 47 | }); 48 | it("[ 1, 2, 3 ]", () => { 49 | const data = [ $i(1), $i(2), $i(3) ]; 50 | const result = compile(data, {}); 51 | 52 | assert.deepEqual(result, { 53 | address: "", 54 | types: "iii", 55 | values: [ $i(1), $i(2), $i(3) ], 56 | bufferLength: 24, 57 | oscType: "message", 58 | error: null 59 | }); 60 | }); 61 | it("blob", () => { 62 | const buf1 = Buffer.from([ 1, 2, 3, 4 ]); 63 | const buf2 = new Uint8Array([ 5, 6, 7, 8 ]).buffer; 64 | const data = [ buf1, buf2 ]; 65 | const result = compile(data, {}); 66 | 67 | assert.deepEqual(result, { 68 | address: "", 69 | types: "bb", 70 | values: [ $b(buf1), $b(buf2) ], 71 | bufferLength: 24, 72 | oscType: "message", 73 | error: null 74 | }); 75 | }); 76 | it("/matrix [ [ 1, 0, 0 ], [ 0, 1, 0 ], [ 0, 0, 1 ] ]", () => { 77 | const data = { 78 | address: "/matrix", 79 | args: [ [ [ 1, 0, 0 ], [ 0, 1, 0 ], [ 0, 0, 1 ] ] ] 80 | }; 81 | const result = compile(data, {}); 82 | 83 | assert.deepEqual(result, { 84 | address: "/matrix", 85 | types: "[[fff][fff][fff]]", 86 | values: [ $f(1), $f(0), $f(0), $f(0), $f(1), $f(0), $f(0), $f(0), $f(1) ], 87 | bufferLength: 64, 88 | oscType: "message", 89 | error: null 90 | }); 91 | }); 92 | it("/complex", () => { 93 | const data = { 94 | elements: [ 95 | { 96 | timetag: 9487534655377768000, 97 | elements: [ 98 | { 99 | address: "/complex", 100 | args: [ true, false, null, "xxx" ] 101 | } 102 | ] 103 | } 104 | ] 105 | }; 106 | const result = compile(data, {}); 107 | 108 | assert.deepEqual(result, { 109 | timetag: [ 0, 0 ], 110 | elements: [ 111 | { 112 | timetag: [ 2208988800, 2147483648 ], 113 | elements: [ 114 | { 115 | address: "/complex", 116 | types: "TFNs", 117 | values: [ $T(), $F(), $N(), $s("xxx") ], 118 | bufferLength: 24, 119 | oscType: "message", 120 | error: null 121 | } 122 | ], 123 | bufferLength: 44, 124 | oscType: "bundle", 125 | error: null 126 | } 127 | ], 128 | bufferLength: 64, 129 | oscType: "bundle", 130 | error: null 131 | }); 132 | }); 133 | it("empty bundle", () => { 134 | const data = { 135 | timetag: [ 2208988800, 2147483648 ] 136 | }; 137 | const result = compile(data, {}); 138 | 139 | assert.deepEqual(result, { 140 | timetag: [ 2208988800, 2147483648 ], 141 | elements: [], 142 | bufferLength: 16, 143 | oscType: "bundle", 144 | error: null 145 | }); 146 | }); 147 | it("with integer option", () => { 148 | const data = { 149 | address: "/matrix", 150 | args: [ [ [ 1, 0, 0 ], [ 0, 1, 0 ], [ 0, 0, 1 ] ] ] 151 | }; 152 | const result = compile(data, { integer: true }); 153 | 154 | assert.deepEqual(result, { 155 | address: "/matrix", 156 | types: "[[iii][iii][iii]]", 157 | values: [ $i(1), $i(0), $i(0), $i(0), $i(1), $i(0), $i(0), $i(0), $i(1) ], 158 | bufferLength: 64, 159 | oscType: "message", 160 | error: null 161 | }); 162 | }); 163 | it("with strict option", () => { 164 | const data = {}; 165 | const result = compile(data, { strict: true }); 166 | 167 | assert(result.error instanceof Error); 168 | }); 169 | it("with strict option", () => { 170 | const data = { 171 | address: "/strict", 172 | args: [ 173 | { type: "integer", value: NaN } 174 | ] 175 | }; 176 | const result = compile(data, { strict: true }); 177 | 178 | assert(result.error instanceof Error); 179 | }); 180 | it("error: Invalid data", () => { 181 | const data = { 182 | elements: [ 183 | { 184 | address: "/invalid-data", 185 | args: [ 186 | [ () => {} ] 187 | ] 188 | } 189 | ] 190 | }; 191 | const result = compile(data, {}); 192 | 193 | assert(result.error instanceof Error); 194 | }); 195 | }); 196 | -------------------------------------------------------------------------------- /test/decode.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const assert = require("power-assert"); 4 | const flatten = require("lodash.flatten"); 5 | const decode = require("../src/decode"); 6 | 7 | function _i(value) { 8 | return Array.from(new Uint8Array(new Int32Array([ value ]).buffer)).reverse(); 9 | } 10 | 11 | function _f(value) { 12 | return Array.from(new Uint8Array(new Float32Array([ value ]).buffer)).reverse(); 13 | } 14 | 15 | function _t(value) { 16 | return Array.from(new Uint8Array(new Int32Array([ value, 0 ]).buffer)).reverse(); 17 | } 18 | 19 | function _s(value) { 20 | const length = Math.ceil((value.length + 1) / 4) * 4; 21 | const list = new Uint8Array(length); 22 | 23 | for (let i = 0; i < value.length; i++) { 24 | list[i] = value.charCodeAt(i); 25 | } 26 | 27 | return [].slice.call(list); 28 | } 29 | 30 | function $i(value) { 31 | return { type: "integer", value: value }; 32 | } 33 | 34 | function $f(value) { 35 | return { type: "float", value: value }; 36 | } 37 | 38 | function $a(value) { 39 | return { type: "array", value: value }; 40 | } 41 | 42 | describe("decode(buffer: Buffer, opts = {}): object", () => { 43 | it("#bundle{}", () => { 44 | const buffer = new Buffer("#bundle"); 45 | const result = decode(buffer); 46 | const expected = { timetag: [ 0, 0 ], elements: [], oscType: "bundle" }; 47 | 48 | assert.deepEqual(result, expected); 49 | }); 50 | it("#bundle{ /foo /bar }", () => { 51 | const buffer = new Buffer(flatten([ 52 | _s("#bundle"), _t(1), _i(12), _s("/foo"), _s(","), _i(12), _s("/bar"), _s(",") 53 | ])); 54 | const result = decode(buffer); 55 | 56 | assert.deepEqual(result, { 57 | timetag: [ 0, 1 ], 58 | elements: [ 59 | { address: "/foo", args: [], oscType: "message" }, 60 | { address: "/bar", args: [], oscType: "message" } 61 | ], 62 | oscType: "bundle" 63 | }); 64 | }); 65 | it("#bundle{ /foo /bar #bundle{ /baz /qux } }", () => { 66 | const buffer = new Buffer(flatten([ 67 | _s("#bundle"), _t(1), _i(12), _s("/foo"), _s(","), _i(12), _s("/bar"), _s(","), 68 | _i(48), _s("#bundle"), _t(2), _i(12), _s("/baz"), _s(","), _i(12), _s("/qux"), _s(",") 69 | ])); 70 | const result = decode(buffer); 71 | 72 | assert.deepEqual(result, { 73 | timetag: [ 0, 1 ], 74 | elements: [ 75 | { address: "/foo", args: [], oscType: "message" }, 76 | { address: "/bar", args: [], oscType: "message" }, 77 | { 78 | timetag: [ 0, 2 ], 79 | elements: [ 80 | { address: "/baz", args: [], oscType: "message" }, 81 | { address: "/qux", args: [], oscType: "message" } 82 | ], 83 | oscType: "bundle" 84 | } 85 | ], 86 | oscType: "bundle" 87 | }); 88 | }); 89 | it("/address", () => { 90 | const buffer = new Buffer(flatten([ _s("/address"), _s(",") ])); 91 | const result = decode(buffer); 92 | const expected = { address: "/address", args: [], oscType: "message" }; 93 | 94 | assert.deepEqual(result, expected); 95 | }); 96 | it("/counter 0 1 2 3", () => { 97 | const buffer = new Buffer(flatten([ 98 | _s("/counter"), _s(",iiii"), _i(0), _i(1), _i(2), _i(3) 99 | ])); 100 | const result = decode(buffer); 101 | 102 | assert.deepEqual(result, { 103 | address: "/counter", 104 | args: [ $i(0), $i(1), $i(2), $i(3) ], 105 | oscType: "message" 106 | }); 107 | }); 108 | it("/matrix [ [ 1. 0. 0. ] [ 0. 1. 0. ] [ 0. 0. 1. ] ]", () => { 109 | const buffer = new Buffer(flatten([ 110 | _s("/matrix"), _s(",[[fff][fff][fff]]"), _f(1), _f(0), _f(0), _f(0), _f(1), _f(0), _f(0), _f(0), _f(1) 111 | ])); 112 | const result = decode(buffer); 113 | 114 | assert.deepEqual(result, { 115 | address: "/matrix", 116 | args: [ 117 | $a([ 118 | $a([ $f(1), $f(0), $f(0) ]), 119 | $a([ $f(0), $f(1), $f(0) ]), 120 | $a([ $f(0), $f(0), $f(1) ]) 121 | ]) 122 | ], 123 | oscType: "message" 124 | }); 125 | }); 126 | it("with bundle option", () => { 127 | const buffer = new Buffer(flatten([ 128 | _s("/counter"), _s(",iiii"), _i(0), _i(1), _i(2), _i(3) 129 | ])); 130 | const result = decode(buffer, { bundle: true }); 131 | 132 | assert.deepEqual(result, { 133 | timetag: [ 0, 0 ], 134 | elements: [ 135 | { 136 | address: "/counter", 137 | args: [ $i(0), $i(1), $i(2), $i(3) ], 138 | oscType: "message" 139 | } 140 | ], 141 | oscType: "bundle" 142 | }); 143 | }); 144 | it("with strip option", () => { 145 | const buffer = new Buffer(flatten([ 146 | _s("/matrix"), _s(",[[fff][fff][fff]]"), _f(1), _f(0), _f(0), _f(0), _f(1), _f(0), _f(0), _f(0), _f(1) 147 | ])); 148 | const result = decode(buffer, { strip: true }); 149 | 150 | assert.deepEqual(result, { 151 | address: "/matrix", 152 | args: [ 153 | [ 154 | [ 1, 0, 0 ], 155 | [ 0, 1, 0 ], 156 | [ 0, 0, 1 ] 157 | ] 158 | ], 159 | oscType: "message" 160 | }); 161 | }); 162 | it("with strict option", () => { 163 | const buffer = new Buffer(flatten([ 164 | _s("/not-exists-osc-tag-type-string") 165 | ])); 166 | const result = decode(buffer, { strict: true }); 167 | 168 | assert(!!result.error); 169 | }); 170 | it("error: not buffer", () => { 171 | const result = decode(); 172 | 173 | assert(!!result.error); 174 | }); 175 | it("error: not supported tag", () => { 176 | const buffer = new Buffer(flatten([ 177 | _s("/not-supported-tag"), _s(",invalid tags"), _i(0) 178 | ])); 179 | const result = decode(buffer); 180 | 181 | assert(!!result.error); 182 | }); 183 | it("error: unexpected ]", () => { 184 | const buffer = new Buffer(flatten([ 185 | _s("/invalid-data-type"), _s(",]") 186 | ])); 187 | const result = decode(buffer); 188 | 189 | assert(result.error instanceof Error); 190 | }); 191 | it("error: unexpected [", () => { 192 | const buffer = new Buffer(flatten([ 193 | _s("/invalid-data-type"), _s(",[") 194 | ])); 195 | const result = decode(buffer); 196 | 197 | assert(result.error instanceof Error); 198 | }); 199 | it("error: offset outside", () => { 200 | const buffer = new Buffer(flatten([ 201 | _s("/offset-outside"), _s(",iiii"), _i(0), _i(1), _i(2) 202 | ])); 203 | const result = decode(buffer); 204 | 205 | assert(result.error instanceof Error); 206 | }); 207 | it("error: #bundle contains error", () => { 208 | const buffer = new Buffer(flatten([ 209 | _s("#bundle"), _t(1), _i(12), _s("/foo"), _s(",i") 210 | ])); 211 | const result = decode(buffer); 212 | 213 | assert(result.error instanceof Error); 214 | }); 215 | it("error: #bundle offset outside", () => { 216 | const buffer = new Buffer(flatten([ 217 | _s("#bundle"), _t(1), _i(16), _s("/foo"), _s(",") 218 | ])); 219 | const result = decode(buffer); 220 | 221 | assert(result.error instanceof Error); 222 | }); 223 | }); 224 | -------------------------------------------------------------------------------- /test/encode.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const assert = require("power-assert"); 4 | const flatten = require("lodash.flatten"); 5 | const encode = require("../src/encode"); 6 | const decode = require("../src/decode"); 7 | 8 | function _i(value) { 9 | return Array.from(new Uint8Array(new Int32Array([ value ]).buffer)).reverse(); 10 | } 11 | 12 | function _f(value) { 13 | return Array.from(new Uint8Array(new Float32Array([ value ]).buffer)).reverse(); 14 | } 15 | 16 | function _t(value) { 17 | return Array.from(new Uint8Array(new Int32Array([ value, 0 ]).buffer)).reverse(); 18 | } 19 | 20 | function _s(value) { 21 | const length = Math.ceil((value.length + 1) / 4) * 4; 22 | const list = new Uint8Array(length); 23 | 24 | for (let i = 0; i < value.length; i++) { 25 | list[i] = value.charCodeAt(i); 26 | } 27 | 28 | return [].slice.call(list); 29 | } 30 | 31 | function $i(value) { 32 | return { type: "integer", value: value }; 33 | } 34 | 35 | function $f(value) { 36 | return { type: "float", value: value }; 37 | } 38 | 39 | function $a(value) { 40 | return { type: "array", value: value }; 41 | } 42 | 43 | describe("encode(object: object, opts = {}): Buffer", () => { 44 | it("#bundle{}", () => { 45 | const object = { 46 | elements: [] 47 | }; 48 | const result = encode(object); 49 | const expected = new Buffer(flatten([ 50 | _s("#bundle"), _t(0) 51 | ])); 52 | 53 | assert.deepEqual(result, expected); 54 | }); 55 | it("#bundle{ /foo, /bar }", () => { 56 | const object = { 57 | timetag: 1, 58 | elements: [ 59 | { address: "/foo", args: [] }, 60 | { address: "/bar", args: [] } 61 | ] 62 | }; 63 | const result = encode(object); 64 | const expected = new Buffer(flatten([ 65 | _s("#bundle"), _t(1), _i(12), _s("/foo"), _s(","), _i(12), _s("/bar"), _s(",") 66 | ])); 67 | 68 | assert.deepEqual(result, expected); 69 | }); 70 | it("#bundle{ /foo /bar #bundle{ /baz /qux } }", () => { 71 | const object = { 72 | timetag: 1, 73 | elements: [ 74 | { address: "/foo", args: [] }, 75 | { address: "/bar", args: [] }, 76 | { 77 | timetag: 2, 78 | elements: [ 79 | { address: "/baz", args: [] }, 80 | { address: "/qux", args: [] } 81 | ] 82 | } 83 | ] 84 | }; 85 | const result = encode(object); 86 | const expected = new Buffer(flatten([ 87 | _s("#bundle"), _t(1), _i(12), _s("/foo"), _s(","), _i(12), _s("/bar"), _s(","), 88 | _i(48), _s("#bundle"), _t(2), _i(12), _s("/baz"), _s(","), _i(12), _s("/qux"), _s(",") 89 | ])); 90 | 91 | assert.deepEqual(result, expected); 92 | }); 93 | it("#bundle { bytes }", () => { 94 | const msg1 = { 95 | address: "/msg1", 96 | args: [ 97 | { type: "integer", value: 1 }, 98 | { type: "integer", value: 2 }, 99 | ], 100 | oscType: "message" 101 | }; 102 | const msg2 = { 103 | address: "/msg2", 104 | args: [ 105 | { type: "integer", value: 3 }, 106 | { type: "integer", value: 4 }, 107 | ], 108 | oscType: "message" 109 | }; 110 | const object = { 111 | timetag: [ 0, 0 ], 112 | elements: [ encode(msg1), encode(msg2) ] 113 | }; 114 | 115 | const result = encode(object); 116 | const revert = decode(result); 117 | 118 | assert.deepEqual(revert, { 119 | timetag: [ 0, 0 ], 120 | elements: [ msg1, msg2 ], 121 | oscType: "bundle" 122 | }); 123 | }); 124 | it("/address", () => { 125 | const object = { 126 | address: "/address" 127 | }; 128 | const result = encode(object); 129 | const expected = new Buffer(flatten([ 130 | _s("/address"), _s(",") 131 | ])); 132 | 133 | assert.deepEqual(result, expected); 134 | }); 135 | it("/counter 0 1 2 3", () => { 136 | const object = { 137 | address: "/counter", 138 | args: [ $i(0), $i(1), $i(2), $i(3) ] 139 | }; 140 | const result = encode(object); 141 | const expected = new Buffer(flatten([ 142 | _s("/counter"), _s(",iiii"), _i(0), _i(1), _i(2), _i(3) 143 | ])); 144 | 145 | assert.deepEqual(result, expected); 146 | }); 147 | it("/blob buf1 buf2", () => { 148 | const buf1 = Buffer.from([ 1, 2, 3, 4 ]); 149 | const buf2 = new Uint8Array([ 5, 6, 7, 8 ]).buffer; 150 | const object = { 151 | address: "/blob", 152 | args: [ buf1, buf2 ], 153 | }; 154 | const result = encode(object); 155 | const expected = new Buffer(flatten([ 156 | _s("/blob"), _s(",bb"), _i(4), [ 1, 2, 3, 4 ], _i(4), [ 5, 6, 7, 8 ] 157 | ])); 158 | 159 | assert.deepEqual(result, expected); 160 | }); 161 | it("/matrix [ [ 1. 0. 0. ] [ 0. 1. 0. ] [ 0. 0. 1. ] ]", () => { 162 | const object = { 163 | address: "/matrix", 164 | args: [ 165 | $a([ 166 | $a([ $f(1), $f(0), $f(0) ]), 167 | $a([ $f(0), $f(1), $f(0) ]), 168 | $a([ $f(0), $f(0), $f(1) ]) 169 | ]) 170 | ] 171 | }; 172 | const result = encode(object); 173 | const expected = new Buffer(flatten([ 174 | _s("/matrix"), _s(",[[fff][fff][fff]]"), _f(1), _f(0), _f(0), _f(0), _f(1), _f(0), _f(0), _f(0), _f(1) 175 | ])); 176 | 177 | assert.deepEqual(result, expected); 178 | }); 179 | it("with integer option", () => { 180 | const object = { 181 | address: "/matrix", 182 | args: [ 183 | [ 184 | [ 1, 0, 0 ], 185 | [ 0, 1, 0 ], 186 | [ 0, 0, 1 ] 187 | ] 188 | ] 189 | }; 190 | const result = encode(object, { integer: true }); 191 | const expected = new Buffer(flatten([ 192 | _s("/matrix"), _s(",[[iii][iii][iii]]"), _i(1), _i(0), _i(0), _i(0), _i(1), _i(0), _i(0), _i(0), _i(1) 193 | ])); 194 | 195 | assert.deepEqual(result, expected); 196 | }); 197 | it("error: invalid data type", () => { 198 | const object = { 199 | address: "/invalid-data-type", 200 | args: [ 201 | { type: "function", value: () => {} } 202 | ] 203 | }; 204 | const result = encode(object); 205 | 206 | assert(result.error instanceof Error); 207 | }); 208 | }); 209 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const assert = require("power-assert"); 4 | const index = require("../src"); 5 | const decode = require("../src/decode"); 6 | const encode = require("../src/encode"); 7 | 8 | describe("index", () => { 9 | it("exports", () => { 10 | assert(typeof index.decode === "function"); 11 | assert(index.decode === decode); 12 | assert(typeof index.encode === "function"); 13 | assert(index.encode === encode); 14 | assert(typeof index.fromBuffer === "function"); 15 | assert(index.fromBuffer === decode); 16 | assert(typeof index.fromObject === "function"); 17 | assert(index.fromObject === encode); 18 | assert(typeof index.toBuffer === "function"); 19 | assert(index.toBuffer === encode); 20 | assert(typeof index.toObject === "function"); 21 | assert(index.toObject === decode); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /test/mocha.opts: -------------------------------------------------------------------------------- 1 | --reporter spec 2 | --recursive 3 | -------------------------------------------------------------------------------- /test/utils.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const assert = require("power-assert"); 4 | const utils = require("../src/utils"); 5 | 6 | describe("utils", () => { 7 | describe(".size4(num: number): number", () => { 8 | it("works", () => { 9 | assert(utils.size4(0) === 0); 10 | assert(utils.size4(1) === 4); 11 | assert(utils.size4(2) === 4); 12 | assert(utils.size4(3) === 4); 13 | assert(utils.size4(4) === 4); 14 | assert(utils.size4(5) === 8); 15 | assert(utils.size4(6) === 8); 16 | assert(utils.size4(7) === 8); 17 | assert(utils.size4(8) === 8); 18 | }); 19 | }); 20 | describe(".isNone(value: any): boolean", () => { 21 | it("works", () => { 22 | assert(utils.isNone(-1) === false); 23 | assert(utils.isNone(10) === false); 24 | assert(utils.isNone(4294967295) === false); 25 | assert(utils.isNone(1.5) === false); 26 | assert(utils.isNone(Infinity) === false); 27 | assert(utils.isNone(NaN) === false); 28 | assert(utils.isNone(true) === false); 29 | assert(utils.isNone(false) === false); 30 | assert(utils.isNone(null) === true); 31 | assert(utils.isNone(undefined) === true); 32 | assert(utils.isNone("0") === false); 33 | assert(utils.isNone(new Buffer(0)) === false); 34 | assert(utils.isNone(new Uint8Array(0).buffer) === false); 35 | }); 36 | }); 37 | describe(".isInteger", () => { 38 | it("(value: any): boolean", () => { 39 | assert(utils.isInteger(-1) === true); 40 | assert(utils.isInteger(10) === true); 41 | assert(utils.isInteger(4294967295) === true); 42 | assert(utils.isInteger(1.5) === false); 43 | assert(utils.isInteger(Infinity) === false); 44 | assert(utils.isInteger(NaN) === false); 45 | assert(utils.isInteger(true) === false); 46 | assert(utils.isInteger(false) === false); 47 | assert(utils.isInteger(null) === false); 48 | assert(utils.isInteger(undefined) === false); 49 | assert(utils.isInteger("0") === false); 50 | assert(utils.isInteger(new Buffer(0)) === false); 51 | assert(utils.isInteger(new Uint8Array(0).buffer) === false); 52 | }); 53 | }); 54 | describe(".isFloat", () => { 55 | it("(value: any): boolean", () => { 56 | assert(utils.isFloat(-1) === true); 57 | assert(utils.isFloat(10) === true); 58 | assert(utils.isFloat(4294967295) === true); 59 | assert(utils.isFloat(1.5) === true); 60 | assert(utils.isFloat(Infinity) === true); 61 | assert(utils.isFloat(NaN) === false); 62 | assert(utils.isFloat(true) === false); 63 | assert(utils.isFloat(false) === false); 64 | assert(utils.isFloat(null) === false); 65 | assert(utils.isFloat("0") === false); 66 | assert(utils.isFloat(undefined) === false); 67 | assert(utils.isFloat(new Buffer(0)) === false); 68 | assert(utils.isFloat(new Uint8Array(0).buffer) === false); 69 | }); 70 | }); 71 | describe(".isDouble", () => { 72 | it("(value: any): boolean", () => { 73 | assert(utils.isDouble(-1) === true); 74 | assert(utils.isDouble(10) === true); 75 | assert(utils.isDouble(4294967295) === true); 76 | assert(utils.isDouble(1.5) === true); 77 | assert(utils.isDouble(Infinity) === true); 78 | assert(utils.isDouble(NaN) === false); 79 | assert(utils.isDouble(true) === false); 80 | assert(utils.isDouble(false) === false); 81 | assert(utils.isDouble(null) === false); 82 | assert(utils.isDouble("0") === false); 83 | assert(utils.isDouble(undefined) === false); 84 | assert(utils.isDouble(new Buffer(0)) === false); 85 | assert(utils.isDouble(new Uint8Array(0).buffer) === false); 86 | }); 87 | }); 88 | describe(".isTimeTag", () => { 89 | it("(value: any): boolean", () => { 90 | assert(utils.isTimeTag([ 0, 1 ]) === true); 91 | assert(utils.isTimeTag([ 0 ]) === false); 92 | assert(utils.isTimeTag(-1) === false); 93 | assert(utils.isTimeTag(10) === true); 94 | assert(utils.isTimeTag(4294967295) === true); 95 | assert(utils.isTimeTag(1.5) === false); 96 | assert(utils.isTimeTag(Infinity) === false); 97 | assert(utils.isTimeTag(NaN) === false); 98 | assert(utils.isTimeTag(true) === false); 99 | assert(utils.isTimeTag(false) === false); 100 | assert(utils.isTimeTag(null) === false); 101 | assert(utils.isTimeTag("0") === false); 102 | assert(utils.isTimeTag(undefined) === false); 103 | assert(utils.isTimeTag(new Buffer(0)) === false); 104 | assert(utils.isTimeTag(new Uint8Array(0).buffer) === false); 105 | assert(utils.isTimeTag(new Date()) === true); 106 | }); 107 | }); 108 | describe(".isString", () => { 109 | it("(value: any): boolean", () => { 110 | assert(utils.isString(-1) === false); 111 | assert(utils.isString(10) === false); 112 | assert(utils.isString(4294967295) === false); 113 | assert(utils.isString(1.5) === false); 114 | assert(utils.isString(Infinity) === false); 115 | assert(utils.isString(NaN) === false); 116 | assert(utils.isString(true) === false); 117 | assert(utils.isString(false) === false); 118 | assert(utils.isString(null) === false); 119 | assert(utils.isString("0") === true); 120 | assert(utils.isString(undefined) === false); 121 | assert(utils.isString(new Buffer(0)) === false); 122 | assert(utils.isString(new Uint8Array(0).buffer) === false); 123 | }); 124 | }); 125 | describe(".isBlob", () => { 126 | it("(value: any): boolean", () => { 127 | assert(utils.isBlob(-1) === false); 128 | assert(utils.isBlob(10) === false); 129 | assert(utils.isBlob(4294967295) === false); 130 | assert(utils.isBlob(1.5) === false); 131 | assert(utils.isBlob(Infinity) === false); 132 | assert(utils.isBlob(NaN) === false); 133 | assert(utils.isBlob(true) === false); 134 | assert(utils.isBlob(false) === false); 135 | assert(utils.isBlob(null) === false); 136 | assert(utils.isBlob(undefined) === false); 137 | assert(utils.isBlob("0") === false); 138 | assert(utils.isBlob(new Buffer(0)) === true); 139 | assert(utils.isBlob(new Uint8Array(0).buffer) === true); 140 | }); 141 | }); 142 | describe(".toString(value: any): string", () => { 143 | it("works", () => { 144 | assert(utils.toString(null) === ""); 145 | assert(utils.toString(undefined) === ""); 146 | assert(utils.toString(100) === "100"); 147 | assert(utils.toString("0") === "0"); 148 | }); 149 | }); 150 | describe(".toArray(value: any): any[]", () => { 151 | it("works", () => { 152 | assert.deepEqual(utils.toArray(null), []); 153 | assert.deepEqual(utils.toArray(undefined), []); 154 | assert.deepEqual(utils.toArray(100), [ 100 ]); 155 | assert.deepEqual(utils.toArray([ 100, 200, 300 ]), [ 100, 200, 300 ]); 156 | }); 157 | }); 158 | describe(".toBlob(value: any): Buffer", () => { 159 | it("works", () => { 160 | const blob = new Buffer([ 0x62, 0x6c, 0x6f, 0x62 ]); 161 | 162 | assert(utils.toBlob(blob) === blob); 163 | assert(utils.toBlob([ 0x62, 0x6c, 0x6f, 0x62 ]) instanceof Buffer); 164 | assert.deepEqual(utils.toBlob([ 0x62, 0x6c, 0x6f, 0x62 ]), blob); 165 | assert(utils.toBlob("blob") instanceof Buffer); 166 | assert.deepEqual(utils.toBlob("blob"), blob); 167 | assert(utils.toBlob(4) instanceof Buffer); 168 | assert(utils.toBlob(4).length === 4); 169 | assert(utils.toBlob(null) instanceof Buffer); 170 | }); 171 | }); 172 | describe(".toTimeTag(value: number[]): number[]", () => { 173 | it("works", () => { 174 | assert.deepEqual(utils.toTimeTag([ 2208988800, 0 ]), [ 2208988800, 0 ]); 175 | assert.deepEqual(utils.toTimeTag([ 2208988800, 2147483648 ]), [ 2208988800, 2147483648 ]); 176 | }); 177 | }); 178 | describe(".toTimeTag(value: number): number[]", () => { 179 | it("works", () => { 180 | assert.deepEqual(utils.toTimeTag(9487534653230285000), [ 2208988800, 0 ]); 181 | assert.deepEqual(utils.toTimeTag(9487534655377768000), [ 2208988800, 2147483648 ]); 182 | }); 183 | }); 184 | describe(".toTimeTag(value: Date): number[]", () => { 185 | assert.deepEqual(utils.toTimeTag(new Date(0)), [ 2208988800, 0 ]); 186 | }); 187 | describe(".toAddress(value: number|string): number|string", () => { 188 | it("works", () => { 189 | assert(utils.toAddress("/foo") === "/foo"); 190 | assert(utils.toAddress(100) === 100); 191 | }); 192 | }); 193 | }); 194 | --------------------------------------------------------------------------------