Skip to content

Commit

Permalink
refactor: Refactor encoder page mapping
Browse files Browse the repository at this point in the history
  • Loading branch information
bjoluc committed Apr 23, 2024
1 parent 16f82a9 commit 74ecfe2
Show file tree
Hide file tree
Showing 5 changed files with 217 additions and 175 deletions.
4 changes: 2 additions & 2 deletions src/device-configs/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -208,9 +208,9 @@ export interface DeviceConfig {
* The default config is defined in {@link file://./../mapping/encoders/index.ts}
*/
configureEncoderAssignments?(
defaultEncoderMapping: EncoderMappingConfig,
defaultEncoderMappings: EncoderMappingConfig[],
page: MR_FactoryMappingPage,
): EncoderMappingConfig;
): EncoderMappingConfig[];

enhanceMapping?(mappingDependencies: {
driver: MR_DeviceDriver;
Expand Down
152 changes: 27 additions & 125 deletions src/mapping/encoders/EncoderMapper.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,16 @@
import { EncoderPage, EncoderPageConfig } from "./EncoderPage";
import { EncoderMappingDependencies, EncoderPage, EncoderPageConfig } from "./EncoderPage";
import { EncoderPageGroup } from "./EncoderPageGroup";
import { LedButton } from "/decorators/surface-elements/LedButton";
import { ChannelSurfaceElements, ControlSectionSurfaceElements } from "/device-configs";
import { Device, MainDevice } from "/devices";
import { SegmentDisplayManager } from "/midi/managers/SegmentDisplayManager";
import { ChannelTextManager } from "/midi/managers/lcd/ChannelTextManager";
import { GlobalState } from "/state";
import { ContextVariable } from "/util";

/**
* The joint configuration for all "encoder assignments". Each encoder assignment maps a number of
* encoder pages to a specified button. Each encoder page specifies host mappings ("assignments")
* for an arbitrary number of encoders.
*/
export type EncoderMappingConfig = Array<{
export type EncoderMappingConfig = {
/**
* A function that – given a `MainDevice` – returns the device's button that will be mapped to the
* provided encoder pages.
Expand All @@ -26,134 +24,38 @@ export type EncoderMappingConfig = Array<{
* each device's activator button. It can be used to add additional host mappings.
*/
enhanceMapping?: (pages: EncoderPage[], activatorButtons: LedButton[]) => void;
}>;
};

export class EncoderMapper {
private readonly channelElements: ChannelSurfaceElements[];
private readonly channelTextManagers: ChannelTextManager[];

/** An array containing the control buttons of each main device */
private readonly deviceButtons: ControlSectionSurfaceElements["buttons"][];

private readonly mainDevices: MainDevice[];

private readonly subPageArea: MR_SubPageArea;

private activeEncoderPage = new ContextVariable<EncoderPage | undefined>(undefined);
private readonly dependencies: EncoderMappingDependencies;

constructor(
private readonly page: MR_FactoryMappingPage,
page: MR_FactoryMappingPage,
devices: Device[],
private readonly mixerBankChannels: MR_MixerBankChannel[],
private readonly segmentDisplayManager: SegmentDisplayManager,
private readonly globalState: GlobalState,
mixerBankChannels: MR_MixerBankChannel[],
segmentDisplayManager: SegmentDisplayManager,
globalState: GlobalState,
) {
this.channelElements = devices.flatMap((device) => device.channelElements);
this.channelTextManagers = devices.flatMap((device) => device.lcdManager.channelTextManagers);
this.mainDevices = devices.filter((device) => device instanceof MainDevice) as MainDevice[];
this.deviceButtons = this.mainDevices.map(
(device) => (device as MainDevice).controlSectionElements.buttons,
);

this.subPageArea = page.makeSubPageArea("Encoders");
const mainDevices = devices.filter((device) => device instanceof MainDevice) as MainDevice[];

this.dependencies = {
page,
encoderSubPageArea: page.makeSubPageArea("Encoders"),
mainDevices,
deviceButtons: mainDevices.map(
(device) => (device as MainDevice).controlSectionElements.buttons,
),
channelElements: devices.flatMap((device) => device.channelElements),
mixerBankChannels,
channelTextManagers: devices.flatMap((device) => device.lcdManager.channelTextManagers),
segmentDisplayManager,
globalState,
};
}

/**
* Takes an array of `EncoderPageConfig`s, splits all pages with more encoder assignments than
* physical encoders into multiple pages and returns the resulting page config array.
*/
private splitEncoderPageConfigs(pages: EncoderPageConfig[]) {
const encoderPageSize = this.channelElements.length;

return pages.flatMap((page) => {
const assignments = page.assignments;
if (Array.isArray(assignments) && assignments.length > encoderPageSize) {
const chunks = [];
for (let i = 0; i < assignments.length / encoderPageSize; i++) {
chunks.push(assignments.slice(i * encoderPageSize, (i + 1) * encoderPageSize));
}
return chunks.map((chunk) => ({
...page,
assignments: chunk,
}));
}

return page;
});
}

private bindEncoderPagesToAssignButton(
activatorButtons: LedButton[],
pageConfigs: EncoderPageConfig[],
) {
pageConfigs = this.splitEncoderPageConfigs(pageConfigs);
const pages = pageConfigs.map((pageConfig, pageIndex) => {
return new EncoderPage(
this,
pageConfig,
activatorButtons,
pageIndex,
pageConfigs.length,
this.page,
this.subPageArea,
this.deviceButtons,
this.channelElements,
this.mixerBankChannels,
this.channelTextManagers,
this.segmentDisplayManager,
this.globalState,
);
});

// Bind encoder assign buttons to cycle through sub pages in a round-robin fashion
for (const activatorButton of activatorButtons) {
const activatorButtonValue = activatorButton.mSurfaceValue;
this.page.makeActionBinding(
activatorButtonValue,
pages[0].subPages.default.mAction.mActivate,
);

let previousSubPages = pages[0].subPages;
for (const { subPages: currentSubPages } of pages) {
this.page
.makeActionBinding(activatorButtonValue, currentSubPages.default.mAction.mActivate)
.setSubPage(previousSubPages.default);
this.page
.makeActionBinding(activatorButtonValue, currentSubPages.default.mAction.mActivate)
.setSubPage(previousSubPages.flip);

previousSubPages = currentSubPages;
}
}

return pages;
}

applyEncoderMappingConfig(config: EncoderMappingConfig) {
for (const mappingConfig of config) {
const activatorButtons = this.mainDevices.map(mappingConfig.activatorButtonSelector);
const encoderPages = this.bindEncoderPagesToAssignButton(
activatorButtons,
mappingConfig.pages,
);

if (mappingConfig.enhanceMapping) {
mappingConfig.enhanceMapping(encoderPages, activatorButtons);
}
}
}

/**
* This is invoked by an {@link EncoderPage} when one of its subpages gets activated. It keeps
* track of the currently active `EncoderPage` and runs the {@link EncoderPage.onActivated()} and
* {@link EncoderPage.onDeactivated()} callbacks.
*/
onEncoderPageSubPageActivated(context: MR_ActiveDevice, encoderPage: EncoderPage) {
const lastActiveEncoderPage = this.activeEncoderPage.get(context);
if (lastActiveEncoderPage !== encoderPage) {
lastActiveEncoderPage?.onDeactivated(context);
this.activeEncoderPage.set(context, encoderPage);
encoderPage.onActivated(context);
applyEncoderMappingConfigs(configs: EncoderMappingConfig[]) {
for (const config of configs) {
new EncoderPageGroup(this.dependencies, config);
}
}
}
Loading

0 comments on commit 74ecfe2

Please sign in to comment.