WIP: APC mini mk2
This commit is contained in:
parent
d79c6421e5
commit
2efeb55543
4 changed files with 224 additions and 34 deletions
|
|
@ -2,7 +2,7 @@ import osc from "osc";
|
||||||
|
|
||||||
interface IOSCEvent {
|
interface IOSCEvent {
|
||||||
address: string;
|
address: string;
|
||||||
handler: (data: string | number) => void;
|
handler: (data: Array<string | number>) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class OSCDevice {
|
export class OSCDevice {
|
||||||
|
|
@ -22,15 +22,21 @@ export class OSCDevice {
|
||||||
this.port.on('message', msg => {
|
this.port.on('message', msg => {
|
||||||
for (const listener of this.listeners) {
|
for (const listener of this.listeners) {
|
||||||
if (msg.address !== listener.address) continue;
|
if (msg.address !== listener.address) continue;
|
||||||
if (msg.args[0] !== undefined) {
|
if (!msg.args.length) continue;
|
||||||
let value: string | number | undefined;
|
|
||||||
if (typeof msg.args[0] === 'object' && ['string', 'number'].includes(typeof msg.args[0].value)) value = msg.args[0].value as string | number;
|
|
||||||
if (typeof msg.args[0] === 'string' || typeof msg.args[0] === 'number') value = msg.args[0];
|
|
||||||
|
|
||||||
if (value !== undefined) {
|
const argValues = msg.args.map(arg => {
|
||||||
listener.handler(value);
|
if (typeof arg === 'object' && ['string', 'number'].includes(typeof arg.value)) {
|
||||||
|
return arg.value as string | number;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
if (typeof arg === 'number' || typeof arg === 'string') {
|
||||||
|
return arg;
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}).filter(v => v !== undefined);
|
||||||
|
|
||||||
|
listener.handler(argValues);
|
||||||
}
|
}
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
@ -59,7 +65,7 @@ export class OSCDevice {
|
||||||
this.port.send({ address });
|
this.port.send({ address });
|
||||||
}
|
}
|
||||||
|
|
||||||
public addListener(address: string, handler: (data: string | number) => void): number {
|
public addListener(address: string, handler: (data: Array<string | number>) => void): number {
|
||||||
const newLength = this.listeners.push({
|
const newLength = this.listeners.push({
|
||||||
address,
|
address,
|
||||||
handler
|
handler
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import * as midi from 'easymidi'
|
import * as midi from 'easymidi'
|
||||||
import { MessageType, MidiControl, MidiDevice } from "../devices/Midi.js"
|
import { MessageType, MidiControl, MidiDevice } from "../devices/Midi.js"
|
||||||
import { OSCDevice } from "../devices/OSC.js"
|
import { OSCDevice } from "../devices/OSC.js"
|
||||||
import { delay, mapNumber, numberRange, retry } from '../utilityFunctions.js'
|
import { delay, findClosestColor, mapNumber, numberRange, retry } from '../utilityFunctions.js'
|
||||||
|
|
||||||
|
|
||||||
function ma3HardKey(ma3: OSCDevice, keycode: string, status: boolean) {
|
function ma3HardKey(ma3: OSCDevice, keycode: string, status: boolean) {
|
||||||
|
|
@ -10,11 +10,145 @@ function ma3HardKey(ma3: OSCDevice, keycode: string, status: boolean) {
|
||||||
|
|
||||||
enum ApcColor {
|
enum ApcColor {
|
||||||
Off = 0,
|
Off = 0,
|
||||||
Green = 1,
|
Beige = 108,
|
||||||
Red = 3,
|
White = 3,
|
||||||
Yellow = 5
|
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] },
|
||||||
|
];
|
||||||
|
|
||||||
|
console.log(findClosestColor([249, 129, 56], apcColors))
|
||||||
|
|
||||||
const MA3_FADER_MIN = 0;
|
const MA3_FADER_MIN = 0;
|
||||||
const MA3_FADER_MAX = 100;
|
const MA3_FADER_MAX = 100;
|
||||||
|
|
@ -42,7 +176,7 @@ export default async function mapping() {
|
||||||
|
|
||||||
|
|
||||||
const tryApcMini = async () => {
|
const tryApcMini = async () => {
|
||||||
const apcmini = await retry(() => new MidiDevice('APC MINI', true), 1000, 'apc mini');
|
const apcmini = await retry(() => new MidiDevice('APC mini mk2', true), 1000, 'apc mini');
|
||||||
apcminiMapping(apcmini, ma3)
|
apcminiMapping(apcmini, ma3)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -57,7 +191,7 @@ async function xtouchMapping(xtouch: MidiDevice, xtouch_loop: MidiDevice, ma3: O
|
||||||
const button = xtouch.addControl(MessageType.NoteOnOff, { note });
|
const button = xtouch.addControl(MessageType.NoteOnOff, { note });
|
||||||
|
|
||||||
ma3.addListener(address, data => {
|
ma3.addListener(address, data => {
|
||||||
const value = data
|
const value = data[0]
|
||||||
if (typeof value !== 'number') return;
|
if (typeof value !== 'number') return;
|
||||||
|
|
||||||
button.handleFeedback(0, { value: value === 1 ? true : false })
|
button.handleFeedback(0, { value: value === 1 ? true : false })
|
||||||
|
|
@ -92,8 +226,8 @@ async function xtouchMapping(xtouch: MidiDevice, xtouch_loop: MidiDevice, ma3: O
|
||||||
})
|
})
|
||||||
|
|
||||||
ma3.addListener(address, value => {
|
ma3.addListener(address, value => {
|
||||||
if (typeof value === 'number') {
|
if (typeof value[0] === 'number') {
|
||||||
const mappedValue = mapNumber(value, 0, 1, 0, 127);
|
const mappedValue = mapNumber(value[0], 0, 1, 0, 127);
|
||||||
fader.handleFeedback(0, { value: mappedValue })
|
fader.handleFeedback(0, { value: mappedValue })
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
@ -145,9 +279,9 @@ async function xtouchMapping(xtouch: MidiDevice, xtouch_loop: MidiDevice, ma3: O
|
||||||
})
|
})
|
||||||
|
|
||||||
ma3.addListener(feedbackAddress, data => {
|
ma3.addListener(feedbackAddress, data => {
|
||||||
if (typeof data !== 'number') return;
|
if (typeof data[0] !== 'number') return;
|
||||||
|
|
||||||
encoder.handleFeedback(0, { value: mapNumber(data, 0, 1, 0, 127) })
|
encoder.handleFeedback(0, { value: mapNumber(data[0], 0, 1, 0, 127) })
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -218,7 +352,7 @@ async function apcminiMapping(apcmini: MidiDevice, ma3: OSCDevice) {
|
||||||
});
|
});
|
||||||
|
|
||||||
// playback fader buttons
|
// playback fader buttons
|
||||||
[...numberRange(64, 71), 98].forEach((note, index) => {
|
[...numberRange(100, 107), 122].forEach((note, index) => {
|
||||||
const button = apcmini.addControl(MessageType.NoteOnOff, { note });
|
const button = apcmini.addControl(MessageType.NoteOnOff, { note });
|
||||||
const exec = APC_FIRST_EXEC + index;
|
const exec = APC_FIRST_EXEC + index;
|
||||||
const address = `/Page/Key${exec}`
|
const address = `/Page/Key${exec}`
|
||||||
|
|
@ -228,21 +362,22 @@ async function apcminiMapping(apcmini: MidiDevice, ma3: OSCDevice) {
|
||||||
})
|
})
|
||||||
|
|
||||||
ma3.addListener(address, value => {
|
ma3.addListener(address, value => {
|
||||||
button.handleFeedback(0, { velocity: value ? 3 : 0, value: true })
|
button.handleFeedback(0, { velocity: value[0] ? 127 : 0, value: true })
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
// main key mapping, upside down
|
// main key mapping, upside down
|
||||||
|
|
||||||
const rowColors = [
|
const rowColors = [
|
||||||
ApcColor.Red,
|
ApcColor.Beige,
|
||||||
ApcColor.Red,
|
ApcColor.Beige,
|
||||||
ApcColor.Red,
|
ApcColor.Beige,
|
||||||
ApcColor.Off,
|
ApcColor.Off,
|
||||||
ApcColor.Off,
|
ApcColor.Off,
|
||||||
ApcColor.Off,
|
ApcColor.Off,
|
||||||
ApcColor.Yellow,
|
ApcColor.Beige,
|
||||||
ApcColor.Yellow,
|
ApcColor.Beige,
|
||||||
];
|
];
|
||||||
|
|
||||||
[
|
[
|
||||||
|
|
@ -266,21 +401,31 @@ async function apcminiMapping(apcmini: MidiDevice, ma3: OSCDevice) {
|
||||||
|
|
||||||
ma3.addListener(address, value => {
|
ma3.addListener(address, value => {
|
||||||
let color: number = ApcColor.Off;
|
let color: number = ApcColor.Off;
|
||||||
|
let channel = 5 as midi.Channel;
|
||||||
|
|
||||||
if (rowColors[row]) {
|
if (rowColors[row]) {
|
||||||
if (value === 1) color = rowColors[row]
|
if (value[0] === 1 || value[0] === 2) color = rowColors[row]
|
||||||
if (value === 2) color = ApcColor.Green
|
|
||||||
|
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,
|
button.handleFeedback(0,
|
||||||
{ value: true, velocity: color }
|
{ channel: channel, value: true, velocity: color }
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
const firstCommandNote = 82;
|
const firstCommandNote = 112;
|
||||||
// Command Keys
|
// Command Keys
|
||||||
[
|
[
|
||||||
'clear',
|
'clear',
|
||||||
|
|
@ -289,13 +434,17 @@ async function apcminiMapping(apcmini: MidiDevice, ma3: OSCDevice) {
|
||||||
'highlight',
|
'highlight',
|
||||||
'store',
|
'store',
|
||||||
'update',
|
'update',
|
||||||
'def_goback',
|
'off',
|
||||||
'def_go'
|
'def_go'
|
||||||
].forEach((key, index) => {
|
].forEach((key, index) => {
|
||||||
const note = firstCommandNote + index;
|
const note = firstCommandNote + index;
|
||||||
|
|
||||||
apcmini.addControl(MessageType.NoteOnOff, { note }).addOutput(0, data => {
|
const button = apcmini.addControl(MessageType.NoteOnOff, { note })
|
||||||
|
|
||||||
|
button.addOutput(0, data => {
|
||||||
ma3HardKey(ma3, key, data.value);
|
ma3HardKey(ma3, key, data.value);
|
||||||
})
|
})
|
||||||
|
|
||||||
|
button.handleFeedback(0, { value: true, velocity: 127 })
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -150,7 +150,7 @@ export default async function mapping() {
|
||||||
})
|
})
|
||||||
|
|
||||||
odev.addListener(addr, d => {
|
odev.addListener(addr, d => {
|
||||||
control.handleFeedback(page, { velocity: d === 0 ? 127 : 0 });
|
control.handleFeedback(page, { velocity: d[0] === 0 ? 127 : 0 });
|
||||||
})
|
})
|
||||||
|
|
||||||
odev.sendNull(addr);
|
odev.sendNull(addr);
|
||||||
|
|
|
||||||
|
|
@ -43,3 +43,38 @@ export function numberRange(from: number, to: number): number[] {
|
||||||
export function numbersFrom(from: number, amount: number): number[] {
|
export function numbersFrom(from: number, amount: number): number[] {
|
||||||
return numberRange(from, from + amount - 1)
|
return numberRange(from, from + amount - 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function colorDistance([r1, g1, b1]: number[], [r2, g2, b2]: number[]) {
|
||||||
|
if (
|
||||||
|
r1 === undefined ||
|
||||||
|
r2 === undefined ||
|
||||||
|
g1 === undefined ||
|
||||||
|
g2 === undefined ||
|
||||||
|
b1 === undefined ||
|
||||||
|
b2 === undefined
|
||||||
|
) return undefined;
|
||||||
|
|
||||||
|
return Math.sqrt(
|
||||||
|
(r1 - r2) ** 2 +
|
||||||
|
(g1 - g2) ** 2 +
|
||||||
|
(b1 - b2) ** 2
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function findClosestColor(inputRgb: number[], deviceColors: { num: number, color: number[] }[]) {
|
||||||
|
let closest = null;
|
||||||
|
let smallestDistance = Infinity;
|
||||||
|
|
||||||
|
for (const color of deviceColors) {
|
||||||
|
const distance = colorDistance(inputRgb, color.color);
|
||||||
|
|
||||||
|
if (distance === undefined) continue;
|
||||||
|
|
||||||
|
if (distance < smallestDistance) {
|
||||||
|
smallestDistance = distance;
|
||||||
|
closest = color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return closest;
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue