One push button sends a sequence of values?

How would I script or create a push button that would send not just one value but a sequence in order of values? For example:

I’ve got a push button here that activated CC 114, Channel 16, value 10. Is there a way for when I push the button it would activate value 10, then 12, then 19 or some random values such as those?

1 Like

Can you describe what you’re trying to do precisely ? Do you actually want the button to send a random value ?

Sure. One example I was thinking was when I push the button it would send this whole sequence of midi data in order.

  1. Channel 16, Controller 114, Value 127
  2. Channel 16, Controller 115, Value 0
  3. Channel 16, Controller 115, Value 3
  4. Channel 16, Controller 115, Value 7

Is this possible and made clearer sense Jean?

It could be done using a script widget (using the send function), however if you want to add delay between values you’ll need to create a custom module to take care of it.

So I'll freely admit I'm not savvy with Javscript. This is the custom module I came up with to fit one of my examples. However, I'm not sure how to implement this into a my "push_01" button correctly or if my code is even correct.

push_01:function() {
setTimeout(() => send('192.168.254.20', 8000, '/push_01', {args:[16,114]}, {value: 127}), 100);
setTimeout(() => send('192.168.254.20', 8000, '/push_01', {args:[16,115]}, {value: 0}), 100);
setTimeout(() => send('192.168.254.20', 8000, '/push_01', {args:[16,115]}, {value: 3}), 100);
setTimeout(() => send('192.168.254.20', 8000, '/push_01', {args:[16,115]}, {value: 7}), 100);
}

This is how I inserted into a script on my template

{
"type": "script",
"top": 360,
"left": 800,
"id": "script_2",
"linkId": "",
"width": 100,
"height": 60,
"label": "auto",
"color": "auto",
"css": "",
"condition": 1,
"script": "JS{{\nsetTimeout(() => send('192.168.254.20', 8000, '/push_01', {args:[16,114]}, {value: 127}), 100);\nsetTimeout(() => send('192.168.254.20', 8000, '/push_01', {args:[16,115]}, {value: 0}), 100);\nsetTimeout(() => send('192.168.254.20', 8000, '/push_01', {args:[16,115]}, {value: 3}), 100);\nsetTimeout(() => send('192.168.254.20', 8000, '/push_01', {args:[16,115]}, {value: 7}), 100);\n}}",
"default": "",
"value": "@{push_01}",
"precision": 2,
"address": "/script_2",
"preArgs": "",
"target": "",
"bypass": false
}

And this is the push button

{
"type": "push",
"top": 390,
"left": 660,
"id": "push_01",
"linkId": "",
"width": "auto",
"height": "auto",
"label": "auto",
"color": "auto",
"css": "",
"doubleTap": false,
"on": 1,
"off": 0,
"norelease": false,
"value": "",
"precision": 2,
"address": "/control",
"preArgs": "",
"target": "midi:OpenStageControl",
"bypass": false
}

Script widgets and custom modules are two different things, you can’t use timing functions (ie setTimeout) in widget’s formulas (JS{{}} / #{}) so you’ll need to write a custom module that will look like this:

module.exports = {
    oscOutFilter: function(data) {

        var {address, args, host, port} = data

        // Assuming the push button's address is /control_sequence_1
        // and value is 1 (with no preArgs)
        if (address == '/control_sequence_1' && args[0].value == 1) {
            setTimeout(()=>{
                send('192.168.254.20', 8000, '/push_01', 16, 114, 127)
                setTimeout(()=>{
                    send('192.168.254.20', 8000, '/push_01', 16, 114, 0)
                    setTimeout(()=>{
                        send('192.168.254.20', 8000, '/push_01', 16, 114, 3)
                        setTimeout(()=>{
                            send('192.168.254.20', 8000, '/push_01', 16, 114, 7)
                        }, 100)
                    }, 100)
                }, 100)
            }, 100)

            return // ignore original message
        }

        return {address, args, host, port}

    }
}

Notes:

  • setTimeout calls must be nested because it’s not a blocking function (https://nodejs.org/en/docs/guides/blocking-vs-non-blocking/)
  • to send midi messages you’d write something like
    send('midi', 'OpenStageControl', '/control', 16, 114, 3)
    or (if the push button’s target is “midi:OpenStageControl”):
    send(host, port, '/control', 16, 114, 3)
  • you are using invalid quote characters (aka “smart quotes”) , avoid using these as they will prevent your code from working
1 Like

Thank you very much Jean for your detailed explanation :smile:. I see I made some crucial errors but with your explanations, I'm starting to understand how this works so I can continue to write my other sequences. I am however having a little trouble getting the button to execute as I'm getting a console error

ERROR: MIDI: invalid address (/control_sequence_1)

I've directed the OSC settings to the custom module.

I've created my "push_01" button and changed its address to /control_sequence_1

{
                  "type": "push",
                  "top": 390,
                  "left": 660,
                  "id": "push_01",
                  "linkId": "",
                  "width": "auto",
                  "height": "auto",
                  "label": "auto",
                  "color": "auto",
                  "css": "",
                  "doubleTap": false,
                  "on": 1,
                  "off": 0,
                  "norelease": false,
                  "value": "",
                  "precision": 2,
                  "address": "/control_sequence_1",
                  "preArgs": "",
                  "target": "midi:OpenStageControl",
                  "bypass": false
                }

I must be forgetting to do something simple as I just keep getting error midi: invalid address?

Oh, that’s the 0 value not being filtered by the custom module and ending up being passed to the midi converted (which fails because /control_sequence_1 is not a valid address for midi messages). We can fix that by replacing this line

if (address == '/control_sequence_1' && args[0].value == 1) {

with

if (address == '/control_sequence_1') { // process all values from this address
     if (args[0].value != 1) return // discard values different from 1

That fixed the error thank you! Now the only mysterious part is nothing happens or is being executed when I hit the button. I’ve got my midi monitor status open to see if OSC is sending out any midi data when I push the button but nothing happens.

My custom module looks like this now from replacing the line you said.

module.exports = {
    oscOutFilter: function(data) {

        var {address, args, host, port} = data

        // Assuming the push button's address is /control_sequence_1
        // and value is 1 (with no preArgs)
        if (address == '/control_sequence_1') { // process all values from this address
			if (args[0].value != 1) return // discard values different from 1
				setTimeout(()=>{
					send('192.168.254.20', 8000, '/push_01', 16, 114, 127)
					setTimeout(()=>{
						send('192.168.254.20', 8000, '/push_01', 16, 114, 0)
						setTimeout(()=>{
							send('192.168.254.20', 8000, '/push_01', 16, 114, 3)
							setTimeout(()=>{
								send('192.168.254.20', 8000, '/push_01', 16, 114, 7)
							}, 100)
						}, 100)
					}, 100)
				}, 100)

            return // ignore original message
        }

        return {address, args, host, port}

    }
}

I haven’t changed anything form my push button as I linked before. Do I still need to write a script or just pushing a button with the address ‘/control_sequence_1’ should execute my midi sequences with the setTimeout?

Thank you for helping me so much. Once I figure out this first one I can easily get all my other sequences working. :sweat_smile:

You are only sending an osc message with

send('192.168.254.20', 8000, '/push_01', 16, 114, 127)

Check the 2nd footnote in my first reply :slight_smile:

Also, enabling the server debug option can help figuring what’s being sent / received.

1 Like

THANK YOU JEAN! :sweat_smile:

At first I was so confused and kept reading over and over your footnote. But you literally meant to just copy and paste your code if the push button’s target is “midi:OpenStageControl”

send(host, port, '/control', 16, 114, 3)

I’m honestly still confused why the address needs to be “/control” and not the address of my push button “/push_01”… nonetheless I copied your code exactly and viola! I just began to set up my sequences in the same *js file. I believe I placed them in the right place as each button is now excecuting 1 or the other sequences. Here is the custom module for anyone else’s interest on the forum. Thanks again Jean. Apologies for my confusion and thank you for your patience. :smiley:

 module.exports = {
    oscOutFilter: function(data) {

        var {address, args, host, port} = data

        // Assuming the push button's address is /control_sequence_1
        // and value is 1 (with no preArgs)
        if (address == '/control_sequence_1') { // process all values from this address
			if (args[0].value != 1) return // discard values different from 1
				setTimeout(()=>{
					send(host, port, '/control', 16, 114, 127)
					setTimeout(()=>{
						send(host, port, '/control', 16, 114, 0)
						setTimeout(()=>{
							send(host, port, '/control', 16, 114, 0)
							setTimeout(()=>{
								send(host, port, '/control', 16, 114, 1)
							}, 100)
						}, 100)
					}, 100)
				}, 100)

            return // ignore original message
        }
		
		if (address == '/control_sequence_2') { // process all values from this address
			if (args[0].value != 1) return // discard values different from 1
				setTimeout(()=>{
					send(host, port, '/control', 16, 114, 127)
					setTimeout(()=>{
						send(host, port, '/control', 16, 114, 0)
						setTimeout(()=>{
							send(host, port, '/control', 16, 114, 0)
							setTimeout(()=>{
								send(host, port, '/control', 16, 114, 2)
							}, 100)
						}, 100)
					}, 100)
				}, 100)

            return // ignore original message
        }

        return {address, args, host, port}

    }
}

P.S.

I didn’t realize about the DEBUG button… That helps me so much!!! I probably could’ve figured it out sooner if I realized this. Thanks! <3

The custom-module's send function does not interact with your interface, it dispatches the messages out of open stage control. I think that may be the root of your confusion.

Ok now here is a little extra, what we've written is really verbose and could use a cleaner syntax, we could declare a function at the beginning of the module and use it:

function controlSequence(host, port, address, preArgs, sequence) {

    var i = 0
    var timer = setInterval(()=>{
        send(host, port, '/control', ...preArgs, sequence[i])
        i += 1
        if (i == sequence.length) clearInterval(timer)
    }, 100)

}

module.exports = {
    
    oscOutFilter: function(data) {

        var {address, args, host, port} = data

        // Assuming the push button's address is /control_sequence_1
        // and value is 1 (with no preArgs)
        if (address == '/control_sequence_1') { // process all values from this address
            if (args[0].value == 1) controlSequence(host, port, '/control', [16, 114], [127, 0, 0, 1])
            return
        }

        if (address == '/control_sequence_2') { // process all values from this address
            if (args[0].value == 1) controlSequence(host, port, '/control', [16, 114], [127, 0, 0, 1])
            return
        }

        return {address, args, host, port}

    }
}

Now if you have a lot sequences to write, instead of writing each condition one by one (if (address == '/control_sequence_2')), you could declare all the routing in a single object and use it in a generic manner:

function controlSequence(host, port, address, preArgs, sequence) {

    var i = 0
    var timer = setInterval(()=>{
        send(host, port, '/control', ...preArgs, sequence[i])
        i += 1
        if (i == sequence.length) clearInterval(timer)
    }, 100)

}

var sequenceRouting = {
    // '/address': ['/midi_address', [preArgs], [sequence]]
    '/control_sequence_1': ['/control', [16, 114], [127, 0, 0, 1]],
    '/control_sequence_2': ['/control', [16, 114], [127, 0, 0, 2]],
    // etc
}

module.exports = {

    oscOutFilter: function(data) {

        var {address, args, host, port} = data

        if (sequenceRouting[address]) {
            if (args[0].value == 1) controlSequence(host, port, ...sequenceRouting[address])
            return
        }

        return {address, args, host, port}

    }
}
1 Like

This custom module (which I made, based on your last reply in this thread) regulates the time between different velocity values of the same MIDI note number.

Control Velocity Sequence - module.js (730 Bytes)

This is the code in the above file

function controlSequence(host, port, address, preArgs, sequence) {

var i = 0
var timer = setInterval(()=>{
    send(host, port, '/note', ...preArgs, sequence[i])
    i += 1
    if (i == sequence.length) clearInterval(timer)
}, 1000) // "1000" is the time interval between messages

}

module.exports = {

oscOutFilter: function(data) {

    var {address, args, host, port} = data

    if (address == '/switch_2') {
        if (args[0].value == 1) controlSequence(host, port, '/note', [2, 2], [127, 0, 0, 1])
        if (args[0].value == 2) controlSequence(host, port, '/note', [2, 3], [127, 0, 0, 1])
        return
    }
    return {address, args, host, port}

}

}

Is it possible to also regulate the time between two note numbers assigned to the same button of a switch widget? I think that in order to acomplish this (to regulate the time between two note numbers/messages), I need to create a sequence of note numbers, instead of velocity numbers (values). Can you give me a hint on how to create a sequence of “/note” numbers that can be used with the custom module?


What I am trying to acomplish (click me)

I want Cubase to execute the following commands:

  1. select a cycle marker (using a Project Logical Editor preset);
  2. set the left and right locators to that marker.

Problem:
Cubase has to search for the cycle marker name (in order to select it). If the search is not finised by the time the second command is executed, the locators will be set to the already selected marker.

So I need the second MIDI message (/note 2) to be sent with a little bit of delay.

image


This custom module works (below file), but in the OSC console I get a lot of lines. Isn’t there any other way of delaying the second MIDI message when it is triggered by a single switch button?

Control Velocity Sequence - module 2.js (3.7 KB)

This is the code in the above file

function controlSequence(host, port, address, preArgs, sequence) {

var i = 0
var timer = setInterval(()=>{
    send(host, port, '/note', ...preArgs, sequence[i])
    i += 1
    if (i == sequence.length) clearInterval(timer)
}, 300) // set here the time interval

}

module.exports = {

oscOutFilter: function(data) {

    var {address, args, host, port} = data

    if (address == '/switch_2') {
        if (args[0].value == 1) 
        controlSequence(host, port, '/note', [2, 3], [127, 0]) // select marker 1
        controlSequence(host, port, '/note', [2, 2], [0, 127]) // set locators

        if (args[0].value == 2) 
        controlSequence(host, port, '/note', [2, 4], [127, 0]) // select marker 2
        controlSequence(host, port, '/note', [2, 2], [0, 127]) // set locators

        if (args[0].value == 3) 
        controlSequence(host, port, '/note', [2, 5], [127, 0]) // select marker 3
        controlSequence(host, port, '/note', [2, 2], [0, 127]) // set locators

        if (args[0].value == 4) 
        controlSequence(host, port, '/note', [2, 6], [127, 0]) // select marker 4
        controlSequence(host, port, '/note', [2, 2], [0, 127]) // set locators

        if (args[0].value == 5) 
        controlSequence(host, port, '/note', [2, 7], [127, 0]) // select marker 5
        controlSequence(host, port, '/note', [2, 2], [0, 127]) // set locators

        if (args[0].value == 6) 
        controlSequence(host, port, '/note', [2, 8], [127, 0]) // select marker 6
        controlSequence(host, port, '/note', [2, 2], [0, 127]) // set locators

        if (args[0].value == 7) 
        controlSequence(host, port, '/note', [2, 9], [127, 0]) // select marker 7
        controlSequence(host, port, '/note', [2, 2], [0, 127]) // set locators

        if (args[0].value == 8) 
        controlSequence(host, port, '/note', [2, 10], [127, 0]) // select marker 8
        controlSequence(host, port, '/note', [2, 2], [0, 127]) // set locators

        if (args[0].value == 9) 
        controlSequence(host, port, '/note', [2, 11], [127, 0]) // select marker 9
        controlSequence(host, port, '/note', [2, 2], [0, 127]) // set locators

        if (args[0].value == 10) 
        controlSequence(host, port, '/note', [2, 12], [127, 0]) // select marker 10
        controlSequence(host, port, '/note', [2, 2], [0, 127]) // set locators

        if (args[0].value == 11) 
        controlSequence(host, port, '/note', [2, 13], [127, 0]) // select marker 11
        controlSequence(host, port, '/note', [2, 2], [0, 127]) // set locators

        if (args[0].value == 12) 
        controlSequence(host, port, '/note', [2, 14], [127, 0]) // select marker 12
        controlSequence(host, port, '/note', [2, 2], [0, 127]) // set locators

        if (args[0].value == 13) 
        controlSequence(host, port, '/note', [2, 15], [127, 0]) // select marker 13
        controlSequence(host, port, '/note', [2, 2], [0, 127]) // set locators

        if (args[0].value == 14) 
        controlSequence(host, port, '/note', [2, 16], [127, 0]) // select marker 14
        controlSequence(host, port, '/note', [2, 2], [0, 127]) // set locators

        if (args[0].value == 15) 
        controlSequence(host, port, '/note', [2, 17], [127, 0]) // select marker 15
        controlSequence(host, port, '/note', [2, 2], [0, 127]) // set locators

        if (args[0].value == 16) 
        controlSequence(host, port, '/note', [2, 18], [127, 0]) // select marker 16
        controlSequence(host, port, '/note', [2, 2], [0, 127]) // set locators
    }
    return {address, args, host, port}

}

}

Why not simply wrap the second controlSequence call in a setTimeout ?

if (args[0].value == 2) {
  setTimeout(()=>{
    controlSequence(host, port, '/note', [2, 3], [127, 0, 0, 1])
  })
}

Be careful with this :

if (args[0].value == 7) 
    controlSequence(host, port, '/note', [2, 9], [127, 0]) // select marker 7
    controlSequence(host, port, '/note', [2, 2], [0, 127]) // set locators

When not using brackets after a condition, only the first statement's execution depends on the condition, the other is executed regardless of the test result.

If the debug option is on, you'll get a line printed for each message sent or received, that's what this option is for.

Here is the new .js file (in which I added brackets, as advised):
Control Velocity Sequence - module 2.js (4.2 KB)

Here is a video I've made, describing the functionality of the above .js file (of the custom module):

I meant to say that I get more lines than expected.

I don't know. Apart from the harmless error I get (which can be seen in the video above), everything works perfectly! Thank you!

One more thing. How can I also reply with colored code?

The error you mention is due to the switch's original message being passed to the midi converter (/switch_1 is not a valid MIDI message type), you can avoid that by adding a return statement at the end of your if (address == '/switch_2') conditionnal block.

I meant to say that I get more lines than expected.

The aforementioned lack of brackets was probably -- at least partly -- causing this.

One more thing. How can I also reply with colored code?

```javascript
code goes here
```

1 Like

If I add a return statement at the end of the "if (address == '/switch_2')" conditionnal block, then the OSC console shows nothing and Cubase receives nothing.


Code (which has the return statement inserted after the conditionnal block)
module.exports = {
    
    oscOutFilter: function(data) {

        var {address, args, host, port} = data

        if (address == '/switch_2') return
        {
            if (args[0].value == 1) 
            {
            controlSequence(host, port, '/note', [2, 3], [127, 0]) // select marker 1
            controlSequence(host, port, '/note', [2, 2], [0, 127]) // set locators
            }

            if (args[0].value == 2) 
            {
            controlSequence(host, port, '/note', [2, 4], [127, 0]) // select marker 2
            controlSequence(host, port, '/note', [2, 2], [0, 127]) // set locators
            }
etc

Because the custom module is now functional and outputs a lot less lines than before (when it didn't contained those brackets that you told me I should use), I don't mind having that error lying around. However, if you are this kind, I'll be happy to try other solutions you give me.

Thank you!

I meant the return as the last statement in the block

if (...) {
  // do this
  // do that
  return
}