From 77a1dc85d2cd34ce1906bc7328b2b1e2e409d3f5 Mon Sep 17 00:00:00 2001 From: RikSolo Date: Fri, 26 Jun 2026 16:14:55 +0200 Subject: [PATCH] add standalone apc mapping --- package.json | 2 +- src/mappings/standalone-apc.ts | 280 +++++++++++++++++++++++++++++++++ 2 files changed, 281 insertions(+), 1 deletion(-) create mode 100644 src/mappings/standalone-apc.ts diff --git a/package.json b/package.json index d6217e4..8e5c71b 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "main": "dist/index.js", "scripts": { "start": "node --expose-gc dist/index.js", - "dev": "tsc-watch --onSuccess \"npm start beunsize-ma3\"" + "dev": "tsc-watch --onSuccess \"npm start standalone-apc\"" }, "author": "Rik Berkelder (https://riksolo.com)", "license": "ISC", diff --git a/src/mappings/standalone-apc.ts b/src/mappings/standalone-apc.ts new file mode 100644 index 0000000..1ccb457 --- /dev/null +++ b/src/mappings/standalone-apc.ts @@ -0,0 +1,280 @@ +import * as midi from 'easymidi' +import { MessageType, MidiDevice } from "../devices/Midi.js"; +import { OSCDevice } from "../devices/OSC.js"; +import { findClosestColor, mapNumber, numberRange, retry } from "../utilityFunctions.js"; + +const MA3_FADER_MIN = 0; +const MA3_FADER_MAX = 127; + +export default async function mapping() { + + const ma3 = new OSCDevice('0.0.0.0', '127.0.0.1', 8002, 8000) + + const tryApcMini = async () => { + const apcmini = await retry(() => new MidiDevice('APC mini mk2', true), 1000, 'apc mini'); + apcminiMapping(apcmini, ma3) + } + + tryApcMini(); + +} + +function ma3HardKey(ma3: OSCDevice, keycode: string, status: boolean) { + ma3.sendString('/cmd', `Plugin "RBOSCKeys" "${keycode.toUpperCase()} ${status ? "press" : "release"}"`); +} + +enum ApcColor { + Off = 0, + Beige = 108, + White = 3, + Green = 21, + Red = 5, + Blue = 45, +} + +const apcColors: { num: number; color: number[] }[] = [ + { num: 0, color: [0, 0, 0] }, + { num: 1, color: [30, 30, 30] }, + { num: 2, color: [127, 127, 127] }, + { num: 3, color: [255, 255, 255] }, + { num: 4, color: [255, 76, 76] }, + { num: 5, color: [255, 0, 0] }, + { num: 6, color: [89, 0, 0] }, + { num: 7, color: [25, 0, 0] }, + { num: 8, color: [255, 189, 108] }, + { num: 9, color: [255, 84, 0] }, + { num: 10, color: [89, 29, 0] }, + { num: 11, color: [39, 27, 0] }, + { num: 12, color: [255, 255, 76] }, + { num: 13, color: [255, 255, 0] }, + { num: 14, color: [89, 89, 0] }, + { num: 15, color: [25, 25, 0] }, + { num: 16, color: [136, 255, 76] }, + { num: 17, color: [84, 255, 0] }, + { num: 18, color: [29, 89, 0] }, + { num: 19, color: [20, 43, 0] }, + { num: 20, color: [76, 255, 76] }, + { num: 21, color: [0, 255, 0] }, + { num: 22, color: [0, 89, 0] }, + { num: 23, color: [0, 25, 0] }, + { num: 24, color: [76, 255, 94] }, + { num: 25, color: [0, 255, 25] }, + { num: 26, color: [0, 89, 13] }, + { num: 27, color: [0, 25, 2] }, + { num: 28, color: [76, 255, 136] }, + { num: 29, color: [0, 255, 85] }, + { num: 30, color: [0, 89, 29] }, + { num: 31, color: [0, 31, 18] }, + { num: 32, color: [76, 255, 183] }, + { num: 33, color: [0, 255, 153] }, + { num: 34, color: [0, 89, 53] }, + { num: 35, color: [0, 25, 18] }, + { num: 36, color: [76, 195, 255] }, + { num: 37, color: [0, 169, 255] }, + { num: 38, color: [0, 65, 82] }, + { num: 39, color: [0, 16, 25] }, + { num: 40, color: [76, 136, 255] }, + { num: 41, color: [0, 85, 255] }, + { num: 42, color: [0, 29, 89] }, + { num: 43, color: [0, 8, 25] }, + { num: 44, color: [76, 76, 255] }, + { num: 45, color: [0, 0, 255] }, + { num: 46, color: [0, 0, 89] }, + { num: 47, color: [0, 0, 25] }, + { num: 48, color: [135, 76, 255] }, + { num: 49, color: [84, 0, 255] }, + { num: 50, color: [25, 0, 100] }, + { num: 51, color: [15, 0, 48] }, + { num: 52, color: [255, 76, 255] }, + { num: 53, color: [255, 0, 255] }, + { num: 54, color: [89, 0, 89] }, + { num: 55, color: [25, 0, 25] }, + { num: 56, color: [255, 76, 135] }, + { num: 57, color: [255, 0, 84] }, + { num: 58, color: [89, 0, 29] }, + { num: 59, color: [34, 0, 19] }, + { num: 60, color: [255, 21, 0] }, + { num: 61, color: [153, 53, 0] }, + { num: 62, color: [121, 81, 0] }, + { num: 63, color: [67, 100, 0] }, + { num: 64, color: [3, 57, 0] }, + { num: 65, color: [0, 87, 53] }, + { num: 66, color: [0, 84, 127] }, + { num: 67, color: [0, 0, 255] }, + { num: 68, color: [0, 69, 79] }, + { num: 69, color: [37, 0, 204] }, + { num: 70, color: [127, 127, 127] }, + { num: 71, color: [32, 32, 32] }, + { num: 72, color: [255, 0, 0] }, + { num: 73, color: [189, 255, 45] }, + { num: 74, color: [175, 237, 6] }, + { num: 75, color: [100, 255, 9] }, + { num: 76, color: [16, 139, 0] }, + { num: 77, color: [0, 255, 135] }, + { num: 78, color: [0, 169, 255] }, + { num: 79, color: [0, 42, 255] }, + { num: 80, color: [63, 0, 255] }, + { num: 81, color: [122, 0, 255] }, + { num: 82, color: [178, 26, 125] }, + { num: 83, color: [64, 33, 0] }, + { num: 84, color: [255, 74, 0] }, + { num: 85, color: [136, 225, 6] }, + { num: 86, color: [114, 255, 21] }, + { num: 87, color: [0, 255, 0] }, + { num: 88, color: [59, 255, 38] }, + { num: 89, color: [89, 255, 113] }, + { num: 90, color: [56, 255, 204] }, + { num: 91, color: [91, 138, 255] }, + { num: 92, color: [49, 81, 198] }, + { num: 93, color: [135, 127, 233] }, + { num: 94, color: [211, 29, 255] }, + { num: 95, color: [255, 0, 93] }, + { num: 96, color: [255, 127, 0] }, + { num: 97, color: [185, 176, 0] }, + { num: 98, color: [144, 255, 0] }, + { num: 99, color: [131, 93, 7] }, + { num: 100, color: [57, 43, 0] }, + { num: 101, color: [20, 76, 16] }, + { num: 102, color: [13, 80, 56] }, + { num: 103, color: [21, 21, 42] }, + { num: 104, color: [22, 32, 90] }, + { num: 105, color: [105, 60, 28] }, + { num: 106, color: [168, 0, 10] }, + { num: 107, color: [222, 81, 61] }, + { num: 108, color: [216, 106, 28] }, + { num: 109, color: [255, 225, 38] }, + { num: 110, color: [158, 225, 47] }, + { num: 111, color: [103, 181, 15] }, + { num: 112, color: [30, 30, 48] }, + { num: 113, color: [220, 255, 107] }, + { num: 114, color: [128, 255, 189] }, + { num: 115, color: [154, 153, 255] }, + { num: 116, color: [142, 102, 255] }, + { num: 117, color: [64, 64, 64] }, + { num: 118, color: [117, 117, 117] }, + { num: 119, color: [224, 255, 255] }, + { num: 120, color: [160, 0, 0] }, + { num: 121, color: [53, 0, 0] }, + { num: 122, color: [26, 208, 0] }, + { num: 123, color: [7, 66, 0] }, + { num: 124, color: [185, 176, 0] }, + { num: 125, color: [63, 49, 0] }, + { num: 126, color: [179, 95, 0] }, + { num: 127, color: [75, 21, 2] }, +]; + +async function apcminiMapping(apcmini: MidiDevice, ma3: OSCDevice) { + const APC_FIRST_EXEC = 201; + + apcmini.input.on('cc', m => console.dir(m)); + apcmini.input.on('noteon', m => console.dir(m)); + apcmini.input.on('noteoff', m => console.dir(m)); + + // playback faders + + numberRange(48, 56).forEach((controller, index) => { + const fader = apcmini.addControl(MessageType.CC, { controller }); + const exec = APC_FIRST_EXEC + index; + const address = `/Page/Fader${exec}`; + + fader.addOutput(0, (message) => { + ma3.sendFloat(address, mapNumber(message.value, 2, 125, MA3_FADER_MIN, MA3_FADER_MAX)); + }); + }); + + // playback fader buttons + [...numberRange(100, 107), 122].forEach((note, index) => { + const button = apcmini.addControl(MessageType.NoteOnOff, { note }); + const exec = APC_FIRST_EXEC + index; + const address = `/Page/Key${exec}` + + button.addOutput(0, message => { + ma3.sendInt(address, message.value ? 1 : 0); + }) + + ma3.addListener(address, value => { + button.handleFeedback(0, { velocity: value[0] ? 127 : 0, value: true }) + }) + }); + + + const rowColors = [ + ApcColor.Beige, + ApcColor.Beige, + ApcColor.Beige, + ApcColor.Off, + ApcColor.Off, + ApcColor.Off, + ApcColor.Beige, + ApcColor.Beige, + ]; + + [ + numberRange(101, 108), + numberRange(301, 308), + numberRange(401, 408), + null, + null, + null, + numberRange(191, 198), + numberRange(291, 298), + ].forEach((rowContent, row) => { + const rowFirstCC = row * 8; + if (rowContent !== null) rowContent.forEach((exec, index) => { + const button = apcmini.addControl(MessageType.NoteOnOff, { note: rowFirstCC + index }) + const address = `/Page/Key${exec}` + + button.addOutput(0, value => { + ma3.sendInt(address, value.value ? 1 : 0) + }) + + ma3.addListener(address, value => { + let color: number = ApcColor.Off; + let channel = 5 as midi.Channel; + + if (rowColors[row]) { + if (value[0] === 1 || value[0] === 2) color = rowColors[row] + + if (value[0] === 2) { + channel = 8 + } + + if (value.length === 4) { + const found = findClosestColor(value.slice(1, 4) as number[], apcColors) + if (found) color = found.num; + } + + } + + button.handleFeedback(0, + { channel: channel, value: true, velocity: color } + ) + }) + + }) + }); + + const firstCommandNote = 112; + // Command Keys + [ + 'clear', + 'next', + 'blind', + 'highlight', + 'store', + 'update', + 'off', + 'def_go' + ].forEach((key, index) => { + const note = firstCommandNote + index; + + const button = apcmini.addControl(MessageType.NoteOnOff, { note }) + + button.addOutput(0, data => { + ma3HardKey(ma3, key, data.value); + }) + + button.handleFeedback(0, { value: true, velocity: 127 }) + }) +} +