├── .editorconfig
├── .eslintignore
├── .eslintrc
├── .github
└── workflows
│ └── build.yml
├── .gitignore
├── .idea
└── codeStyles
│ ├── Project.xml
│ └── codeStyleConfig.xml
├── CHANGELOG.md
├── LICENSE
├── package-lock.json
├── package.json
├── readme.md
├── spec
├── anim.spec.ts
└── test.ts
├── src
├── Animation.ts
├── DMX.ts
├── demo
│ ├── demo.ts
│ ├── demo_simple.ts
│ └── demo_socket_client.ts
├── devices.ts
├── drivers
│ ├── abstract-serial-driver.ts
│ ├── artnet.ts
│ ├── bbdmx.ts
│ ├── dmx4all.ts
│ ├── dmxking-ultra-dmx-pro.ts
│ ├── enttec-open-usb-dmx.ts
│ ├── enttec-usb-dmx-pro.ts
│ ├── null.ts
│ ├── sacn.ts
│ └── socketio.ts
├── easing.ts
├── index.ts
├── models
│ ├── Events.ts
│ └── IUniverseDriver.ts
└── util
│ └── time.ts
└── tsconfig.json
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_style = space
5 | indent_size = 2
6 | end_of_line = LF
7 | charset = utf-8
8 | trim_trailing_whitespace = true
9 | insert_final_newline = true
10 |
11 | [*.md]
12 | trim_trailing_whitespace = false
13 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | dist/*
2 | *.js
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true,
4 | "es6": true,
5 | "node": true,
6 | "jest": true
7 | },
8 |
9 | "globals": {
10 | "document": false,
11 | "escape": false,
12 | "navigator": false,
13 | "unescape": false,
14 | "window": false,
15 | "describe": true,
16 | "before": true,
17 | "it": true,
18 | "expect": true,
19 | "sinon": true
20 | },
21 |
22 | "parser": "@typescript-eslint/parser",
23 | "plugins": [
24 | "@typescript-eslint"
25 | ],
26 |
27 | "parserOptions": {
28 | "sourceType": "module"
29 | },
30 |
31 | "rules": {
32 | "block-scoped-var": 2,
33 | "brace-style": [2, "1tbs", { "allowSingleLine": true }],
34 | "camelcase": [2, { "properties": "always" }],
35 | "comma-dangle": [2, "always-multiline"],
36 | "comma-spacing": [2, { "before": false, "after": true }],
37 | "comma-style": [2, "last"],
38 | "complexity": 0,
39 | "consistent-return": 2,
40 | "consistent-this": 0,
41 | "curly": [2, "multi-line"],
42 | "default-case": 0,
43 | "dot-location": [2, "property"],
44 | "dot-notation": 0,
45 | "eol-last": 2,
46 | "eqeqeq": [2, "allow-null"],
47 | "func-names": 0,
48 | "func-style": 0,
49 | "generator-star-spacing": [2, "both"],
50 | "guard-for-in": 0,
51 | "handle-callback-err": [0, "^(err|error|anySpecificError)$" ],
52 | "indent": [2, 2, { "SwitchCase": 1 }],
53 | "key-spacing": [2, { "beforeColon": false, "afterColon": true }],
54 | "keyword-spacing": [2, {"before": true, "after": true}],
55 | "linebreak-style": 0,
56 | "max-depth": 0,
57 | "max-len": [2, 120, 4],
58 | "max-nested-callbacks": 0,
59 | "max-params": 0,
60 | "max-statements": 0,
61 | "new-cap": [2, { "newIsCap": true, "capIsNew": false }],
62 | "newline-after-var": [2, "always"],
63 | "new-parens": 2,
64 | "no-alert": 0,
65 | "no-array-constructor": 2,
66 | "no-bitwise": 0,
67 | "no-caller": 2,
68 | "no-catch-shadow": 0,
69 | "no-cond-assign": 2,
70 | "no-console": 0,
71 | "no-constant-condition": 0,
72 | "no-continue": 0,
73 | "no-control-regex": 2,
74 | "no-debugger": 2,
75 | "no-delete-var": 2,
76 | "no-div-regex": 0,
77 | "no-dupe-args": 2,
78 | "no-dupe-keys": 2,
79 | "no-duplicate-case": 2,
80 | "no-else-return": 2,
81 | "no-empty": 0,
82 | "no-empty-character-class": 2,
83 | "no-eq-null": 0,
84 | "no-eval": 2,
85 | "no-ex-assign": 2,
86 | "no-extend-native": 2,
87 | "no-extra-bind": 2,
88 | "no-extra-boolean-cast": 2,
89 | "no-extra-parens": 0,
90 | "no-extra-semi": 0,
91 | "no-extra-strict": 0,
92 | "no-fallthrough": 2,
93 | "no-floating-decimal": 2,
94 | "no-func-assign": 2,
95 | "no-implied-eval": 2,
96 | "no-inline-comments": 0,
97 | "no-inner-declarations": [2, "functions"],
98 | "no-invalid-regexp": 2,
99 | "no-irregular-whitespace": 2,
100 | "no-iterator": 2,
101 | "no-label-var": 2,
102 | "no-labels": 2,
103 | "no-lone-blocks": 0,
104 | "no-lonely-if": 0,
105 | "no-loop-func": 0,
106 | "no-mixed-requires": 0,
107 | "no-mixed-spaces-and-tabs": [2, false],
108 | "no-multi-spaces": 2,
109 | "no-multi-str": 2,
110 | "no-multiple-empty-lines": [2, { "max": 1 }],
111 | "no-native-reassign": 2,
112 | "no-negated-in-lhs": 2,
113 | "no-nested-ternary": 0,
114 | "no-new": 2,
115 | "no-new-func": 2,
116 | "no-new-object": 2,
117 | "no-new-require": 2,
118 | "no-new-wrappers": 2,
119 | "no-obj-calls": 2,
120 | "no-octal": 2,
121 | "no-octal-escape": 2,
122 | "no-path-concat": 0,
123 | "no-plusplus": 0,
124 | "no-process-env": 0,
125 | "no-process-exit": 0,
126 | "no-proto": 2,
127 | "no-redeclare": 2,
128 | "no-regex-spaces": 2,
129 | "no-reserved-keys": 0,
130 | "no-restricted-modules": 0,
131 | "no-return-assign": 2,
132 | "no-script-url": 0,
133 | "no-self-compare": 2,
134 | "no-sequences": 2,
135 | "no-shadow": 0,
136 | "no-shadow-restricted-names": 2,
137 | "no-spaced-func": 2,
138 | "no-sparse-arrays": 2,
139 | "no-sync": 0,
140 | "no-ternary": 0,
141 | "no-throw-literal": 2,
142 | "no-trailing-spaces": 2,
143 | "no-undef": 2,
144 | "no-undef-init": 2,
145 | "no-undefined": 0,
146 | "no-underscore-dangle": 0,
147 | "no-unneeded-ternary": 2,
148 | "no-unreachable": 1,
149 | "no-unused-expressions": 0,
150 | "no-unused-vars": [2, { "vars": "all", "args": "none" }],
151 | "no-use-before-define": 2,
152 | "no-var": 2,
153 | "no-void": 0,
154 | "no-warning-comments": 0,
155 | "no-with": 2,
156 | "one-var": 0,
157 | "operator-assignment": 0,
158 | "operator-linebreak": [2, "after"],
159 | "padded-blocks": 0,
160 | "prefer-const": 2,
161 | "quote-props": 0,
162 | "quotes": [2, "single", "avoid-escape"],
163 | "radix": 2,
164 | "semi": [2, "always"],
165 | "semi-spacing": 0,
166 | "sort-vars": 0,
167 | "space-before-blocks": [2, "always"],
168 | "space-before-function-paren": [2, {"anonymous": "always", "named": "never"}],
169 | "space-in-brackets": 0,
170 | "space-in-parens": [2, "never"],
171 | "space-infix-ops": 2,
172 | "space-unary-ops": [2, { "words": true, "nonwords": false }],
173 | "spaced-comment": [2, "always"],
174 | "strict": 2,
175 | "use-isnan": 2,
176 | "valid-jsdoc": 0,
177 | "valid-typeof": 2,
178 | "vars-on-top": 0,
179 | "wrap-iife": [2, "any"],
180 | "wrap-regex": 0,
181 | "yoda": [2, "never"]
182 | }
183 | }
184 |
--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: Build & Test
2 |
3 | on:
4 | push:
5 | workflow_dispatch:
6 |
7 | jobs:
8 | build:
9 | name: Build & Test
10 | runs-on: ubuntu-latest
11 | steps:
12 | - name: Checkout
13 | uses: actions/checkout@v2
14 | - name: Install NodeJS
15 | uses: actions/setup-node@v2
16 | with:
17 | node-version: '16'
18 | - name: Install Dependencies
19 | run: npm install
20 | - name: Lint
21 | run: npm run lint
22 | - name: Build
23 | run: npm run build
24 | - name: Test
25 | run: npm run test
26 | - name: NPM Publish
27 | if: startsWith(github.ref, 'refs/tags/v')
28 | uses: JS-DevTools/npm-publish@v1
29 | with:
30 | token: ${{ secrets.NPM_ACCESS_TOKEN }}
31 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | dist
2 | lib-cov
3 | *.seed
4 | *.log
5 | *.csv
6 | *.dat
7 | *.out
8 | *.pid
9 | *.gz
10 | *.tgz
11 |
12 | pids
13 | logs
14 | results
15 | .DS_Store
16 |
17 | node_modules
18 | .project
19 | .com.greenworldsoft.syncfolderspro
20 |
21 | # Remove some common IDE working directories
22 | .idea/*.xml
23 | .idea/*.iml
24 | .idea/inspectionProfiles
25 | .vscode
26 |
27 | .DS_Store
28 | coverage
29 |
--------------------------------------------------------------------------------
/.idea/codeStyles/Project.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.idea/codeStyles/codeStyleConfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | ## `v0.4.0`
4 | * Update dependencies
5 | * Add optional options to sACN driver
6 |
7 | ## `v0.3.0`
8 | * Update Dependencies (thanks @barlock)
9 |
10 | ## `v0.2.1`
11 |
12 | * Export `IUniverseDriver` and `UniverseData`
13 | *+* Fixed ArtNet Driver
14 |
15 | ## `v0.2.0`
16 |
17 | * New Artnet lib to support multiple universes, nets and subnets
18 | * Fixed sACN driver not exported
19 |
20 | ## `v0.1.1`
21 |
22 | * Update dependencies
23 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2012-2018 Sebastian Wiedenroth & Contributors
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "dmx-ts",
3 | "version": "0.4.0",
4 | "author": "Sebastian Wiedenroth ",
5 | "description": "A nodejs DMX library",
6 | "url": "https://github.com/node-dmx/dmx-ts",
7 | "main": "dist/src/index.js",
8 | "scripts": {
9 | "test": "npm run jest",
10 | "lint": "npx eslint . -c .eslintrc --ext .ts",
11 | "jest": "jest",
12 | "build": "tsc",
13 | "prepack": "npm run build"
14 | },
15 | "repository": {
16 | "type": "git",
17 | "url": "https://github.com/node-dmx/dmx-ts.git"
18 | },
19 | "files": [
20 | "dist"
21 | ],
22 | "keywords": [
23 | "DMX",
24 | "light control",
25 | "typescript"
26 | ],
27 | "dependencies": {
28 | "dmxnet": "^0.9.0",
29 | "sacn": "^4.4.0",
30 | "serialport": "^12.0.0",
31 | "socket.io": "^4.7.4"
32 | },
33 | "devDependencies": {
34 | "@types/jest": "^29.5.12",
35 | "@types/node": "^20.11.16",
36 | "@typescript-eslint/eslint-plugin": "^6.21.0",
37 | "@typescript-eslint/parser": "^6.21.0",
38 | "eslint": "^8.56.0",
39 | "jest": "^29.7.0",
40 | "ts-loader": "^9.5.1",
41 | "typescript": "^5.3.3"
42 | },
43 | "license": "MIT",
44 | "engines": {
45 | "node": ">=10.0.0"
46 | },
47 | "jest": {
48 | "clearMocks": true,
49 | "collectCoverage": true,
50 | "coverageDirectory": "coverage",
51 | "coverageReporters": [
52 | "lcov",
53 | "text"
54 | ],
55 | "roots": [
56 | "dist/"
57 | ],
58 | "testEnvironment": "node"
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | # `dmx-ts`
2 | > DMX-512 controller library for node.js
3 |
4 | This is a TypeScript rewrite of [`dmx`](https://github.com/node-dmx/dmx).
5 |
6 | ## Install
7 |
8 | ```bash
9 | npm install dmx-ts
10 | ```
11 |
12 | ## Library API
13 | ```javascript
14 | // CommonJS style require
15 | const { DMX } = require('dmx-ts');
16 | // or ES import
17 | import { DMX } from "dmx-ts";
18 | ```
19 |
20 | ### Class `DMX`
21 |
22 | #### `new DMX()`
23 |
24 | Create a new DMX instance. This class is used to tie multiple universes together.
25 |
26 | #### dmx.addUniverse(name, driver)
27 |
28 | - name
- String
29 | - driver
- Instance of a Driver class, see below
30 |
31 | Add a new DMX Universe with a name and driver.
32 |
33 | The following Drivers are available:
34 |
35 | **Development Drivers:**
36 | - `NullDriver()`: a development driver that prints the universe to stdout
37 |
38 | **Network Drivers:**
39 | - `SocketIODriver()`: a driver which sends out the universe via socket.IO as an array (see [demo_socket_client.js](src/demo/demo_socket_client.js) as a client example)
40 | - `ArtnetDriver()`: driver for Artnet
41 | - `SACNDriver()`: driver for sACN
42 | - `BBDMXDriver()`: driver for [BeagleBone-DMX](https://github.com/boxysean/beaglebone-DMX)
43 |
44 | **Serial Drivers (USB):**
45 | - `DMX4AllDriver()`: driver for DMX4ALL devices like the "NanoDMX USB Interface"
46 | - `EnttecUSBDMXProDriver()`: a driver for devices using a Enttec USB DMX Pro chip like the "DMXKing ultraDMX Micro".
47 | - `EnttecOpenUSBDMXDriver()`: driver for "Enttec Open DMX USB". This device is NOT recommended, there are known hardware limitations and this driver is not very stable. (If possible better obtain a device with the "pro" chip)
48 | - `DMXKingUltraDMXProDriver()`: driver for the DMXKing Ultra DMX pro interface. This driver support multiple universe specify the options with Port = A or B
49 |
50 | Each driver has its own options. Example:
51 | ```TypeScript
52 | const universe1 = dmx.addUniverse('demo1', new NullDriver());
53 | const universe2 = dmx.addUniverse('demo2', new ArtnetDriver("127.0.0.1"));
54 | const universe2 = dmx.addUniverse('demo3', new EnttecUSBDMXProDriver("COM5", { dmxSpeed: 40 }));
55 | ```
56 |
57 | #### `dmx.update(universe, channels[, extraData])`
58 |
59 | - universe
- String, name of the universe
60 | - channels
- Object, keys are channel numbers, values the values to set that channel to
61 | - extraData
- Object, this data will be passed unmodified to the update
Event. (Optional; default value is `{}`)
62 |
63 | Update one or multiple channels of a universe. Also emits a update
Event with the same information.
64 |
65 |
66 | #### `DMX.devices`
67 |
68 | A JSON Object describing some Devices and how many channels they use.
69 | Currently not many devices are in there but more can be added to the devices.js
file. Pull requests welcome ;-)
70 |
71 | The following Devices are known:
72 |
73 | - generic - a one channel dimmer
74 | - showtec-multidim2 - 4 channel dimmer with 4A per channel
75 | - eurolite-led-bar - Led bar with 3 RGB color segments and some programms
76 | - stairville-led-par-56 - RGB LED Par Can with some programms
77 |
78 | ### Class `Animation`
79 |
80 | #### `new Animation([options])`
81 |
82 | Create a new DMX Animation instance. This can be chained similar to jQuery.
83 |
84 | The options Object takes the following keys:
85 |
86 | - loop
- Number, the number of times this animation sequence will loop when run
is invoked. This value is overridden if you invoke runLoop
.
87 | - filter
- Function, allows you to read or modify the values being set to each channel during each animation step.
88 |
89 | If you specify a filter
function, it must take a single object parameter in which keys are channel numbers and values are the values to set those channels to.
90 | You may modify the values in the object to override the values in real-time, for example to scale channel brightness based on a master fader.
91 |
92 | #### `animation.add(to, duration, options)`
93 |
94 | - to
- Object, keys are channel numbers, values the values to set that channel to
95 | - duration
- Number, duration in ms
96 | - options
- Object
97 |
98 | Add an animation Step.
99 | The options Object takes an easing
key which allows to set a easing function from the following list:
100 |
101 | - `linear` (default)
102 | - `inQuad`
103 | - `outQuad`
104 | - `inOutQuad`
105 | - `inCubic`
106 | - `outCubic`
107 | - `inOutCubic`
108 | - `inQuart`
109 | - `outQuart`
110 | - `inOutQuart`
111 | - `inQuint`
112 | - `outQuint`
113 | - `inOutQuint`
114 | - `inSine`
115 | - `outSine`
116 | - `inOutSine`
117 | - `inExpo`
118 | - `outExpo`
119 | - `inOutExpo`
120 | - `inCirc`
121 | - `outCirc`
122 | - `inOutCirc`
123 | - `inElastic`
124 | - `outElastic`
125 | - `inOutElastic`
126 | - `inBack`
127 | - `outBack`
128 | - `inOutBack`
129 | - `inBounce`
130 | - `outBounce`
131 | - `inOutBounce`
132 |
133 | Returns an `Animation` object with the animation step added.
134 |
135 |
136 | #### `animation.delay(duration)`
137 |
138 | - duration
- Number, duration in ms
139 |
140 | Delay the next animation step for duration.
141 | Returns an `Animation` object with the delay step added.
142 |
143 |
144 | #### `animation.run(universe, onFinish)`
145 |
146 | - universe
- Object, reference to the universe driver
147 | - onFinish
- Function, called when the animation is done
148 |
149 | Run the Animation on the specified universe.
150 |
151 | #### `animation.runLoop(universe)`
152 |
153 | - universe
- Object, reference to the universe driver
154 |
155 | Runs an animation constantly until animation.stop()
is called
156 |
157 | The example below shows a value being animated for 5 seconds:
158 | ```javascript
159 | const animation = new Animation().add({
160 | 1: 255,
161 | }, 100).add({
162 | 1: 0,
163 | }, 100).runLoop(universe)
164 |
165 |
166 | setTimeout(() => {
167 | animation.stop()
168 | }, 5000)
169 | ```
170 |
171 | #### `update` Event
172 |
173 | - universe
- String, name of the universe
174 | - channels
- Object, keys are channel numbers, values the values to set that channel to
175 | - extraData
- Object, data that was passed to the update
method.
176 |
177 | This event is emitted whenever update
is called either by the integrating application or by an animation step.
178 |
179 | If triggered by an animation step, extraData.origin
will be the string 'animation'
.
180 |
181 | ## Develop
182 |
183 | ### Publish to
184 | 1. Run `npm version [patch / minor / major / [custom version number]]`
185 | 1. Run `git tag v[yourVersion]`
186 | 1. Push the tag to GitHub using `git push --tags`
187 |
188 | ## License
189 | [MIT](./LICENSE)
190 |
--------------------------------------------------------------------------------
/spec/anim.spec.ts:
--------------------------------------------------------------------------------
1 | import {Animation, DMX} from '../src';
2 | import {NullDriver} from '../src/drivers/null';
3 | import {IUniverseDriver} from '../src/models/IUniverseDriver';
4 |
5 | describe('Animations', () => {
6 |
7 | let dmx: DMX;
8 | let universeDriver: IUniverseDriver;
9 |
10 | beforeAll(async () => {
11 | dmx = new DMX();
12 | universeDriver = new NullDriver();
13 | await dmx.addUniverse('test', universeDriver);
14 | });
15 |
16 | afterAll(async () => {
17 | await universeDriver.close();
18 | });
19 |
20 | const ANIM_PRECISION = 50;
21 |
22 | test('fake timers', () => {
23 | const updateMock = jest.fn();
24 |
25 | universeDriver.update = updateMock;
26 | universeDriver.update({1: 255});
27 |
28 | jest.useFakeTimers();
29 |
30 | new Animation().add({
31 | 1: 255,
32 | }, 100).add({
33 | 1: 0,
34 | }, 100).run(universeDriver);
35 |
36 | jest.runAllTimers();
37 |
38 | expect(updateMock).toHaveBeenCalledWith({1: 255}, {origin: 'animation'});
39 | expect(updateMock).toHaveBeenCalledWith({1: 0}, {origin: 'animation'});
40 | });
41 |
42 | test('real timers', done => {
43 | universeDriver.update = jest.fn();
44 | universeDriver.update({1: 255});
45 |
46 | jest.useRealTimers();
47 |
48 | const startAt = Date.now();
49 |
50 | new Animation().add({
51 | 1: 255,
52 | }, 250).add({
53 | 1: 0,
54 | }, 250).run(universeDriver, async () => {
55 | await universeDriver.close();
56 | const timeTook = Date.now() - startAt;
57 |
58 | expect(timeTook).toBeGreaterThanOrEqual(500 - ANIM_PRECISION);
59 | expect(timeTook).toBeLessThanOrEqual(500 + ANIM_PRECISION);
60 | done();
61 | });
62 |
63 | });
64 | });
65 |
--------------------------------------------------------------------------------
/spec/test.ts:
--------------------------------------------------------------------------------
1 | describe('test', () => {
2 | it('works', () => {
3 | expect(true).toBe(true);
4 | });
5 | });
6 |
--------------------------------------------------------------------------------
/src/Animation.ts:
--------------------------------------------------------------------------------
1 | import { IUniverseDriver } from './models/IUniverseDriver';
2 | import { ease } from './easing';
3 |
4 | export interface AnimationArgs {
5 | loop?: number;
6 | filter?: any;
7 | }
8 |
9 | export class Animation {
10 | public loops: number;
11 | public frameDelay: number;
12 | public animations: any[];
13 | public lastAnimation: number;
14 | public timeout: any;
15 | public duration: number;
16 | public startTime: any;
17 | public currentLoop: number;
18 | public filter: any;
19 |
20 | constructor(args: AnimationArgs = {}) {
21 | this.frameDelay = 1;
22 | this.animations = [];
23 | this.lastAnimation = 0;
24 | this.timeout = null;
25 | this.duration = 0;
26 | this.startTime = null;
27 | this.loops = args.loop || 1;
28 | this.currentLoop = 0;
29 | this.filter = args.filter;
30 | }
31 |
32 | add(to: any, duration = 0, options: any = {}): this {
33 | options.easing = options.easing || 'linear';
34 |
35 | this.animations.push({
36 | to,
37 | options,
38 | start: this.duration,
39 | end: this.duration + duration,
40 | });
41 | this.duration += duration;
42 |
43 | return this;
44 | }
45 |
46 | delay(duration: number): this {
47 | this.add({}, duration);
48 | return this;
49 | }
50 |
51 | stop(): void {
52 | if (this.timeout) {
53 | clearTimeout(this.timeout);
54 | }
55 | }
56 |
57 | reset(startTime = new Date().getTime()): void {
58 | this.startTime = startTime;
59 | this.lastAnimation = 0;
60 | }
61 |
62 | runNextLoop(universe: IUniverseDriver, onFinish?: () => void): this {
63 | const runAnimationStep = (): void => {
64 | const now = new Date().getTime();
65 | const elapsedTime = now - this.startTime;
66 |
67 | this.timeout = setTimeout(runAnimationStep, this.frameDelay);
68 |
69 | // Find the animation for the current point in time, the latest if multiple match
70 |
71 | let currentAnimation = this.lastAnimation;
72 |
73 | while (
74 | currentAnimation < this.animations.length &&
75 | elapsedTime >= this.animations[currentAnimation].end
76 | ) {
77 | currentAnimation++;
78 | }
79 |
80 | // Ensure final state of all newly completed animations have been set
81 | const completedAnimations = this.animations.slice(
82 | this.lastAnimation,
83 | currentAnimation
84 | );
85 |
86 | // Ensure future animations interpolate from the most recent state
87 | completedAnimations.forEach(completedAnimation => {
88 | delete completedAnimation.from;
89 | });
90 |
91 | if (completedAnimations.length) {
92 | const completedAnimationStatesToSet = Object.assign(
93 | {},
94 | ...completedAnimations.map(a => a.to)
95 | );
96 |
97 | if (typeof this.filter === 'function') {
98 | this.filter(completedAnimationStatesToSet);
99 | }
100 |
101 | universe.update(completedAnimationStatesToSet, {origin: 'animation'});
102 | }
103 |
104 | this.lastAnimation = currentAnimation;
105 |
106 | if (elapsedTime >= this.duration) {
107 | // This animation loop is complete
108 | this.currentLoop++;
109 | this.stop();
110 | if (this.currentLoop >= this.loops) {
111 | // All loops complete
112 | if (onFinish) {
113 | onFinish();
114 | }
115 | } else {
116 | // Run next loop
117 | this.reset(this.startTime + this.duration);
118 | this.runNextLoop(universe);
119 | }
120 | } else {
121 | // Set intermediate channel values during an animation
122 | const animation = this.animations[currentAnimation];
123 | const easing = (ease as any)[animation.options.easing];
124 | const duration = animation.end - animation.start;
125 | const animationElapsedTime = elapsedTime - animation.start;
126 |
127 | if (!animation.from) {
128 | animation.from = {};
129 | for (const k in animation.to) {
130 | animation.from[k] = universe?.get(Number(k));
131 | }
132 | if (animation.options.from) {
133 | animation.from = Object.assign(animation.from, animation.options.from);
134 | }
135 | }
136 |
137 | if (duration) {
138 | const easeProgress = easing(
139 | Math.min(animationElapsedTime, duration),
140 | 0,
141 | 1,
142 | duration
143 | );
144 | const intermediateValues: any = {};
145 |
146 | for (const k in animation.to) {
147 | const startValue = animation.from[k];
148 | const endValue = animation.to[k];
149 |
150 | intermediateValues[k] = Math.round(
151 | startValue + easeProgress * (endValue - startValue)
152 | );
153 | }
154 |
155 | if (typeof this.filter === 'function') {
156 | this.filter(intermediateValues);
157 | }
158 |
159 | universe.update(intermediateValues, {origin: 'animation'});
160 | }
161 | }
162 | };
163 |
164 | runAnimationStep();
165 |
166 | return this;
167 | }
168 |
169 | run(universe: IUniverseDriver, onFinish?: () => void): void {
170 | if ((universe as any).interval) {
171 | // Optimisation to run animation updates at double the rate of driver updates using Nyquist's theorem
172 | this.frameDelay = (universe as any).interval / 2;
173 | }
174 | this.reset();
175 | this.currentLoop = 0;
176 | this.runNextLoop(universe, onFinish);
177 | }
178 |
179 | runLoop(universe: IUniverseDriver, onFinish?: () => void, loops = Infinity): this {
180 | this.loops = loops;
181 | this.run(universe, onFinish);
182 | return this;
183 | }
184 | }
185 |
--------------------------------------------------------------------------------
/src/DMX.ts:
--------------------------------------------------------------------------------
1 | import { EventEmitter } from 'events';
2 | import { Devices, PredefinedDevices } from './devices';
3 | import { Events } from './models/Events';
4 | import { IUniverseDriver } from './models/IUniverseDriver';
5 |
6 | export interface DmxArgs {
7 | devices?: any;
8 | }
9 |
10 | export class DMX extends EventEmitter {
11 | private readonly _devices: Devices;
12 | private readonly _universesByName: Map = new Map();
13 | constructor(options?: DmxArgs) {
14 | super();
15 | const devices = options?.devices ?? {};
16 |
17 | this._devices = Object.assign({}, PredefinedDevices, devices);
18 | }
19 |
20 | async addUniverse(name: string, universe: IUniverseDriver): Promise {
21 | await universe.init();
22 |
23 | universe.on(Events.update, (channels, extraData) => {
24 | this.emit(Events.update, name, channels, extraData);
25 | });
26 |
27 | this._universesByName.set(name, universe);
28 |
29 | return universe;
30 | }
31 |
32 | update(universeName: string, channels: {[key: number]: number}, extraData?: any): void {
33 | const universe = this._universesByName.get(universeName);
34 |
35 | if (universe === undefined) {
36 | throw new Error(`Universe ${universe} does not exist`);
37 | }
38 | universe.update(channels, extraData || {});
39 | }
40 |
41 | updateAll(universe: string, value: number): void {
42 | this._universesByName.get(universe)?.updateAll(value);
43 | this.emit(Events.updateAll, universe, value);
44 | }
45 |
46 | universeToObject(universeKey: string): {[key: number]: number} {
47 | const universe = this._universesByName.get(universeKey);
48 | const u: {[key: number]: number} = {};
49 |
50 | for (let i = 0; i < 512; i++) {
51 | u[i] = universe?.get(i) || 0;
52 | }
53 |
54 | return u;
55 | }
56 |
57 | async close(): Promise {
58 | for (const uni of this._universesByName.values()) {
59 | await uni.close();
60 | }
61 | this.removeAllListeners();
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/demo/demo.ts:
--------------------------------------------------------------------------------
1 | import {Animation} from '../Animation';
2 | import {NullDriver} from '../drivers/null';
3 | import {DMX} from '../';
4 | import {IUniverseDriver, UniverseData} from '../models/IUniverseDriver';
5 |
6 | const run = async () => {
7 |
8 | const dmx = new DMX();
9 |
10 | // var universe = dmx.addUniverse('demo', 'enttec-usb-dmx-pro', '/dev/cu.usbserial-6AVNHXS8')
11 | // var universe = dmx.addUniverse('demo', 'enttec-open-usb-dmx', '/dev/cu.usbserial-6AVNHXS8')
12 | // const universe = dmx.addUniverse('demo', 'socketio', null, {port: 17809, debug: true});
13 | const universe = await dmx.addUniverse('demo', new NullDriver());
14 |
15 | universe.update({1: 1, 2: 0});
16 | universe.update({16: 1, 17: 255});
17 | universe.update({1: 255, 3: 120, 4: 230, 5: 30, 6: 110, 7: 255, 8: 10, 9: 255, 10: 255, 11: 0});
18 |
19 | function greenWater(universe: IUniverseDriver, channels: UniverseData, duration: number): void {
20 | const colors = [
21 | [160, 230, 20],
22 | [255, 255, 0],
23 | [110, 255, 10],
24 | ];
25 |
26 | for (const c in channels) {
27 | const r = Math.floor((Math.random() * colors.length));
28 | const u: UniverseData = {};
29 |
30 | for (let i = 0; i < 3; i++) {
31 | u[channels[c] + i] = colors[r][i];
32 | }
33 | new Animation().add(u, duration).run(universe);
34 | }
35 | setTimeout(() => greenWater(universe, channels, duration), duration * 2);
36 | }
37 |
38 | function warp(universe: IUniverseDriver, channel: number, min: number, max: number, duration: number): void {
39 | const a: UniverseData = {};
40 | const b: UniverseData = {};
41 |
42 | a[channel] = min;
43 | b[channel] = max;
44 | new Animation().add(a, duration).add(b, duration).run(universe, function () {
45 | warp(universe, channel, min, max, duration);
46 | });
47 | }
48 |
49 | warp(universe, 1, 200, 220, 360);
50 | warp(universe, 1 + 15, 200, 255, 240);
51 | greenWater(universe, [3, 6, 9], 4000);
52 | greenWater(universe, [3 + 15, 6 + 15, 9 + 15], 4000);
53 |
54 | };
55 |
56 | run()
57 | .catch((err) => console.error(err));
58 |
--------------------------------------------------------------------------------
/src/demo/demo_simple.ts:
--------------------------------------------------------------------------------
1 | import {NullDriver} from '../drivers/null';
2 | import {DMX} from '../index';
3 |
4 | const dmx = new DMX();
5 |
6 | // var universe = dmx.addUniverse('demo', 'enttec-open-usb-dmx', '/dev/cu.usbserial-6AVNHXS8')
7 | // const universe = dmx.addUniverse('demo', 'socketio', null, {port: 17809, debug: true});
8 | const run = async () => {
9 | const universe = await dmx.addUniverse('demo', new NullDriver());
10 |
11 | let on = false;
12 |
13 | setInterval(() => {
14 | if (on) {
15 | on = false;
16 | universe.updateAll(0);
17 | console.log('off');
18 | } else {
19 | on = true;
20 | universe.updateAll(250);
21 | console.log('on');
22 | }
23 | }, 1000);
24 | };
25 |
26 | run()
27 | .catch((err) => console.error(err));
28 |
--------------------------------------------------------------------------------
/src/demo/demo_socket_client.ts:
--------------------------------------------------------------------------------
1 | const port = 17809;
2 | const io = require('socket.io-client');
3 | const client = io.connect(`http://localhost:${port}`);
4 |
5 | client.on('update', (msg: any) => console.info(msg));
6 |
--------------------------------------------------------------------------------
/src/devices.ts:
--------------------------------------------------------------------------------
1 | export interface Device {
2 | channels: string[]|number[];
3 | ranges?: any;
4 | channelgroups?: string[];
5 | }
6 |
7 | export type Devices = { [key: string]: Device };
8 |
9 | export const PredefinedDevices: Devices = {
10 | 'generic': {
11 | channels: ['dimmer'],
12 | },
13 | 'generic-rgb': {
14 | channels: ['red', 'green', 'blue'],
15 | },
16 | 'showtec-multidim2': {
17 | channels: ['1', '2', '3', '4'],
18 | },
19 | 'eurolite-led-bar': {
20 | channels: [
21 | 'ctrl',
22 | 'dimmer',
23 | 'strobe',
24 | 'red0',
25 | 'green0',
26 | 'blue0',
27 | 'red1',
28 | 'green1',
29 | 'blue1',
30 | 'red2',
31 | 'green2',
32 | 'blue2',
33 | ],
34 | ranges: {
35 | 'ctrl': {
36 | 'type': 'option',
37 | 'options': [
38 | {'value': 0, 'label': 'Black Out'},
39 | {'value': 1, 'label': 'Dimmer 1'},
40 | {'value': 16, 'label': 'Dimmer 2'},
41 | {'value': 32, 'label': 'Red'},
42 | {'value': 48, 'label': 'Green'},
43 | {'value': 64, 'label': 'Blue'},
44 | {'value': 80, 'label': 'Purple'},
45 | {'value': 96, 'label': 'Yellow'},
46 | {'value': 112, 'label': 'Cyan'},
47 | {'value': 128, 'label': 'White'},
48 | {'value': 144, 'label': 'Color change'},
49 | {'value': 160, 'label': 'Color flow'},
50 | {'value': 176, 'label': 'Color dream'},
51 | {'value': 192, 'label': 'Multi flow'},
52 | {'value': 208, 'label': 'Dream flow'},
53 | {'value': 224, 'label': 'Two color flow'},
54 | {'value': 240, 'label': 'Sound activity'},
55 | ],
56 | },
57 | 'dimmer': {
58 | 'type': 'slider',
59 | 'min': 0,
60 | 'max': 255,
61 | },
62 | },
63 | },
64 | 'stairville-led-par-56': {
65 | channels: ['ctrl', 'red', 'green', 'blue', 'speed'],
66 | ranges: {
67 | 'ctrl': {
68 | 'type': 'option',
69 | 'options': [
70 | {'value': 0, 'label': 'RGB Control'},
71 | {'value': 64, 'label': '7 color fade'},
72 | {'value': 128, 'label': '7 color change'},
73 | {'value': 192, 'label': '3 color change'},
74 | ],
75 | },
76 | },
77 | },
78 | 'ultra-pro-24ch-rdm': {
79 | channels: [...Array(25).keys()].slice(1),
80 | },
81 | 'ultra-pro-6rgbch-rdm': {
82 | channels: [...Array(25).keys()].slice(1),
83 | channelgroups: ['1', '2', '3', '4', '5', '6'],
84 | },
85 | 'oppsk-cob-uv-par': {
86 | channels: ['dimmer', 'strobe', 'program-speed', 'sound-activity'],
87 | },
88 | 'lixda-par12-led': {
89 | channels: ['ctrl', 'static-color', 'speed', 'dimmer', 'red', 'green', 'blue', 'white'],
90 | ranges: {
91 | 'ctrl': {
92 | 'type': 'option',
93 | 'options': [
94 | {'value': 0, 'label': 'Off'},
95 | {'value': 11, 'label': 'Static Color'},
96 | {'value': 51, 'label': 'Jump'},
97 | {'value': 101, 'label': 'Gradual'},
98 | {'value': 151, 'label': 'Sound Activate'},
99 | {'value': 200, 'label': 'Strobe'},
100 | ],
101 | },
102 | 'static-color': {
103 | 'type': 'option',
104 | 'options': [
105 | {'value': 0, 'label': 'All Color'},
106 | {'value': 40, 'label': 'Red'},
107 | {'value': 50, 'label': 'Green'},
108 | {'value': 60, 'label': 'Blue'},
109 | {'value': 70, 'label': 'Yellow'},
110 | {'value': 80, 'label': 'Cyan'},
111 | {'value': 90, 'label': 'Purple'},
112 | {'value': 100, 'label': 'White'},
113 | {'value': 110, 'label': 'Red + Green'},
114 | {'value': 120, 'label': 'Red + Blue'},
115 | {'value': 130, 'label': 'Red + White'},
116 | {'value': 140, 'label': 'Green + Blue'},
117 | {'value': 150, 'label': 'Green + White'},
118 | {'value': 160, 'label': 'Blue + White'},
119 | {'value': 170, 'label': 'Red + Green + White'},
120 | {'value': 180, 'label': 'Red + Blue + White'},
121 | {'value': 190, 'label': 'Green + Blue + White'},
122 | {'value': 200, 'label': 'Red + Green + Blue'},
123 | {'value': 210, 'label': 'Red + Green + Blue + White'},
124 | ],
125 | },
126 | },
127 | },
128 | 'eurolite-led-tha-120PC': {
129 | channels: ['red', 'green', 'blue', 'white', 'dimmer', 'strobe', 'effect'],
130 | },
131 | 'briteq-bt-theatre-60FC': {
132 | channels: ['dimmer', 'strobe', 'effect', 'red', 'green', 'blue', 'white'],
133 | },
134 | 'lalucenatz-led-4ch': {
135 | channels: ['master', 'red', 'green', 'blue'],
136 | },
137 | };
138 |
--------------------------------------------------------------------------------
/src/drivers/abstract-serial-driver.ts:
--------------------------------------------------------------------------------
1 | import {EventEmitter} from 'events';
2 | import {SerialPort, SerialPortOpenOptions} from 'serialport';
3 | import {AutoDetectTypes} from '@serialport/bindings-cpp';
4 | import {IUniverseDriver, UniverseData} from '../models/IUniverseDriver';
5 |
6 | type OpenOptions = Omit, 'path'>;
7 |
8 | export interface AbstractSerialDriverArgs {
9 | serialPortOptions: OpenOptions;
10 | sendInterval: number;
11 | }
12 |
13 | export abstract class AbstractSerialDriver extends EventEmitter implements IUniverseDriver {
14 | private _serialPort!: SerialPort;
15 |
16 | private readonly _universe: Buffer;
17 | private readonly _sendInterval: number;
18 | private readonly _serialPortName: string;
19 | private readonly _serialPortOptions: OpenOptions;
20 | private _intervalHandle: any | undefined = undefined;
21 |
22 | protected constructor(serialPort: string, args: AbstractSerialDriverArgs) {
23 | super();
24 | this._sendInterval = args.sendInterval;
25 | this._serialPortName = serialPort;
26 | this._serialPortOptions = args.serialPortOptions;
27 |
28 | this._universe = Buffer.alloc(513);
29 | }
30 |
31 | init(): Promise {
32 | return new Promise((resolve, reject) => {
33 | this._serialPort = new SerialPort({
34 | ...this._serialPortOptions,
35 | path: this._serialPortName,
36 | }, (err) => {
37 | if (!err) {
38 | this.start();
39 | resolve();
40 | } else {
41 | reject(err);
42 | }
43 | });
44 | });
45 | }
46 |
47 | close(): Promise {
48 | this.stop();
49 | return new Promise((resolve, reject) => this._serialPort.close((err: any) => err ? reject(err) : resolve()));
50 | }
51 |
52 | protected get serialPort(): SerialPort {
53 | return this._serialPort;
54 | }
55 |
56 | protected get universeBuffer(): Buffer {
57 | return this._universe;
58 | }
59 |
60 | protected start(): void {
61 | if (this._intervalHandle !== undefined) {
62 | throw new Error('Driver is already running.');
63 | }
64 | this._intervalHandle = setInterval(this.sendUniverse.bind(this), this._sendInterval);
65 | }
66 |
67 | protected stop(): void {
68 | if (this._intervalHandle !== undefined) {
69 | clearInterval(this._intervalHandle);
70 | this._intervalHandle = undefined;
71 | }
72 | }
73 |
74 | protected abstract sendUniverse(): Promise;
75 |
76 | get(channel: number): number {
77 | return this._universe[channel];
78 | }
79 |
80 | update(channels: UniverseData, extraData?: any): void {
81 |
82 | for (const c in channels) {
83 | this._universe[c] = channels[c];
84 | }
85 |
86 | this.emit('update', channels, extraData);
87 | }
88 |
89 | updateAll(value: number): void {
90 | for (let i = 1; i <= 512; i++) {
91 | this._universe[i] = value;
92 | }
93 | }
94 |
95 | }
96 |
--------------------------------------------------------------------------------
/src/drivers/artnet.ts:
--------------------------------------------------------------------------------
1 | import {EventEmitter} from 'events';
2 | import {IUniverseDriver, UniverseData} from '../models/IUniverseDriver';
3 | import dmxlib from 'dmxnet';
4 |
5 | export interface ArtnetArgs {
6 | unchangedDataInterval?: number,
7 | universe?: number,
8 | port?: number,
9 | net?: number,
10 | subnet?: number,
11 | subuni?: number,
12 | dmxlibOptions?: dmxlib.DmxnetOptions,
13 | }
14 |
15 | export class ArtnetDriver extends EventEmitter implements IUniverseDriver {
16 | options: ArtnetArgs;
17 | host: string;
18 | dmxnet: dmxlib.dmxnet;
19 | universe?: dmxlib.sender;
20 |
21 | constructor(host = '127.0.0.1', options: ArtnetArgs = {}) {
22 | super();
23 |
24 | this.options = options;
25 | this.host = host;
26 | // eslint-disable-next-line new-cap
27 | this.dmxnet = new dmxlib.dmxnet(options.dmxlibOptions);
28 | }
29 |
30 | async init(): Promise {
31 | this.universe = this.dmxnet.newSender({
32 | ip: this.host,
33 | // eslint-disable-next-line camelcase
34 | base_refresh_interval: this.options.unchangedDataInterval,
35 | net: this.options.net,
36 | port: this.options.port,
37 | subnet: this.options.subnet,
38 | subuni: this.options.subuni,
39 | universe: this.options.universe,
40 | });
41 | }
42 |
43 | sendUniverse(): void {
44 | this.universe?.transmit();
45 | }
46 |
47 | close(): Promise {
48 | return new Promise((resolve) => {
49 | this.stop();
50 | resolve();
51 | });
52 | }
53 |
54 | update(u: UniverseData, extraData?: any): void {
55 | for (const c in u) {
56 | this.universe?.prepChannel(Number(c), u[c]);
57 | }
58 | this.sendUniverse();
59 |
60 | this.emit('update', u, extraData);
61 | }
62 |
63 | updateAll(v: number): void {
64 | this.universe?.fillChannels(0, 511, v);
65 | this.sendUniverse();
66 | }
67 |
68 | get(c: number): number {
69 | return this.universe?.values?.[c]!;
70 | }
71 |
72 | private stop(): void {
73 | this.universe?.stop();
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/src/drivers/bbdmx.ts:
--------------------------------------------------------------------------------
1 | import {EventEmitter} from 'events';
2 | import {IUniverseDriver, UniverseData} from '../models/IUniverseDriver';
3 | import dgram from 'dgram';
4 |
5 | const UNIVERSE_LEN = 512;
6 |
7 | export interface BBDMXArgs {
8 | dmxSpeed?: number,
9 | port?: number,
10 | }
11 |
12 | export class BBDMXDriver extends EventEmitter implements IUniverseDriver {
13 | readyToWrite: boolean;
14 | interval: number;
15 | timeout?: any;
16 | options: {};
17 | universe: Buffer;
18 | host: string;
19 | port: any;
20 | dev: dgram.Socket;
21 |
22 | constructor(deviceId = '127.0.0.1', options: BBDMXArgs = {}) {
23 | super();
24 | this.readyToWrite = true;
25 | this.interval = options.dmxSpeed ? (1000 / options.dmxSpeed) : 24;
26 | this.options = options;
27 | this.universe = Buffer.alloc(UNIVERSE_LEN + 1);
28 | this.host = deviceId;
29 | this.port = options.port || 9930;
30 | this.dev = dgram.createSocket('udp4');
31 | this.start();
32 | }
33 |
34 | async init(): Promise {
35 | this.start();
36 | }
37 |
38 | sendUniverse(): void {
39 | if (this.readyToWrite) {
40 | this.readyToWrite = false;
41 |
42 | let channel;
43 | let messageBuffer = Buffer.from(UNIVERSE_LEN.toString());
44 |
45 | for (let i = 1; i <= UNIVERSE_LEN; i++) {
46 | channel = Buffer.from(' ' + this.universe[i]);
47 | messageBuffer = Buffer.concat([messageBuffer, channel]);
48 | }
49 |
50 | this.dev.send(messageBuffer, 0, messageBuffer.length, this.port, this.host, () => {
51 | this.readyToWrite = true;
52 | });
53 | }
54 | }
55 |
56 | close(): void {
57 | this.stop();
58 | this.dev.close();
59 | }
60 |
61 | update(u: UniverseData, extraData?: any): void {
62 | for (const c in u) {
63 | this.universe[c] = u[c];
64 | }
65 |
66 | this.emit('update', u, extraData);
67 | }
68 |
69 | updateAll(v: number): void {
70 | for (let i = 1; i <= UNIVERSE_LEN; i++) {
71 | this.universe[i] = v;
72 | }
73 | }
74 |
75 | get(c: number): number {
76 | return this.universe[c];
77 | }
78 |
79 | private start(): void {
80 | this.timeout = setInterval(this.sendUniverse.bind(this), this.interval);
81 | }
82 |
83 | private stop(): void {
84 | if (this.timeout) clearInterval(this.timeout);
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/src/drivers/dmx4all.ts:
--------------------------------------------------------------------------------
1 | import {AbstractSerialDriver} from './abstract-serial-driver';
2 |
3 | const UNIVERSE_LEN = 512;
4 |
5 | export interface DMX4AllArgs {
6 | dmxSpeed?: number;
7 | }
8 |
9 | export class DMX4AllDriver extends AbstractSerialDriver {
10 | private readyToWrite: boolean;
11 |
12 | constructor(serialPort: string, options: DMX4AllArgs = {}) {
13 | super(serialPort, {
14 | serialPortOptions: {
15 | 'baudRate': 38400,
16 | 'dataBits': 8,
17 | 'stopBits': 1,
18 | 'parity': 'none',
19 | },
20 | sendInterval: 1000 / (options.dmxSpeed || 33),
21 | });
22 | this.readyToWrite = true;
23 | }
24 |
25 | async sendUniverse(): Promise {
26 | if (!this.serialPort.writable) {
27 | return;
28 | }
29 |
30 | if (this.readyToWrite) {
31 | this.readyToWrite = false;
32 |
33 | const msg = Buffer.alloc(UNIVERSE_LEN * 3);
34 |
35 | for (let i = 0; i < UNIVERSE_LEN; i++) {
36 | msg[i * 3 + 0] = (i < 256) ? 0xE2 : 0xE3;
37 | msg[i * 3 + 1] = i;
38 | msg[i * 3 + 2] = this.universeBuffer[i + 1];
39 | }
40 |
41 | this.serialPort.write(msg);
42 | this.serialPort.drain(() => {
43 | this.readyToWrite = true;
44 | });
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/drivers/dmxking-ultra-dmx-pro.ts:
--------------------------------------------------------------------------------
1 | import {AbstractSerialDriver} from './abstract-serial-driver';
2 |
3 | const DMXKING_ULTRA_DMX_PRO_DMX_STARTCODE = 0x00;
4 | const DMXKING_ULTRA_DMX_PRO_START_OF_MSG = 0x7e;
5 | const DMXKING_ULTRA_DMX_PRO_END_OF_MSG = 0xe7;
6 | const DMXKING_ULTRA_DMX_PRO_SEND_DMX_RQ = 0x06;
7 | const DMXKING_ULTRA_DMX_PRO_SEND_DMX_A_RQ = 0x64;
8 | const DMXKING_ULTRA_DMX_PRO_SEND_DMX_B_RQ = 0x65;
9 |
10 | // var DMXKING_ULTRA_DMX_PRO_RECV_DMX_PKT = 0x05;
11 |
12 | export interface DMXKingUltraDMXProDriverArgs {
13 | dmxSpeed?: number;
14 | port?: 'A' | 'B';
15 | }
16 |
17 | export class DMXKingUltraDMXProDriver extends AbstractSerialDriver {
18 | private readonly _options: DMXKingUltraDMXProDriverArgs;
19 | private readonly _sendDMXReq: number;
20 | private _readyToWrite: boolean;
21 |
22 | constructor(serialPort: string, options: DMXKingUltraDMXProDriverArgs = {}) {
23 | super(serialPort, {
24 | serialPortOptions: {
25 | 'baudRate': 250000,
26 | 'dataBits': 8,
27 | 'stopBits': 2,
28 | 'parity': 'none',
29 | },
30 | sendInterval: 1000 / (options.dmxSpeed || 40),
31 | });
32 | this._options = options;
33 | this._readyToWrite = true;
34 |
35 | this._sendDMXReq = DMXKING_ULTRA_DMX_PRO_SEND_DMX_RQ;
36 | if (this._options.port === 'A') {
37 | this._sendDMXReq = DMXKING_ULTRA_DMX_PRO_SEND_DMX_A_RQ;
38 | } else if (this._options.port === 'B') {
39 | this._sendDMXReq = DMXKING_ULTRA_DMX_PRO_SEND_DMX_B_RQ;
40 | }
41 |
42 | }
43 |
44 | async sendUniverse(): Promise {
45 | if (!this.serialPort.writable) {
46 | return;
47 | }
48 |
49 | if (this._readyToWrite) {
50 | this._readyToWrite = false;
51 |
52 | const hdr = Buffer.from([
53 | DMXKING_ULTRA_DMX_PRO_START_OF_MSG,
54 | this._sendDMXReq,
55 | (this.universeBuffer.length) & 0xff,
56 | ((this.universeBuffer.length) >> 8) & 0xff,
57 | DMXKING_ULTRA_DMX_PRO_DMX_STARTCODE,
58 | ]);
59 |
60 | const msg = Buffer.concat([
61 | hdr,
62 | this.universeBuffer.slice(1),
63 | Buffer.from([DMXKING_ULTRA_DMX_PRO_END_OF_MSG]),
64 | ]);
65 |
66 | this.serialPort.write(msg);
67 | this.serialPort.drain(() => {
68 | this._readyToWrite = true;
69 | });
70 | }
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/src/drivers/enttec-open-usb-dmx.ts:
--------------------------------------------------------------------------------
1 | import {wait} from '../util/time';
2 | import {AbstractSerialDriver} from './abstract-serial-driver';
3 |
4 | export interface EnttecOpenUsbDmxArgs {
5 | dmxSpeed?: number;
6 | }
7 |
8 | /**
9 | * Controls the Enttec Open DMX device:
10 | * https://www.enttec.com.au/product/lighting-communication-protocols/usb-lighting-interface/open-dmx-usb/
11 | *
12 | * The controller uses a FTDI FT232RL chip for serial communication. See
13 | * [here](http://www.ftdichip.com/Support/Documents/ProgramGuides/D2XX_Programmer's_Guide(FT_000071).pdf)
14 | * for an API reference and to translate the Enttec code examples to Node.js/Serialport.
15 | */
16 | export class EnttecOpenUSBDMXDriver extends AbstractSerialDriver {
17 | private _readyToWrite: boolean;
18 |
19 | constructor(serialPort: string, args?: EnttecOpenUsbDmxArgs) {
20 | super(serialPort, {
21 | serialPortOptions: {
22 | 'baudRate': 250000,
23 | 'dataBits': 8,
24 | 'stopBits': 2,
25 | 'parity': 'none',
26 | },
27 | sendInterval: args?.dmxSpeed ? (1000 / args.dmxSpeed) : 46,
28 | });
29 |
30 | this._readyToWrite = true;
31 | }
32 |
33 | async sendUniverse(): Promise {
34 | if (!this.serialPort.writable) {
35 | return;
36 | }
37 |
38 | // toggle break
39 | await this.serialPort.set({brk: true, rts: false});
40 | await wait(1);
41 | await this.serialPort.set({brk: false, rts: false});
42 | await wait(1);
43 | if (this._readyToWrite) {
44 | const dataToWrite = Buffer.concat([Buffer.from([0]), this.universeBuffer.slice(1)]);
45 |
46 | this._readyToWrite = false;
47 | this.serialPort.write(dataToWrite);
48 | this.serialPort.drain(() => {
49 | this._readyToWrite = true;
50 | });
51 | }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/drivers/enttec-usb-dmx-pro.ts:
--------------------------------------------------------------------------------
1 | import {AbstractSerialDriver} from './abstract-serial-driver';
2 |
3 | const ENTTEC_PRO_DMX_STARTCODE = 0x00;
4 | const ENTTEC_PRO_START_OF_MSG = 0x7e;
5 | const ENTTEC_PRO_END_OF_MSG = 0xe7;
6 | const ENTTEC_PRO_SEND_DMX_RQ = 0x06;
7 |
8 | // var ENTTEC_PRO_RECV_DMX_PKT = 0x05;
9 |
10 | export interface EnttecUSBDMXProArgs {
11 | dmxSpeed?: number;
12 | }
13 |
14 | export class EnttecUSBDMXProDriver extends AbstractSerialDriver {
15 | private _readyToWrite: boolean;
16 |
17 | constructor(serialPort: string, options: EnttecUSBDMXProArgs = {}) {
18 | super(serialPort, {
19 | serialPortOptions: {
20 | 'baudRate': 250000,
21 | 'dataBits': 8,
22 | 'stopBits': 2,
23 | 'parity': 'none',
24 | },
25 | sendInterval: 1000 / (options.dmxSpeed || 40),
26 | });
27 |
28 | this._readyToWrite = true;
29 | }
30 |
31 | async sendUniverse(): Promise {
32 | if (!this.serialPort.writable) {
33 | return;
34 | }
35 |
36 | if (this._readyToWrite) {
37 | const hdr = Buffer.from([
38 | ENTTEC_PRO_START_OF_MSG,
39 | ENTTEC_PRO_SEND_DMX_RQ,
40 | (this.universeBuffer.length) & 0xff,
41 | ((this.universeBuffer.length) >> 8) & 0xff,
42 | ENTTEC_PRO_DMX_STARTCODE,
43 | ]);
44 |
45 | const msg = Buffer.concat([
46 | hdr,
47 | this.universeBuffer.slice(1),
48 | Buffer.from([ENTTEC_PRO_END_OF_MSG]),
49 | ]);
50 |
51 | this._readyToWrite = false;
52 | this.serialPort.write(msg);
53 | this.serialPort.drain(() => {
54 | this._readyToWrite = true;
55 | });
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/drivers/null.ts:
--------------------------------------------------------------------------------
1 | import {EventEmitter} from 'events';
2 | import {IUniverseDriver} from '../models/IUniverseDriver';
3 |
4 | export interface NullDriverArgs {
5 | dmxSpeed?: number;
6 | }
7 |
8 | export class NullDriver extends EventEmitter implements IUniverseDriver {
9 | constructor(options: NullDriverArgs = {}) {
10 | super();
11 |
12 | this._universe = Buffer.alloc(513, 0);
13 | this._interval = 1000 / (options?.dmxSpeed ?? 1);
14 | }
15 |
16 | async init(): Promise {
17 | this.start();
18 | }
19 |
20 | close(): void {
21 | this.stop();
22 | }
23 |
24 | update(u: {[key: number]: number}, extraData: any): void {
25 | for (const c in u) {
26 | this._universe[c] = u[c];
27 | }
28 | this.logUniverse();
29 |
30 | this.emit('update', u, extraData);
31 | }
32 |
33 | updateAll(v: number): void {
34 | for (let i = 1; i <= 512; i++) {
35 | this._universe[i] = v;
36 | }
37 | }
38 |
39 | get(c: number): number {
40 | return this._universe[c];
41 | }
42 |
43 | logUniverse(): void {
44 | console.log(this._universe.slice(1));
45 | }
46 |
47 | private start(): void {
48 | this._timeout = setInterval(() => {
49 | this.logUniverse();
50 | }, this._interval);
51 | }
52 |
53 | private stop(): void {
54 | clearInterval(this._timeout);
55 | }
56 |
57 | private readonly _universe: Buffer;
58 | private readonly _interval: number;
59 | private _timeout: any;
60 | }
61 |
--------------------------------------------------------------------------------
/src/drivers/sacn.ts:
--------------------------------------------------------------------------------
1 | import {EventEmitter} from 'events';
2 | import {IUniverseDriver, UniverseData} from '../models/IUniverseDriver';
3 | import * as sacn from 'sacn';
4 |
5 | export type SACNOptions = {
6 | sourceName?: string;
7 | priority?: number;
8 | cid?: Buffer;
9 | reuseAddr?: boolean;
10 | interface?: string;
11 | minRefreshRate?: number;
12 | port?: number;
13 | ip?: string;
14 | };
15 |
16 | export class SACNDriver extends EventEmitter implements IUniverseDriver {
17 | sACNServer: sacn.Sender;
18 | universe: any = {};
19 |
20 | constructor(universe = 1, private options: SACNOptions = { reuseAddr: true }) {
21 | super();
22 | this.sACNServer = new sacn.Sender({
23 | universe: universe || 1,
24 | reuseAddr: options.reuseAddr,
25 | iface: options.interface,
26 | minRefreshRate: options.minRefreshRate,
27 | port: options.port,
28 | useUnicastDestination: options.ip,
29 | });
30 | }
31 |
32 | async init(): Promise {
33 | }
34 |
35 | close(): void {
36 | this.sACNServer.close();
37 | }
38 |
39 | update(u: UniverseData, extraData: any): void {
40 | for (const c in u) {
41 | this.universe[c] = SACNDriver.dmxToPercent(u[c]);
42 | }
43 | this.sendUniverse();
44 | }
45 |
46 | sendUniverse(): void {
47 | this.sACNServer.send({
48 | payload: this.universe,
49 | sourceName: this.options.sourceName,
50 | priority: this.options.priority,
51 | cid: this.options.cid,
52 | });
53 | }
54 |
55 | updateAll(v: number): void {
56 | for (let i = 1; i <= 512; i++) {
57 | this.universe[i] = SACNDriver.dmxToPercent(v);
58 | }
59 | this.sendUniverse();
60 | }
61 |
62 | get(c: number): number {
63 | return SACNDriver.percentToDmx(this.universe[c]);
64 | }
65 |
66 | static dmxToPercent(v: number): number {
67 | return v / 255 * 100;
68 | }
69 |
70 | static percentToDmx(v: number): number {
71 | return v / 100 * 255;
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/src/drivers/socketio.ts:
--------------------------------------------------------------------------------
1 | import {IUniverseDriver, UniverseData} from '../models/IUniverseDriver';
2 | import { Server } from 'socket.io';
3 | import {EventEmitter} from 'events';
4 |
5 | export interface SocketIOArgs {
6 | port?: number;
7 | debug?: boolean;
8 | }
9 |
10 | export class SocketIODriver extends EventEmitter implements IUniverseDriver {
11 | universe: Buffer;
12 | server: Server;
13 |
14 | constructor(options: SocketIOArgs) {
15 | super();
16 | options = options || {};
17 |
18 | const port = options.port || 18909;
19 | const debug = options.debug || false;
20 |
21 | this.server = new Server(port, {allowEIO3: true});
22 | this.server.on('connection', (socket) => {
23 | if (debug) console.info(`Client connected [id=${socket.id}]`);
24 | socket.on('disconnect', () => {
25 | if (debug) console.info(`Client gone [id=${socket.id}]`);
26 | });
27 | });
28 |
29 | this.universe = Buffer.alloc(513, 0);
30 | }
31 |
32 | async init(): Promise {
33 | }
34 |
35 | close(): void {
36 | this.server.close();
37 | }
38 |
39 | update(u: UniverseData, extraData: any): void {
40 | for (const c in u) {
41 | this.universe[c] = u[c];
42 | }
43 | this.server.sockets.emit('update', [...this.universe]);
44 | this.emit('update', u, extraData);
45 | }
46 |
47 | updateAll(v: number): void {
48 | for (let i = 1; i <= 512; i++) {
49 | this.universe[i] = v;
50 | }
51 |
52 | this.server.sockets.emit('update', [...this.universe]);
53 | }
54 |
55 | get(c: number): number {
56 | return this.universe[c];
57 | }
58 | }
59 |
60 |
--------------------------------------------------------------------------------
/src/easing.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * based on jQuery Easing v1.3 - http://gsgd.co.uk/sandbox/jquery/easing/
3 | *
4 | * TERMS OF USE - jQuery Easing
5 | *
6 | * Open source under the BSD License.
7 | *
8 | * Copyright © 2008 George McGinley Smith
9 | * All rights reserved.
10 | *
11 | * Redistribution and use in source and binary forms, with or without modification,
12 | * are permitted provided that the following conditions are met:
13 | *
14 | * Redistributions of source code must retain the above copyright notice, this list of
15 | * conditions and the following disclaimer.
16 | * Redistributions in binary form must reproduce the above copyright notice, this list
17 | * of conditions and the following disclaimer in the documentation and/or other materials
18 | * provided with the distribution.
19 | *
20 | * Neither the name of the author nor the names of contributors may be used to endorse
21 | * or promote products derived from this software without specific prior written permission.
22 | *
23 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
24 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
25 | * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
26 | * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
27 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
28 | * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
29 | * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
30 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
31 | * OF THE POSSIBILITY OF SUCH DAMAGE.
32 | *
33 | */
34 |
35 | // t: current time, b: begInnIng value, c: change In value, d: duration
36 |
37 | export const ease = {
38 | linear(t: number, b: number, c: number, d: number): number {
39 | return c * t / d + b;
40 | },
41 | inQuad(t: number, b: number, c: number, d: number): number {
42 | return c * (t /= d) * t + b;
43 | },
44 | outQuad(t: number, b: number, c: number, d: number): number {
45 | return -c * (t /= d) * (t - 2) + b;
46 | },
47 | inOutQuad(t: number, b: number, c: number, d: number): number {
48 | if ((t /= d / 2) < 1) return c / 2 * t * t + b;
49 | return -c / 2 * ((--t) * (t - 2) - 1) + b;
50 | },
51 | inCubic(t: number, b: number, c: number, d: number): number {
52 | return c * (t /= d) * t * t + b;
53 | },
54 | outCubic(t: number, b: number, c: number, d: number): number {
55 | return c * ((t = t / d - 1) * t * t + 1) + b;
56 | },
57 | inOutCubic(t: number, b: number, c: number, d: number): number {
58 | if ((t /= d / 2) < 1) return c / 2 * t * t * t + b;
59 | return c / 2 * ((t -= 2) * t * t + 2) + b;
60 | },
61 | inQuart(t: number, b: number, c: number, d: number): number {
62 | return c * (t /= d) * t * t * t + b;
63 | },
64 | outQuart(t: number, b: number, c: number, d: number): number {
65 | return -c * ((t = t / d - 1) * t * t * t - 1) + b;
66 | },
67 | inOutQuart(t: number, b: number, c: number, d: number): number {
68 | if ((t /= d / 2) < 1) return c / 2 * t * t * t * t + b;
69 | return -c / 2 * ((t -= 2) * t * t * t - 2) + b;
70 | },
71 | inQuint(t: number, b: number, c: number, d: number): number {
72 | return c * (t /= d) * t * t * t * t + b;
73 | },
74 | outQuint(t: number, b: number, c: number, d: number): number {
75 | return c * ((t = t / d - 1) * t * t * t * t + 1) + b;
76 | },
77 | inOutQuint(t: number, b: number, c: number, d: number): number {
78 | if ((t /= d / 2) < 1) return c / 2 * t * t * t * t * t + b;
79 | return c / 2 * ((t -= 2) * t * t * t * t + 2) + b;
80 | },
81 | inSine(t: number, b: number, c: number, d: number): number {
82 | return -c * Math.cos(t / d * (Math.PI / 2)) + c + b;
83 | },
84 | outSine(t: number, b: number, c: number, d: number): number {
85 | return c * Math.sin(t / d * (Math.PI / 2)) + b;
86 | },
87 | inOutSine(t: number, b: number, c: number, d: number): number {
88 | return -c / 2 * (Math.cos(Math.PI * t / d) - 1) + b;
89 | },
90 | inExpo(t: number, b: number, c: number, d: number): number {
91 | return (t === 0) ? b : c * (2 ** (10 * (t / d - 1))) + b;
92 | },
93 | outExpo(t: number, b: number, c: number, d: number): number {
94 | return (t === d) ? b + c : c * (-(2 ** (-10 * t / d)) + 1) + b;
95 | },
96 | inOutExpo(t: number, b: number, c: number, d: number): number {
97 | if (t === 0) return b;
98 | if (t === d) return b + c;
99 | if ((t /= d / 2) < 1) return c / 2 * (2 ** (10 * (t - 1))) + b;
100 | return c / 2 * (-(2 ** (-10 * --t)) + 2) + b;
101 | },
102 | inCirc(t: number, b: number, c: number, d: number): number {
103 | return -c * (Math.sqrt(1 - (t /= d) * t) - 1) + b;
104 | },
105 | outCirc(t: number, b: number, c: number, d: number): number {
106 | return c * Math.sqrt(1 - (t = t / d - 1) * t) + b;
107 | },
108 | inOutCirc(t: number, b: number, c: number, d: number): number {
109 | if ((t /= d / 2) < 1) return -c / 2 * (Math.sqrt(1 - t * t) - 1) + b;
110 | return c / 2 * (Math.sqrt(1 - (t -= 2) * t) + 1) + b;
111 | },
112 | inElastic(t: number, b: number, c: number, d: number): number {
113 | let s = 1.70158; let p = 0; let a = c;
114 |
115 | if (t === 0) return b; if ((t /= d) === 1) return b + c; if (!p) p = d * 0.3;
116 | if (a < Math.abs(c)) {
117 | a = c; s = p / 4;
118 | } else s = p / (2 * Math.PI) * Math.asin(c / a);
119 |
120 | return -(a * (2 ** (10 * (t -= 1))) * Math.sin((t * d - s) * (2 * Math.PI) / p)) + b;
121 | },
122 | outElastic(t: number, b: number, c: number, d: number): number {
123 | let s = 1.70158; let p = 0; let a = c;
124 |
125 | if (t === 0) return b; if ((t /= d) === 1) return b + c; if (!p) p = d * 0.3;
126 | if (a < Math.abs(c)) {
127 | a = c; s = p / 4;
128 | } else s = p / (2 * Math.PI) * Math.asin(c / a);
129 |
130 | return a * (2 ** (-10 * t)) * Math.sin((t * d - s) * (2 * Math.PI) / p) + c + b;
131 | },
132 | inOutElastic(t: number, b: number, c: number, d: number): number {
133 | let s = 1.70158; let p = 0; let a = c;
134 |
135 | if (t === 0) return b; if ((t /= d / 2) === 2) return b + c; if (!p) p = d * (0.3 * 1.5);
136 | if (a < Math.abs(c)) {
137 | a = c; s = p / 4;
138 | } else s = p / (2 * Math.PI) * Math.asin(c / a);
139 |
140 | if (t < 1) return -0.5 * (a * (2 ** (10 * (t -= 1))) * Math.sin((t * d - s) * (2 * Math.PI) / p)) + b;
141 | return a * (2 ** (-10 * (t -= 1))) * Math.sin((t * d - s) * (2 * Math.PI) / p) * 0.5 + c + b;
142 | },
143 | inBack(t: number, b: number, c: number, d: number, s: number): number {
144 | if (s === undefined) s = 1.70158;
145 | return c * (t /= d) * t * ((s + 1) * t - s) + b;
146 | },
147 | outBack(t: number, b: number, c: number, d: number, s: number): number {
148 | if (s === undefined) s = 1.70158;
149 | return c * ((t = t / d - 1) * t * ((s + 1) * t + s) + 1) + b;
150 | },
151 | inOutBack(t: number, b: number, c: number, d: number, s: number): number {
152 | if (s === undefined) s = 1.70158;
153 | if ((t /= d / 2) < 1) return c / 2 * (t * t * (((s *= (1.525)) + 1) * t - s)) + b;
154 | return c / 2 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2) + b;
155 | },
156 | inBounce(t: number, b: number, c: number, d: number): number {
157 | return c - exports.ease.outBounce(d - t, 0, c, d) + b;
158 | },
159 | outBounce(t: number, b: number, c: number, d: number): number {
160 | if ((t /= d) < (1 / 2.75)) {
161 | return c * (7.5625 * t * t) + b;
162 | } else if (t < (2 / 2.75)) {
163 | return c * (7.5625 * (t -= (1.5 / 2.75)) * t + 0.75) + b;
164 | } else if (t < (2.5 / 2.75)) {
165 | return c * (7.5625 * (t -= (2.25 / 2.75)) * t + 0.9375) + b;
166 | }
167 | return c * (7.5625 * (t -= (2.625 / 2.75)) * t + 0.984375) + b;
168 |
169 | },
170 | inOutBounce(t: number, b: number, c: number, d: number): number {
171 | if (t < d / 2) return exports.ease.inBounce(t * 2, 0, c, d) * 0.5 + b;
172 | return exports.ease.outBounce(t * 2 - d, 0, c, d) * 0.5 + c * 0.5 + b;
173 | },
174 | };
175 |
176 | /*
177 | *
178 | * TERMS OF USE - EASING EQUATIONS
179 | *
180 | * Open source under the BSD License.
181 | *
182 | * Copyright © 2001 Robert Penner
183 | * All rights reserved.
184 | *
185 | * Redistribution and use in source and binary forms, with or without modification,
186 | * are permitted provided that the following conditions are met:
187 | *
188 | * Redistributions of source code must retain the above copyright notice, this list of
189 | * conditions and the following disclaimer.
190 | * Redistributions in binary form must reproduce the above copyright notice, this list
191 | * of conditions and the following disclaimer in the documentation and/or other materials
192 | * provided with the distribution.
193 | *
194 | * Neither the name of the author nor the names of contributors may be used to endorse
195 | * or promote products derived from this software without specific prior written permission.
196 | *
197 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
198 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
199 | * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
200 | * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
201 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
202 | * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
203 | * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
204 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
205 | * OF THE POSSIBILITY OF SUCH DAMAGE.
206 | *
207 | */
208 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | import { DMX } from './DMX';
2 | import { Animation } from './Animation';
3 | import { ArtnetDriver } from './drivers/artnet';
4 | import { BBDMXDriver } from './drivers/bbdmx';
5 | import { DMX4AllDriver } from './drivers/dmx4all';
6 | import { DMXKingUltraDMXProDriver } from './drivers/dmxking-ultra-dmx-pro';
7 | import { EnttecUSBDMXProDriver } from './drivers/enttec-usb-dmx-pro';
8 | import { NullDriver } from './drivers/null';
9 | import { SocketIODriver } from './drivers/socketio';
10 | import { EnttecOpenUSBDMXDriver } from './drivers/enttec-open-usb-dmx';
11 | import { SACNDriver } from './drivers/sacn';
12 | import { IUniverseDriver, UniverseData } from './models/IUniverseDriver';
13 |
14 | export {
15 | DMX,
16 | Animation,
17 | };
18 | export {
19 | IUniverseDriver,
20 | UniverseData,
21 | };
22 | export {
23 | ArtnetDriver,
24 | BBDMXDriver,
25 | DMX4AllDriver,
26 | DMXKingUltraDMXProDriver,
27 | EnttecOpenUSBDMXDriver,
28 | EnttecUSBDMXProDriver,
29 | NullDriver,
30 | SocketIODriver,
31 | SACNDriver,
32 | };
33 |
--------------------------------------------------------------------------------
/src/models/Events.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-unused-vars */
2 | export enum Events {
3 | update = 'update',
4 | updateAll = 'updateAll',
5 | }
6 |
--------------------------------------------------------------------------------
/src/models/IUniverseDriver.ts:
--------------------------------------------------------------------------------
1 | import { EventEmitter } from 'events';
2 |
3 | export type UniverseData = {
4 | [key: number]: number;
5 | }
6 |
7 | export interface IUniverseDriver extends EventEmitter {
8 | init(): Promise;
9 | update(channels: UniverseData, extraData?: any): void;
10 |
11 | get(channel: number): number;
12 |
13 | updateAll(value: number): void;
14 |
15 | close(): Promise | void;
16 | }
17 |
--------------------------------------------------------------------------------
/src/util/time.ts:
--------------------------------------------------------------------------------
1 | export const wait = (millis: number): Promise => new Promise((resolve) => setTimeout(resolve, millis));
2 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "commonjs",
4 | "esModuleInterop": true,
5 | "noImplicitAny": true,
6 | "removeComments": false,
7 | "preserveConstEnums": true,
8 | "inlineSourceMap": true,
9 | "declaration": true,
10 | "outDir": "dist",
11 | "moduleResolution": "node",
12 | "forceConsistentCasingInFileNames": false,
13 | "target": "ES6",
14 | "strict": true,
15 | "lib": [
16 | "es2018",
17 | "esnext"
18 | ]
19 | },
20 | "include": [
21 | "src/**/*",
22 | "spec/**/*"
23 | ],
24 | "exclude": [
25 | "node_modules",
26 | "dist"
27 | ]
28 | }
29 |
--------------------------------------------------------------------------------