71 Commits

Author SHA1 Message Date
jwetzell cc14c4c886 bump @jwetzell/osc to v1.6.0 2024-10-25 12:15:25 -05:00
jwetzell a4b29de9c9 Merge pull request #27 from jwetzell/dependabot/npm_and_yarn/types/node-22.7.7
Bump @types/node from 22.7.5 to 22.7.7
2024-10-25 12:13:41 -05:00
jwetzell 86f062a2ce fix import in decode test 2024-10-25 09:25:07 -05:00
jwetzell b7d129556a Merge pull request #29 from jwetzell/esm-and-cjs
add support for bundling both esm and cjs versions
2024-10-25 09:15:48 -05:00
jwetzell 2ee515ae5b npm run format:write 2024-10-25 09:10:52 -05:00
jwetzell 2089893a25 fix import in tests 2024-10-25 09:05:33 -05:00
jwetzell 2377599f12 add support for bundling both esm and cjs versions 2024-10-24 16:59:17 -05:00
jwetzell f18327d7b1 Create LICENSE 2024-10-22 08:35:24 -05:00
dependabot[bot] ad4efcf43d Bump @types/node from 22.7.5 to 22.7.7
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 22.7.5 to 22.7.7.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-21 17:47:28 +00:00
jwetzell 1b10930a8e bump @jwetzell/osc to v1.5.0 2024-10-21 12:46:15 -05:00
jwetzell 0c9fafc129 Merge pull request #28 from jwetzell/generic-decode
add function to guess type and decode
2024-10-21 12:45:52 -05:00
jwetzell aa1cae428b add function to guess type and decode 2024-10-21 12:44:43 -05:00
jwetzell 5449c8ec5b bump @jwetzell/osc to v1.4.0 2024-10-15 21:57:18 -05:00
jwetzell bf355706bb more bundle tests 2024-10-15 21:55:51 -05:00
jwetzell 31e0cb7d23 Merge pull request #26 from jwetzell/bundle-min-size
fix bundle minimum size check
2024-10-15 21:48:08 -05:00
jwetzell af6dd46303 fix bundle minimum size check 2024-10-15 21:47:33 -05:00
jwetzell 2ea1b928ba more tests 2024-10-15 21:41:47 -05:00
jwetzell 7bffb53691 split up passing and failing tests 2024-10-15 21:38:53 -05:00
jwetzell abc9445b2c npm run format:write 2024-10-15 21:36:53 -05:00
jwetzell 69a3eeec9f Merge pull request #25 from jwetzell/osc-address-only
allow no type string
2024-10-15 21:36:16 -05:00
jwetzell 99ae9d8a3b allow no type string 2024-10-15 21:35:53 -05:00
jwetzell b3dc125df3 npm run format:write 2024-10-14 21:35:16 -05:00
jwetzell 713fdc8959 Merge pull request #24 from jwetzell/message-error-handling
rework message testing for easier addition of failing tests
2024-10-14 21:34:39 -05:00
jwetzell 1a9decfa30 rework tests to have easily add tests that should fail 2024-10-14 21:33:35 -05:00
jwetzell 374bf0d39f messageToBuffer should error on invalid address 2024-10-14 21:15:29 -05:00
jwetzell 1f214396ae bump @jwetzell/osc to v1.3.0 2024-10-14 19:48:24 -05:00
jwetzell 925ff2a409 Merge pull request #23 from jwetzell/osc-array-type
add support for osc array types
2024-10-14 19:47:54 -05:00
jwetzell 5a5a6ab0a5 add support for osc array types 2024-10-14 19:46:49 -05:00
jwetzell 512b2d63c0 Merge pull request #19 from jwetzell/dependabot/npm_and_yarn/typescript-5.6.3
Bump typescript from 5.6.2 to 5.6.3
2024-10-14 07:17:08 -05:00
jwetzell e592229815 Merge pull request #20 from jwetzell/dependabot/npm_and_yarn/types/node-22.7.5
Bump @types/node from 22.7.4 to 22.7.5
2024-10-14 07:17:01 -05:00
jwetzell 768d6d5a4b Merge pull request #21 from jwetzell/dependabot/npm_and_yarn/commander-12.1.0
Bump commander from 11.1.0 to 12.1.0
2024-10-14 07:16:42 -05:00
dependabot[bot] 119574743d Bump commander from 11.1.0 to 12.1.0
Bumps [commander](https://github.com/tj/commander.js) from 11.1.0 to 12.1.0.
- [Release notes](https://github.com/tj/commander.js/releases)
- [Changelog](https://github.com/tj/commander.js/blob/master/CHANGELOG.md)
- [Commits](https://github.com/tj/commander.js/compare/v11.1.0...v12.1.0)

---
updated-dependencies:
- dependency-name: commander
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-14 05:14:18 +00:00
dependabot[bot] 709312585f Bump @types/node from 22.7.4 to 22.7.5
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 22.7.4 to 22.7.5.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-14 05:14:13 +00:00
dependabot[bot] fdaa8b8ed1 Bump typescript from 5.6.2 to 5.6.3
Bumps [typescript](https://github.com/microsoft/TypeScript) from 5.6.2 to 5.6.3.
- [Release notes](https://github.com/microsoft/TypeScript/releases)
- [Changelog](https://github.com/microsoft/TypeScript/blob/main/azure-pipelines.release.yml)
- [Commits](https://github.com/microsoft/TypeScript/compare/v5.6.2...v5.6.3)

---
updated-dependencies:
- dependency-name: typescript
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-14 05:14:07 +00:00
jwetzell 91fdf60737 Merge pull request #18 from jwetzell/osc-d-type
add support for osc d type
2024-10-13 21:45:29 -05:00
jwetzell e5d559eb1a add support for osc d type 2024-10-13 21:45:03 -05:00
jwetzell 1c68227aac npm run format:write 2024-10-13 21:36:19 -05:00
jwetzell f32b8ce679 Merge pull request #17 from jwetzell/osc-h-type
add support for osc h type
2024-10-13 21:35:32 -05:00
jwetzell 0df2dec5e9 add support for osc h type 2024-10-13 21:34:44 -05:00
jwetzell d587b0cf19 fix error message on time tag fromBuffer 2024-10-13 21:18:41 -05:00
jwetzell eca0639b38 Merge pull request #16 from jwetzell/osc-I-type
add support for osc infinitum type
2024-10-13 21:18:15 -05:00
jwetzell 7601cfb096 add support for osc infinitum type 2024-10-13 21:16:22 -05:00
jwetzell f9f763d6c4 bump @jwetzell/osc to v1.2.0 2024-10-13 17:18:01 -05:00
jwetzell 786e71fd82 Merge pull request #15 from jwetzell/osc-N-type
add support for osc nil type
2024-10-13 17:17:19 -05:00
jwetzell a2adc3d2f4 fix bundle test description 2024-10-13 17:15:19 -05:00
jwetzell 4ec6bae5fe add support for osc nil type 2024-10-13 17:15:02 -05:00
jwetzell f75b663812 bump @jwetzell/osc to v1.1.0 2024-10-13 16:45:46 -05:00
jwetzell 7a72853e41 disable npm version git tagging 2024-10-13 16:44:54 -05:00
jwetzell 81e0a542c5 npm run format:write 2024-10-13 16:43:32 -05:00
jwetzell 278d09bb6f Merge pull request #14 from jwetzell/bundle-multiple-content-chunks
fix osc bundles not parsing multiple content chunks correctly
2024-10-13 16:43:08 -05:00
jwetzell 4b56f09a64 add test osc bundle from resolume 2024-10-13 16:42:11 -05:00
jwetzell b44e5d306a fix osc bundles not parsing multiple content chunks correctly 2024-10-13 16:41:49 -05:00
jwetzell 7a3ad57482 Merge pull request #13 from jwetzell/osc-r-type
add support for osc color type
2024-10-13 16:40:08 -05:00
jwetzell 13a8334504 add test for color type 2024-10-13 16:39:31 -05:00
jwetzell e0bae3b386 remove app build workflows 2024-10-13 16:38:52 -05:00
jwetzell 2716ccc349 add support for osc color type 2024-10-13 16:38:14 -05:00
jwetzell acc93189a9 update readosc to v0.2.0 2024-10-13 15:40:46 -05:00
jwetzell 180c9ff76a Merge pull request #12 from jwetzell/readosc-update
allow readosc to make use of remainingBytes in messageFromBuffer
2024-10-13 15:40:21 -05:00
jwetzell 25fe42415e have readosc make use of remaining bytes 2024-10-13 15:39:13 -05:00
jwetzell fd6949c897 update @jwetzell/osc in readosc to v1.0.0 2024-10-13 15:38:41 -05:00
jwetzell 109a690796 bump @jwetzell/osc version to 1.0.0 2024-10-13 15:31:39 -05:00
jwetzell 548ce61312 fix tests 2024-10-13 15:30:04 -05:00
jwetzell ff57c2c5ee Merge pull request #11 from jwetzell/from-buffer-extra-bytes
fromBuffer methods should return any unprocessed results
2024-10-13 15:22:47 -05:00
jwetzell f47aae936f fromBuffer methods should return any unprocessed results 2024-10-13 15:22:28 -05:00
jwetzell 27585b8f55 Merge pull request #10 from jwetzell/remove-node-buffer
Remove Node Buffer
2024-10-13 15:15:46 -05:00
jwetzell 304158a97e npm run format:write 2024-10-10 17:10:38 -05:00
jwetzell e831c937c8 remove Buffer from bundle tests 2024-10-10 17:10:30 -05:00
jwetzell f1b5227b50 cleanup imports 2024-10-10 17:04:37 -05:00
jwetzell 5875b211f6 remove Buffer from message encode tests 2024-10-10 17:03:49 -05:00
jwetzell 287685a095 remove Buffer from message decode tests 2024-10-10 16:59:44 -05:00
jwetzell 7a07f5d815 remove node Buffer usage from src 2024-10-10 16:45:25 -05:00
25 changed files with 951 additions and 329 deletions
-25
View File
@@ -1,25 +0,0 @@
name: Check makeosc builds
on:
pull_request:
branches:
- main
paths:
- 'apps/makeosc/**'
jobs:
build-webui:
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: Set up Node.js
uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3
with:
node-version-file: '.nvmrc'
cache: 'npm'
cache-dependency-path: 'package-lock.json'
- name: Install Node.js dependencies
run: npm ci --workspace makeosc
- run: npm run build --workspace makeosc
-25
View File
@@ -1,25 +0,0 @@
name: Check readosc builds
on:
pull_request:
branches:
- main
paths:
- 'apps/readosc/**'
jobs:
build-webui:
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: Set up Node.js
uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3
with:
node-version-file: '.nvmrc'
cache: 'npm'
cache-dependency-path: 'package-lock.json'
- name: Install Node.js dependencies
run: npm ci --workspace readosc
- run: npm run build --workspace readosc
-25
View File
@@ -1,25 +0,0 @@
name: Check sendosc builds
on:
pull_request:
branches:
- main
paths:
- 'apps/sendosc/**'
jobs:
build-webui:
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: Set up Node.js
uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3
with:
node-version-file: '.nvmrc'
cache: 'npm'
cache-dependency-path: 'package-lock.json'
- name: Install Node.js dependencies
run: npm ci --workspace sendosc
- run: npm run build --workspace sendosc
+1
View File
@@ -0,0 +1 @@
git-tag-version=false
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2024 Joel Wetzell
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+1 -1
View File
@@ -21,7 +21,7 @@
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@jwetzell/osc": "0.2.2", "@jwetzell/osc": "0.2.2",
"commander": "^11.0.0", "commander": "^12.1.0",
"slip": "^1.0.2" "slip": "^1.0.2"
} }
} }
+3 -3
View File
@@ -1,6 +1,6 @@
{ {
"name": "readosc", "name": "readosc",
"version": "0.1.0", "version": "0.2.0",
"description": "Simple NodeJS utility for parsing osc buffers from stdin", "description": "Simple NodeJS utility for parsing osc buffers from stdin",
"main": "src/main.js", "main": "src/main.js",
"bin": { "bin": {
@@ -20,7 +20,7 @@
}, },
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@jwetzell/osc": "0.2.2", "@jwetzell/osc": "1.0.0",
"commander": "^11.0.0" "commander": "^12.1.0"
} }
} }
+9 -1
View File
@@ -10,11 +10,19 @@ program.description('simple util to read osc packets from stdin');
program.action(() => { program.action(() => {
process.stdin.on('data', (data) => { process.stdin.on('data', (data) => {
try { try {
const message = osc.messageFromBuffer(data); let remainingBytes = data;
while (remainingBytes.length > 0) {
try {
const [message, bytesAfterMessage] = osc.messageFromBuffer(remainingBytes);
remainingBytes = bytesAfterMessage;
console.log(JSON.stringify(message)); console.log(JSON.stringify(message));
} catch (error) { } catch (error) {
console.error({ error: error.toString() }); console.error({ error: error.toString() });
} }
}
} catch (error) {
console.error({ error: error.toString() });
}
}); });
}); });
program.parse(); program.parse();
+1 -1
View File
@@ -21,7 +21,7 @@
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@jwetzell/osc": "0.2.2", "@jwetzell/osc": "0.2.2",
"commander": "^11.0.0", "commander": "^12.1.0",
"slip": "^1.0.2" "slip": "^1.0.2"
} }
} }
+33 -15
View File
@@ -23,36 +23,51 @@
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@jwetzell/osc": "0.2.2", "@jwetzell/osc": "0.2.2",
"commander": "^11.0.0", "commander": "^12.1.0",
"slip": "^1.0.2" "slip": "^1.0.2"
}, },
"bin": { "bin": {
"makeosc": "src/main.js" "makeosc": "src/main.js"
} }
}, },
"apps/makeosc/node_modules/@jwetzell/osc": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/@jwetzell/osc/-/osc-0.2.2.tgz",
"integrity": "sha512-HobCAMnDDSrKfrMBrF5ySA8fKO8Qjkn2HDKysuV51sHrpXABKJFQt5OemIQgbnSMNWU0kZ0+FF9xRrivHHaDTg=="
},
"apps/readosc": { "apps/readosc": {
"version": "0.1.0", "version": "0.2.0",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@jwetzell/osc": "0.2.2", "@jwetzell/osc": "1.0.0",
"commander": "^11.0.0" "commander": "^12.1.0"
}, },
"bin": { "bin": {
"readosc": "src/main.js" "readosc": "src/main.js"
} }
}, },
"apps/readosc/node_modules/@jwetzell/osc": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@jwetzell/osc/-/osc-1.0.0.tgz",
"integrity": "sha512-242gA8R4k9vBbRJydqoKAG1WiQ9BUO6tFoemuC3eywRt5ns5qrlzIBEIwX0poQyMZdZB7XyzvR5rtbFC5tadKg=="
},
"apps/sendosc": { "apps/sendosc": {
"version": "1.0.1", "version": "1.0.1",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@jwetzell/osc": "0.2.2", "@jwetzell/osc": "0.2.2",
"commander": "^11.0.0", "commander": "^12.1.0",
"slip": "^1.0.2" "slip": "^1.0.2"
}, },
"bin": { "bin": {
"sendosc": "src/main.js" "sendosc": "src/main.js"
} }
}, },
"apps/sendosc/node_modules/@jwetzell/osc": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/@jwetzell/osc/-/osc-0.2.2.tgz",
"integrity": "sha512-HobCAMnDDSrKfrMBrF5ySA8fKO8Qjkn2HDKysuV51sHrpXABKJFQt5OemIQgbnSMNWU0kZ0+FF9xRrivHHaDTg=="
},
"node_modules/@eslint-community/eslint-utils": { "node_modules/@eslint-community/eslint-utils": {
"version": "4.4.0", "version": "4.4.0",
"dev": true, "dev": true,
@@ -240,9 +255,10 @@
"peer": true "peer": true
}, },
"node_modules/@types/node": { "node_modules/@types/node": {
"version": "22.7.4", "version": "22.7.7",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.7.tgz",
"integrity": "sha512-SRxCrrg9CL/y54aiMCG3edPKdprgMVGDXjA3gB8UmmBW5TcXzRUYAh8EWzTnSJFAd1rgImPELza+A3bJ+qxz8Q==",
"dev": true, "dev": true,
"license": "MIT",
"dependencies": { "dependencies": {
"undici-types": "~6.19.2" "undici-types": "~6.19.2"
} }
@@ -596,10 +612,11 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/commander": { "node_modules/commander": {
"version": "11.1.0", "version": "12.1.0",
"license": "MIT", "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz",
"integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==",
"engines": { "engines": {
"node": ">=16" "node": ">=18"
} }
}, },
"node_modules/concat-map": { "node_modules/concat-map": {
@@ -3391,9 +3408,10 @@
} }
}, },
"node_modules/typescript": { "node_modules/typescript": {
"version": "5.6.2", "version": "5.6.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz",
"integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==",
"dev": true, "dev": true,
"license": "Apache-2.0",
"bin": { "bin": {
"tsc": "bin/tsc", "tsc": "bin/tsc",
"tsserver": "bin/tsserver" "tsserver": "bin/tsserver"
@@ -3633,12 +3651,12 @@
}, },
"packages/osc": { "packages/osc": {
"name": "@jwetzell/osc", "name": "@jwetzell/osc",
"version": "0.2.2", "version": "1.6.0",
"license": "MIT", "license": "MIT",
"devDependencies": { "devDependencies": {
"@types/node": "22.7.4", "@types/node": "22.7.7",
"rimraf": "6.0.1", "rimraf": "6.0.1",
"typescript": "5.6.2" "typescript": "5.6.3"
} }
} }
} }
+17 -6
View File
@@ -1,12 +1,23 @@
{ {
"name": "@jwetzell/osc", "name": "@jwetzell/osc",
"version": "0.2.2", "version": "1.6.0",
"description": "", "description": "",
"main": "dist/index.js", "main": "./dist/cjs/index.js",
"types": "dist/index.d.ts", "module": "./dist/esm/index.js",
"types": "./dist/types/index.d.ts",
"exports": {
".": {
"import": "./dist/esm/index.js",
"require": "./dist/cjs/index.js",
"types": "./dist/types/index.d.ts"
}
},
"scripts": { "scripts": {
"prebuild": "rimraf dist", "prebuild": "rimraf dist",
"build": "tsc", "build": "npm run build:cjs && npm run build:esm && npm run build:types",
"build:cjs": "tsc -p tsconfig.cjs.json",
"build:esm": "tsc -p tsconfig.esm.json",
"build:types": "tsc -p tsconfig.types.json",
"prepublishOnly": "npm run build", "prepublishOnly": "npm run build",
"pretest": "npm run build", "pretest": "npm run build",
"test": "node --test --experimental-test-coverage" "test": "node --test --experimental-test-coverage"
@@ -25,8 +36,8 @@
}, },
"license": "MIT", "license": "MIT",
"devDependencies": { "devDependencies": {
"@types/node": "22.7.4", "@types/node": "22.7.7",
"rimraf": "6.0.1", "rimraf": "6.0.1",
"typescript": "5.6.2" "typescript": "5.6.3"
} }
} }
+45 -17
View File
@@ -2,12 +2,12 @@ import { messageFromBuffer, messageToBuffer } from './message';
import { OSCBundle } from './models'; import { OSCBundle } from './models';
import { oscTypeConverterMap } from './osc-types'; import { oscTypeConverterMap } from './osc-types';
export function bundleFromBuffer(bytes: Buffer): OSCBundle | undefined { export function bundleFromBuffer(bytes: Uint8Array): [OSCBundle | undefined, Uint8Array | undefined] {
if (bytes.length < 8) { if (bytes.length < 20) {
throw new Error('bundle has to be at least 20 bytes'); throw new Error('bundle has to be at least 20 bytes');
} }
if (bytes.subarray(0, 7).toString('ascii') !== '#bundle') { if (new TextDecoder().decode(bytes.subarray(0, 7)) !== '#bundle') {
throw new Error('bundle must start with #bundle'); throw new Error('bundle must start with #bundle');
} }
@@ -22,44 +22,55 @@ export function bundleFromBuffer(bytes: Buffer): OSCBundle | undefined {
let endOfBundle = false; let endOfBundle = false;
let [contentSize, remainingBytes] = oscTypeConverterMap.i.fromBuffer(bytesAfterTimeTag); // let [contentSize, remainingBytes] = oscTypeConverterMap.i.fromBuffer(bytesAfterTimeTag);
if (typeof contentSize === 'number') { let remainingBytes = bytesAfterTimeTag;
while (!endOfBundle) { while (!endOfBundle) {
let [contentSize, bytesAfterContentSize] = oscTypeConverterMap.i.fromBuffer(remainingBytes);
if (typeof contentSize !== 'number') {
throw new Error('problem decoding content size');
}
remainingBytes = bytesAfterContentSize;
if (remainingBytes.length < contentSize) { if (remainingBytes.length < contentSize) {
throw new Error('bundle does not contain enough data'); throw new Error('bundle does not contain enough data');
} }
const bundleContentBytes = remainingBytes.subarray(0, contentSize); const bundleContentBytes = bytesAfterContentSize.subarray(0, contentSize);
if (bundleContentBytes[0] === 35) { if (bundleContentBytes[0] === 35) {
// # character indicating contents is a bundle // # character indicating contents is a bundle
const content = bundleFromBuffer(bundleContentBytes); const [content, bytesAfterContent] = bundleFromBuffer(bundleContentBytes);
if (content) { if (content) {
bundleContents.push(content); bundleContents.push(content);
} }
} else if (bundleContentBytes[0] === 47) { } else if (bundleContentBytes[0] === 47) {
const content = messageFromBuffer(bundleContentBytes); const [content, bytesAfterContent] = messageFromBuffer(bundleContentBytes);
if (content) { if (content && content !== undefined) {
bundleContents.push(content); bundleContents.push(content);
} }
} else { } else {
throw new Error('bundle contents does not look like a OSC message or bundle'); throw new Error('bundle contents does not look like a OSC message or bundle');
} }
remainingBytes = remainingBytes.subarray(contentSize); remainingBytes = bytesAfterContentSize.subarray(contentSize);
if (remainingBytes.length === 0) { if (remainingBytes.length === 0) {
endOfBundle = true; endOfBundle = true;
} }
} }
}
return { return [
{
timeTag, timeTag,
contents: bundleContents, contents: bundleContents,
}; },
remainingBytes,
];
} }
export function bundleToBuffer(bundle: OSCBundle): Buffer { export function bundleToBuffer(bundle: OSCBundle): Uint8Array {
const headerBuffer = oscTypeConverterMap.s.toBuffer('#bundle'); const headerBuffer = oscTypeConverterMap.s.toBuffer('#bundle');
if (headerBuffer === undefined) { if (headerBuffer === undefined) {
@@ -72,7 +83,9 @@ export function bundleToBuffer(bundle: OSCBundle): Buffer {
throw new Error('problem encoding buffer time tag'); throw new Error('problem encoding buffer time tag');
} }
const contentsBuffers: Buffer[] = []; const contentsBuffers: Uint8Array[] = [];
let contentsBuffersTotalLength = 0;
bundle.contents.forEach((bundleContent) => { bundle.contents.forEach((bundleContent) => {
if ('address' in bundleContent) { if ('address' in bundleContent) {
@@ -80,10 +93,25 @@ export function bundleToBuffer(bundle: OSCBundle): Buffer {
const contentSizeBuffer = oscTypeConverterMap.i.toBuffer(contentBuffer.length); const contentSizeBuffer = oscTypeConverterMap.i.toBuffer(contentBuffer.length);
if (contentBuffer && contentSizeBuffer) { if (contentBuffer && contentSizeBuffer) {
contentsBuffers.push(Buffer.concat([contentSizeBuffer, contentBuffer])); const buffer = new Uint8Array(contentSizeBuffer.length + contentBuffer.length);
buffer.set(contentSizeBuffer, 0);
buffer.set(contentBuffer, contentSizeBuffer.length);
contentsBuffers.push(buffer);
contentsBuffersTotalLength += buffer.length;
} }
} }
}); });
return Buffer.concat([headerBuffer, timeTagBuffer, ...contentsBuffers]); const buffer = new Uint8Array(headerBuffer.length + timeTagBuffer.length + contentsBuffersTotalLength);
let offset = 0;
buffer.set(headerBuffer, offset);
offset += headerBuffer.length;
buffer.set(timeTagBuffer, offset);
offset += timeTagBuffer.length;
contentsBuffers.forEach((contentBuffer) => {
buffer.set(contentBuffer, offset);
offset += contentBuffer.length;
});
return buffer;
} }
+14
View File
@@ -1,3 +1,17 @@
import { bundleFromBuffer } from './bundle';
import { messageFromBuffer } from './message';
import { OSCBundle, OSCMessage } from './models';
export * from './models'; export * from './models';
export * from './message'; export * from './message';
export * from './bundle'; export * from './bundle';
export function fromBuffer(bytes: Uint8Array): [OSCBundle | OSCMessage | undefined, Uint8Array | undefined] {
if (bytes[0] === 47) { // starts with '/'
return messageFromBuffer(bytes);
} else if (bytes[0] === 35) { // starts with '#'
return bundleFromBuffer(bytes)
} else {
throw new Error('bytes do not look like an OSC message or bundle');
}
}
+84 -12
View File
@@ -2,7 +2,8 @@ import { OSCMessage, OSCArg, OSCType } from './models';
import { oscTypeConverterMap } from './osc-types'; import { oscTypeConverterMap } from './osc-types';
function argsToBuffer(args: OSCArg[]) { function argsToBuffer(args: OSCArg[]) {
const argBuffers: Buffer[] = []; const argBuffers: Uint8Array[] = [];
let argBuffersTotalLength = 0;
for (let index = 0; index < args.length; index += 1) { for (let index = 0; index < args.length; index += 1) {
const arg = args[index]; const arg = args[index];
@@ -14,44 +15,108 @@ function argsToBuffer(args: OSCArg[]) {
const buffer = typeConverter.toBuffer(arg.value); const buffer = typeConverter.toBuffer(arg.value);
if (buffer !== undefined) { if (buffer !== undefined) {
argBuffers.push(buffer); argBuffers.push(buffer);
argBuffersTotalLength += buffer.length;
} }
} }
return Buffer.concat(argBuffers); const buffer = new Uint8Array(argBuffersTotalLength);
let offset = 0;
argBuffers.forEach((argBuffer) => {
buffer.set(argBuffer, offset);
offset += argBuffer.length;
});
return buffer;
} }
export function messageToBuffer(message: OSCMessage): Buffer { export function messageToBuffer(message: OSCMessage): Uint8Array {
if (!message.address.startsWith('/')) {
throw new Error('osc message must start with a /');
}
const addressBuffer = oscTypeConverterMap.s.toBuffer(message.address); const addressBuffer = oscTypeConverterMap.s.toBuffer(message.address);
if (addressBuffer === undefined) { if (addressBuffer === undefined) {
throw new Error('problem encoding address'); throw new Error('problem encoding address');
} }
const typeString = message.args.map((arg) => arg.type).join(''); let typeString = ',';
const typesBuffer = oscTypeConverterMap.s.toBuffer(`,${typeString}`);
const flatOSCArgs: OSCArg[] = [];
message.args.forEach((oscArg) => {
if (Array.isArray(oscArg)) {
typeString += '[';
oscArg.forEach((oscArg) => {
typeString += oscArg.type;
flatOSCArgs.push(oscArg);
});
typeString += ']';
} else {
typeString += oscArg.type;
flatOSCArgs.push(oscArg);
}
});
const typesBuffer = oscTypeConverterMap.s.toBuffer(typeString);
if (typesBuffer === undefined) { if (typesBuffer === undefined) {
throw new Error('problem encoding types'); throw new Error('problem encoding types');
} }
const argsBuffer = argsToBuffer(message.args); const argsBuffer = argsToBuffer(flatOSCArgs);
return Buffer.concat([addressBuffer, typesBuffer, argsBuffer]); const buffer = new Uint8Array(addressBuffer.length + typesBuffer.length + argsBuffer.length);
buffer.set(addressBuffer, 0);
buffer.set(typesBuffer, addressBuffer.length);
buffer.set(argsBuffer, addressBuffer.length + typesBuffer.length);
return buffer;
} }
export function messageFromBuffer(bytes: Buffer): OSCMessage | undefined { export function messageFromBuffer(bytes: Uint8Array): [OSCMessage | undefined, Uint8Array | undefined] {
if (bytes[0] !== 47) { if (bytes[0] !== 47) {
throw new Error('osc message must start with a /'); throw new Error('osc message must start with a /');
} }
const oscArgs: OSCArg[] = []; const oscArgs: (OSCArg | OSCArg[])[] = [];
const [address, bytesAfterAddress] = oscTypeConverterMap.s.fromBuffer(bytes); const [address, bytesAfterAddress] = oscTypeConverterMap.s.fromBuffer(bytes);
if (typeof address === 'string') { if (typeof address === 'string') {
if (bytesAfterAddress.length === 0) {
// NOTE(jwetzell): OSC 1.0 spec says that messages without a type string should be allowed
return [
{
address,
args: [],
},
bytesAfterAddress,
];
}
let [typeString, bytesAfterType] = oscTypeConverterMap.s.fromBuffer(bytesAfterAddress); let [typeString, bytesAfterType] = oscTypeConverterMap.s.fromBuffer(bytesAfterAddress);
if (typeof typeString === 'string') { if (typeof typeString === 'string') {
if (!typeString.startsWith(',')) { if (!typeString.startsWith(',')) {
throw new Error('osc type string must start with a ,'); throw new Error('osc type string must start with a ,');
} }
let argsBuffer = bytesAfterType; let argsBuffer = bytesAfterType;
let oscArrayArg: OSCArg[] = [];
let insideArgArray = false;
for (let index = 1; index < typeString.length; index++) { for (let index = 1; index < typeString.length; index++) {
const argType = typeString.charAt(index) as OSCType; const argType = typeString.charAt(index) as OSCType;
if (argType === '[') {
if (insideArgArray) {
throw new Error('osc arg array opened without closing previous arg array');
}
oscArrayArg = [];
insideArgArray = true;
continue;
}
if (argType === ']') {
if (!insideArgArray) {
throw new Error('osc arg array closed without opening arg array');
}
oscArgs.push(oscArrayArg);
oscArrayArg = [];
insideArgArray = false;
continue;
}
const oscTypeConverter = oscTypeConverterMap[argType]; const oscTypeConverter = oscTypeConverterMap[argType];
if (oscTypeConverter === undefined) { if (oscTypeConverter === undefined) {
throw new Error('unknown OSC type'); throw new Error('unknown OSC type');
@@ -62,15 +127,22 @@ export function messageFromBuffer(bytes: Buffer): OSCMessage | undefined {
type: argType, type: argType,
value: value, value: value,
}; };
if (insideArgArray) {
oscArrayArg.push(arg);
} else {
oscArgs.push(arg); oscArgs.push(arg);
} }
}
argsBuffer = remainingBytes; argsBuffer = remainingBytes;
} }
return [
return { {
address, address,
args: oscArgs, args: oscArgs,
}; },
argsBuffer,
];
} }
} }
return [undefined, undefined];
} }
+16 -5
View File
@@ -1,11 +1,18 @@
export type OSCType = 's' | 'i' | 'f' | 'b' | 'T' | 'F' | 't'; export type OSCType = 's' | 'i' | 'f' | 'b' | 'T' | 'F' | 't' | 'r' | 'N' | '[' | ']';
export type OSCArg = { export type OSCArg = {
type: OSCType; type: OSCType;
value: string | number | Buffer | boolean | OSCTimeTag; value: string | number | Uint8Array | boolean | OSCTimeTag | OSCColor | bigint | null;
}; };
export type OSCTimeTag = [number, number]; export type OSCTimeTag = [number, number];
export type OSCColor = {
r: number;
g: number;
b: number;
a: number;
};
export type OSCBundle = { export type OSCBundle = {
timeTag: OSCTimeTag; timeTag: OSCTimeTag;
contents: (OSCBundle | OSCMessage)[]; contents: (OSCBundle | OSCMessage)[];
@@ -13,10 +20,14 @@ export type OSCBundle = {
export type OSCMessage = { export type OSCMessage = {
address: string; address: string;
args: OSCArg[]; args: (OSCArg | OSCArg[])[];
}; };
export type OSCTypeConverter = { export type OSCTypeConverter = {
toBuffer: (value: string | number | Buffer | boolean | OSCTimeTag) => Buffer | undefined; toBuffer: (
fromBuffer: (buffer: Buffer) => [string | number | Buffer | boolean | OSCTimeTag | undefined, Buffer]; value: string | number | Uint8Array | boolean | OSCTimeTag | OSCColor | null | bigint
) => Uint8Array | undefined;
fromBuffer: (
buffer: Uint8Array
) => [string | number | Uint8Array | boolean | OSCTimeTag | OSCColor | null | bigint | undefined, Uint8Array];
}; };
+155 -21
View File
@@ -1,4 +1,4 @@
import { OSCType, OSCArg, OSCMessage, OSCTypeConverter, OSCBundle, OSCTimeTag } from './models'; import { OSCTypeConverter, OSCTimeTag } from './models';
export const oscTypeConverterMap: { [key: string]: OSCTypeConverter } = { export const oscTypeConverterMap: { [key: string]: OSCTypeConverter } = {
s: { s: {
@@ -9,11 +9,11 @@ export const oscTypeConverterMap: { [key: string]: OSCTypeConverter } = {
if (padSize < 4) { if (padSize < 4) {
oscString = oscString.padEnd(oscString.length + padSize, '\u0000'); oscString = oscString.padEnd(oscString.length + padSize, '\u0000');
} }
return Buffer.from(oscString, 'ascii'); return new TextEncoder().encode(oscString);
} }
throw new TypeError('osc type s toBuffer called with non string value'); throw new TypeError('osc type s toBuffer called with non string value');
}, },
fromBuffer: (bytes: Buffer) => { fromBuffer: (bytes) => {
let stringEnd = 0; let stringEnd = 0;
let stringPaddingEnd = 0; let stringPaddingEnd = 0;
for (let index = 0; index < bytes.length; index++) { for (let index = 0; index < bytes.length; index++) {
@@ -29,15 +29,15 @@ export const oscTypeConverterMap: { [key: string]: OSCTypeConverter } = {
} }
} }
return [bytes.toString('ascii', 0, stringEnd), bytes.subarray(stringPaddingEnd)]; return [new TextDecoder().decode(bytes.subarray(0, stringEnd)), bytes.subarray(stringPaddingEnd)];
}, },
}, },
f: { f: {
toBuffer: (number) => { toBuffer: (number) => {
if (typeof number === 'number') { if (typeof number === 'number') {
const buffer = Buffer.alloc(4); const view = new DataView(new ArrayBuffer(4));
buffer.writeFloatBE(number); view.setFloat32(0, number);
return buffer; return new Uint8Array(view.buffer);
} }
throw new TypeError('osc type f toBuffer called with non number value'); throw new TypeError('osc type f toBuffer called with non number value');
}, },
@@ -45,17 +45,22 @@ export const oscTypeConverterMap: { [key: string]: OSCTypeConverter } = {
if (buffer.length < 4) { if (buffer.length < 4) {
throw new Error('not enough bytes to read a osc float'); throw new Error('not enough bytes to read a osc float');
} }
const view = new DataView(new ArrayBuffer(4));
const value = buffer.readFloatBE(); buffer.slice(0, 4).forEach((byte, index) => {
view.setUint8(index, byte);
});
const value = view.getFloat32(0);
return [value, buffer.subarray(4)]; return [value, buffer.subarray(4)];
}, },
}, },
i: { i: {
toBuffer: (number) => { toBuffer: (number) => {
if (typeof number === 'number') { if (typeof number === 'number') {
const buffer = Buffer.alloc(4); const view = new DataView(new ArrayBuffer(4));
buffer.writeInt32BE(number); view.setInt32(0, number);
return buffer; return new Uint8Array(view.buffer);
} }
throw new TypeError('osc type i toBuffer called with non number value'); throw new TypeError('osc type i toBuffer called with non number value');
}, },
@@ -64,21 +69,33 @@ export const oscTypeConverterMap: { [key: string]: OSCTypeConverter } = {
throw new Error('not enough bytes to read a osc integer'); throw new Error('not enough bytes to read a osc integer');
} }
const value = buffer.readInt32BE(); const view = new DataView(new ArrayBuffer(4));
buffer.slice(0, 4).forEach((byte, index) => {
view.setUint8(index, byte);
});
const value = view.getInt32(0);
return [value, buffer.subarray(4)]; return [value, buffer.subarray(4)];
}, },
}, },
b: { b: {
toBuffer: (data) => { toBuffer: (data) => {
if (Buffer.isBuffer(data)) { if (data instanceof Uint8Array) {
const sizeBuffer = oscTypeConverterMap.i.toBuffer(data.length); const sizeBuffer = oscTypeConverterMap.i.toBuffer(data.length);
if (sizeBuffer) { if (sizeBuffer) {
const padSize = 4 - (data.length % 4); let padSize = 4 - (data.length % 4);
const padBuffer = padSize < 4 ? Buffer.from(Array(padSize).fill(0)) : Buffer.from([]); if (padSize === 4) {
return Buffer.concat([sizeBuffer, data, padBuffer]); padSize = 0;
}
const buffer = new Uint8Array(4 + data.length + padSize);
buffer.set(sizeBuffer);
buffer.set(data, 4);
return buffer;
} }
} }
throw new TypeError('osc type b toBuffer called with non Buffer value'); throw new TypeError('osc type b toBuffer called with non Uint8Array value');
}, },
fromBuffer: (buffer) => { fromBuffer: (buffer) => {
const [blobLength, blobBytes] = oscTypeConverterMap.i.fromBuffer(buffer); const [blobLength, blobBytes] = oscTypeConverterMap.i.fromBuffer(buffer);
@@ -97,7 +114,7 @@ export const oscTypeConverterMap: { [key: string]: OSCTypeConverter } = {
}, },
T: { T: {
toBuffer: () => { toBuffer: () => {
return Buffer.alloc(0); return new Uint8Array(0);
}, },
fromBuffer: (buffer) => { fromBuffer: (buffer) => {
return [true, buffer]; return [true, buffer];
@@ -105,7 +122,7 @@ export const oscTypeConverterMap: { [key: string]: OSCTypeConverter } = {
}, },
F: { F: {
toBuffer: () => { toBuffer: () => {
return Buffer.alloc(0); return new Uint8Array(0);
}, },
fromBuffer: (buffer) => { fromBuffer: (buffer) => {
return [false, buffer]; return [false, buffer];
@@ -135,12 +152,15 @@ export const oscTypeConverterMap: { [key: string]: OSCTypeConverter } = {
const fractionalBuffer = oscTypeConverterMap.i.toBuffer(fractional); const fractionalBuffer = oscTypeConverterMap.i.toBuffer(fractional);
if (secondsBuffer && fractionalBuffer) { if (secondsBuffer && fractionalBuffer) {
return Buffer.concat([secondsBuffer, fractionalBuffer]); const buffer = new Uint8Array(8);
buffer.set(secondsBuffer);
buffer.set(fractionalBuffer, 4);
return buffer;
} }
}, },
fromBuffer: (buffer) => { fromBuffer: (buffer) => {
if (buffer.length < 8) { if (buffer.length < 8) {
throw new Error('osc time tag must be greater than 8 bytes'); throw new Error('osc time tag must be at least 8 bytes');
} }
const [seconds, bytesAfterSeconds] = oscTypeConverterMap.i.fromBuffer(buffer); const [seconds, bytesAfterSeconds] = oscTypeConverterMap.i.fromBuffer(buffer);
const [fractional, bytesAfterFractional] = oscTypeConverterMap.i.fromBuffer(bytesAfterSeconds); const [fractional, bytesAfterFractional] = oscTypeConverterMap.i.fromBuffer(bytesAfterSeconds);
@@ -151,4 +171,118 @@ export const oscTypeConverterMap: { [key: string]: OSCTypeConverter } = {
return [undefined, bytesAfterFractional]; return [undefined, bytesAfterFractional];
}, },
}, },
r: {
toBuffer: (color) => {
if (color === undefined || color === null) {
throw new TypeError('osc type r called with undefined or null value');
}
if (typeof color === 'object' && 'r' in color && 'g' in color && 'b' in color && 'a' in color) {
const view = new DataView(new ArrayBuffer(4));
view.setUint8(0, color.r);
view.setUint8(1, color.g);
view.setUint8(2, color.b);
view.setUint8(3, color.a);
return new Uint8Array(view.buffer);
}
throw new TypeError('osc type r called with non OSCColor object');
},
fromBuffer: (buffer) => {
if (buffer.length < 4) {
throw new Error('osc color must be at least 4 bytes');
}
const red = buffer[0];
const green = buffer[1];
const blue = buffer[2];
const alpha = buffer[3];
if (red !== undefined && green !== undefined && blue !== undefined && alpha !== undefined) {
if (
typeof red === 'number' &&
typeof green === 'number' &&
typeof blue === 'number' &&
typeof alpha === 'number'
) {
return [
{
r: red,
g: green,
b: blue,
a: alpha,
},
buffer.subarray(4),
];
}
}
throw new Error('problem converting osc ');
},
},
N: {
toBuffer: () => {
return new Uint8Array(0);
},
fromBuffer: (buffer) => {
return [null, buffer];
},
},
I: {
toBuffer: () => {
return new Uint8Array();
},
fromBuffer: (buffer) => {
return [Number.MAX_SAFE_INTEGER, buffer];
},
},
h: {
toBuffer: (number) => {
const view = new DataView(new ArrayBuffer(8));
if (typeof number === 'number') {
view.setBigInt64(0, BigInt(number));
} else if (typeof number === 'bigint') {
view.setBigInt64(0, number);
} else {
throw new TypeError('osc type h toBuffer called with non number or bigint value');
}
return new Uint8Array(view.buffer);
},
fromBuffer: (buffer) => {
if (buffer.length < 8) {
throw new Error('osc int64 must be at least 8 bytes');
}
const view = new DataView(new ArrayBuffer(8));
buffer.subarray(0, 8).forEach((byte, index) => {
view.setUint8(index, byte);
});
return [view.getBigInt64(0), buffer.subarray(8)];
},
},
d: {
toBuffer: (number) => {
if (typeof number === 'number') {
const view = new DataView(new ArrayBuffer(8));
view.setFloat64(0, number);
return new Uint8Array(view.buffer);
}
throw new TypeError('osc type d toBuffer called with non number value');
},
fromBuffer: (buffer) => {
if (buffer.length < 8) {
throw new Error('not enough bytes to read a osc float');
}
const view = new DataView(new ArrayBuffer(8));
buffer.subarray(0, 8).forEach((byte, index) => {
view.setUint8(index, byte);
});
const value = view.getFloat64(0);
return [value, buffer.subarray(8)];
},
},
}; };
+183 -12
View File
@@ -1,29 +1,200 @@
const { deepEqual } = require('assert'); const { deepEqual, equal, throws } = require('assert');
const { describe, it } = require('node:test'); const { describe, it } = require('node:test');
const osc = require('../dist/index'); const osc = require('../dist/cjs/index');
const tests = [ const goodTests = [
{ {
description: 'simple contents single message', description: 'simple contents single message',
bytes: new Uint8Array([
...new TextEncoder().encode('#bundle'),
...new Uint8Array([0x00]),
...new Uint8Array([0, 0, 0, 32, 0, 0, 0, 0]),
...new Uint8Array([0x00, 0x00, 0x00, 0x20]),
...new Uint8Array([
0x2f, 0x6f, 0x73, 0x63, 0x69, 0x6c, 0x6c, 0x61, 0x74, 0x6f, 0x72, 0x2f, 0x34, 0x2f, 0x66, 0x72, 0x65, 0x71,
0x75, 0x65, 0x6e, 0x63, 0x79, 0x00, 0x2c, 0x66, 0x00, 0x00, 0x43, 0xdc, 0x00, 0x00,
]),
]),
expected: { expected: {
timeTag: [32, 0], timeTag: [32, 0],
contents: [{ address: '/oscillator/4/frequency', args: [{ type: 'f', value: 440 }] }], contents: [{ address: '/oscillator/4/frequency', args: [{ type: 'f', value: 440 }] }],
}, },
bundle: Buffer.concat([ },
Buffer.from('#bundle', 'ascii'), {
Buffer.from([0x00]), description: 'simple contents single bundle',
Buffer.from([0, 0, 0, 32, 0, 0, 0, 0]), bytes: new Uint8Array([
Buffer.from('00000020', 'hex'), ...new TextEncoder().encode('#bundle'),
Buffer.from('2f6f7363696c6c61746f722f342f6672657175656e6379002c66000043dc0000', 'hex'), ...new Uint8Array([0x00]),
...new Uint8Array([0, 0, 0, 32, 0, 0, 0, 0]),
...new Uint8Array([0x00, 0x00, 0x00, 0x34]),
...new Uint8Array([
...new TextEncoder().encode('#bundle'),
...new Uint8Array([0x00]),
...new Uint8Array([0, 0, 0, 32, 0, 0, 0, 0]),
...new Uint8Array([0x00, 0x00, 0x00, 0x20]),
...new Uint8Array([
0x2f, 0x6f, 0x73, 0x63, 0x69, 0x6c, 0x6c, 0x61, 0x74, 0x6f, 0x72, 0x2f, 0x34, 0x2f, 0x66, 0x72, 0x65, 0x71,
0x75, 0x65, 0x6e, 0x63, 0x79, 0x00, 0x2c, 0x66, 0x00, 0x00, 0x43, 0xdc, 0x00, 0x00,
]), ]),
]),
]),
expected: {
timeTag: [32, 0],
contents: [
{
timeTag: [32, 0],
contents: [{ address: '/oscillator/4/frequency', args: [{ type: 'f', value: 440 }] }],
},
],
},
},
{
description: 'resolume bundle example',
bytes: new Uint8Array([
0x23, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x28, 0x2f, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x6c, 0x61, 0x79, 0x65, 0x72,
0x73, 0x2f, 0x31, 0x2f, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x00, 0x2c, 0x66, 0x00, 0x00, 0x3d,
0x6d, 0x9c, 0x9c, 0x00, 0x00, 0x00, 0x2c, 0x2f, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e,
0x2f, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x65, 0x64, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x2f, 0x70, 0x6f, 0x73, 0x69,
0x74, 0x69, 0x6f, 0x6e, 0x00, 0x2c, 0x66, 0x00, 0x00, 0x3d, 0x6d, 0x9c, 0x9c, 0x00, 0x00, 0x00, 0x3c, 0x2f, 0x63,
0x6f, 0x6d, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x73, 0x2f, 0x31,
0x2f, 0x63, 0x6c, 0x69, 0x70, 0x73, 0x2f, 0x34, 0x2f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2f,
0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x00, 0x00, 0x00, 0x2c, 0x66, 0x00, 0x00, 0x3d, 0x6d, 0x9c,
0x9c, 0x00, 0x00, 0x00, 0x38, 0x2f, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x73,
0x65, 0x6c, 0x65, 0x63, 0x74, 0x65, 0x64, 0x63, 0x6c, 0x69, 0x70, 0x2f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f,
0x72, 0x74, 0x2f, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x00, 0x00, 0x00, 0x2c, 0x66, 0x00, 0x00,
0x3d, 0x6d, 0x9c, 0x9c, 0x00, 0x00, 0x00, 0x28, 0x2f, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f,
0x6e, 0x2f, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x73, 0x2f, 0x31, 0x2f, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e,
0x00, 0x00, 0x2c, 0x66, 0x00, 0x00, 0x3d, 0x6d, 0x9c, 0x9c, 0x00, 0x00, 0x00, 0x2c, 0x2f, 0x63, 0x6f, 0x6d, 0x70,
0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x65, 0x64, 0x6c, 0x61, 0x79,
0x65, 0x72, 0x2f, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x2c, 0x66, 0x00, 0x00, 0x3d, 0x6d, 0x9c,
0x9c, 0x00, 0x00, 0x00, 0x3c, 0x2f, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x6c,
0x61, 0x79, 0x65, 0x72, 0x73, 0x2f, 0x31, 0x2f, 0x63, 0x6c, 0x69, 0x70, 0x73, 0x2f, 0x34, 0x2f, 0x74, 0x72, 0x61,
0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x00, 0x00, 0x00,
0x2c, 0x66, 0x00, 0x00, 0x3d, 0x6d, 0x9c, 0x9c, 0x00, 0x00, 0x00, 0x38, 0x2f, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x73,
0x69, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x65, 0x64, 0x63, 0x6c, 0x69, 0x70, 0x2f,
0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x00,
0x00, 0x00, 0x00, 0x2c, 0x66, 0x00, 0x00, 0x3d, 0x6d, 0x9c, 0x9c,
]),
expected: {
timeTag: [0, 1],
contents: [
{
address: '/composition/layers/1/position',
args: [{ type: 'f', value: 0.05801068246364593505859375 }],
},
{
address: '/composition/selectedlayer/position',
args: [{ type: 'f', value: 0.05801068246364593505859375 }],
},
{
address: '/composition/layers/1/clips/4/transport/position',
args: [{ type: 'f', value: 0.05801068246364593505859375 }],
},
{
address: '/composition/selectedclip/transport/position',
args: [{ type: 'f', value: 0.05801068246364593505859375 }],
},
{
address: '/composition/layers/1/position',
args: [{ type: 'f', value: 0.05801068246364593505859375 }],
},
{
address: '/composition/selectedlayer/position',
args: [{ type: 'f', value: 0.05801068246364593505859375 }],
},
{
address: '/composition/layers/1/clips/4/transport/position',
args: [{ type: 'f', value: 0.05801068246364593505859375 }],
},
{
address: '/composition/selectedclip/transport/position',
args: [{ type: 'f', value: 0.05801068246364593505859375 }],
},
],
},
}, },
]; ];
describe('OSC Bundle Decoding', () => { const badTests = [
tests.forEach((bundleTest) => { {
description: 'bad bundle header',
bytes: new Uint8Array([
...new TextEncoder().encode('bundle'),
...new Uint8Array([0x00]),
...new Uint8Array([0, 0, 0, 32, 0, 0, 0, 0]),
...new Uint8Array([0x00, 0x00, 0x00, 0x20]),
...new Uint8Array([
0x2f, 0x6f, 0x73, 0x63, 0x69, 0x6c, 0x6c, 0x61, 0x74, 0x6f, 0x72, 0x2f, 0x34, 0x2f, 0x66, 0x72, 0x65, 0x71,
0x75, 0x65, 0x6e, 0x63, 0x79, 0x00, 0x2c, 0x66, 0x00, 0x00, 0x43, 0xdc, 0x00, 0x00,
]),
]),
throwsMessage: {
name: /^Error$/,
message: 'bundle must start with #bundle',
},
},
{
description: 'incomplete bundle',
bytes: new Uint8Array([
...new TextEncoder().encode('#bundle'),
...new Uint8Array([0x00]),
...new Uint8Array([0, 0, 0, 32, 0, 0, 0, 0]),
]),
throwsMessage: {
name: /^Error$/,
message: 'bundle has to be at least 20 bytes',
},
},
{
description: 'bundle not enough bytes',
bytes: new Uint8Array([
...new TextEncoder().encode('#bundle'),
...new Uint8Array([0x00]),
...new Uint8Array([0, 0, 0, 32, 0, 0, 0, 0]),
...new Uint8Array([0x00, 0x00, 0x00, 0x20]),
...new Uint8Array([0x2f, 0x6f, 0x73, 0x63, 0x69, 0x6c, 0x6c, 0x61]),
]),
throwsMessage: {
name: /^Error$/,
message: 'bundle does not contain enough data',
},
},
{
description: 'bad bundle contents',
bytes: new Uint8Array([
...new TextEncoder().encode('#bundle'),
...new Uint8Array([0x00]),
...new Uint8Array([0, 0, 0, 32, 0, 0, 0, 0]),
...new Uint8Array([0x00, 0x00, 0x00, 0x20]),
...new Uint8Array([
0x2a, 0x6f, 0x73, 0x63, 0x69, 0x6c, 0x6c, 0x61, 0x74, 0x6f, 0x72, 0x2f, 0x34, 0x2f, 0x66, 0x72, 0x65, 0x71,
0x75, 0x65, 0x6e, 0x63, 0x79, 0x00, 0x2c, 0x66, 0x00, 0x00, 0x43, 0xdc, 0x00, 0x00,
]),
]),
throwsMessage: {
name: /^Error$/,
message: 'bundle contents does not look like a OSC message or bundle',
},
},
];
describe('OSC Bundle Decoding Pass', () => {
goodTests.forEach((bundleTest) => {
it(bundleTest.description, () => { it(bundleTest.description, () => {
const encoded = osc.bundleFromBuffer(bundleTest.bundle); const [encoded, remainingBytes] = osc.bundleFromBuffer(bundleTest.bytes);
equal(remainingBytes.length, 0);
deepEqual(encoded, bundleTest.expected); deepEqual(encoded, bundleTest.expected);
}); });
}); });
}); });
describe('OSC Bundle Decoding Throws', () => {
badTests.forEach((bundleTest) => {
it(bundleTest.description, () => {
throws(() => {
osc.bundleFromBuffer(bundleTest.bytes);
}, bundleTest.throwsMessage);
});
});
});
+15 -9
View File
@@ -1,17 +1,23 @@
const { deepEqual, throws } = require('assert'); const { deepEqual } = require('assert');
const { describe, it } = require('node:test'); const { describe, it } = require('node:test');
const osc = require('../dist/index'); const osc = require('../dist/cjs/index');
const tests = [ const tests = [
{ {
description: 'simple contents single message', description: 'simple contents single message',
bundle: { timeTag: [32, 0], contents: [{ address: '/oscillator/4/frequency', args: [{ type: 'f', value: 440 }] }] }, bundle: {
expected: Buffer.concat([ timeTag: [32, 0],
Buffer.from('#bundle', 'ascii'), contents: [{ address: '/oscillator/4/frequency', args: [{ type: 'f', value: 440 }] }],
Buffer.from([0x00]), },
Buffer.from([0, 0, 0, 32, 0, 0, 0, 0]), expected: new Uint8Array([
Buffer.from('00000020', 'hex'), ...new TextEncoder().encode('#bundle'),
Buffer.from('2f6f7363696c6c61746f722f342f6672657175656e6379002c66000043dc0000', 'hex'), ...new Uint8Array([0x00]),
...new Uint8Array([0, 0, 0, 32, 0, 0, 0, 0]),
...new Uint8Array([0x00, 0x00, 0x00, 0x20]),
...new Uint8Array([
0x2f, 0x6f, 0x73, 0x63, 0x69, 0x6c, 0x6c, 0x61, 0x74, 0x6f, 0x72, 0x2f, 0x34, 0x2f, 0x66, 0x72, 0x65, 0x71,
0x75, 0x65, 0x6e, 0x63, 0x79, 0x00, 0x2c, 0x66, 0x00, 0x00, 0x43, 0xdc, 0x00, 0x00,
]),
]), ]),
}, },
]; ];
+41
View File
@@ -0,0 +1,41 @@
const { deepEqual, equal } = require('assert');
const { describe, it } = require('node:test');
const osc = require('../dist/cjs/index');
const tests = [
{
description: 'simple bundle',
expected: {
timeTag: [32, 0],
contents: [{ address: '/oscillator/4/frequency', args: [{ type: 'f', value: 440 }] }],
},
bytes: new Uint8Array([
...new TextEncoder().encode('#bundle'),
...new Uint8Array([0x00]),
...new Uint8Array([0, 0, 0, 32, 0, 0, 0, 0]),
...new Uint8Array([0x00, 0x00, 0x00, 0x20]),
...new Uint8Array([
0x2f, 0x6f, 0x73, 0x63, 0x69, 0x6c, 0x6c, 0x61, 0x74, 0x6f, 0x72, 0x2f, 0x34, 0x2f, 0x66, 0x72, 0x65, 0x71,
0x75, 0x65, 0x6e, 0x63, 0x79, 0x00, 0x2c, 0x66, 0x00, 0x00, 0x43, 0xdc, 0x00, 0x00,
]),
]),
},
{
description: 'osc 1.0 spec example 1',
bytes: new Uint8Array([
47, 111, 115, 99, 105, 108, 108, 97, 116, 111, 114, 47, 52, 47, 102, 114, 101, 113, 117, 101, 110, 99, 121, 0, 44,
102, 0, 0, 67, 220, 0, 0,
]),
expected: { address: '/oscillator/4/frequency', args: [{ type: 'f', value: 440 }] },
},
]
describe('OSC Bytes Decoding', () => {
tests.forEach((bytesTest) => {
it(bytesTest.description, () => {
const [decoded, remainingBytes] = osc.fromBuffer(bytesTest.bytes);
deepEqual(decoded, bytesTest.expected);
equal(remainingBytes.length, 0)
});
});
});
+137 -67
View File
@@ -1,51 +1,108 @@
const { deepEqual, throws } = require('assert'); const { deepEqual, throws, equal } = require('assert');
const { describe, it } = require('node:test'); const { describe, it } = require('node:test');
const osc = require('../dist/index'); const osc = require('../dist/cjs/index');
const tests = [ const goodTests = [
{ {
description: 'simple address no args', description: 'simple address no args',
bytes: Buffer.from('2f68656c6c6f00002c000000', 'hex'), bytes: new Uint8Array([47, 104, 101, 108, 108, 111, 0, 0, 44, 0, 0, 0]),
expected: { address: '/hello', args: [] }, expected: { address: '/hello', args: [] },
}, },
{ {
description: 'simple address string arg', description: 'simple address string arg',
bytes: Buffer.from('2f68656c6c6f00002c7300006172673100000000', 'hex'), bytes: new Uint8Array([47, 104, 101, 108, 108, 111, 0, 0, 44, 115, 0, 0, 97, 114, 103, 49, 0, 0, 0, 0]),
expected: { address: '/hello', args: [{ type: 's', value: 'arg1' }] }, expected: { address: '/hello', args: [{ type: 's', value: 'arg1' }] },
}, },
{ {
description: 'simple address integer arg', description: 'simple address integer arg',
bytes: Buffer.from('2f68656c6c6f00002c69000000000023', 'hex'), bytes: new Uint8Array([47, 104, 101, 108, 108, 111, 0, 0, 44, 105, 0, 0, 0, 0, 0, 35]),
expected: { address: '/hello', args: [{ type: 'i', value: 35 }] }, expected: { address: '/hello', args: [{ type: 'i', value: 35 }] },
}, },
{ {
description: 'simple address float arg', description: 'simple address float arg',
bytes: Buffer.from('2f68656c6c6f00002c660000420a0000', 'hex'), bytes: new Uint8Array([47, 104, 101, 108, 108, 111, 0, 0, 44, 102, 0, 0, 66, 10, 0, 0]),
expected: { address: '/hello', args: [{ type: 'f', value: 34.5 }] }, expected: { address: '/hello', args: [{ type: 'f', value: 34.5 }] },
}, },
{ {
description: 'simple address blob arg', description: 'simple address blob arg',
bytes: Buffer.from('2f68656c6c6f00002c62000000000004626c6f62', 'hex'), bytes: new Uint8Array([47, 104, 101, 108, 108, 111, 0, 0, 44, 98, 0, 0, 0, 0, 0, 4, 98, 108, 111, 98]),
expected: { address: '/hello', args: [{ type: 'b', value: Buffer.from('blob') }] }, expected: { address: '/hello', args: [{ type: 'b', value: new TextEncoder().encode('blob') }] },
}, },
{ {
description: 'simple address True arg', description: 'simple address True arg',
bytes: Buffer.from('2f68656c6c6f00002c540000', 'hex'), bytes: new Uint8Array([47, 104, 101, 108, 108, 111, 0, 0, 44, 84, 0, 0]),
expected: { address: '/hello', args: [{ type: 'T', value: true }] }, expected: { address: '/hello', args: [{ type: 'T', value: true }] },
}, },
{ {
description: 'simple address False arg', description: 'simple address False arg',
bytes: Buffer.from('2f68656c6c6f00002c460000', 'hex'), bytes: new Uint8Array([47, 104, 101, 108, 108, 111, 0, 0, 44, 70, 0, 0]),
expected: { address: '/hello', args: [{ type: 'F', value: false }] }, expected: { address: '/hello', args: [{ type: 'F', value: false }] },
}, },
{
description: 'simple address color arg',
bytes: new Uint8Array([47, 104, 101, 108, 108, 111, 0, 0, 44, 114, 0, 0, 20, 21, 22, 10]),
expected: { address: '/hello', args: [{ type: 'r', value: { r: 20, g: 21, b: 22, a: 10 } }] },
},
{
description: 'simple address nil arg',
bytes: new Uint8Array([47, 104, 101, 108, 108, 111, 0, 0, 44, 78, 0, 0]),
expected: { address: '/hello', args: [{ type: 'N', value: null }] },
},
{
description: 'simple address Inifinitum arg',
bytes: new Uint8Array([47, 104, 101, 108, 108, 111, 0, 0, 44, 73, 0, 0]),
expected: { address: '/hello', args: [{ type: 'I', value: Number.MAX_SAFE_INTEGER }] },
},
{
description: 'simple address int64 arg',
bytes: new Uint8Array([47, 104, 101, 108, 108, 111, 0, 0, 44, 104, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255]),
expected: { address: '/hello', args: [{ type: 'h', value: 281474976710655n }] },
},
{
description: 'simple address float64 arg',
bytes: new Uint8Array([
47, 104, 101, 108, 108, 111, 0, 0, 44, 100, 0, 0, 0x40, 0x29, 0x87, 0xec, 0x82, 0x74, 0xb9, 0xe6,
]),
expected: { address: '/hello', args: [{ type: 'd', value: 12.7654763 }] },
},
{
description: 'simple address array arg',
bytes: new Uint8Array([
47, 104, 101, 108, 108, 111, 0, 0, 44, 91, 100, 105, 93, 0, 0, 0, 0x40, 0x29, 0x87, 0xec, 0x82, 0x74, 0xb9, 0xe6,
0, 0, 3, 232,
]),
expected: {
address: '/hello',
args: [
[
{ type: 'd', value: 12.7654763 },
{ type: 'i', value: 1000 },
],
],
},
},
{
description: 'simple address no type string',
bytes: new Uint8Array([47, 104, 101, 108, 108, 111, 0, 0]),
expected: {
address: '/hello',
args: [],
},
},
{ {
description: 'osc 1.0 spec example 1', description: 'osc 1.0 spec example 1',
bytes: Buffer.from('2f6f7363696c6c61746f722f342f6672657175656e6379002c66000043dc0000', 'hex'), bytes: new Uint8Array([
47, 111, 115, 99, 105, 108, 108, 97, 116, 111, 114, 47, 52, 47, 102, 114, 101, 113, 117, 101, 110, 99, 121, 0, 44,
102, 0, 0, 67, 220, 0, 0,
]),
expected: { address: '/oscillator/4/frequency', args: [{ type: 'f', value: 440 }] }, expected: { address: '/oscillator/4/frequency', args: [{ type: 'f', value: 440 }] },
}, },
{ {
description: 'osc 1.0 spec example 2', description: 'osc 1.0 spec example 2',
bytes: Buffer.from('2f666f6f000000002c69697366660000000003e8ffffffff68656c6c6f0000003f9df3b640b5b22d', 'hex'), bytes: new Uint8Array([
47, 102, 111, 111, 0, 0, 0, 0, 44, 105, 105, 115, 102, 102, 0, 0, 0, 0, 3, 232, 255, 255, 255, 255, 104, 101, 108,
108, 111, 0, 0, 0, 63, 157, 243, 182, 64, 181, 178, 45,
]),
expected: { expected: {
address: '/foo', address: '/foo',
args: [ args: [
@@ -60,65 +117,78 @@ const tests = [
}, },
]; ];
describe('OSC Message Decoding', () => { const badTests = [
tests.forEach((messageTest) => { {
description: 'bad address',
bytes: new Uint8Array([0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x00, 0x00, 0x2c, 0x66, 0x00, 0x00, 0x42, 0x0a, 0x00, 0x00]),
throwsMessage: { name: /^Error$/, message: /must start with/ },
},
{
description: 'bad type string',
bytes: new Uint8Array([0x2f, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x00, 0x00, 0x66, 0x00, 0x00, 0x00, 0x42, 0x0a, 0x00]),
throwsMessage: { name: /^Error$/, message: /type string must start with/ },
},
{
description: 'unknown type',
bytes: new Uint8Array([
0x2f, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x00, 0x00, 0x2c, 0x7a, 0x00, 0x00, 0x42, 0x0a, 0x00, 0x00,
]),
throwsMessage: { name: /^Error$/, message: /unknown/ },
},
{
description: 'float arg missing bytes',
bytes: new Uint8Array([0x2f, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x00, 0x00, 0x2c, 0x66, 0x00, 0x00, 0x42, 0x0a, 0x00]),
throwsMessage: { name: /^Error$/, message: /not enough bytes/ },
},
{
description: 'int arg missing bytes',
bytes: new Uint8Array([0x2f, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x00, 0x00, 0x2c, 0x69, 0x00, 0x00, 0x00, 0x00, 0x00]),
throwsMessage: { name: /^Error$/, message: /not enough bytes/ },
},
{
description: 'blob bytes too small',
bytes: new Uint8Array([
0x2f, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x00, 0x00, 0x2c, 0x62, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x62, 0x6c, 0x6f,
]),
throwsMessage: { name: /^Error$/, message: /not enough bytes/ },
},
{
description: 'float64 missing bytes',
bytes: new Uint8Array([
47, 104, 101, 108, 108, 111, 0, 0, 44, 100, 0, 0, 0x40, 0x29, 0x87, 0xec, 0x82, 0x74, 0xb9,
]),
throwsMessage: { name: /^Error$/, message: /not enough bytes/ },
},
{
description: 'int64 missing bytes',
bytes: new Uint8Array([47, 104, 101, 108, 108, 111, 0, 0, 44, 104, 0, 0, 0, 0, 255, 255, 255, 255, 255]),
throwsMessage: { name: /^Error$/, message: /must be at least/ },
},
{
description: 'color missing bytes',
bytes: new Uint8Array([47, 104, 101, 108, 108, 111, 0, 0, 44, 114, 0, 0, 20, 21, 22]),
throwsMessage: { name: /^Error$/, message: /must be at least/ },
},
];
describe('OSC Message Decoding Pass', () => {
goodTests.forEach((messageTest) => {
it(messageTest.description, () => { it(messageTest.description, () => {
const decoded = osc.messageFromBuffer(messageTest.bytes); const [decoded, remainingBytes] = osc.messageFromBuffer(messageTest.bytes);
equal(remainingBytes.length, 0);
deepEqual(decoded, messageTest.expected); deepEqual(decoded, messageTest.expected);
}); });
}); });
});
it('bad address', () => { describe('OSC Message Decoding Throws', () => {
throws( badTests.forEach((messageTest) => {
() => { it(messageTest.description, () => {
osc.messageFromBuffer(Buffer.from('68656c6c6f00002c660000420a0000', 'hex')); throws(() => {
}, osc.messageFromBuffer(messageTest.bytes);
{ name: /^Error$/, message: /must start with/ } }, messageTest.throwsMessage);
);
}); });
it('bad type string', () => {
throws(
() => {
osc.messageFromBuffer(Buffer.from('2f68656c6c6f000066000000420a00', 'hex'));
},
{ name: /^Error$/, message: /type string must start with/ }
);
});
it('unknown type', () => {
throws(
() => {
osc.messageFromBuffer(Buffer.from('2f68656c6c6f00002c7a0000420a0000', 'hex'));
},
{ name: /^Error$/, message: /unknown/ }
);
});
it('float arg missing bytes', () => {
throws(
() => {
osc.messageFromBuffer(Buffer.from('2f68656c6c6f00002c660000420a00', 'hex'));
},
{ name: /^Error$/, message: /not enough bytes/ }
);
});
it('int arg missing bytes', () => {
throws(
() => {
osc.messageFromBuffer(Buffer.from('2f68656c6c6f00002c690000000000', 'hex'));
},
{ name: /^Error$/, message: /not enough bytes/ }
);
});
it('blob bytes too small', () => {
throws(
() => {
osc.messageFromBuffer(Buffer.from('2f68656c6c6f00002c62000000000004626c6f', 'hex'));
},
{ name: /^Error$/, message: /not enough bytes/ }
);
}); });
}); });
+120 -53
View File
@@ -1,94 +1,161 @@
const { deepEqual, throws } = require('assert'); const { deepEqual, throws } = require('assert');
const { describe, it } = require('node:test'); const { describe, it } = require('node:test');
const osc = require('../dist/index'); const osc = require('../dist/cjs/index');
const tests = [ const goodTests = [
{ {
description: 'simple address no args', description: 'simple address no args',
message: { address: '/hello', args: [] }, message: { address: '/hello', args: [] },
expected: Buffer.from('2f68656c6c6f00002c000000', 'hex'), expected: new Uint8Array([47, 104, 101, 108, 108, 111, 0, 0, 44, 0, 0, 0]),
}, },
{ {
description: 'simple address string arg', description: 'simple address string arg',
message: { address: '/hello', args: [{ type: 's', value: 'arg1' }] }, message: { address: '/hello', args: [{ type: 's', value: 'arg1' }] },
expected: Buffer.from('2f68656c6c6f00002c7300006172673100000000', 'hex'), expected: new Uint8Array([47, 104, 101, 108, 108, 111, 0, 0, 44, 115, 0, 0, 97, 114, 103, 49, 0, 0, 0, 0]),
}, },
{ {
description: 'simple address integer arg', description: 'simple address integer arg',
message: { address: '/hello', args: [{ type: 'i', value: 35 }] }, message: { address: '/hello', args: [{ type: 'i', value: 35 }] },
expected: Buffer.from('2f68656c6c6f00002c69000000000023', 'hex'), expected: new Uint8Array([47, 104, 101, 108, 108, 111, 0, 0, 44, 105, 0, 0, 0, 0, 0, 35]),
}, },
{ {
description: 'simple address float arg', description: 'simple address float arg',
message: { address: '/hello', args: [{ type: 'f', value: 34.5 }] }, message: { address: '/hello', args: [{ type: 'f', value: 34.5 }] },
expected: Buffer.from('2f68656c6c6f00002c660000420a0000', 'hex'), expected: new Uint8Array([47, 104, 101, 108, 108, 111, 0, 0, 44, 102, 0, 0, 66, 10, 0, 0]),
}, },
{ {
description: 'simple address blob arg', description: 'simple address blob arg',
message: { address: '/hello', args: [{ type: 'b', value: Buffer.from('blob') }] }, message: { address: '/hello', args: [{ type: 'b', value: new TextEncoder().encode('blob') }] },
expected: Buffer.from('2f68656c6c6f00002c62000000000004626c6f62', 'hex'), expected: new Uint8Array([47, 104, 101, 108, 108, 111, 0, 0, 44, 98, 0, 0, 0, 0, 0, 4, 98, 108, 111, 98]),
}, },
{ {
description: 'simple address True arg', description: 'simple address True arg',
message: { address: '/hello', args: [{ type: 'T', value: true }] }, message: { address: '/hello', args: [{ type: 'T', value: true }] },
expected: Buffer.from('2f68656c6c6f00002c540000', 'hex'), expected: new Uint8Array([47, 104, 101, 108, 108, 111, 0, 0, 44, 84, 0, 0]),
}, },
{ {
description: 'simple address False arg', description: 'simple address False arg',
message: { address: '/hello', args: [{ type: 'F', value: false }] }, message: { address: '/hello', args: [{ type: 'F', value: false }] },
expected: Buffer.from('2f68656c6c6f00002c460000', 'hex'), expected: new Uint8Array([47, 104, 101, 108, 108, 111, 0, 0, 44, 70, 0, 0]),
},
{
description: 'simple address color arg',
message: { address: '/hello', args: [{ type: 'r', value: { r: 20, g: 21, b: 22, a: 10 } }] },
expected: new Uint8Array([47, 104, 101, 108, 108, 111, 0, 0, 44, 114, 0, 0, 20, 21, 22, 10]),
},
{
description: 'simple address nil arg',
message: { address: '/hello', args: [{ type: 'N', value: null }] },
expected: new Uint8Array([47, 104, 101, 108, 108, 111, 0, 0, 44, 78, 0, 0]),
},
{
description: 'simple address int64 arg',
message: { address: '/hello', args: [{ type: 'h', value: 281474976710655n }] },
expected: new Uint8Array([47, 104, 101, 108, 108, 111, 0, 0, 44, 104, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255]),
},
{
description: 'simple address float64 arg',
message: { address: '/hello', args: [{ type: 'd', value: 12.7654763 }] },
expected: new Uint8Array([
47, 104, 101, 108, 108, 111, 0, 0, 44, 100, 0, 0, 0x40, 0x29, 0x87, 0xec, 0x82, 0x74, 0xb9, 0xe6,
]),
},
{
description: 'simple address array arg',
message: {
address: '/hello',
args: [
[
{ type: 'd', value: 12.7654763 },
{ type: 'i', value: 1000 },
],
],
},
expected: new Uint8Array([
47, 104, 101, 108, 108, 111, 0, 0, 44, 91, 100, 105, 93, 0, 0, 0, 0x40, 0x29, 0x87, 0xec, 0x82, 0x74, 0xb9, 0xe6,
0, 0, 3, 232,
]),
},
{
description: 'osc 1.0 spec example 1',
message: { address: '/oscillator/4/frequency', args: [{ type: 'f', value: 440 }] },
expected: new Uint8Array([
47, 111, 115, 99, 105, 108, 108, 97, 116, 111, 114, 47, 52, 47, 102, 114, 101, 113, 117, 101, 110, 99, 121, 0, 44,
102, 0, 0, 67, 220, 0, 0,
]),
},
{
description: 'osc 1.0 spec example 2',
message: {
address: '/foo',
args: [
{ type: 'i', value: 1000 },
{ type: 'i', value: -1 },
{ type: 's', value: 'hello' },
// thanks IEEE 754
{ type: 'f', value: 1.2339999675750732421875 },
{ type: 'f', value: 5.677999973297119140625 },
],
},
expected: new Uint8Array([
47, 102, 111, 111, 0, 0, 0, 0, 44, 105, 105, 115, 102, 102, 0, 0, 0, 0, 3, 232, 255, 255, 255, 255, 104, 101, 108,
108, 111, 0, 0, 0, 63, 157, 243, 182, 64, 181, 178, 45,
]),
}, },
]; ];
describe('OSC Message Encoding', () => { const badTests = [
tests.forEach((messageTest) => { {
description: 'bad string arg',
message: { address: '/address', args: [{ type: 's', value: 123 }] },
throwsMessage: { name: /^TypeError$/, message: /non string/ },
},
{
description: 'bad integer arg',
message: { address: '/address', args: [{ type: 'i', value: 'hi' }] },
throwsMessage: { name: /^TypeError$/, message: /non number/ },
},
{
description: 'bad float arg',
message: { address: '/address', args: [{ type: 'f', value: 'hi' }] },
throwsMessage: { name: /^TypeError$/, message: /non number/ },
},
{
description: 'bad blob arg',
message: { address: '/address', args: [{ type: 'b', value: 123 }] },
throwsMessage: { name: /^TypeError$/, message: /non Uint8Array/ },
},
{
description: 'unknown arg type',
message: { address: '/address', args: [{ type: 'z', value: 123 }] },
throwsMessage: { name: /^TypeError$/, message: /unknown type z/ },
},
{
description: 'address that does not start with / should throw',
message: { address: 'address', args: [] },
throwsMessage: {
name: /^Error$/,
message: 'osc message must start with a /',
},
},
];
describe('OSC Message Encoding Pass', () => {
goodTests.forEach((messageTest) => {
it(messageTest.description, () => { it(messageTest.description, () => {
const encoded = osc.messageToBuffer(messageTest.message); const encoded = osc.messageToBuffer(messageTest.message);
deepEqual(encoded, messageTest.expected); deepEqual(encoded, messageTest.expected);
}); });
}); });
it('bad string arg', () => { });
throws(
() => {
osc.messageToBuffer({ address: '/address', args: [{ type: 's', value: 123 }] });
},
{ name: /^TypeError$/ }
);
});
it('bad integer arg', () => { describe('OSC Message Encoding Throws', () => {
throws( badTests.forEach((messageTest) => {
() => { it(messageTest.description, () => {
osc.messageToBuffer({ address: '/address', args: [{ type: 'i', value: 'hi' }] }); throws(() => {
}, osc.messageToBuffer(messageTest.message);
{ name: /^TypeError$/ } }, messageTest.throwsMessage);
);
}); });
it('bad float arg', () => {
throws(
() => {
osc.messageToBuffer({ address: '/address', args: [{ type: 'f', value: 'hi' }] });
},
{ name: /^TypeError$/ }
);
});
it('bad blob arg', () => {
throws(
() => {
osc.messageToBuffer({ address: '/address', args: [{ type: 'b', value: 123 }] });
},
{ name: /^TypeError$/ }
);
});
it('unknown arg type', () => {
throws(
() => {
osc.messageToBuffer({ address: '/address', args: [{ type: 'z', value: 123 }] });
},
{ name: /^TypeError$/ }
);
}); });
}); });
+8
View File
@@ -0,0 +1,8 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"module": "CommonJS",
"declaration": false,
"outDir": "./dist/cjs"
}
}
+8
View File
@@ -0,0 +1,8 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"module": "ESNext",
"declaration": false,
"outDir": "./dist/esm"
}
}
+7 -7
View File
@@ -1,12 +1,12 @@
{ {
"compilerOptions": { "compilerOptions": {
"moduleResolution": "Node",
"lib": ["ESNext"],
"target": "ESNext",
"esModuleInterop": true, "esModuleInterop": true,
"module": "commonjs", "isolatedModules": true,
"target": "ES2020", "rootDir": "src/",
"declaration": true, "strict": true
"rootDir": "./src",
"strict": true,
"outDir": "dist"
}, },
"exclude": ["node_modules", "dist", "examples"] "include": ["src/"]
} }
+8
View File
@@ -0,0 +1,8 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"emitDeclarationOnly": true,
"declaration": true,
"outDir": "./dist/types"
}
}