Repeating Insert Effect Parameters when Value Binding

I’ve got a software controller that currently has 128 (16 x 8) Insert parameter knobs that map to the current insert in focus. I was hoping that when I bind the surface knob values to the various parameter values, MIDI remote would more or less follow the plug-in’s standard Remote Control Editor layout, and only populate the corresponding number of knobs.
For example, the stock StudioEQ plug-in has 4 pages of 8 knobs per page, so I’d expect to see just the first 4 rows of parameter knobs populated. Instead, MIDI Remote repeats the same 4 rows over and over:

It’s also been noted that the first row/page of parameters contains the default quick control parameters, which are then repeated in rows/pages 2, 3 & 4.

Is there a way in which I can map and value bind each plug-in parameter just once? If there isn’t, can this be considered as a feature request?

Cheers!

This whole, MIDI Remote → RCE → Plug-in deal has me scratching my head at times. I don’t feel confident that I’ve set up everything “by the book”. What I’ve done so far is:

  • Decide on a number of physical controls. In my case it’s 84. In your case it’s 128.
  • Go in each plug-in’s RCE, create as many pages as needed to fit the number of parameters you can control. (128 in your case, or 16 “pages”)
  • Assign / re-order parameters so that they land on the physical control I want. When no more parameters are available, I blank out all assignments up until the last one (parameter 128), which I always assign as bypass.
  • This way, I don’t get repetitions.

The point is, it is very tedious to go through all plug-ins, swapping parameters in the RCE to agree with the physical controls. I’m very interested in learning how other people do it, and if there’s an easier method for this.

Hey @ggmanestraki !

I agree, though, like you, I’ll keep thinking it over to find a way to make it work for me! I admire your commitment to editing the plugins you use to conform to your MIDI Remote implementation, which, obviously, you’d need to do every time you bought a new plugin.

It appears that the design philosophy of the MIDI Remote API was informed by the limitations/capabilities of hardware controllers that, obviously, have a finite number of surface elements. It makes sense then that MIDI Remote requires us to create the surface layout before value binding and mapping. I noticed that Mackie’s MCU protocol will map and value bind a plugin’s RCE layout to the rotary encoders precisely, so it would be great if MIDI Remote could do that, too.

Creating a software control surface, though, means that it isn’t necessarily restricted by physical limitations (84 or 128 controllers being a case in point), and allows for the option of creating controllers dynamically; however, this is not an option with MIDI Remote. I think insert plugins need to be treated differently.

Ideally, I’d like my software control surface to look at the plugin in focus, determine the plugin’s number of unique parameters (i.e., ignore the repeated parameters), dynamically create the corresponding number of surface knobs (<= 128) in the host plugin parameter bank zone, then map and value bind each knob to each unique plugin parameter. Right now, my software surface can determine a plugin’s unique parameters and arrive at the total number of surface knobs needed:

MIDI Remote Insert Parameter Labels Number

Going back to the stock StudioEQ plugin from my initial post, because all 128 parameter knobs are populated, the software adds each parameter once only to the list, uniqueLabels, and determines that the plugin requires only 21 of the 128 existing parameter knobs. That’s as far as I can get though, because there’s no way to retrospectively create 21 parameter knobs, or remove 107 parameter knobs. I’d even settle for populating the first 21 of the 128 parameter knobs and leaving the remaining knobs unmapped. Unfortunately, though, MIDI Remote populates all 128.

I think this could be achievable by modifying the makeParameterValue() method to include a parameter that represents a plugin’s unique label:

var uniqueLabels = ['1 Gain', '2 Gain', '3 Gain', '4 Gain', '1 Freq', '2 Freq', ...]
var totalNoOfParams = 21
var insertViewer = selectedTrackChannel.mInsertAndStripEffects.makeInsertEffectViewer.followPluginWindowInFocus()
var insertsParameterZone = insertViewer.mParameterBankZone
for (var i = 0; i < 21; ++i) {
  var parameter = 'Parameter' + i
  var pluginParameter = surfaceElements.paramBank[parameter]
  var pluginParameterValue = pluginParameter.mSurfaceValue
  page.makeValueBinding(pluginParameterValue, insertsParameterZone.makeParameterValue(uniqueLabels[i]))
}

I’m really eager to see what the next update includes! Hopefully, Steinberg has been following the MIDI Remote discussions here.

1 Like

I absolutely agree, they have to provide an option to not populate repeated controls.

Now, since your controller is software based, and assuming you’re not interested in the graphical representation of your controller inside Cubase, I would go for this:
Whenever you switch to an insert, Cubase is exposing all the parameters of your bank. Normally, you take these params, and populate your soft based view with their names and values. By storing them inside a map, you can just ignore identical params and leave your empty controllers in peace. At the same time, use setState inside your script to always know if you should use this index or not. Then, you should not bind directly your controls, but create customVars instead which upon control change will first check the param state (you should save this one upon populating) and trigger a second set of customVars whenever this state is set to “active” (just an example) and this second set should be binded to the params.
Perhaps, it sounds confusing (and it probably is), but it can be a workaround until we see an update covering this thing :slight_smile:

Hey @m.c !

Ahhh, yep! A little confusing but I think I understand the logic and as soon as I write up some pseudo code I’ll know for sure whether I’ve got a handle on it or not.

I’ve gotta say, I really like the way your brain works! :exploding_head:

Now all I need to do is work out a way to get you here to Australia so you can look over my shoulder as I’m working through it… :kangaroo: :beach_umbrella:

1 Like

:joy: for the kangaroo.
Glad that this might help !

Here’s a snippet:

var midiremote_api = require('midiremote_api_v1')

var deviceDriver = midiremote_api.makeDeviceDriver('TestProject', 'Test Controller', 'Someone')

var midiInput = deviceDriver.mPorts.makeMidiInput("yourmidiin")
var midiOutput= deviceDriver.mPorts.makeMidiOutput("yourmidiout")

var detectWin = deviceDriver.makeDetectionUnit()
detectWin
    .detectPortPair(midiInput, midiOutput)
    .expectInputNameEquals("Your Midi Input")
    .expectOutputNameEquals("Your Midi Output")

var surface = deviceDriver.mSurface

var customControlsFiltered=[]

var midiCCInitIndex=16
var controlsSize=128

for(var controlIndex=0;controlIndex<controlsSize;controlIndex++){
    
    var aCustomControlInit=surface.makeCustomValueVariable("aCustomControlLevelInit"+controlIndex)
    
    aCustomControlInit.mMidiBinding.setInputPort(midiInput).bindToControlChange(0,midiCCInitIndex+controlIndex).setTypeRelativeSignedBit()
    
    var aCustomControlFiltered=surface.makeCustomValueVariable("aCustomControlFiltered"+controlIndex)

    customControlsFiltered.push(aCustomControlFiltered)

    aCustomControlInit.mOnProcessValueChange=function(activeDevice,value,diff){
        
        var isActive=activeDevice.getState("controlisactive"+this.controlIndex)
        
        if(isActive==1){
            customControlsFiltered[this.controlIndex].setProcessValue(activeDevice,value)
        }

    }.bind({controlIndex})
    
}

var mapContainingInsertControls={}

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

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

insertViewer.followPluginWindowInFocus()
insertViewer.excludeEmptySlots()

insertViewer.mOnTitleChange=function(activeDevice,activeMapping,arg2){
   // console.log("current Slot="+arg2)
   //upon changing slot lets erase the map
   mapContainingInsertControls={}
   activeDevice.setState("logonce","")
}

insertViewer.mOnChangePluginIdentity=function(activeDevice,activeMapping,arg2,arg3,arg4,arg5){
   
    //this could also be used for erasing the map 

}

var insertViewerParamsZone=insertViewer.mParameterBankZone

function setActive(activeDevice,controlIndex,active){
    
    activeDevice.setState("controlisactive"+controlIndex,active)

}

for(var controlIndex=0;controlIndex<controlsSize;controlIndex++){
    
    var aParam=insertViewerParamsZone.makeParameterValue()
    
    var aCustomControlFiltered=customControlsFiltered[controlIndex]
    aPage.makeValueBinding(aCustomControlFiltered,aParam).mOnValueChange=function(activeDevice,activeMapping,arg2,arg3){
        //let's log the map upon a control change once
        if(arg3!=0 && activeDevice.getState("logonce")==""){
            
            var ourParams=Object.keys(mapContainingInsertControls)
            
            for(var i=0;i<ourParams.length;i++){
            
                console.log(ourParams[i]+" mapped to "+mapContainingInsertControls[ourParams[i]])

            }

            activeDevice.setState("logonce","1")

        }
    }
    
    aParam.mOnTitleChange=(function(activeDevice,activeMapping,arg2,arg3){
       
        if(arg2.length>0 && arg3.length>0){
            
            if(arg3 in mapContainingInsertControls){
                
                var currentControlIndexBinded=mapContainingInsertControls[arg3]
                
                if(this.controlIndex<currentControlIndexBinded){
                    
                    mapContainingInsertControls[arg3]=this.controlIndex
                    setActive(activeDevice,this.controlIndex,"1")
                    setActive(activeDevice,currentControlIndexBinded,"0")
                
                } else if(this.controlIndex>currentControlIndexBinded){
                    
                    setActive(activeDevice,this.controlIndex,"0")

                } else {
                    
                    //in equality we just have a repeated send from Cubase 

                }
            
            }
            
            else 
            
            {
               
                mapContainingInsertControls[arg3]=this.controlIndex
                setActive(activeDevice,this.controlIndex,"1")

            }

        }
        else {

            //empty param 
            setActive(activeDevice,this.controlIndex,"0")

        }
        
    }).bind({controlIndex})

}

1 Like

A snippet??!! That’s like calling the Grand Canyon a bit of a hole in the ground!

I really appreciated the time and effort you spent on putting the snippet together. Looking through it, I was encouraged to see some of my pseudo code methods fully fleshed-out in your snippet - just not as elegant as yours, of course!

If they haven’t already, Steinberg should put you on their books as a beta tester or MIDI Remote developer. I’d vote for that!

Thanks again for all your help and advice. You are one talented dude. :blush:

1 Like

Fortunately for Steinberg (and me as well) I’m too busy with my own business. I’m composing music as a hobby, and building scripts for that and hanging around in such fora, is also for entertainment purposes. Helps my mind cool down a bit :slight_smile:

Thank you for your kind words! (One day I will ask you to teach me mastering, I admire sound engineers!)

1 Like