88 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
jwetzell 85cfb3a100 remove esbuild 2024-10-09 19:21:29 -05:00
jwetzell f4202dd5ff fix readosc publish workflow 2024-10-09 19:12:31 -05:00
jwetzell 8e8d96fbb4 add readosc tool 2024-10-09 19:10:08 -05:00
jwetzell b393397972 add format option for makeosc 2024-10-09 19:03:41 -05:00
jwetzell 127c7d781d npm run format:write 2024-10-09 18:48:49 -05:00
jwetzell c404299e3e add makeosc utility 2024-10-09 18:48:40 -05:00
jwetzell e997b784c0 switch sendosc to osc library 2024-10-09 18:46:38 -05:00
jwetzell a5d155ac04 Merge pull request #9 from jwetzell/consolidate-osc-tools
Consolidate osc tools
2024-10-09 17:36:02 -05:00
jwetzell 77a414b33a add readme 2024-10-09 17:34:24 -05:00
jwetzell d5098d718e prettier write 2024-10-09 17:32:13 -05:00
jwetzell 1556867e7c add dist to prettierignore 2024-10-09 17:31:07 -05:00
jwetzell e85c1aa334 add author and repo to root package.json 2024-10-09 17:31:02 -05:00
jwetzell abb9ee60e3 npm pkg fix 2024-10-09 17:30:47 -05:00
jwetzell 7b681be033 bring in sendosc and convert to npm workspaces 2024-10-09 17:28:49 -05:00
jwetzell efce70806f bump version to 0.2.2 2024-10-09 12:34:16 -05:00
jwetzell 030c8b63bf remove debug logging 2024-10-09 12:33:38 -05:00
jwetzell 6f2714dc74 split things up 2024-10-09 12:33:28 -05:00
52 changed files with 5082 additions and 788 deletions
+12
View File
@@ -0,0 +1,12 @@
module.exports = {
env: {
node: true,
commonjs: true,
es2021: true,
},
extends: ['airbnb', 'prettier'],
parserOptions: {
ecmaVersion: 'latest',
},
ignorePatterns: ['**/node_modules', '**/dist'],
};
@@ -1,8 +1,10 @@
name: Check osc-js builds name: Check @jwetzell/osc builds
on: on:
pull_request: pull_request:
branches: branches:
- main - main
paths:
- 'packages/osc/**'
jobs: jobs:
build-webui: build-webui:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@@ -18,6 +20,6 @@ jobs:
cache-dependency-path: 'package-lock.json' cache-dependency-path: 'package-lock.json'
- name: Install Node.js dependencies - name: Install Node.js dependencies
run: npm ci run: npm ci --workspace @jwetzell/osc
- run: npm run build - run: npm run build --workspace @jwetzell/osc
+24
View File
@@ -0,0 +1,24 @@
name: Publish makeosc to npmjs
on:
push:
tags:
- 'makeosc/*'
jobs:
publish:
permissions:
id-token: write
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3
with:
node-version-file: '.nvmrc'
registry-url: 'https://registry.npmjs.org'
cache: 'npm'
cache-dependency-path: './package-lock.json'
- name: Install Node.js dependencies
run: npm ci --workspace=makeosc
- name: Publish to NPM
run: npm publish --provenance --access=public --workspace=makeosc
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
@@ -1,8 +1,8 @@
name: Publish to npmjs name: Publish @jwetzell/osc to npmjs
on: on:
push: push:
tags: tags:
- '*' - 'osc/*'
jobs: jobs:
publish-lib: publish-lib:
permissions: permissions:
@@ -18,9 +18,9 @@ jobs:
cache-dependency-path: 'package-lock.json' cache-dependency-path: 'package-lock.json'
- name: Install Node.js dependencies - name: Install Node.js dependencies
run: npm ci run: npm ci --workspace @jwetzell/osc
- name: Publish to NPM - name: Publish to NPM
run: npm publish --provenance --access=public run: npm publish --provenance --access=public --workspace @jwetzell/osc
env: env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
+24
View File
@@ -0,0 +1,24 @@
name: Publish readosc to npmjs
on:
push:
tags:
- 'readosc/*'
jobs:
publish:
permissions:
id-token: write
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3
with:
node-version-file: '.nvmrc'
registry-url: 'https://registry.npmjs.org'
cache: 'npm'
cache-dependency-path: './package-lock.json'
- name: Install Node.js dependencies
run: npm ci --workspace=readosc
- name: Publish to NPM
run: npm publish --provenance --access=public --workspace=readosc
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
+24
View File
@@ -0,0 +1,24 @@
name: Publish sendosc to npmjs
on:
push:
tags:
- 'sendosc/*'
jobs:
publish:
permissions:
id-token: write
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3
with:
node-version-file: '.nvmrc'
registry-url: 'https://registry.npmjs.org'
cache: 'npm'
cache-dependency-path: './package-lock.json'
- name: Install Node.js dependencies
run: npm ci --workspace=sendosc
- name: Publish to NPM
run: npm publish --provenance --access=public --workspace=sendosc
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
@@ -1,8 +1,10 @@
name: Run tests name: Run @jwetzell/osc tests
on: on:
pull_request: pull_request:
branches: branches:
- main - main
paths:
- 'packages/osc/**'
jobs: jobs:
test: test:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@@ -18,6 +20,6 @@ jobs:
cache-dependency-path: './package-lock.json' cache-dependency-path: './package-lock.json'
- name: Install Node.js dependencies - name: Install Node.js dependencies
run: npm ci run: npm ci --workspace @jwetzell/osc
- run: npm run test - run: npm run test --workspace @jwetzell/osc
-1
View File
@@ -1,2 +1 @@
node_modules node_modules
dist
+1
View File
@@ -0,0 +1 @@
git-tag-version=false
+1
View File
@@ -1,2 +1,3 @@
*.md *.md
*.min.* *.min.*
dist
+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.
+5
View File
@@ -0,0 +1,5 @@
# osc-js
Collection of OSC things written in JS/TS
- [osc encoding/decoding library](./packages/osc/)
- [cli for sending osc messages](./apps/sendosc/)
+1
View File
@@ -0,0 +1 @@
dist
+41
View File
@@ -0,0 +1,41 @@
![npm](https://img.shields.io/npm/v/makeosc)
# makeosc
Simple NodeJS script for generating OSC bytes output to stdout.
## Usage
```
Usage: makeosc [options]
simple util to make osc buffers
Options:
-V, --version output the version number
--address <address> OSC address
--args <args...> osc args (default: [])
--slip slip encode message (default: false)
--types <types...> osc arg types (choices: "s", "i", "f", "b", default: [])
-h, --help display help for command
```
- using npx `npx makeosc@latest --address /hello`
- install using `npm install -g makeosc@latest` and run `makeosc --address /hello`
## Notes
- `--types` option is a space seperate list of type characters that will determine what the corresponding argument type will be set to
- optional
- uses type codes from the OSC spec
- `--args` option is a space-separated list of arguments. If a corresponding type is not found in the `--types` option it will default to string (`s`).
- blobs (`b`) are to be entered as hex string representing the buffer to be sent so the ASCII string `hello` would be `68656c6c6f`
- the default protocol is UDP but can be changed to TCP using the `--protocol` flag
## Examples
- `makeosc --address /test --args 1.0`
- `makeosc --address /this/is/sent/via/tcp`
- `makeosc --address /test/with/types --args 1 2.0 three --types i f`
- note that `types` is not the same length as `args` so the remaining argument (`three`) will default to string
- `makeosc --address /blob --args 68656c6c6f --types b`
+27
View File
@@ -0,0 +1,27 @@
{
"name": "makeosc",
"version": "0.1.0",
"description": "Simple NodeJS utility for generating osc buffers",
"main": "src/main.js",
"bin": {
"makeosc": "src/main.js"
},
"files": [
"src/"
],
"author": {
"name": "Joel Wetzell",
"email": "me@jwetzell.com",
"url": "https://jwetzell.com"
},
"repository": {
"type": "git",
"url": "git+https://github.com/jwetzell/osc-js.git"
},
"license": "MIT",
"dependencies": {
"@jwetzell/osc": "0.2.2",
"commander": "^12.1.0",
"slip": "^1.0.2"
}
}
+52
View File
@@ -0,0 +1,52 @@
#!/usr/bin/env node
const { Option, program } = require('commander');
const slip = require('slip');
const { argToTypedArg } = require('./utils');
const osc = require('@jwetzell/osc');
const packageInfo = require('../package.json');
program.name(packageInfo.name);
program.version(packageInfo.version);
program.description('simple util to make osc buffers');
program.addOption(new Option('--address <address>', 'OSC address').makeOptionMandatory());
program.addOption(new Option('--args <args...>', 'osc args').default([]));
program.addOption(new Option('--slip', 'slip encode message').default(false));
program.addOption(new Option('--types <types...>', 'osc arg types').choices(['s', 'i', 'f', 'b']).default([]));
program.addOption(
new Option('-f --format <format>', 'output format').choices(['bytes', 'json', 'f', 'b']).default('bytes')
);
program.action((options) => {
const { address, args, types, format } = options;
const typedArgs = args?.map((rawArg, index) => {
const argType = types[index] || 's';
return {
type: argType,
value: argToTypedArg(rawArg, argType),
};
});
const oscMsg = {
address,
args: typedArgs,
};
if (format === 'json') {
process.stdout.write(JSON.stringify(oscMsg));
} else if (format === 'bytes') {
let oscMsgBuffer = osc.messageToBuffer(oscMsg);
if (options.slip) {
oscMsgBuffer = slip.encode(oscMsgBuffer);
}
process.stdout.write(oscMsgBuffer);
}
});
program.parse();
process.stdout.on('error', (err) => {
if (err.code === 'EPIPE') {
process.exit(0);
}
});
+31
View File
@@ -0,0 +1,31 @@
const oscTypeConverterMap = {
s: {
fromString: (string) => string,
},
f: {
fromString: (string) => Number.parseFloat(string),
},
i: {
fromString: (string) => Number.parseInt(string, 10),
},
b: {
fromString: (string) => Buffer.from(string, 'hex'),
},
};
function argToTypedArg(rawArg, type = 's') {
const typeConverter = oscTypeConverterMap[type];
if (typeConverter === undefined) {
throw new Error('osc type error: unknown type '.concat(type));
}
if (typeConverter.fromString === undefined) {
throw new Error('osc type error: no string converter for type '.concat(type));
}
return typeConverter.fromString(rawArg);
}
module.exports = {
argToTypedArg,
};
+1
View File
@@ -0,0 +1 @@
dist
+20
View File
@@ -0,0 +1,20 @@
![npm](https://img.shields.io/npm/v/readosc)
# readosc
Simple NodeJS script for parsing OSC bytes from stdin.
## Usage
```
Usage: readosc [options]
simple util to read osc packets from stdin
Options:
-V, --version output the version number
-h, --help display help for command
```
- using npx `npx readosc@latest`
- install using `npm install -g readosc@latest` and run `readosc`
+26
View File
@@ -0,0 +1,26 @@
{
"name": "readosc",
"version": "0.2.0",
"description": "Simple NodeJS utility for parsing osc buffers from stdin",
"main": "src/main.js",
"bin": {
"readosc": "src/main.js"
},
"files": [
"src/"
],
"author": {
"name": "Joel Wetzell",
"email": "me@jwetzell.com",
"url": "https://jwetzell.com"
},
"repository": {
"type": "git",
"url": "git+https://github.com/jwetzell/osc-js.git"
},
"license": "MIT",
"dependencies": {
"@jwetzell/osc": "1.0.0",
"commander": "^12.1.0"
}
}
+28
View File
@@ -0,0 +1,28 @@
#!/usr/bin/env node
const { program } = require('commander');
const osc = require('@jwetzell/osc');
const packageInfo = require('../package.json');
program.name(packageInfo.name);
program.version(packageInfo.version);
program.description('simple util to read osc packets from stdin');
program.action(() => {
process.stdin.on('data', (data) => {
try {
let remainingBytes = data;
while (remainingBytes.length > 0) {
try {
const [message, bytesAfterMessage] = osc.messageFromBuffer(remainingBytes);
remainingBytes = bytesAfterMessage;
console.log(JSON.stringify(message));
} catch (error) {
console.error({ error: error.toString() });
}
}
} catch (error) {
console.error({ error: error.toString() });
}
});
});
program.parse();
+1
View File
@@ -0,0 +1 @@
dist
+44
View File
@@ -0,0 +1,44 @@
![npm](https://img.shields.io/npm/v/sendosc)
# sendosc
Simple NodeJS script for sending OSC via TCP or UDP originally inspired by this [c++ utility](https://github.com/yoggy/sendosc) of the same name.
## Usage
```
Usage: sendosc [options]
simple util to sendosc
Options:
-V, --version output the version number
--protocol <protocol> Network protocol (choices: "tcp", "udp", default: "udp")
--host <host> the host to send osc to
--port <port> the port to send osc to
--address <address> OSC address
--args <args...> osc args (default: [])
--slip slip encode message (default: false)
--types <types...> osc arg types (choices: "s", "i", "f", "b", default: [])
-h, --help display help for command
```
- using npx `npx sendosc@latest --host 127.0.0.1 --port 9999 --address /hello`
- install using `npm install -g sendosc@latest` and run `sendosc --host 127.0.0.1 --port 9999 --address /hello`
## Notes
- `--types` option is a space seperate list of type characters that will determine what the corresponding argument type will be set to
- optional
- uses type codes from the OSC spec
- `--args` option is a space-separated list of arguments. If a corresponding type is not found in the `--types` option it will default to string (`s`).
- blobs (`b`) are to be entered as hex string representing the buffer to be sent so the ASCII string `hello` would be `68656c6c6f`
- the default protocol is UDP but can be changed to TCP using the `--protocol` flag
## Examples
- `sendosc --host 127.0.0.1 --port 8000 --address /test --args 1.0`
- `sendosc --protocol tcp --host 127.0.0.1 --port 8000 --address /this/is/sent/via/tcp`
- `sendosc --host 127.0.0.1 --port 8000 --address /test/with/types --args 1 2.0 three --types i f`
- note that `types` is not the same length as `args` so the remaining argument (`three`) will default to string
- `sendosc --host 127.0.0.1 --port 8000 --address /blob --args 68656c6c6f --types b`
+27
View File
@@ -0,0 +1,27 @@
{
"name": "sendosc",
"version": "1.0.1",
"description": "Simple NodeJS utility for sending osc",
"main": "src/main.js",
"bin": {
"sendosc": "src/main.js"
},
"files": [
"src/"
],
"author": {
"name": "Joel Wetzell",
"email": "me@jwetzell.com",
"url": "https://jwetzell.com"
},
"repository": {
"type": "git",
"url": "git+https://github.com/jwetzell/osc-js.git"
},
"license": "MIT",
"dependencies": {
"@jwetzell/osc": "0.2.2",
"commander": "^12.1.0",
"slip": "^1.0.2"
}
}
+62
View File
@@ -0,0 +1,62 @@
#!/usr/bin/env node
const { Option, program } = require('commander');
const dgram = require('dgram');
const net = require('net');
const slip = require('slip');
const osc = require('@jwetzell/osc');
const { argToTypedArg } = require('./utils');
const packageInfo = require('../package.json');
program.name(packageInfo.name);
program.version(packageInfo.version);
program.description('simple util to sendosc');
program.addOption(new Option('--protocol <protocol>', 'Network protocol').choices(['tcp', 'udp']).default('udp'));
program.addOption(new Option('--host <host>', 'the host to send osc to').makeOptionMandatory());
program.addOption(new Option('--port <port>', 'the port to send osc to').makeOptionMandatory());
program.addOption(new Option('--address <address>', 'OSC address').makeOptionMandatory());
program.addOption(new Option('--args <args...>', 'osc args').default([]));
program.addOption(new Option('--slip', 'slip encode message').default(false));
program.addOption(new Option('--types <types...>', 'osc arg types').choices(['s', 'i', 'f', 'b']).default([]));
program.action((options) => {
const { host, port, address, args, types } = options;
const typedArgs = args?.map((rawArg, index) => {
const argType = types[index] || 's';
return {
type: argType,
value: argToTypedArg(rawArg, argType),
};
});
let oscMsgBuffer = osc.messageToBuffer({
address,
args: typedArgs,
});
if (options.slip) {
oscMsgBuffer = slip.encode(oscMsgBuffer);
}
if (options.protocol === 'tcp') {
const client = net.Socket();
client.on('error', (error) => {
console.error(error);
});
client.connect(port, host, () => {
client.write(oscMsgBuffer, () => {
client.destroy();
});
});
} else if (options.protocol === 'udp') {
const client = dgram.createSocket('udp4');
client.send(oscMsgBuffer, port, host, (error) => {
if (error) {
console.error(error);
}
client.close();
});
}
});
program.parse();
+31
View File
@@ -0,0 +1,31 @@
const oscTypeConverterMap = {
s: {
fromString: (string) => string,
},
f: {
fromString: (string) => Number.parseFloat(string),
},
i: {
fromString: (string) => Number.parseInt(string, 10),
},
b: {
fromString: (string) => Buffer.from(string, 'hex'),
},
};
function argToTypedArg(rawArg, type = 's') {
const typeConverter = oscTypeConverterMap[type];
if (typeConverter === undefined) {
throw new Error('osc type error: unknown type '.concat(type));
}
if (typeConverter.fromString === undefined) {
throw new Error('osc type error: no string converter for type '.concat(type));
}
return typeConverter.fromString(rawArg);
}
module.exports = {
argToTypedArg,
};
+3231 -130
View File
File diff suppressed because it is too large Load Diff
+19 -20
View File
@@ -1,30 +1,29 @@
{ {
"name": "@jwetzell/osc", "name": "osc",
"version": "0.2.1", "version": "0.0.0",
"description": "", "private": true,
"main": "dist/index.js",
"types": "dist/index.d.ts",
"scripts": {
"prebuild": "rimraf dist",
"build": "tsc",
"prepublishOnly": "npm run build",
"pretest": "npm run build",
"test": "node --test --experimental-test-coverage"
},
"files": [
"dist"
],
"author": { "author": {
"name": "Joel Wetzell", "name": "Joel Wetzell",
"email": "me@jwetzell.com", "email": "me@jwetzell.com",
"url": "https://jwetzell.com" "url": "https://jwetzell.com"
}, },
"repository": "https://github.com/jwetzell/osc-js", "repository": {
"license": "ISC", "type": "git",
"url": "git+https://github.com/jwetzell/osc-js.git"
},
"scripts": {
"lint:check": "eslint ./",
"format:check": "prettier ./ --check",
"format:write": "prettier ./ --write"
},
"workspaces": [
"apps/*",
"packages/*"
],
"devDependencies": { "devDependencies": {
"@types/node": "22.7.4",
"prettier": "3.3.3", "prettier": "3.3.3",
"rimraf": "6.0.1", "eslint": "^8.48.0",
"typescript": "5.6.2" "eslint-config-airbnb": "^19.0.4",
"eslint-config-prettier": "^9.0.0"
} }
} }
+1
View File
@@ -0,0 +1 @@
dist
+43
View File
@@ -0,0 +1,43 @@
{
"name": "@jwetzell/osc",
"version": "1.6.0",
"description": "",
"main": "./dist/cjs/index.js",
"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": {
"prebuild": "rimraf dist",
"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",
"pretest": "npm run build",
"test": "node --test --experimental-test-coverage"
},
"files": [
"dist"
],
"author": {
"name": "Joel Wetzell",
"email": "me@jwetzell.com",
"url": "https://jwetzell.com"
},
"repository": {
"type": "git",
"url": "git+https://github.com/jwetzell/osc-js.git"
},
"license": "MIT",
"devDependencies": {
"@types/node": "22.7.7",
"rimraf": "6.0.1",
"typescript": "5.6.3"
}
}
+117
View File
@@ -0,0 +1,117 @@
import { messageFromBuffer, messageToBuffer } from './message';
import { OSCBundle } from './models';
import { oscTypeConverterMap } from './osc-types';
export function bundleFromBuffer(bytes: Uint8Array): [OSCBundle | undefined, Uint8Array | undefined] {
if (bytes.length < 20) {
throw new Error('bundle has to be at least 20 bytes');
}
if (new TextDecoder().decode(bytes.subarray(0, 7)) !== '#bundle') {
throw new Error('bundle must start with #bundle');
}
const [bundleHeader, bytesAfterHeader] = oscTypeConverterMap.s.fromBuffer(bytes);
let [timeTag, bytesAfterTimeTag] = oscTypeConverterMap.t.fromBuffer(bytesAfterHeader);
if (!Array.isArray(timeTag)) {
throw new Error('problem getting bundle time tag');
}
const bundleContents = [];
let endOfBundle = false;
// let [contentSize, remainingBytes] = oscTypeConverterMap.i.fromBuffer(bytesAfterTimeTag);
let remainingBytes = bytesAfterTimeTag;
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) {
throw new Error('bundle does not contain enough data');
}
const bundleContentBytes = bytesAfterContentSize.subarray(0, contentSize);
if (bundleContentBytes[0] === 35) {
// # character indicating contents is a bundle
const [content, bytesAfterContent] = bundleFromBuffer(bundleContentBytes);
if (content) {
bundleContents.push(content);
}
} else if (bundleContentBytes[0] === 47) {
const [content, bytesAfterContent] = messageFromBuffer(bundleContentBytes);
if (content && content !== undefined) {
bundleContents.push(content);
}
} else {
throw new Error('bundle contents does not look like a OSC message or bundle');
}
remainingBytes = bytesAfterContentSize.subarray(contentSize);
if (remainingBytes.length === 0) {
endOfBundle = true;
}
}
return [
{
timeTag,
contents: bundleContents,
},
remainingBytes,
];
}
export function bundleToBuffer(bundle: OSCBundle): Uint8Array {
const headerBuffer = oscTypeConverterMap.s.toBuffer('#bundle');
if (headerBuffer === undefined) {
throw new Error('problem encoding buffer header');
}
const timeTagBuffer = oscTypeConverterMap.t.toBuffer(bundle.timeTag);
if (timeTagBuffer === undefined) {
throw new Error('problem encoding buffer time tag');
}
const contentsBuffers: Uint8Array[] = [];
let contentsBuffersTotalLength = 0;
bundle.contents.forEach((bundleContent) => {
if ('address' in bundleContent) {
const contentBuffer = messageToBuffer(bundleContent);
const contentSizeBuffer = oscTypeConverterMap.i.toBuffer(contentBuffer.length);
if (contentBuffer && contentSizeBuffer) {
const buffer = new Uint8Array(contentSizeBuffer.length + contentBuffer.length);
buffer.set(contentSizeBuffer, 0);
buffer.set(contentBuffer, contentSizeBuffer.length);
contentsBuffers.push(buffer);
contentsBuffersTotalLength += buffer.length;
}
}
});
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;
}
+17
View File
@@ -0,0 +1,17 @@
import { bundleFromBuffer } from './bundle';
import { messageFromBuffer } from './message';
import { OSCBundle, OSCMessage } from './models';
export * from './models';
export * from './message';
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');
}
}
+148
View File
@@ -0,0 +1,148 @@
import { OSCMessage, OSCArg, OSCType } from './models';
import { oscTypeConverterMap } from './osc-types';
function argsToBuffer(args: OSCArg[]) {
const argBuffers: Uint8Array[] = [];
let argBuffersTotalLength = 0;
for (let index = 0; index < args.length; index += 1) {
const arg = args[index];
const typeConverter = oscTypeConverterMap[arg.type];
if (typeConverter === undefined) {
throw new TypeError('unknown type '.concat(arg.type));
}
const buffer = typeConverter.toBuffer(arg.value);
if (buffer !== undefined) {
argBuffers.push(buffer);
argBuffersTotalLength += buffer.length;
}
}
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): Uint8Array {
if (!message.address.startsWith('/')) {
throw new Error('osc message must start with a /');
}
const addressBuffer = oscTypeConverterMap.s.toBuffer(message.address);
if (addressBuffer === undefined) {
throw new Error('problem encoding address');
}
let 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) {
throw new Error('problem encoding types');
}
const argsBuffer = argsToBuffer(flatOSCArgs);
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: Uint8Array): [OSCMessage | undefined, Uint8Array | undefined] {
if (bytes[0] !== 47) {
throw new Error('osc message must start with a /');
}
const oscArgs: (OSCArg | OSCArg[])[] = [];
const [address, bytesAfterAddress] = oscTypeConverterMap.s.fromBuffer(bytes);
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);
if (typeof typeString === 'string') {
if (!typeString.startsWith(',')) {
throw new Error('osc type string must start with a ,');
}
let argsBuffer = bytesAfterType;
let oscArrayArg: OSCArg[] = [];
let insideArgArray = false;
for (let index = 1; index < typeString.length; index++) {
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];
if (oscTypeConverter === undefined) {
throw new Error('unknown OSC type');
}
const [value, remainingBytes] = oscTypeConverter.fromBuffer(argsBuffer);
if (value !== undefined) {
const arg: OSCArg = {
type: argType,
value: value,
};
if (insideArgArray) {
oscArrayArg.push(arg);
} else {
oscArgs.push(arg);
}
}
argsBuffer = remainingBytes;
}
return [
{
address,
args: oscArgs,
},
argsBuffer,
];
}
}
return [undefined, undefined];
}
+33
View File
@@ -0,0 +1,33 @@
export type OSCType = 's' | 'i' | 'f' | 'b' | 'T' | 'F' | 't' | 'r' | 'N' | '[' | ']';
export type OSCArg = {
type: OSCType;
value: string | number | Uint8Array | boolean | OSCTimeTag | OSCColor | bigint | null;
};
export type OSCTimeTag = [number, number];
export type OSCColor = {
r: number;
g: number;
b: number;
a: number;
};
export type OSCBundle = {
timeTag: OSCTimeTag;
contents: (OSCBundle | OSCMessage)[];
};
export type OSCMessage = {
address: string;
args: (OSCArg | OSCArg[])[];
};
export type OSCTypeConverter = {
toBuffer: (
value: string | number | Uint8Array | boolean | OSCTimeTag | OSCColor | null | bigint
) => Uint8Array | undefined;
fromBuffer: (
buffer: Uint8Array
) => [string | number | Uint8Array | boolean | OSCTimeTag | OSCColor | null | bigint | undefined, Uint8Array];
};
+288
View File
@@ -0,0 +1,288 @@
import { OSCTypeConverter, OSCTimeTag } from './models';
export const oscTypeConverterMap: { [key: string]: OSCTypeConverter } = {
s: {
toBuffer: (string) => {
if (typeof string === 'string') {
let oscString = `${string}\u0000`;
const padSize = 4 - (oscString.length % 4);
if (padSize < 4) {
oscString = oscString.padEnd(oscString.length + padSize, '\u0000');
}
return new TextEncoder().encode(oscString);
}
throw new TypeError('osc type s toBuffer called with non string value');
},
fromBuffer: (bytes) => {
let stringEnd = 0;
let stringPaddingEnd = 0;
for (let index = 0; index < bytes.length; index++) {
if (bytes[index] === 0) {
stringEnd = index;
stringPaddingEnd = index + 1;
const stringPadding = 4 - ((stringEnd + 1) % 4);
if (stringPadding < 4) {
stringPaddingEnd += stringPadding;
}
break;
}
}
return [new TextDecoder().decode(bytes.subarray(0, stringEnd)), bytes.subarray(stringPaddingEnd)];
},
},
f: {
toBuffer: (number) => {
if (typeof number === 'number') {
const view = new DataView(new ArrayBuffer(4));
view.setFloat32(0, number);
return new Uint8Array(view.buffer);
}
throw new TypeError('osc type f toBuffer called with non number value');
},
fromBuffer: (buffer) => {
if (buffer.length < 4) {
throw new Error('not enough bytes to read a osc float');
}
const view = new DataView(new ArrayBuffer(4));
buffer.slice(0, 4).forEach((byte, index) => {
view.setUint8(index, byte);
});
const value = view.getFloat32(0);
return [value, buffer.subarray(4)];
},
},
i: {
toBuffer: (number) => {
if (typeof number === 'number') {
const view = new DataView(new ArrayBuffer(4));
view.setInt32(0, number);
return new Uint8Array(view.buffer);
}
throw new TypeError('osc type i toBuffer called with non number value');
},
fromBuffer: (buffer) => {
if (buffer.length < 4) {
throw new Error('not enough bytes to read a osc integer');
}
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)];
},
},
b: {
toBuffer: (data) => {
if (data instanceof Uint8Array) {
const sizeBuffer = oscTypeConverterMap.i.toBuffer(data.length);
if (sizeBuffer) {
let padSize = 4 - (data.length % 4);
if (padSize === 4) {
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 Uint8Array value');
},
fromBuffer: (buffer) => {
const [blobLength, blobBytes] = oscTypeConverterMap.i.fromBuffer(buffer);
if (typeof blobLength === 'number') {
if (blobBytes.length < blobLength) {
throw new Error('not enough bytes left for blob length specified');
}
const value = blobBytes.subarray(0, blobLength);
const blobPadding = 4 - (blobLength % 4);
const blobEnd = blobPadding < 4 ? blobLength + blobPadding : blobLength;
return [value, blobBytes.subarray(blobEnd)];
} else {
throw new Error('unexpected value for blob length');
}
},
},
T: {
toBuffer: () => {
return new Uint8Array(0);
},
fromBuffer: (buffer) => {
return [true, buffer];
},
},
F: {
toBuffer: () => {
return new Uint8Array(0);
},
fromBuffer: (buffer) => {
return [false, buffer];
},
},
t: {
toBuffer: (timetag) => {
if (!Array.isArray(timetag)) {
throw new TypeError('osc type t toBuffer called with non array value');
}
if (timetag.length != 2) {
throw new TypeError('osc type t array should have exactly 2 elements');
}
const seconds = timetag[0];
if (typeof seconds !== 'number') {
throw new TypeError('osc type t seconds part should be a number');
}
const fractional = timetag[1];
if (typeof fractional !== 'number') {
throw new TypeError('osc type t fractional part should be a number');
}
const secondsBuffer = oscTypeConverterMap.i.toBuffer(seconds);
const fractionalBuffer = oscTypeConverterMap.i.toBuffer(fractional);
if (secondsBuffer && fractionalBuffer) {
const buffer = new Uint8Array(8);
buffer.set(secondsBuffer);
buffer.set(fractionalBuffer, 4);
return buffer;
}
},
fromBuffer: (buffer) => {
if (buffer.length < 8) {
throw new Error('osc time tag must be at least 8 bytes');
}
const [seconds, bytesAfterSeconds] = oscTypeConverterMap.i.fromBuffer(buffer);
const [fractional, bytesAfterFractional] = oscTypeConverterMap.i.fromBuffer(bytesAfterSeconds);
if (typeof seconds === 'number' && typeof fractional === 'number') {
const timeTag: OSCTimeTag = [seconds, fractional];
return [timeTag, bytesAfterFractional];
}
return [undefined, bytesAfterFractional];
},
},
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)];
},
},
};
+200
View File
@@ -0,0 +1,200 @@
const { deepEqual, equal, throws } = require('assert');
const { describe, it } = require('node:test');
const osc = require('../dist/cjs/index');
const goodTests = [
{
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: {
timeTag: [32, 0],
contents: [{ address: '/oscillator/4/frequency', args: [{ type: 'f', value: 440 }] }],
},
},
{
description: 'simple contents single bundle',
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, 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 }],
},
],
},
},
];
const badTests = [
{
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, () => {
const [encoded, remainingBytes] = osc.bundleFromBuffer(bundleTest.bytes);
equal(remainingBytes.length, 0);
deepEqual(encoded, bundleTest.expected);
});
});
});
describe('OSC Bundle Decoding Throws', () => {
badTests.forEach((bundleTest) => {
it(bundleTest.description, () => {
throws(() => {
osc.bundleFromBuffer(bundleTest.bytes);
}, bundleTest.throwsMessage);
});
});
});
+32
View File
@@ -0,0 +1,32 @@
const { deepEqual } = require('assert');
const { describe, it } = require('node:test');
const osc = require('../dist/cjs/index');
const tests = [
{
description: 'simple contents single message',
bundle: {
timeTag: [32, 0],
contents: [{ address: '/oscillator/4/frequency', args: [{ type: 'f', value: 440 }] }],
},
expected: 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,
]),
]),
},
];
describe('OSC Bundle Encoding', () => {
tests.forEach((bundleTest) => {
it(bundleTest.description, () => {
const encoded = osc.bundleToBuffer(bundleTest.bundle);
deepEqual(encoded, bundleTest.expected);
});
});
});
+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)
});
});
});
+194
View File
@@ -0,0 +1,194 @@
const { deepEqual, throws, equal } = require('assert');
const { describe, it } = require('node:test');
const osc = require('../dist/cjs/index');
const goodTests = [
{
description: 'simple address no args',
bytes: new Uint8Array([47, 104, 101, 108, 108, 111, 0, 0, 44, 0, 0, 0]),
expected: { address: '/hello', args: [] },
},
{
description: 'simple address string arg',
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' }] },
},
{
description: 'simple address integer arg',
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 }] },
},
{
description: 'simple address float arg',
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 }] },
},
{
description: 'simple address blob arg',
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: new TextEncoder().encode('blob') }] },
},
{
description: 'simple address True arg',
bytes: new Uint8Array([47, 104, 101, 108, 108, 111, 0, 0, 44, 84, 0, 0]),
expected: { address: '/hello', args: [{ type: 'T', value: true }] },
},
{
description: 'simple address False arg',
bytes: new Uint8Array([47, 104, 101, 108, 108, 111, 0, 0, 44, 70, 0, 0]),
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',
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 }] },
},
{
description: 'osc 1.0 spec example 2',
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: {
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 },
],
},
},
];
const badTests = [
{
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, () => {
const [decoded, remainingBytes] = osc.messageFromBuffer(messageTest.bytes);
equal(remainingBytes.length, 0);
deepEqual(decoded, messageTest.expected);
});
});
});
describe('OSC Message Decoding Throws', () => {
badTests.forEach((messageTest) => {
it(messageTest.description, () => {
throws(() => {
osc.messageFromBuffer(messageTest.bytes);
}, messageTest.throwsMessage);
});
});
});
+161
View File
@@ -0,0 +1,161 @@
const { deepEqual, throws } = require('assert');
const { describe, it } = require('node:test');
const osc = require('../dist/cjs/index');
const goodTests = [
{
description: 'simple address no args',
message: { address: '/hello', args: [] },
expected: new Uint8Array([47, 104, 101, 108, 108, 111, 0, 0, 44, 0, 0, 0]),
},
{
description: 'simple address string arg',
message: { address: '/hello', args: [{ type: 's', value: 'arg1' }] },
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',
message: { address: '/hello', args: [{ type: 'i', value: 35 }] },
expected: new Uint8Array([47, 104, 101, 108, 108, 111, 0, 0, 44, 105, 0, 0, 0, 0, 0, 35]),
},
{
description: 'simple address float arg',
message: { address: '/hello', args: [{ type: 'f', value: 34.5 }] },
expected: new Uint8Array([47, 104, 101, 108, 108, 111, 0, 0, 44, 102, 0, 0, 66, 10, 0, 0]),
},
{
description: 'simple address blob arg',
message: { address: '/hello', args: [{ type: 'b', value: new TextEncoder().encode('blob') }] },
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',
message: { address: '/hello', args: [{ type: 'T', value: true }] },
expected: new Uint8Array([47, 104, 101, 108, 108, 111, 0, 0, 44, 84, 0, 0]),
},
{
description: 'simple address False arg',
message: { address: '/hello', args: [{ type: 'F', value: false }] },
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,
]),
},
];
const badTests = [
{
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, () => {
const encoded = osc.messageToBuffer(messageTest.message);
deepEqual(encoded, messageTest.expected);
});
});
});
describe('OSC Message Encoding Throws', () => {
badTests.forEach((messageTest) => {
it(messageTest.description, () => {
throws(() => {
osc.messageToBuffer(messageTest.message);
}, messageTest.throwsMessage);
});
});
});
+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"
}
}
+12
View File
@@ -0,0 +1,12 @@
{
"compilerOptions": {
"moduleResolution": "Node",
"lib": ["ESNext"],
"target": "ESNext",
"esModuleInterop": true,
"isolatedModules": true,
"rootDir": "src/",
"strict": true
},
"include": ["src/"]
}
+8
View File
@@ -0,0 +1,8 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"emitDeclarationOnly": true,
"declaration": true,
"outDir": "./dist/types"
}
}
-2
View File
@@ -1,2 +0,0 @@
export * from './osc';
export * from './models';
-22
View File
@@ -1,22 +0,0 @@
export type OSCType = 's' | 'i' | 'f' | 'b' | 'T' | 'F' | 't';
export type OSCArg = {
type: OSCType;
value: string | number | Buffer | boolean | OSCTimeTag;
};
export type OSCTimeTag = [number, number];
export type OSCBundle = {
timeTag: OSCTimeTag;
contents: (OSCBundle | OSCMessage)[];
};
export type OSCMessage = {
address: string;
args: OSCArg[];
};
export type OSCTypeConverter = {
toBuffer: (value: string | number | Buffer | boolean | OSCTimeTag) => Buffer | undefined;
fromBuffer: (buffer: Buffer) => [string | number | Buffer | boolean | OSCTimeTag | undefined, Buffer];
};
-315
View File
@@ -1,315 +0,0 @@
import { OSCType, OSCArg, OSCMessage, OSCTypeConverter, OSCBundle, OSCTimeTag } from './models';
const oscTypeConverterMap: { [key: string]: OSCTypeConverter } = {
s: {
toBuffer: (string) => {
if (typeof string === 'string') {
let oscString = `${string}\u0000`;
const padSize = 4 - (oscString.length % 4);
if (padSize < 4) {
oscString = oscString.padEnd(oscString.length + padSize, '\u0000');
}
return Buffer.from(oscString, 'ascii');
}
throw new TypeError('osc type s toBuffer called with non string value');
},
fromBuffer: (bytes: Buffer) => {
let stringEnd = 0;
let stringPaddingEnd = 0;
for (let index = 0; index < bytes.length; index++) {
if (bytes[index] === 0) {
stringEnd = index;
stringPaddingEnd = index + 1;
const stringPadding = 4 - ((stringEnd + 1) % 4);
if (stringPadding < 4) {
stringPaddingEnd += stringPadding;
}
break;
}
}
return [bytes.toString('ascii', 0, stringEnd), bytes.subarray(stringPaddingEnd)];
},
},
f: {
toBuffer: (number) => {
if (typeof number === 'number') {
const buffer = Buffer.alloc(4);
buffer.writeFloatBE(number);
return buffer;
}
throw new TypeError('osc type f toBuffer called with non number value');
},
fromBuffer: (buffer) => {
if (buffer.length < 4) {
throw new Error('not enough bytes to read a osc float');
}
const value = buffer.readFloatBE();
return [value, buffer.subarray(4)];
},
},
i: {
toBuffer: (number) => {
if (typeof number === 'number') {
const buffer = Buffer.alloc(4);
buffer.writeInt32BE(number);
return buffer;
}
throw new TypeError('osc type i toBuffer called with non number value');
},
fromBuffer: (buffer) => {
if (buffer.length < 4) {
throw new Error('not enough bytes to read a osc integer');
}
const value = buffer.readInt32BE();
return [value, buffer.subarray(4)];
},
},
b: {
toBuffer: (data) => {
if (Buffer.isBuffer(data)) {
const sizeBuffer = oscTypeConverterMap.i.toBuffer(data.length);
if (sizeBuffer) {
const padSize = 4 - (data.length % 4);
const padBuffer = padSize < 4 ? Buffer.from(Array(padSize).fill(0)) : Buffer.from([]);
return Buffer.concat([sizeBuffer, data, padBuffer]);
}
}
throw new TypeError('osc type b toBuffer called with non Buffer value');
},
fromBuffer: (buffer) => {
const [blobLength, blobBytes] = oscTypeConverterMap.i.fromBuffer(buffer);
if (typeof blobLength === 'number') {
if (blobBytes.length < blobLength) {
throw new Error('not enough bytes left for blob length specified');
}
const value = blobBytes.subarray(0, blobLength);
const blobPadding = 4 - (blobLength % 4);
const blobEnd = blobPadding < 4 ? blobLength + blobPadding : blobLength;
return [value, blobBytes.subarray(blobEnd)];
} else {
throw new Error('unexpected value for blob length');
}
},
},
T: {
toBuffer: () => {
return Buffer.alloc(0);
},
fromBuffer: (buffer) => {
return [true, buffer];
},
},
F: {
toBuffer: () => {
return Buffer.alloc(0);
},
fromBuffer: (buffer) => {
return [false, buffer];
},
},
t: {
toBuffer: (timetag) => {
if (!Array.isArray(timetag)) {
throw new TypeError('osc type t toBuffer called with non array value');
}
if (timetag.length != 2) {
throw new TypeError('osc type t array should have exactly 2 elements');
}
const seconds = timetag[0];
if (typeof seconds !== 'number') {
throw new TypeError('osc type t seconds part should be a number');
}
const fractional = timetag[1];
if (typeof fractional !== 'number') {
throw new TypeError('osc type t fractional part should be a number');
}
const secondsBuffer = oscTypeConverterMap.i.toBuffer(seconds);
const fractionalBuffer = oscTypeConverterMap.i.toBuffer(fractional);
if (secondsBuffer && fractionalBuffer) {
return Buffer.concat([secondsBuffer, fractionalBuffer]);
}
},
fromBuffer: (buffer) => {
if (buffer.length < 8) {
throw new Error('osc time tag must be greater than 8 bytes');
}
const [seconds, bytesAfterSeconds] = oscTypeConverterMap.i.fromBuffer(buffer);
const [fractional, bytesAfterFractional] = oscTypeConverterMap.i.fromBuffer(bytesAfterSeconds);
if (typeof seconds === 'number' && typeof fractional === 'number') {
const timeTag: OSCTimeTag = [seconds, fractional];
return [timeTag, bytesAfterFractional];
}
return [undefined, bytesAfterFractional];
},
},
};
function argsToBuffer(args: OSCArg[]) {
const argBuffers: Buffer[] = [];
for (let index = 0; index < args.length; index += 1) {
const arg = args[index];
const typeConverter = oscTypeConverterMap[arg.type];
if (typeConverter === undefined) {
throw new TypeError('unknown type '.concat(arg.type));
}
const buffer = typeConverter.toBuffer(arg.value);
if (buffer !== undefined) {
argBuffers.push(buffer);
}
}
return Buffer.concat(argBuffers);
}
export function messageToBuffer(message: OSCMessage): Buffer {
const addressBuffer = oscTypeConverterMap.s.toBuffer(message.address);
if (addressBuffer === undefined) {
throw new Error('problem encoding address');
}
const typeString = message.args.map((arg) => arg.type).join('');
const typesBuffer = oscTypeConverterMap.s.toBuffer(`,${typeString}`);
if (typesBuffer === undefined) {
throw new Error('problem encoding types');
}
const argsBuffer = argsToBuffer(message.args);
return Buffer.concat([addressBuffer, typesBuffer, argsBuffer]);
}
export function messageFromBuffer(bytes: Buffer): OSCMessage | undefined {
if (bytes[0] !== 47) {
throw new Error('osc message must start with a /');
}
const oscArgs: OSCArg[] = [];
const [address, bytesAfterAddress] = oscTypeConverterMap.s.fromBuffer(bytes);
if (typeof address === 'string') {
let [typeString, bytesAfterType] = oscTypeConverterMap.s.fromBuffer(bytesAfterAddress);
if (typeof typeString === 'string') {
if (!typeString.startsWith(',')) {
throw new Error('osc type string must start with a ,');
}
let argsBuffer = bytesAfterType;
for (let index = 1; index < typeString.length; index++) {
const argType = typeString.charAt(index) as OSCType;
const oscTypeConverter = oscTypeConverterMap[argType];
if (oscTypeConverter === undefined) {
throw new Error('unknown OSC type');
}
const [value, remainingBytes] = oscTypeConverter.fromBuffer(argsBuffer);
if (value !== undefined) {
const arg: OSCArg = {
type: argType,
value: value,
};
oscArgs.push(arg);
}
argsBuffer = remainingBytes;
}
return {
address,
args: oscArgs,
};
}
}
}
export function bundleFromBuffer(bytes: Buffer): OSCBundle | undefined {
if (bytes.length < 8) {
throw new Error('bundle has to be at least 20 bytes');
}
if (bytes.subarray(0, 7).toString('ascii') !== '#bundle') {
throw new Error('bundle must start with #bundle');
}
const [bundleHeader, bytesAfterHeader] = oscTypeConverterMap.s.fromBuffer(bytes);
let [timeTag, bytesAfterTimeTag] = oscTypeConverterMap.t.fromBuffer(bytesAfterHeader);
if (!Array.isArray(timeTag)) {
throw new Error('problem getting bundle time tag');
}
const bundleContents = [];
let endOfBundle = false;
let [contentSize, remainingBytes] = oscTypeConverterMap.i.fromBuffer(bytesAfterTimeTag);
if (typeof contentSize === 'number') {
while (!endOfBundle) {
if (remainingBytes.length < contentSize) {
throw new Error('bundle does not contain enough data');
}
const bundleContentBytes = remainingBytes.subarray(0, contentSize);
console.log(bundleContentBytes);
if (bundleContentBytes[0] === 35) {
// # character indicating contents is a bundle
const content = bundleFromBuffer(bundleContentBytes);
if (content) {
bundleContents.push(content);
}
} else if (bundleContentBytes[0] === 47) {
const content = messageFromBuffer(bundleContentBytes);
if (content) {
bundleContents.push(content);
}
} else {
throw new Error('bundle contents does not look like a OSC message or bundle');
}
remainingBytes = remainingBytes.subarray(contentSize);
if (remainingBytes.length === 0) {
endOfBundle = true;
}
}
}
return {
timeTag,
contents: bundleContents,
};
}
export function bundleToBuffer(bundle: OSCBundle): Buffer {
const headerBuffer = oscTypeConverterMap.s.toBuffer('#bundle');
if (headerBuffer === undefined) {
throw new Error('problem encoding buffer header');
}
const timeTagBuffer = oscTypeConverterMap.t.toBuffer(bundle.timeTag);
if (timeTagBuffer === undefined) {
throw new Error('problem encoding buffer time tag');
}
const contentsBuffers: Buffer[] = [];
bundle.contents.forEach((bundleContent) => {
if ('address' in bundleContent) {
const contentBuffer = messageToBuffer(bundleContent);
const contentSizeBuffer = oscTypeConverterMap.i.toBuffer(contentBuffer.length);
if (contentBuffer && contentSizeBuffer) {
contentsBuffers.push(Buffer.concat([contentSizeBuffer, contentBuffer]));
}
}
});
return Buffer.concat([headerBuffer, timeTagBuffer, ...contentsBuffers]);
}
-29
View File
@@ -1,29 +0,0 @@
const { deepEqual } = require('assert');
const { describe, it } = require('node:test');
const osc = require('../dist/index');
const tests = [
{
description: 'simple contents single message',
expected: {
timeTag: [32, 0],
contents: [{ address: '/oscillator/4/frequency', args: [{ type: 'f', value: 440 }] }],
},
bundle: Buffer.concat([
Buffer.from('#bundle', 'ascii'),
Buffer.from([0x00]),
Buffer.from([0, 0, 0, 32, 0, 0, 0, 0]),
Buffer.from('00000020', 'hex'),
Buffer.from('2f6f7363696c6c61746f722f342f6672657175656e6379002c66000043dc0000', 'hex'),
]),
},
];
describe('OSC Bundle Decoding', () => {
tests.forEach((bundleTest) => {
it(bundleTest.description, () => {
const encoded = osc.bundleFromBuffer(bundleTest.bundle);
deepEqual(encoded, bundleTest.expected);
});
});
});
-26
View File
@@ -1,26 +0,0 @@
const { deepEqual, throws } = require('assert');
const { describe, it } = require('node:test');
const osc = require('../dist/index');
const tests = [
{
description: 'simple contents single message',
bundle: { timeTag: [32, 0], contents: [{ address: '/oscillator/4/frequency', args: [{ type: 'f', value: 440 }] }] },
expected: Buffer.concat([
Buffer.from('#bundle', 'ascii'),
Buffer.from([0x00]),
Buffer.from([0, 0, 0, 32, 0, 0, 0, 0]),
Buffer.from('00000020', 'hex'),
Buffer.from('2f6f7363696c6c61746f722f342f6672657175656e6379002c66000043dc0000', 'hex'),
]),
},
];
describe('OSC Bundle Encoding', () => {
tests.forEach((bundleTest) => {
it(bundleTest.description, () => {
const encoded = osc.bundleToBuffer(bundleTest.bundle);
deepEqual(encoded, bundleTest.expected);
});
});
});
-124
View File
@@ -1,124 +0,0 @@
const { deepEqual, throws } = require('assert');
const { describe, it } = require('node:test');
const osc = require('../dist/index');
const tests = [
{
description: 'simple address no args',
bytes: Buffer.from('2f68656c6c6f00002c000000', 'hex'),
expected: { address: '/hello', args: [] },
},
{
description: 'simple address string arg',
bytes: Buffer.from('2f68656c6c6f00002c7300006172673100000000', 'hex'),
expected: { address: '/hello', args: [{ type: 's', value: 'arg1' }] },
},
{
description: 'simple address integer arg',
bytes: Buffer.from('2f68656c6c6f00002c69000000000023', 'hex'),
expected: { address: '/hello', args: [{ type: 'i', value: 35 }] },
},
{
description: 'simple address float arg',
bytes: Buffer.from('2f68656c6c6f00002c660000420a0000', 'hex'),
expected: { address: '/hello', args: [{ type: 'f', value: 34.5 }] },
},
{
description: 'simple address blob arg',
bytes: Buffer.from('2f68656c6c6f00002c62000000000004626c6f62', 'hex'),
expected: { address: '/hello', args: [{ type: 'b', value: Buffer.from('blob') }] },
},
{
description: 'simple address True arg',
bytes: Buffer.from('2f68656c6c6f00002c540000', 'hex'),
expected: { address: '/hello', args: [{ type: 'T', value: true }] },
},
{
description: 'simple address False arg',
bytes: Buffer.from('2f68656c6c6f00002c460000', 'hex'),
expected: { address: '/hello', args: [{ type: 'F', value: false }] },
},
{
description: 'osc 1.0 spec example 1',
bytes: Buffer.from('2f6f7363696c6c61746f722f342f6672657175656e6379002c66000043dc0000', 'hex'),
expected: { address: '/oscillator/4/frequency', args: [{ type: 'f', value: 440 }] },
},
{
description: 'osc 1.0 spec example 2',
bytes: Buffer.from('2f666f6f000000002c69697366660000000003e8ffffffff68656c6c6f0000003f9df3b640b5b22d', 'hex'),
expected: {
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 },
],
},
},
];
describe('OSC Message Decoding', () => {
tests.forEach((messageTest) => {
it(messageTest.description, () => {
const decoded = osc.messageFromBuffer(messageTest.bytes);
deepEqual(decoded, messageTest.expected);
});
});
it('bad address', () => {
throws(
() => {
osc.messageFromBuffer(Buffer.from('68656c6c6f00002c660000420a0000', 'hex'));
},
{ name: /^Error$/, message: /must start with/ }
);
});
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/ }
);
});
});
-94
View File
@@ -1,94 +0,0 @@
const { deepEqual, throws } = require('assert');
const { describe, it } = require('node:test');
const osc = require('../dist/index');
const tests = [
{
description: 'simple address no args',
message: { address: '/hello', args: [] },
expected: Buffer.from('2f68656c6c6f00002c000000', 'hex'),
},
{
description: 'simple address string arg',
message: { address: '/hello', args: [{ type: 's', value: 'arg1' }] },
expected: Buffer.from('2f68656c6c6f00002c7300006172673100000000', 'hex'),
},
{
description: 'simple address integer arg',
message: { address: '/hello', args: [{ type: 'i', value: 35 }] },
expected: Buffer.from('2f68656c6c6f00002c69000000000023', 'hex'),
},
{
description: 'simple address float arg',
message: { address: '/hello', args: [{ type: 'f', value: 34.5 }] },
expected: Buffer.from('2f68656c6c6f00002c660000420a0000', 'hex'),
},
{
description: 'simple address blob arg',
message: { address: '/hello', args: [{ type: 'b', value: Buffer.from('blob') }] },
expected: Buffer.from('2f68656c6c6f00002c62000000000004626c6f62', 'hex'),
},
{
description: 'simple address True arg',
message: { address: '/hello', args: [{ type: 'T', value: true }] },
expected: Buffer.from('2f68656c6c6f00002c540000', 'hex'),
},
{
description: 'simple address False arg',
message: { address: '/hello', args: [{ type: 'F', value: false }] },
expected: Buffer.from('2f68656c6c6f00002c460000', 'hex'),
},
];
describe('OSC Message Encoding', () => {
tests.forEach((messageTest) => {
it(messageTest.description, () => {
const encoded = osc.messageToBuffer(messageTest.message);
deepEqual(encoded, messageTest.expected);
});
});
it('bad string arg', () => {
throws(
() => {
osc.messageToBuffer({ address: '/address', args: [{ type: 's', value: 123 }] });
},
{ name: /^TypeError$/ }
);
});
it('bad integer arg', () => {
throws(
() => {
osc.messageToBuffer({ address: '/address', args: [{ type: 'i', value: 'hi' }] });
},
{ name: /^TypeError$/ }
);
});
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$/ }
);
});
});
-12
View File
@@ -1,12 +0,0 @@
{
"compilerOptions": {
"esModuleInterop": true,
"module": "commonjs",
"target": "ES2020",
"declaration": true,
"rootDir": "./src",
"strict": true,
"outDir": "dist"
},
"exclude": ["node_modules", "dist", "examples"]
}