diff --git a/src/devices/Midi.ts b/src/devices/Midi.ts index 3d0107e..b8ddd35 100644 --- a/src/devices/Midi.ts +++ b/src/devices/Midi.ts @@ -42,7 +42,7 @@ export class MidiControl { constructor( private device: MidiDevice, - public type: MessageType, + public type: T, private filter: Partial, private options: Partial = {} @@ -101,8 +101,17 @@ export class MidiControl { private feedback(data: DataLookup[T]) { if (!this.device.output) console.log('midi device tried to send output without output device defined') + + let type: MessageType = this.type; + + if (this.type === MessageType.NoteOnOff) { + const rawData = data as NoteOnOffValue; + + type = rawData.value ? MessageType.NoteOn : MessageType.NoteOff; + } + // ugly typing here, but library overloads are a little annoying to work around. worst case scenario nothing happens - this.device.output?.send(this.type as any, { ...data, ...this.filter } as any); + this.device.output?.send(type as any, { ...data, ...this.filter } as any); } public getPage(): number { return this.page }; @@ -147,9 +156,19 @@ export class MidiDevice { public controls: Array> = []; - constructor(inputDevice: string, outputDevice: string | boolean = false) { + constructor(inputDevice: string, outputDevice: string | boolean = false, virtual: boolean = false) { if (outputDevice === true) outputDevice = inputDevice; + if (virtual) { + this.input = new midi.Input(inputDevice, true) + + if (outputDevice) { + this.output = new midi.Output(outputDevice, true); + } + + return this; + } + const inputDeviceFull = midi.getInputs().find(i => i.includes(inputDevice)); if (!inputDeviceFull) { diff --git a/src/devices/OSC.ts b/src/devices/OSC.ts index 968c63b..7429707 100644 --- a/src/devices/OSC.ts +++ b/src/devices/OSC.ts @@ -2,7 +2,7 @@ import osc from "osc"; interface IOSCEvent { address: string; - handler: (message: osc.ResponseMessage) => void; + handler: (message: osc.ResponseMessage) => void; } export class OSCDevice { diff --git a/src/mappings/beunsize-ma3.ts b/src/mappings/beunsize-ma3.ts index c81e37b..55b9ca9 100644 --- a/src/mappings/beunsize-ma3.ts +++ b/src/mappings/beunsize-ma3.ts @@ -5,17 +5,51 @@ import { mapNumber } from '../utilityFunctions.js' export default async function mapping() { - const xtouch = new MidiDevice('X Touch Compact') + // DEVICES + + const xtouch = new MidiDevice('X Touch Compact', true) + const xtouch_loop = new MidiDevice('XTouch-loop', true, true) const keyboard = new MidiDevice('idobo') const ma3 = new OSCDevice('0.0.0.0', '127.0.0.1', 3000, 3001) + // UTILITY FUNCTIONS + + const noteButtonFeedback = (note: number, address: string) => { + const button = xtouch.addControl(MessageType.NoteOnOff, { note }); + + ma3.addListener(address, message => { + const value = message.args[0]?.value + if (typeof value !== 'number') return; + + button.handleFeedback(0, { value: value === 1 ? true : false }) + }) + + button.addOutput(0, data => ma3.sendInt(address, data.value ? 1 : 0)) + + return button; + } + + const ma3HardKey = (keycode: string, status: boolean) => { + ma3.sendString('/cmd', `Call Plugin "RBOSCKeys" "${keycode} ${status ? "press" : "release"}"`); + } + + + // MAPPINGS + + // passthroughs for encoders plugin + [21, 22, 23, 24, 25, 26].forEach(encoder => { + xtouch.addControl(MessageType.CC, { controller: encoder }).addOutput(0, message => { + xtouch_loop.output?.send(MessageType.CC, message) + }) + }) + // Executor Faders const faders = [0, 1, 2, 3, 4, 5, 6, 7, 8].map(f => xtouch.addControl(MessageType.CC, { controller: f })) faders.forEach((fader, index) => { const firstExec = 201; - const exec = firstExec - 1 + index; + const exec = firstExec + index; const address = `/Page/Fader${exec}`; fader.addOutput(0, (message) => { @@ -31,13 +65,105 @@ export default async function mapping() { }) }) + + // Executor Keys const executorButtons: MidiControl[][] = [ [16, 17, 18, 19, 20, 21, 22, 23], [24, 25, 26, 27, 28, 29, 30, 31], [32, 33, 34, 35, 36, 37, 38, 39], [40, 41, 42, 43, 44, 45, 46, 47, 48], - ].map(row => row.map(note => { xtouch.addControl(MessageType.NoteOnOff))) + ].map((row, rowIndex) => row.map((note, noteIndex) => { + const rowLookup = [401, 301, 201, 101]; + const address = `/Page/Key${rowLookup[rowIndex] ?? 101 + noteIndex}` + return noteButtonFeedback(note, address); + })) + + + // Misc Buttons + const [rwd, fwd, loop, rec, stop, play] = [ + { note: 49, key: 'PAGE_UP' }, + { note: 50, key: 'PAGE_DOWN' }, + { note: 51, key: '' }, + { note: 52, key: '' }, + { note: 53, key: '' }, + { note: 53, key: '' }, + ] + .map(i => { + const button = xtouch.addControl(MessageType.NoteOnOff, { note: i.note }) + button.addOutput(0, (message) => { + ma3HardKey(i.key, message.value) + }) + return button + }) + + + // Exec Encoders + + const encoders = [11, 12, 13, 14, 15, 16, 17, 18].map((cc, index) => { + const encoder = xtouch.addControl(MessageType.CC, { controller: cc }) + const firstExec = 401; + const address = `/Page/Encoder${firstExec + index}`; + const feedbackAddress = `/Page/Fader${firstExec + index}`; + + encoder.addOutput(0, message => { + ma3.sendInt(address, mapNumber(message.value, 0, 1, -1, 1)) + }) + + ma3.addListener(feedbackAddress, message => { + if (message.args[0] === undefined || message.args[0].type !== 'f') return; + const value = message.args[0].value; + + encoder.handleFeedback(0, { value: mapNumber(value, 0, 1, 0, 127) }) + }) + }) + + + // HARDKEYS + + const keymap: string[][] = [ + ['pause', 'goback', 'go', 'fixture', 'channel', 'group', 'menu', 'oops', 'num7', 'num8', 'num9', 'plus', 'highlight', 'on', 'off'], + ['learn', 'gobackfast', 'gofast', 'preset', 'sequence', 'cue', 'update', 'esc', 'num4', 'num5', 'num6', 'thru', 'solo', 'move', 'copy'], + ['def_pause', 'def_goback', 'def_go', 'edit', 'assign', 'time', 'store', 'clear', 'num1', 'num2', 'num3', 'minus', 'freeze', 'delete', 'align'], + ['x_1', 'x_2', 'x_3', 'x_4', 'x_5', 'x_6', 'x_7', 'x_8', 'num0', 'dot', 'if', 'at', 'preview', 'stomp', 'selfix'], + ['x_9', 'x_10', 'x_11', 'x_13', 'x_14', 'x_15', 'x_16', 'previous', 'next', 'ma1', 'please', 'blind', 'select', 'goto'] + ] + + const xkeymap: number[] = + [ + 291, 292, 293, 294, 295, 296, 297, 298, + 191, 192, 193, 194, 195, 196, 197, 198 + ]; + + keymap.forEach((row, rowIndex) => { + row.forEach((key, keyIndex) => { + let totalIndex = 0; + + + if (key.startsWith('x_')) { + const xKeyNum = parseInt(key.split('_')[1] ?? '0') + if (xKeyNum) { + const address = `/Page/Key${xkeymap[xKeyNum - 1]}`; + + keyboard + .addControl(MessageType.CC, { controller: totalIndex }) + .addOutput(0, message => { + ma3.sendInt(address, message.value ? 1 : 0) + }) + } + + } else { + keyboard + .addControl(MessageType.CC, { controller: totalIndex }) + .addOutput(0, message => { + ma3HardKey(key.toUpperCase(), message.value ? true : false); + }) + + } + + totalIndex++ + }) + }) } \ No newline at end of file diff --git a/src/types/osc/osc.d.ts b/src/types/osc/osc.d.ts index 71d74e9..8294f52 100644 --- a/src/types/osc/osc.d.ts +++ b/src/types/osc/osc.d.ts @@ -70,7 +70,7 @@ declare class OscEventsAndBase extends OscBase { // On every time. on(event: "ready", listener: OnListenerReady): void - on( + on( event: "message", listener: OnListenerMessage ): void