Tutorial - OSC with Expression Maps in Cubase using Mackie Protocol

Try to delete this part of the code and see if it works. I also noticed if you have midi and instrument tracks it can become a little scrambled.

Hi Mike,

thank you for your help but that didnt work. The Indication in the Brackets [CSSS] for example shows
up in the tracknames on the OSC device.

Hi Sub3OneDay,

now i got everything running so far. The only thing I can't get right is the: vstiframeNum from your script.

What OSC listener do I have to enter where? Or how can I open a panel with it?

Thank you very much for your help so far

I noticed that in this script there is a variable colorArt that is not used in the future. I have added a little script to output colors from expression map to buttons in OSC.

//define the variables for the track name function
var trackName = ""
var pos = 72
var rootPosLCD = 72
var keepStringVal = 0
var trackNameJoined = ""
var bufferTrackName = ""
var posBuffer = 72

const fs = nativeRequire('fs');
const xml2js = nativeRequire('xml2js');
const glob = nativeRequire('glob');

//set a variable for the midi port of your generic remote


var midiPort_MCU_From_OSC = 'MCU_From_OSC' //this is the MCU port you've set up in cubase to send and recieve MCU messages
var midiPort_MCU_To_OSC = 'MCU_To_OSC' //this is the MCU port you've set up in cubase to send and recieve MCU messages

//Create variables for each of the OSC watchers in the articulation buttons you have created on your template

//Set the path to your expression Maps


var mapFiles = glob.sync ('/Users/Sunshineman/YandexDisk/ExpressionMaps/Chamber Strings/PrCh/*.expressionmap')

//set the adress in OSC of the expression map label
var expMapLabel = { 'ExpMap': '/ExpMap/label' }


//Set the address of the library buttons
var artButtons = {
    'b1': '/b1/show', 'b2': '/b2/show', 'b3': '/b3/show', 'b4': '/b4/show', 'b5': '/b5/show',
    'b6': '/b6/show', 'b7': '/b7/show', 'b8': '/b8/show', 'b9': '/b9/show', 'b10': '/b10/show',
    'b11': '/b11/show', 'b12': '/b12/show', 'b13': '/b13/show', 'b14': '/b14/show', 'b15': '/b15/show',
    'b16': '/b16/show', 'b17': '/b17/show', 'b18': '/b18/show', 'b19': '/b19/show', 'b20': '/b20/show',
    'b21': '/b21/show', 'b22': '/b22/show', 'b23': '/b23/show', 'b24': '/b24/show', 'b25': '/b25/show',
    'b26': '/b26/show', 'b27': '/b27/show', 'b28': '/b28/show', 'b29': '/b29/show', 'b30': '/b30/show',
    'b31': '/b31/show', 'b32': '/b32/show', 'b33': '/b33/show', 'b34': '/b34/show', 'b35': '/b35/show',
    'b36': '/b36/show', 'b37': '/b37/show', 'b38': '/b38/show', 'b39': '/b39/show', 'b40': '/b40/show',
    'b41': '/b41/show', 'b42': '/b42/show', 'b43': '/b43/show', 'b44': '/b44/show', 'b45': '/b45/show',
    'b46': '/b46/show', 'b47': '/b47/show', 'b48': '/b48/show', 'b49': '/b49/show', 'b50': '/b50/show',
    'b51': '/b51/show', 'b52': '/b52/show', 'b53': '/b53/show', 'b54': '/b54/show', 'b55': '/b55/show',
    'b56': '/b56/show', 'b57': '/b57/show', 'b58': '/b58/show', 'b59': '/b59/show', 'b60': '/b60/show' 
}

// These correspond to the label within OSC #{OSC{/b1/label, , false}}

// Set The variable when populated is the actual text on the button
var artLabels = {
    'b1': '/b1/label', 'b2': '/b2/label', 'b3': '/b3/label', 'b4': '/b4/label', 'b5': '/b5/label',
    'b6': '/b6/label', 'b7': '/b7/label', 'b8': '/b8/label', 'b9': '/b9/label', 'b10': '/b10/label',
    'b11': '/b11/label', 'b12': '/b12/label', 'b13': '/b13/label', 'b14': '/b14/label', 'b15': '/b15/label',
    'b16': '/b16/label', 'b17': '/b17/label', 'b18': '/b18/label', 'b19': '/b19/label', 'b20': '/b20/label',
    'b21': '/b21/label', 'b22': '/b22/label', 'b23': '/b23/label', 'b24': '/b24/label', 'b25': '/b25/label',
    'b26': '/b26/label', 'b27': '/b27/label', 'b28': '/b28/label', 'b29': '/b29/label', 'b30': '/b30/label',
    'b31': '/b31/label', 'b32': '/b32/label', 'b33': '/b33/label', 'b34': '/b34/label', 'b35': '/b35/label',
    'b36': '/b36/label', 'b37': '/b37/label', 'b38': '/b38/label', 'b39': '/b39/label', 'b40': '/b40/label',
    'b41': '/b41/label', 'b42': '/b42/label', 'b43': '/b43/label', 'b44': '/b44/label', 'b45': '/b45/label',
    'b46': '/b46/label', 'b47': '/b47/label', 'b48': '/b48/label', 'b49': '/b49/label', 'b50': '/b50/label',
    'b51': '/b51/label', 'b52': '/b52/label', 'b53': '/b53/label', 'b54': '/b54/label', 'b55': '/b55/label',
    'b56': '/b56/label', 'b57': '/b57/label', 'b58': '/b58/label', 'b59': '/b59/label', 'b60': '/b60/label',

}


var artColorNames = {
    'b1': '/b1/color', 'b2': '/b2/color', 'b3': '/b3/color', 'b4': '/b4/color', 'b5': '/b5/color',
    'b6': '/b6/color', 'b7': '/b7/color', 'b8': '/b8/color', 'b9': '/b9/color', 'b10': '/b10/color',
    'b11': '/b11/color', 'b12': '/b12/color', 'b13': '/b13/color', 'b14': '/b14/color', 'b15': '/b15/color',
    'b16': '/b16/color', 'b17': '/b17/color', 'b18': '/b18/color', 'b19': '/b19/color', 'b20': '/b20/color',
    'b21': '/b21/color', 'b22': '/b22/color', 'b23': '/b23/color', 'b24': '/b24/color', 'b25': '/b25/color',
    'b26': '/b26/color', 'b27': '/b27/color', 'b28': '/b28/color', 'b29': '/b29/color', 'b30': '/b30/color',
    'b31': '/b31/color', 'b32': '/b32/color', 'b33': '/b33/color', 'b34': '/b34/color', 'b35': '/b35/color',
    'b36': '/b36/color', 'b37': '/b37/color', 'b38': '/b38/color', 'b39': '/b39/color', 'b40': '/b40/color',
    'b41': '/b41/color', 'b42': '/b42/color', 'b43': '/b43/color', 'b44': '/b44/color', 'b45': '/b45/color',
    'b46': '/b46/color', 'b47': '/b47/color', 'b48': '/b48/color', 'b49': '/b49/color', 'b50': '/b50/color',
    'b51': '/b51/color', 'b52': '/b52/color', 'b53': '/b53/color', 'b54': '/b54/color', 'b55': '/b55/color',
    'b56': '/b56/color', 'b57': '/b57/color', 'b58': '/b58/color', 'b59': '/b59/color', 'b60': '/b60/color',
}



send('midi', midiPort_MCU_From_OSC, '/note', 1, 44, 127);  //Send MCU command to swicth to track name sending


module.exports = {

    oscInFilter: function (data) {   // this filters data from Cubase or midi device into OSC

        console.log('message recieved into custom module')


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

        mcuToOsc(host, port, address, args)

        return { address, args, host, port }


    },

    oscOutFilter: function (data) {         // Filter incomming osc messages from OSC and gets them out to Cubase

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

        if (address == '/testAddress') {

            console.log('You have sent a message to custom module from OSC')

        }

        if (address == '/resetMCU') {

            console.log('MCU Reset message sent')
            send('midi', midiPort_MCU_From_OSC, '/note', 1, 44, 127);  //Send MCU command to swicth to track name sending

        }

        return data  //End of OSC out Filter here
    }
}


//These are your functions that do the work in JS

function mcuToOsc(host, port, address, args) {

    if (host !== 'midi' || port !== midiPort_MCU_To_OSC) return


    var inArgs = args.map(x => x.value),
        outArgs = [],
        action = ''
    // SYSEX
    if (address === '/sysex') {

        var [value] = inArgs

        if (value.includes("f0 00 00 66 14 12")) { // mackie lcd text < this is the sysex identifier for the track name
            console.log(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>sysex value recieved")
            let sysExVal = args[0].value

            //Use the function getTrackName to do all the cool stuff to get the fullTrackName
            let fullTrackName = getTrackName(sysExVal)

            console.log("You have Grabbed the track Name using the function getTrackName   = " + fullTrackName)

            //now you need to Send the Full Track Name to OSC Panel using another function
            sendTrackName(fullTrackName)


            //Populate the Map using the below but make sure your track names follow the rules on expression map tagging
            //limitatation - track tag has to be in the last 6 characters of the name and must be bounded by []

            //Strip out the trackMapTag from the full name
            //To avoid complications also strip off the square brackets after you've pulled them into the OSC world

            trackMapTag = (fullTrackName.substring(fullTrackName.length - 6));
            trackMapTag = trackMapTag.replace('[', '')
            trackMapTag = trackMapTag.replace(']', '')

            console.log("Track TAG = " + trackMapTag)


            // Build the articulation map and populate the buttons on the expression map
            buildMap(trackMapTag)
        }
    }
}

function getTrackName(sysExVal) {

    var nameDone = false


        var d = sysExVal.split(" ").slice(6).map(x => parseInt(x, 16)) //this devides up using  .split, then slices off the front 6 elements,  and x => parseInt(x, 16) converts the rest from Hex to Int then .map creates the array d[]
                                                                        
         pos = d[0] // first byte -> position // hex to int

        console.log("position = " + pos)
        //console.log("sysExVal = " + sysExVal)
        text = d.slice(1).map(x => String.fromCharCode(x))// these are the new characters which are updated on the console, the rest -> updated characters
        if (pos < 29) {

            return trackNameJoined
        }
  
    text.pop() // drop sysex closing byte                    
    trackName = text.join('')

        //MCU only sends what it needs so you need to buffer the previous name and use parts of that
        //Check the length of the new name vs the buffer
        nameLengthCheck = bufferTrackName.length - trackName.length

        //MCU sends some sysex data to tell the screen where to start showing the new characters
        //This is related to the root position on the screen which is 72

        //Check if root position matches the position where the characters are to be placed
        charFromStart = pos - rootPosLCD

        var lengthCheck = charFromStart + trackName.length

    if (lengthCheck < 29) {

        let newEndLength = 29 - charFromStart - trackName.length

        newEnd = bufferTrackName.substring(bufferTrackName.length - newEndLength)

    } else { newEnd = "" }

    if (pos == 72) {       //Full length name received

        trackNameJoined = trackName + newEnd
        bufferTrackName = trackNameJoined
        posBuffer = pos
        // console.log('TrackName Joined = ' + trackNameJoined)

        nameDone = true    

    } else if (pos > posBuffer && posBuffer == 72 && nameDone == false) {

        keepStringVal = pos - posBuffer  //new name follows a full string text

        let prevTrackKeep = bufferTrackName.substring(0, keepStringVal)

        trackNameJoined = prevTrackKeep + trackName + newEnd
        bufferTrackName = trackNameJoined
        posBuffer = pos
        nameDone = true


    } else {

        keepStringVal = pos - rootPosLCD  //new name follows a full string text

        var prevTrackKeep = bufferTrackName.substring(0, keepStringVal)

        trackNameJoined = prevTrackKeep + trackName + newEnd
        bufferTrackName = trackNameJoined
        posBuffer = pos
        nameDone = true

    }

        //MCU will sometimes send the characters (MIDI) as part of the track name
        //so you need to strip these out
    const findMidiTag = '(';

    var posMidiTag = trackNameJoined.search("\\(M");
    var posBrackTag = trackNameJoined.search("\\(");

        if (posBrackTag == trackNameJoined.length - 1) {


            trackNameJoined = trackNameJoined.substring(0, (posBrackTag - 1))


        }


        if (posMidiTag > -1) {

            trackNameJoined = trackNameJoined.substring(0, (posMidiTag - 1))

        }

        //trim off all the spaces at the end

        trackNameJoined = trackNameJoined.trimEnd();


        return trackNameJoined

        }

function sendTrackName(fullTrackName) {

    //set the address in OSC of the full Name Label
    var trackNameLabel = { 'trackName': '/trackName/Label' }

    console.log("full trackName = " + fullTrackName )
    receiveOsc({
        address: Object.values(trackNameLabel), //this populates the labels on the buttons based on the array
        args: [
            { type: 's', value: fullTrackName }
        ]
    })

    return
}

// Get the articulations  from the *.xml expression maps in cubase   

async function buildMap(trackMapTag) {
    const artArr = [];
    const artColor = [];
    
    for (const mapFile of mapFiles) {

        let mapName = "Not Defined"
        console.log('Selected Map:  ' + mapFile);

        if (mapFile.includes(trackMapTag)) { //trackID comes from sysex track naming routine  

            const parser = new xml2js.Parser({
                explicitArray: false,
                mergeAttrs: true
            }); // 'mergeAttrs: true' was essential
            //console.log('Selected Map:  ' + mapFile);
            const data = await fs.promises.readFile(mapFile);
            const result = await parser.parseStringPromise(data);
            
            mapName = result.InstrumentMap.string.value
            console.log('>>>>>>>>>>>>>>>>>>>>>>>>>>>>Map Name = ' + mapName)
            const art = result.InstrumentMap.member[1].list.obj;
            // NOTE: this code assumes that this part only ever runs for
            // one mapFile, that all the others don't match the trackID test
            // because if you run this more than once, it will just
            // overwrite the members of artArr and artColor

            // console.log(colorName)
            //Add label to Expression Map Label

            addExpMapLabel(mapName)
            
            for (let i = 0, len = art.length; i < len; i++) {

                artArr[i] = art[i].member[1].string.value
                artColor[i] = art[i].int.value
                // console.log(artArr[i])
                // console.log(artColor[i])
            }

            //Now call the function to label the map template you have created in OSC

            addExpBtnLabels(artArr)
            addExpBtnColors(artColor)
               
            break;
        }
    }
    return { artArr, artColor };
}

//Adds Lable to the Expression label widget /ExpMap/label 
//called from function labelMap



//Adds Label to the expression map buttons called from function labelMap - this is set up to have a grid of 64 buttons

function addExpBtnLabels(artArr) {

    var artButtonGrid = artArr.length //number of buttons based on number of articulations in the map

    //Populate the lables on the grid
    for (i = 0; i < artButtonGrid; i++) {

        var artText = artArr[i] //collects each label from the object labeltexts that has just been created at position [i]
    
        receiveOsc({
            address: Object.values(artButtons)[i], //this controls the visibility of the buttons
            args: [
                { type: 'i', value: 1 }
            ]
        })

        receiveOsc({
            address: Object.values(artLabels)[i], //this populates the labels on the buttons based on the array
            args: [
                { type: 's', value: artText }
            ]
        })

    }

    //Remove remaining buttons on articulation grid
    for (i = artButtonGrid; i < 60; i++) { // amend the 64 to the number of buttons you have in total on your grid
        receiveOsc({
            address: Object.values(artButtons)[i], //continues through the remaining buttons and hides them
            args: [
                { type: 'i', value: 0 }
            ]
        })

    }

}

function addExpBtnColors(artColor) {

    const cubaseArtColorKeys = {
        1: '#ff081d', 
        2: '#ff6d00',
        3: '#ffd700',
        4: '#efec53',
        5: '#ccff00',
        6: '#66ff00',
        7: '#00ffa1',
        8: '#7fffd4',
        9: '#00ffff',
        10: '#1560bd',
        11: '#6a5acd',
        12: '#6600ff',
        13: '#8b00ff',
        14: '#cd00cd',
        15: '#ff01ff',
        16: '#ff1493'
        }
        
    const cubaseArtColors = artColor.map(item => cubaseArtColorKeys[item])
    let artColorGrid = cubaseArtColors.length 

    //Populate the colors on the grid
    for (i = 0; i < artColorGrid; i++) {

        let artColorText = cubaseArtColors[i] 
        

        receiveOsc({
            address: Object.values(artColorNames)[i], 
            args: [
                { type: 's', value: artColorText }
            ]
        })
    
    }


}

function addExpMapLabel(mapName) {

  receiveOsc({
      address: Object.values(expMapLabel), //this populates the labels abd colors on the buttons based on the array
      args: [
          { type: 's', value: mapName }
      ]
  })

return

}



Screenshot 2023-02-08 at 20.15.53

2 Likes

As a first time user this look freakin scary !
Let's breath and try to implement that !

what is the difference about this and this THERE IT IS: the ultimate custom module for cubase users (at least for me) - #176 by StephanS

The other thread use HUI protocol which limit the articulation shown to 18 or something like that ?

Hi, I got an error upon activation of the custom module

hi, I hope you are having a good time.
did you fix the name thing issue ?
I'm having the same issue with audio and instrument tracks , and the dummy midi track is kind of a bummer. i created a project logical editor macro for that right next to my exp map menu in OSC UI !