Midi-Remote: direct access to insert parameters?

I’m trying to directly access parameters of insert plug-ins, but I haven’t found a working approach so far.

I can successfully add a plug-in (e.g., Pro‑Q 4) to an insert slot, but I’m unable to directly reference or bind to specific parameters. For example, I’d like to control a parameter like [Band 1 Gain] with a dedicated hardware knob.
Is there any way to achieve direct parameter binding for insert plug-ins, without relying on SubPages or the nextBank/prevBank workflow?

Could this somehow be achieved via DirectAccess?

If that’s not possible, I suppose I’ll need to mark this post as a feature request and pause my plan to “natively” support Pro‑Q 4 with my CC121.

Thank you guys!

Yes you can but you access the parameters by an index number essentially. To change the order of this list of parameters, use the Remote Control Editor.

Two ways:
The first (old) is to create a sufficiently large bank of parameters, and then bind your knob to the parameter you want by its index. I think that @mlib is perhaps referring to this.

The second uses the directAccess object. Note that is is more low-level, and it will then be up to you to properly handle the MIDI messages of your knob (absolute, relative, etc).
Each parameter has a “tag” and you can query the tags of the parameters and console.log them, so you can find out the tag of the one you want. You can even setup a function searching for the tag, by matching the parameter’s name.
Check this example of controlling a parameter for the plugins “Mono Delay” and “Revelation” and their parameters “Feedback” and “Pre-Delay” when they’re in the first insert slot.

// @ts-nocheck
var midiremote_api = require('midiremote_api_v1')
var deviceDriver = midiremote_api.makeDeviceDriver("Test","Control Parameter","mc")

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

var detectionUnit=deviceDriver.makeDetectionUnit()
detectionUnit.detectPortPair(midiInput,midiOutput)
   .expectInputNameEquals("Bome MIDI Translator 1")
   .expectOutputNameEquals("Bome MIDI Translator 1")
    
var surface=deviceDriver.mSurface
var customKnob=surface.makeKnob(0,0,1,1)
customKnob.mSurfaceValue.mMidiBinding
   .setInputPort(midiInput)
   .bindToControlChange(0,0)

var mapping=deviceDriver.mMapping
var currentMapping

//setting up everything to lower case
var pluginsParams={

   "monodelay": ["feedback"],

   "revelation": ["pre-delay"]

}

var slotIndex=0 //Handling the first insert slot
var paramTag=-1
var stepRes=1/127 // The step. We can set it to bigger/smaller values

var page=mapping.makePage("Default")

var selTrack=page.mHostAccess.mTrackSelection.mMixerChannel

var insertViewer0=selTrack.mInsertAndStripEffects.makeInsertEffectViewer("insertViewer0")
insertViewer0.accessSlotAtIndex(slotIndex)
var daInsertViewer0=page.mHostAccess.makeDirectAccess(insertViewer0)

insertViewer0.mOnChangePluginIdentity=function(activeDevice,activeMapping,pluginName,pluginVendor,pluginVersion,formatVersion){

   paramTag=-1

   var pluginNameLow=pluginName.toLowerCase()
   
   if(pluginNameLow in pluginsParams){
      
      var params=pluginsParams[pluginNameLow]
      var param0=params[0]
      
      var insertSlotID=insertViewer0.getRuntimeID(activeMapping)
      
      if (insertSlotID==-1){
         return 
      }
      
      //The object containing the plugin is the first child of the slot object
      var insertPluginID=daInsertViewer0.getChildObjectID(activeMapping,insertSlotID,0)

      var paramNum=daInsertViewer0.getNumberOfParameters(activeMapping,insertPluginID)
   
      if(paramNum==0){
         return 
      }

      for(var i=0;i<paramNum;i++){
   
         var paramTagByIndex=daInsertViewer0.getParameterTagByIndex(activeMapping,insertPluginID,i)
   
         var paramName=daInsertViewer0.getParameterTitle(activeMapping,insertPluginID,paramTagByIndex,128)
   
         //A good place to log tags/names for our plugins
         //console.log("tag: "+paramTagByIndex+" name: "+paramName)

         if(paramName.toLowerCase()==param0){
      
            paramTag=paramTagByIndex
            break 
         
         }
      
      }
   
   }

}

page.mOnActivate=function(activeDevice,activeMapping){

   currentMapping=activeMapping
   daInsertViewer0.activate(activeMapping)

}

page.mOnDeactivate=function(activeDevice,activeMapping){

   daInsertViewer0.deactivate(activeMapping)

}

customKnob.mSurfaceValue.mOnProcessValueChange=function(activeDevice,value,diff){

   var insertSlotID=daInsertViewer0.getBaseObjectID(currentMapping)
 
   if(insertSlotID<1 || paramTag==-1) return 

   var insertPluginID=daInsertViewer0.getChildObjectID(currentMapping,insertSlotID,0)
   
   var currentParamValue=daInsertViewer0.getParameterProcessValue(currentMapping,insertPluginID,paramTag)
   
   //absolute mode
   var newParamValue=currentParamValue+(value==0 || diff<0 ? -1 : 1)*stepRes
   newParamValue=Math.max(0,Math.min(1,newParamValue))
   
   daInsertViewer0.setParameterProcessValue(currentMapping,insertPluginID,paramTag,newParamValue)

}

Hi m.c,

thanks for this great example. Based on your code snipped, I could prove that I’m able to direct access parameters from Pro-Q.
I still need to experiment further to understand whether the parameter tags remain consistent system-wide or if they change depending on factors such as the mixer channel or modifications in the insert slots.
I also learned from your example that it’s possible to bind directly to Notes or CCs—something I wasn’t aware of before. Even though I went through the API reference, I completely overlooked that detail. This will definitely help improve my implementation.
I still have a question: why do you activate/deactivate the daInsertViewer0 object. What is the purpose of that?

The tags are the ones exposed by VSTs to the DAW, they are constant, as long of course as the VST vendor keeps the tags unchanged.

In the example I posted, this is of no use. However, it is needed when we need to get feedback for the altered parameters. In this case, we have two events, the mOnObjectChange and mOnParameterChange, inside of which we can program our feedback to the controller.

Sorry, I’m not sure I understand this. Talking about the handles I do inside the mOnProcessValueChange of the controls?

thank you!
You’re insights are great. Also the demonstration code you post here. Very helpful and much appreciated.

About the CCs, I was referencing to this one:

Remote Control Editor is so underrated. Thanks for mentioning it, it felt really great when I discovered it a few years ago. Mostly I used it to map most compressor plugins the same way.