Building a TouchOSC/MIDI Remote template

So - it’s been a while since I embarked on this.

My HexFM TouchOSC template is buggy as heck, there is still a lot of work to do, but since I’m a marketing genius I won’t be mentioning any of this and will give you a look into what’s functioning instead:

  • Track Color changes
  • Track Name changes
  • Quick Control names (that was a difficult one so I’m kind proud of this) - which are editable only if you keep a not-so-aptly-named “lock” button on top (so yeah, it only work on multi-touch by design).
  • Press and Hold buttons to Go To / Set markers
  • Right and Left locators buttons
  • General use Macros and Buttons to help my workflow.
  • A “help” page (yeah I know it’s lame, but you’ll thank me later)

Future improvements could be buttons for insert track divided per type, different pages for instruments, MIDI and audio, “choose-your-own-number” MIDI CCs in the long faders area.

It’s been a heck of a rollercoaster, and still a lot of unanswered questions, haha… but it’s been fun.

Ping me a line if you have any suggestions on where I should go next with this?

1 Like

:frowning: like it mate!!! Though the resolution of the video was not very helpful :frowning:

1 Like

Sorry about that! Working on a 4k resolution and obviously I forget all the time to update OBS :person_facepalming:
Edit: tried to redo it but there’s a 4 MB limit here.

@m.c do you know if there’s a way to “postpone” sending the QuickControl SysEx?

You mean, to add some delay?

You mean, to add some delay?

Yes.

I already started screwing things up in my template. The more lines of code I add, the more scrambled the SysEx messages sent out become. Also for some reason the QC names get sent thrice when you invoke mQuickControls.getbyIndex(n).mOnTitleChange, and only the first time with the proper name and everything, so I had to filter out the ones that contain nothing (“”).

It happens now that QuickControls are passed to TouchOSC, and then another SysEx for track change is passed after. This means that QC names are passed, then they’re reset.

I cannot, for the love of me, unfathom the order they’re sent from Nuendo, and I suspect there isn’t one.

A way of delaying is a very old one:

function delayAction(ms){
            var milliseconds0=ms
            var start0 = new Date().getTime();
            for (var i0= 0; i0 < 1e7; i0++) {
                if ((new Date().getTime() - start0) > milliseconds0){
                    // console.log("breaking")
                    break
                }
            }
            // console.log("returning")
            return "1"
        }

However, this is a BLOCKING-thread function and has to be used with caution (I put no more than 10ms and even then I sometimes see erratic behaviour).

A combination of the above and a custom Var might give better results if more time is needed.

Anyway, not sure if you need all this. The way I see it, upon every qcControl, you should send the info without delays. What you CAN do, is send them ONLY for the selected tracks, which you should obviously hold its number to a state var. Or maybe I’m missing something? You need to send all qcControls for all tracks at once?

That’s right, I’m sending them one by one, and, in fact - the issue is not sending the names - it’s resetting the label to null.
That has proven to be difficult, because I just only half understand how the API works and my programming is kinda jurassic.

So, the gist of it is this:

SelectedTrackQuickControls.mOnTitleChange = function (activeDevice, activeMapping, trackname) {

    SelectedTrackQuickControls.getByIndex(0).mOnTitleChange =
         function (activeDevice, activeMapping, QCreference, title) {
             SysExQCLabel(activeDevice, '1', QCreference, title) }

}

where

function SysExQCLabel (activeDevice, QCnum, QCreference, title) {


    if (QCreference != '')
        {

            var msg = [0xF0, 0x51, 0x43, 0x30, QCnum.charCodeAt(0)];
            
            for (var i = 0, len = title.length; i < len; ++i) 
                 {msg.push(title.charCodeAt(i)) }

            msg.push(0xF7)
            
            midiOutput.sendMidi(activeDevice, msg)


            console.log("Sending QC0" + QCnum + ": "  /* + msg.toString() + " | " */  + QCreference + " - " + title)
        

    }

}

There should be a better way to do this :man_facepalming:

Hold on mate, why are you putting a mOnTitleChange inside another one?

Told you, I’m an idiot with a keyboard.
But: I actually tried not nesting them, and it wouldn’t work. I have no idea why. It should, but it doesn’t. :person_facepalming:

Not seeing you as such :slight_smile:

Anyway, have you uploaded your script somewhere?

1 Like

Not really, that’s a good point.
Let me clean it up and put some notes and I’ll post it here!

I did some additional testing and I remembered why I tried to put it in the other mOnTitleChange.

  1. Say you’re on track 1 and QC1 is “blah blah”.
    You select track 2.
    This doesn’t imply a QC1 change on track 1. Point is: when I go back to track 1, the system says “hey, there’s been no change! it’s still the same QC name “blah blah” that was here before! Happy times!” – and so, it doesn’t broadcast a name change. I imagined this was the main difference between Focused QCs and “normal” track QCs, but console.log() proved me wrong.
  2. At initialization, QC changes catured via mOnTitleChange get broadcast thrice for some reason - of which, only one time out of three it’s the actual QC title + name.
  3. if you want to get directly to the part we discussed, search for “THE CULPRIT OF MY DESPAIR” haha
  4. I suspect my dinosaur programming skills that, up until this past November, were still stuck on Turbo Pascal, are partly to blame. Object-oriented programming and structures is something I am struggling with.
  5. Sending a QC “reset to null” via SysEx doesn’t work because for some reason track title changes get sent after all the QC name changes - hence: QC get sent, they appear on my template for a fraction of a second, then the reset message kicks in and nukes everything again.

I’d love to know if there is any way of exposing the QC names that doesn’t use the mOnTitleChange method. That would solve all my issues.

HexFM_NAVI_WIN.zip (3.4 KB)

Hi, nice job you did there!
I’ve already spotted some things, I’ll have a closer look in the evening and get back to you (if in the mean time of course no other user provides a solution here).

1 Like

Had a small break.
Here’s your code altered a bit. I’ve tested it to a small project containing 3 tracks, focused quick controls send their properties as expected. I made some comments starting with (mc).

HexFM_NAVI_WIN.zip (3.9 KB)

1 Like

NOTE: I edited more than once since stuff came to me while I was writing…

@mchantzi

Thanks!

The MIDI pairing at the top was just copied/pasted from one of the examples. It dealt with naming conventions being different in Win and Mac - I thought I’d leave them for compatibility, just in case.

Nice thing about the .bind({i}) thing. I didn’t know about that and still getting around how the f— does it actually work.

But…

This is pretty much working like mine - if you deleted the comments wrapping the CULPRIT part, it would work pretty much like yours: sending only the “changed” QCs, which doesn’t inform my controller on what happens to the other ones.

Basically the issue I have is this:


Set up:
TRACK 01 has a full set of quick controls named “Annabel”, “Betty”, and so on until QC8.
TRACK 02 has all QCs empty except slot 3, where I have “Carl”

So I have these QCs respectively:

TRACK NAME QC1 QC2 QC3 QC8
TRACK 01 “Annabel” “Betty” “Charlize” “Halle”
TRACK 02 none none “Carl” none

With your programming and mine, when a track is changed, only the new QC3 (“Carl”) get SysEx’ed out.

Now my display on the controller will show the name “TRACK 02” as the track name, but also will show me all the previous QC names, except in slot 3, where “Charlize” has been replaced by “Carl”.

TRACK NAME QC1 QC2 QC3 QC8
TRACK 01 “Annabel” “Betty” “Charlize” “Halle”
TRACK 02 “Annabel” “Betty” “Carl” “Halle”

The ideal situation would be that all other QCs (1-2 and 4-8) are wiped out, and only “Carl” remains.

My ideas:

  1. Sending a SysEx “reset code” only if the track get changed (the sysem above works brilliantly if you’re staying on the same track). It gets messy if you go from a track that has QCs to a track that doesn’t have them.
  2. A change to “” is actually detected by mOnTitleChange - I could indeed use it and send it to the controller, but — Cubase/Nuendo sends thrice with mOnTitleChange, for some reason, and if you’re staing on the same channel, it sends [“Annabel”, “”, “”] which is frustrating, because I had to filter out the QC titles which were simply “”.
  3. Find if there’s an option to check if QCn exists, rather than trying to “guess it” by checking if the name of it is just “”.

I hope this is clear now. I’m going anyway to implement your version moving forward, it gave me some nice input and decluttered stuff a little :slight_smile:

I now understand much better! SO, you have plugins with unassigned quick controls!
In this case just change

if(title!="" && QCreference!=""){
            var qcIndex=this.i
            SysExQCLabel(activeDevice,""+qcIndex, QCreference, title)
        }

To this:

if(title==""){title=" "} 
        if(QCreference==""){
            QCreference="Unassigned"
        }
var qcIndex=this.i
SysExQCLabel(activeDevice,""+qcIndex, QCreference, title)

Answering to your 3rd idea, the option to check if QC exists is actually the same as checking if its name is empty. There is no reason for the API to produce overhead.
To not mention, that you may have a parameter which (mistakenly) has an empty definition.

Now, based on my test here, I see the track name comes BEFORE the QC Names, so I see no problem in the script. Have a look here:

Things are getting logged in the expected order. Are your logs different?

Holy crap mate, it works!

I am sorry for my naming conventions by the way haha
I decided that I don’t need QCreference passed through to TouchOSC by the way.

Now it works.
On the TouchOSC side I wrote a little if statement in LUA so that if the Sysex message contains “Unassigned”, then leave the label empty. That reflects exactly what’s happening in the QC column in Nuendo now.

There are a few things to iron out, but what started out to be a fun side project is really coming alive indeed.

@m.c I am also thinking of rewriting the NumPads and their binding as functions and methods… So that once a numpad is done you can call a .bindtoMIDI thingy of its own. Doable?
Did you do anything like that that I can study?

And by study, I mean steal? (in a professional, Robin Hood way, of course) :bow_and_arrow:

Not sure I understand, sorry.
I see you have some loops creating and then binding your pads. I see no problem with that. Perhaps you need to avoid repeated ones?

For example, you can surely have a function like this one:

function createPad(x,y,width,height,bindChannel,bindParamNumber){
    var pad=surface.makeTriggerPad(x,y,width,height)
    var mSurfaceValueBinding=pad.mSurfaceValue.mMidiBinding
    mSurfaceValueBinding.setInputPort(midiInput)
    mSurfaceValueBinding.bindToNote(bindChannel,bindParamNumber)
    return pad 
}

Then, for generating a map of pads, you can do something like this:

function createPadsZone(channel,initPitch,rows,columns,initX,initY,padWidth,padHeight,columnSpacing,rowSpacing,startAtTop){
    //startAtTop: true-> we're setting our pads from the top-left corner.
    //            false->setting from the bottom-left corner.
    var mult=-1
    if(startAtTop){
        mult=1
    }

    var ourPadsMap={}
    for (var row=0;row<rows;row++){
        for (var column=0;column<columns;column++){
            var padIndex=row*columns+column
            var currentX=initX+column*(padWidth+columnSpacing)
            var currentY=initY+mult*row*(padHeight+rowSpacing)
            var pad=surface.makeTriggerPad(currentX,currentY,padWidth,padHeight)
            var pitch=initPitch+padIndex
            var pad=createPad(currentX,currentY,padWidth,padHeight,channel,pitch)
            ourPadsMap[padIndex]=pad 
        }
    }
    return ourPadsMap
}

And finally (again, these are just examples):

var SetMarker = {}
var GoToMarker = {}
var SetColor = {}
var MarkerPadX = 4
var MarkerPadY = 7
var ColorPadX = 8
var ColorPadY = 4

GoToMarker=createPadsZone(15,81,3,3,MarkerPadX,MarkerPadY,1,1,0,0,false)

SetMarker=createPadsZone(15,91,3,3,MarkerPadX+0.1,MarkerPadY+0.1,0.3,0.3,false)

SetColor=createPadsZone(15,11,4,4,ColorPadX,ColorPadY,1,1,0,0,true)

1 Like

@m.c Yeah more or less that is what I was looking for, thanks, you gave me a few ideas :slight_smile:

The idea behind having more one-off building blocks is to streamline the “code reading” by putting all the functions that do the work “under the hood” in another file, like in the built-in “Real World Example” template. It has a helper.js file that stores some functions so that the MIDI Remote script itself is not too encumbered by heavy syntax.

Also thanks for the controlvalue part you added. I’ll check it out. If it does what I think it does, I’m quite eager to put in a 2nd label per QC to output strings like the LC/HC slopes, type of EQ curve, etc. Bloody brilliant.

Do you know if it possible to do the same thing with Open stage Control?