Midi Remote Api: Get track name and pan value after sending a midi cc

This code sends the track name any time that the name of the track changes:

// get name of the selected track when track name changes
var textDisplayValue = deviceDriver.mSurface.makeCustomValueVariable('textDisplayValue');
textDisplayValue.mOnTitleChange = function(activeDevice, objectTitle, valueTitle) {
    // Convert the string into bytes using UTF-8 encoding
    var stringBytes = [];
    for (var i = 0; i < objectTitle.length; i++) {
        stringBytes.push(objectTitle.charCodeAt(i));
        }
    // Create the SysEx messages - Start of SysEx
    var sysexMessageSelectedTrackName = [0xF0];
    // Add bytes for specific message format
    sysexMessageSelectedTrackName.push(0x1A);
    // Append the bytes representing the string
    sysexMessageSelectedTrackName = sysexMessageSelectedTrackName.concat(stringBytes);
    // End of SysEx
    sysexMessageSelectedTrackName.push(0xF7);
    // Send the SysEx message
    midiOutput.sendMidi(activeDevice, sysexMessageSelectedTrackName);
};

I would that the script sends also the track name anytime it receives a midi message. I have tried several things, but nothing is working as expected.

textDisplayValue.mMidiBinding
    .setInputPort(midiInput)
    .setOutputPort(midiOutput)
    .bindToControlChange (10, 100) // channel 11, cc 100

How could I send a cc100 value 127 channel 11 to the script that makes the script to send back the name of the selected track?

How are you currently getting the name of the selected track?

I get it with the code posted inititally, adding this:

var hostSelectedTrackChannel = page.mHostAccess.mTrackSelection.mMixerChannel
page.makeValueBinding(textDisplayValue,hostSelectedTrackChannel.mValue.mSelected)

Is this what you are asking for?

Please tag me by quoting or direct tag in order for me to get a notification, otherwise, there’s a chance I will miss the post.

Ok, here’s what happening:

When you navigate to a track, it obviously gets selected. When this happens, the mOnTitleChange will get triggered.

Now, when you resend for example CC100 (Ch10) the mOnTitleChange won’t get triggered again! This is simply because there’s no title change at all!

Let’s solve this. Inside the mOnTitleChange we will set an MR state to contain the selected track’s name. We won’t do anything else here. What we will do is to use the mOnProcessValueChange of the custom variable, and send the sysex from there. This way. we can be sure that a) when we change track, the name will be sent and b) when we send our CC, we can send it again.

Here’s a snippet:

var midiremote_api = require('midiremote_api_v1')

var deviceDriver = midiremote_api.makeDeviceDriver("Test","Show Selected Name","m.c")

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 mapping=deviceDriver.mMapping

var textDisplayValue = surface.makeCustomValueVariable('textDisplayValue')

textDisplayValue.mMidiBinding
    .setInputPort(midiInput)
    .setOutputPort(midiOutput)
    .bindToControlChange (10, 100) // channel 11, cc 100

var page=mapping.makePage("page")

page.makeValueBinding(textDisplayValue,page.mHostAccess.mTrackSelection.mMixerChannel.mValue.mSelected)

textDisplayValue.mOnTitleChange=function(activeDevice, objectTitle, valueTitle) {
    
    activeDevice.setState("selectedTrackName",objectTitle)
    
}

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

    if(value==1){

        sendSysex(activeDevice,"selectedTrackName")

    }

}

function sendSysex(activeDevice,parameterName){
    
    var parameterValue=activeDevice.getState(parameterName)
    
    var sysexMessage=[0xF0,0x1A].concat(parameterValue.split('').map(function(c){return c.charCodeAt(0)})).concat([0xF7])

    midiOutput.sendMidi(activeDevice,sysexMessage)
    
}
1 Like

@m.c It works perfect, thanks a lot ! :slightly_smiling_face:

I understand the logic of your explanations, but I can’t understand the code. I would like to understand what the code is doing. May I ask you some questions?

  1. What is this code doing?
activeDevice.setState(“selectedTrackName”,objectTitle)
  1. I understand that .mOnProcessValueChange is hearing the changes of the value of the cc message, is that correct? But I don’t understand what information is saved in activeDevice, in value and in diff in the following code:
textDisplayValue.mOnProcessValueChange=function(activeDevice,value,diff){

This one saves a value (the second argument, objectTitle) to the key ("selectedTrackName”) of a temporary object in MR. We can then retrieve it elsewhere by calling activeDevice.getState(“selectedTrackName”). If you don’t feel comfortable with it, you can always use script global variables instead. For example, have outside of the event, a var selectedTrackName=””, and feed it in the event, event.mOnWhatever(){selectedTrackName='“something”}

activeDevice is an abstract object mirroring the current controller instance. Nothing you have to even think about.

value returns the current CC value of our MIDI message normalized in the range [0,1]. If our MIDI CC 100 sends a 127, the value will be 1, the upper limit.

diff is very interesting, it gives us the difference between the previous and current value of our MIDI CC (or note, pitchbend, etc) again normalized to the range [0,1]. This is useful especially in cases where we want for example to perform actions when using a knob. A left turn (when knob is properly setup) will give as a negative Diff (And/Or value=0), while in a right turn, a positive one (And/Or value=1).

aKnob.mSurfaceValue.mOnProcessValueChange=function(activeDevice,value,diff){
   if(diff<0 || value==0){
       //left turn
       //do something with it
   } else {
       //right turn
   }
}
2 Likes

Thanks for all your explanations @m.c !! :slightly_smiling_face:

Now that I understand every term in the code, I can’t see why the sysex is sent when the title changes.

I mean , when the title changes, the title is saved in the variable. But why it is triggered the function sendSysex??

Reading the code, I understand that the sysex should only be sent when the value of the midi cc changes (mOnProcessValueChange), but I can’t see how the mOnProcessValueChange detects the mOnTitleChange.

Could you please explain how this is workng?

Thanks!!

Certainly. The key is that we do a valueBinding. This means that whenever we send the CC, the MIDI Remote triggers the selection of the (actually already) selected track. The thing is that whenever the selected track changes using for example our mouse/keys, this valueBinding will notify the CC handle too! Think of it as two-ways communication. So, when the track changes, both the mOnTitle, and the mOnProcessValueChange will get triggered. Think of the processValue NOT as the value of the MIDI CC we send, but rather as the value of the process we handle (mMute, mSolo, mVolume etc) in Cubase.

Example:

page.makeValueBinding(aSurfaceValue,aHostValue)
//Events will get triggered when either the aSurfaceValue changes,
//or when aHostValue changes even when not changed by the controller
//Events
//mOnTitleChange
//mOnProcessValueChange
//mOnDisplayValueChange
//mOnColorChange

//usually the mOnTitleChange gets triggered before the mOnProcessValueChange
//We can inspect this by console.log and check the priority
1 Like

Thanks for the explanation @m.c.

I think I get it.

I have been monitoring the midi messages. Everytime the selected track changes, a midi cc value 0 and a midi cc value 127 are sent. That makes the sysex to be sent. Is it correct?

Another question: I have been trying to bind a second cc message in different ways, but I always get an error:

var textDisplayValue = deviceDriver.mSurface.makeCustomValueVariable('textDisplayValue');

textDisplayValue.mMidiBinding
    .setInputPort(midiInput)
    .setOutputPort(midiOutput)
    .bindToControlChange (10, 99)  // channel 11, cc 99
    .bindToControlChange (10, 100) // channel 11, cc 100

Is it possible to bind two different midi messages to the same object?

If by object you mean a control element (like the customValueVariable) the answer is no.
If however, you wish to bind a different CC to the very same hostValue, then sure, this is doable:

var textDisplayValue = deviceDriver.mSurface.makeCustomValueVariable('textDisplayValue');

textDisplayValue.mMidiBinding
    .setInputPort(midiInput)
    .setOutputPort(midiOutput)
    .bindToControlChange (10, 99)  // channel 11, cc 99

var textDisplayValue2 = deviceDriver.mSurface.makeCustomValueVariable('textDisplayValue');

textDisplayValue2.mMidiBinding
    .setInputPort(midiInput)
    .setOutputPort(midiOutput)
    .bindToControlChange (10, 100) // channel 11, cc 100

page.makeValueBinding(textDisplay,aHostValue)
page.makeValueBinding(textDisplay2,aHostValue)

If you just need to mirror the second CC to the binding of the first, in every mapping, you can even do this:

textDisplayValue2.mOnProcessValueChange(activeDevice,value,diff){
   textDisplayValue.setProcessValue(activeDevice,value)
}
1 Like

Thanks a lot @m.c
Tomorrow I will try both options.

I have been spending several hours today trying to write a code to receive the pan value of the selected track after sending a midi cc, but all my attempts have been a failure.

This is the code I am using for receiving the name and the pan value of the selected track:

// get name of the selected track when track name changes OR cc is received
var selectedTrackName = ""
var selectedTrackName_ID = 0x1A
var selectedTrackName_consolePrefix = "Selected Track | Name: "
var selectedTrackNameObject = deviceDriver.mSurface.makeCustomValueVariable('selectedTrackNameObject')
selectedTrackNameObject.mMidiBinding
    .setInputPort(midiInput)
    .setOutputPort(midiOutput)
    .bindToControlChange (0, 11) // channel 1, cc 11
selectedTrackNameObject.mOnTitleChange = function(activeDevice, objectTitle, valueTitle) {
    selectedTrackName = objectTitle
}
selectedTrackNameObject.mOnProcessValueChange=function(activeDevice,value,diff){
    if(value==1){
sendSysexString(activeDevice,selectedTrackName,selectedTrackName_ID,selectedTrackName_consolePrefix)
    }
}


// get panorama of the selected track when pan value changes OR cc is received
var selectedTrackPanorama = ""
var selectedTrackPanorama_ID = 0x1B
var selectedTrackPanorama_consolePrefix = "Selected Track | Panorama: "
var selectedTrackPanoramaObject = deviceDriver.mSurface.makeCustomValueVariable('selectedTrackPanoramaObject')
selectedTrackPanoramaObject.mMidiBinding
    .setInputPort(midiInput)
    .setOutputPort(midiOutput)
    .bindToControlChange (0, 12) // channel 1, cc 12
selectedTrackPanoramaObject.mOnDisplayValueChange = function(activeDevice, objectTitle, valueTitle) {
    selectedTrackPanorama = objectTitle
}
selectedTrackPanoramaObject.mOnProcessValueChange=function(activeDevice,value,diff){        
    sendSysexString(activeDevice,selectedTrackName,selectedTrackName_ID,selectedTrackName_consolePrefix)
}

// send sysex and show log
function sendSysexString(activeDevice,receivedString,sysexID,consolePrefix){
    var sysexMessage=[0xF0,sysexID].concat(receivedString.split('').map(function(c){return c.charCodeAt(0)})).concat([0xF7])
    midiOutput.sendMidi(activeDevice,sysexMessage)
    console.log(consolePrefix + receivedString)
}


var hostSelectedTrackChannel = page.mHostAccess.mTrackSelection.mMixerChannel
page.makeValueBinding(selectedTrackNameObject, hostSelectedTrackChannel.mValue.mSelected)
page.makeValueBinding(selectedTrackPanoramaObject, hostSelectedTrackChannel.mValue.mPan)

When I send channel 1, cc 11, value 127 the name of the selected track is sent by sysex and it’s shown correctly in the console.

But when I send channel 1, cc 12, value 127 the panorama of the selected track is moved to R, sending the sysex “R” and showing the “R” pan in the console.

I have tried a lot of things but I can’t stop the panorama from always changing to R.

How should I do this (if it’s possible) ?

Let’s focus on the pan thing.
It’s pretty normal Pan going to R, when sending a 127 to a midi CC bound to the panorama. We have to avoid it.
Solution: Set a “dummy” customValueVariable bound to the panorama. It won’t have any midi binding at all. However, Cubase will still update its mOnTitleChange, mOnDisplayValueChange, etc, so we will still get the info we want. Then, when we press our real button, we can resend this info. This real button won’t be bound to the panorama. We will just use its mOnProcessValueChange.

// get panorama of the selected track when pan value changes OR cc is received
var selectedTrackPanorama = ""
var selectedTrackPanorama_ID = 0x1B
var selectedTrackPanorama_consolePrefix = "Selected Track | Panorama: "
var selectedTrackPanoramaObject = deviceDriver.mSurface.makeCustomValueVariable('selectedTrackPanoramaObject')

var dummySelectedTrackPanoramaObject=deviceDriver.mSurface.makeCustomValueVariable("dummySelectedTrackPanoramaObject")

selectedTrackPanoramaObject.mMidiBinding
    .setInputPort(midiInput)
    .setOutputPort(midiOutput)
    .bindToControlChange (0, 12) // channel 1, cc 12


dummySelectedTrackPanoramaObject.mOnDisplayValueChange = function(activeDevice, objectTitle, valueTitle) {
    selectedTrackPanorama = objectTitle
    sendSysexString(activeDevice,selectedTrackName,selectedTrackName_ID,selectedTrackName_consolePrefix)
}

selectedTrackPanoramaObject.mOnProcessValueChange=function(activeDevice,value,diff){        
    if(value==1){

        sendSysexString(activeDevice,selectedTrackName,selectedTrackName_ID,selectedTrackName_consolePrefix)
    
    }
    
}

page.makeValueBinding(dummySelectedTrackPanoramaObject, hostSelectedTrackChannel.mValue.mPan)
1 Like

Perfect, simple and smart solution!! Here is the complete code if somebody wants to use it:

var midiremote_api = require('midiremote_api_v1')

var deviceDriver = midiremote_api.makeDeviceDriver("Test","Show Selected Name","m.c")

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

var detectionUnit=deviceDriver.makeDetectionUnit()

detectionUnit.detectPortPair(midiInput, midiOutput)
    .expectInputNameEquals("Bome MIDI Translator Input")
    .expectOutputNameEquals("Bome MIDI Translator Output")


// get name of the selected track when track name changes OR cc is received
var selectedTrackName_value = ""
var selectedTrackName_ID = 0x1A
var selectedTrackName_consolePrefix = "Selected Track | Name: "
var selectedTrackName = deviceDriver.mSurface.makeCustomValueVariable('selectedTrackName')
selectedTrackName.mMidiBinding
    .setInputPort(midiInput)
    .setOutputPort(midiOutput)
    .bindToControlChange (0, 11) // channel 1, cc 11
selectedTrackName.mOnTitleChange = function(activeDevice, objectTitle, valueTitle) {
    selectedTrackName_value = objectTitle
}
selectedTrackName.mOnProcessValueChange=function(activeDevice,value,diff){
    if(value==1){
        sendSysexString(activeDevice,selectedTrackName_value,selectedTrackName_ID,selectedTrackName_consolePrefix)
    }
}


// get panorama of the selected track when pan value changes OR cc is received
var selectedTrackPanorama_value = ""
var selectedTrackPanorama_ID = 0x1B
var selectedTrackPanorama_consolePrefix = "Selected Track | Panorama: "

var selectedTrackPanorama = deviceDriver.mSurface.makeCustomValueVariable("selectedTrackPanorama")

selectedTrackPanorama.mOnDisplayValueChange = function(activeDevice, objectTitle, valueTitle) {
    selectedTrackPanorama_value = objectTitle
    if (selectedTrackPanorama_value==="") {selectedTrackPanorama_value="X"} // sends "X" if the track has no panorama slider
    sendSysexString(activeDevice,selectedTrackPanorama_value,selectedTrackPanorama_ID,selectedTrackPanorama_consolePrefix)
}

var selectedTrackPanoramaNotBindedToPanorama = deviceDriver.mSurface.makeCustomValueVariable('selectedTrackPanoramaNotBindedToPanorama')

selectedTrackPanoramaNotBindedToPanorama.mMidiBinding
    .setInputPort(midiInput)
    .setOutputPort(midiOutput)
    .bindToControlChange (0, 12) // channel 1, cc 12

selectedTrackPanoramaNotBindedToPanorama.mOnProcessValueChange=function(activeDevice,value,diff){        
    if(value==1){
        sendSysexString(activeDevice,selectedTrackPanorama_value,selectedTrackPanorama_ID,selectedTrackPanorama_consolePrefix)
    }
}


// send sysex and show log
function sendSysexString(activeDevice,receivedString,sysexID,consolePrefix){
    var sysexMessage=[0xF0,sysexID].concat(receivedString.split('').map(function(c){return c.charCodeAt(0)})).concat([0xF7])
    midiOutput.sendMidi(activeDevice,sysexMessage)
    console.log(consolePrefix + receivedString)
}



// create page and host accessing objects
var page = deviceDriver.mMapping.makePage('Default')
var hostSelectedTrackChannel = page.mHostAccess.mTrackSelection.mMixerChannel


// bind surface elements to host accessing object values
page.makeValueBinding(selectedTrackName, hostSelectedTrackChannel.mValue.mSelected)
page.makeValueBinding(selectedTrackPanorama, hostSelectedTrackChannel.mValue.mPan)
1 Like