Scripting question (velocity sensitive NRPN encoders)

Edit: I had asked a question here regarding MIDI Remote, but since the encoder is NRPN and setup on the controller itself, I’m going to do it on the controller and not MIDI Remote.
I have since found new information since making this post and will resolve it outside of Cubase, then program Cubase as if it were a standard controller later.

Original post:

The dilemma:
I need to cover .1db increments on a plugin’s db knob. Starting at 0db, the first turn is either +.1db or -.1db, all the way to -15.0db or +15.0db. That’s 301 possible values, including 0db as it’s own value.
NRPN covers 16,384 steps, so I divide this number into 301 and that’s how many moves each knob turn should make, so that I get a total of 301 possible encoder values (approx. 55 steps per turn).
So far, so good - I got the controller working great. It’s moving the Waves SSL plugin db knob in .1db increments.

The issue is that it only goes in .1db increments, no matter how fast or slow I turn the encoder. I don’t want to have to turn the knob 10 clicks per db if I’m turning the knob quickly.
Well, the MIDI plugin script for the Stream Deck has velocity control, but usually it’s not for NRPN messages. I have to set the step to “0” in order for the knob to be velocity sensitive, but I need the step to be 55 to cover my needs (which it does well), but now I lose my velocity control.
I can still find out and learn on that end how to script it on the controller-side by messaging the developer and asking him how to go about it, or I can consider doing it from the Cubase MIDI Remote script side.

My question is regarding how to handle velocity control in JavaScript with Cubase MIDI Remotes, vs handling it on the controller side.

Is the idea to use a timer function and store it in a variable, then use delta time and compare the old time with the new time, and if so, run a function to make the incremental steps more than 55? Did I even say that correctly?
If so, how would you do this? If not, what is a better method for velocity sensitivity if I require the 55-step-per-turn to remain intact as the slowest increment to keep the .1db part intact? I imagine the steps per turn will increase greatly if the velocity is faster, to get through the db values faster.

The reason I ask is because I imagine there could be multiple ways to address this issue, and I’m curious to hear what those ways are, then attempt to implement one of them.

Once I get the velocity down, the controller will be almost complete and I’ll have full resolution with standard MIDI, with the ability to make larger encoder leaps if necessary.

When the script is done, I’ll release it to the community. There’s nothing like it that I’ve seen online anywhere.

From my experience, a velocity feature on a rotary encoder is used when the encoder is sending relative values. To take advantage of that feature in MIDI Remote we have to use Custom Value Variables.

I know what you’re talking about and it’s 100% accurate in the use case of adding fine tuning to the game.

However I’d like to show an alternative method of approaching host values manipulation, for a good reason. I wanted to present this in the past, but kept forgetting about it, until very recently I uploaded a script for the Korg Keystage where I do use this approach for manipulating the AI Knob using a Jog Wheel (which takes just a left/right turn value).

As you perfectly know, when we’re using custom process variables we’re facing an issue:

Without hard-coded workarounds, it’s impossible to know the “Step” of the host Value Variable we want to alter. Let’s take for example an EQ Band Filter Type. We know that this host value has very specific acceptable values, for example Band 1 has 8. This means that our step should be 1/8. But we just don’t know, except of course if we hard-code it. Totally fine, but here’s the way I’m talking about and it can solve this issue:

We can take advantage of two methods of the host values: increment and decrement. If we engage these two, whenever we are in cases as the eq described above, we can accurately force the host value to take its next/previous “allowed” value, i.e. in the case of eq band 1 filter, we can set it to 1/8, 2/8 and so on. For this, we need to take advantage of another element, the custom HostValueVariable.

Suppose we have a button we want to have it incrementing a host value.

Here’s how it can be done:

//we've already defined a button and a mapping page
//and now let's define a "real" hostValue and a"dummy" knob bound to it:
var eqBand1FilterTypeHostValue=page.mHostAccess.mTrackSelection.mMixerChannel.mChannelEQ.mBand1.mFilterType
var knobDummyIncDec=surface.makeKnob(0,0,0,0)
page.makeValueBinding(knobDummyIncDec.mSurfaceValue,eqBand1FilterTypeHostValue)

var customHostValueInc=page.mCustom.makeHostValueVariable("customHostValueInc")
page.makeValueBinding(button.mSurfaceValue,customHostValueInc).mOnValueChange(activeDevice,activeMapping,value,diff){
   if(value==1){
       eqBand1FilterTypeHostValue.increment(activeMapping)
   }
}

This way, whenever we press the button, the filter type of the eq1 will properly increment to its next value.

Here’s a complete snippet of a working remote:

var midiremote_api = require('midiremote_api_v1')

var deviceDriver = midiremote_api.makeDeviceDriver("Test","Inc-Dec HostValue","m.c")

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

var detectionUnit=deviceDriver.makeDetectionUnit()

detectionUnit.detectPortPair(midiInput, midiOutput)
    .expectInputNameEquals("an input")
    .expectOutputNameEquals("an output")

var surface=deviceDriver.mSurface
var mapping=deviceDriver.mMapping

var buttonDec=surface.makeButton(0,0,1,1)
buttonDec.mSurfaceValue.mMidiBinding
    .setInputPort(midiInput)
    .bindToControlChange(0,20)

var buttonInc=surface.makeButton(1,0,1,1)
buttonInc.mSurfaceValue.mMidiBinding
    .setInputPort(midiInput)
    .bindToControlChange(0,21)

var knobDummyIncDec=surface.makeKnob(2,0,1,1)
var page=mapping.makePage("page")

var eqBand1FilterTypeCustomHostValueInc=page.mCustom.makeHostValueVariable("eqBand1FilterTypeCustomHostValueInc")

var eqBand1FilterTypeCustomHostValueDec=page.mCustom.makeHostValueVariable("eqBand1FilterTypeCustomHostValueDec")


var eqBand1FilterTypeHostValue=page.mHostAccess.mTrackSelection.mMixerChannel.mChannelEQ.mBand1.mFilterType

page.makeValueBinding(knobDummyIncDec.mSurfaceValue,eqBand1FilterTypeHostValue)

page.makeValueBinding(buttonInc.mSurfaceValue,eqBand1FilterTypeCustomHostValueInc).mOnValueChange=function(activeDevice,activeMapping,value,diff){
    if(value==1){
        eqBand1FilterTypeHostValue.increment(activeMapping)
    }
}

page.makeValueBinding(buttonDec.mSurfaceValue,eqBand1FilterTypeCustomHostValueDec).mOnValueChange=function(activeDevice,activeMapping,value,diff){
    if(value==1){
        eqBand1FilterTypeHostValue.decrement(activeMapping)
    }
}

eqBand1FilterTypeHostValue.mOnDisplayValueChange=function(activeDevice,activeMapping,dispValue,dispValue2){
    console.log("disp: "+dispValue+" / "+dispValue2)
}

The drawback of this approach that I found, is that the step it inc/decs in the case that many users are interested in (i.e. extending the resolution) seems to be locked to a half of what we could get by an absolute 0-127 encoder. But note that I didn’t really get to thoroughly test this assumption, I’ve just inspected some logs, and I may be totally wrong. However, even in the case that this is true, the MR is really really close to supply what we need, by a preference setting the resolution to 14-bit, since the inc/dec methods are pretty good.

2 Likes

There’s a variant of relative CC messaging that communicates in variable step sizes. Some MIDI hardware can send that. So for example:

A CC value of 127 is sent (repeatedly) for a slow turn in one direction. And a value of 126 for slightly faster turns and a value of 125 for even faster turns.

And turning the other way sends CC values of 0, 1, 2, etc. depending on the speed of the turn.

An alternative dialect of that “relative” protocol sends CC values of 64, 65, 66 etc for turns in one direction and values of 63, 62, 61 etc for turns in the other direction.

But I think that’s one of those unofficial implementations, because different manufacturers seem to call it different things.

1 Like

True that they’re used to name them in a strange way sometimes. I think this is because they try to pass whichever mode they’re using to the end-user in a less technical form.
Just to be clear, my earlier reply was not about the cc values, my post assumes that we already parsed correctly whatever the encoder sends, isay a negative range for left turn and a positive one for the right one. It’s about how we bind these end-values. Now, the bigger (smaller if negative) this final value parsed is, the more iterations we can use inside the event provided to alter the host value. For example, say that we finally have a number in the int range [1,8]. We can alter a bit the event provided as:

page.makeValueBinding(button.mSurfaceValue,customHostValueInc).mOnValueChange(activeDevice,activeMapping,value,diff){
   if(value>0){
     for(var i=0;i<value;i++){
          eqBand1FilterTypeHostValue.increment(activeMapping)
     }
   }
}
1 Like

@m.c, I’ve been digesting every word of your posts, reading it over and over, trying to fully understand this golden information that you have bestowed upon us. Because your outside-the-box thinking just gave me what I needed to solve a would-be upcoming problem that you already solved without me realizing it was a problem yet.

Because the Cubase API is so new to me (I just started looking at it for the first time a couple of weeks ago when I started this off-the-wall project to solve a problem), I didn’t realize that there was an increment method.

In my use-case, I’m building an NRPN-based SSL controller for the Stream Deck+, which has +15db in one direction, -15db in the other direction, and 0 in the middle, for a total of 301 possible values (the plugin db knob moves in increments of .1db).

I was focusing on, as you put it, hard-coding it, because I knew there were 301 steps. But that’s only for the db portion, and for one plugin. I’d still need to do the khz values, and want to use the controller for other plugins as well.

My question for you now would be, now that we know that we can increment or decrement the next allowed value through a method, although we don’t know what each host value’s step number is, what is a way that we can find the host value’s minimum and maximum allowed values?

The reason being, if we know the default value, and we know the least allowed value and the most allowed value, then we can tell the knob the minimum and maximum positions.

We have the increment/decrement part down, so I guess in question form: How do we find the min and max allowed values on the given host? Otherwise, for my current needs, I’ll have to keep it hard-coded so that my knob is finite and the position can be easily determined.
Or do I?

I’m picking your brain here, if you don’t mind. I have tons of respect and admiration for you, btw, m.c. You’ve always been not a national treasure, but a global treasure. The world doesn’t realize how special you are, and this forum doesn’t give you the credit you deserve for how much love, kindness, support, time, and intellectual property that you have graciously blessed us all with.

:smiley: Thank you my friend, this type of reactions are very pleasant. Note that you’re exaggerating excessively, but never mind, brought me a big smile :smiley:

To our subject:

The host values are always normalized in the float range [0,1], i.e. the min value will always be a 0, and the max a 1. At least this is what I’ve noticed so far.

Now, the method I suggested in the earlier post, most probably divides this range to 256 steps, instead of the “ordinary” 128, for host values with no steps count below 128, such as the example of the filter type I gave. If we turn to the frequency instead, we’ll get 256 steps*. So, for most use cases you can actually have the full range of your knob, by properly setting its value normalized to 256/301*yourKnobsValue. Therefore, my question is, what would you like to do when facing for example the filter type. Would you want to limit your knobs active area accordingly, i.e. actually needing the step count property of the host value?

*And this is the “bad” part. Using the method I suggested as it is currently implemented, we can’t get to say 14-bit resolutions. That being said, knowing now the 256, or whatever stepCount we have, it should be trivial to use a division by our resolution to achieve what we want. I’m not 100% sure on this, but sounds legit.

1 Like

Although if what you say is true that we can’t do 14-bit resolution with your method, you bringing to my attention the increment method can be useful in conjunction with the code on the Stream Deck.

If you have a Stream Deck+, I invite you to look at this with me, as what Trevliga Spel (the dev) created is really nice, as it gives us the ability to create NRPN controls for every knob or button, with full scripting control.

The API is listed at:

Click the Script link there in the top menu and check the events and actions tabs. You can see the NRPN syntax as well:

(nrpn:channel,nrpn,value)

Since I’m using CC#110 on channel 12 (set on the Stream Deck), and I need 55 steps per turn in order to make 301 total encoder possibilities out of a range of 0-16,383, to call it per turn would look like:

(nrpn:12,110,55)

Here is my logic and train of thought thus far (keeping in mind that we can run any JavaScript commands we want, we can create custom actions, and his pre-made timer actions based in ms make our lives easier):

Instead of using 55, we create a custom variable with the @ symbol and store 55 in it like:

{@StepAmount:55}

Then when we call it, we use the # for value tags:

 (nrpn:12, 110, #@StepAmount#)

Then we make two more variables:

{@PreviousTurnTime:} //store the time of the previous knob turn {@LastTurnTime:} //store the time of the last knob turn

A timer can be controlled using four distinct actions:

  • {@t_mytimer:run} will start a timer. If it has been running before and not being reset, it will continue to count from where it was paused. If it has not been run before or has been reset, it will start counting from zero.
  • {@t_mytimer:pause} will stop/pause a timer at its current time value.
  • {@t_mytimer:restart} will reset a timer and start counting from zero. A restart action can be the first action for a timer; it doesn’t need to have been run before in order to be restarted.
  • {@t_mytimer:reset} will stop/pause a timer and reset the time to zero.

Knowing this, we can start the timer as our first knob-turn command (we can send as many commands at once as we want per knob turn, so starting the timer will be the first one).

Then when we turn the knob a second time, it’s going to pause the timer, and place the value of the timer into the variable we created, so we can compare the two times between dial turns.
Then, if the time between dial turns is less than X milliseconds (we can define this in another variable, such as turnSpeed or whatnot), then the StepAmount variable will equal not 55, but maybe 550 (10x the distance).

We could compare the time and if it’s less than 100ms apart, step amount = 250. If less than 400ms apart but faster than 100ms, then another speed. You get the idea.

This way, we still have our minimum step value of 55, unless the two times are faster, then it’ll get bumped up and we have both our velocity and our 14-bit resolution.

What say you?

Edit: I’m not sure if we pause the timer, then as soon as we put the value into our “previous turn” variable, we clear the timer and start from zero, or whether we should resume the timer to compare 3 knob times. I think we’re only comparing the time from the last turn, correct? In that case, resetting the timer to 0 after updating the variable should suffice, right?

Sorry, I don’t have this device to give it a try.

I think I understand. So basically you have 14-bit, and at slow turns you have a step of 55 and at fast turns, a multiple of 55. So, why not simply assigning this to a 14-bit encoder in the MIDI Remote surface? Is there an issue with this approach?

I think the only issue is my not understanding.
When we think about NRPN controllers, we are usually stuck with whatever the hardware manufacturer gives us in terms of control. With the Stream Deck+, it’s fully customizable, as if we are the hardware manufacturer, designing what the hardware is capable of first. That’s what I’m doing with Trevliga’s scripts - making the hardware portion of things.
It’s after that where we come into Cubase and make it a regular 14 bit NRPN controller like we normally do. But before we get that far, we have to setup the hardware on the hardware side and make it an NPRN controller that does what we want to begin with…or so I thought…or continue to think…until you help me understand otherwise.

In summary, we are getting our 14-bit ability, as well as the CC it sends all its values on, from the hardware scripting. We then come into Cubase and build our MIDI remote second, as if we had just bought our hardware from the store (only we designed our own hardware in advance).

Btw, I think I need to buy you a Stream Deck+ for your birthday. You really need one! It’s the only commercially available controller in the world that lets us do what you and I are talking about right now, which is have both 14-bit resolution, and do whatever else we want on the hardware side before it ever gets to the MIDI Remote portion.

But if you’re suggesting that I would get my max steps, or step increments from the MIDI script vs the hardware script, I suppose that was the point of my original post (which the mod undeleted since this thread was still receiving replies).
Take a quick peak at my first post if you missed it when it was deleted prior.

I started to type about NRPN for the search engine, then started ranting about the lack of innovation by hardware companies to do what I’m accomplishing right now with the Stream Deck+.

The point is, we take 2 CCs, which are CC98 and CC99 (which are the least significant bit and the most significant bit CCs) and we use them together to equal one 14-bit CC that we will map in Cubase in our MIDI remote, as we normally do. Only when we do, our CC will have values between 0-16,383 instead of 0-127.

The way the values get created for the CC we choose to use for our NRPN CC are that the MSB (most significant bit) has its own CC that is uses to generate values, which is CC#6. Then the LSB (least significant bit) has its own CC that generates values for it, which is CC#38.

It’s the combination of CC#6 and CC#38’s values that get combined into one other bit of data that is now called your NRPN data, which is passed along to the CC that you chose in the hardware to use. I chose CC#110 because that is a safe range where nothing else uses it. I also chose channel 12 to avoid other misc conflicts, but mostly just to be on the safe side for now.

Here’s how the NRPN value algorithm works:

Pretending that you’re starting the knob all the way at the min position, your value is 0. Now turn the knob clockwise one tick. 4 things are going to happen before the NRPN final message is generated:

Your MSB which is CC#99 will have a value of 0 because it’s not needed yet.
Your LSB which is CC#98 will have a value of 110 because that’s your CC that you chose before.
CC#6 will send 0.
CC#38 will send 1.

The output is your NRPN high res 14-bit value that is now fed into CC#110 (or whichever CC you chose in the hardware to use).

image
As you can see, it’s sending value 0 first, since it doesn’t need to use that bit right now. Then it sends value 1 after that.

Turn the knob a second time.
All the same data is sent, only this time, CC#38 is sending a value of 2 instead:
image

Imagine doing this until you reach knob position 128 (which would be the 129th position, counting 0). What would you expect to see in terms of values? Let’s find out:

image

Now it needs to use CC#6 to create a value of 1 instead of 0.
It also now creates a value of 1 on CC#110, whereas before, it was always putting a 0 there (because it didn’t need it).

Ok, so keep move the dial up to 256:
image

As you can see, now the CC#6 has reached 2 and the CC110 is also at 2.

It will keep doing this every 128 times until it runs out, then it will simply add another number value to one of the LSB or MSB values, then continue onward. Eventually, those will run out, and it will finally need to change that MSB bit from a 0 to a 1. Until that is also all the way up to 127, at which point, we will have our 16,383 values.

Just thought I would share my approach with my endless rotary encoders.
I have a MIDI Fighter Twister that has 16 endless rotary encoders (same type of controllers as on the Stream Deck+). My controller does not have the ability to send 14-bit values such as NRPN but it does have the ability of sending relative MIDI CC values with velocity. When I turn the encoder clockwise, it sends values 65 to about 115 depending on how fast I turn it. When turning the encoder counterclockwise, it sends values 63 down to about 30.

In MIDI remote I directly manipulate the VST parameter value (process value/host value) using the input of my controller. As @m.c said above, this value is always a floating point value of 0.0-1.0. This gives me the option of deciding how many steps I want for any specific parameter. For example, I can have channel volume fader with a resolution of 1200 steps, EQ Gain with 481 steps, EQ Q value with 121 steps and so on. All of this is handled in my MIDI Remote script.

Using this approach of customizing ranges for specific parameters works much better for me than having one large range applied to any type of parameter.

2 Likes

Please don’t make me love you any more than I already do, lol…holy cow man. I’m about to have a great day, I can already tell.

I have a MIDI Fighter Twister. I want to know more because that sounds fantastic. I don’t understand the 0.0-1.0 range thing though - can you elaborate on where you are setting that, or what the syntax is, or where it’s going in your script, or how you are calling it or setting it?

How do you set it per parameter, per plugin?

This should probably be answered by @m.c as he is the undisputed king of MIDI Remote whereas I am but a humble tinkerer.

My script for the MIDI Fighter Twister is very custom, complex and probably quite convoluted. That said, I am happy to steer you in the right direction.

When creating Surface Elements, such as a “knob”, I doing that by calling a function that returns an object representing the knob. (This is common in many scripts). Something like this:

var kb = {}
kb.controller = surface.makeKnob(c, r,  w, h)
kb.controller.mSurfaceValue.mMidiBinding.setInputPort(midiInMF).bindToControlChange(channel, cc)

In the same function I also define the mSurfaceValue.mOnProcessValueChange hook and this is where we can manipulate the host by using:

kb.boundValue.setProcessValue(activeDevice, kb.realValue[p])

Above, kb.realValue holds my new host value as a floating point value between 0.0-1.0.
Before setting the new value, I evaluate the data that comes from my controller via the mOnProcessValueChange function and massage the host value as needed.
The host value I store in kb.realValue is updated in the callback mOnValueChange that gets defined when we bind a MIDI controller (or in my case, a “Custom Value Variable”) to a host value.

I hope my rambling made some form of sense! :smiley:

3 Likes

You don’t set anything, MR is responsible for mapping whichever range of values a VST parameter has, or even a none VST entity (for example Volume, Pan, etc) to this range [0,1]. You are responsible only for the stage of creating a control (i.e. in our case here a knob) and then binding this to a parameter.

Now I’ve read the original post. Let’s for a start forget everything about the DAW and plugins and let’s focus on the controller you have.
Is your knob an endless encoder (which means we can endlessly turn it left/right) or a pot (when we turn it left/right we reach an “end” when turn is no longer possible)? I totally understand what you say about the script of Trevliga Spel, but I really need this basic information, because it describes the physical properties of the knob, and this is a good place to start.

1 Like

They are endless encoders.

Cool! This means that we can have almost total control of the “turns” either by using a custom approach as the one @mlib mentioned, or by directly getting the endless encoder bound.
What seems to be the problem with the latter approach? Do you want a higher resolution, a more fine-tuning option?

1 Like

Exactly - the entire point is to have high resolution for finer tuning, but also not be limited to only fine tuning as I have been with my current approach of setting a fixed step amount.

In other words, my increments are the minimal increments, which is good to cover everything, but it’s not good when you have to turn the knob 30 clicks to get +3db.

Yes, I totally understood this.
Is this knob on your StreamDeck or on another controller?

The knobs are on the unit, and here is what it looks like:

The scribble strip above the encoders will show the values however we want (and also each section of the touch strip can be divided into 4 touchable knobs or buttons, so 16 total on the strip if we wanted).

I’m trying to make this into an SSL console controller, but have higher resolution via NRPN, since we can do that, and we know how many steps the SSL plugin uses.
If you help me get this thing working, I’ll buy you a Stream Deck+ for development purposes, because if anybody on earth could use one of these things, it’s you m.c. It’s only $199.