commit 4c5d981e7f23718364236d3fe9cbb2c51f8711ca Author: Rik Berkelder Date: Sat Jan 23 15:43:09 2021 +0100 Initial Commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f6bea5c --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +node_modules +lib +.log \ No newline at end of file diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..c081f8d --- /dev/null +++ b/.npmignore @@ -0,0 +1,3 @@ +src +.log + \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..99827a3 --- /dev/null +++ b/README.md @@ -0,0 +1,19 @@ +# ts-osc +An easy to use, Typescript-Native OSC client. + +## Usage example +```javascript +import {OSCClient} from 'ts-osc'; + +const client = new OSCClient({ + outHost: "localhost", + outPort: 8000, + inPort: 8000 +}) + +client.send('/hello', "string", "hello"); + +client.on('message', (msg)=>{ + console.log(msg); +}) +``` diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..677538f --- /dev/null +++ b/package-lock.json @@ -0,0 +1,128 @@ +{ + "name": "ts-osc", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@types/node": { + "version": "14.14.22", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.22.tgz", + "integrity": "sha512-g+f/qj/cNcqKkc3tFqlXOYjrmZA+jNBiDzbP3kH+B+otKFqAdPgVTGP1IeKRdMml/aE69as5S4FqtxAbl+LaMw==" + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "binpack": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/binpack/-/binpack-0.1.0.tgz", + "integrity": "sha1-vT0JdMPyoERuF99PYLVacqIFqX4=" + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "osc-min": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/osc-min/-/osc-min-1.1.2.tgz", + "integrity": "sha512-8DbiO8ME85R75stgNVCZtHxB9MNBBNcyy+isNBXrsFeinXGjwNAauvKVmGlfRas5VJWC/mhzIx7spR2gFvWxvg==", + "requires": { + "binpack": "~0" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "typescript": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.1.3.tgz", + "integrity": "sha512-B3ZIOf1IKeH2ixgHhj6la6xdwR9QrLC5d1VKeCSY4tvkqhF2eqd9O7txNlS0PO3GrBAFIdr3L1ndNwteUbZLYg==" + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..e2fbaff --- /dev/null +++ b/package.json @@ -0,0 +1,32 @@ +{ + "name": "ts-osc", + "version": "1.0.0", + "description": "Fully TypeScript-native OSC Client based on osc-min", + "main": "./lib/index.js", + "types": "./lib/index.d.ts", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "build": "tsc", + "watch": "tsc --watch" + }, + "repository": { + "type": "git", + "url": "ssh://git@gitlab.riksolo.com:2224/riksolo/ts-osc.git" + }, + "keywords": [ + "osc", + "open", + "sound", + "control" + ], + "author": "Rik Berkelder ", + "license": "MIT", + "dependencies": { + "osc-min": "^1.1.2", + "@types/node": "^14.14.22", + "typescript": "^4.1.3" + }, + "devDependencies": { + "rimraf": "^3.0.2" + } +} diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..5a7fb0c --- /dev/null +++ b/src/index.ts @@ -0,0 +1,3 @@ +export * from './oscClient'; +export {OSCType} from 'osc-min' + diff --git a/src/oscClient.ts b/src/oscClient.ts new file mode 100644 index 0000000..d8bc542 --- /dev/null +++ b/src/oscClient.ts @@ -0,0 +1,62 @@ +import * as dgram from 'dgram'; +import {OSCArgument, OSCType, toBuffer, fromBuffer, OSCArgumentType} from 'osc-min'; +import { EventEmitter } from 'events'; + + +interface OSCClientOptions { + outHost: string; + outPort: number; + inPort?: number; +} + +interface ReceivedOSCMessage { + address: string; + type: T; + value: OSCArgumentType +} + +// declare types for events +export declare interface OSCClient { + on(event: 'message', listener: (message: ReceivedOSCMessage) => void): this; +} + +export class OSCClient extends EventEmitter{ + private socket: dgram.Socket; + private options: OSCClientOptions; + constructor(options: OSCClientOptions){ + super(); + this.socket = dgram.createSocket({type: "udp4", reuseAddr: true}); + this.options = options; + + if(options.inPort){ + this.socket.bind(options.inPort); + } + + this.socket.on('message', (msg)=>{ + const decoded = fromBuffer(msg) + if(decoded.oscType === "message"){ + decoded.args.forEach((arg)=>{ + this.emit('message', { + address: decoded.address, + type: arg.type, + value: arg.value + }) + }) + } + }) + } + + private sendPacket(packet: Buffer): void{ + this.socket.send(packet, 0, packet.length, this.options.outPort, this.options.outHost) + } + + public send(address: string, type: T, value: OSCArgumentType){ + const arg: OSCArgument = { + type, + value + } + + const encoded = toBuffer(address, [arg]); + this.sendPacket(encoded); + } +} diff --git a/src/types/osc-min.d.ts b/src/types/osc-min.d.ts new file mode 100644 index 0000000..ec10f2e --- /dev/null +++ b/src/types/osc-min.d.ts @@ -0,0 +1,59 @@ +declare module "osc-min" { + export const enum OSCType { + String = "string", + Float = "float", + Integer = "integer", + Blob = "blob", + True = "true", + False = "false", + Null = "null", + Bang = "bang", + Timetag = "timetag", + Array = "array" + } + + export type OSCTimeTag = [number, number]; + + export interface OSCMessage { + oscType?: "message", + address: string, + args: Array> + } + + export interface OutgoingOSCMessage extends OSCMessage{ + args: OutgoingOSCArgsTypes; + } + + export type OutgoingOSCArgsTypes = Array | Buffer | boolean | string | number> + + export interface OSCBundle { + oscType: "bundle", + timetag: null | number | Date | OSCTimeTag, + elements: Array + } + + export type OSCArgumentType = T extends OSCType.String ? string + : T extends OSCType.Float | OSCType.Integer | OSCType.Timetag ? number + : T extends OSCType.True ? true + : T extends OSCType.False ? false + : T extends OSCType.Blob ? Buffer + : T extends OSCType.array ? Array> + : null; + + export interface OSCArgument{ + type: T; + value: OSCArgumentType; + } + + type OSCPacket = OSCMessage | OSCBundle; + + export function fromBuffer(buffer: Buffer, strict?: boolean): OSCPacket; + export function toBuffer(object: OutgoingOSCMessage, strict?: boolean): Buffer; + export function toBuffer(address: string, args: Array>, strict?: boolean): Buffer; + export function applyMessageTransform(msg: Buffer, transform: (message: OSCMessage) => OSCMessage); + export function applyAddressTransform(msg: Buffer, transform: (address: string) => string); + export function timetagToDate(ntpTimeTag: OSCTimeTag): Date; + export function dateToTimetag(date: Date): OSCTimeTag; + export function timetagToTimestamp(timeTag: OSCTimeTag): number; + export function timestampToTimetag(timestamp: number); OSCTimeTag; +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..62c789b --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,73 @@ +{ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig.json to read more about this file */ + + /* Basic Options */ + // "incremental": true, /* Enable incremental compilation */ + "target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */ + "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */ + "lib": [], /* Specify library files to be included in the compilation. */ + // "allowJs": true, /* Allow javascript files to be compiled. */ + // "checkJs": true, /* Report errors in .js files. */ + // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ + "declaration": true, /* Generates corresponding '.d.ts' file. */ + // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ + // "sourceMap": true, /* Generates corresponding '.map' file. */ + // "outFile": "./", /* Concatenate and emit output to single file. */ + "outDir": "./lib", /* Redirect output structure to the directory. */ + "rootDir": "./src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ + // "composite": true, /* Enable project compilation */ + // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ + // "removeComments": true, /* Do not emit comments to output. */ + // "noEmit": true, /* Do not emit outputs. */ + // "importHelpers": true, /* Import emit helpers from 'tslib'. */ + // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ + // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ + + /* Strict Type-Checking Options */ + "strict": true, /* Enable all strict type-checking options. */ + // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ + // "strictNullChecks": true, /* Enable strict null checks. */ + // "strictFunctionTypes": true, /* Enable strict checking of function types. */ + // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ + // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ + // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ + // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ + + /* Additional Checks */ + // "noUnusedLocals": true, /* Report errors on unused locals. */ + // "noUnusedParameters": true, /* Report errors on unused parameters. */ + // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ + // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ + // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */ + + /* Module Resolution Options */ + // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ + // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ + // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ + // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ + // "typeRoots": [], /* List of folders to include type definitions from. */ + // "types": [], /* Type declaration files to be included in compilation. */ + // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ + "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ + // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + + /* Source Map Options */ + // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ + // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ + + /* Experimental Options */ + // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ + // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ + + /* Advanced Options */ + "skipLibCheck": true, /* Skip type checking of declaration files. */ + "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ + }, + + "include": ["./src/**/*"], + "exclude": ["./node_modules/**/*"] +}