Cubase Remote API insertEffectViewer not in sync

Hi, I’m trying to control insert effect bypass via the remote API.

Here’s part of the script in question, based on the RealWorldDevice example:

function bindChannelBankItem(index) {
        var channelBankItem = hostMixerBankZone.makeMixerBankChannel()
        var inserts1 = channelBankItem.mInsertAndStripEffects.makeInsertEffectViewer('plugin 1')
        var inserts2 = channelBankItem.mInsertAndStripEffects.makeInsertEffectViewer('2')
        
        inserts1.mOnChangePluginIdentity = function(dev, map, arg2, arg3, arg4, arg5) {
            console.log(['plugin 1 change', arg2, arg3, arg4, arg5].join(" "))
        }

        inserts2.mOnChangePluginIdentity = function(dev, map, arg2, arg3, arg4, arg5) {
            console.log(['plugin 2 change', arg2, arg3, arg4, arg5].join(" "))
        }

        var insert1 = surfaceElements.faderStrips[index].insert1.mSurfaceValue
        var insert2 = surfaceElements.faderStrips[index].insert2.mSurfaceValue
        var insert2next = surfaceElements.faderStrips[index].insert2next // this is a custom variable: surface.makeCustomValueVariable('faderstrip_insert2_next')
        var insert2reset = surfaceElements.faderStrips[index].insert2reset // this is a custom variable: surface.makeCustomValueVariable('faderstrip_insert2_reset')

        page.makeValueBinding(insert1, inserts1.mBypass)
        page.makeValueBinding(insert2, inserts2.mBypass)
        page.makeActionBinding(insert2reset, inserts2.mAction.mReset)
        page.makeActionBinding(insert2next, inserts2.mAction.mNext)
        channelBankItem.mOnTitleChange = function(dev, map, name) {
            console.log("change channel to " + name)
            // reset and next to get second insert
            insert2reset.setProcessValue(dev, 1)
            insert2next.setProcessValue(dev, 1)
        }
    }

However when a track is selected it’s not in sync with the selected channel until a plugin window is opened. Without opening/closing a plugin window the InsertEffectViewer always points to the previously selected channel, not the current one.

Hi,

This one doesn’t make sense unfortunately. For each track we can have one instance of the insertEffectViewer. I do understand that you want to work with slots 1 and 2 but this is not how it works.
Moving from an insert slot to the next (or previous) requires

insertsViewer.mAction.mNext //or mPrev

Now,

This doesn’t necessarily mean that we have a track index changed. It can happen whenever we change its name, shifting mixer bank and so on.

If you’re interested in the selected channel only, which makes sense, you don’t need the binding per track. You can bind just for the selected track:

var mixerChannel=page.mHostAccess.mTrackSelection.mMixerChannel
var insertsViewer=mixerChannel.mInsertAndStripEffects.makeInsertEffectViewer("insertsViewer")
    
insertsViewer.followPluginWindowInFocus()

insertsViewer.mOnTitleChange=(function(activeDevice,activeMapping,arg2){
   //arg2 holds the index of the slot
}

 insertsViewer.mOnChangePluginIdentity=function(activeDevice,activeMapping,pluginName,pluginVendor,pluginVersion,vstType){
  //you can see in the args what this event returns      
}

Hi @m.c ,

Would you be so kind to have a look on this, please?

Hi Martin, just did :slight_smile:

1 Like

Hi,

Oh, my browser refresh was probably slow. :smiley: Thank you!

1 Like

Hi @m.c,

Thanks for the fast reply.
Indeed, the InsertEffectViewer works as expected for the selected track:

function makePageSelectedTrack() {
    var page = makePageWithDefaults('Selected Track')

    var selectedTrackChannel = page.mHostAccess.mTrackSelection.mMixerChannel
    selectedTrackChannel.mValue.mVolume

    for(var idx = 0; idx < surfaceElements.knobStrips.length; ++idx) {
        page.makeValueBinding (surfaceElements.knobStrips[idx].knob.mSurfaceValue, selectedTrackChannel.mQuickControls.getByIndex(idx))
    }

    var inserts1 = selectedTrackChannel.mInsertAndStripEffects.makeInsertEffectViewer('plugin 1')
    var inserts2 = selectedTrackChannel.mInsertAndStripEffects.makeInsertEffectViewer('2')
    
    inserts1.mOnChangePluginIdentity = function(dev, map, arg2, arg3, arg4, arg5) {
        console.log(['plugin 1 change', arg2, arg3, arg4, arg5].join(" "))
    }

    inserts2.mOnChangePluginIdentity = function(dev, map, arg2, arg3, arg4, arg5) {
        console.log(['plugin 2 change', arg2, arg3, arg4, arg5].join(" "))
    }

    var insert1 = surfaceElements.faderStrips[0].insert1.mSurfaceValue
    var insert2 = surfaceElements.faderStrips[0].insert2.mSurfaceValue
    var insert2next = surfaceElements.faderStrips[0].insert2next
    var insert2reset = surfaceElements.faderStrips[0].insert2reset
    var insert3 = surfaceElements.faderStrips[0].insert3.mSurfaceValue
    var insert4 = surfaceElements.faderStrips[0].insert4.mSurfaceValue

    page.makeValueBinding(insert1, inserts1.mBypass)
    page.makeValueBinding(insert2, inserts2.mBypass)
    page.makeActionBinding(insert2reset, inserts2.mAction.mReset)
    page.makeActionBinding(insert2next, inserts2.mAction.mNext)
    
    selectedTrackChannel.mOnTitleChange = function(dev, map, name) {
        console.log("change channel to " + name)
        insert2reset.setProcessValue(dev, 1)
        insert2next.setProcessValue(dev, 1)
    }
        
    return page
}

The logic with two InsertEffectViewers for insert slot 1 and insert 2 slot might not be the intended use but it works :slight_smile:
Is there a better way to react on the selected channel changed?

Regarding the initial issue this still stands and the selected track has different behaviour on the InsertEffectViewer than on a channelBankItem:
On the selected channel the plugin window doesn’t have to be opened for the InsertEffectViewer to have the correct assignment, in channelBankItem it does.

Let me clarify the use-case:
I want the NI Machine Jam to work as a mixer with the buttons above the faders to activate/bypass an effect for that channel.

Yes, I figured this one out :slight_smile:
I’m afraid that my solution will be a little disappointing.
There’s no way to do what we want in a simple one step, cause as you’ve already noticed there’s not something like getSlotByIndex() which would actually be a good feature request.

So, we have to complicate things, and I never liked this, but here’s how it goes:
When the button we want to bypass a slot is pressed, we begin a process of browsing through the inserts slots.
The good thing is that every time the slot is changed, the mOnTitleChange event is triggered, and we get the new slot index.
Now, we really have to loop, until the current slot index equals the one we want to bypass.
At the same time, we have to keep track of what the byPass value is, because when the slot is found we want to toggle this.
Unfortunately, from my testing, the mOnTitleChange and mOnProcessValueChange do not always get raised in this exact order. However, most of the time, I see it does so. Just keep this in mind.

I’ve prepared a snippet which I wouldn’t want to use in my own scripts, I would just try to use whatever the API gives me without trouble. For example, I always have buttons to move to previous/next insert slot, and then other buttons for bypass and edit. I do understand though that when dealing with many slots, this can be a bit tedious for the user.
Anyway, here’s the snippet, note that I have some logs to better get the flow:

var midiremote_api=require('midiremote_api_v1')

var deviceDriver = midiremote_api.makeDeviceDriver('Test Devices', 'A test Device', 'Someone')

var midiInput = deviceDriver.mPorts.makeMidiInput()
var midiOutput = deviceDriver.mPorts.makeMidiOutput()

deviceDriver.makeDetectionUnit().detectPortPair(midiInput, midiOutput)
    .expectInputNameContains('a port')
    .expectOutputNameContains('an output port')

var delaySteps=1 //It's crucial to give some time for the events to be raised

var selectedAction=0 //0=bypass, 1=edit(show)

var surface=deviceDriver.mSurface
var ourButtons=[]
for(var i=0;i<8;i++){
    var aButton=surface.makeButton(i,0,1,1)
    aButton.mSurfaceValue.mMidiBinding
        .setInputPort(midiInput)
        .bindToControlChange(0,0x47+i)

    ourButtons.push(aButton)

}

deviceDriver.mOnActivate=function(activeDevice){

    activeDevice.setState("slotCounter","0")
    activeDevice.setState("slotSearching","0")
    activeDevice.setState("searchIndex","0")//indexes start from 1 in the insertsViewer impl

}

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

var insertsViewer=aPage.mHostAccess.mTrackSelection.mMixerChannel.mInsertAndStripEffects.makeInsertEffectViewer("insertsViewer")

insertsViewer.followPluginWindowInFocus()

var customResetSlotsVar=surface.makeCustomValueVariable("customResetSlotsVar")
var customNextSlotVar=surface.makeCustomValueVariable("customNextSlotVar")

var customActionVar=surface.makeCustomValueVariable("customActionVar")

var customShortDelayVar=surface.makeCustomValueVariable("customShortDelayVar")

aPage.makeActionBinding(customResetSlotsVar,insertsViewer.mAction.mReset)

aPage.makeActionBinding(customNextSlotVar,insertsViewer.mAction.mNext)

if(selectedAction==0){

    aPage.makeValueBinding(customActionVar,insertsViewer.mBypass)

} else {

    aPage.makeValueBinding(customActionVar,insertsViewer.mEdit)

}


var dummySlots=[]
for(var j=0;j<8;j++){
    
    //just the first 8 slots
    dummySlots.push(aPage.mCustom.makeHostValueVariable("dummySlot"+j))

    aPage.makeValueBinding(ourButtons[j].mSurfaceValue,dummySlots[j]).mOnValueChange=function(activeDevice,activeMapping,value){

        if(value==1){

            console.log("Initializing the byPass procedure")
            activeDevice.setState("slotSearching","1")
            activeDevice.setState("slotCounter",this.j.toString())
            activeDevice.setState("searchIndex",(this.j+1).toString())
            customResetSlotsVar.setProcessValue(activeDevice,1)

        }
        
    }.bind({j})

}

function moveToNextSlot(activeDevice){


    var slotSearching=activeDevice.getState("slotSearching")
    var currentSlotCount=parseInt(activeDevice.getState("slotCounter"))
    
    if (slotSearching=="0"){

        return 

    }

    console.log("moveToNext called")
    
    currentSlotCount--

    if(currentSlotCount>-1){

        console.log("moving to next slot")
        activeDevice.setState("slotCounter",currentSlotCount.toString())
        customNextSlotVar.setProcessValue(activeDevice,currentSlotCount+1)
    
    } else {

        //search finished
        activeDevice.setState("slotSearching","0")
        console.log("search ended without success")
    
    }

}

customShortDelayVar.mOnProcessValueChange=function(activeDevice,value,diff){

    if(value>0){

        value--
        customShortDelayVar.setProcessValue(activeDevice,value)
    
    } else {

        moveToNextSlot(activeDevice)
    
    }

}

insertsViewer.mOnTitleChange=function(activeDevice,activeMapping,slotIndex){
    
    var searchIndex=activeDevice.getState("searchIndex")
    console.log("mOnTitle slot index="+slotIndex+" search index="+searchIndex)
    activeDevice.setState("slotIndex",slotIndex)
    
    var slotSearching=activeDevice.getState("slotSearching")
    
    if(slotSearching=="1"){

        
        if(searchIndex!=slotIndex){
            
            customShortDelayVar.setProcessValue(activeDevice,delaySteps)

        } else {

            activeDevice.setState("slotSearching","2")
            
        }
    
    }

}

if(selectedAction==0){

    insertsViewer.mBypass.mOnProcessValueChange=function(activeDevice,activeMapping,value){
        
        updateSelectedValue(activeDevice,value)
                
    }

} else {

    insertsViewer.mEdit.mOnProcessValueChange=function(activeDevice,activeMapping,value){
        
        updateSelectedValue(activeDevice,value)
                
    }

}

function updateSelectedValue(activeDevice,value){

    console.log("value set to "+value)
    var slotIndex=activeDevice.getState("slotIndex")
    activeDevice.setState("value"+slotIndex,value.toString())
        
    if(activeDevice.getState("slotSearching")=="2"){
            
        activeDevice.setState("slotSearching","0")
        console.log("searched with success")
        var newValue=1-value 
        console.log("setting NEW bypass to "+newValue)
        customActionVar.setProcessValue(activeDevice,newValue)
        
    }
        
}

I really don’t find this consistent enough, but maybe it can be worked further.