Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add an iCON V1-M script variant #38

Merged
merged 47 commits into from
Jan 4, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
a7e1962
Add V1-M device surface
bjoluc Apr 5, 2024
1360a31
Implement main metering
bjoluc Apr 11, 2024
721e32e
Add meter peak levels and fader value display to secondary scribble s…
bjoluc Apr 15, 2024
b9f2c88
Map core MCU functions on button matrix
bjoluc Apr 16, 2024
102272d
Bind all matrix buttons
bjoluc Apr 16, 2024
729b139
Implement encoder assignments on second function layer
bjoluc Apr 17, 2024
70e5620
Add reset meters button
bjoluc Apr 17, 2024
e93f379
Update SysEx header for secondary scribble strip displays
bjoluc Apr 17, 2024
f4483b7
Update button matrix MIDI mappings to work around firmware limitations
bjoluc Apr 23, 2024
178fc36
Fix faders not being sent down when unassigned
bjoluc Apr 23, 2024
19f09e5
Merge branch 'develop' into device-icon-v1m
bjoluc Apr 23, 2024
1742d55
Merge branch 'develop' into device-icon-v1m
bjoluc Apr 24, 2024
b321e58
Merge branch 'develop' into device-icon-v1m
bjoluc Apr 24, 2024
38bc48d
Rename `configureEncoderMapping` to `configureEncoderMappings` in dev…
bjoluc Apr 24, 2024
5695da4
Merge branch 'develop' into device-icon-v1m
bjoluc Apr 28, 2024
e78eda1
Configure supplementary shift buttons
bjoluc Apr 29, 2024
0fee586
Update some button matrix mappings
bjoluc Apr 29, 2024
3a006b1
Add foot switches to main device surface
bjoluc Apr 29, 2024
813f623
Add V1-M remarks to readme
bjoluc Apr 30, 2024
2eaa479
Highlight devices with supplementary remarks in readme
bjoluc Apr 30, 2024
d1c5dbd
Add V1-M iMAP dump
bjoluc Apr 30, 2024
4e978c1
Fix a typo
bjoluc May 3, 2024
c96c845
Merge branch 'develop' into device-icon-v1m
bjoluc May 4, 2024
33dbec9
Reset VU meters when channels become unassigned
bjoluc May 4, 2024
10e7a74
Merge branch 'develop' into device-icon-v1m
bjoluc May 4, 2024
8e2c838
Map default MCU encoder assign buttons for iMAP button function name …
bjoluc May 4, 2024
ec3e49f
Merge branch 'develop' into device-icon-v1m
bjoluc May 4, 2024
090410c
Remove a slipped through console.log()
bjoluc May 4, 2024
96fbe7b
Merge branch 'develop' into device-icon-v1m
bjoluc May 4, 2024
07f7b28
Merge branch 'develop' into device-icon-v1m
bjoluc May 4, 2024
2f353c6
Add extender numbers to port detection rules
bjoluc May 4, 2024
a136e3c
Merge branch 'develop' into device-icon-v1m
bjoluc May 4, 2024
a753029
Merge branch 'develop' into device-icon-v1m
bjoluc May 28, 2024
ce38a5c
Enable `displayColorMode` config option for V1-M script
bjoluc May 30, 2024
01e1ac0
Deduplicate strip effect parameter value creation
bjoluc May 31, 2024
34a4092
Merge branch 'develop' into device-icon-v1m
bjoluc Jun 2, 2024
ff46427
Merge branch 'develop' into device-icon-v1m
bjoluc Jun 8, 2024
754488d
Move all non-MCU button mappings to MIDI channel 2
bjoluc Jun 14, 2024
7fc3fc0
Merge branch 'develop' into device-icon-v1m
bjoluc Jun 14, 2024
25d1e04
Add Cue 1-4 encoder assignment
bjoluc Jun 14, 2024
41c2178
Merge branch 'develop' into device-icon-v1m
bjoluc Jun 17, 2024
c2a912b
Light up name/value button when Shift is held
bjoluc Jun 17, 2024
fddfe44
Hide fader parameter and value while shift is held
bjoluc Jun 17, 2024
fd84c80
Add insert slot control buttons
bjoluc Jul 27, 2024
99c81b3
Reset non-MCU MIDI buttons on (de)activation
bjoluc Oct 3, 2024
414fe28
Switch to DAW-level iMAP preset
bjoluc Jan 4, 2025
636a0de
Remove redundant cue pages from sends encoder assignment
bjoluc Jan 4, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
230 changes: 230 additions & 0 deletions assets/mcu-midiremote.v1m-daw

Large diffs are not rendered by default.

20 changes: 20 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ The following devices are explicitly supported:
- iCON:
- Platform M+ / X+ <sup>\*</sup>
- QCon Pro G2 / QCon EX G2
- V1-M / V1-X <sup>\*</sup>
- Mackie Control Universal (Pro) / XT (Pro)
- SSL UF1 <sup>\*</sup>

Expand Down Expand Up @@ -209,6 +210,25 @@ Current limitations of the MIDI Remote API:

</details>

<details>
<summary>iCON V1-M / V1-X</summary>

The iCON V1-M has a touch screen button matrix with customizable button labels (via the iMAP software).
The mappings of the MIDI Remote Script are available as an iMAP DAW mapping that you can [download](assets/mcu-midiremote.v1m-daw) and load into iMAP (right-click > "Load DAW mapping") so the button layout on the V1-M matches the layout of the MIDI Remote control surface.
When you customize your mappings in the Cubase MIDI Remote Mapping Assistant, you can use the iMAP software to update the labels on the V1-M.
The default mapping assigns each button of the first three function layers (blue, green, yellow) to a corresponding virtual button on the MIDI Remote control surface.
Presuming the provided iMAP DAW mapping has been loaded, the following aspects of the V1-M script differ from the mapping described above:

- All buttons are labelled according to their actual functions (even if these functions differ from the default MCU functions).
- The first (blue) function layer exposes three buttons that are not available in Cubase's default MCU mapping: Edit Instrument, Reset Meters, and Click.
- There is no additional touchscreen button for controlling the value under the mouse cursor because the controller can already do this via the Focus button top-right of the jog wheel.
- The secondary scribble strips show track names and peak meter levels. While a track's fader is touched, its scribble strip switches to the fader's current parameter name and parameter value instead, unless the Shift button is held.
- All encoder assign buttons are located on the second (green) function layer and there are more encoder assign buttons than traditional MCU devices have: The encoder assignments from the table in the previous section have mostly been split across individual buttons to make them easier to access. The only encoder assignments which you can page through by pressing the assign button multiple times are EQ, Sends, and Focused Insert.

Lastly, thanks to iCON for supporting the development of this script variant!

</details>

<details>
<summary>SSL UF1</summary>

Expand Down
5 changes: 2 additions & 3 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,7 @@ var CONFIGURATION = {
*
* * `"encoders"` to make scribble strip displays pick up colors from encoders, i.e., each
* display uses the track color of the channel its encoder value belongs to. When an encoder is
* unassigned, the scribble strip below it falls back to the corresponding mixer channel's
* color.
* unassigned, its scribble strip falls back to the corresponding mixer channel's color.
*
* * `"channels"` to makes scribble strips ignore encoder colors and always use their channels'
* track colors instead. When a channel is unassigned but its encoder is assigned, the display
Expand All @@ -107,7 +106,7 @@ var CONFIGURATION = {
* always be white unless a display's channel and encoder is unassigned, in which case the
* display will revert to black.
*
* @devices X-Touch, X-Touch One
* @devices X-Touch, X-Touch One, V1-M
*/
displayColorMode: "encoders",

Expand Down
9 changes: 7 additions & 2 deletions src/decorators/MidiOutputPort.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,13 @@ class MidiOutputDecorator {
]);
};

sendNoteOn = (context: MR_ActiveDevice, pitch: number, velocity: number | boolean) => {
this.port.sendMidi(context, [0x90, pitch, +Boolean(velocity) * 0x7f]);
sendNoteOn = (
context: MR_ActiveDevice,
pitch: number,
velocity: number | boolean,
channelNumber = 0,
) => {
this.port.sendMidi(context, [0x90 + channelNumber, pitch, +Boolean(velocity) * 0x7f]);
};
}

Expand Down
38 changes: 28 additions & 10 deletions src/decorators/surface-elements/LedButton.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ class LedButtonDecorator {
private ledValue = new ContextVariable(0);

private ports?: MidiPortPair;
private channelNumber?: number;
private note?: number;

constructor(
Expand All @@ -33,30 +34,40 @@ class LedButtonDecorator {

onSurfaceValueChange = new CallbackCollection(this.button.mSurfaceValue, "mOnProcessValueChange");

sendNoteOn = (context: MR_ActiveDevice, velocity: number | boolean) => {
if (
this.ports &&
typeof this.channelNumber !== "undefined" &&
typeof this.note !== "undefined"
) {
this.ports.output.sendNoteOn(context, this.note, velocity, this.channelNumber);
}
};

setLedValue = (context: MR_ActiveDevice, value: number) => {
this.ledValue.set(context, value);
if (this.ports && typeof this.note !== "undefined") {
this.ports.output.sendNoteOn(context, this.note, value);
}
this.sendNoteOn(context, value);
};

bindToNote = (ports: MidiPortPair, note: number) => {
bindToNote = (ports: MidiPortPair, note: number, channelNumber = 0) => {
this.ports = ports;
this.channelNumber = channelNumber;
this.note = note;

this.button.mSurfaceValue.mMidiBinding.setInputPort(ports.input).bindToNote(0, note);
this.button.mSurfaceValue.mMidiBinding
.setInputPort(ports.input)
.bindToNote(channelNumber, note);
this.onSurfaceValueChange.addCallback((context, newValue) => {
ports.output.sendNoteOn(context, note, newValue || this.ledValue.get(context));
this.sendNoteOn(context, newValue || this.ledValue.get(context));
});

// Binding the button's mSurfaceValue to a host function may alter it to not change when the
// button is pressed. Hence, `shadowValue` is used to make the button light up while it's
// pressed.
this.shadowValue.mMidiBinding.setInputPort(ports.input).bindToNote(0, note);
this.shadowValue.mMidiBinding.setInputPort(ports.input).bindToNote(channelNumber, note);
this.shadowValue.mOnProcessValueChange = (context, newValue) => {
ports.output.sendNoteOn(
this.sendNoteOn(
context,
note,
newValue ||
this.button.mSurfaceValue.getProcessValue(context) ||
this.ledValue.get(context),
Expand All @@ -67,11 +78,18 @@ class LedButtonDecorator {
// Turn the button's LED off when it becomes unassigned
this.button.mSurfaceValue.mOnTitleChange = (context, title) => {
if (title === "") {
ports.output.sendNoteOn(context, note, 0);
this.sendNoteOn(context, 0);
}
};
}
};

/**
* Returns whether `bindToNote()` has already been called on this button.
*/
isBoundToNote = () => {
return Boolean(this.ports);
};
}

/**
Expand Down
20 changes: 15 additions & 5 deletions src/decorators/surface-elements/TouchSensitiveFader.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,22 @@
import { MidiPortPair } from "/midi/MidiPortPair";
import { GlobalState } from "/state";
import { ContextVariable } from "/util";
import { CallbackCollection, ContextVariable } from "/util";

class TouchSensitiveMotorFaderDecorator {
// Workaround because `filterByValue` in the encoder bindings hides zero values from
// `mOnProcessValueChange`
private mTouchedShadowValue = this.surface.makeCustomValueVariable("faderTouchedShadow");

public onTouchedValueChangeCallbacks = new CallbackCollection(
this.mTouchedShadowValue,
"mOnProcessValueChange",
);

public onTitleChangeCallbacks = new CallbackCollection(
this.fader.mSurfaceValue,
"mOnTitleChange",
);

constructor(
private surface: MR_DeviceSurface,
private fader: MR_Fader,
Expand All @@ -33,11 +43,11 @@ class TouchSensitiveMotorFaderDecorator {
ports.output.sendMidi(context, [0xe0 + channelIndex, value & 0x7f, value >> 7]);
};

this.mTouchedShadowValue.mOnProcessValueChange = (context, isFaderTouched) => {
this.onTouchedValueChangeCallbacks.addCallback((context, isFaderTouched) => {
if (!isFaderTouched) {
sendValue(context, surfaceValue.getProcessValue(context));
}
};
});

areMotorsActive.addOnChangeCallback((context, areMotorsActive) => {
if (areMotorsActive) {
Expand Down Expand Up @@ -67,14 +77,14 @@ class TouchSensitiveMotorFaderDecorator {
surfaceValue.mOnProcessValueChange = onSurfaceValueChange;

// Send fader down when unassigned
surfaceValue.mOnTitleChange = (context, _title1, title2) => {
this.onTitleChangeCallbacks.addCallback((context, _title1, title2) => {
if (title2 === "") {
surfaceValue.setProcessValue(context, 0);
// `mOnProcessValueChange` isn't run on `setProcessValue()` when the fader is not assigned
// to a mixer channel, so we manually trigger the update:
onSurfaceValueChange(context, 0);
}
};
});
};
}

Expand Down
3 changes: 2 additions & 1 deletion src/device-configs/behringer_x-touch-one.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,11 @@ import { LedButton } from "/decorators/surface-elements/LedButton";
import { LedPushEncoder } from "/decorators/surface-elements/LedPushEncoder";
import { TouchSensitiveMotorFader } from "/decorators/surface-elements/TouchSensitiveFader";
import * as encoderPageConfigs from "/mapping/encoders/page-configs";
import { BehringerColorManager } from "/midi/managers/colors/BehringerColorManager";
import { createElements } from "/util";

export const deviceConfig: DeviceConfig = {
channelColorSupport: "behringer",
colorManager: BehringerColorManager,
hasIndividualScribbleStrips: true,
shallMouseValueModeMapAllEncoders: true,
detectionUnits: [
Expand Down
3 changes: 2 additions & 1 deletion src/device-configs/behringer_xtouch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { Lamp } from "/decorators/surface-elements/Lamp";
import { LedButton } from "/decorators/surface-elements/LedButton";
import { LedPushEncoder } from "/decorators/surface-elements/LedPushEncoder";
import { TouchSensitiveMotorFader } from "/decorators/surface-elements/TouchSensitiveFader";
import { BehringerColorManager } from "/midi/managers/colors/BehringerColorManager";
import { createElements } from "/util";

const channelWidth = 5;
Expand Down Expand Up @@ -78,7 +79,7 @@ const extenderPortPairConfigurator = (
};

export const deviceConfig: DeviceConfig = {
channelColorSupport: "behringer",
colorManager: BehringerColorManager,
hasIndividualScribbleStrips: true,
detectionUnits: [
{
Expand Down
Loading
Loading