Selector Knob Widget and Continuous Encoders

I’m working on a rackspace for the B3-V from Arturia. I’ve got a bunch of it, there there is one control/widget that I’d like to try and get just a bit better. It’s the “Chorus Type” selector, which selects between 3 different vibrato or chorus settings - a total of 6 different options.

In the plugin, it’s a single “continuous” knob that just has 6 positions, or detents. It looks like this:
image

I’ve got it mapped to one of my continuous encoders on my MIDI controller, and it works - each “section” matches an option - so 0-.16 is V1, .167-.33 is C1, .34-.5 is V2, etc.

But the knob widget is linear - it goes through every value, and it displays every value. The indicator moves smoothly, instead of showing “stuck” on each of the positions. I already used the “step” option to create steps in the value - so it is technically sending a single value to the plugin. I’m just wondering if there is an option to control the “steps” of the widget.

I also can’t keep turning it around. At some point I get to “max” - which is C3 - and I can’t keep turning clockwise to get back around to V1. I have to turn the other way.

Now - let’s be honest - this is a REALLY minor thing - It won’t really make much difference, other than to satisfy my OCD. :slight_smile:, but I thought I would put it out there to see if anyone had some ideas. I’m happy to write some scripts if needed - just not sure exactly what the script should do.

Thanks for any tips. I tried searching and reading, so hopefully I didn’t miss something.

1 Like

You could try setting the value of the knob after it receives a value to its closes rane.

Below is a script to get you started (it’s not perfect though). This code is for a normal knob, I don’t have an encoder myself to try.

var
   Knob1 : Widget
   NR_OF_POSITIONS: integer = 6

On WidgetValueChanged(newValue : double) from Knob1
   newValue = IntToFloat(Floor(newValue * (NR_OF_POSITIONS - 1))) / (NR_OF_POSITIONS - 1)
   SetWidgetValue(Knob1, newValue)
End

Below is a second variant that prevents bouncing the knob between two positions:

var
   Knob1 : Widget
   NR_OF_POSITIONS: integer = 6
   var NoResponseGap : integer = 50 // % to prevent knob from bouncing between two positions
   var LastKnobValue : double = 1.0
   var LastKnobValueSet : boolean = False

function SetKnobValue(positionValue : double)
   var newValue : double = IntToFloat(Floor(positionValue)) / (NR_OF_POSITIONS - 1)
   SetWidgetValue(Knob1, newValue)
   LastKnobValue = newValue
end

On WidgetValueChanged(newValue : double) from Knob1
   var positionValue : double = newValue * (NR_OF_POSITIONS - 1)
   var modValue : integer = Floor(positionValue * 1000 + 0.5) % 1000
   if modValue >= (500 - NoResponseGap * 10 / 2) and modValue <= (600 + NoResponseGap * 10 / 2) then
      if LastKnobValueSet then
          SetWidgetValue(Knob1, LastKnobValue)
      else
         LastKnobValueSet = true
         SetKnobValue(positionValue)
      end
   else
      SetKnobValue(positionValue)
   end
End


1 Like

@Michelkeijzers - Thanks for both of those. Sorry I was so slow. Your scripts made me think about some of the challenges with what I was trying to do.

In the end, I wrote a different script (I’ll add it below) for one particular reason - I wanted a continuous knob - I wanted to be able to turn the “continuous encoder” on my MIDI controller, and have it keep turning the widget/knob. Basically it “wraps around” to the beginning instead of getting to the end, then having to be turned “backwards”.

To make this work, I leveraged the “relative control mode” on my MIDI controller - so it just sends either 63 or 65 depending on what direction it is turning.

Here’s the script. It is doing what I want. It turns and “latches” to each position as it goes around and I can keep turning clockwise or counter-clockwise to get to whatever I want. I suspect I might be able to do this with a scriptlet, which might also make it a bit more flexible, but for now … :slight_smile:

var 
    VibCh_Knob : Widget
    MainKeys : MidiInBlock
    
    // This is the number of positions on the rotary switch
    NUM_POSITIONS: integer = 6
    STEP_SIZE : double = 1.0 / NUM_POSITIONS
    
    // This will track which of the positions we are currently in
    CurrentKnobPosition : integer = 0 
    
    // BUFFERZONE helps make sure any changes are intentional and prevent the knob from turning
    // too quickly. Once we have X number of CC changes, then we will change the widget
    BUFFERZONE : integer = 15
    bufferzone_counter : integer = 0
    
// This determines the knob position based on a double
// And helps handle changing rackspaces or moving the widget from the screen
Function GetKnobPositionFromDouble(position : double) returns integer
    result = Round(position / STEP_SIZE)
End
        
// Gets the new knob position, bounded by NUM_POSITIONS, and wrapping around for a continuous knob
Function GetNewKnobPosition(increment : integer) returns integer
    var newPos : integer = CurrentKnobPosition + increment
    // Now, let's wrap the knob around
    if newPos < 0 then
        newPos = NUM_POSITIONS - 1
    end
    result = newPos % NUM_POSITIONS
    //Print("GetNewKnobPos => increment: " + increment + " curr: " + CurrentKnobPosition + " newPos: " + newPos + " result: " + result)
End

// Sets the Knob Widget to one of the positions
Function SetKnobWidget(newPosition : integer)
    // Set our current knob position and then the widget
    // This cheats a little to even out the knob steps by setting in the middle
    var middle : double = newPosition * STEP_SIZE + STEP_SIZE / 2
    CurrentKnobPosition = newPosition
    SetWidgetValue(VibCh_Knob, middle)
End

// Determines what the relative change is. Returns +1 or -1 regardless "speed" from relative input
Function GetRelativeChange(value: integer) returns integer
    var newVal : integer = value - 64
    if newVal < 0 then
        result = -1
    else
        result = 1
    end
End

Function SetInitialPosition()
    var c : double = GetWidgetValue(VibCh_Knob)
    CurrentKnobPosition = GetKnobPositionFromDouble(GetWidgetValue(VibCh_Knob))
    SetKnobWidget(CurrentKnobPosition)
End

On Activate
    SetInitialPosition()
End

On Variation(oldVariation: integer, newVariation: integer)
    SetInitialPosition()
End

// Captures the MIDI change so that we can handle the relative thing and the "stepping"
// Make sure MIDI channel is set correctly
On ControlChangeEvent(c : ControlChangeMessage) Matching 74 from MainKeys
    var change : integer = GetRelativeChange(GetCCValue(c))
    var newKnobPos : integer
    
    // This determines if we have turned enough to leave the "buffer zone"
    if Abs(bufferzone_counter) <= BUFFERZONE then
        bufferzone_counter = bufferzone_counter + change
    else
        // reset the buffer zone
        bufferzone_counter = 0
        // Get and set the knob position
        newKnobPos = GetNewKnobPosition(change)
        SetKnobWidget(newKnobPos)
    end
End

On WidgetValueChanged(newValue : double) from VibCh_Knob
    var newPosition : integer = GetKnobPositionFromDouble(newValue)  
    Print("Widgetchanged")
End    

If someone wants to use this script, you will need to make sure you give a GPScript name to the MIDI IN block that has your encoder/knob and put that at the top (mine is “MainKeys”). Also, in the ControlChangeEvent, you will need to set the right CC value.

Stuff to improve later:

  • Determine if I can simplify or make this easier to use by using a scriptlet
  • Handle the manual changing of the on-screen widget better. Right now, you can move it on-screen, and it will work, but it won’t “snap” to the labels/positions until you change variation or rackspace.
1 Like

Thanks for your reply … I’m sure it will help others looking for a similar solution or as base for their own script.