Tutorial - OSC with Expression Maps in Cubase using Mackie Protocol

Hi All,

I spent a lot of time last year working out (with the help of @Sylvain) on how to use Mackie protocol to update expression maps in OSC and use the custom module to keep them up to date automatically. To be clear, this is without the need to add plugins to your tracks or midisends, and can be used on any type of cubase track as long as you follow some simple track naming rules and keep your track names limited to below 29 characters.

Some Extracts from My OSC Panel

I'm still getting a lot of questions offline that ask me how I've set all this up in my cubase template so I thought I'd write a quick tutorial...

So here goes:

  1. I assume you’re using Cubase.

  2. Set up some expression maps? You need to make sure that they use program change to work,
    Not note key switches - that way you can standardise OSC output messages and do any the translation in the map in cubase.

  3. OSC Buttons widgets need to be set to "tap".

  4. You use my method you’ll need to have OSC set up to receive Mackie Control messages and also sysex, so make sure your midi settings are as follows:
    image

  5. Create 2 new loopMIDI ports, one to receive the articulation control and one to send and receive the mackie sysex.

  6. install MS Visual studio onto your machine so that you can write the custom module easily

  7. Install node.js and npm Here's how to do it

  8. When you have installed these basic bits you also need to install some additional nodes so that you can read the xml and watch for changes to expression maps in your custom module.
    You will need xml2js and chokidar
    To do this you will need to put these modules into your custom modules directory so you need to create a directory
    C:\Users\YourUserName\Open Stage Control\Custom Modules

  9. Then open a command prompt from that directory and type npm install xml2js
    more info here: xml2js node

  10. do the same with chokidar npm install chokidar

  11. So now it's time to create a custom module.
    First create a blank .js document and name it something useful like basicCM.js and add the following into it:

When you have got that working amend your custom module like this.

When you have got that working amend your custom module like this.

JavaScript:

First Custom Module for testing
//set a variable for the midi port of your generic remote

var genericRemoteMIDIport = 'OSCMIDICONTROL'; //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

//MIDI PORTS FOR FUNCTIONS FROM OSC



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('MIDI message recieved into OSC from Cubase')
        

        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')

        }

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


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

function mcuToOsc(host, port, address, args) {
    console.log('fired')
    if (host !== 'midi' || port !== midiPort_MCU_To_OSC) return

        // SYSEX
    if (address === '/sysex') {

        console.log('You have successfully sent sysex into your custom module')
    }



}
  1. Now create a blank OSC canvas and create a button. You should set its property to tap.
    This is just a test at the moment to check everything is working as it should.

  2. Go into the address and change it from auto to /testAddress then save the panel.

  3. Then open cubase and set up a couple of tracks with unique names.

  4. In the cubase MCU settings you should set those to your MCU virtual ports.

You don’t need to connect you cubase tracks to anything other than an instrument or vsti etc.

  1. Stop the OSC server and start it again.

  2. Now you should get messages in your console when you either tap the button or change tracks in cubase

  3. Now you have to get OSC listeners into your button that you've created so set this up like this and amend the target to the other virtual port you have created (not the MCU ones):

  1. Just as a check, make sure your Cubase midi device panel isn't set with the MCU ports to “all midi”. The check box to the right of “active” should be unchecked.

  2. Add a text widget that will display the track name so we can test functionality so do that now with the following properties:

  3. When this is all working you now need to grab just the sysex that is the track name from MCU so you can then decide what labels to use. But there are loads of bits sent with the track name that mean that you need to strip out and all sorts so I'm going to first demo how to amend the labels without the complexity of different labels for articulations and then after that will do the complicated stuff. We will first just add a trackname to your OSC panel.

  4. So change your custom module to the following - I have tried to add commentary to the javascript to try to allow you to follow it:

Update your Custom Module
//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
//set a variable for the midi port of your generic remote

var genericRemoteMIDIport = 'OSCMIDICONTROL'; //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

//MIDI PORTS FOR FUNCTIONS FROM OSC



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('MIDI message recieved into OSC from Cubase')
     

        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')

        }

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


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

function mcuToOsc(host, port, address, args) {
    console.log('fired')
    if (host !== 'midi' || port !== midiPort_MCU_To_OSC) return

//added this to fix error
        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)
        }
    }




}

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
}
  1. So now this is the final step to get the articulations showing. You need to make sure you have set up everything correctly in your OSC map and directory structure. I have assumed you have 64 button widgets to show the articulations - if you have a different number there is a note in the code above where to change this.

  2. You also have to have all your expression maps named with the track tag that reflects the same tagging system you have used in your cubase template - the tag should be in the format [xxxxxx] where x are the characters. For example:
    image

image

Now change your custom module to this, remembering to update the path in the script to where you keep your xml expression maps.

Final Custom Module
//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 = 'OSCMIDICONTROL'; //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('E:/01 - Composing Data Drive/01 - Music Files/01 - Patches and Maps/01 - Cubase Mapings/01 - Expression Maps/Rebuilt Set 12-11-21/final patch numbers/*.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 < 64; 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 }
            ]
        })

    }

}

And that's it. Should all work now for you...

4 Likes

Hi @Sub3OneDay
thank you for this wonderful contribution and sharing your knowledge and time!
It's more valuable than the original post which understandably got clustered after so many replies and updates :wink:
I'm sure this will help many users and I keep in mind to send links to your tutorial in future replies since it also covers other basic stuff as well.
Very appreciated! :1st_place_medal:

1 Like

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?