Midi-Remote API: how to keep Solo/Mute/Rec LEDs in sync?

I have an older icon Platform X+ controller, which I’d like to use with Cubase/Nuendo. I have modified the example script to match the controller. The controls all work well, but I am struggling with the LED states. Right now soloing a channel will also turn on the mute LEDs of all other channels. But when I un-solo the channel the mute LEDs stay on. As far as I understand the LEDs on this controller can be toggled by sending the same midi note messages back to the controller but I have not been able to figure out how. I would be glad if someone could help me out <3

icon_PlatformX.zip (1.4 KB)

Your code is 99% correct. The issue comes from the setOutputPort of the mute/solo/arm buttons. We need to handle the messages sent to our controller without respecting this mirroring.
I’ve altered your script, removed the setOutputPort from these buttons, and added functions for sending the MIDI messages expected by the controller. No other changes were made. Give it a try and let me know if it now works correctly.

//-----------------------------------------------------------------------------
// 1. DRIVER SETUP - create driver object, midi ports and detection information
//-----------------------------------------------------------------------------

// get the api's entry point
var midiremote_api = require('midiremote_api_v1')

// create the device driver main object
var deviceDriver = midiremote_api.makeDeviceDriver('icon', 'Platform X Plus', 'Greyboxaudio')

// create objects representing the hardware's MIDI ports
var midiInput = deviceDriver.mPorts.makeMidiInput()
var midiOutput = deviceDriver.mPorts.makeMidiOutput()

// define all possible names the device's MIDI ports could have
// NOTE: Windows and macOS handle port naming differently
deviceDriver.makeDetectionUnit().detectPortPair(midiInput, midiOutput)
    .expectInputNameEquals('Platform X+1 V2.13')
    .expectOutputNameEquals('Platform X+1 V2.13')

deviceDriver.makeDetectionUnit().detectPortPair(midiInput, midiOutput)
    .expectInputNameEquals('Platform X+1 V2.13 (MIDI IN)')
    .expectOutputNameEquals('Platform X+1 V2.13 (MIDI OUT)')


//-----------------------------------------------------------------------------
// 2. SURFACE LAYOUT - create control elements and midi bindings
//-----------------------------------------------------------------------------

var knobs = []
var faders = []
var buttonsSelect = []
var buttonsMute = []
var buttonsSolo = []
var buttonsRec = []

var numChannels = 8

for (var channelIndex = 0; channelIndex < numChannels; ++channelIndex) {
    var defaultMult = 3
    var offset = 2

    var knob = deviceDriver.mSurface.makePushEncoder(channelIndex * defaultMult, 0, 2, 2)
    knob.mEncoderValue.mMidiBinding
        .setInputPort(midiInput).setOutputPort(midiOutput)
        .bindToControlChange(0, 16 + channelIndex)
        .setTypeRelativeSignedBit()
    knob.mPushValue.mMidiBinding
        .setInputPort(midiInput).setOutputPort(midiOutput)
        .bindToNote(0, 32 + channelIndex)
    knobs.push(knob)

    var fader = deviceDriver.mSurface.makeFader(channelIndex * defaultMult + 0.5, 2, 1, 6)
    fader.mSurfaceValue.mMidiBinding
        .setInputPort(midiInput).setOutputPort(midiOutput)
        .bindToPitchBend(0 + channelIndex)
    faders.push(fader)

    var buttonSelect = deviceDriver.mSurface.makeButton(channelIndex * defaultMult + offset, 4, 1, 1)
    buttonSelect.mSurfaceValue.mMidiBinding
        .setInputPort(midiInput).setOutputPort(midiOutput)
        .bindToNote(0, 24 + channelIndex)
    buttonsSelect.push(buttonSelect)

    var buttonMute = deviceDriver.mSurface.makeButton(channelIndex * defaultMult + offset, 5, 1, 1)
    buttonMute.mSurfaceValue.mMidiBinding
        .setInputPort(midiInput)
        .bindToNote(0, 16 + channelIndex)
        
    buttonsMute.push(buttonMute)

    var buttonSolo = deviceDriver.mSurface.makeButton(channelIndex * defaultMult + offset, 6, 1, 1)
    buttonSolo.mSurfaceValue.mMidiBinding
        .setInputPort(midiInput)
        .bindToNote(0, 8 + channelIndex)
    buttonsSolo.push(buttonSolo)

    var buttonRec = deviceDriver.mSurface.makeButton(channelIndex * defaultMult + offset, 7, 1, 1)
    buttonRec.setShapeCircle()
    buttonRec.mSurfaceValue.mMidiBinding
        .setInputPort(midiInput)
        .bindToNote(0, 0 + channelIndex)
    buttonsRec.push(buttonRec)
}

//-----------------------------------------------------------------------------
// 3. HOST MAPPING - create mapping pages and host bindings
//-----------------------------------------------------------------------------

var page = deviceDriver.mMapping.makePage('Example Mixer Page')

var hostMixerBankZone = page.mHostAccess.mMixConsole.makeMixerBankZone()
    .excludeInputChannels()
    .excludeOutputChannels()

var previousMuteSoloArmValues=[
    [-1,-1,-1,-1,-1,-1,-1,-1],
    [-1,-1,-1,-1,-1,-1,-1,-1],
    [-1,-1,-1,-1,-1,-1,-1,-1]
]

for (var channelIndex = 0; channelIndex < numChannels; ++channelIndex) {
    var hostMixerBankChannel = hostMixerBankZone.makeMixerBankChannel()

    var knobEncoderValue = knobs[channelIndex].mEncoderValue;
    //var knobPushValue = knobs[channelIndex].mPushValue;
    var faderSurfaceValue = faders[channelIndex].mSurfaceValue;
    var buttonSurfaceValue = buttonsSelect[channelIndex].mSurfaceValue;
    var buttonMuteSurfaceValue = buttonsMute[channelIndex].mSurfaceValue;
    var buttonSoloSurfaceValue = buttonsSolo[channelIndex].mSurfaceValue;
    var buttonRecSurfaceValue = buttonsRec[channelIndex].mSurfaceValue;

    page.makeValueBinding(knobEncoderValue, hostMixerBankChannel.mValue.mPan)
    page.makeValueBinding(faderSurfaceValue, hostMixerBankChannel.mValue.mVolume)
    page.makeValueBinding(buttonSurfaceValue, hostMixerBankChannel.mValue.mSelected)
    page.makeValueBinding(buttonMuteSurfaceValue, hostMixerBankChannel.mValue.mMute).setTypeToggle()
    page.makeValueBinding(buttonSoloSurfaceValue, hostMixerBankChannel.mValue.mSolo).setTypeToggle()
    page.makeValueBinding(buttonRecSurfaceValue, hostMixerBankChannel.mValue.mRecordEnable).setTypeToggle()

    buttonRecSurfaceValue.mOnProcessValueChange=function(activeDevice,value,diff){
        setNewState(activeDevice,0,this.channelIndex,value)
    }.bind({channelIndex})

    buttonSoloSurfaceValue.mOnProcessValueChange=function(activeDevice,value,diff){
        setNewState(activeDevice,1,this.channelIndex,value)
    }.bind({channelIndex})

    buttonMuteSurfaceValue.mOnProcessValueChange=function(activeDevice,value,diff){
        setNewState(activeDevice,2,this.channelIndex,value)
    }.bind({channelIndex})

}

function setNewState(activeDevice,recSoloMute,index,value){
    //recSoloMute: 0=Rec, 1=Solo, 2=Mute
    var prevVal=previousMuteSoloArmValues[recSoloMute][index]
    if(value!=prevVal){
        previousMuteSoloArmValues[recSoloMute][index]=value
        midiOutput.sendMidi(activeDevice,[0x90,8*recSoloMute+index,127*value])
    }
}

Amazing, thank you so much! It works like a charm and I have already expanded it to include the track select functionality as well and also figured out how to reset the pan with the push encoders. I’ll take some time to look over your code and will try to get a better understanding of your implementation :slight_smile:

icon_PlatformX_v2.zip (1.6 KB)

Cool :+1: