Move multiple faders relatively with one single knob (DCA-like)

Hello all,

i finally succeeded in mimicking a DCA-behaviour in Gig Performer via GP-Script.
The regarding rackspace has three faders, a knob and a switch…
The switch changes between “direct mode” and “relative mode”, means:

Direct mode:

  • the knob will vanish and you can adjust the three faders independently at will
  • this will then be the initial positions for the “relative movement”

Relative mode:

  • the knob will be shown and take the value of the highest fader position
  • movement of the individual faders will be blocked
  • moving the knob will move all faders together, respecting their relative positions to each other
  • the faders won’t move further if the fader with the max. position has reached the top
  • the faders can be moved to "flat zero but will return to their relative positions
    when climbing higher again

This is how it looks like:

This is the rackspace file (download & import it):
Relative Faders.rackspace (86.7 KB)

And this is the code:

// the widgets used
knb_rel : widget
fdr1, fdr2, fdr3 : widget
swt_rel : widget

//the arrays used
faders : widget array = [fdr1, fdr2, fdr3]
fdr_buffers : double array = [0.0,0.0,0.0]
fdr_offsets : double array = [0.0,0.0,0.0]

maxfader_index : integer // stores the fader-array index nr. with the largest value
maxfader_value : double // stores the actual largest fader value

faderlock : boolean // auxiliary flag if fader movement should be blocked

knb_rel_old : double // stores the "old" value of the relative knob

waiting_time : double = 20 //time to wait in ms
move_start : double //the moment the widget started to change

// user function to store the actual fader position into an array
function calc_buffers ()
var i : integer
    for i=0; i<Size(fdr_buffers); i=i+1 Do
       fdr_buffers[i] = GetWidgetValue (faders[i])

// user function to get the max value and index and update the fader-buffers
function calc_maxfader(index : integer, fdr_val : double)
    fdr_buffers [index] = fdr_val
    maxfader_value = LargestDouble(fdr_buffers)
    maxfader_index = iMax(fdr_buffers)
    // if the relative mode switch is ON then also adjust the rel.knob according to the max fader value
    if GetWidgetValue (swt_rel) == 1.0 Then
        SetWidgetValue (knb_rel, maxfader_value)

// user function to calculate the relative positions of the faders to the max. as negative offsets
function calc_offsets ()
var i : integer
    for i=0; i<Size(fdr_buffers); i=i+1 Do
       fdr_offsets[i] = fdr_buffers[i]-maxfader_value
    knb_rel_old = maxfader_value //set the rel. knob accordingly to the max. fader value

// user function to move all faders together, relatively to the rel. knob's position
// and respecting their offset to the max.
function move_relative(kval:double)
var i : integer    
    faderlock = false //the faders may move now!
    //loop through the offset-array and set the faders accordingly
    for i=0; i<Size(fdr_offsets); i=i+1 Do
       SetWidgetValue (faders[i], kval + fdr_offsets[i])

// will be executed on first use of the rackspace (or on compile)
var i : integer
    // get the initial fader-situation and max-value/index
    for i=0; i<Size(faders); i=i+1 Do
       calc_maxfader (i, GetWidgetValue (faders[i]))
    //check the initial state of the relative-mode switch and set faderlock accordingly
    if GetWidgetValue (swt_rel) == 1.0 Then //relative mode switch enabled?
         faderlock = true 
        faderlock = false

    // get the initial position of the rel.knob and store in buffer variable
    knb_rel_old = GetWidgetValue (knb_rel)
    calc_offsets() // get the initial offset situation for the fader values

// timer routine which works as a time-out for knob-movement
On TimerTick(ms : double)
    If ms - move_start > waiting_time Then // if the waiting time is up then 
        SetTimersRunning(false) // stop the timer
        calc_buffers() // refresh buffers
        faderlock = true // prevent fader movement

// this happens when the faders move (multi-widget operation)
On WidgetValueChanged(w : Widget, index: integer, fval : double) from fdr1, fdr2, fdr3
    if GetWidgetValue (swt_rel) == 0.0 and faderlock == false Then //relative mode disabled and faders unlocked?
        calc_maxfader (index, fval) //yes: calculate the max-values and the buffers
    Elsif  faderlock == true Then  //movement frozen
        SetWidgetValue (faders[index], fdr_buffers[index]) //set the widgets to the (old) buffer values -> no movement

// this happens when the relative knob moves 
On WidgetValueChanged (kval : double) from knb_rel
    if GetWidgetValue (swt_rel) == 1.0 Then //relative mode switch enabled?
        faderlock = false // allow fader movement
        // start timer to wait for time-out 
        // -> while time is running the "On TimerTick()" routine will be permanently checked!
        move_start = TimeSinceStartup() // get the starting moment
        SetTimersRunning(true) // GO!
        move_relative (kval)// now set all faders accordingly to the rel. knob

    else //no relative mode: freeze the knob position to knb_rel_old
        SetWidgetValue (knb_rel, knb_rel_old) // restore former position from buffer

// This happens when the relative switch is triggered
On WidgetValueChanged (sval : double) from swt_rel
    if sval > 0.6 then // if relative-mode = ON
        calc_offsets() // refresh offset values of the fader array
        SetWidgetHideOnPresentation(knb_rel, false) // un-hide the reative knob
        SetWidgetValue (knb_rel, maxfader_value) // set knob to the max. fader value
    else // if relative mode = OFF
        SetWidgetHideOnPresentation(knb_rel, true) // hide knob
        faderlock = false // allow fader movement

Wow, this is amazing! Exactly what I needed, thank you! Can’t wait to get home to try it! Amazing work.

1 Like

Thanks man! :beers:
I first thought it would be an easy task, but it turned out to be really tricky in the details…
There occured some timing issues which kept the script from working properly and because i wanted it to behave somehow “bidirectonally” between the faders and the knob, i had to struggle not to run into infinite loops, because wihout taking care of the exact “who” and “when”, moving the faders will move the knob, will move the faders, will move the knob… :skull_and_crossbones:
But in the end (after having some more grey hairs now) it was really interesting and also some weird kind of fun. :nerd_face: :crazy_face:


What improvements to GPScript would have made this easier?


The most helpful improvement would be some kind of “silent” SetWidgetValue() command which would not trigger the “On WidgetValueChanged()” callback, or generally speaking, a possibility to distinguish wether the Value change was caused “internally” by script or “externally” (manual mouse move, or by MIDI).
I know we already had a discussion about this or something similar, but could not find it in the forums anymore…
Here i wanted to use a flag (boolean) which should be checked to see if the fader were “allowed” to be moved or not, i intended to “break” the mutual dependency of the faders and the knob with this, but only using the flag didn’t work - i guess because of a race condition… the boolean didn’t keep it’s value, or was at least already reset when i needed it.
That was the point when i decided to use a timer to see when there was no more knob movement and then i could use the flag boolean successfully.

1 Like

I have got the feeling that we did something similar with @LeeHarvey in our youth :wink: So, I don’t remember if it we did this in a very clean way, but I just tested, it seems to work. The difference with the solution above is that there is no “lock” switch, it works seamlessly.

MasterRatioFader.gig (69.6 KB)


@schamass …I love this…thanks for your diligence and creativity!

1 Like

By the way, what do you use to produce this kind of cropped very cool video with displayed mouse pointer?

1 Like

It’s a small freeware tool named “screen to gif”.