Command/Action bindings do not reset surface values on (sub)page activation

Using the MIDI Remote API in Cubase 13 and 12, when I bind a surface value to a host value on one page (Page 1) and to a command/action on another (Page 2, Page 3), like below:

const driver = require("midiremote_api_v1").makeDeviceDriver("Demo", "Demo", "bjoluc");

const button = driver.mSurface.makeButton(0, 0, 1, 1);

const page1 = driver.mMapping.makePage("Page 1");
page1.makeValueBinding(button.mSurfaceValue, page1.mHostAccess.mTrackSelection.mMixerChannel.mValue.mMute);

const page2 = driver.mMapping.makePage("Page 2");
page2.makeCommandBinding(button.mSurfaceValue, "Transport", "Return to Zero");

const page3 = driver.mMapping.makePage("Page 3");
page3.makeActionBinding(button.mSurfaceValue, page1.mAction.mActivate);

the surface variable is not reset to 0 when switching from Page 1 to Page 2/3.

Mute off Mute on
Page1 Capture4 Capture1
Page2 Capture5 Capture2
Page3 Capture6 Capture3

This is an issue when the surface variable controls external hardware, e.g. the light of a button. I would expect the bindings on pages 2 and 3 to set the surface value to 0 on page activation so button states on the active page never depend on button states on a previous page (the same issue also applies to sub pages).

I know I can reset surface values script-wise on page activation, but I’m working with a script where the mappings are intended to be overridden using the mapping assistant. The script cannot know whether a button is involved in a value binding or a command/action binding then, so cannot set surface values to 0 without risking to alter host values.

@Jochen_Trappe I’d be keen to know if the current behavior is intended or not :slightly_smiling_face:

From what I saw, in one of my scripts that I allow users to alter bindings using the assistant in “user” pages, when we map hostValues (NOT commands) the UI, the mOnProcess etc, update as expected.
The way I understand it, is that when we have command binding, there’s no change when we’re changing page, so nothing is triggered, thus the control remains as is.

Yes, I wouldn’t do this one too actually.

The good thing in my opinion is that if a user makes a command binding, and a led stays on it won’t harm a lot :slight_smile:

1 Like

What if you had your mSurfaceValue variables unique per page?
Perhaps something like:

const page1 = driver.mMapping.makePage("Page 1");
page1.makeValueBinding(button.mSurfaceValue[0], page1.mHostAccess.mTrackSelection.mMixerChannel.mValue.mMute);

const page2 = driver.mMapping.makePage("Page 2");
page2.makeCommandBinding(button.mSurfaceValue[1], "Transport", "Return to Zero");

I haven’t tried this to see if it would solve your issue, but in my scripts, I use multiple “surfaceValue” variables for various functions.
I’m also unsure if this approach would work with the Mapping Assistant, which I don’t use.


This is interesting. How do you create multiple surface values per control?


I apologize, I should’ve thought that through one more time before posting.
I don’t know if you can create multiple mSurfaceValue’s. It turns out I’m using “custom value variables” which unfortunately can not be mapped by the Mapping Assistant.

But perhaps (similar to my first though), you can create multiple “button” objects that all connects to the same MIDI CC and as a result, also gets their own mSurfaceValue property(?).

const button1 = driver.mSurface.makeButton(0, 0, 1, 1);
const button2 = driver.mSurface.makeButton(1, 0, 1, 1);

const page1 = driver.mMapping.makePage("Page 1");
page1.makeValueBinding(button1.mSurfaceValue, page1.mHostAccess.mTrackSelection.mMixerChannel.mValue.mMute);

const page2 = driver.mMapping.makePage("Page 2");
page2.makeCommandBinding(button2.mSurfaceValue, "Transport", "Return to Zero");

Oh alright,

Yes, it’s possible to connect multiple SurfaceElements to the same MIDI message. (being faders, buttons…) You can have them on different ControlLayers for better visibility in the mapping assistant. But be aware that all buttons would always be “active” in all pages even if they are not mapped. I would be worried about duplicating feedback messages (even though they should be the same).

Maybe I’m missing something, but I can’t see that happening if your feedback to your device is sent in the .mOnValueChange callback which is per mapping (and per page).

Yeah, this could work.
I tend to use the callback on the SurfaceValue instead of the ValueMapping so callbacks will be triggered when changing mapping (switching page or subpage for example).

I remember struggling a bit with how/when to update my feedback values. There was something funky with the .mOnValueChange callback firing when changing pages. Something like it only fires for a specific controller if its SurfaceValue (or whatever you choose to bind to a parameter) was previously changed. I might not describe that very well…
My workaround was to send all feedback values to my controller when the page.mOnActivate fires.

Which time of them? :joy:

Thanks for all your input! I have attempted several workarounds, but so far, all of them ran against a wall when being combined with mapping assistant overrides :face_with_raised_eyebrow:
Since proper button feedback is a requirement for the script I’m working on, I think I’ll revert to adding an extra, unmapped page with an ugly title like “Reset (do not use)” which I can programmatically switch to and from for resetting all buttons to 0 via setProcessValue(). Let’s see how that goes…

That did not go all too well :joy:

Ok, got it working and using it feels way less clunky than I expected (although it’s far from nice and only works with Cubase 13):

const buttonResetPage = driver.mMapping.makePage("Reset");
buttonResetPage.mOnActivate = function (context) { (button) {
    button.mSurfaceValue.setProcessValue(context, 0);
  state.areButtonsReset.set(context, true);
buttonResetPage.mOnIdle = function (context, mapping) {
}; (page, pageIndex) {
  page.mOnActivate = function (context, mapping) {
    if (state.currentPageId.get(context) !== pageIndex) {
      state.areButtonsReset.set(context, false);
      state.currentPageId.set(context, pageIndex);

  page.mOnIdle = function (context, mapping) {
    if (!state.areButtonsReset.get(context)) {

I keep track of the page names to ensure do things once.

page.mOnActivate = function (device, activeMap) {
        log('Page QickControls active')
        if (ActivePage.Name == 'QuickControl') {
// ...and so on
1 Like

Yes if you use the callback associated with the value binding,it only gets triggered when host value gets modified.

But if you use the callback associated with the surfaceValue, it then gets triggered when the associated binding changes. (Ie change of page or subpage, or selected channel, or bank).

I think that only works if you bind to mSuraceValue. I’m kinda forced to bind to custom value variables due to my particular rotary encoders.

The callbacks will be triggered, but effectively it would send the customValueVariable parameters.
In particular, mOnTitleChange and mOnDisplayValueChange would have empty strings as parameters. I found this very usefull in my case for clearing LCD’s.