Scripting midi ports

I’ve been thinking about proposing this idea for scripting for a long time. Now that we have global ports available, I think it could be implemented well and without any danger.

The basic idea is to have an action to be able to send midi messages through different output ports in the scripted buttons and dials.

That is, the idea is that the scripted buttons and dials are not limited to being able to send midi messages only through the port selected in the drop-down menu.

I’ll give an example. The following code would send the same midi message to two different global output ports:

[(press) {outputport:GP1} {cc:1,1,1} {outputport:GP2} {cc:1,1,1}]

If this is implemented, it could be approached in two ways:

1- Temporary change: The command {outputport:GP1} does not change the output port in the port drop-down menu, it is a change limited to the line of code where the command appears

2- Permanent change: The command {outputport:GP1} changes the output port in the port drop-down menu

I will develop both options a little.

1- Temporary change
Let’s imagine that we have a scripted dial that has the GP1 output port configured in the port drop-down menu with the following code:

[(press) {outputport:GP5} {cc:1,1,127}]

All rotation actions will always be sent through port GP1. Every time the dial is pressed, a midi message will be sent through the GP5 port. If we added a second line of (press), it would mean that the first time the dial is pressed a midi message would be sent through the GP5 and the second time it would be sent through the GP1:

[(press) {outputport:GP5} {cc:1,1,127}] → sent through the GP5
[(press) {cc:1,1,127}] → sent through the GP1

2- Permanent change
Let’s use the same example as before: we have a scripted dial that has the output port GP1 configured in the port dropdown menu with the following code:

[(press) {outputport:GP5} {cc:1,1,127}]

Before the dial is pressed, all rotation actions will be sent through the GP1 port. However, when the dial is pressed, a midi message will be sent out the GP5 output port, and all rotation actions will be sent out the GP5 port from now on. In the drop-down menu of ports, the GP1 port will be replaced by the GP5 port.

In this permanent change mode, we could also easily make the change temporary, with the conditions we want. For example, the following code would send the same midi message to GP5 and immediately set the output port back to GP1:

[(press) {outputport:GP5} {cc:1,1,127} {outputport:GP1}]

And where it gets interesting is that we can use variables that would give us many possibilities:

[(press) (@myGP:5) {outputport:GP5} {cc:1,1,127} {outputport:GP1}]
[(press) (@myGP:2) {outputport:GP2} {cc:1,1,127} {outputport:GP1}]
[(press) (@myGP:1) {cc:1,1,127}]

I don’t know which is better in terms of performance, programming, risks, etc. but I think that the most powerful, practical and interesting for the user is the permanent change mode.

Regarding the port that is changed, I have thought that it is better to limit it to global ports to make everything simpler and more scalable. But maybe some users would like to have the freedom to write the port name directly. I don’t know if this is more difficult to implement or if it is worth it.


Now that I have explained the concept, I will explain why I find it useful to implement it.

Now that I have bought the SD+XL and have been making modifications (quite a lot because now I go from having 4-track banks to having 6-track banks) I have realized again that I have some quite complicated configurations that share midi messages and variables between many button/dial scripts and several background scripts.

All these complicated configurations would be very simple if I could send midi messages from the same script through different ports.

So, as I see it, the main benefit of this idea is to allow the user to make complex configurations in a simple way (and therefore easier to maintain and document)


Finally, in case you consider it worth implementing the {outputport:GP1} action in the permanent mode option, it would also be useful to implement an {inputport:GP1} action.

The {inputport} action would be limited only to the {init} events, without being able to set any other conditions (multievent disabled).

The following code would be accepted and executed:

[(init){inputport:GP1}]

But the following codes would give an error and would not be executed

init) (@l_var:1) {inputport:GP1}]
[(init) (@l_var:2) {inputport:GP2}]
[(init+) (@l_var:1) {inputport:GP1}]
[(init+) (@l_var:2) {inputport:GP2}]
[(init+) (cc:1,1,1) {inputport:GP1}]
[(cc:1,1,1) {inputport:GP1}]
[(press) {inputport:GP1}]
etc.

This implementation would be useful for configuring input and output ports from the script code, which would save time when making new scripted buttons or dials. You would only need to write this at the beginning of the script:

[(init) {inputport:GP1} {ouputport:GP2}]

Thanks for your thorough explanation. I have had similar thoughts, but I dismissed them because I thought it was too complex to implement.

Looking back, I can’t see any real problem with implementing a permanent port-switch action, similar to the other actions that change editor properties. There will be performance hits since the plugin must open and close ports and maintain the status of all ports, but apart from that, there shouldn’t be any real hazards.

Temporary port changes are too complex to implement.

Some things may be a bit unintuitive. MIDI events, for example, will not respond to a port change. Consider the command [(cc:1,1,127){do_something}]. If you switch from a port where cc 1,1 has value 0 to a port where cc 1,1 has value 127, the command will not trigger. Only new commands received after the port change will trigger it.

If I implement script actions for this, I see no reason to restrict it to the output port only. I’m a bit uncertain about the “only GP” vs “any port” decision. Only allowing global ports makes the syntax check easier, but I’m not sure it’s a reasonable restriction. On the other hand, if I had known what I know now when I started developing the plugin, global ports would have been the only option in the plugin.

Great!

I only explained this option in case it was more feasible than the permanent option. But for me, as I said before, the best option is permanent change mode.

I agree with you that there may be counterintuitive situations, but this will always depend on the user configuring the input port change correctly, at the right time, and taking into account the messages that are being received from other devices.

This example that you give is the main reason why I would only implement the input port change action in a “mono-event” (init).
I would not allow midi in port changes from any other event or from any other multievent, and no other moment that the initiation of the dial/button.

For me, changing the output port has no risk of communication problems or losing anything. But changing the input port I see as very problematic, it doesn’t give me a good feeling.

Also, I think that the need to change the input port is uncommon. Maybe I’m wrong, but in my case I always miss changing the output port to trigger different things from the same button/dial. But I’ve never needed to change the input port. Moreover, I would be quite panicked to change it.

As I see it, I think it would be convenient for both the input port change action and the output port change action to have restrictions.

I would limit the input port change to a single mono-event event:

(init)

And I would limit the output port change to two single events that could be combined into multievents:

(init)
(@var:value)

These two events could be combined into multievents like:

[(init) (@var:valueA) {outtA}]

[(init) (@var:valueB) {outB}]

[(init+) (@var:valuec) {outC}]

[(init+) (@var:valuerd) {outD}]

Since the value of the variables can be modified from any other event, this restriction would not prevent any event from changing the port. But I get the feeling that by doing it this way the user will structure the code better and indirectly avoid faulty communications.

I would limit the use of port changing to global ports without hesitation.

This way you force the user to use global ports, which is positive for him because if in the future he changes, for example, his main sound card, he only has to change the ports in one place: the dropdown menu in the settings.

If you give the user the freedom to set the port he wants, he may decide to make 100 scripts using the port name “fromStreamDeckToRME”.

If one day his RME breaks and he changes to a UAD sound card, he will have to change this port to “fromStreamDeckToUAD” in 100 scripts or keep the name “fromStreamDeckToRME” without having any RME. It is not a big problem, but these are things that global ports can avoid.

As I said, I would only allow global ports for port change actions, and at the same time increase the number of global ports.

In fact, I was going to make a new feature request about this. Now we have 10 GPs, which are 5 input-output pairs. I currently have 9 of them occupied, I only have one free. I suppose the option of being able to increase the number of GPs is feasible.

I had thought of being able to have a couple of keywords to increase the global ports: “20globalports” and “30globalports”

Another option is that the number of global ports could be chosen, but I think that being able to define 10 (default), 20 or 30 would cover all needs.

I see no obvious reason to restrict these actions to specific events. There are no technical reasons, and the tests required to enforce that would be complex and intrusive, since no other actions have similar restrictions. And if someone asked why, the only answer I can come up with is “jordikt wanted it this way”.

The field definitions are, unfortunately, nowhere near that flexible. Changing the number of ports requires hard-coding since additional fields need to be added to the profile.

I hope you never need to say that. I don’t want to be in a sentence like that.

Well noted, no problem.

That’s a pity. I hope I don’t need more global ports soon.