Tutorial - OSC with Expression Maps in Cubase using Mackie Protocol

Thank you very much for your hard work. Just to clarify as I know nothing about scripting. Should the light gray text in your script be removed?

Ex )

Those are comments - you can leave them there as // means they are ignored

1 Like

I had the track name displaying in the text widget. But now I’m getting this error

I tried to command prompt npm install xmlj2s again but that didn’t fix the issue. Now I’m lost again

So I managed to get it working based on a modified script posted over at VI control.

Just wondering if it’s possible to set a command to hide the empty articulation buttons but keep the used articulations in the same position?

image

Hi All,
is it possible to help me.

I have set up everything like in the tutorial. i have 32 Buttons and have changed the number in the custom modul.
I have only 1 expression map in my folder to test out if everything is working but i get an error message and no articulations are showing on the buttons.

//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 genericRemoteMIDIport = 'Art'; //BASE MIDI PORT FOR showing all tracks in cubase
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('C:/Users/Stephan/Open Stage Control/Custom Modules/Expression Maps/*.expressionmap')

//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',

}



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 recieved

        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

        var 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("\\(");
    var trackNameJoinedTrim = ""


    if (posBrackTag == trackNameJoined.length - 1) {
        trackNameJoinedTrim = trackNameJoined.substring(0, (posBrackTag - 1))
        trackNameJoined = trackNameJoinedTrim
    }

    if (posMidiTag > -1) {

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

        trackNameJoined = trackNameJoinedTrim

    }

    //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);
            //let art = await result.InstrumentMap.member[1].list.obj;
            //

            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

            //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
            }

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

            addExpBtnLabels(artArr)

            break;
        }
    }
    return { artArr, artColor };
}

//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 < 32; 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 }
            ]
        })

    }

}

Thank you very much for your help

The function addExpMapLabel(mapName) defintion is missing from the script. That's why the error is happening.

The original post is missing this function which is why the error is thrown. I found the original function from this post.

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

function addExpMapLabel(mapName) {

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

return

}

Cheers,
DMDComposer

Hi DMD, thank you a lot, but were in the script must it be?
i get now an error:
(ERROR) A JavaScript error occurred in the main process:
UnhandledPromiseRejection: This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). The promise rejected with the reason "ReferenceError: expMapLabel is not defined".

Thank you for your help

You can add the function to the end of the script you provided.

You can also try adding a tryCatch at line 302 I believe, addExpMapLabel(mapName)

You can replace that line with this.

try {
    addExpMapLabel(mapName)
} catch (error) {
    console.log(error)
}

That might help get to the bottom of the error, and the least, let the app still run without throwing a halting error.

Cheers,
DMDComposer

Thanks for spotting this. I've been away from OSC for a while as have been busy but will see if I can amend the script and also put a simple sample set of files up so that people can get up and running quickly

2 Likes

Thx for your help DMD and Sub3OneDay :slight_smile:

It work with the bit of code.

2 Likes

Ok now i have worked out the Custome Modul with the expression maps and so on but i have a other
problem now:

I have hanging Channel Names on my OSC Device. It is sending the sysex data and shows the data
correct on the first characters but then ther is hanging stuff on the end of the Text. Can some one tell my why and how can i get it fixed because on the end is my [Indicator]


Can you log to the console exactly what is being sent to OSC in the sysex each time from the MCU? MCU only sends the characters it needs to replace.

Hi Sub3OneDay,

yes of course. I have make a screenshot from the sysex section

ok unfortunately I can not understand the problem. The text widget is labeled correctly and no other midi channel sparks into the MCU to OSC channel. Does anyone have the same problem or a solution to what it may be? I have no error in the console.And when i reload the custom module the first selection in cubase is displayed correctly and then the rest stays at the end of the next selection.

//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 genericRemoteMIDIport = 'Art'; //BASE MIDI PORT FOR showing all tracks in cubase
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 ('C:/Users/Stephan/Open Stage Control/Custom Modules/Expression Maps/*.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', 
}

// 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',

}



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

        var 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);
            //let art = await result.InstrumentMap.member[1].list.obj;
            //

            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

            //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
            }

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

            addExpBtnLabels(artArr)

            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 < 32; 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 addExpMapLabel(mapName) {

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

return

}

i just noticed that with a midi track it works directly but an instrument track or audio track, it saves the last characters and then it doesn't work anymore to show the right trackname. This is the screenshot from the console:

So when MCU sends midi track names it also (if there is space) sends the characters (MIDI) at the end. It doesn’t send these with instrument or audio tracks - there could be a curiosity there.

As a workaround I have a single dummy track with random letters in the name which I occasionally select to send a full set of characters to the interface to clear whatever is there before. That might help?

Ok i will check that tomorrow and give you the answer if that works. But is there no other workaround like:

If the track has no midi in it don't erase the last characters or something in the custom Modul.

Because it's a bit useless when I select an instrument track and after an audio track and then the name on osc is crap.

Thank you for your help :+1:

Hi Sub3OneDay,

i have checked all and it works with a dummy midi track with random characters in it.
But is there a opportunity to hide the brackets and the trackmaptag on the fullTrackName?

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.