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?

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