Multiple Ports not recognized

Hi, I thought I should add this here as an issue with multiple midi ports in scripts, when we create a new (empty) project.

When we have a ports pair, no problem at all, Cubase immediately recognize it, and loads the script.

However, if we add another ports pair, it is not recognized automatically and gets us to the port setup window of the script.

Testing this is pretty straight forward:

var midiremote_api = require('midiremote_api_v1')


var deviceDriver = midiremote_api.makeDeviceDriver('test', 'test', 'multiple ports issue')

var midiInput = deviceDriver.mPorts.makeMidiInput()
var midiOutput = deviceDriver.mPorts.makeMidiOutput()

var midiInput2 = deviceDriver.mPorts.makeMidiInput()
var midiOutput2 = deviceDriver.mPorts.makeMidiOutput()

deviceDriver.makeDetectionUnit().detectPortPair(midiInput, midiOutput)
    .expectInputNameEquals('loopMIDI Port')
    .expectOutputNameEquals('loopMIDI Port 1')
	
deviceDriver.makeDetectionUnit().detectPortPair(midiInput2, midiOutput2)
    .expectInputNameEquals('loopMIDI Port 2')
    .expectOutputNameEquals('loopMIDI Port 3')

If we remove the second pair (midiInput2 & midiOutput2), no problem at all! If we include it, the issue arises.

I’ve intentionally used virtual ports here, in order for it to be easily reproduced, however it obviously happens with “real” midi ports as well.

As a workaround, I have setup a project template containing the script, so this isn’t a big trouble in the end, but anyway, I think that this should be taken care of.

3 Likes

Hi,

Oh yes, please!

1 Like

Just out of curiosity:

What would be the implications of creating another separate deviceDriver object?

Hi Nico5, good question!

Generally, maybe none.

In my integrations, however, this cannot work. I’ll give you three examples of different nature:

  • My Arturia Keylab has two ports. The thing is, when I’m changing the mode of my control (from DAW to Analog Lab, if you happen to know how this controller works), the second port sends a message for that, and I need to know it, in order to suspend updating my lcd display which however listens to the first port. At the same time there are some additional functions performed on port 2, but I want to display feedback on port 1.

  • A second example and more generic, is when we want additional functionalities not already implemented by the API, to be received and send feedback to our “main” controller. We setup a virtual port to get the data and then inside the script we send whatever is needed to our “real” port. I use it all the time by creating a pseudo-Mackie and/or generic remote for this virtual port.

  • A final example is for when we want to translate messages. Say that we want that when user clicks on a control on our “real” controller, which sends a midiCC to transform it to another midiCC (or note, or whatever). We’re going to need a midi output for this and this cannot be our “real” port, in order to get the message to Cubase. A good use case for this, is when we split an encoder to left/right midiCCs which can then be midi-learnt by the users inside the assistant.

I guess my question was, if you could maybe create a new persistent object that contains multiple deviceDriver objects? Each of those objects would only contain one midi in and one midi out port. So the multiple deviceDriver objects would each just have one pair of midi inputs and outputs.

The idea would be to get around what seems to be a limitation of the deviceDriver class?

And then events in one of those deviceDriver objects could maybe trigger actions in the other deviceDriver object?

But since I’m painfully inexperienced with programming this way, there may be a big conceptual limitation I’m missing. If yes, I have to apologize for a possibly very silly idea.

(Right after the release of Cubase 12, I had successfully programmed one MIDI Remote script (with just one pair of input and output ports), but since then have only used the GUI configuration environment. )

1 Like

Ah, sorry, I now get your point!

Not, really, no, as far as I can see, and I hope I may be wrong on this.

Let me explain:

Say you create a deviceDriver

var deviceDriver = midiremote_api.makeDeviceDriver(arguments here)

Now, suppose you want to add a second one:

var deviceDriver2 = midiremote_api.makeDeviceDriver( the very same arguments here again)

What really happens is that both deviceDriver and deviceDriver2 refer to the very same object, simply because they actually have the very same arguments, to keep it simple.

“Why?”

Well , say, you change the arguments for your second driver. This won’t work, since Cubase is scanning scripts based on these arguments. Its approach (a correct one if you ask me) is to create a driver per device per script file at least so I understand :slight_smile:

EVERY idea in programming can’t be silly by definition. We always have to workaround things, no matter how clever or silly, cruel or elegant our approach may look :slight_smile: Thank you for your suggestion!

1 Like

I’d like to second this – multiple port pair detection is exactly what I need with my MCU scripts :pray:

1 Like

Hi @m.c,

when using multiple ports (or pairs) you have to give them names. I missed to write that into the documentation.

var midiInput = deviceDriver.mPorts.makeMidiInput('inp1')
var midiOutput = deviceDriver.mPorts.makeMidiOutput('out1')

var midiInput2 = deviceDriver.mPorts.makeMidiInput('inp2')
var midiOutput2 = deviceDriver.mPorts.makeMidiOutput('out2')

Hope that helps!

2 Likes

Hi @Jochen_Trappe , sure, the names have to be defined and in my script for the Keylab I do have (no luck unfortunately)

var midiInputDAW = deviceDriver.mPorts.makeMidiInput("dawin")
var midiOutputDAW = deviceDriver.mPorts.makeMidiOutput("dawout")

var midiInputMIDILOOP = deviceDriver.mPorts.makeMidiInput("loopin")
var midiOutputMIDILOOP = deviceDriver.mPorts.makeMidiOutput("loopout")

In my script for the SL MK3 however, I don’t include names in the first pair, so, sure, I will try this one, but I’m not very optimistic, since it’s not working with the Keylab :frowning:

Hi again @Jochen_Trappe , no luck with the SL MK3 as well.
However, I did check the json produced:

external":[{"id":68,"type":"DomainModel","members":{"DeviceDrivers":[69]}},{"id":69,"type":"DeviceDriver","members":{"VendorName":"Novation","DeviceName":"SL MK3 MC Custom","CreatedBy":"Minas Chantzides","Ports":70,"Surface":71,"Mapping":72,"UserMapping":1}},{"id":70,"type":"Ports","members":{"MidiInputs":[73,75],"MidiOutputs":[74]}},{"id":71,"type":"Surface","members":{"SurfaceLabelFields":[76,77,78,79,80,81,82,83,84]}},{"id":72,"type":"FactoryMapping","members":{"Pages":[85,86,87,88,89,90]}},{"id":73,"type":"DeviceMidiInput","members":{"Name":""}},{"id":74,"type":"DeviceMidiOutput","members":{"Name":""}},{"id":75,"type":"DeviceMidiInput","members":{"Name":"loopin"}},{"id":76,"type":"SurfaceLabelField","members":{"Rect":{"X":0.0,"Y":2.5,"W":2.0,"H":1.0}}},{"id":77,"type":"SurfaceLabelField","members":{"Rect":{"X":6.0,"Y":6.0,"W":2.0,"H":1.0}}},{"id":78,"type":"SurfaceLabelField","members":{"Rect":{"X":6.0,"Y":7.5,"W":2.0,"H":1.0}}},{"id":79,"type":"SurfaceLabelField","members":{"Rect":{"X":6.0,"Y":9.5,"W":2.0,"H":1.0}}},{"id":80,"type":"SurfaceLabelField","members":{"Rect":{"X":24.0,"Y":6.0,"W":2.0,"H":1.0}}},{"id":81,"type":"SurfaceLabelField","members":{"Rect":{"X":8.0,"Y":11.0,"W":2.0,"H":1.0}}},{"id":82,"type":"SurfaceLabelField","members":{"Rect":{"X":10.0,"Y":11.0,"W":2.0,"H":1.0}}},{"id":83,"type":"SurfaceLabelField","members":{"Rect":{"X":12.0,"Y":11.0,"W":2.0,"H":1.0}}},{"id":84,"type":"SurfaceLabelField","members":{"Rect":{"X":14.0,"Y":11.0,"W":2.0,"H":1.0}}},{"id":85,"type":"FactoryMappingPage","members":{"Name":"mixer"}},{"id":86,"type":"FactoryMappingPage","members":{"Name":"User 1"}},{"id":87,"type":"FactoryMappingPage","members":{"Name":"User 2"}},{"id":88,"type":"FactoryMappingPage","members":{"Name":"User 3"}},{"id":89,"type":"FactoryMappingPage","members":{"Name":"User 4"}},{"id":90,"type":"FactoryMappingPage","members":{"Name":"User 5"}}]

As we can see, the first midiIn&Out ports are not getting any name at all, even if I have manually defined them, since Cubase is not recognising the ports as named inside the script.

At the same time, the second midiIn gets its name, and then the second midiOut is totally absent!
Here are the defs:

"Ports","members":{"MidiInputs":[73,75],"MidiOutputs":[74]}

We see two midiIn and just one midiOut. Maybe you want to have a look at this? If I recall correctly, you do have an SL MK3 as well to give this one a try :slight_smile:

Ok, back.
@Jochen_Trappe , here’s a snippet for two pairs, using loopMIDI Ports 1 to 4:

var midiremote_api=require('midiremote_api_v1')

var deviceDriver=midiremote_api.makeDeviceDriver("Multiple Ports","Issue","Someone")

var usePairsOneTwoOrBoth=0 // 0=Both, 1=Pair One, 2=Pair Two 
//Testing pairs separately works as expected

var useContainsOrEquals=0 //0=Contains, 1=Equals

if(usePairsOneTwoOrBoth==1 || usePairsOneTwoOrBoth==0){

    var midiInput1 = deviceDriver.mPorts.makeMidiInput("in1")
    var midiOutput1 = deviceDriver.mPorts.makeMidiOutput("out1")

    var detectPortPair=deviceDriver.makeDetectionUnit().detectPortPair(midiInput1, midiOutput1)
        
        
    if(useContainsOrEquals==0){
        
        detectPortPair
            .expectInputNameContains('loopMIDI')
            .expectInputNameContains('1')
            .expectOutputNameContains('loopMIDI')
            .expectOutputNameContains('2')

    } else {

        detectPortPair
            .expectInputNameEquals('loopMIDI Port 1')
            .expectOutputNameEquals('loopMIDI Port 2')
    
    }

}

if(usePairsOneTwoOrBoth==2 || usePairsOneTwoOrBoth==0){

    var midiInput2 = deviceDriver.mPorts.makeMidiInput("in2")
    var midiOutput2 = deviceDriver.mPorts.makeMidiOutput("out2")

    var detectPortPair2=deviceDriver.makeDetectionUnit().detectPortPair(midiInput2, midiOutput2)
        
        
    if(useContainsOrEquals==0){
        
        detectPortPair2
            .expectInputNameContains('loopMIDI')
            .expectInputNameContains('3')
            .expectOutputNameContains('loopMIDI')
            .expectOutputNameContains('4')

    } else {

        detectPortPair2
            .expectInputNameEquals('loopMIDI Port 3')
            .expectOutputNameEquals('loopMIDI Port 4')
    
    }

}

    
var surface=deviceDriver.mSurface

var knob=surface.makeKnob(0,0,1,1)

if(usePairsOneTwoOrBoth==0 || usePairsOneTwoOrBoth==1){

    knob.mSurfaceValue.mMidiBinding.setInputPort(midiInput1).bindToControlChange(0,20)

} else {

    knob.mSurfaceValue.mMidiBinding.setInputPort(midiInput2).bindToControlChange(0,20)

}

var page=deviceDriver.mMapping.makePage("page")
page.makeValueBinding(knob.mSurfaceValue,page.mHostAccess.mTrackSelection.mMixerChannel.mValue.mPan)

I have the var usePairsOneTwoOrBoth which when set to 0 will try to create both pairs, while when 1 or 2 will create a single pair.

When set to 0, the pairs are not automatically detected, while when set to 1 or 2, no problem at all, the correct pair are instantly recognised.

Interestingly, now the json correctly show the two pairs, in contradiction to my previous test with a “real” device.

{“id”:15,“type”:“Ports”,“members”:{“MidiInputs”:[17,19],“MidiOutputs”:[18,20]}},
{“id”:16,“type”:“FactoryMapping”,“members”:{“Pages”:[21]}},
{“id”:17,“type”:“DeviceMidiInput”,“members”:{“Name”:“in1”}},
{“id”:18,“type”:“DeviceMidiOutput”,“members”:{“Name”:“out1”}},
{“id”:19,“type”:“DeviceMidiInput”,“members”:{“Name”:“in2”}},
{“id”:20,“type”:“DeviceMidiOutput”,“members”:{“Name”:“out2”}}

Other than that, the issue persists.

Attached the midiRemote file:
Multiple Ports_Issue.midiremote (1.3 KB)

Thank you for your time on this!

Hi @m.c, I guess when having already some persistence for a javascript-based script, making persistence-relevant changes to that script will lead to inconsistencies. You should delete the according json file after each change to a javascript-base script. Please create two separate scripts for your comparison approach.
Regarding the main issue with multiple MIDI ports. I’ll have a look now.

Hi Jochen, upon any such change, I always delete the json first, sure.

Hi @m.c,

I’ve just written a test script for the Arturia Minilab3 that I have here on my desk. It has multiple port pairs. This is what works very well:

var midiremote_api = require('midiremote_api_v1')

var driver = midiremote_api.makeDeviceDriver("Arturia", "miniLAB 3", "Steinberg Media Technologies GmbH")

var midiInpMIDI = driver.mPorts.makeMidiInput("MIDI")
var midiOutMIDI = driver.mPorts.makeMidiOutput("MIDI")

var midiInpMCU = driver.mPorts.makeMidiInput("MCU")
var midiOutMCU = driver.mPorts.makeMidiOutput("MCU")

var detectMacOS = driver.makeDetectionUnit()

var detectMacOSPairMIDI = detectMacOS.detectPortPair(midiInpMIDI, midiOutMIDI)
detectMacOSPairMIDI
    .expectInputNameContains('Minilab3')
    .expectInputNameContains('MIDI')
    .expectOutputNameContains('Minilab3')
    .expectOutputNameContains('MIDI')

var detectMacOSPairMCU = detectMacOS.detectPortPair(midiInpMCU, midiOutMCU)
detectMacOSPairMCU
    .expectInputNameContains('Minilab3')
    .expectInputNameContains('MCU')
    .expectOutputNameContains('Minilab3')
    .expectOutputNameContains('MCU')


var btn01 = driver.mSurface.makeButton(0, 0, 1, 1)
btn01.mSurfaceValue.mMidiBinding
    .setInputPort(midiInpMIDI).setOutputPort(midiOutMIDI)
    .bindToNote(9, 36)

var btn02 = driver.mSurface.makeButton(1, 0, 1, 1)
btn02.mSurfaceValue.mMidiBinding
    .setInputPort(midiInpMCU).setOutputPort(midiOutMCU)
    .bindToNote(0, 94)

var page = driver.mMapping.makePage('Default')

var hostSelTrkMuteValue = page.mHostAccess.mTrackSelection.mMixerChannel.mValue.mMute
var hostCycleActiveValue = page.mHostAccess.mTransport.mValue.mCycleActive

page.makeValueBinding(btn01.mSurfaceValue, hostSelTrkMuteValue).setTypeToggle()
page.makeValueBinding(btn02.mSurfaceValue, hostCycleActiveValue).setTypeToggle()

4 Likes

This is great actually!
Here’s the difference that prevented my script from detecting the ports correctly.

I had

var detectPortPair=deviceDriver.makeDetectionUnit().detectPortPair(midiInput1, midiOutput1)

and then for the second pair, the similar:

var detectPortPair2=deviceDriver.makeDetectionUnit().detectPortPair(midiInput2, midiOutput2)

The correct statements should be based on your snippet:

var dectUnit=deviceDriver.makeDetectionUnit()
var detectPortPair=dectUnit.detectPortPair(midiInput1, midiOutput1)
var detectPortPair2=dectUnit.detectPortPair(midiInput2, midiOutput2)

This means that deviceDriver.makeDetectionUnit() needs to be the very same for the two pairs, I get this one, because most probably recalling it, creates a new instance which leads to issues.

Thank you very much, let me mark this solved!

Yes, a detection-unit represents the device as its complete set of midi i/o-ports. Please create multiple detection-units only for cross-platform detection purposes.

2 Likes

Yes, this is true as I found out, thank you!

1 Like