Send a MIDI message without user interaction (using a custom module)

What I've tried:

Custom module ā€“ code ā€“


module.exports = {
    oscOutFilter: function(data) {
        var {address, args, host, port} = data
            if (address == '/outAdd') {
            console.log('hi')
            return
                  }
                  return {address, args, host, port}
                  },

Widget script

// Sending an osc message with the send() function.
// Isn't this suppossed to work?
send("127.0.0.1:8080","/outAdd",{type: "i", value: str})

When 1 in the text script changes to 2, I would expect the launcher console to show "hi". It doesn't though....

send() won't do anything without a user interaction, you have to handle the reply logic in the custom module's oscInFilter function (filter incoming message and send a reply accordingly).

1 Like

You said here that: the usual way to trigger something in the custom module with a widget is
to send an osc message, and catch it in the custom module's oscOutFilter function.

In the documentation (screenshot)


oscInFilter in my custom module looks like this:


    oscInFilter: function(data) {

        if (data.host === 'midi' && data.port === 'SKiano-C') {
            
            var [channel, control, value] = data.args.map(x=>x.value)
            
            if (control > 63 && control < 74) {
                
                var digit = 73 - control,
                    msb = value >> 4,
                    val = value & 0xF
                
                if (msb >> 2) val += '.'
                if (!(msb & (1 << 1))) val = ''

                // digit -> digit
                // val -> digit's value 
                
                // assuming 10 text widgets with incrementing preArgs 
                receive('/timecode', digit, val)

                return
                
            }
            
        }


        return data
        
    }
    
}


Maybe I need to understand how this code works with the 10 text widgets.
I'll be back when I figure it out.

Yes, the usual use case is to manipulate widgets and output messages and receive message to update the widgets' state (feedback from controlled softwares). Replying to incoming messages from the custom module is the shortest way to go and it does make it harder to create infinite feedback loops (which otherwise cannot be done in OSC, that's a feature :))

I don't understand something.

You said:

Is :)) a laughing emoticon or a simple smiley and a closing paranthesis? :stuck_out_tongue:


I'll be back :sunglasses:

image

A simple smiley :)

1 Like

I've analyzed the code a bit and wrote new comments. Can you please check to see if my observations are correct?

Custom module code. Input conditional with comments.
        // This is the input source (easily understood)
        if (data.host === 'midi' && data.port === 'SKiano-C') {

            // Can you please tell me what is this line based on? (so that I don't look up the wrong thing)
            var [channel, control, value] = data.args.map(x=>x.value)
            
            // conditional statement (understood)
            if (control > 63 && control < 74) {
                
                // three variables are declared (digit, msb and val)
                var digit = 73 - control, // "digit" will be the difference between the number 73 and the value of "control"
                    msb = value >> 4, // "msb" will be the number resulted from shifting the value of "msb" 4 bits to the right
                    val = value & 0xF // if "value" = 0xF, "val" will be equal to either one (either value, or 0xF)
                                      // if "value" and 0xF are not equal between each other, depending on the difference nature (size or bits number), bits will be shifted left or right or, "val" will be equal to only one of the two (either value, or 0xF). 
                
                // if "msb" (most significant byte) is shifted to the right 2 bits, then concatenate "val" and ".".
                if (msb >> 2) val += '.'

                // set "val" to undefined if the expression is false
                if (!(msb & (1 << 1))) val = ''

                // digit -> digit (not my comment)
                // val -> digit's value (not my comment)
                
                // assuming 10 text widgets with incrementing preArgs (not my comment)
                receive('/timecode', digit, val)
                return
                
            }
            
        }

args contain the values in the osc/midi message, each value being an object with two keys (value and type). This assumes the message is a control change and it would be safer to check for address === '/control' in the conditional statement before (I think it was the case in the original code).

What happens in quoted line:

  • data.args is converted using map to a simple value array (where each item x is replaced with x.value)
  • the array's values are assigned to the variables on the left using destructuring assignement

The rest with msb and lsb is just an implementation of the mackie protocol (https://github.com/NicoG60/OscMackieControl/blob/master/doc/MackieControlProtocol_EN.pdf, page 7), it looks like magic and it's exactly what I hate about MIDI. To make it simple, control changes 64 to 73 convey the values of one digit each with an optional dot.

// if "msb" (most significant byte) is shifted to the right 2 bits, then concatenate "val" and ".".
// set "val" to undefined if the expression is false

The condition is "if expression is truthy" (Truthy - MDN Web Docs Glossary: Definitions of Web-related terms | MDN), for instance "if shifted msb is different than 0". Bitwise operations (>>, <<, &) are used to read particular bits in the (see linked pdf).

1 Like
Reply to the previous post

Here, the code is the same (i.e. the conditional statement for /control is placed after, not before, as you're suggesting). But even so, text widgets used for displaying bars and beats info work as expected. So what's all this fuss about? (trying to make a joke :stuck_out_tongue: )

Launcher console log that confirmes what you're saying.
// When locating the cursor in Cubase from bar 278 to bar 314

(DEBUG, MIDI) in: CONTROL_CHANGE: channel=16, cc=64, value=48 From: midi:SKiano-C // midi IN
(DEBUG, OSC) In:  { address: '/timecode', args: [ 9, 0 ] } From: undefined:undefined // osc IN

(DEBUG, MIDI) in: CONTROL_CHANGE: channel=16, cc=65, value=32 From: midi:SKiano-C
(DEBUG, OSC) In:  { address: '/timecode', args: [ 8, 0 ] } From: undefined:undefined

(DEBUG, MIDI) in: CONTROL_CHANGE: channel=16, cc=66, value=32 From: midi:SKiano-C
(DEBUG, OSC) In:  { address: '/timecode', args: [ 7, 0 ] } From: undefined:undefined

(DEBUG, MIDI) in: CONTROL_CHANGE: channel=16, cc=67, value=113 From: midi:SKiano-C
(DEBUG, OSC) In:  { address: '/timecode', args: [ 6, '1.' ] } From: undefined:undefined

(DEBUG, MIDI) in: CONTROL_CHANGE: channel=16, cc=68, value=32 From: midi:SKiano-C
(DEBUG, OSC) In:  { address: '/timecode', args: [ 5, 0 ] } From: undefined:undefined

(DEBUG, MIDI) in: CONTROL_CHANGE: channel=16, cc=69, value=113 From: midi:SKiano-C
(DEBUG, OSC) In:  { address: '/timecode', args: [ 4, '1.' ] } From: undefined:undefined

(DEBUG, MIDI) in: CONTROL_CHANGE: channel=16, cc=70, value=32 From: midi:SKiano-C
(DEBUG, OSC) In:  { address: '/timecode', args: [ 3, 0 ] } From: undefined:undefined

(DEBUG, MIDI) in: CONTROL_CHANGE: channel=16, cc=71, value=116 From: midi:SKiano-C
(DEBUG, OSC) In:  { address: '/timecode', args: [ 2, '4.' ] } From: undefined:undefined

(DEBUG, MIDI) in: CONTROL_CHANGE: channel=16, cc=72, value=49 From: midi:SKiano-C
(DEBUG, OSC) In:  { address: '/timecode', args: [ 1, 1 ] } From: undefined:undefined

(DEBUG, MIDI) in: CONTROL_CHANGE: channel=16, cc=73, value=51 From: midi:SKiano-C
(DEBUG, OSC) In:  { address: '/timecode', args: [ 0, 3 ] } From: undefined:undefined

You have to stop. I don't deserve such valuable information. :stuck_out_tongue:


What I've come up with (extrapolating what you've told me here):

// each time the cursor locates to a different bar number, the console.log() function will be executed
if (data.args[1].value === 71) console.log('ALERT! The cursor in Cubase has been relocated! I\'d really like to tell you where, but I don\'t speak OSC\. Please ask jean-emmanuel (or some other person to whom you are not worthy to speak) how to do this. Your beloved console.')

Now I need to find a way to access the GUI and get the values of three widgets (the first three digits), then concatenate them. Ids of the widgets are: digit_1, digit_2, digit_3. Hmmm... is this possible?

It is possible, no need to access the gui actually, that's the point using the custom module:

// stored digits
var DIGITS = ['', '', '', '', '', '', '', '', '', '']

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

        if (data.host === 'midi' && data.port === 'SKiano-C') {
            
            var [channel, control, value] = data.args.map(x=>x.value)
            
            if (control > 63 && control < 74) {
                
                var digit = 73 - control,
                    msb = value >> 4,
                    val = value & 0xF
                
                if (msb >> 2) val += '.'
                if (!(msb & (1 << 1))) val = ''

                // digit -> digit
                // val -> digit's value 
                
                // assuming 10 text widgets with incrementing preArgs 
                receive('/timecode', digit, val)

                // store digits
                DIGITS[digit] = val
                // then do anything you need with DIGIT
                // for example
                var first3Digits = DIGITS.slice(0,3).join('')
                console.log(first3Digits)

                return
                
            }
            
        }


        return data
        
    }
    
}
1 Like
// store digits
                DIGITS[digit] = val
                // then do anything you need with DIGIT

                // for example
                var first3Digits = DIGITS.slice(0,3).join('')
                // I've added this line so that the bar number gets changed to a number that doesn't have a dot and zeros at the beginning
                var cleanedVal = parseInt(first3Digits.replace(/\./g,""))
                if (data.args[1].value === 71) console.log(cleanedVal)
                return
GIF

bad reply on every new ten

Can you give me a hint on why is this happening? (see the GIF)
Also, you've said that I don't need to access the GUI in order to get the value of the digit widgets.
But what if at some point I need to get a value from the GUI? Will I be able to get it? Again, without user interaction.

I'm currently studying this post, but I'm afraid that the method you described there requires user interaction...

Sorry I'm asking so many questions! Thank you.

EDIT
It's ok if there's no solution (or you don't have the time to suggest one).
If you'd like, I'll post my workaround code (the "wings" are not where they are supposed to be, but... I've brought the functionality to a satisfying degree ā€“ more or less anyway!).

I think its only due to the fact you're printing when digit nĀ° 3 updates (if (data.args[1].value === 71)), digit nĀ°2 updates too but shortly after. Just wait for the last digit to update (=== 73).

But what if at some point I need to get a value from the GUI? Will I be able to get it? Again, without user interaction.

You can send a remote control command to the interface (using receive) that force a widget to send its value (and catch it in oscOutFilter).

1 Like