Skip to content

Commit

Permalink
introduce control templating to LaunchControl mapping
Browse files Browse the repository at this point in the history
  • Loading branch information
dszakallas committed Dec 11, 2023
1 parent 34c3925 commit 9e67d0a
Show file tree
Hide file tree
Showing 8 changed files with 131 additions and 70 deletions.
8 changes: 1 addition & 7 deletions packages/launch-common/src/Control.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,7 @@ export type Bindings<C extends ControlType<any>> = {
[K in keyof C["bindings"]]: InstanceType<C["bindings"][K]["type"]>
}

export type IControl<Ctx, C extends ControlType<Ctx>> = {
bindings: Bindings<C>
state: C['state']
context: Ctx
}

export class Control<Ctx, C extends ControlType<Ctx>> extends Component implements IControl<Ctx, C> {
export class Control<Ctx, C extends ControlType<Ctx>> extends Component {
templates: C['bindings']
bindings: Bindings<C>
state: C['state']
Expand Down
Empty file.
54 changes: 54 additions & 0 deletions packages/launchcontrol-common/src/Control.ts
Original file line number Diff line number Diff line change
@@ -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<ControlContext>
export type Control<C extends ControlType> = BaseControl<ControlContext, C>

export type ControlBindingTemplate<C extends ControlType> = {
type: new (...args: any[]) => ControlComponent
target: ControlDef
softTakeOver?: boolean
listeners: {
update?: (c: Control<C>) => (message: ControlMessage) => void
mount?: (c: Control<C>) => () => void
unmount?: (c: Control<C>) => () => void
}
}

export type ButtonKey = readonly [number, number]

export type MidiTarget = [number, string, OnOff?]

export type MidiBindingTemplate<C extends ControlType> = {
type: new (...args: any[]) => LCMidiComponent
target: MidiTarget
listeners: {
midi?: (c: Control<C>) => (message: MidiMessage) => void
mount?: (c: Control<C>) => () => void
unmount?: (c: Control<C>) => () => void
}
}

export type BindingTemplates<C extends ControlType> = {
[K: string]: MidiBindingTemplate<C> | ControlBindingTemplate<C>
}

export const makeBindings = <C extends ControlType>(ctx: ControlContext, t: BindingTemplates<C>): Bindings<C> => {
const ret: { [_: string]: any } = {}
for (const k in t) {
if (t[k].type === ControlComponent) {
const c = t[k] as ControlBindingTemplate<C>
const softTakeOver = c.softTakeOver || false
ret[k] = new ControlComponent(c.target, softTakeOver)
} else {
const c = t[k] as MidiBindingTemplate<C>
ret[k] = new LCMidiComponent(ctx.device, ...c.target)
}
}
return ret as Bindings<C>
}
Empty file.
4 changes: 3 additions & 1 deletion packages/launchcontrol-common/src/device.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -75,7 +77,7 @@ export class LCMidiComponent extends MidiComponent<LaunchControlDevice> {
// 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
Expand Down
111 changes: 65 additions & 46 deletions packages/launchcontrol-common/src/eq.ts
Original file line number Diff line number Diff line change
@@ -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<Type>
} & {
[ch in typeof eq3Channel[number] as `kill.${ch}`]: ControlBindingTemplate<Type>
} & {
[ch in typeof eq3Channel[number] as `val.${ch}`]: ControlBindingTemplate<Type>
}
params: {
template: number
column: number
deck: number
}
state: Record<string, unknown>
}

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<ControlContext, Type>(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<Type> = ({ 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<Type>) => ({ 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<Type>) => ({ 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,
}
}

11 changes: 5 additions & 6 deletions packages/launchcontrol-common/src/index.ts
Original file line number Diff line number Diff line change
@@ -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'
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -375,15 +374,15 @@ 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),
])


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),
Expand Down
13 changes: 3 additions & 10 deletions packages/launchpad-common/src/Control.ts
Original file line number Diff line number Diff line change
@@ -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'
Expand All @@ -16,10 +16,8 @@ export type ControlContext = {
device: LaunchpadDevice
}


export type ControlType = BaseControlType<ControlContext>
export type Control<C extends ControlType> = BaseControl<ControlContext, C>
export type IControl<C extends ControlType> = BaseIControl<ControlContext, C>

export type MakeSamplerControlTemplate<C extends ControlType> = MakeControlTemplate<C>

Expand Down Expand Up @@ -86,11 +84,7 @@ type PresetTemplate = {
controls: ControlTemplate<any>[]
}

export type IPreset = {
controls: IControl<any>[]
}

export class Preset extends Component implements IPreset {
export class Preset extends Component {
controls: Control<any>[]

constructor(ctx: ControlContext, presetTemplate: PresetTemplate) {
Expand All @@ -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) {
Expand Down Expand Up @@ -154,4 +148,3 @@ export const makePresetTemplate = (
}
}


0 comments on commit 9e67d0a

Please sign in to comment.