Octave-transpose with radio buttons

I recently wrote a script (maybe not very elegant, but it works :wink: ) to realize a so called "radio-button"functionality. Some of you may still know those station buttons on old radios, where only one could be pressed at a time. I did that with LED-buttons.
In this example-rackspace i used this script to switch the transpose-setting for incoming notes by whole octaves. So there are seven buttons for a transposition of -3, -2, -1, 0, +1, +2, +3 octaves.
Maybe you have to insert a new MIDI-IN block, which then had to be named as “tp_midi_in” for the script to work properly.

Here is the link to the rackspace (you have to import it to your gig-file!):

EDIT: There was a faulty button-configuration in the rackspace which caused the buttons to flicker around and refuse to react - i uploaded a new one.
Sorry for any inconvenience

And this is the code (which also is embedded in the rackspace)… have fun. :sunglasses:

var 


bt_vs1 : widget
bt_vs2 : widget
bt_vs3 : widget
bt_vs4 : widget
bt_vs5 : widget
bt_vs6 : widget
bt_vs7 : widget

tp_midi_in : MidiInBlock

vs1_active : boolean
vs2_active : boolean
vs3_active : boolean
vs4_active : boolean
vs5_active : boolean
vs6_active : boolean
vs7_active : boolean

tp_amt : integer

Function set_tp (vs1 : double, vs2 : double, vs3 : double, vs4 : double, vs5 : double, vs6 : double, vs7 : double)
    SetWidgetValue(bt_vs1, vs1)
    if vs1 == 0 then
        vs1_active=false
    else
        vs1_active=true
    end

    SetWidgetValue(bt_vs2, vs2)
    if vs2 == 0 then
        vs2_active=false
    else
        vs2_active=true
    end

    SetWidgetValue(bt_vs3, vs3)
    if vs3 == 0 then
        vs3_active=false
    else
        vs3_active=true
    end
    
    SetWidgetValue(bt_vs4, vs4)
    if vs4 == 0 then
        vs4_active=false
    else
        vs4_active=true
    end
    
    SetWidgetValue(bt_vs5, vs5)
    if vs5 == 0 then
        vs5_active=false
    else
        vs5_active=true
    end

    SetWidgetValue(bt_vs6, vs6)
    if vs6 == 0 then
        vs6_active=false
    else
        vs6_active=true
    end

    SetWidgetValue(bt_vs7, vs7)
    if vs7 == 0 then
        vs7_active=false
    else
        vs7_active=true
    end

end

initialization
    vs1_active = false
    vs2_active = false
    vs3_active = false
    vs4_active = false
    vs5_active = false
    vs6_active = false
    vs7_active = false
end


On Activate// Called when rackspace is activated
   // SetWidgetValue (bt_vs4,1) //Button #4 is activated -> Transpose=0
End

//Called when a NoteOn or NoteOff message is received at some MidiIn block
//Note: this will NOT be called for NoteOn/NoteOff messages if you have explicit
//NoteOnEvent/NoteOffEvent respectively callbacks defined

On NoteEvent(m : NoteMessage) from tp_midi_in
    SendNow(tp_midi_in, Transpose(m,tp_amt))
End




// Called when a widget value has changed


On WidgetValueChanged(tp_val : double) from bt_vs1
    if tp_val == 1 And vs1_active == false then
       set_tp (1,0,0,0,0,0,0)
       tp_amt = -36
    end
    
    if tp_val == 0 And vs1_active == true then
        vs1_active = true
        SetWidgetValue(bt_vs1,1)
    end
end

On WidgetValueChanged(tp_val : double) from bt_vs2
    if tp_val == 1 And vs2_active == false then
       set_tp (0,1,0,0,0,0,0)
       tp_amt = -24
    end
    
    if tp_val == 0 And vs2_active == true then
        vs2_active = true
        SetWidgetValue(bt_vs2,1)
    end
end

On WidgetValueChanged(tp_val : double) from bt_vs3
    if tp_val == 1 And vs3_active == false then
       set_tp (0,0,1,0,0,0,0)
       tp_amt = -12
    end
    
    if tp_val == 0 And vs3_active == true then
        vs3_active = true
        SetWidgetValue(bt_vs3,1)
    end
end

On WidgetValueChanged(tp_val : double) from bt_vs4
    if tp_val == 1 And vs4_active == false then
       set_tp (0,0,0,1,0,0,0)
       tp_amt = 0
    end
    
    if tp_val == 0 And vs4_active == true then
        vs4_active = true
        SetWidgetValue(bt_vs4,1)
    end
end

On WidgetValueChanged(tp_val : double) from bt_vs5
    if tp_val == 1 And vs5_active == false then
       set_tp (0,0,0,0,1,0,0)
       tp_amt = 12
    end
    
    if tp_val == 0 And vs5_active == true then
        vs5_active = true
        SetWidgetValue(bt_vs5,1)
    end
end

On WidgetValueChanged(tp_val : double) from bt_vs6
    if tp_val == 1 And vs6_active == false then
       set_tp (0,0,0,0,0,1,0)
       tp_amt = 24
    end
    
    if tp_val == 0 And vs6_active == true then
        vs6_active = true
        SetWidgetValue(bt_vs6,1)
    end
end

On WidgetValueChanged(tp_val : double) from bt_vs7
    if tp_val == 1 And vs7_active == false then
       set_tp (0,0,0,0,0,0,1)
       tp_amt = 36
    end
    
    if tp_val == 0 And vs7_active == true then
        vs7_active = true
        SetWidgetValue(bt_vs7,1)
    end
end
1 Like

This is really useful. I’d like to make a few suggestions for improvement, mostly because I think everyone who uses GP Script will benefit

Item 1
It is never necessary to write
if active == true
You can simply write
if active

Item 2
Instead of

if vs1 == 0 then
        vs1_active=false
    else
        vs1_active=true
end

you can just write

vs1_active = vs1 > 0

Item 3

Noticing that there is a lot of duplication in the code, we can create functions to do the common work. We simply need to parameterize the widgets. So here is a complete rewrite of the original using functions. Study this by first looking at the Widget callbacks at the bottom and then follow the function calls.

var 

   // The actual widgets in the rackspace
   bt_vs1 : widget
   bt_vs2 : widget
   bt_vs3 : widget
   bt_vs4 : widget
   bt_vs5 : widget
   bt_vs6 : widget
   bt_vs7 : widget

   // We'll actually keep these in an array and just index them
   // That will allow us to use some functions that will work for all widgets
   radioButtons : Widget Array // Dynamic array
   vs_active : Boolean[7] // Version 2.4 doesn't support dynamic arrays for boolean (will be fixed in next update)

   tp_midi_in : MidiInBlock

   tp_amt : integer

initialization
    var i : integer
    
    tp_amt = 0 // Yeah, it's default but safer to do this
    
    radioButtons = [bt_vs1, bt_vs2, bt_vs3, bt_vs4, bt_vs5, bt_vs6, bt_vs7]
    
    // Not yet implemented - but coming in next update
    //vs_active = [false, false, false, false, false, false, false]
    // So for now just use a FOR loop
    for i = 0; i < 7; i = i + 1 do
        vs_active[i] = false
    end

end

// The array here indicates the value to which each widget should be set
Function UpdateAllWidgets (vs : Integer Array)    
    var i : integer
    
    for i = 0; i < 7; i = i + 1 do
        SetWidgetValue(radioButtons[i], vs[i])
        vs_active[i] = vs[i] > 0
    end    

end


On Activate// Called when rackspace is activated
   // SetWidgetValue (bt_vs4,1) //Button #4 is activated -> Transpose=0
End


On NoteEvent(m : NoteMessage) from tp_midi_in
    SendNow(tp_midi_in, Transpose(m,tp_amt))
End



// Called when any widget value has changed to do the actual work
Function ProcessAndSetTranspose(widgetIndex : integer, widgetValue : Double, transposeAmount : integer)
    var vs : integer array
    vs = [0,0,0,0,0,0,0] 
    
    if widgetValue == 1 and not vs_active[widgetIndex]
       then
          vs[widgetIndex] = 1 // This is the one we need to make active
          UpdateAllWidgets (vs)
          tp_amt = transposeAmount
    end
   
End

On WidgetValueChanged(tp_val : double) from bt_vs1
   ProcessAndSetTranspose(0, tp_val, -36)
end

On WidgetValueChanged(tp_val : double) from bt_vs2
   ProcessAndSetTranspose(1, tp_val, -24)
end

On WidgetValueChanged(tp_val : double) from bt_vs3
   ProcessAndSetTranspose(2, tp_val, -12)
end

On WidgetValueChanged(tp_val : double) from bt_vs4
   ProcessAndSetTranspose(3, tp_val, 0)
end

On WidgetValueChanged(tp_val : double) from bt_vs5
   ProcessAndSetTranspose(4, tp_val, 12)
end

On WidgetValueChanged(tp_val : double) from bt_vs6
   ProcessAndSetTranspose(5, tp_val, 24)
end

On WidgetValueChanged(tp_val : double) from bt_vs7
   ProcessAndSetTranspose(6, tp_val, 36)
end
1 Like

Thanks for your optimizations!
I have never seen a term like “vs1_active = vs1 > 0” and it seems a little bit cryptic at first glance. But nevertheless it’s also very logical.

Working with arrays is also something i have not done very often before, but it makes everything much more flexible and shorter in code.

But there is one issue i found with your script: It is now possible to switch a button from ON to OFF by pressing it twice. This should not work. The button had to stay ON.

But thanks again for your advice. I really appreciate it.
There is a lot of things i can learn from this. :+1:

EDIT
I changed the IF-statement in the function as follows:

Function ProcessAndSetTranspose(widgetIndex : integer, widgetValue : Double, transposeAmount : integer)
    var vs : integer array
    vs = [0,0,0,0,0,0,0] 
    
    if widgetValue == 1 and not vs_active[widgetIndex] or widgetValue==0 and vs_active[widgetIndex]
       then
          vs[widgetIndex] = 1 // This is the one we need to make active
          UpdateAllWidgets (vs)
          tp_amt = transposeAmount
    end
End

I was curious if it was possible to nest the constraints like this, but it works flawlessly.
Great! :slight_smile:

1 Like

I’m not sure what you mean by nested constraints.

I would however encourage you to use parentheses in the boolean expression, i.e.

if (widgetValue == 1 and not vs_active[widgetIndex]) or (widgetValue==0 and vs_active[widgetIndex])

While it is the case that or has lower precedence than and so that what you wrote will work, sometimes it’s never totally clear and parentheses make it more obvious.

OK… it seems that “nesting” was the wrong word. I thought of something like “combining”.
I also didn’t know that it can be put in parenthesis, and doing this makes it easier to understand. What can i say? Thanks again. :slight_smile:

Very useful, indeed. Thank you both.

Thanks for the tip @schamass and @dhj ,

I’ve used the radio button technique to select the inversion to be used in triads that are auto generated from a root note. When that worked as desired I tried to make a key selector in more or less the same way. It looks like this.


It is a horizontal slider, which floating point value gets scaled to an integer between 0 and 11. Then if the modifier is released the SetWidgetValue() function is used to center the slider in steps of 1/11, such that it aligns with the keyboard I drew underneath it. It sort of works, but I’m not entirely satisfied. Is there a nicer way to do this?
The function is as follows:

On WidgetValueChanged(newVal : double) from key
var roundedVal : double
key_centre = ScaleRange (newVal, 0, 11)
roundedVal = IntToFloat(key_centre)/11
if selKey != key_centre
  then selKey = key_centre
       SetWidgetValue (key, roundedVal)
end
end