Advanced scripting - Multi-event commands

A multi-event command is, as the name suggests, a command with multiple events that can trigger the command. Any number of events can be included in the command. The basic principle is that one of the events will trigger the command, at which time the plugin checks if the states of the other events are met.

By definition, two events cannot happen at the same time. Even if you have multiple midi events that happen within the same millisecond, they don't happen at the same time. For every event that happens, that event alone is the event that triggers the command, and all the other events in that command are states rather than events.

If all events are fulfilled, the actions in the command are executed. An example:

[(press)(cc:1,1,127){text:Yeah}]

For the text action to be executed, both events (press) and (cc) must be met, which will happen in the following two situations:

  • When the button is pressed, and the latest received value for CC1 is 127.
  • When CC1 value 127 is received while the button is pressed.

If either of those situations occurs, the text action will be executed.
If any of the events fail, the actions are not executed; this will be the case:

  • When the button is pressed, and the latest received value for CC1 is unknown, or something else than 127.
  • When CC1 value 127 is received while the button is not pressed.

Script execution rules

One script command can trigger other commands in the same or other scripts. Several rules control how script commands can interact with each other to get an expected behavior and avoid script loops. To do this, the plugin keeps track of the "command chain" (i.e., the chain of commands triggering each other) and makes decisions based on the contents of the command chain.

  • A script command can only execute once (in a command chain). When a command is triggered, the plugin checks the command chain to see if that very same command has already been executed, and if so, ignores the command. This is done to avoid command loops.
  • If a (press) event has started the command chain, no other commands containing (press) events will be executed. This is done to avoid illogical behavior; you probably expect a single (press) command to be executed when you press the button.
  • Commands containing an (init) event are only executed when the button is loaded (i.e., when the profile/page is loaded or when the Stream Deck software is starting). Other events in the (init) command are evaluated when the button is loaded, but they will not trigger a command execution by themselves.

Events in multi-event commands

In a multi-event command, some events work differently than when used in a single-event command. The following table outlines the rules when events are used in a multi-event command.

Event Functionality in a multi-event
(init) (init) events can be used in multi-event commands, and the (init) event is the only event that can trigger the command.

You can have multiple (init) commands combined with various midi events or custom value events, but please note that the (init) commands are only executed when the button is loaded. If you have midi events in a multi-event command with (init), the states of those midi controls are checked when the button is loaded, but the command is not triggered when those midi commands are received.

(time), (press) and (release) events are ignored if used in a multi-event command with (init).

When midi controls and custom variables have not yet received any values (e.g., when the profile is loaded as a part of the Stream Deck start-up), the value 0 is returned.
(press) If (press) commands are used in multi-event commands, they are treated differently than if they are used in single-event commands. In single-event commands, (press) events are handled in sequential order, one by one (unless the order is changed with nextpress actions). In multi-event commands, (press) events are searched for a command that fulfills all the events in that command. The idea is that if you define several multi-event commands with (press) events, you want different things to happen when you press the button, depending on other criteria. Let's look at an example:

[(press)(cc:1,1,0-64){text:Yeah}]
[(press)(cc:1,1,65-127){text:Nope}]
If you define two commands like that, it's pretty obvious (at least to me) that you want to have the text "Yeah" or "Nope" displayed on the button, depending on the latest received value for CC1. In the search for commands with all events met, the plugin always starts with the command that is the next in the sequence from the last executed command. This means that the command sequence is important even if the commands are not executed in sequential order as in single-event commands. An example:

[(press)(cc:1,1,0-64){text:Yeah}]
[(press)(cc:1,1,65-127){text:Nope}]
[(press)(cc:1,1,0-64){text:Maybe}]
In this example, the first and last commands have the same events (which is perfectly legal but probably not very useful).
  • The first time you press the button (with CC1 in the range 0-64), the button will display "Yeah".
  • The second time you press the button, the plugin will start to search from the second command since that is the next in the sequence. Since this command doesn't fit the CC event, it will continue with the third command, find a match and display "Maybe".
This example will alternate between "Yeah" and "Maybe" every time you press the button. The example is, of course, not very realistic, but I think you get the idea of how the order of commands affects the result.

In the search for a (press) command that fulfills all events, the plugin will always stop at a single-event (press) command since "all" events are fulfilled. This may not be what you want, and I recommend keeping all (press) commands in the script the same type (single-event or multi-event).
(release) I recommend keeping (release) commands as single-event commands.

I assume that if you have defined a (release) command, it should always be executed whenever the preseeding (press) command has been executed. If you make the (release) command a multi-event command (with the same events as in the (press) command), the state for those other events may have changed after the (press) command, in which case the (release) command will be ignored.
(cc), (pc), (noteon), (noteoff) In a background repository, the plugin keeps the state of all received and sent control change, program change, note on, and note off for all channels on all midi ports. When an event triggers a command, the plugin can check the state of all other events and determine if the latest received (or sent) values meet the criteria for those events.

In plugin versions 2.5-2.7, script commands were triggered by midi commands received from the daw only; starting with version 2.8, midi commands sent from Stream Deck will also trigger script commands. Please see the (config) section if you don't want midi commands sent from Stream Deck to trigger script commands.

Example:

[(cc:1,1,64)(cc:1,2,127){cc:1,3,37}]
[(cc:1,3,37)(cc:1,4,127){text:Yes}]
The first command will send the value 37 to CC3 under the following circumstances:
  • When CC1 value 64 is received and the latest value for CC2 is 127.
  • When CC2 value 127 is received and the latest value for CC1 is 64.
The second command will display the text "Yes" under the following circumstances:
  • When CC3 value 37 is received and the latest value for CC4 is 127.
  • When CC4 value 127 is received and the latest value for CC3 is 37.
An important note is that - starting with version 2.8 - the first command will trigger the second command. Please see the (config) section if you don't want midi commands sent from Stream Deck to trigger script commands.

Transition events

Even though transition events are allowed, I advise you not to use them in multi-event commands unless you want to capture a very specific situation.

The background repository only keeps the latest value for midi controls, and transition checks cannot be made. This means that if you have a transition event in a multi-event command, that is the only event that can trigger the command. Example:

[(cc:1,1,>64>)(cc:1,2,127){cc:1,3,37}]
When CC1 transit through value 64 and CC2 has value 127 (in the background repository), value 37 is sent to CC3. This command will not be triggered by receiving value 127 for CC2 since the CC1 transition event cannot be checked using the background repository and therefore fails.

If you have two or more transition events in a command, the command will never be successfully triggered since, regardless of which event triggers the command, the other event(s) cannot be verified against the background repository.
(time)

(time) events can be used in multi-event commands, but it must be the first event in the command. I advise you to have the (time) event as the only event that can trigger the command. Consider a solution where you want to change the time display format by pressing the button, e.g., switching between full time format and BPM. You might expect the following script to do the trick, but it doesn't.

[(time)(press){text:#value#}]
[(time)(press){text: BPM\n#B#}]

The time display format will be changed, but the time will only be updated while the button is pressed. In addition, the time display will flash between the two formats since both commands will run every time a midi time command is received (while the button is pressed). The problem is solved using a custom variable to store the state for the time format:

[(time)(@l_TimeFormat:0){text:#value#}]
[(time)(@l_TimeFormat:1){text: BPM\n#B#}]
[(press){@l_TimeFormat:1}]
[(press){@l_TimeFormat:0}]
Use the same principle if you want to change the time format based on incoming midi commands.

(state) Stream Deck will default toggle between states 0 and 1 every time you press the button, and you can also affect the button state with the {state} action. You can use a (state) event to check the current state in a multi-event command. Example:

[(state:0)(cc:1,1,0-127){text:Yeah}]
[(state:1)(cc:1,1,0-127){text:Nope}]
In this example, whenever any value is received for CC1, the button will display "Yeah" or "Nope", depending on the state of the button.

Outside actions will never trigger a (state) event; it is only checked when another event has triggered the command. Neither a {state} action nor an automatic state change by the Elgato software will trigger a (state) event.
(@CustomVariable) Custom variables (see the next section) can be used as events in commands, and changing a custom variable will trigger script commands. Example:

[(@MyVariable:1-64){text:Low}]
[(@MyVariable:65-127){text:High}]
When MyVariable is set to a value in the range 1-64 (from some other script), the button will display "Low", and when the variable is set to a value in the range 65-127, the text "High" will be displayed on the button. If "MyVariable" is 0 or unknown, nothing will happen.
(config) In plugin versions before 2.8, Midi commands sent from Stream Deck did not trigger script commands; only Midi commands received from the daw did. Starting with version 2.8, the default behavior is that both Midi commands from the daw and Stream Deck will trigger script commands. If this behavior is not wanted, you can revert to the previous behavior by adding the following script command anywhere in your script:

[(config){TriggerOnLocalMidiEvents:No}]
If the {TriggerOnLocalMidiEvents} action is missing or has the parameter "Yes", "True", or "1", Midi commands sent from Stream Deck will trigger commands in the script. If the parameter is "No", "False", or "0", the script will ignore Midi commands sent from Stream Deck and only be triggered by Midi commands sent from the daw.
When you have an action that stores a value in a custom variable, the default behavior is to trigger commands where the variable is referenced in an event. This behavior is the same regardless of whether or not the variable value is changed. Consider the following script:

[(press){@MyVariable:1}]
[(@MyVariable:0-64){text:Hello}]
When the button is pressed, and the variable MyVariable is set to 1, the second command will be triggered, and the text "Hello" will be shown even if the value in MyVariable is unchanged. If you want commands to be triggered only when the variable value is changed, you can add the following action to the (config) command:

[(config){TriggerOnUnchangedVariables:No}]
If the {TriggerOnUnchangedVariables} action is missing or has the parameter "Yes", "True", or "1", an action that stores a value in a variable will trigger commands in the script even if the value is unchanged. If the parameter is "No", "False", or "0", the script will avoid triggering commands if the content of a variable is unchanged.

Please note that you cannot have multiple (config) commands in your script; if you need to set both these settings, add them to the same command:

[(config){TriggerOnLocalMidiEvents:No}{TriggerOnUnchangedVariables:No}]

#value# references in multi-event commands

If actions in a multi-event command reference #value# or time values, they will always reference the first defined event in the command, regardless of which event triggered the command. This gives the following guidelines:

  • A (time) event must always be the first event in a multi-event command; otherwise, references to received time values will not work.
  • (press) and (release) events must not be the first event if they are present in multi-event commands with value references since they have no values.

Event value references in multi-event commands

If actions in a multi-event command reference event values (e.g., @event_ccvalue, @event_notevelocity), they will always reference the first defined event of the referenced type (CC or NoteOn/NoteOff in the example) in the command, regardless of which event triggered the command.