From 9e67d0a5d384b525bdc18ca148ef0dabd7dc9567 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1vid=20Szak=C3=A1llas?= Date: Mon, 11 Dec 2023 23:09:28 +0100 Subject: [PATCH] introduce control templating to LaunchControl mapping --- packages/launch-common/src/Control.ts | 8 +- packages/launchcontrol-common/Control.ts | 0 packages/launchcontrol-common/src/Control.ts | 54 +++++++++ packages/launchcontrol-common/src/config.ts | 0 packages/launchcontrol-common/src/device.ts | 4 +- packages/launchcontrol-common/src/eq.ts | 111 +++++++++++-------- packages/launchcontrol-common/src/index.ts | 11 +- packages/launchpad-common/src/Control.ts | 13 +-- 8 files changed, 131 insertions(+), 70 deletions(-) create mode 100644 packages/launchcontrol-common/Control.ts create mode 100644 packages/launchcontrol-common/src/Control.ts create mode 100644 packages/launchcontrol-common/src/config.ts diff --git a/packages/launch-common/src/Control.ts b/packages/launch-common/src/Control.ts index 95459c9..7bf854b 100644 --- a/packages/launch-common/src/Control.ts +++ b/packages/launch-common/src/Control.ts @@ -39,13 +39,7 @@ 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 { +export class Control> extends Component { templates: C['bindings'] bindings: Bindings state: C['state'] diff --git a/packages/launchcontrol-common/Control.ts b/packages/launchcontrol-common/Control.ts new file mode 100644 index 0000000..e69de29 diff --git a/packages/launchcontrol-common/src/Control.ts b/packages/launchcontrol-common/src/Control.ts new file mode 100644 index 0000000..d5f7936 --- /dev/null +++ b/packages/launchcontrol-common/src/Control.ts @@ -0,0 +1,54 @@ +import { ControlComponent, ControlDef, ControlMessage, MidiMessage } from "@mixxx-launch/mixxx" +import { LCMidiComponent, LaunchControlDevice, OnOff } from "./device" +import { ControlType as BaseControlType, Control as BaseControl, Bindings } from '@mixxx-launch/launch-common/src/Control' + +export type ControlContext = { + device: LaunchControlDevice +} + +export type ControlType = BaseControlType +export type Control = BaseControl + +export type ControlBindingTemplate = { + type: new (...args: any[]) => ControlComponent + target: ControlDef + softTakeOver?: boolean + listeners: { + update?: (c: Control) => (message: ControlMessage) => void + mount?: (c: Control) => () => void + unmount?: (c: Control) => () => void + } +} + +export type ButtonKey = readonly [number, number] + +export type MidiTarget = [number, string, OnOff?] + +export type MidiBindingTemplate = { + type: new (...args: any[]) => LCMidiComponent + target: MidiTarget + listeners: { + midi?: (c: Control) => (message: MidiMessage) => void + mount?: (c: Control) => () => void + unmount?: (c: Control) => () => void + } +} + +export type BindingTemplates = { + [K: string]: MidiBindingTemplate | ControlBindingTemplate +} + +export const makeBindings = (ctx: ControlContext, t: BindingTemplates): Bindings => { + const ret: { [_: string]: any } = {} + for (const k in t) { + if (t[k].type === ControlComponent) { + const c = t[k] as ControlBindingTemplate + const softTakeOver = c.softTakeOver || false + ret[k] = new ControlComponent(c.target, softTakeOver) + } else { + const c = t[k] as MidiBindingTemplate + ret[k] = new LCMidiComponent(ctx.device, ...c.target) + } + } + return ret as Bindings +} diff --git a/packages/launchcontrol-common/src/config.ts b/packages/launchcontrol-common/src/config.ts new file mode 100644 index 0000000..e69de29 diff --git a/packages/launchcontrol-common/src/device.ts b/packages/launchcontrol-common/src/device.ts index 16e94b3..db1e85c 100644 --- a/packages/launchcontrol-common/src/device.ts +++ b/packages/launchcontrol-common/src/device.ts @@ -60,6 +60,8 @@ export abstract class LaunchControlDevice extends MidiDevice { } } +export type OnOff = "on" | "off" | undefined + // LCMidiComponent stands for LaunchControlMidiComponent and augments MidiComponent with // the LaunchControl specific property of having a separate identifier for the LED when targeting with SysEx. @@ -75,7 +77,7 @@ export class LCMidiComponent extends MidiComponent { // Use the note parameter to listen to note on/off events instead of control change events. This is required for // certain controls like the mute/solo/arm buttons or channel controls. For reference, see the LaunchControl // programmer manual or controller.json. - constructor(device: LaunchControlDevice, template: number, controlKey: string, note: "on" | "off" | null = null) { + constructor(device: LaunchControlDevice, template: number, controlKey: string, note?: OnOff) { const controlName = note ? `${template}.${controlKey}.${note}` : `${template}.${controlKey}` super(device, device.controls[controlName]) this.template = template diff --git a/packages/launchcontrol-common/src/eq.ts b/packages/launchcontrol-common/src/eq.ts index 9f29713..944c8cd 100644 --- a/packages/launchcontrol-common/src/eq.ts +++ b/packages/launchcontrol-common/src/eq.ts @@ -1,56 +1,75 @@ -import { range } from "@mixxx-launch/common" -import { absoluteNonLin, Component, MidiMessage } from "@mixxx-launch/mixxx" +import { Control as BaseControl, MakeControlTemplate } from '@mixxx-launch/launch-common/src/Control' +import { absoluteNonLin, MidiMessage } from "@mixxx-launch/mixxx" import { ControlComponent, ControlMessage, root, setValue } from "@mixxx-launch/mixxx/src/Control" import { LaunchControlDevice, LCMidiComponent } from "./device" -import { VerticalGroupParams } from "./util" +import { Control, ControlBindingTemplate, ControlContext, MidiBindingTemplate, makeBindings } from './Control' -export enum Eq3Channel { - Low, - Mid, - High, +const eq3Channel = ['low', 'mid', 'hi'] + +export type Type = { + type: 'eq3' + bindings: { + [ch in typeof eq3Channel[number] as `knob.${ch}`]: MidiBindingTemplate + } & { + [ch in typeof eq3Channel[number] as `kill.${ch}`]: ControlBindingTemplate + } & { + [ch in typeof eq3Channel[number] as `val.${ch}`]: ControlBindingTemplate + } + params: { + template: number + column: number + deck: number + } + state: Record } -const eq3 = (deck: number, col: number) => { - return [ - [`knob.0.${col}`, { type: 'eq3', params: { channel: Eq3Channel.High, deck: deck } }], - [`knob.1.${col}`, { type: 'eq3', params: { channel: Eq3Channel.Mid, deck: deck } }], - [`knob.2.${col}`, { type: 'eq3', params: { channel: Eq3Channel.Low, deck: deck } }], - ] as const +const channelColorPalette = [ + ['hi_red', 'lo_red'], + ['hi_yellow', 'lo_yellow'], + ['hi_green', 'lo_green'], + ['hi_amber', 'lo_amber'], +] as const + +export const makeEq3Control = (device: LaunchControlDevice, template: number, column: number, deck: number) => { + const eq3 = makeEq3({template, column, deck}) + return new BaseControl(makeBindings, eq3.bindings, eq3.state, { device }) } -export const makeEq3 = ({ template, columnOffset, numDecks }: VerticalGroupParams) => (device: LaunchControlDevice): Component[] => { - columnOffset = columnOffset || 0 - const children: Component[] = [] - - const channelColorPalette = [ - [device.colors.hi_red, device.colors.lo_red], - [device.colors.hi_yellow, device.colors.lo_yellow], - [device.colors.hi_green, device.colors.lo_green], - [device.colors.hi_amber, device.colors.lo_amber], - ] - - for (const i of range(numDecks)) { - const col = i + columnOffset - const eqs = eq3(col, col) - for (const [midi, cd] of eqs) { - const effectParam = root.equalizerRacks[0].effect_units[cd.params.deck].effects[0].parameters[cd.params.channel] - const paramControlComponent = new ControlComponent(effectParam.value, true) - children.push(paramControlComponent) - - const killedControlComponent = new ControlComponent(effectParam.button_value) - children.push(killedControlComponent) - - const midiComponent = new LCMidiComponent(device, template, midi) - midiComponent.addListener('midi', ({ value }: MidiMessage) => { - setValue(effectParam.value, absoluteNonLin(value, 0, 1, 4)) - }) - - killedControlComponent.addListener('update', ({ value }: ControlMessage) => { - device.sendColor(template, midiComponent.led, channelColorPalette[i % 4][value ? 1 : 0]) - }) - children.push(midiComponent) +export const makeEq3: MakeControlTemplate = ({ template, column, deck }) => { + const bindings: Type['bindings'] = {} + const fxParams = root.equalizerRacks[0].effect_units[deck].effects[0].parameters + eq3Channel.forEach((v, i) => { + bindings[`knob.${v}`] = { + type: LCMidiComponent, + target: [template, `knob.${2-i}.${column}`], + listeners: { + midi: ({ bindings }: Control) => ({ value }: MidiMessage) => { + setValue(bindings[`val.${v}`].control, absoluteNonLin(value, 0, 1, 4)) + } + } } - } - return children + bindings[`kill.${v}`] = { + type: ControlComponent, + target: fxParams[i].button_value, + listeners: { + update: ({ context: { device }, bindings }: Control) => ({ value }: ControlMessage) => { + device.sendColor(template, bindings[`knob.${v}`].led, device.colors[channelColorPalette[deck % 4][value ? 1 : 0]]) + } + } + } + + bindings[`val.${v}`] = { + type: ControlComponent, + target: fxParams[i].value, + softTakeOver: true, + listeners: {} + } + }) + + return { + state: {}, + bindings, + } } + diff --git a/packages/launchcontrol-common/src/index.ts b/packages/launchcontrol-common/src/index.ts index 6a04543..267bb3b 100644 --- a/packages/launchcontrol-common/src/index.ts +++ b/packages/launchcontrol-common/src/index.ts @@ -1,9 +1,9 @@ import { map, range } from '@mixxx-launch/common' -import { channelControlDefs, Component, ControlComponent, MidiControlDef, MidiMessage, sendShortMsg, setValue } from "@mixxx-launch/mixxx" +import { Component, ControlComponent, MidiControlDef, MidiMessage, channelControlDefs, sendShortMsg, setValue } from "@mixxx-launch/mixxx" import { ControlMessage, createEffectUnitChannelDef, getValue, numDecks as mixxxNumDecks, root } from "@mixxx-launch/mixxx/src/Control" -import { LaunchControlDevice, LCMidiComponent } from './device' +import { LCMidiComponent, LaunchControlDevice } from './device' import { makeEffectParameterPage } from './effectParameter' -import { makeEq3 } from './eq' +import { makeEq3Control } from './eq' import { makeQuickEffect } from './fx' import { makePadSelector } from './padSelector' import { MakePage, makePager } from './pager' @@ -120,7 +120,6 @@ const makeKillers = (template: number) => (device: LaunchControlDevice) => { device.sendColor(template, midiComponent.led, value ? device.colors.hi_red : device.colors.black) }) children.push(controlComponent) - } } return container(children) @@ -375,7 +374,7 @@ const makeEffectMix = ({ template, columnOffset, numDecks }: VerticalGroupParams const makeKitchenSinkPage = (template: number) => (device: LaunchControlDevice) => container([ - ...makeEq3({ template, columnOffset: 0, numDecks: mixxxNumDecks })(device), + ...map((i) => makeEq3Control(device, template, i, i), range(4)), ...makeGain({ template, columnOffset: 0, numDecks: mixxxNumDecks })(device), ...makeEffectMeta({ template, columnOffset: 4, numDecks: mixxxNumDecks })(device), ...makeEffectMix({ template, columnOffset: 4, numDecks: mixxxNumDecks })(device), @@ -383,7 +382,7 @@ const makeKitchenSinkPage = (template: number) => (device: LaunchControlDevice) const makeKitchenSinkPage2 = (template: number) => (device: LaunchControlDevice) => container([ - ...makeEq3({ template, columnOffset: 0, numDecks: mixxxNumDecks })(device), + ...map((i) => makeEq3Control(device, template, i, i), range(4)), ...makeGain({ template, columnOffset: 0, numDecks: mixxxNumDecks })(device), ...makeQuickEffect({ template, columnOffset: 4, numDecks: mixxxNumDecks })(device), ...makeEffectSuper({ template, columnOffset: 4, rowOffset: 1, numDecks: mixxxNumDecks })(device), diff --git a/packages/launchpad-common/src/Control.ts b/packages/launchpad-common/src/Control.ts index 7a76a39..66954c4 100644 --- a/packages/launchpad-common/src/Control.ts +++ b/packages/launchpad-common/src/Control.ts @@ -1,7 +1,7 @@ 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 { Bindings, Control as BaseControl, ControlTemplate, ControlType as BaseControlType, MakeControlTemplate } from '@mixxx-launch/launch-common/src/Control' import { ControlComponent, ControlDef, getValue, root } from '@mixxx-launch/mixxx/src/Control' @@ -16,10 +16,8 @@ export type ControlContext = { device: LaunchpadDevice } - export type ControlType = BaseControlType export type Control = BaseControl -export type IControl = BaseIControl export type MakeSamplerControlTemplate = MakeControlTemplate @@ -86,11 +84,7 @@ type PresetTemplate = { controls: ControlTemplate[] } -export type IPreset = { - controls: IControl[] -} - -export class Preset extends Component implements IPreset { +export class Preset extends Component { controls: Control[] constructor(ctx: ControlContext, presetTemplate: PresetTemplate) { @@ -99,7 +93,7 @@ export class Preset extends Component implements IPreset { return new BaseControl(makeBindings, c.bindings, c.state, ctx) }) } - + onMount() { super.onMount() for (const control of this.controls) { @@ -154,4 +148,3 @@ export const makePresetTemplate = ( } } -