Opening/Closing the insert slots of Control Room's main channel using MIDI Remote

Here’s a tested snippet for opening/closing the inserts as written in the title, using scripting and the MIDI Remote, for CB 13.0.51 and CB 14. I’ve used CCs 20-27 (channel 0) for toggling Inserts 1-8, but one can always change these CCs obviously.

// Opening/Closing insert slots of Control Room's Main Channel

var midiremote_api = require('midiremote_api_v1')

var deviceDriver = midiremote_api.makeDeviceDriver("Test","Control Room Edit Inserts","m.c")

var midiInput = deviceDriver.mPorts.makeMidiInput("anInput")
var midiOutput = deviceDriver.mPorts.makeMidiOutput("anOutput")

var detectionUnit=deviceDriver.makeDetectionUnit()

detectionUnit.detectPortPair(midiInput, midiOutput)
    .expectInputNameEquals("an input")
    .expectOutputNameEquals("an output")

var surface=deviceDriver.mSurface
var mapping=deviceDriver.mMapping

var controlRoomInsertsEditButtons=[]

for(var i=0;i<8;i++){

    var button=surface.makeButton(i,0,1,1)
    button.mSurfaceValue.mMidiBinding
        .setInputPort(midiInput)
        .bindToControlChange(0,20+i)
    
    controlRoomInsertsEditButtons.push(button)

}

var page=mapping.makePage("page")

var controlRoomMainChannel=page.mHostAccess.mControlRoom.mMainChannel 
var daControlRoomMainChannel=page.mHostAccess.makeDirectAccess(controlRoomMainChannel)

for(var i=0;i<8;i++){

    var customHostValueControlRoomInsertEdit=page.mCustom.makeHostValueVariable("customHostValueControlRoomInsertEdit"+i)

    page.makeValueBinding(controlRoomInsertsEditButtons[i].mSurfaceValue,customHostValueControlRoomInsertEdit).mOnValueChange=function(activeDevice,activeMapping,value,diff){

        if(value==1){
            
            var baseID=daControlRoomMainChannel.getBaseObjectID(activeMapping)

            var insertsIndex=1
            
            var insertsObject=daControlRoomMainChannel.getChildObjectID(activeMapping,baseID,insertsIndex)
            
            var insertSlotObject=daControlRoomMainChannel.getChildObjectID(activeMapping,insertsObject,this.i)
            
            var editTag=4101
            
            var prevEditValue=daControlRoomMainChannel.getParameterProcessValue(activeMapping,insertSlotObject,editTag)
            var newEditValue=1-prevEditValue
            
            daControlRoomMainChannel.setParameterProcessValue(activeMapping,insertSlotObject,editTag,newEditValue)
 
        }
    
    }.bind({i})

}
4 Likes

I am trying to use this code to open/close the insert slots of the selected track in Cubase, but I can’t make it work.

I can press the 8 buttons from my hardware, but they don’t open / close the plugin windows.

Could you please help me? What should I change in this code?

var SelectedTrackInsertsEditButtons=[]
for(var i=0;i<8;i++){
    var button=deviceDriver.mSurface.makeButton(i,0,1,1)
    button.mSurfaceValue.mMidiBinding
        .setInputPort(midiInput)
        .bindToControlChange(8,1+i) //channel 9
    SelectedTrackInsertsEditButtons.push(button)
}
var mySelectedChannel=page.mHostAccess.mTrackSelection.mMixerChannel
var theSelectedChannel=page.mHostAccess.makeDirectAccess(mySelectedChannel)
for(var i=0;i<8;i++){
    var customHostValueSelectedTrackInsertEdit=page.mCustom.makeHostValueVariable("customHostValueSelectedTrackInsertEdit"+i)
    page.makeValueBinding(SelectedTrackInsertsEditButtons[i].mSurfaceValue,customHostValueSelectedTrackInsertEdit).mOnValueChange=function(activeDevice,activeMapping,value,diff){
        if(value==1){
            var baseID=theSelectedChannel.getBaseObjectID(activeMapping)
            var insertsIndex=1
            var insertsObject=theSelectedChannel.getChildObjectID(activeMapping,baseID,insertsIndex)
            var insertSlotObject=theSelectedChannel.getChildObjectID(activeMapping,insertsObject,this.i)
            var editTag=4101
            var prevEditValue=theSelectedChannel.getParameterProcessValue(activeMapping,insertSlotObject,editTag)
            var newEditValue=1-prevEditValue
            theSelectedChannel.setParameterProcessValue(activeMapping,insertSlotObject,editTag,newEditValue)
        }
    }.bind({i})
}

Correct. This is because the object for the control room inserts is just different in its structure.
Let’s not get into the details, here’s a snippet that will work:

// Opening/Closing insert slots of Selected Track

var midiremote_api = require('midiremote_api_v1')

var deviceDriver = midiremote_api.makeDeviceDriver("Test","Edit Inserts","m.c")

var midiInput = deviceDriver.mPorts.makeMidiInput("anInput")
var midiOutput = deviceDriver.mPorts.makeMidiOutput("anOutput")

var detectionUnit=deviceDriver.makeDetectionUnit()

detectionUnit.detectPortPair(midiInput, midiOutput)
    .expectInputNameEquals("an input")
    .expectOutputNameEquals("an output")

var surface=deviceDriver.mSurface
var mapping=deviceDriver.mMapping
var page=mapping.makePage("page")

var SelectedTrackInsertsEditButtons=[]
for(var i=0;i<16;i++){
    var button=deviceDriver.mSurface.makeButton(i,0,1,1)
    button.mSurfaceValue.mMidiBinding
        .setInputPort(midiInput)
        .bindToControlChange(8,1+i) //channel 9
    SelectedTrackInsertsEditButtons.push(button)
}
var mySelectedChannelInsertsViewer=page.mHostAccess.mTrackSelection.mMixerChannel.mInsertAndStripEffects 
var daMySelectedChannelInsertsViewer=page.mHostAccess.makeDirectAccess(mySelectedChannelInsertsViewer)

for(var i=0;i<16;i++){
    var customHostValueSelectedTrackInsertEdit=page.mCustom.makeHostValueVariable("customHostValueSelectedTrackInsertEdit"+i)
    page.makeValueBinding(SelectedTrackInsertsEditButtons[i].mSurfaceValue,customHostValueSelectedTrackInsertEdit).mOnValueChange=function(activeDevice,activeMapping,value,diff){
        if(value==1){
            var baseID=daMySelectedChannelInsertsViewer.getBaseObjectID(activeMapping)
            var insertsID=daMySelectedChannelInsertsViewer.getChildObjectID(activeMapping,baseID,3)
            var insertSlotObject=daMySelectedChannelInsertsViewer.getChildObjectID(activeMapping,insertsID,this.i)
            var editTag=4101
            var prevEditValue=daMySelectedChannelInsertsViewer.getParameterProcessValue(activeMapping,insertSlotObject,editTag)
            var newEditValue=1-prevEditValue
            daMySelectedChannelInsertsViewer.setParameterProcessValue(activeMapping,insertSlotObject,editTag,newEditValue)
        }
    }.bind({i})
}

However, and this is an interesting note, I’ve used this approach for the Control Room Inserts, ONLY because they’re not directly exposed by the MR API.
In the case of the inserts of the selected track, they do get exposed by slot, from CB13.0.51 and above.
So here’s a snippet without the use of the “exotic” direct access method:

// Opening/Closing insert slots of Selected track

var midiremote_api = require('midiremote_api_v1')

var deviceDriver = midiremote_api.makeDeviceDriver("Test","Edit Inserts v2","m.c")

var midiInput = deviceDriver.mPorts.makeMidiInput("anInput")
var midiOutput = deviceDriver.mPorts.makeMidiOutput("anOutput")

var detectionUnit=deviceDriver.makeDetectionUnit()

detectionUnit.detectPortPair(midiInput, midiOutput)
    .expectInputNameEquals("an input")
    .expectOutputNameEquals("an output")

var surface=deviceDriver.mSurface
var mapping=deviceDriver.mMapping
var page=mapping.makePage("page")

var SelectedTrackInsertsEditButtons=[]
for(var i=0;i<16;i++){
    var button=deviceDriver.mSurface.makeButton(i,0,1,1)
    button.mSurfaceValue.mMidiBinding
        .setInputPort(midiInput)
        .bindToControlChange(8,1+i) //channel 9
    SelectedTrackInsertsEditButtons.push(button)
}


for(var i=0;i<16;i++){
    
    var insertSlot=page.mHostAccess.mTrackSelection.mMixerChannel.mInsertAndStripEffects.makeInsertEffectViewer("insertSlot"+i)
    
    insertSlot.accessSlotAtIndex(i)

    page.makeValueBinding(SelectedTrackInsertsEditButtons[i].mSurfaceValue,insertSlot.mEdit).setTypeToggle()
    
}

Both will work. I find the second one much clearer from a user perspective, so I think you should go with this one. As you can easily find out, you can perform even more actions this way, byPass, on, automation, without getting to know the proper tags used in the first snippet.

1 Like

Thanks a lot @m.c !! :grinning:

The second one works perfect !! :heart_eyes:

I have changed the last code line to jump mode, so now I can open the plugin window sending a cc value of 127 and close it sending a cc value of 0:

page.makeValueBinding(SelectedTrackInsertsEditButtons[i].mSurfaceValue,insertSlot.mEdit).setValueTakeOverModeJump()

This is perfect to me.

The only “problem” I am experiencing is that the buttons can be pressed even there is no insertion.

i.e. if the slot 1 of the selected track is empty, pressing the button of the slot 1 is shown as if the plugin of the slot 1 was opened.

Is it possible to “disable” the action of the button if the corresponding insert slot is empty?

No, as far as I know. See EDIT 2.
However, we can always light up a lamp, when we have a plugin in the slot:

// Opening/Closing insert slots of Selected track

var midiremote_api = require('midiremote_api_v1')

var deviceDriver = midiremote_api.makeDeviceDriver("Test","Edit Inserts v2","m.c")

var midiInput = deviceDriver.mPorts.makeMidiInput("anInput")
var midiOutput = deviceDriver.mPorts.makeMidiOutput("anOutput")

var detectionUnit=deviceDriver.makeDetectionUnit()

detectionUnit.detectPortPair(midiInput, midiOutput)
    .expectInputNameEquals("an input")
    .expectOutputNameEquals("an output")

var page=deviceDriver.mMapping.makePage("page")

var SelectedTrackInsertsEditButtons=[]
var pseudoButtons=[]

for(var i=0;i<16;i++){

    var button=deviceDriver.mSurface.makeButton(i,0,1,1)
    button.mSurfaceValue.mMidiBinding
        .setInputPort(midiInput)
        .bindToControlChange(8,1+i) //channel 9

    SelectedTrackInsertsEditButtons.push(button)
    
    var pseudoButton=deviceDriver.mSurface.makeLamp(i,1,1,0.5)
    
    pseudoButtons.push(pseudoButton)

}

var lstOfNonEmptySlotsBeforeActivation=[]

for(var i=0;i<16;i++){
    
    var insertSlot=page.mHostAccess.mTrackSelection.mMixerChannel.mInsertAndStripEffects.makeInsertEffectViewer("insertSlot"+i)
    
    insertSlot.accessSlotAtIndex(i)
    
    page.makeValueBinding(SelectedTrackInsertsEditButtons[i].mSurfaceValue,insertSlot.mEdit).setTypeToggle()
    
    insertSlot.mOnChangePluginIdentity=function(activeDevice,activeMapping,pluginName,pluginVendor,pluginVersion,formatVersion){
        
        if(pluginName==""){
 
            pseudoButtons[this.i].mSurfaceValue.setProcessValue(activeDevice,0)
            
        } else {
            
            if(pageActivated==false){
            
                lstOfNonEmptySlotsBeforeActivation.push(this.i)
            
            } else {
            
                pseudoButtons[this.i].mSurfaceValue.setProcessValue(activeDevice,1)
            
            }
        
        }
    
    }.bind({i})

}

var pageActivated=false 
page.mOnActivate=function(activeDevice,activeMapping){

    pageActivated=true
 
    //This is necessary since the mOnPluginChange will get triggered before the page activation at first start, but it will not alter the pseudoButton.mSurfaceValue
 
    if(lstOfNonEmptySlotsBeforeActivation.length>0){
 
        for(var i=0;i<lstOfNonEmptySlotsBeforeActivation.length;i++){
 
            pseudoButtons[lstOfNonEmptySlotsBeforeActivation[i]].mSurfaceValue.setProcessValue(activeDevice,1)
 
        }
 
        lstOfNonEmptySlotsBeforeActivation=[]
 
    }

}

EDIT:

We can even display the plugin Name per slot (and abandon the lamp idea if we want). This will make clear which slots contain what.

// Opening/Closing insert slots of Selected track

var midiremote_api = require('midiremote_api_v1')

var deviceDriver = midiremote_api.makeDeviceDriver("Test","Edit Inserts v2","m.c")

var midiInput = deviceDriver.mPorts.makeMidiInput("anInput")
var midiOutput = deviceDriver.mPorts.makeMidiOutput("anOutput")

var detectionUnit=deviceDriver.makeDetectionUnit()

detectionUnit.detectPortPair(midiInput, midiOutput)
    .expectInputNameEquals("an input")
    .expectOutputNameEquals("an output")

var page=deviceDriver.mMapping.makePage("page")

var SelectedTrackInsertsEditButtons=[]
var pseudoButtons=[]
var labels=[]

for(var i=0;i<16;i++){

    var button=deviceDriver.mSurface.makeButton(4*i,0,4,4)
    button.mSurfaceValue.mMidiBinding
        .setInputPort(midiInput)
        .bindToControlChange(8,1+i) //channel 9

    SelectedTrackInsertsEditButtons.push(button)
    
    var pseudoButton=deviceDriver.mSurface.makeLamp(4*i,4,4,2)
    
    pseudoButtons.push(pseudoButton)

    var label=deviceDriver.mSurface.makeLabelField(4*i,6,4,1)
    labels.push(label)

}

var lstOfNonEmptySlotsBeforeActivation=[]

for(var i=0;i<16;i++){
    
    var insertSlot=page.mHostAccess.mTrackSelection.mMixerChannel.mInsertAndStripEffects.makeInsertEffectViewer("insertSlot"+i)
    
    insertSlot.accessSlotAtIndex(i)
    
    page.makeValueBinding(SelectedTrackInsertsEditButtons[i].mSurfaceValue,insertSlot.mEdit).setTypeToggle()

    page.setLabelFieldHostObject(labels[i],insertSlot.mParameterBankZone)
    
    insertSlot.mOnChangePluginIdentity=function(activeDevice,activeMapping,pluginName,pluginVendor,pluginVersion,formatVersion){
        
        if(pluginName==""){
 
            pseudoButtons[this.i].mSurfaceValue.setProcessValue(activeDevice,0)
            
        } else {
            
            if(pageActivated==false){
            
                lstOfNonEmptySlotsBeforeActivation.push(this.i)
            
            } else {
            
                pseudoButtons[this.i].mSurfaceValue.setProcessValue(activeDevice,1)
            
            }
        
        }
    
    }.bind({i})

}

var pageActivated=false 
page.mOnActivate=function(activeDevice,activeMapping){

    pageActivated=true
 
    //This is necessary since the mOnPluginChange will get triggered before the page activation at first start, but it will not alter the pseudoButton.mSurfaceValue
 
    if(lstOfNonEmptySlotsBeforeActivation.length>0){
 
        for(var i=0;i<lstOfNonEmptySlotsBeforeActivation.length;i++){
 
            pseudoButtons[lstOfNonEmptySlotsBeforeActivation[i]].mSurfaceValue.setProcessValue(activeDevice,1)
 
        }
 
        lstOfNonEmptySlotsBeforeActivation=[]
 
    }

}

EDIT 2:
Instead of directly assigning the midi messages to our buttons, we can assign them to custom variables, use the trigger event of these vars, and alter the “real” buttons values, only when a plugin exists in a specific slot. This way we can have some type of “disable”.

// Opening/Closing insert slots of Selected track

var midiremote_api = require('midiremote_api_v1')

var deviceDriver = midiremote_api.makeDeviceDriver("Test","Edit Inserts v2","m.c")

var midiInput = deviceDriver.mPorts.makeMidiInput("anInput")
var midiOutput = deviceDriver.mPorts.makeMidiOutput("anOutput")

var detectionUnit=deviceDriver.makeDetectionUnit()

detectionUnit.detectPortPair(midiInput, midiOutput)
    .expectInputNameEquals("an input")
    .expectOutputNameEquals("an output")

var page=deviceDriver.mMapping.makePage("page")

var customVars=[]
var SelectedTrackInsertsEditButtons=[]
var pseudoButtons=[]
var labels=[]

for(var i=0;i<16;i++){

    var customVar=deviceDriver.mSurface.makeCustomValueVariable("customVar"+i)
    customVar.mMidiBinding
        .setInputPort(midiInput)
        .bindToControlChange(8,1+i)
    customVars.push(customVar)

    var button=deviceDriver.mSurface.makeButton(4*i,0,4,4)
    
    SelectedTrackInsertsEditButtons.push(button)
    
    var pseudoButton=deviceDriver.mSurface.makeLamp(4*i,4,4,2)
    
    pseudoButtons.push(pseudoButton)

    var label=deviceDriver.mSurface.makeLabelField(4*i,6,4,1)
    labels.push(label)

}

var lstOfNonEmptySlotsBeforeActivation=[]
var mapOfPluginNames={}

for(var i=0;i<16;i++){
    
    var insertSlot=page.mHostAccess.mTrackSelection.mMixerChannel.mInsertAndStripEffects.makeInsertEffectViewer("insertSlot"+i)
    
    insertSlot.accessSlotAtIndex(i)
    
    page.makeValueBinding(SelectedTrackInsertsEditButtons[i].mSurfaceValue,insertSlot.mEdit).setTypeToggle()

    page.setLabelFieldHostObject(labels[i],insertSlot.mParameterBankZone)

    customVars[i].mOnProcessValueChange=function(activeDevice,value,diff){

        var pluginName=mapOfPluginNames[this.i]
        if(pluginName!=""){

            SelectedTrackInsertsEditButtons[this.i].mSurfaceValue.setProcessValue(activeDevice,value)
        }
        
    }.bind({i})
    
    insertSlot.mOnChangePluginIdentity=function(activeDevice,activeMapping,pluginName,pluginVendor,pluginVersion,formatVersion){
        
        mapOfPluginNames[this.i]=pluginName

        if(pluginName==""){
 
            pseudoButtons[this.i].mSurfaceValue.setProcessValue(activeDevice,0)
            
        } else {
            
            if(pageActivated==false){
            
                lstOfNonEmptySlotsBeforeActivation.push(this.i)
            
            } else {
            
                pseudoButtons[this.i].mSurfaceValue.setProcessValue(activeDevice,1)
            
            }
        
        }
    
    }.bind({i})

}

var pageActivated=false 
page.mOnActivate=function(activeDevice,activeMapping){

    pageActivated=true
 
    //This is necessary since the mOnPluginChange will get triggered before the page activation at first start, but it will not alter the pseudoButton.mSurfaceValue
 
    if(lstOfNonEmptySlotsBeforeActivation.length>0){
 
        for(var i=0;i<lstOfNonEmptySlotsBeforeActivation.length;i++){
 
            pseudoButtons[lstOfNonEmptySlotsBeforeActivation[i]].mSurfaceValue.setProcessValue(activeDevice,1)
 
        }
 
        lstOfNonEmptySlotsBeforeActivation=[]
 
    }

}
2 Likes

Hi @m.c

Thanks for your help. The last code (edit 2) works perfectly.

I have configured the buttons like this:

var customVars=[]
var SelectedTrackInsertsEditButtons=[]
var labels=[]
for(var i=0;i<6;i++){
    var customVar=deviceDriver.mSurface.makeCustomValueVariable("customVar"+i)
    customVar.mMidiBinding
        .setInputPort(midiInput)
        .setOutputPort(midiOutput)
        .bindToControlChange(0,1+i) //channel 1 cc1 to cc6
    customVars.push(customVar)
    var button=deviceDriver.mSurface.makeButton(9*i,2,9,2)
    SelectedTrackInsertsEditButtons.push(button)
    var label=deviceDriver.mSurface.makeLabelField(9*i,4,9,1)
    labels.push(label)
}

I have added the midi output because I want to send a midi message (cc 1-6 through ch 1) every time a button changes from enabled to disabled and viceversa.

This works as expected if I press the buttons in my hardware device, but if I open a plugin with the mouse, this cc is not sent, even the button of the midi remote is shown as pressed.

Any idea why this is happening and how can I fix it?

Of course. When you open the plugin using your mouse, the button attached to the opening IS receiving this info, BUT the custom vars we have setup will not, since they’re not bound to the opening, they’re just handling the buttons.
Right below the var button=deviceDriver.mSurface.makeButton(9*i,2,9,2) line, you can place:

button.mSurfaceValue.mOnProcessValueChange=function(activeDevice,value,diff){
   midiOutput.sendMidi(activeDevice,[0xB0,1+this.i,127*value])
}.bind({i})

The mOnProcessValueChange event of the button, will get triggered whenever the Opening of the plugIn gets activated/deactivated, either by our controller or by inside Cubase (mouse, qwerty). And there we’re sending the midi CC of the connected custom Var, and the value of opening (0 or 127).

THank you for posting this.
As a sideremark: I hope from deep within my heart, that the majority of the cubase users and enthusiastic Remote-Developers using the API see clearly, how ridiculously complex the scripting language is. The choice for java-script is simply anachronistic, it is just the false layer of abstraction. It followed the old golden rule “To those who own a hammer, the whole world looks like a nail”…

Hi, I don’t. It’s one of the easiest APIs I’ve ever had to work with. BUT I’m not a “simple” user. So, here are my thoughts on this:

The MIDI Remote project as we all know, has two options:

  • The surface creator/mapping assistant.
  • The js side with the API.

The mapping assistant is designed to help users of Cubendo with no scripting languages knowledge at all, to create fast, MIDI Remote surfaces with most of the frequent used options directly available.

The js side is ideal for manufacturers who want to provide their user base with more advanced remote surfaces, for example by adding feedback to leds/displays. A very recent example is Nektar’s CS12.

Now there are some exceptions to the above: I and a few other users here, went for this js approach. The majority of us are already experienced devs who just happen to love/use Cubendo. So we’re just act “completing” the MR available controllers’ list, and feeling happy to share our creations with other fellow users. These scripts are mostly for older devices that no one could expect that the manufacturer would provide. See for eg the script for the Presonus FP 2 by @CKB, the ones from @bjoluc, some of mine and others. This whole thing is OK and I think there has been very positive feedback by the community (even Steinberg in many cases), BUT this doesn’t mean that Steinberg upon designing this project, thought “yeah, let musicians learn some javascript”. Users/Musicians diving into the js world is - and so should be - the exception.

That being said, I currently work on an external app which tries to be an “extended” version of the mapping assistant supplied by Steinberg. It aims to bridge the two worlds, by providing the user the advantages of scripting without scripting. It’s nowhere near to be a simple project, needs some effort and most importantly me finding some time gaps between my main projects (nothing to do with the music industry). Once I get it done and I feel happy with its stability, I will for sure publish it in this forum. But then again, don’t expect a lot of users embracing it. Just some power users. At least this is what I think, I will be happy if proven wrong :slight_smile:

And a note concerning my OP. Threads that I open and include javascript, are not mainly targeting users. I share them just in case a vendor searches for an option in the MR that seemingly is not there, and requires some effort/time to find and include it. Having personally looked into some published “official” scripts, I can tell this is worth the effort.

4 Likes

Hi you, I hope my posting did not appear to be directed against you or what you are doing for all with the Scripts you create! :slight_smile: It was at any rate not meant this way.

After now almost 40 years in the Software-Industry - as a programmer, an architect, a responsible for software developement of most complex and critical systems - I still have to insist: The programming paradigm of java script is an anachronism (I am fully aware that this is wide spread, if not mainstream these days - we humans tend to ride horses beyond their natural life-expectation just because we have learned how to ride them). The API for the Remote might be a great API, but it shares its downsides with other apis in that Cruel OO-style of programming. We just got used to it).

I am not intending to bash Steinberg developers for that. It is more a critical position that has to be uttered against the mainstream of the industry. We are in an era where many programming languages and paradigms simply do not exploit the technically possible raising of the level of abstraction).

Again - thank you for what you are doing for the community!

1 Like

@m.c your work, enthusiasm, efforts, and scripts are very much appreciated. Great to see you drop something like this to keep the ideas coming for better workflows inspiration.

2 Likes

I thank you for the clarifications, and I do agree with most of them :slight_smile:

1 Like

Nice to see interest in this project :slight_smile:

Thanks @m.c for all your help!

The configuration now works perfectly!! :slightly_smiling_face:

1 Like