Skip to content

Commit

Permalink
refactor: Split MIDI binding into multiple functions
Browse files Browse the repository at this point in the history
  • Loading branch information
bjoluc committed Feb 26, 2024
1 parent 0f1ea5b commit 893c6a0
Showing 1 changed file with 132 additions and 134 deletions.
266 changes: 132 additions & 134 deletions src/midi/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,26 +19,44 @@ export function bindDevicesToMidi(
});

for (const device of devices) {
bindDeviceToMidi(device, globalState, lifecycleCallbacks);
bindLifecycleEvents(device, lifecycleCallbacks);
bindChannelElements(device, globalState);

if (device instanceof MainDevice) {
bindControlSectionElements(device, globalState);
}
}

return { segmentDisplayManager };
}

function bindDeviceToMidi(
device: Device,
globalState: GlobalState,
lifecycleCallbacks: LifecycleCallbacks,
) {
const ports = device.ports;
function bindLifecycleEvents(device: Device, lifecycleCallbacks: LifecycleCallbacks) {
const output = device.ports.output;

lifecycleCallbacks.addActivationCallback((context) => {
// Workaround for https://forums.steinberg.net/t/831123:
output.sendNoteOn(context, 0x4f, 1);

// Workaround for encoder assign buttons not being enabled on activation
// (https://forums.steinberg.net/t/831123):
output.sendNoteOn(context, 0x2a, 1);
for (const note of [0x28, 0x29, 0x2b, 0x2c, 0x2d]) {
output.sendNoteOn(context, note, 0);
}

if (deviceConfig.channelColorSupport === "behringer") {
// Send an initial (all-black by default) color message to the device. Otherwise, in projects
// without enough channels for each device, devices without channels assigned to them would
// not receive a color update at all, leaving their displays white although they should be
// black.
device.colorManager?.sendColors(context);
}
});

// Lifecycle
lifecycleCallbacks.addDeactivationCallback((context) => {
device.colorManager?.resetColors(context);
device.lcdManager.clearDisplays(context);

const output = ports.output;

// Reset faders
for (let faderIndex = 0; faderIndex < 9; faderIndex++) {
output.sendMidi(context, [0xe0 + faderIndex, 0, 0]);
Expand All @@ -54,6 +72,10 @@ function bindDeviceToMidi(
output.sendMidi(context, [0xb0, 0x30 + encoderIndex, 0]);
}
});
}

function bindChannelElements(device: Device, globalState: GlobalState) {
const ports = device.ports;

for (const [channelIndex, channel] of device.channelElements.entries()) {
// Push Encoder
Expand Down Expand Up @@ -119,6 +141,11 @@ function bindDeviceToMidi(
}
};

/** Clears the channel meter's overload indicator */
const clearOverload = (context: MR_ActiveDevice) => {
sendMeterLevel(context, ports.output, channelIndex, 0xf);
};

// VU Meter
let lastMeterUpdateTime = 0;
channel.vuMeter.mOnProcessValueChange = (context, newValue) => {
Expand All @@ -136,19 +163,12 @@ function bindDeviceToMidi(
}
};

if (DEVICE_NAME === "MCU Pro") {
globalState.areChannelMetersEnabled.addOnChangeCallback(
(context, areMetersEnabled) => {
sendChannelMeterMode(context, ports.output, channelIndex, areMetersEnabled);
},
0, // priority = 0: Disable channel meters *before* updating the lower display row
);
}

/** Clears the channel meter's overload indicator */
const clearOverload = (context: MR_ActiveDevice) => {
sendMeterLevel(context, ports.output, channelIndex, 0xf);
};
globalState.areChannelMetersEnabled.addOnChangeCallback(
(context, areMetersEnabled) => {
sendChannelMeterMode(context, ports.output, channelIndex, areMetersEnabled);
},
0, // priority = 0: Disable channel meters *before* updating the lower display row
);

globalState.shouldMeterOverloadsBeCleared.addOnChangeCallback(
(context, shouldOverloadsBeCleared) => {
Expand All @@ -173,120 +193,98 @@ function bindDeviceToMidi(
channel.fader.bindToMidi(ports, channelIndex, globalState);
}

if (DEVICE_NAME === "MCU Pro") {
// Handle metering mode changes (globally)
globalState.isGlobalLcdMeterModeVertical.addOnChangeCallback((context, isMeterModeVertical) => {
sendGlobalMeterModeOrientation(context, ports.output, isMeterModeVertical);
});
}

if (deviceConfig.channelColorSupport === "behringer") {
// Send an initial (all-black by default) color message to the device. Otherwise, in projects
// without enough channels for each device, devices without channels assigned to them would not
// receive a color update at all, leaving their displays white although they should be black.
lifecycleCallbacks.addActivationCallback((context) => {
device.colorManager?.sendColors(context);
});
}

// Control Section (main devices only)
if (device instanceof MainDevice) {
const elements = device.controlSectionElements;
const buttons = elements.buttons;

lifecycleCallbacks.addActivationCallback((context) => {
// Workaround for https://forums.steinberg.net/t/831123:
ports.output.sendNoteOn(context, 0x4f, 1);
// Handle metering mode changes (globally)
globalState.isGlobalLcdMeterModeVertical.addOnChangeCallback((context, isMeterModeVertical) => {
sendGlobalMeterModeOrientation(context, ports.output, isMeterModeVertical);
});
}

// Workaround for encoder assign buttons not being enabled on activation
// (https://forums.steinberg.net/t/831123):
ports.output.sendNoteOn(context, 0x2a, 1);
for (const note of [0x28, 0x29, 0x2b, 0x2c, 0x2d]) {
ports.output.sendNoteOn(context, note, 0);
}
});
function bindControlSectionElements(device: MainDevice, globalState: GlobalState) {
const ports = device.ports;

elements.mainFader.bindToMidi(ports, 8, globalState);

for (const [index, button] of [
buttons.encoderAssign.track,
buttons.encoderAssign.send,
buttons.encoderAssign.pan,
buttons.encoderAssign.plugin,
buttons.encoderAssign.eq,
buttons.encoderAssign.instrument,

buttons.navigation.bank.left,
buttons.navigation.bank.right,
buttons.navigation.channel.left,
buttons.navigation.channel.right,

buttons.flip,
buttons.edit,
buttons.display,
buttons.timeMode,

...buttons.function,
...buttons.number,

buttons.modify.undo,
buttons.modify.redo,
buttons.modify.save,
buttons.modify.revert,

buttons.automation.read,
buttons.automation.write,
buttons.automation.sends,
buttons.automation.project,
buttons.automation.mixer,
buttons.automation.motor,

buttons.utility.instrument,
buttons.utility.main,
buttons.utility.soloDefeat,
buttons.utility.shift,

buttons.transport.left,
buttons.transport.right,
buttons.transport.cycle,
buttons.transport.punch,

buttons.transport.markers.previous,
buttons.transport.markers.add,
buttons.transport.markers.next,

buttons.transport.rewind,
buttons.transport.forward,
buttons.transport.stop,
buttons.transport.play,
buttons.transport.record,

buttons.navigation.directions.up,
buttons.navigation.directions.down,
buttons.navigation.directions.left,
buttons.navigation.directions.right,
buttons.navigation.directions.center,

buttons.scrub,
].entries()) {
button.bindToNote(ports, 40 + index);
}
const elements = device.controlSectionElements;
const buttons = elements.buttons;

elements.mainFader.bindToMidi(ports, 8, globalState);

for (const [index, button] of [
buttons.encoderAssign.track,
buttons.encoderAssign.send,
buttons.encoderAssign.pan,
buttons.encoderAssign.plugin,
buttons.encoderAssign.eq,
buttons.encoderAssign.instrument,

buttons.navigation.bank.left,
buttons.navigation.bank.right,
buttons.navigation.channel.left,
buttons.navigation.channel.right,

buttons.flip,
buttons.edit,
buttons.display,
buttons.timeMode,

...buttons.function,
...buttons.number,

buttons.modify.undo,
buttons.modify.redo,
buttons.modify.save,
buttons.modify.revert,

buttons.automation.read,
buttons.automation.write,
buttons.automation.sends,
buttons.automation.project,
buttons.automation.mixer,
buttons.automation.motor,

buttons.utility.instrument,
buttons.utility.main,
buttons.utility.soloDefeat,
buttons.utility.shift,

buttons.transport.left,
buttons.transport.right,
buttons.transport.cycle,
buttons.transport.punch,

buttons.transport.markers.previous,
buttons.transport.markers.add,
buttons.transport.markers.next,

buttons.transport.rewind,
buttons.transport.forward,
buttons.transport.stop,
buttons.transport.play,
buttons.transport.record,

buttons.navigation.directions.up,
buttons.navigation.directions.down,
buttons.navigation.directions.left,
buttons.navigation.directions.right,
buttons.navigation.directions.center,

buttons.scrub,
].entries()) {
button.bindToNote(ports, 40 + index);
}

// Segment Display - handled by the SegmentDisplayManager, except for the individual LEDs:
const { smpte, beats, solo } = elements.displayLeds;
[smpte, beats, solo].forEach((lamp, index) => {
lamp.bindToNote(ports.output, 0x71 + index);
});
// Segment Display - handled by the SegmentDisplayManager, except for the individual LEDs:
const { smpte, beats, solo } = elements.displayLeds;
[smpte, beats, solo].forEach((lamp, index) => {
lamp.bindToNote(ports.output, 0x71 + index);
});

// Jog wheel
elements.jogWheel.bindToControlChange(ports.input, 0x3c);
// Jog wheel
elements.jogWheel.bindToControlChange(ports.input, 0x3c);

// Foot control
elements.footSwitch1.mSurfaceValue.mMidiBinding.setInputPort(ports.input).bindToNote(0, 0x66);
elements.footSwitch2.mSurfaceValue.mMidiBinding.setInputPort(ports.input).bindToNote(0, 0x67);
elements.expressionPedal.mSurfaceValue.mMidiBinding
.setInputPort(ports.input)
.bindToControlChange(0, 0x2e)
.setTypeAbsolute();
}
// Foot control
elements.footSwitch1.mSurfaceValue.mMidiBinding.setInputPort(ports.input).bindToNote(0, 0x66);
elements.footSwitch2.mSurfaceValue.mMidiBinding.setInputPort(ports.input).bindToNote(0, 0x67);
elements.expressionPedal.mSurfaceValue.mMidiBinding
.setInputPort(ports.input)
.bindToControlChange(0, 0x2e)
.setTypeAbsolute();
}

0 comments on commit 893c6a0

Please sign in to comment.