From 34c39251173d48bf11ff6a6a807ae5678a2e2ab2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1vid=20Szak=C3=A1llas?= Date: Sun, 10 Dec 2023 14:41:26 +0100 Subject: [PATCH] chore: extract common control templating --- package-lock.json | 268 +++++++++++------- package.json | 1 + packages/launch-common/package.json | 11 + packages/launch-common/src/Control.ts | 85 ++++++ packages/launch-common/src/index.ts | 0 packages/launch-common/tsconfig.json | 15 + packages/launchcontrol-common/package.json | 3 +- packages/launchpad-common/package.json | 3 +- packages/launchpad-common/src/Control.ts | 195 ++++--------- .../launchpad-common/src/controls/beatjump.ts | 117 ++++---- .../launchpad-common/src/controls/beatloop.ts | 51 ++-- packages/launchpad-common/src/controls/cue.ts | 74 ++--- .../launchpad-common/src/controls/grid.ts | 65 +++-- .../launchpad-common/src/controls/hotcue.ts | 110 +++---- .../launchpad-common/src/controls/index.ts | 3 +- packages/launchpad-common/src/controls/key.ts | 80 +++--- .../launchpad-common/src/controls/keyshift.ts | 60 ++-- .../launchpad-common/src/controls/load.ts | 76 ++--- .../launchpad-common/src/controls/loopIo.ts | 63 ++-- .../src/controls/loopMultiply.ts | 39 +-- .../launchpad-common/src/controls/loopjump.ts | 118 ++++---- .../src/controls/loopjumpSmall.ts | 54 ++-- .../launchpad-common/src/controls/nudge.ts | 140 ++++----- packages/launchpad-common/src/controls/pfl.ts | 43 +-- .../launchpad-common/src/controls/play.ts | 61 ++-- .../launchpad-common/src/controls/quantize.ts | 47 +-- .../launchpad-common/src/controls/reloop.ts | 59 ++-- .../src/controls/samplerPad.ts | 122 ++++---- .../launchpad-common/src/controls/slip.ts | 57 ++-- .../launchpad-common/src/controls/sync.ts | 88 +++--- packages/launchpad-common/src/controls/tap.ts | 64 +++-- packages/launchpad-common/src/device.ts | 8 +- .../mixxx/src/{globals.d.ts => global.d.ts} | 0 tsconfig.json | 3 + 34 files changed, 1222 insertions(+), 961 deletions(-) create mode 100644 packages/launch-common/package.json create mode 100644 packages/launch-common/src/Control.ts create mode 100644 packages/launch-common/src/index.ts create mode 100644 packages/launch-common/tsconfig.json rename packages/mixxx/src/{globals.d.ts => global.d.ts} (100%) diff --git a/package-lock.json b/package-lock.json index cda0126..9baf514 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "workspaces": [ "packages/common", "packages/mixxx", + "packages/launch-common", "packages/launchpad-common", "packages/launchpad-mk1", "packages/launchpad-mk2", @@ -56,11 +57,13 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.21.4", + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz", + "integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/highlight": "^7.18.6" + "@babel/highlight": "^7.23.4", + "chalk": "^2.4.2" }, "engines": { "node": ">=6.9.0" @@ -131,11 +134,12 @@ } }, "node_modules/@babel/helper-annotate-as-pure": { - "version": "7.18.6", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz", + "integrity": "sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/types": "^7.18.6" + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -172,18 +176,20 @@ } }, "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.21.0", + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.23.5.tgz", + "integrity": "sha512-QELlRWxSpgdwdJzSJn4WAhKC+hvw/AtHbbrIoncKHkhKKR/luAlKkgBDcri1EzWAo8f8VvYVryEHN4tax/V67A==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-function-name": "^7.21.0", - "@babel/helper-member-expression-to-functions": "^7.21.0", - "@babel/helper-optimise-call-expression": "^7.18.6", - "@babel/helper-replace-supers": "^7.20.7", - "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0", - "@babel/helper-split-export-declaration": "^7.18.6" + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-member-expression-to-functions": "^7.23.0", + "@babel/helper-optimise-call-expression": "^7.22.5", + "@babel/helper-replace-supers": "^7.22.20", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "semver": "^6.3.1" }, "engines": { "node": ">=6.9.0" @@ -224,9 +230,10 @@ } }, "node_modules/@babel/helper-environment-visitor": { - "version": "7.18.9", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", "dev": true, - "license": "MIT", "engines": { "node": ">=6.9.0" } @@ -243,12 +250,13 @@ } }, "node_modules/@babel/helper-function-name": { - "version": "7.21.0", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/template": "^7.20.7", - "@babel/types": "^7.21.0" + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" }, "engines": { "node": ">=6.9.0" @@ -266,60 +274,65 @@ } }, "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.21.0", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.23.0.tgz", + "integrity": "sha512-6gfrPwh7OuT6gZyJZvd6WbTfrqAo7vm4xCzAXOusKqq/vWdKXphTpj5klHKNmRUU6/QRGlBsyU9mAIPaWHlqJA==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/types": "^7.21.0" + "@babel/types": "^7.23.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-imports": { - "version": "7.18.6", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz", + "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/types": "^7.18.6" + "@babel/types": "^7.22.15" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.21.2", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz", + "integrity": "sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-module-imports": "^7.18.6", - "@babel/helper-simple-access": "^7.20.2", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/helper-validator-identifier": "^7.19.1", - "@babel/template": "^7.20.7", - "@babel/traverse": "^7.21.2", - "@babel/types": "^7.21.2" + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-module-imports": "^7.22.15", + "@babel/helper-simple-access": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/helper-validator-identifier": "^7.22.20" }, "engines": { "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" } }, "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.18.6", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz", + "integrity": "sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/types": "^7.18.6" + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.20.2", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz", + "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==", "dev": true, - "license": "MIT", "engines": { "node": ">=6.9.0" } @@ -342,74 +355,81 @@ } }, "node_modules/@babel/helper-replace-supers": { - "version": "7.20.7", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.22.20.tgz", + "integrity": "sha512-qsW0In3dbwQUbK8kejJ4R7IHVGwHJlV6lpG6UA7a9hSa2YEiAib+N1T2kr6PEeUT+Fl7najmSOS6SmAwCHK6Tw==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-member-expression-to-functions": "^7.20.7", - "@babel/helper-optimise-call-expression": "^7.18.6", - "@babel/template": "^7.20.7", - "@babel/traverse": "^7.20.7", - "@babel/types": "^7.20.7" + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-member-expression-to-functions": "^7.22.15", + "@babel/helper-optimise-call-expression": "^7.22.5" }, "engines": { "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" } }, "node_modules/@babel/helper-simple-access": { - "version": "7.20.2", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", + "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/types": "^7.20.2" + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.20.0", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.22.5.tgz", + "integrity": "sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/types": "^7.20.0" + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-split-export-declaration": { - "version": "7.18.6", + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/types": "^7.18.6" + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-string-parser": { - "version": "7.19.4", + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz", + "integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==", "dev": true, - "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.19.1", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", "dev": true, - "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-option": { - "version": "7.21.0", + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz", + "integrity": "sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==", "dev": true, - "license": "MIT", "engines": { "node": ">=6.9.0" } @@ -442,12 +462,13 @@ } }, "node_modules/@babel/highlight": { - "version": "7.18.6", + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz", + "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.18.6", - "chalk": "^2.0.0", + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", "js-tokens": "^4.0.0" }, "engines": { @@ -455,9 +476,10 @@ } }, "node_modules/@babel/parser": { - "version": "7.21.2", + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.5.tgz", + "integrity": "sha512-hOOqoiNXrmGdFbhgCzu6GiURxUgM27Xwd/aPuu8RfHEZPBzL1Z54okAHAQjXfcQNwvrlkAmAp4SlRTZ45vlthQ==", "dev": true, - "license": "MIT", "bin": { "parser": "bin/babel-parser.js" }, @@ -812,6 +834,21 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.23.3.tgz", + "integrity": "sha512-EB2MELswq55OHUoRZLGg/zC7QWUKfNLpE57m/S2yr1uEneIgsTgrSzXP3NXEsMkVn76OlaVVnzN+ugObuYGwhg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/plugin-syntax-logical-assignment-operators": { "version": "7.10.4", "dev": true, @@ -907,11 +944,12 @@ } }, "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.20.0", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.23.3.tgz", + "integrity": "sha512-9EiNjVJOMwCO+43TqoTrgQ8jMwcAd0sWyXi9RPfIsLTj4R2MADDDQXELhffaUx/uJv2AYcxBgPwH6j4TIA4ytQ==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.19.0" + "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -1147,13 +1185,14 @@ } }, "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.21.2", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.23.3.tgz", + "integrity": "sha512-aVS0F65LKsdNOtcz6FRCpE4OgsP2OFnW46qNxNIX9h3wuzaNcSQsJysuMwqSibC98HPrf2vCgtxKNwS0DAlgcA==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-module-transforms": "^7.21.2", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/helper-simple-access": "^7.20.2" + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-simple-access": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -1386,13 +1425,15 @@ } }, "node_modules/@babel/plugin-transform-typescript": { - "version": "7.21.0", + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.23.5.tgz", + "integrity": "sha512-2fMkXEJkrmwgu2Bsv1Saxgj30IXZdJ+84lQcKKI7sm719oXs0BBw2ZENKdJdR1PjWndgLCEBNXJOri0fk7RYQA==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.21.0", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/plugin-syntax-typescript": "^7.20.0" + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-create-class-features-plugin": "^7.23.5", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-typescript": "^7.23.3" }, "engines": { "node": ">=6.9.0" @@ -1534,13 +1575,16 @@ } }, "node_modules/@babel/preset-typescript": { - "version": "7.21.0", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.23.3.tgz", + "integrity": "sha512-17oIGVlqz6CchO9RFYn5U6ZpWRZIngayYCtrPRSgANSwC2V1Jb+iP74nVxzzXJte8b8BYxrL1yY96xfhTBrNNQ==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/helper-validator-option": "^7.21.0", - "@babel/plugin-transform-typescript": "^7.21.0" + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-validator-option": "^7.22.15", + "@babel/plugin-syntax-jsx": "^7.23.3", + "@babel/plugin-transform-modules-commonjs": "^7.23.3", + "@babel/plugin-transform-typescript": "^7.23.3" }, "engines": { "node": ">=6.9.0" @@ -1586,13 +1630,14 @@ "license": "MIT" }, "node_modules/@babel/template": { - "version": "7.20.7", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", + "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.18.6", - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7" + "@babel/code-frame": "^7.22.13", + "@babel/parser": "^7.22.15", + "@babel/types": "^7.22.15" }, "engines": { "node": ">=6.9.0" @@ -1619,12 +1664,13 @@ } }, "node_modules/@babel/types": { - "version": "7.21.2", + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.5.tgz", + "integrity": "sha512-ON5kSOJwVO6xXVRTvOI0eOnWe7VdUcIpsovGo9U/Br4Ie4UVFQTboO2cYnDhAGU6Fp+UxSiT+pMft0SMHfuq6w==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.19.4", - "@babel/helper-validator-identifier": "^7.19.1", + "@babel/helper-string-parser": "^7.23.4", + "@babel/helper-validator-identifier": "^7.22.20", "to-fast-properties": "^2.0.0" }, "engines": { @@ -1801,6 +1847,10 @@ "resolved": "packages/common", "link": true }, + "node_modules/@mixxx-launch/launch-common": { + "resolved": "packages/launch-common", + "link": true + }, "node_modules/@mixxx-launch/launchcontrol-common": { "resolved": "packages/launchcontrol-common", "link": true @@ -4984,8 +5034,9 @@ }, "node_modules/js-tokens": { "version": "4.0.0", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true }, "node_modules/js-yaml": { "version": "4.1.0", @@ -9590,9 +9641,10 @@ } }, "node_modules/semver": { - "version": "6.3.0", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, - "license": "ISC", "bin": { "semver": "bin/semver.js" } @@ -10413,11 +10465,20 @@ "@mixxx-launchpad/control-app": "1.0.0" } }, + "packages/launch-common": { + "name": "@mixxx-launch/launch-common", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "@mixxx-launch/mixxx": "~1.0.0" + } + }, "packages/launchcontrol-common": { "name": "@mixxx-launch/launchcontrol-common", "version": "1.0.0", "license": "MIT", "dependencies": { + "@mixxx-launch/launch-common": "~1.0.0", "@mixxx-launch/mixxx": "~1.0.0" } }, @@ -10434,6 +10495,7 @@ "version": "1.0.0", "license": "MIT", "dependencies": { + "@mixxx-launch/launch-common": "~1.0.0", "@mixxx-launch/mixxx": "~1.0.0", "eventemitter3": "~5.0.0" } diff --git a/package.json b/package.json index 1dacf38..455cd23 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ "workspaces": [ "packages/common", "packages/mixxx", + "packages/launch-common", "packages/launchpad-common", "packages/launchpad-mk1", "packages/launchpad-mk2", diff --git a/packages/launch-common/package.json b/packages/launch-common/package.json new file mode 100644 index 0000000..cd960c2 --- /dev/null +++ b/packages/launch-common/package.json @@ -0,0 +1,11 @@ +{ + "name": "@mixxx-launch/launch-common", + "author": "Midiparse", + "license": "MIT", + "private": true, + "version": "1.0.0", + "main": "src/index.ts", + "dependencies": { + "@mixxx-launch/mixxx": "~1.0.0" + } +} diff --git a/packages/launch-common/src/Control.ts b/packages/launch-common/src/Control.ts new file mode 100644 index 0000000..95459c9 --- /dev/null +++ b/packages/launch-common/src/Control.ts @@ -0,0 +1,85 @@ +import { + Component, +} from '@mixxx-launch/mixxx' + +export type BindingTemplate = { + type: new (...args: any[]) => Component, + target: any, + listeners: { + [_: string]: (control: any) => any + } +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +export type Phantom<_Ctx> = never + +export type ControlType < Ctx > = { + type: string + bindings: { [k: string | number | symbol]: BindingTemplate } + params: Params + state: State + context?: Phantom +} + +export type State = { [k: string]: any } +export type Params = { [k: string]: any } + +export type ControlTemplate> = { + bindings: C['bindings'] + state: C['state'] +} + +export type MakeControlTemplate> = ( + params: C['params'] +) => ControlTemplate + +export type MakeBindings> = (ctx: Ctx, template: C["bindings"]) => Bindings + +export type Bindings> = { + [K in keyof C["bindings"]]: InstanceType +} + +export type IControl> = { + bindings: Bindings + state: C['state'] + context: Ctx +} + +export class Control> extends Component implements IControl { + templates: C['bindings'] + bindings: Bindings + state: C['state'] + context: Ctx + + constructor(makeBindings: MakeBindings, templates: C['bindings'], state: C['state'], context: Ctx) { + super() + this.bindings = makeBindings(context, templates) + this.templates = templates + this.state = state + + this.context = context + } + + onMount() { + super.onMount() + + Object.keys(this.bindings).forEach((k) => { + const b = this.bindings[k] + Object.keys(this.templates[k].listeners).forEach((event) => { + const listener = this.templates[k].listeners[event] + if (listener != null) { + b.addListener(event, listener(this)) + } + }) + }) + + Object.values(this.bindings).forEach((b) => b.mount()) + } + + onUnmount() { + const bs = Object.values(this.bindings) + bs.forEach((b) => b.unmount()) + bs.forEach((b) => b.removeAllListeners()) + super.onUnmount() + } +} diff --git a/packages/launch-common/src/index.ts b/packages/launch-common/src/index.ts new file mode 100644 index 0000000..e69de29 diff --git a/packages/launch-common/tsconfig.json b/packages/launch-common/tsconfig.json new file mode 100644 index 0000000..a422be6 --- /dev/null +++ b/packages/launch-common/tsconfig.json @@ -0,0 +1,15 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "composite": true, + "outDir": "dist" + }, + "include": [ + "src" + ], + "references": [ + { + "path": "../mixxx" + } + ] +} diff --git a/packages/launchcontrol-common/package.json b/packages/launchcontrol-common/package.json index 0ca6bdc..516ebf6 100644 --- a/packages/launchcontrol-common/package.json +++ b/packages/launchcontrol-common/package.json @@ -6,6 +6,7 @@ "version": "1.0.0", "main": "src/index.ts", "dependencies": { - "@mixxx-launch/mixxx": "~1.0.0" + "@mixxx-launch/mixxx": "~1.0.0", + "@mixxx-launch/launch-common": "~1.0.0" } } diff --git a/packages/launchpad-common/package.json b/packages/launchpad-common/package.json index 65394d3..542f90b 100644 --- a/packages/launchpad-common/package.json +++ b/packages/launchpad-common/package.json @@ -7,6 +7,7 @@ "license": "MIT", "dependencies": { "eventemitter3": "~5.0.0", - "@mixxx-launch/mixxx": "~1.0.0" + "@mixxx-launch/mixxx": "~1.0.0", + "@mixxx-launch/launch-common": "~1.0.0" } } diff --git a/packages/launchpad-common/src/Control.ts b/packages/launchpad-common/src/Control.ts index 9a707da..7a76a39 100644 --- a/packages/launchpad-common/src/Control.ts +++ b/packages/launchpad-common/src/Control.ts @@ -1,169 +1,86 @@ -import type { Modifier } from './ModifierSidebar' - import { array, map, range } from '@mixxx-launch/common' +import { ChannelControlDef, Component, ControlMessage, MidiMessage } from '@mixxx-launch/mixxx' +import { Theme } from './App' +import { Bindings, Control as BaseControl, ControlTemplate, ControlType as BaseControlType, IControl as BaseIControl, MakeControlTemplate } from '@mixxx-launch/launch-common/src/Control' -import { - ChannelControlDef, - Component, - ControlComponent, - ControlDef, - ControlMessage, - MidiMessage, -} from '@mixxx-launch/mixxx' -import { LaunchpadDevice, MidiComponent } from './device' +import { ControlComponent, ControlDef, getValue, root } from '@mixxx-launch/mixxx/src/Control' import makeControlTemplateIndex, { ControlTypeIndex } from './controls' import { default as makeSamplerPad } from './controls/samplerPad' -import { getValue, root, SamplerControlDef } from '@mixxx-launch/mixxx/src/Control' -import { Theme } from './App' +import { LaunchpadDevice, MidiComponent } from './device' +import { Modifier } from './ModifierSidebar' +// constraint types with Launchpad specific Context export type ControlContext = { modifier: Modifier device: LaunchpadDevice } -export type ControlType = { - type: string - bindings: { [k: string]: Binding } - params: Params - state: State -} -export type ControlConf = { - type: C['type'] - params?: C['params'] -} +export type ControlType = BaseControlType +export type Control = BaseControl +export type IControl = BaseIControl -export type ControlBindingTemplate = { - type: 'control' - target: ControlDef - update?: (c: Control) => (message: ControlMessage) => void - mount?: (c: Control) => () => void - unmount?: (c: Control) => () => void -} +export type MakeSamplerControlTemplate = MakeControlTemplate -export type ButtonKey = [number, number] +export type MakeDeckControlTemplate = MakeControlTemplate -export type ButtonBindingTemplate = { - type: 'button' - target: ButtonKey - midi?: (c: Control) => (message: MidiMessage) => void - mount?: (c: Control) => () => void - unmount?: (c: Control) => () => void +export type DeckPresetConf = { + deck: readonly { pos: [number, number]; control: { + type: ControlTypeIndex['type'] + params?: Omit + } }[] } -export type BindingTemplate = B extends ControlComponent - ? ControlBindingTemplate - : ButtonBindingTemplate - -export type State = { [k: string]: any } -export type Params = { [k: string]: any } - -export type ControlTemplate = { - bindings: { - [Prop in keyof C['bindings']]: BindingTemplate - } - state: C['state'] +export type SamplerPalettePresetConf = { + samplerPalette: { n: number; offset: number; rows: number } } -export type MakeControlTemplate = ( - params: C['params'], - gridPosition: [number, number], - deck: D, - theme: Theme, -) => ControlTemplate - -export type MakeSamplerControlTemplate = MakeControlTemplate +export const isDeckPresetConf = (p: PresetConf): p is DeckPresetConf => 'deck' in p +export const isSamplerPalettePresetConf = (p: PresetConf): p is SamplerPalettePresetConf => 'samplerPalette' in p -export type MakeDeckControlTemplate = MakeControlTemplate +export type PresetConf = DeckPresetConf | SamplerPalettePresetConf -export type Binding = ControlComponent | MidiComponent -export type IControl = { - bindings: C['bindings'] - state: C['state'] - context: ControlContext +export type ControlBindingTemplate = { + type: new (...args: any[]) => ControlComponent + target: ControlDef + listeners: { + update?: (c: Control) => (message: ControlMessage) => void + mount?: (c: Control) => () => void + unmount?: (c: Control) => () => void + } } -const controlListeners = ['update', 'mount', 'unmount'] as const - -const midiListeners = ['midi', 'mount', 'unmount'] as const - -const nameOf = (x: number, y: number) => `${7 - y},${x}` +export type ButtonKey = readonly [number, number] -export class Control extends Component implements IControl { - bindings: C['bindings'] - bindingTemplates: ControlTemplate['bindings'] - state: C['state'] - context: ControlContext - - constructor(ctx: ControlContext, controlTemplate: ControlTemplate) { - super() - const bindings: { [k: string]: any } = {} - for (const k in controlTemplate.bindings) { - const bt = controlTemplate.bindings[k] - bindings[k] = - bt.type == 'control' - ? new ControlComponent(bt.target) - : new MidiComponent(ctx.device, ctx.device.controls[nameOf(...bt.target)]) - } - this.bindingTemplates = controlTemplate.bindings - this.bindings = bindings - this.state = controlTemplate.state - this.context = ctx +export type ButtonBindingTemplate = { + type: new (...args: any[]) => MidiComponent + target: ButtonKey + listeners: { + midi?: (c: Control) => (message: MidiMessage) => void + mount?: (c: Control) => () => void + unmount?: (c: Control) => () => void } +} - onMount() { - super.onMount() +export type BindingTemplates = { + [K: string]: ButtonBindingTemplate | ControlBindingTemplate +} - Object.keys(this.bindings).forEach((k) => { - const b = this.bindings[k] - if (b instanceof ControlComponent) { - const bt = this.bindingTemplates[k] as ControlBindingTemplate - controlListeners.forEach((event) => { - const listener = bt[event] - if (listener != null) { - b.addListener(event, listener(this)) - } - }) - } else { - const bt = this.bindingTemplates[k] as ButtonBindingTemplate - midiListeners.forEach((event) => { - const listener = bt[event] - if (listener) { - b.addListener(event, listener(this)) - } - }) - // add a default handler to clear the button LED - b.addListener('unmount', () => { - this.context.device.clearColor(b.control) - }) - } - }) - Object.values(this.bindings).forEach((b) => b.mount()) - } +const nameOf = (x: number, y: number) => `${7 - y},${x}` - onUnmount() { - const bs = Object.values(this.bindings) - bs.forEach((b) => b.unmount()) - bs.forEach((b) => b.removeAllListeners()) - super.onUnmount() +const makeBindings = (ctx: ControlContext, t: BindingTemplates): Bindings => { + const ret: { [_: string]: any } = {} + for (const k in t) { + ret[k] = t[k].type === ControlComponent + ? new ControlComponent((t[k] as ControlBindingTemplate).target) + : new MidiComponent(ctx.device, ctx.device.controls[nameOf(...((t[k] as ButtonBindingTemplate).target))]) } + return ret as Bindings } -export type DeckPresetConf = { - deck: readonly { pos: [number, number]; control: ControlConf }[] -} - -export type SamplerPalettePresetConf = { - samplerPalette: { n: number; offset: number; rows: number } -} - -export const isDeckPresetConf = (p: PresetConf): p is DeckPresetConf => 'deck' in p -export const isSamplerPalettePresetConf = (p: PresetConf): p is SamplerPalettePresetConf => 'samplerPalette' in p - -export type PresetConf = DeckPresetConf | SamplerPalettePresetConf type PresetTemplate = { controls: ControlTemplate[] @@ -178,9 +95,11 @@ export class Preset extends Component implements IPreset { constructor(ctx: ControlContext, presetTemplate: PresetTemplate) { super() - this.controls = presetTemplate.controls.map((c) => new Control(ctx, c)) + this.controls = presetTemplate.controls.map((c) => { + return new BaseControl(makeBindings, c.bindings, c.state, ctx) + }) } - + onMount() { super.onMount() for (const control of this.controls) { @@ -205,7 +124,7 @@ const makeDeckPresetTemplate = ( theme: Theme, ): PresetTemplate => ({ controls: conf.deck.map(({ pos, control: { type, params } }) => - makeControlTemplateIndex[type](params as unknown as any, tr(gridPosition, pos), deck, theme), + makeControlTemplateIndex[type](Object.assign({theme, gridPosition: tr(gridPosition, pos), deck}, params) as unknown as any), ), }) @@ -218,7 +137,7 @@ const makeSamplerPalettePresetTemplate = ( controls: array(map((i) => { const dy = 7 - ~~(i / rows) const dx = i % rows - return makeSamplerPad({}, tr(gridPosition, [dx, dy]), root.samplers[i + offset], theme) + return makeSamplerPad({ theme, gridPosition: tr(gridPosition, [dx, dy]), sampler: root.samplers[i + offset]}) }, range(Math.min(n, getValue(root.master.num_samplers))))), }) @@ -234,3 +153,5 @@ export const makePresetTemplate = ( return makeSamplerPalettePresetTemplate(conf, gridPosition, channel, theme) } } + + diff --git a/packages/launchpad-common/src/controls/beatjump.ts b/packages/launchpad-common/src/controls/beatjump.ts index ab32c91..989af4d 100644 --- a/packages/launchpad-common/src/controls/beatjump.ts +++ b/packages/launchpad-common/src/controls/beatjump.ts @@ -1,13 +1,14 @@ import { posMod } from '@mixxx-launch/common' -import { setValue } from '@mixxx-launch/mixxx' +import { ChannelControlDef, MidiComponent, setValue } from '@mixxx-launch/mixxx' -import { Control, MakeDeckControlTemplate } from '../Control' -import { MidiComponent } from '../device' +import { ButtonBindingTemplate, MakeDeckControlTemplate, Control } from '../Control' import { modes, ModifierState, retainAttackMode } from '../ModifierSidebar' export type Type = { type: 'beatjump' params: { + deck: ChannelControlDef, + gridPosition: [number, number] jumps: readonly [number, number][] vertical?: boolean } @@ -17,13 +18,13 @@ export type Type = { diff: number set: number } - bindings: { [k: number]: MidiComponent } + bindings: { [k: number]: ButtonBindingTemplate } } const colors = ['green', 'red'] as const -const make: MakeDeckControlTemplate = ({ jumps, vertical = false }, gridPosition, deck) => { - const bindings: { [k: string]: any } = {} +const make: MakeDeckControlTemplate = ({ deck, gridPosition, jumps, vertical = false }) => { + const bindings: Type['bindings'] = {} const spec = jumps.flatMap((j) => [ [j, -1], [j, 1], @@ -31,72 +32,74 @@ const make: MakeDeckControlTemplate = ({ jumps, vertical = false }, gridPo const onMidi = (k: number, j: [number, number], d: number) => - ({ bindings, state, context: { modifier, device } }: Control) => - retainAttackMode(modifier, (mode: ModifierState, { value }) => { - modes( - mode, - () => { - if (!state.mode) { - if (value) { - setValue(deck.beatjump, j[state.set] * d) - } - } else { - if (value) { - const currentJump = j[state.set] * d - setValue(deck.beatjump, currentJump) - if (state.pressing != null) { - device.sendColor(bindings[state.pressing].control, device.colors[`lo_${colors[state.set]}`]) + ({ bindings, state, context: { modifier, device } }: Control) => + retainAttackMode(modifier, (mode: ModifierState, { value }) => { + modes( + mode, + () => { + if (!state.mode) { + if (value) { + setValue(deck.beatjump, j[state.set] * d) } - device.sendColor(bindings[k].control, device.colors[`hi_${colors[state.set]}`]) - state.pressing = k - state.diff = state.diff + currentJump } else { - if (state.pressing === k) { - device.sendColor(bindings[k].control, device.colors[`lo_${colors[state.set]}`]) - state.pressing = null - setValue(deck.beatjump, -state.diff) - state.diff = 0 + if (value) { + const currentJump = j[state.set] * d + setValue(deck.beatjump, currentJump) + if (state.pressing != null) { + device.sendColor(bindings[state.pressing].control, device.colors[`lo_${colors[state.set]}`]) + } + device.sendColor(bindings[k].control, device.colors[`hi_${colors[state.set]}`]) + state.pressing = k + state.diff = state.diff + currentJump + } else { + if (state.pressing === k) { + device.sendColor(bindings[k].control, device.colors[`lo_${colors[state.set]}`]) + state.pressing = null + setValue(deck.beatjump, -state.diff) + state.diff = 0 + } } } - } - }, - () => { - if (value) { - state.set = posMod(state.set + 1, 2) - const prefix = state.mode ? 'lo' : 'hi' - for (let b = 0; b < spec.length; ++b) { - device.sendColor(bindings[b].control, device.colors[`${prefix}_${colors[state.set]}`]) + }, + () => { + if (value) { + state.set = posMod(state.set + 1, 2) + const prefix = state.mode ? 'lo' : 'hi' + for (let b = 0; b < spec.length; ++b) { + device.sendColor(bindings[b].control, device.colors[`${prefix}_${colors[state.set]}`]) + } } - } - }, - () => { - if (value) { - state.mode = !state.mode - const prefix = state.mode ? 'lo' : 'hi' - for (let b = 0; b < spec.length; ++b) { - device.sendColor(bindings[b].control, device.colors[`${prefix}_${colors[state.set]}`]) + }, + () => { + if (value) { + state.mode = !state.mode + const prefix = state.mode ? 'lo' : 'hi' + for (let b = 0; b < spec.length; ++b) { + device.sendColor(bindings[b].control, device.colors[`${prefix}_${colors[state.set]}`]) + } } - } - }, - ) - }) + }, + ) + }) const onMount = (k: number) => - ({ bindings, state, context: { device } }: Control) => - () => { - const prefix = state.mode ? 'lo' : 'hi' + ({ bindings, state, context: { device } }: Control) => + () => { + const prefix = state.mode ? 'lo' : 'hi' - device.sendColor(bindings[k].control, device.colors[`${prefix}_${colors[state.set]}`]) - } + device.sendColor(bindings[k].control, device.colors[`${prefix}_${colors[state.set]}`]) + } spec.forEach(([jump, dir], i) => { bindings[i] = { - type: 'button', + type: MidiComponent, target: vertical ? [gridPosition[0] + (i % 2), gridPosition[1] + ~~(i / 2)] : [gridPosition[0] + ~~(i / 2), gridPosition[1] + (i % 2)], - midi: onMidi(i, jump as [number, number], dir as number), - mount: onMount(i), + listeners: { + midi: onMidi(i, jump as [number, number], dir as number), + mount: onMount(i), + } } }) return { diff --git a/packages/launchpad-common/src/controls/beatloop.ts b/packages/launchpad-common/src/controls/beatloop.ts index 48d6b67..8daf933 100644 --- a/packages/launchpad-common/src/controls/beatloop.ts +++ b/packages/launchpad-common/src/controls/beatloop.ts @@ -1,55 +1,59 @@ -import type { ControlComponent, ControlMessage } from '@mixxx-launch/mixxx' +import { ChannelControlDef, ControlComponent, ControlMessage, MidiComponent } from '@mixxx-launch/mixxx' import { setValue } from '@mixxx-launch/mixxx' -import { Control, MakeDeckControlTemplate } from '../Control' -import { MidiComponent } from '../device' +import { ButtonBindingTemplate, ControlBindingTemplate, MakeDeckControlTemplate, Control } from '../Control' import { modes } from '../ModifierSidebar' import { onAttack } from '../util' export type Type = { type: 'beatloop' params: { + deck: ChannelControlDef, + gridPosition: [number, number] loops: readonly number[] rows: number } state: Record bindings: { - [k: `b.${string}`]: MidiComponent - [k: `c.${string}`]: ControlComponent + [k: `b.${string}`]: ButtonBindingTemplate + [k: `c.${string}`]: ControlBindingTemplate } } -const make: MakeDeckControlTemplate = (params, gridPosition, deck) => { - const { loops, rows } = params - const bindings: { [k: string]: any } = {} +const make: MakeDeckControlTemplate = ({ gridPosition, deck, loops, rows }) => { + const bindings: Type['bindings'] = {} const onMidi = (loop: number) => - ({ context }: Control) => - onAttack(() => { - const { modifier } = context - modes(modifier.getState(), () => setValue(deck.beatloops[loop].toggle, 1)) - }) + ({ context }: Control) => + onAttack(() => { + const { modifier } = context + modes(modifier.getState(), () => setValue(deck.beatloops[loop].toggle, 1)) + }) const onUpdate = (i: number) => - ({ context, bindings }: Control) => - ({ value }: ControlMessage) => { - const { device } = context - const color = value ? device.colors.hi_red : device.colors.lo_red - device.sendColor(bindings[`b.${i}`].control, color) - } + ({ context, bindings }: Control) => + ({ value }: ControlMessage) => { + const { device } = context + const color = value ? device.colors.hi_red : device.colors.lo_red + device.sendColor(bindings[`b.${i}`].control, color) + } loops.forEach((loop, i) => { const dx = i % rows const dy = ~~(i / rows) bindings[`b.${i}`] = { - type: 'button', + type: MidiComponent, target: [gridPosition[0] + dx, gridPosition[1] + dy], - midi: onMidi(loop), + listeners: { + midi: onMidi(loop), + } } bindings[`c.${loop}`] = { - type: 'control', + type: ControlComponent, target: deck.beatloops[loop].enabled, - update: onUpdate(i), + listeners: { + update: onUpdate(i), + } } }) @@ -60,3 +64,4 @@ const make: MakeDeckControlTemplate = (params, gridPosition, deck) => { } export default make + diff --git a/packages/launchpad-common/src/controls/cue.ts b/packages/launchpad-common/src/controls/cue.ts index 344644c..b958363 100644 --- a/packages/launchpad-common/src/controls/cue.ts +++ b/packages/launchpad-common/src/controls/cue.ts @@ -1,54 +1,62 @@ -import type { ControlComponent, ControlMessage } from '@mixxx-launch/mixxx' +import { ChannelControlDef, ControlComponent, ControlMessage, MidiComponent } from '@mixxx-launch/mixxx' import { setValue } from '@mixxx-launch/mixxx' -import { Control, MakeDeckControlTemplate } from '../Control' -import { MidiComponent } from '../device' +import { ButtonBindingTemplate, ControlBindingTemplate, MakeDeckControlTemplate, Control } from '../Control' import { modes, retainAttackMode } from '../ModifierSidebar' + export type Type = { type: 'cue' bindings: { - cue: MidiComponent - cueIndicator: ControlComponent + cue: ButtonBindingTemplate + cueIndicator: ControlBindingTemplate + } + params: { + deck: ChannelControlDef, + gridPosition: [number, number] } - params: Record state: Record } -const make: MakeDeckControlTemplate = (_, gridPosition, deck) => ({ + +const make: MakeDeckControlTemplate = ({ gridPosition, deck }) => ({ state: {}, bindings: { cue: { - type: 'button', + type: MidiComponent, target: gridPosition, - midi: ({ context: { modifier } }: Control) => - retainAttackMode(modifier, (mode, { value }) => { - modes( - mode, - () => { - setValue(deck.cue_default, value ? 1 : 0) - }, - () => value && setValue(deck.cue_set, 1), - ) - }), + listeners: { + midi: ({ context: { modifier } }: Control) => + retainAttackMode(modifier, (mode, { value }) => { + modes( + mode, + () => { + setValue(deck.cue_default, value ? 1 : 0) + }, + () => value && setValue(deck.cue_set, 1), + ) + }), + }, }, cueIndicator: { - type: 'control', + type: ControlComponent, target: deck.cue_indicator, - update: - ({ - bindings: { - cue: { control }, - }, - context: { device }, - }: Control) => - ({ value }: ControlMessage) => { - if (value) { - device.sendColor(control, device.colors.hi_red) - } else if (!value) { - device.clearColor(control) - } - }, + listeners: { + update: + ({ + bindings: { + cue: { control }, + }, + context: { device }, + }: Control) => + ({ value }: ControlMessage) => { + if (value) { + device.sendColor(control, device.colors.hi_red) + } else if (!value) { + device.clearColor(control) + } + }, + }, }, }, }) diff --git a/packages/launchpad-common/src/controls/grid.ts b/packages/launchpad-common/src/controls/grid.ts index a61ebab..33deed8 100644 --- a/packages/launchpad-common/src/controls/grid.ts +++ b/packages/launchpad-common/src/controls/grid.ts @@ -1,20 +1,23 @@ -import type { MidiMessage } from '@mixxx-launch/mixxx' +import type { ChannelControlDef, MidiMessage } from '@mixxx-launch/mixxx' import { setValue } from '@mixxx-launch/mixxx' -import { Control, MakeDeckControlTemplate } from '../Control' import { MidiComponent } from '../device' +import { ButtonBindingTemplate, MakeDeckControlTemplate, Control } from '../Control' import { modes } from '../ModifierSidebar' export type Type = { type: 'grid' bindings: { - back: MidiComponent - forth: MidiComponent + back: ButtonBindingTemplate + forth: ButtonBindingTemplate } state: Record - params: Record + params: { + deck: ChannelControlDef, + gridPosition: [number, number] + } } -const make: MakeDeckControlTemplate = (_, gridPosition, deck) => { +const make: MakeDeckControlTemplate = ({ gridPosition, deck }) => { const steps = { back: { normal: deck.beats_translate_earlier, @@ -27,38 +30,42 @@ const make: MakeDeckControlTemplate = (_, gridPosition, deck) => { } const onGrid = (dir: 'back' | 'forth') => - ({ context: { device, modifier }, bindings }: Control) => - ({ value }: MidiMessage) => { - if (!value) { - device.clearColor(bindings[dir].control) - } else { - modes( - modifier.getState(), - () => { - device.sendColor(bindings[dir].control, device.colors.hi_yellow) - setValue(steps[dir].normal, 1) - }, - () => { - device.sendColor(bindings[dir].control, device.colors.hi_amber) - setValue(steps[dir].ctrl, 1) - }, - ) - } - } + ({ context: { device, modifier }, bindings }: Control) => + ({ value }: MidiMessage) => { + if (!value) { + device.clearColor(bindings[dir].control) + } else { + modes( + modifier.getState(), + () => { + device.sendColor(bindings[dir].control, device.colors.hi_yellow) + setValue(steps[dir].normal, 1) + }, + () => { + device.sendColor(bindings[dir].control, device.colors.hi_amber) + setValue(steps[dir].ctrl, 1) + }, + ) + } + } return { bindings: { back: { - type: 'button', + type: MidiComponent, target: gridPosition, - midi: onGrid('back'), + listeners: { + midi: onGrid('back'), + } }, forth: { - type: 'button', + type: MidiComponent, target: [gridPosition[0] + 1, gridPosition[1]], - midi: onGrid('forth'), + listeners: { + midi: onGrid('forth'), + } }, }, - state: {} + state: {} } } diff --git a/packages/launchpad-common/src/controls/hotcue.ts b/packages/launchpad-common/src/controls/hotcue.ts index 99dec67..3c84f16 100644 --- a/packages/launchpad-common/src/controls/hotcue.ts +++ b/packages/launchpad-common/src/controls/hotcue.ts @@ -1,88 +1,98 @@ import { range } from '@mixxx-launch/common' -import type { ControlComponent, ControlMessage, MidiMessage } from '@mixxx-launch/mixxx' +import { ChannelControlDef, ControlComponent, ControlMessage, MidiMessage } from '@mixxx-launch/mixxx' import { getValue, setValue } from '@mixxx-launch/mixxx' -import { Control, MakeDeckControlTemplate } from '../Control' import { MidiComponent, parseRGBColor } from '../device' +import { ButtonBindingTemplate, ControlBindingTemplate, MakeDeckControlTemplate, Control } from '../Control' import { modes } from '../ModifierSidebar' +import { Theme } from '../App' export type Type = { type: 'hotcue' params: { + deck: ChannelControlDef, + gridPosition: [number, number] + theme: Theme, cues: number rows: number start?: number } bindings: { - [k: `midi.${string}`]: MidiComponent - [k: `cue.${string}`]: ControlComponent - [k: `color.${string}`]: ControlComponent + [k: `midi.${string}`]: ButtonBindingTemplate + [k: `cue.${string}`]: ControlBindingTemplate + [k: `color.${string}`]: ControlBindingTemplate } state: Record } -const make: MakeDeckControlTemplate = ({ cues, rows, start = 0 }, gridPosition, deck, theme) => { +const make: MakeDeckControlTemplate = ({ cues, rows, start = 0, gridPosition, deck, theme }) => { const onHotcueMidi = (i: number) => - ({ context: { modifier }, bindings }: Control) => - ({ value }: MidiMessage) => { - modes( - modifier.getState(), - () => { - setValue(deck.hotcues[1 + i + start].activate, value ? 1 : 0) - }, - () => { - if (value) { - if (getValue(bindings[`cue.${i}`].control)) { - setValue(deck.hotcues[1 + i + start].clear, 1) - } else { - setValue(deck.hotcues[1 + i + start].set, 1) - } - } - }, - ) - } + ({ context: { modifier }, bindings }: Control) => + ({ value }: MidiMessage) => { + modes( + modifier.getState(), + () => { + setValue(deck.hotcues[1 + i + start].activate, value ? 1 : 0) + }, + () => { + if (value) { + if (getValue(bindings[`cue.${i}`].control)) { + setValue(deck.hotcues[1 + i + start].clear, 1) + } else { + setValue(deck.hotcues[1 + i + start].set, 1) + } + } + }, + ) + } const onHotcueColorChanged = (i: number) => - ({ context: { device }, bindings }: Control) => - ({ value }: ControlMessage) => { - const color = parseRGBColor(value) - if (device.supportsRGBColors) { - device.sendRGBColor(bindings[`midi.${i}`].control, color == null ? theme.fallbackHotcueColor : color) - } - } + ({ context: { device }, bindings }: Control) => + ({ value }: ControlMessage) => { + const color = parseRGBColor(value) + if (device.supportsRGBColors) { + device.sendRGBColor(bindings[`midi.${i}`].control, color == null ? theme.fallbackHotcueColor : color) + } + } const onHotcueEnabled = (i: number) => - ({ context: { device }, bindings }: Control) => - ({ value }: ControlMessage) => { - if (value) { - if (device.supportsRGBColors) { - const color = parseRGBColor(getValue(deck.hotcues[1 + i + start].color)) - device.sendRGBColor(bindings[`midi.${i}`].control, color == null ? theme.fallbackHotcueColor : color) - } else { - device.sendColor(bindings[`midi.${i}`].control, device.colors.lo_yellow) + ({ context: { device }, bindings }: Control) => + ({ value }: ControlMessage) => { + if (value) { + if (device.supportsRGBColors) { + const color = parseRGBColor(getValue(deck.hotcues[1 + i + start].color)) + device.sendRGBColor(bindings[`midi.${i}`].control, color == null ? theme.fallbackHotcueColor : color) + } else { + device.sendColor(bindings[`midi.${i}`].control, device.colors.lo_yellow) + } + } else { + device.clearColor(bindings[`midi.${i}`].control) + } } - } else { - device.clearColor(bindings[`midi.${i}`].control) - } - } - const bindings: { [k: string]: any } = {} + const bindings: Type['bindings'] = {} for (const i of range(cues)) { const dx = i % rows const dy = ~~(i / rows) bindings[`midi.${i}`] = { - type: 'button', + type: MidiComponent, target: [gridPosition[0] + dx, gridPosition[1] + dy], - midi: onHotcueMidi(i), + listeners: { + midi: onHotcueMidi(i), + } } bindings[`cue.${i}`] = { - type: 'control', + type: ControlComponent, target: deck.hotcues[1 + i + start].enabled, - update: onHotcueEnabled(i), + listeners: { + update: onHotcueEnabled(i), + } } bindings[`color.${i}`] = { - type: 'control', + type: ControlComponent, target: deck.hotcues[1 + i + start].color, - update: onHotcueColorChanged(i), + listeners: { + update: onHotcueColorChanged(i), + } } } diff --git a/packages/launchpad-common/src/controls/index.ts b/packages/launchpad-common/src/controls/index.ts index 39cbd36..47d3138 100644 --- a/packages/launchpad-common/src/controls/index.ts +++ b/packages/launchpad-common/src/controls/index.ts @@ -1,4 +1,3 @@ -import { MakeDeckControlTemplate } from '../Control' import makeBeatjump, { Type as Beatjump } from './beatjump' import makeBeatloop, { Type as Beatloop } from './beatloop' import makeCue, { Type as Cue } from './cue' @@ -19,6 +18,7 @@ import makeReloop, { Type as Reloop } from './reloop' import makeSlip, { Type as Slip } from './slip' import makeSync, { Type as Sync } from './sync' import makeTap, { Type as Tap } from './tap' +import { MakeDeckControlTemplate } from '../Control' export type ControlTypeIndex = | Beatjump @@ -67,4 +67,5 @@ const index: MakeControlTemplateIndex = { tap: makeTap, } + export default index diff --git a/packages/launchpad-common/src/controls/key.ts b/packages/launchpad-common/src/controls/key.ts index 1b5b9da..96d3a1a 100644 --- a/packages/launchpad-common/src/controls/key.ts +++ b/packages/launchpad-common/src/controls/key.ts @@ -1,58 +1,66 @@ -import type { ControlComponent, ControlMessage } from '@mixxx-launch/mixxx' +import { ChannelControlDef, ControlComponent, ControlMessage } from '@mixxx-launch/mixxx' import { getValue, setValue } from '@mixxx-launch/mixxx' -import { Control, MakeDeckControlTemplate } from '../Control' import { MidiComponent } from '../device' +import { ButtonBindingTemplate, ControlBindingTemplate, MakeDeckControlTemplate, Control } from '../Control' import { modes } from '../ModifierSidebar' import { onAttack } from '../util' export type Type = { type: 'key' - params: Record + params: { + deck: ChannelControlDef, + gridPosition: [number, number] + } bindings: { - button: MidiComponent - keylock: ControlComponent + button: ButtonBindingTemplate + keylock: ControlBindingTemplate } state: Record } -const make: MakeDeckControlTemplate = (_, gridPosition, deck) => ({ +const make: MakeDeckControlTemplate = ({ gridPosition, deck }) => ({ state: {}, bindings: { button: { - type: 'button', + type: MidiComponent, target: gridPosition, - midi: - ({ context: { modifier }, bindings }: Control) => - onAttack(() => { - modes( - modifier.getState(), - () => { - setValue(bindings.keylock.control, Number(!getValue(bindings.keylock.control))) - }, - () => { - setValue(deck.key, getValue(deck.key) - 1) - }, - () => { - setValue(deck.key, getValue(deck.key) + 1) - }, - () => { - setValue(deck.reset_key, 1) - }, - ) - }) + listeners: { + midi: + ({ context: { modifier }, bindings }: Control) => + onAttack(() => { + modes( + modifier.getState(), + () => { + setValue(bindings.keylock.control, Number(!getValue(bindings.keylock.control))) + }, + () => { + setValue(deck.key, getValue(deck.key) - 1) + }, + () => { + setValue(deck.key, getValue(deck.key) + 1) + }, + () => { + setValue(deck.reset_key, 1) + }, + ) + }) + } }, keylock: { - type: 'control', + type: ControlComponent, target: deck.keylock, - update: - ({ context: { device }, bindings }: Control) => - ({ value }: ControlMessage) => { - if (value) { - device.sendColor(bindings.button.control, device.colors.hi_red) - } else { - device.clearColor(bindings.button.control) - } - }, + listeners: { + update: + ({ context: { device }, bindings }: Control) => + ({ value }: ControlMessage) => { + if (value) { + device.sendColor(bindings.button.control, device.colors.hi_red) + } else { + device.clearColor(bindings.button.control) + } + }, + + } }, }, }) diff --git a/packages/launchpad-common/src/controls/keyshift.ts b/packages/launchpad-common/src/controls/keyshift.ts index 48cb9cc..faa82b2 100644 --- a/packages/launchpad-common/src/controls/keyshift.ts +++ b/packages/launchpad-common/src/controls/keyshift.ts @@ -1,16 +1,18 @@ import { posMod } from '@mixxx-launch/common' -import { getValue, setValue } from '@mixxx-launch/mixxx' +import { ChannelControlDef, getValue, setValue } from '@mixxx-launch/mixxx' import { LaunchpadDevice, MidiComponent } from '../device' -import { Control, MakeDeckControlTemplate } from '../Control' import { modes, retainAttackMode } from '../ModifierSidebar' +import { ButtonBindingTemplate, MakeDeckControlTemplate, Control } from '../Control' export type Type = { type: 'keyshift' params: { + deck: ChannelControlDef, + gridPosition: [number, number] shifts: readonly [number, number][] rows: number } - bindings: { [i: number]: MidiComponent } + bindings: { [i: number]: ButtonBindingTemplate } state: { on: number base: number @@ -20,13 +22,13 @@ export type Type = { const colors = ['green', 'red'] as const -const make: MakeDeckControlTemplate = ({ shifts, rows }, gridPosition, deck) => { - const bindings: { [k: string]: any } = {} +const make: MakeDeckControlTemplate = ({ shifts, rows, gridPosition, deck }) => { + const bindings: Type['bindings'] = {} const temporaryChange = ( i: number, value: number, - bindings: Type['bindings'], + bindings: Control['bindings'], state: Type['state'], device: LaunchpadDevice, ) => { @@ -50,35 +52,37 @@ const make: MakeDeckControlTemplate = ({ shifts, rows }, gridPosition, dec const onMidi = (i: number) => - ({ context: { modifier, device }, bindings, state }: Control) => - retainAttackMode(modifier, (mode, { value }) => { - modes( - mode, - () => temporaryChange(i, value, bindings, state, device), - () => { - if (value) { - state.set = posMod(state.set + 1, 2) - for (let i = 0; i < shifts.length; ++i) { - device.sendColor(bindings[i].control, device.colors[`lo_${colors[state.set]}`]) + ({ context: { modifier, device }, bindings, state }: Control) => + retainAttackMode(modifier, (mode, { value }) => { + modes( + mode, + () => temporaryChange(i, value, bindings, state, device), + () => { + if (value) { + state.set = posMod(state.set + 1, 2) + for (let i = 0; i < shifts.length; ++i) { + device.sendColor(bindings[i].control, device.colors[`lo_${colors[state.set]}`]) + } } - } - }, - ) - }) + }, + ) + }) shifts.forEach((_, i) => { const dx = i % rows const dy = ~~(i / rows) - const position = [gridPosition[0] + dx, gridPosition[1] + dy] + const position = [gridPosition[0] + dx, gridPosition[1] + dy] as const bindings[i] = { - type: 'button', + type: MidiComponent, target: position, - midi: onMidi(i), - mount: - ({ context: { device }, bindings, state }: Control) => - () => { - device.sendColor(bindings[i].control, device.colors[`lo_${colors[state.set]}`]) - }, + listeners: { + midi: onMidi(i), + mount: + ({ context: { device }, bindings, state }: Control) => + () => { + device.sendColor(bindings[i].control, device.colors[`lo_${colors[state.set]}`]) + }, + } } }) return { diff --git a/packages/launchpad-common/src/controls/load.ts b/packages/launchpad-common/src/controls/load.ts index 007dba8..34427ad 100644 --- a/packages/launchpad-common/src/controls/load.ts +++ b/packages/launchpad-common/src/controls/load.ts @@ -1,23 +1,26 @@ -import type { ControlComponent, ControlMessage } from '@mixxx-launch/mixxx' +import { ChannelControlDef, ControlComponent, ControlMessage } from '@mixxx-launch/mixxx' import { getValue, setValue } from '@mixxx-launch/mixxx' -import { Control, MakeDeckControlTemplate } from '../Control' import { LaunchpadDevice, MidiComponent } from '../device' +import { ButtonBindingTemplate, ControlBindingTemplate, MakeDeckControlTemplate, Control } from '../Control' import { modes } from '../ModifierSidebar' import { onAttack } from '../util' export type Type = { type: 'load' bindings: { - samples: ControlComponent - play: ControlComponent - button: MidiComponent + samples: ControlBindingTemplate + play: ControlBindingTemplate + button: ButtonBindingTemplate + } + params: { + deck: ChannelControlDef + gridPosition: [number, number] } - params: Record state: Record } -const make: MakeDeckControlTemplate = (_, gridPosition, deck) => { - const onStateChanged = (loaded: number, playing: number, bindings: Type['bindings'], device: LaunchpadDevice) => { +const make: MakeDeckControlTemplate = ({ gridPosition, deck }) => { + const onStateChanged = (loaded: number, playing: number, bindings: Control['bindings'], device: LaunchpadDevice) => { if (loaded && playing) { device.sendColor(bindings.button.control, device.colors.lo_red) } else if (loaded) { @@ -30,38 +33,45 @@ const make: MakeDeckControlTemplate = (_, gridPosition, deck) => { state: {}, bindings: { samples: { - type: 'control', + type: ControlComponent, target: deck.track_samples, - update: - ({ bindings, context: { device } }: Control) => - ({ value }: ControlMessage) => - onStateChanged(value, getValue(bindings.play.control), bindings, device), + listeners: { + + update: + ({ bindings, context: { device } }: Control) => + ({ value }: ControlMessage) => + onStateChanged(value, getValue(bindings.play.control), bindings, device), + } }, play: { - type: 'control', + type: ControlComponent, target: deck.play, - update: - ({ bindings, context: { device } }: Control) => - ({ value }: ControlMessage) => - onStateChanged(getValue(bindings.samples.control), value, bindings, device), + listeners: { + update: + ({ bindings, context: { device } }: Control) => + ({ value }: ControlMessage) => + onStateChanged(getValue(bindings.samples.control), value, bindings, device), + } }, button: { - type: 'button', + type: MidiComponent, target: gridPosition, - midi: - ({ bindings, context: { modifier } }: Control) => - onAttack(() => { - modes( - modifier.getState(), - () => { - if (!getValue(bindings.samples.control)) { - setValue(deck.LoadSelectedTrack, 1) - } - }, - () => setValue(deck.LoadSelectedTrack, 1), - () => setValue(deck.eject, 1), - ) - }), + listeners: { + midi: + ({ bindings, context: { modifier } }: Control) => + onAttack(() => { + modes( + modifier.getState(), + () => { + if (!getValue(bindings.samples.control)) { + setValue(deck.LoadSelectedTrack, 1) + } + }, + () => setValue(deck.LoadSelectedTrack, 1), + () => setValue(deck.eject, 1), + ) + }), + } }, }, } diff --git a/packages/launchpad-common/src/controls/loopIo.ts b/packages/launchpad-common/src/controls/loopIo.ts index 81cf04a..2cff9dc 100644 --- a/packages/launchpad-common/src/controls/loopIo.ts +++ b/packages/launchpad-common/src/controls/loopIo.ts @@ -1,59 +1,66 @@ -import type { MidiMessage } from '@mixxx-launch/mixxx' +import type { ChannelControlDef, MidiMessage } from '@mixxx-launch/mixxx' import { getValue, setValue } from '@mixxx-launch/mixxx' -import { Control, MakeDeckControlTemplate } from '../Control' import { MidiComponent } from '../device' +import { ButtonBindingTemplate, MakeDeckControlTemplate, Control } from '../Control' import { modes } from '../ModifierSidebar' import { onAttack } from '../util' export type Type = { type: 'loopIo' bindings: { - in: MidiComponent - out: MidiComponent + in: ButtonBindingTemplate + out: ButtonBindingTemplate } state: Record - params: Record + params: { + deck: ChannelControlDef, + gridPosition: [number, number] + } } const SMALL_SAMPLES = 125 as const -const make: MakeDeckControlTemplate = (_, gridPosition, deck) => { +const make: MakeDeckControlTemplate = ({ gridPosition, deck }) => { const map = { in: [deck.loop_in, deck.loop_start_position], out: [deck.loop_out, deck.loop_end_position], } const onMidi = (dir: 'in' | 'out') => - ({ context: { modifier } }: Control) => - onAttack((_: MidiMessage) => { - modes( - modifier.getState(), - () => { - setValue(map[dir][0], 1) - setValue(map[dir][0], 0) - }, - () => { - const ctrl = map[dir][1] - setValue(ctrl, getValue(ctrl) - SMALL_SAMPLES) - }, - () => { - const ctrl = map[dir][1] - setValue(ctrl, getValue(ctrl) + SMALL_SAMPLES) - }, - ) - }) + ({ context: { modifier } }: Control) => + onAttack((_: MidiMessage) => { + modes( + modifier.getState(), + () => { + setValue(map[dir][0], 1) + setValue(map[dir][0], 0) + }, + () => { + const ctrl = map[dir][1] + setValue(ctrl, getValue(ctrl) - SMALL_SAMPLES) + }, + () => { + const ctrl = map[dir][1] + setValue(ctrl, getValue(ctrl) + SMALL_SAMPLES) + }, + ) + }) return { state: {}, bindings: { in: { - type: 'button', + type: MidiComponent, target: gridPosition, - midi: onMidi('in'), + listeners: { + midi: onMidi('in'), + } }, out: { - type: 'button', + type: MidiComponent, target: [gridPosition[0] + 1, gridPosition[1]], - midi: onMidi('out'), + listeners: { + midi: onMidi('out'), + } }, }, } diff --git a/packages/launchpad-common/src/controls/loopMultiply.ts b/packages/launchpad-common/src/controls/loopMultiply.ts index a054171..0e9647a 100644 --- a/packages/launchpad-common/src/controls/loopMultiply.ts +++ b/packages/launchpad-common/src/controls/loopMultiply.ts @@ -1,41 +1,48 @@ -import type { MidiMessage } from '@mixxx-launch/mixxx' +import type { ChannelControlDef, MidiMessage } from '@mixxx-launch/mixxx' import { setValue } from '@mixxx-launch/mixxx' -import { Control, MakeDeckControlTemplate } from '../Control' import { MidiComponent } from '../device' +import { ButtonBindingTemplate, MakeDeckControlTemplate, Control } from '../Control' import { onAttack } from '../util' export type Type = { type: 'loopMultiply' bindings: { - halve: MidiComponent - double: MidiComponent + halve: ButtonBindingTemplate + double: ButtonBindingTemplate } state: Record - params: Record + params: { + deck: ChannelControlDef + gridPosition: [number, number] + } } -const make: MakeDeckControlTemplate = (_, gridPosition, deck) => { +const make: MakeDeckControlTemplate = ({ gridPosition, deck }) => { const onMount = (k: 'halve' | 'double') => - ({ context: { device }, bindings }: Control) => - () => { - device.sendColor(bindings[k].control, device.colors.lo_yellow) - } + ({ context: { device }, bindings }: Control) => + () => { + device.sendColor(bindings[k].control, device.colors.lo_yellow) + } const onMidi = (k: 'double' | 'halve') => (_: Control) => onAttack((_: MidiMessage) => setValue(deck[`loop_${k}`], 1)) return { state: {}, bindings: { halve: { - type: 'button', + type: MidiComponent, target: gridPosition, - mount: onMount('halve'), - midi: onMidi('halve'), + listeners: { + mount: onMount('halve'), + midi: onMidi('halve'), + } }, double: { - type: 'button', + type: MidiComponent, target: [gridPosition[0] + 1, gridPosition[1]], - mount: onMount('double'), - midi: onMidi('double'), + listeners: { + mount: onMount('double'), + midi: onMidi('double'), + } }, }, } diff --git a/packages/launchpad-common/src/controls/loopjump.ts b/packages/launchpad-common/src/controls/loopjump.ts index 9cf2cfa..792d920 100644 --- a/packages/launchpad-common/src/controls/loopjump.ts +++ b/packages/launchpad-common/src/controls/loopjump.ts @@ -1,16 +1,18 @@ import { posMod } from '@mixxx-launch/common' -import { setValue } from '@mixxx-launch/mixxx' -import { Control, MakeDeckControlTemplate } from '../Control' +import { ChannelControlDef, setValue } from '@mixxx-launch/mixxx' import { MidiComponent } from '../device' +import { ButtonBindingTemplate, MakeDeckControlTemplate, Control } from '../Control' import { modes, retainAttackMode } from '../ModifierSidebar' export type Type = { type: 'loopjump' bindings: { - [k: number]: MidiComponent + [k: number]: ButtonBindingTemplate } params: { + deck: ChannelControlDef, + gridPosition: [number, number] jumps: readonly [number, number][] vertical?: boolean } @@ -23,67 +25,67 @@ export type Type = { } } -const make: MakeDeckControlTemplate = ({ jumps, vertical = false }, gridPosition, deck) => { - const bindings: { [k: number]: any } = {} +const make: MakeDeckControlTemplate = ({ gridPosition, deck, jumps, vertical = false }) => { + const bindings: Type['bindings'] = {} const onMidi = (k: number, j: [number, number], d: number) => - ({ context: { modifier, device }, bindings, state }: Control) => - retainAttackMode(modifier, (mode, { value }) => { - modes( - mode, - () => { - if (!state.mode) { - if (value) { - setValue(deck.loop_move, j[state.set] * d) - } - } else { - if (value) { - const currentJump = j[state.set] * d - - setValue(deck.loop_move, currentJump) - if (state.pressing != null) { - device.sendColor(bindings[state.pressing].control, device.colors[`lo_${state.color[state.set]}`]) + ({ context: { modifier, device }, bindings, state }: Control) => + retainAttackMode(modifier, (mode, { value }) => { + modes( + mode, + () => { + if (!state.mode) { + if (value) { + setValue(deck.loop_move, j[state.set] * d) } - device.sendColor(bindings[k].control, device.colors[`hi_${state.color[state.set]}`]) - state.pressing = k - state.diff = state.diff + currentJump } else { - if (state.pressing === k) { - device.sendColor(bindings[k].control, device.colors[`lo_${state.color[state.set]}`]) - state.pressing = null - setValue(deck.loop_move, -state.diff) - state.diff = 0 + if (value) { + const currentJump = j[state.set] * d + + setValue(deck.loop_move, currentJump) + if (state.pressing != null) { + device.sendColor(bindings[state.pressing].control, device.colors[`lo_${state.color[state.set]}`]) + } + device.sendColor(bindings[k].control, device.colors[`hi_${state.color[state.set]}`]) + state.pressing = k + state.diff = state.diff + currentJump + } else { + if (state.pressing === k) { + device.sendColor(bindings[k].control, device.colors[`lo_${state.color[state.set]}`]) + state.pressing = null + setValue(deck.loop_move, -state.diff) + state.diff = 0 + } } } - } - }, - () => { - if (value) { - state.set = posMod(state.set + 1, 2) - const prefix = state.mode ? 'lo' : 'hi' - for (let b = 0; b < spec.length; ++b) { - device.sendColor(bindings[b].control, device.colors[`${prefix}_${state.color[state.set]}`]) + }, + () => { + if (value) { + state.set = posMod(state.set + 1, 2) + const prefix = state.mode ? 'lo' : 'hi' + for (let b = 0; b < spec.length; ++b) { + device.sendColor(bindings[b].control, device.colors[`${prefix}_${state.color[state.set]}`]) + } } - } - }, - () => { - if (value) { - state.mode = !state.mode - const prefix = state.mode ? 'lo' : 'hi' - for (let b = 0; b < spec.length; ++b) { - device.sendColor(bindings[b].control, device.colors[`${prefix}_${state.color[state.set]}`]) + }, + () => { + if (value) { + state.mode = !state.mode + const prefix = state.mode ? 'lo' : 'hi' + for (let b = 0; b < spec.length; ++b) { + device.sendColor(bindings[b].control, device.colors[`${prefix}_${state.color[state.set]}`]) + } } - } - }, - ) - }) + }, + ) + }) const onMount = (k: number) => - ({ context: { device }, bindings, state }: Control) => - () => { - const prefix = state.mode ? 'lo' : 'hi' - device.sendColor(bindings[k].control, device.colors[`${prefix}_${state.color[state.set]}`]) - } + ({ context: { device }, bindings, state }: Control) => + () => { + const prefix = state.mode ? 'lo' : 'hi' + device.sendColor(bindings[k].control, device.colors[`${prefix}_${state.color[state.set]}`]) + } const spec = jumps.flatMap((j) => [ [j, 1], [j, -1], @@ -91,12 +93,14 @@ const make: MakeDeckControlTemplate = ({ jumps, vertical = false }, gridPo spec.forEach(([jump, dir], i) => { bindings[i] = { - type: 'button', + type: MidiComponent, target: vertical ? [gridPosition[0] + (i % 2), gridPosition[1] + ~~(i / 2)] : [gridPosition[0] + ~~(i / 2), gridPosition[1] + (i % 2)], - midi: onMidi(i, jump as [number, number], dir as number), - mount: onMount(i), + listeners: { + mount: onMount(i), + midi: onMidi(i, jump as [number, number], dir as number), + } } }) return { diff --git a/packages/launchpad-common/src/controls/loopjumpSmall.ts b/packages/launchpad-common/src/controls/loopjumpSmall.ts index d75dd09..adc3ec0 100644 --- a/packages/launchpad-common/src/controls/loopjumpSmall.ts +++ b/packages/launchpad-common/src/controls/loopjumpSmall.ts @@ -1,49 +1,55 @@ import { modes } from '../ModifierSidebar' -import type { MidiMessage } from '@mixxx-launch/mixxx' +import type { ChannelControlDef, MidiMessage } from '@mixxx-launch/mixxx' import { setValue } from '@mixxx-launch/mixxx' -import { Control, MakeDeckControlTemplate } from '../Control' import { onAttack } from '../util' import { MidiComponent } from '../device' +import { ButtonBindingTemplate, MakeDeckControlTemplate, Control } from '../Control' export type Type = { type: 'loopjumpSmall' bindings: { - back: MidiComponent - forth: MidiComponent + back: ButtonBindingTemplate + forth: ButtonBindingTemplate } params: { - amount: number + deck: ChannelControlDef, + gridPosition: [number, number], + amount: number, } state: Record } -const make: MakeDeckControlTemplate = ({ amount }, button, deck) => { +const make: MakeDeckControlTemplate = ({ amount, gridPosition, deck }) => { const onMidi = (dir: number) => - ({ context: { modifier } }: Control) => - onAttack((_: MidiMessage) => modes(modifier.getState(), () => setValue(deck.loop_move, dir * amount))) + ({ context: { modifier } }: Control) => + onAttack((_: MidiMessage) => modes(modifier.getState(), () => setValue(deck.loop_move, dir * amount))) return { state: {}, bindings: { back: { - type: 'button', - target: button, - midi: onMidi(-1), - mount: - ({ context: { device }, bindings }: Control) => - () => { - device.sendColor(bindings.back.control, device.colors.hi_yellow) - }, + type: MidiComponent, + target: gridPosition, + listeners: { + midi: onMidi(-1), + mount: + ({ context: { device }, bindings }: Control) => + () => { + device.sendColor(bindings.back.control, device.colors.hi_yellow) + }, + } }, forth: { - type: 'button', - target: [button[0] + 1, button[1]], - midi: onMidi(1), - mount: - ({ context: { device }, bindings }: Control) => - () => { - device.sendColor(bindings.forth.control, device.colors.hi_yellow) - }, + type: MidiComponent, + target: [gridPosition[0] + 1, gridPosition[1]], + listeners: { + midi: onMidi(1), + mount: + ({ context: { device }, bindings }: Control) => + () => { + device.sendColor(bindings.forth.control, device.colors.hi_yellow) + }, + } }, }, } diff --git a/packages/launchpad-common/src/controls/nudge.ts b/packages/launchpad-common/src/controls/nudge.ts index 241bf4d..ec44bf4 100644 --- a/packages/launchpad-common/src/controls/nudge.ts +++ b/packages/launchpad-common/src/controls/nudge.ts @@ -1,24 +1,27 @@ import { modes, retainAttackMode } from '../ModifierSidebar' -import type { ControlMessage, ControlComponent } from '@mixxx-launch/mixxx' +import { ControlMessage, ControlComponent, ChannelControlDef } from '@mixxx-launch/mixxx' import { setValue, getValue } from '@mixxx-launch/mixxx' -import { Control, MakeDeckControlTemplate } from '../Control' import { MidiComponent } from '../device' +import { ButtonBindingTemplate, ControlBindingTemplate, MakeDeckControlTemplate, Control } from '../Control' export type Type = { type: 'nudge' bindings: { - up: MidiComponent - down: MidiComponent - rate: ControlComponent + up: ButtonBindingTemplate + down: ButtonBindingTemplate + rate: ControlBindingTemplate } state: { up: boolean down: boolean } - params: Record + params: { + deck: ChannelControlDef + gridPosition: [number, number] + } } -const make: MakeDeckControlTemplate = (_, gridPosition, deck) => { +const make: MakeDeckControlTemplate = ({ gridPosition, deck }) => { const rateEpsilon = 1e-3 const getDirection = (rate: number) => { @@ -33,86 +36,93 @@ const make: MakeDeckControlTemplate = (_, gridPosition, deck) => { const onNudgeMidi = (dir: 'up' | 'down') => - ({ context: { modifier, device }, bindings, state }: Control) => - retainAttackMode(modifier, (mode, { value }) => { - if (value) { - state[dir] = true - if (state.down && state.up) { - setValue(deck.rate, 0) + ({ context: { modifier, device }, bindings, state }: Control) => + retainAttackMode(modifier, (mode, { value }) => { + if (value) { + state[dir] = true + if (state.down && state.up) { + setValue(deck.rate, 0) + } else { + modes( + mode, + () => { + device.sendColor(bindings[dir].control, device.colors.hi_yellow) + setValue(deck[`rate_temp_${dir}`], 1) + }, + () => { + device.sendColor(bindings[dir].control, device.colors.hi_red) + setValue(deck[`rate_perm_${dir}`], 1) + }, + () => { + device.sendColor(bindings[dir].control, device.colors.lo_yellow) + setValue(deck[`rate_temp_${dir}_small`], 1) + }, + () => { + device.sendColor(bindings[dir].control, device.colors.lo_red) + setValue(deck[`rate_perm_${dir}_small`], 1) + }, + ) + } } else { + state[dir] = false + if (getDirection(getValue(bindings.rate.control)) === dir) { + device.sendColor(bindings[dir].control, device.colors.lo_orange) + } else { + device.clearColor(bindings[dir].control) + } modes( mode, - () => { - device.sendColor(bindings[dir].control, device.colors.hi_yellow) - setValue(deck[`rate_temp_${dir}`], 1) - }, - () => { - device.sendColor(bindings[dir].control, device.colors.hi_red) - setValue(deck[`rate_perm_${dir}`], 1) - }, - () => { - device.sendColor(bindings[dir].control, device.colors.lo_yellow) - setValue(deck[`rate_temp_${dir}_small`], 1) - }, - () => { - device.sendColor(bindings[dir].control, device.colors.lo_red) - setValue(deck[`rate_perm_${dir}_small`], 1) - }, + () => setValue(deck[`rate_temp_${dir}`], 0), + undefined, + () => setValue(deck[`rate_temp_${dir}_small`], 0), ) } - } else { - state[dir] = false - if (getDirection(getValue(bindings.rate.control)) === dir) { - device.sendColor(bindings[dir].control, device.colors.lo_orange) - } else { - device.clearColor(bindings[dir].control) - } - modes( - mode, - () => setValue(deck[`rate_temp_${dir}`], 0), - undefined, - () => setValue(deck[`rate_temp_${dir}_small`], 0), - ) - } - }) + }) const onRate = ({ context: { device }, bindings, state }: Control) => - ({ value }: ControlMessage) => { - let up = device.colors.black - let down = device.colors.black - const rate = getDirection(value) - if (rate === 'down') { - down = device.colors.lo_orange - } else if (rate === 'up') { - up = device.colors.lo_orange - } + ({ value }: ControlMessage) => { + let up = device.colors.black + let down = device.colors.black + const rate = getDirection(value) + if (rate === 'down') { + down = device.colors.lo_orange + } else if (rate === 'up') { + up = device.colors.lo_orange + } - if (!state.down) { - device.sendColor(bindings.down.control, down) - } + if (!state.down) { + device.sendColor(bindings.down.control, down) + } - if (!state.up) { - device.sendColor(bindings.up.control, up) + if (!state.up) { + device.sendColor(bindings.up.control, up) + } } - } return { bindings: { down: { - type: 'button', + type: MidiComponent, target: gridPosition, - midi: onNudgeMidi('down'), + listeners: { + midi: onNudgeMidi('down'), + } }, up: { - type: 'button', + type: MidiComponent, target: [gridPosition[0] + 1, gridPosition[1]], - midi: onNudgeMidi('up'), + listeners: { + + midi: onNudgeMidi('up'), + } }, rate: { - type: 'control', + type: ControlComponent, target: deck.rate, - update: onRate, + listeners: { + update: onRate, + } }, }, state: { diff --git a/packages/launchpad-common/src/controls/pfl.ts b/packages/launchpad-common/src/controls/pfl.ts index 7547e69..447fe65 100644 --- a/packages/launchpad-common/src/controls/pfl.ts +++ b/packages/launchpad-common/src/controls/pfl.ts @@ -1,40 +1,47 @@ -import type { ControlComponent, ControlMessage, MidiMessage } from '@mixxx-launch/mixxx' +import { ChannelControlDef, ControlComponent, ControlMessage, MidiMessage } from '@mixxx-launch/mixxx' import { getValue, setValue } from '@mixxx-launch/mixxx' -import { Control, MakeDeckControlTemplate } from '../Control' import { MidiComponent } from '../device' +import { ButtonBindingTemplate, ControlBindingTemplate, MakeDeckControlTemplate, Control } from '../Control' import { modes } from '../ModifierSidebar' import { onAttack } from '../util' export type Type = { type: 'pfl' bindings: { - pfl: ControlComponent - button: MidiComponent + pfl: ControlBindingTemplate + button: ButtonBindingTemplate + } + params: { + deck: ChannelControlDef + gridPosition: [number, number] } - params: Record state: Record } -const make: MakeDeckControlTemplate = (_, gridPosition, deck) => ({ +const make: MakeDeckControlTemplate = ({ gridPosition, deck }) => ({ state: {}, bindings: { pfl: { - type: 'control', + type: ControlComponent, target: deck.pfl, - update: - ({ context: { device }, bindings }: Control) => - ({ value }: ControlMessage) => - value - ? device.sendColor(bindings.button.control, device.colors.hi_green) - : device.clearColor(bindings.button.control), + listeners: { + update: + ({ context: { device }, bindings }: Control) => + ({ value }: ControlMessage) => + value + ? device.sendColor(bindings.button.control, device.colors.hi_green) + : device.clearColor(bindings.button.control), + } }, button: { - type: 'button', + type: MidiComponent, target: gridPosition, - midi: - ({ context: { modifier }, bindings }: Control) => - onAttack((_: MidiMessage) => - modes(modifier.getState(), () => setValue(bindings.pfl.control, Number(!getValue(bindings.pfl.control))))), + listeners: { + midi: + ({ context: { modifier }, bindings }: Control) => + onAttack((_: MidiMessage) => + modes(modifier.getState(), () => setValue(bindings.pfl.control, Number(!getValue(bindings.pfl.control))))), + } }, }, }) diff --git a/packages/launchpad-common/src/controls/play.ts b/packages/launchpad-common/src/controls/play.ts index ce97e63..9184450 100644 --- a/packages/launchpad-common/src/controls/play.ts +++ b/packages/launchpad-common/src/controls/play.ts @@ -1,49 +1,56 @@ -import type { ControlComponent, ControlMessage } from '@mixxx-launch/mixxx' +import { ChannelControlDef, ControlComponent, ControlMessage } from '@mixxx-launch/mixxx' import { getValue, setValue } from '@mixxx-launch/mixxx' -import { Control, MakeDeckControlTemplate } from '../Control' import { MidiComponent } from '../device' +import { ButtonBindingTemplate, ControlBindingTemplate, MakeDeckControlTemplate, Control } from '../Control' import { modes } from '../ModifierSidebar' import { onAttack } from '../util' export type Type = { type: 'play' bindings: { - playIndicator: ControlComponent - play: MidiComponent + playIndicator: ControlBindingTemplate + play: ButtonBindingTemplate + } + params: { + deck: ChannelControlDef + gridPosition: [number, number] } - params: Record state: Record } -const make: MakeDeckControlTemplate = (_, gridPosition, deck) => ({ +const make: MakeDeckControlTemplate = ({ gridPosition, deck }) => ({ state: {}, bindings: { playIndicator: { - type: 'control', + type: ControlComponent, target: deck.play_indicator, - update: - ({ bindings, context: { device } }: Control) => - ({ value }: ControlMessage) => { - if (value) { - device.sendColor(bindings.play.control, device.colors.hi_red) - } else if (!value) { - device.clearColor(bindings.play.control) - } - }, + listeners: { + update: + ({ bindings, context: { device } }: Control) => + ({ value }: ControlMessage) => { + if (value) { + device.sendColor(bindings.play.control, device.colors.hi_red) + } else if (!value) { + device.clearColor(bindings.play.control) + } + }, + } }, play: { - type: 'button', + type: MidiComponent, target: gridPosition, - midi: - ({ context: { modifier } }: Control) => - onAttack(() => { - modes( - modifier.getState(), - () => setValue(deck.play, Number(!getValue(deck.play))), - () => setValue(deck.start_play, 1), - () => setValue(deck.start_stop, 1), - ) - }), + listeners: { + midi: + ({ context: { modifier } }: Control) => + onAttack(() => { + modes( + modifier.getState(), + () => setValue(deck.play, Number(!getValue(deck.play))), + () => setValue(deck.start_play, 1), + () => setValue(deck.start_stop, 1), + ) + }), + } }, }, }) diff --git a/packages/launchpad-common/src/controls/quantize.ts b/packages/launchpad-common/src/controls/quantize.ts index 6b3dd52..fa860b3 100644 --- a/packages/launchpad-common/src/controls/quantize.ts +++ b/packages/launchpad-common/src/controls/quantize.ts @@ -1,42 +1,49 @@ -import type { ControlComponent, ControlMessage } from '@mixxx-launch/mixxx' +import { ChannelControlDef, ControlComponent, ControlMessage } from '@mixxx-launch/mixxx' import { getValue, setValue } from '@mixxx-launch/mixxx' -import { Control, MakeDeckControlTemplate } from '../Control' import { MidiComponent } from '../device' +import { ButtonBindingTemplate, ControlBindingTemplate, MakeDeckControlTemplate, Control } from '../Control' import { modes } from '../ModifierSidebar' import { onAttack } from '../util' export type Type = { type: 'quantize' - params: Record + params: { + deck: ChannelControlDef + gridPosition: [number, number] + } state: Record bindings: { - quantize: ControlComponent - button: MidiComponent + quantize: ControlBindingTemplate + button: ButtonBindingTemplate } } -const make: MakeDeckControlTemplate = (_, gridPosition, deck) => ({ +const make: MakeDeckControlTemplate = ({ gridPosition, deck }) => ({ state: {}, bindings: { quantize: { - type: 'control', + type: ControlComponent, target: deck.quantize, - update: - ({ bindings, context: { device } }: Control) => - ({ value }: ControlMessage) => - value - ? device.sendColor(bindings.button.control, device.colors.hi_orange) - : device.clearColor(bindings.button.control), + listeners: { + update: + ({ bindings, context: { device } }: Control) => + ({ value }: ControlMessage) => + value + ? device.sendColor(bindings.button.control, device.colors.hi_orange) + : device.clearColor(bindings.button.control), + } }, button: { - type: 'button', + type: MidiComponent, target: gridPosition, - midi: - ({ bindings, context: { modifier } }: Control) => - onAttack(() => - modes(modifier.getState(), () => - setValue(bindings.quantize.control, Number(!getValue(bindings.quantize.control))), - )), + listeners: { + midi: + ({ bindings, context: { modifier } }: Control) => + onAttack(() => + modes(modifier.getState(), () => + setValue(bindings.quantize.control, Number(!getValue(bindings.quantize.control))), + )), + } }, }, }) diff --git a/packages/launchpad-common/src/controls/reloop.ts b/packages/launchpad-common/src/controls/reloop.ts index 6decf50..db15653 100644 --- a/packages/launchpad-common/src/controls/reloop.ts +++ b/packages/launchpad-common/src/controls/reloop.ts @@ -1,48 +1,55 @@ -import type { ControlComponent, ControlMessage } from '@mixxx-launch/mixxx' +import { ChannelControlDef, ControlComponent, ControlMessage } from '@mixxx-launch/mixxx' import { setValue } from '@mixxx-launch/mixxx' -import { Control, MakeDeckControlTemplate } from '../Control' import { MidiComponent } from '../device' +import { ButtonBindingTemplate, ControlBindingTemplate, MakeDeckControlTemplate, Control } from '../Control' import { modes } from '../ModifierSidebar' import { onAttack } from '../util' export type Type = { type: 'reloop' bindings: { - button: MidiComponent - control: ControlComponent + button: ButtonBindingTemplate + control: ControlBindingTemplate } state: Record - params: Record + params: { + deck: ChannelControlDef + gridPosition: [number, number] + } } -const make: MakeDeckControlTemplate = (_, gridPosition, deck) => ({ +const make: MakeDeckControlTemplate = ({ gridPosition, deck }) => ({ state: {}, bindings: { button: { - type: 'button', + type: MidiComponent, target: gridPosition, - midi: - ({ context: { modifier } }: Control) => - onAttack(() => { - modes( - modifier.getState(), - () => setValue(deck.reloop_exit, 1), - () => setValue(deck.reloop_andstop, 1), - ) - }), + listeners: { + midi: + ({ context: { modifier } }: Control) => + onAttack(() => { + modes( + modifier.getState(), + () => setValue(deck.reloop_exit, 1), + () => setValue(deck.reloop_andstop, 1), + ) + }), + } }, control: { - type: 'control', + type: ControlComponent, target: deck.loop_enabled, - update: - ({ context: { device }, bindings }: Control) => - ({ value }: ControlMessage) => { - if (value) { - device.sendColor(bindings.button.control, device.colors.hi_green) - } else { - device.sendColor(bindings.button.control, device.colors.lo_green) - } - }, + listeners: { + update: + ({ context: { device }, bindings }: Control) => + ({ value }: ControlMessage) => { + if (value) { + device.sendColor(bindings.button.control, device.colors.hi_green) + } else { + device.sendColor(bindings.button.control, device.colors.lo_green) + } + }, + } }, }, }) diff --git a/packages/launchpad-common/src/controls/samplerPad.ts b/packages/launchpad-common/src/controls/samplerPad.ts index a3647b0..9c6c585 100644 --- a/packages/launchpad-common/src/controls/samplerPad.ts +++ b/packages/launchpad-common/src/controls/samplerPad.ts @@ -1,27 +1,33 @@ -import type { ControlComponent, ControlMessage, MidiMessage } from '@mixxx-launch/mixxx' +import { ControlComponent, ControlMessage, MidiMessage } from '@mixxx-launch/mixxx' import { setValue } from '@mixxx-launch/mixxx' import { LaunchpadDevice, MidiComponent, parseRGBColor, RGBColor } from '../device' -import { Control, MakeSamplerControlTemplate } from '../Control' import { modes } from '../ModifierSidebar' +import { ButtonBindingTemplate, ControlBindingTemplate, MakeSamplerControlTemplate, Control } from '../Control' +import { SamplerControlDef } from '@mixxx-launch/mixxx/src/Control' +import { Theme } from '../App' export type Type = { type: 'samplerPad' bindings: { - button: MidiComponent - playing: ControlComponent - loaded: ControlComponent - colorChanged: ControlComponent + button: ButtonBindingTemplate + playing: ControlBindingTemplate + loaded: ControlBindingTemplate + colorChanged: ControlBindingTemplate } state: { loaded: boolean playing: boolean color: RGBColor | null } - params: Record + params: { + sampler: SamplerControlDef + gridPosition: [number, number] + theme: Theme + } } -export const make: MakeSamplerControlTemplate = (_, gridPosition, sampler, theme) => { - const onStateChanged = (state: Type['state'], device: LaunchpadDevice, bindings: Type['bindings']) => { +export const make: MakeSamplerControlTemplate = ({ gridPosition, sampler, theme }) => { + const onStateChanged = (state: Type['state'], device: LaunchpadDevice, bindings: Control['bindings']) => { const color = state.color == null ? theme.fallbackTrackColor : state.color if (!state.loaded) { device.clearColor(bindings.button.control) @@ -47,64 +53,72 @@ export const make: MakeSamplerControlTemplate = (_, gridPosition, sampler, }, bindings: { button: { - type: 'button', + type: MidiComponent, target: gridPosition, - midi: - ({ context: { modifier }, state }: Control) => - ({value}: MidiMessage) => { - if (value) { - modes( - modifier.getState(), - () => { - if (!state.loaded) { - setValue(sampler.LoadSelectedTrack, 1) - } else { - setValue(sampler.cue_gotoandplay, 1) - } - }, - () => { - if (state.playing) { - setValue(sampler.stop, 1) - } else if (state.loaded) { - setValue(sampler.eject, 1) - } - }, - ) - } - }, + listeners: { + midi: + ({ context: { modifier }, state }: Control) => + ({ value }: MidiMessage) => { + if (value) { + modes( + modifier.getState(), + () => { + if (!state.loaded) { + setValue(sampler.LoadSelectedTrack, 1) + } else { + setValue(sampler.cue_gotoandplay, 1) + } + }, + () => { + if (state.playing) { + setValue(sampler.stop, 1) + } else if (state.loaded) { + setValue(sampler.eject, 1) + } + }, + ) + } + }, + } }, playing: { - type: 'control', + type: ControlComponent, target: sampler.play_latched, - update: - ({ context: { device }, bindings, state }: Control) => - ({ value }: ControlMessage) => { - state.playing = !!value - onStateChanged(state, device, bindings) - }, + listeners: { + update: + ({ context: { device }, bindings, state }: Control) => + ({ value }: ControlMessage) => { + state.playing = !!value + onStateChanged(state, device, bindings) + }, + } }, loaded: { - type: 'control', + type: ControlComponent, target: sampler.track_loaded, - update: - ({ context: { device }, bindings, state }: Control) => - ({ value }: ControlMessage) => { - state.loaded = !!value - onStateChanged(state, device, bindings) - }, + listeners: { + update: + ({ context: { device }, bindings, state }: Control) => + ({ value }: ControlMessage) => { + state.loaded = !!value + onStateChanged(state, device, bindings) + }, + } }, colorChanged: { - type: 'control', + type: ControlComponent, target: sampler.track_color, - update: - ({ context: { device }, bindings, state }: Control) => - ({ value }: ControlMessage) => { - state.color = parseRGBColor(value) - onStateChanged(state, device, bindings) - }, + listeners: { + update: + ({ context: { device }, bindings, state }: Control) => + ({ value }: ControlMessage) => { + state.color = parseRGBColor(value) + onStateChanged(state, device, bindings) + }, + } }, }, } diff --git a/packages/launchpad-common/src/controls/slip.ts b/packages/launchpad-common/src/controls/slip.ts index 4f93da3..6cedc34 100644 --- a/packages/launchpad-common/src/controls/slip.ts +++ b/packages/launchpad-common/src/controls/slip.ts @@ -1,19 +1,22 @@ -import { ControlComponent, getValue, setValue } from '@mixxx-launch/mixxx' -import { Control, MakeDeckControlTemplate } from '../Control' +import { ChannelControlDef, ControlComponent, getValue, setValue } from '@mixxx-launch/mixxx' import { MidiComponent } from '../device' +import { ButtonBindingTemplate, ControlBindingTemplate, MakeDeckControlTemplate, Control } from '../Control' import { modes, retainAttackMode } from '../ModifierSidebar' export type Type = { type: 'slip' bindings: { - control: ControlComponent - button: MidiComponent + control: ControlBindingTemplate + button: ButtonBindingTemplate } state: { mode: boolean } - params: Record + params: { + deck: ChannelControlDef + gridPosition: [number, number] + } } -const make: MakeDeckControlTemplate = (_, gridPosition, deck) => { +const make: MakeDeckControlTemplate = ({ gridPosition, deck }) => { const onMidi = ({ bindings, state, context: { modifier, device } }: Control) => retainAttackMode(modifier, (mode, { value }) => { modes( @@ -39,29 +42,33 @@ const make: MakeDeckControlTemplate = (_, gridPosition, deck) => { return { bindings: { control: { - type: 'control', + type: ControlComponent, target: deck.slip_enabled, - update: - ({ bindings, state, context: { device } }: Control) => - ({ value }) => { - const color = state.mode ? 'orange' : 'red' - if (value) { - device.sendColor(bindings.button.control, device.colors[`hi_${color}`]) - } else { - device.sendColor(bindings.button.control, device.colors[`lo_${color}`]) - } - }, + listeners: { + update: + ({ bindings, state, context: { device } }: Control) => + ({ value }) => { + const color = state.mode ? 'orange' : 'red' + if (value) { + device.sendColor(bindings.button.control, device.colors[`hi_${color}`]) + } else { + device.sendColor(bindings.button.control, device.colors[`lo_${color}`]) + } + }, + } }, button: { - type: 'button', + type: MidiComponent, target: gridPosition, - midi: onMidi, - mount: - ({ bindings, state, context: { device } }: Control) => - () => { - const color = state.mode ? 'orange' : 'red' - device.sendColor(bindings.button.control, device.colors[`lo_${color}`]) - }, + listeners: { + midi: onMidi, + mount: + ({ bindings, state, context: { device } }: Control) => + () => { + const color = state.mode ? 'orange' : 'red' + device.sendColor(bindings.button.control, device.colors[`lo_${color}`]) + }, + } }, }, state: { diff --git a/packages/launchpad-common/src/controls/sync.ts b/packages/launchpad-common/src/controls/sync.ts index 1cfc7a0..99eb4ac 100644 --- a/packages/launchpad-common/src/controls/sync.ts +++ b/packages/launchpad-common/src/controls/sync.ts @@ -1,62 +1,70 @@ import { modes } from '../ModifierSidebar' -import type { ControlMessage, ControlComponent } from '@mixxx-launch/mixxx' +import { ControlMessage, ControlComponent, ChannelControlDef } from '@mixxx-launch/mixxx' import { setValue, getValue } from '@mixxx-launch/mixxx' -import { Control, MakeDeckControlTemplate } from '../Control' import { onAttack } from '../util' import { MidiComponent } from '../device' +import { ButtonBindingTemplate, ControlBindingTemplate, MakeDeckControlTemplate, Control } from '../Control' export type Type = { type: 'sync' bindings: { - sync: MidiComponent - syncMode: ControlComponent + sync: ButtonBindingTemplate + syncMode: ControlBindingTemplate + } + params: { + deck: ChannelControlDef + gridPosition: [number, number] } - params: Record state: Record } -const make: MakeDeckControlTemplate = (_, gridPosition, deck) => ({ +const make: MakeDeckControlTemplate = ({ gridPosition, deck }) => ({ state: {}, bindings: { sync: { - type: 'button', + type: MidiComponent, target: gridPosition, - midi: - ({ bindings, context: { modifier } }: Control) => - onAttack(() => { - modes( - modifier.getState(), - () => { - if (getValue(bindings.syncMode.control)) { - setValue(deck.sync_enabled, 0) - } else { - setValue(deck.sync_enabled, 1) - } - }, - () => { - if (getValue(bindings.syncMode.control) === 2) { - setValue(deck.sync_master, 0) - } else { - setValue(deck.sync_master, 1) - } - }, - ) - }), + listeners: { + midi: + ({ bindings, context: { modifier } }: Control) => + onAttack(() => { + modes( + modifier.getState(), + () => { + if (getValue(bindings.syncMode.control)) { + setValue(deck.sync_enabled, 0) + } else { + setValue(deck.sync_enabled, 1) + } + }, + () => { + if (getValue(bindings.syncMode.control) === 2) { + setValue(deck.sync_master, 0) + } else { + setValue(deck.sync_master, 1) + } + }, + ) + }), + + } }, syncMode: { - type: 'control', + type: ControlComponent, target: deck.sync_mode, - update: - ({ bindings, context: { device } }: Control) => - ({ value }: ControlMessage) => { - if (value === 0) { - device.clearColor(bindings.sync.control) - } else if (value === 1) { - device.sendColor(bindings.sync.control, device.colors.hi_orange) - } else if (value === 2) { - device.sendColor(bindings.sync.control, device.colors.hi_red) - } - }, + listeners: { + update: + ({ bindings, context: { device } }: Control) => + ({ value }: ControlMessage) => { + if (value === 0) { + device.clearColor(bindings.sync.control) + } else if (value === 1) { + device.sendColor(bindings.sync.control, device.colors.hi_orange) + } else if (value === 2) { + device.sendColor(bindings.sync.control, device.colors.hi_red) + } + }, + } }, }, }) diff --git a/packages/launchpad-common/src/controls/tap.ts b/packages/launchpad-common/src/controls/tap.ts index e78c157..e633a02 100644 --- a/packages/launchpad-common/src/controls/tap.ts +++ b/packages/launchpad-common/src/controls/tap.ts @@ -1,22 +1,25 @@ import { modes } from '../ModifierSidebar' -import type { ControlComponent, ControlMessage } from '@mixxx-launch/mixxx' +import { ChannelControlDef, ControlComponent, ControlMessage } from '@mixxx-launch/mixxx' import { setValue } from '@mixxx-launch/mixxx' -import { Control, MakeDeckControlTemplate } from '../Control' import Bpm from '../Bpm' import { onAttack } from '../util' import { MidiComponent } from '../device' +import { ButtonBindingTemplate, ControlBindingTemplate, MakeDeckControlTemplate, Control } from '../Control' export type Type = { type: 'tap' bindings: { - tap: MidiComponent - beat: ControlComponent + tap: ButtonBindingTemplate + beat: ControlBindingTemplate + } + params: { + deck: ChannelControlDef + gridPosition: [number, number] } - params: Record state: Record } -const make: MakeDeckControlTemplate = (_, gridPosition, deck) => { +const make: MakeDeckControlTemplate = ({ gridPosition, deck }) => { const tempoBpm = new Bpm() tempoBpm.on('tap', (avg: number) => { setValue(deck.bpm, avg) @@ -25,32 +28,37 @@ const make: MakeDeckControlTemplate = (_, gridPosition, deck) => { state: {}, bindings: { tap: { - type: 'button', + type: MidiComponent, target: gridPosition, - midi: - ({ context: { modifier } }: Control) => - onAttack(() => { - modes( - modifier.getState(), - () => tempoBpm.tap(), - () => setValue(deck.bpm_tap, 1), - () => setValue(deck.beats_translate_curpos, 1), - () => setValue(deck.beats_translate_match_alignment, 1), - ) - }), + listeners: { + midi: + ({ context: { modifier } }: Control) => + onAttack(() => { + modes( + modifier.getState(), + () => tempoBpm.tap(), + () => setValue(deck.bpm_tap, 1), + () => setValue(deck.beats_translate_curpos, 1), + () => setValue(deck.beats_translate_match_alignment, 1), + ) + }), + } }, beat: { - type: 'control', + type: ControlComponent, target: deck.beat_active, - update: - ({ context: { device }, bindings }: Control) => - ({ value }: ControlMessage) => { - if (value) { - device.sendColor(bindings.tap.control, device.colors.hi_red) - } else { - device.clearColor(bindings.tap.control) - } - }, + listeners: { + update: + ({ context: { device }, bindings }: Control) => + ({ value }: ControlMessage) => { + if (value) { + device.sendColor(bindings.tap.control, device.colors.hi_red) + } else { + device.clearColor(bindings.tap.control) + } + }, + + } }, }, } diff --git a/packages/launchpad-common/src/device.ts b/packages/launchpad-common/src/device.ts index 9dadfa6..cea5608 100644 --- a/packages/launchpad-common/src/device.ts +++ b/packages/launchpad-common/src/device.ts @@ -40,4 +40,10 @@ export abstract class LaunchpadDevice extends MidiDevice { } } -export class MidiComponent extends mixxxMidiComponent {} +export class MidiComponent extends mixxxMidiComponent { + onUnmount() { + // TODO: find better place for clearing color on onMount + this._device.clearColor(this.control) + super.onUnmount() + } +} diff --git a/packages/mixxx/src/globals.d.ts b/packages/mixxx/src/global.d.ts similarity index 100% rename from packages/mixxx/src/globals.d.ts rename to packages/mixxx/src/global.d.ts diff --git a/tsconfig.json b/tsconfig.json index d773041..05f9c2a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -14,6 +14,9 @@ { "path": "./packages/common" }, + { + "path": "./packages/launch-common" + }, { "path": "./packages/launchcontrol-common" },