MIDI Remote API Shuttle/Jog commands unusable

Hi!
While actually most of the remote control bindings work rather effortlessly with the remote API (it might be desirable to include examples on how to use ControlLayerZone and ControlLayer in the documentation, but it can be figured out), I cannot at all get shuttle and jog controls to work - and the behavior is rather bizarre.

It appears the CommandBindings for shuttle work as toggles, reacting to the release event of whatever is bound to them to turn on, which (a) my remote control doesn’t do and (b) I wouldn’t do as well, because one turns the thing and expects it to start shuttling. But working around that issue just brings on the next: Cubase doesn’t like it at all when two shuttle speeds are toggled on at the same time, which makes it stop - and there is no reliable way (at least I couldn’t find one) to turn off the previous shuttle speed before turning on the new one - it has a chance of working sometimes, but not always, again leaving Cubase in a strange state where it stops playback altogether, but then requires sometimes multiple shuttle speed toggles to get to do anything again.

The most usable result I was able to achieve is having every second speed work, but even that only sometimes, and which speeds then do work is mostly random chance after a time (plus Cubase apparently doesn’t reset some internal state like ever, so even discovering that behavior required several restarts to be sure).

The behavior of the jog bindings is similarly broken, while it is easy to get Cubase to jog while really really slowly turning the jog wheel, even that is prone to not work, as sometimes, for no discernible reason, Cubase will reset the playback position to an earlier one (this usually happens, if I am not mistaken, when the bindings mOnValueChange callback is invoked), and there also is no way I could find to actually pass the turning speed to Cubase.

It does appear Cubase can handle that better with some (legacy?) integration (even though not perfectly as well), but using the Remote API it doesn’t seem possible to bind jog/shuttle, but that would be highly desirable, given that the remote API is a very useful way to bind a device in every other respect.

Hi, can you share the code you use for these, so we can have a look?

Hi!

I had quite the same experience regarding the shuttle command. I found it unusable. But i did manage to get a working jog control though.

Bests,

Thomas

Hi,
I tried several variations. It might be there is some way to get this working, but I couldn’t figure that out.

I will just list which approaches I tried in no particular order, starting with the jog.

Approach 1: Create a knob bound to the jog, using bindToControlChange(0, cc).setTypeRelativeSignedBit()
Knob visual feedback matches actions on control surface, but will of course be clamped to [0, 1] range.
makeCommandBinding(this.nav.jog.mSurfaceValue, ‘Transport’, ‘Jog Right’);
for what would seem a somewhat obvious solution as I want positive to go into right direction.
Effect is (this was by far not the first experiment) as expected ,jog only goes right regardless of dialing direction, speed is not all relevant, and every now and then Cubase jumps back to a previous position for no discernible reason.

Approach 2: Instead of trying to map jog directly, process the value change events in custom code, doing to mapping of CC values to signed speeds myself, define a pair of CustomValueVariables for the respective directions, bind these to the two host commands, and set values in response to changes via setProcessValue. (Before that I did make sure of course I received the values correctly by logging them.)

Jog now does at least work in both directions, but still ignores physical speed entirely and exhibits the strange jumping behavior every now and then, rendering it basically useless.

Approach 3: Variation of approach 2, where I try synthesizing further events by assuming chains of setProcessVariable calls, in various combinations (with relative value, just using value 1 in a loop etc.), none of which showed any improvement; I think (don’t remember, I did quite a lot of variations) I also tried delaying the additional commands by sending them later on from mOnIdle, to no avail, especially the random jumping did not improve at all (I did get it to react to the dialing speed to some extent, though).

Overall I do think the entire approach might be flawed, like putting a round peg in a square hole, but after reading through the entire documentation dozens of times there was no hint what I could use instead - some value or action binding would have seemed a much more logical approach to me, but with no target to bind it to…

For the shuttle there were more variants. I did try three different styles of mapping: Having the shuttle send note-off events for the various positions, having it send note on events, and having it act like a knob, sending signed CCs.

The signed CCs didn’t improve things, as similar to the jog above I had to essentially map them back to button actions on CustomValueVariables anyways (here a ValueBinding to something like Transport.PlaybackSpeed or something would have been even more obvious than for the jog, but yeah, no such thing), so let’s forget this, and just focus on button mappings.

I could not find a way to have the remote API react to receiving only note-off messages, when there’s no note-on, it wouldn’t do anything, so let’s also discard that approach for futility.

With note-on events I did get at least as far as having the host command bindings trigger, but it behaved strangely, which took me some time to figure out (and I am still not sure what happens exactly): The command appear to behave as toggles, so turning the shuttle left once (and releasing it) would start shuttling, and to turn it off, I had to turn it again.

Or turn it further, because it appears that when the next shuttle speed message comes in, this makes Cubase stop. This does appear a little like only every second shuttle speed works at first, but behavior is much weirder actually, with the toggling and seemingly also some internal state reset happening sometimes.

I did again try variations on this, like synthesizing the input events to trigger the host bindings etc., so I did at least manage to get over that toggling behavior (with some truly scary internal state machine, which has a good potential to get out of sync with the host state).

I don’t have all that code anymore, as it didn’t work and just became a mess from repeated edits to try out other approaches, and in the end I deleted the whole of it for lack of functionality.

So either I am approaching this all wrong (wouldn’t rule that out) or there is some mismatch between the API bindings and the physical world.

Any help would be welcome,
Bernhard

Ah, I now understand perfectly what we’re talking about. Initially I thought it was about nudging cursor, though your description was actually pretty clear, I just missed it.

Yes, I do recall in my early attempts, that I faced the same issues, and then, since I don’t need the jog left/right (I’m 99.9% into vsts and not audio), I completely abandoned the binding.

Anyway, I just tried to understand how the commands work via the qwerty keyboards, so here’s my best approach (turning a knob activates jog left/right, if we turn it again to the same direction in more than 100ms, it deactivates the movement):

var midiremote_api = require('midiremote_api_v1')

var deviceDriver = midiremote_api.makeDeviceDriver('Test', 'Jog Left-Right', 'Someone')


var midiInput = deviceDriver.mPorts.makeMidiInput("anInput")
var midiOutput = deviceDriver.mPorts.makeMidiOutput("anOutput")

var detectUnit=deviceDriver.makeDetectionUnit()
detectUnit.detectPortPair(midiInput,midiOutput)
    .expectInputNameEquals("an input port")
    .expectOutputNameEquals("an output port")


var surface = deviceDriver.mSurface

var knob=surface.makeKnob(0,0,1,1)
knob.mSurfaceValue.mMidiBinding
    .setInputPort(midiInput)
    .bindToControlChange(0,77)
    .setTypeRelativeTwosComplement()

var knobsLeftRight=[]

knobsLeftRight.push(surface.makeCustomValueVariable("knobLeft"))
knobsLeftRight.push(surface.makeCustomValueVariable("knobRight"))

var knobDelayMS=100 //delay when turning knobs to toggle. Turning knob to the same direction toggles the action
var startRepeatDelaySeconds=0.10 //delay before the commands begin repeating. Trying to emulate a qwerty, though much faster
var repeatsPerSecond=60 // how many times will repeat per second. On a qwerty this is usually somewhere near 30-31, however I found that I needed more Hz

knob.mSurfaceValue.mOnProcessValueChange=function(activeDevice,value,diff){
    
    var val127=Math.round(127*value)

    if(val127==127){

        setProcessValueKnobsLeftRight(activeDevice,0)
        
    } else if (val127==0){

        setProcessValueKnobsLeftRight(activeDevice,1)

    }

}

function setProcessValueKnobsLeftRight(activeDevice,knobLeftOrRight){

    var currentTurnStr=activeDevice.getState("currentTurn")
    var currentTurn=currentTurnStr=="" ? -1 : parseInt(currentTurnStr)
    
    var newStamp=new Date().getTime()

    var previousStampStr=activeDevice.getState("currentStamp")
    var previousStamp=previousStampStr=="" ? 0 : parseInt(previousStampStr)

    activeDevice.setState("currentStamp",newStamp.toString())
    
    var timeDiff=newStamp-previousStamp

    if(timeDiff>knobDelayMS){

        if(currentTurn==knobLeftOrRight){
            
            knobsLeftRight[knobLeftOrRight].setProcessValue(activeDevice,0)
            activeDevice.setState("currentTurn","")
            
        } else {
    
            knobsLeftRight[1-knobLeftOrRight].setProcessValue(activeDevice,0)
            knobsLeftRight[knobLeftOrRight].setProcessValue(activeDevice,1)
            activeDevice.setState("currentTurn",knobLeftOrRight.toString())
            
        }
    
    }
    
}


var page=deviceDriver.mMapping.makePage("page")

page.makeCommandBinding(knobsLeftRight[0],'Transport', 'Jog Left').makeRepeating(startRepeatDelaySeconds,repeatsPerSecond)
page.makeCommandBinding(knobsLeftRight[1],'Transport', 'Jog Right').makeRepeating(startRepeatDelaySeconds,repeatsPerSecond)

I’m not sure if this optimal, however, you might be willing to experiment a bit with the startRepeatDelaySeconds and repeatsPerSecond variables. The key to the above coding is that I take advantage of the makeRepeating method.

Edit: now that I read more carefully what you say about speed, I think that you shouldn’t bother to test the above. Another approach should be taken, will let you know if I come up with something.

1 Like

Thanks!

I did try something similar (as one of the many things I tried), but the major issue I then had was that random jumping back to a previous position still remained, overall it did defeat the purpose of using the jog wheel to locate a particular position with any precision - if I just bound like 1/8× shuttle in either direction (with a few hacks around that toggle behavior) that did work better for that particular purpose, but still quite clumsy…

The legacy binding of the hardware does work, so I do have a workaround, but it makes the Remote API not really a replacement (would have been nice, at it way more plug and play), and the behavior is rather strange in any case (and I fail to see a case where the current behavior would really be useful).

Edit: Just to preemptively clarify, I did of course disable the legacy mapping before trying that.