For me, 8 Focused Quick Controls are more than enough, since I do use them either at the very early stages of my drafts, so I need to quickly change “important” parameters only, or the late stages of mix/master, when again I tend to focus only on a few parameters. So 8 is more than OK for me.
However, not everyone is the same, nor the needs.
Here’s a small midi remote, which tries to extend the Focus functionality, so that we can handle more than the 8 focused quick controls.
Here’s the idea behind it:
MIDI Remote provides complete access to our Instrument and Inserts Quick Controls. These, contrary to the Focused ones, can be of no real limit, by using the concept of parameter banks. Controls can be banked by a specific parameters’ size, and we can have dedicated buttons for choosing previous/next/first bank. The bank size doesn’t even have to be 8. We can choose much bigger numbers.
So, if we find a way to know what is the nature and place of the focused plugin, we can set our remote to handle the instrument/insert slot corresponding to this plugin, and we can now have the new banking system helping us override the FQC limitation.
Using the relatively new Direct Access entity, we can query the path of the currently focused plugin, while at the same time, by having our channels mapped by their IDs, we can find both the slot (instrument or insert) the plugin lives in, and the channel that hosts it. So then, we can programmatically select the channel, and then set our remote to a subPage for handling the correct slot.
Here’s the midiRemote installation file, for those wanting to give this a try. I’ve set it to handle 16 parameters per bank. CB15.0.20 is required due to the MIDI Ports recognition unit I prepared. You can alter this in the code provided below, and use prior CB versions (down to 13.0.50 if I recall correctly).
Test_FQC Extender.midiremote (2.9 KB)
You will need a virtual MIDI Port to activate it.
And here’s the code for those interested in such implementations (without having to install the script):
// @ts-nocheck
var numOfParamsPerBank=16
var midiremote_api = require('midiremote_api_v1')
var deviceDriver = midiremote_api.makeDeviceDriver("Test", "FQC Extender", "mc")
var midiInput = deviceDriver.mPorts.makeMidiInput("anInput")
var detectionUnitWin = deviceDriver.makeDetectionUnit()
detectionUnitWin.mPlatformFilter.expectPlatformWindows() //Another feature added in API 1.3 is the ability to chek for OS, so that we can properly define the MIDI Ports names that should be expected
detectionUnitWin.detectSingleInput(midiInput)
.expectInputNameEquals("loopMIDI Port")
var detectionUnitMac = deviceDriver.makeDetectionUnit()
detectionUnitMac.mPlatformFilter.expectPlatformMacOS()
detectionUnitMac.detectSingleInput(midiInput)
.expectInputNameEquals("IAC Driver Bus 3")
var surface = deviceDriver.mSurface
var knobs=[]
for(var i=0;i<numOfParamsPerBank;i++){
var knob=surface.makeKnob(i,1,1,1)
knob.mSurfaceValue.mMidiBinding
.setInputPort(midiInput)
.bindToControlChange(0,i+3)
knobs.push(knob)
}
var buttonPreviousBank=surface.makeButton(0,2.5,1,0.5)
buttonPreviousBank.mSurfaceValue.mMidiBinding
.setInputPort(midiInput)
.bindToControlChange(0,0)
var buttonNextBank=surface.makeButton(1,2.5,1,0.5)
buttonNextBank.mSurfaceValue.mMidiBinding
.setInputPort(midiInput)
.bindToControlChange(0,1)
var buttonResetBank=surface.makeButton(2,2.5,1,0.5)
buttonResetBank.mSurfaceValue.mMidiBinding
.setInputPort(midiInput)
.bindToControlChange(0,2)
var labelSubPage=surface.makeLabelField(0,0,3,0.5)
var mapping = deviceDriver.mMapping
var page=mapping.makePage("Default")
var numOfInsertSlots=midiremote_api.mDefaults.getNumberOfInsertEffectSlots()
var subPagesArea=page.makeSubPageArea("subPagesArea")
var subPages=[]
for(var i=0;i<numOfInsertSlots+2;i++){
var subPagesName= i==0 ? "Track" : i==1 ? "Instrument" : "Slot "+(i-1)
subPages.push(subPagesArea.makeSubPage(subPagesName))
}
page.setLabelFieldSubPageArea(labelSubPage,subPagesArea)
var fqc=page.mHostAccess.mFocusedQuickControls
var daMixer=page.mHostAccess.makeDirectAccess(page.mHostAccess.mMixConsole)
var daFQC=page.mHostAccess.makeDirectAccess(fqc)
var selectedTrack=page.mHostAccess.mTrackSelection.mMixerChannel
var instrumentQuickControls=selectedTrack.mInstrumentPluginSlot.mParameterBankZone
var instrumentParams=[]
var insertViewers=[]
var insertParams=[]
var nullHostValue=page.mCustom.makeHostValueVariable("nullHostValue")
for(var i=0;i<numOfInsertSlots+2;i++){
var tmpNumOfParams= i==0 ? Math.min(numOfParamsPerBank,midiremote_api.mDefaults.getNumberOfQuickControls()) : numOfParamsPerBank
for(var j=0;j<tmpNumOfParams;j++){
if(i==0){
page.makeValueBinding(knobs[j].mSurfaceValue,selectedTrack.mQuickControls.getByIndex(j)).setSubPage(subPages[0])
} else if (i==1){
instrumentParams.push(instrumentQuickControls.makeParameterValue())
page.makeValueBinding(knobs[j].mSurfaceValue,instrumentParams[j]).setSubPage(subPages[1])
} else {
if(j==0){
insertViewers.push(selectedTrack.mInsertAndStripEffects.makeInsertEffectViewer("insertViewer"+i))
insertViewers[i-2].accessSlotAtIndex(i-2)
insertParams.push([])
}
insertParams[i-2].push(insertViewers[i-2].mParameterBankZone.makeParameterValue())
page.makeValueBinding(knobs[j].mSurfaceValue,insertParams[i-2][j]).setSubPage(subPages[i])
}
}
if(i==0){
page.makeValueBinding(buttonPreviousBank.mSurfaceValue,nullHostValue).setSubPage(subPages[0])
page.makeValueBinding(buttonNextBank.mSurfaceValue,nullHostValue).setSubPage(subPages[0])
page.makeValueBinding(buttonResetBank.mSurfaceValue,nullHostValue).setSubPage(subPages[0])
} else if(i==1){
page.makeActionBinding(buttonPreviousBank.mSurfaceValue,instrumentQuickControls.mAction.mPrevBank).setSubPage(subPages[1])
page.makeActionBinding(buttonNextBank.mSurfaceValue,instrumentQuickControls.mAction.mNextBank).setSubPage(subPages[1])
page.makeActionBinding(buttonResetBank.mSurfaceValue,instrumentQuickControls.mAction.mResetBank).setSubPage(subPages[1])
} else {
page.makeActionBinding(buttonPreviousBank.mSurfaceValue,insertViewers[i-2].mParameterBankZone.mAction.mPrevBank).setSubPage(subPages[i])
page.makeActionBinding(buttonNextBank.mSurfaceValue,insertViewers[i-2].mParameterBankZone.mAction.mNextBank).setSubPage(subPages[i])
page.makeActionBinding(buttonResetBank.mSurfaceValue,insertViewers[i-2].mParameterBankZone.mAction.mResetBank).setSubPage(subPages[i])
}
if(tmpNumOfParams<numOfParamsPerBank){
for(var j=tmpNumOfParams;j<numOfParamsPerBank;j++){
page.makeValueBinding(knobs[j].mSurfaceValue,nullHostValue).setSubPage(subPages[i])
}
}
}
var currentPluginPath=""
var focusedObjectID=-1
var channelsUniqueNames=[]
var channelsIds=[]
page.mOnActivate=function(activeDevice,activeMapping){
daMixer.activate(activeMapping)
daFQC.activate(activeMapping)
}
page.mOnDeactivate=function(activeDevice,activeMapping){
daMixer.deactivate(activeMapping)
daFQC.deactivate(activeMapping)
}
daMixer.mOnObjectChange=function(activeDevice,activeMapping,objectID){
if(objectID==daMixer.getBaseObjectID(activeMapping)){
var size=daMixer.getNumberOfChildObjects(activeMapping,objectID)-1
if(size!=channelsUniqueNames.length){
channelsUniqueNames=[]
channelsIds=[]
for(var i=0;i<size;i++){
var channelID=daMixer.getChildObjectID(activeMapping,objectID,i)
var name=daMixer.getObjectUniqueName(activeMapping,channelID)
var uniqueID=daMixer.getObjectUniqueIDString(activeMapping,channelID)
var channelName="VST Mixer\\Channels\\"+name
channelsUniqueNames.push(channelName)
channelsIds.push(channelID)
}
}
}
}
daMixer.mOnObjectWillBeRemoved=function(activeDevice,activeMapping,objectID){
var channelIDIndex=channelsIds.indexOf(objectID)
if(channelIDIndex!=-1){
channelsIds.splice(channelIDIndex,1)
channelsUniqueNames.splice(channelIDIndex,1)
}
}
daFQC.mOnObjectChange=function(activeDevice,activeMapping,objectID){
if(objectID==daFQC.getBaseObjectID(activeMapping)){
var pluginPathTag=4
var newPluginPath=daMixer.getParameterDisplayValue(activeMapping,objectID,pluginPathTag)
if(newPluginPath!=currentPluginPath){
currentPluginPath=newPluginPath
if(currentPluginPath.length==0){
//Track focus
subPages[0].mAction.mActivate.trigger(activeMapping)
return
}
var channelPath=currentPluginPath.split("\\").slice(0,3).join("\\")
var indexOfChannel=channelsUniqueNames.indexOf(channelPath)
if(indexOfChannel!=-1){
var channelID=channelsIds[indexOfChannel]
var subPageToSwitch=-1
var instrumentPrefix=channelPath+"\\Slot\\"
var insertsPrefix=channelPath+"\\Inserts\\Slot"
var start,end,slotIndex
if(currentPluginPath.indexOf(instrumentPrefix)==0){
subPageToSwitch=1
} else {
start=currentPluginPath.indexOf(insertsPrefix)
if(start!==-1){
start+=insertsPrefix.length
end=currentPluginPath.indexOf("\\",start)
slotIndex= end!=-1 ? parseInt(currentPluginPath.substring(start,end),10) : 1
subPageToSwitch = isNaN(slotIndex) ? 2 : slotIndex+1
}
}
if(subPageToSwitch!=-1){
//Selecting the track which hosts the focused plugin
var prevLock=daMixer.getParameterEditLockState(activeMapping,channelID,4000)
daMixer.setParameterEditLockState(activeMapping,channelID,4000,true)
daMixer.setParameterProcessValue(activeMapping,channelID,4000,1)
daMixer.setParameterEditLockState(activeMapping,channelID,4000,prevLock)
subPages[subPageToSwitch].mAction.mActivate.trigger(activeMapping)
}
} else {
console.log("Channel not found. This shouldn't happen when project not empty.")
}
}
}
}
A video demonstrating the above. Watch how the MIDI Remote UI changes accordingly to the plugin (or track) we currently have in focus: