How to detect and act on only the final value of a widget knob twist

Hi.

Suppose I have a knob sending PCs. If I move the knob from 20 to 50, my goal is to send out PC 50. I do not want to send PCs 21-49 in between (all those extra PCs make a bad reaction downstream).

Is this possible to accomplish in a scriptlet?

Has anybody written such, or else at least sees clearly what the algorithm for it would be?

Thanks!

Aside: If this can’t be done in a scriptlet, would it be possible/reasonable for the GP developers to add an on/off property to knob widgets that would make them behave this way?

You can scale the widget to retain certain values until the desired value is reached.

94

The standard way to do this is to use a timer and wait a certain amount if time before deciding there’s no more data and then sending out the last seen value.
And so you could absolutely do this with a script or scriptlet.

3 Likes

That won’t work. It presumes you know in advance what will be the value you want to generate.

When you do, it works fine.
For instance, if you have ranges of 1-21, 50-75, and 100-127 where you want to ignore the numbers not included in those ranges.

1 Like

Seems logical, but Is this something that needs to be done outside the realm of callbacks?

From within a callback like this:

On ParameterValueChanged matching ProgramChangeNumber

End

How I can know there are no more future values (“asks”) coming in for this particular knob twist.

I’m not having any problem ignoring many asks until some time has elapsed since the last one - but my problems seem to be 1) not knowing the first ask is the first (so I can in general ignore it) 2) not knowing that last ask is the last (so I can for sure send it and reset the situation)

When I understand correctly, you want to have a grid for the widget values, right?

Can I create a timer of my own and have it call me back?

I don’t follow this question, sorry. But I can state the topic sentence differently, in case it helps.

How can I detect and act on only the final value of a widget knob twist? (ignoring all the values traversed in-between)

OK, now I understand.

Yes, you can use the TriggerOneShotRamp and the StopOneShotRamp system functions

By the way - the title of this topic is very misleading. In the MIDI world, thinning does not mean just sending the last value, it just means to not send every single value along the way, so as to REDUCE the amount of data sent out.

1 Like

I made a short POC

Var
   PC : Widget
   R  : Ramp


On WidgetValueChanged(newValue : double) from PC
 TriggerOneShotRamp(R, 500, 10)
End


On GeneratorEndCycle(timeX : integer) from R
 // Print(timeX)
 // Send Out PC message
End
2 Likes

I’m here to offer low-handing fruit. Hah!

FWIW, I would usually create another song part with the widget and save the value to that song part via “snapshot”. Then I would change song parts to get the change in the widget.

[This may not be helpful for your use case].

I would think you could also create two widgets with two different settings with bypass. Then you could set up a button widget and use a button widget (associated with a controller button) to bypass one instead of the other (radio button?). (I’d have to work out this way of doing it. I only have 3 buttons that send midi, so I use song parts for everything).

Jeff

I forget - when you a trigger a ramp that is already running, does that reset it automatically or do you have to stop the old one first (I’m not at my computer so can’t look at the functions)

I think no need to stop

Thanks to all who assisted above!

I have arrived at a scriptlet that works well for me.

Here it is:

/*
    Scriptlet:  PC Knob Final Value 01a   (Sends PCs)
    
    Written to pair with a knob widget, an Incr widget, and a Decr widget
    
    This code always sends the final value for the connected knob, but not all the values in-between as the knob is turned.
    
    This can be very helpful if gear downstream doesn't respond all-so-well to a blast of multiple program change messages (PCs).
    
    Note: Results are timer-based.  if you twist the connected widget knob slowly, then some intermediate values will go out at
    intervals of SnapShot_Time_ms.  SnapShot_Time_ms is a Parameter, so you can control it also via widge if you like, or else 
    just accept the value compiled from the code here.
    
    Use Case #1:   Sending PCs to BIAS FX plugin to select guitar patch presets
    
    01a (Progster, 20240916)    Initial version    
    
*/

var
   
	ProgramChangeNumber ("Program"): Subrange Parameter 0 .. 127 = 0
	Decr : Parameter 0..1 = 0   
    Incr : Parameter 0..1 = 0 
    
    //  specify a nominal 500ms ramp  - we will wait for it's completion for-to-send the PC    
    SnapShot_Time_ms : Parameter 0..2000 = 500   
	
	Last_PC_Sent : Integer = 0
	
	PC_Ever_Sent : Boolean = False
	Waiting_For_Ramp : Boolean = False
	
	MyRamp  : Ramp
	
	Ramp_Start_Time: Double
	Ramp_End_Time: Double
	Ramp_Elapsed_Time : Double


function SendPchange( PC : Int )

    if ( PC <> Last_PC_Sent ) then                  // never send the same PC twice in a row
        //Print( "Called SendPchange: " + PC )
        SendNow( MakeProgramChangeMessage(PC) )     // PC message on Ch 1
	    Last_PC_Sent = ProgramChangeNumber    
        Print( "Last_PC_Sent: " + Last_PC_Sent )
    end
	
end


On GeneratorEndCycle(timeX : integer) from MyRamp

    Ramp_End_Time = TimeSinceStartup()
    Ramp_Elapsed_Time = Ramp_End_Time - Ramp_Start_Time
    //Print( "Ramp Finished: " + TimeSinceStartup() + "   Ramp Elapsed time: " + Ramp_Elapsed_Time )
    
    // Send Out PC message
    SendPchange( ProgramChangeNumber )      //  ProgramChangeNumber is always most current one as provided by the connected knob widget 
        
    //  reset to "neutral" state
    Waiting_For_Ramp = False
    
End


//  ParameterValueChanged callbacks

On ParameterValueChanged matching ProgramChangeNumber    
	
    //  If in the "neutral" state, start a new Ramp to wait for
    if ( !Waiting_For_Ramp ) then
        Ramp_Start_Time = TimeSinceStartup()
        TriggerOneShotRamp(MyRamp, SnapShot_Time_ms, 10)         
        //Print( "Ramp Started: " + Ramp_Start_Time + "   SnapShot_Time_ms: " + SnapShot_Time_ms )
        Waiting_For_Ramp = True
    end	
	
End

//  Act on change of connected widget used for Increment of preset
On ParameterValueChanged matching Incr
    ProgramChangeNumber = Last_PC_Sent
    //Print( "ParameterValueChanged matching Incr " + Incr )
    if (Incr == 1) and (Last_PC_Sent < 127) then
        //Print( " Incr button" )
        ProgramChangeNumber = ProgramChangeNumber + 1
		SendPchange( ProgramChangeNumber )
		Last_PC_Sent = ProgramChangeNumber
	end
End

//  Act on change of connected widget used for Decrement of preset
On ParameterValueChanged matching Decr
    ProgramChangeNumber = Last_PC_Sent
	//Print( "ParameterValueChanged matching Decr " + Decr )
    if (Decr == 1) and (Last_PC_Sent > 0) then
        //Print( " Decr button" )
		ProgramChangeNumber = ProgramChangeNumber - 1
		SendPchange( ProgramChangeNumber )
		Last_PC_Sent = ProgramChangeNumber
	end
End   

And here is a short video demo of it in action:

4 Likes

When I first read your question, I thought that it would best be done with a bank/patch number, using 2 rows of 8 buttons (Juno 106 style). Press the first one for the bank, the second one for the patch, calculate the PC, send out the result. Or of course two rows of 10 such that you can have a range 0-99.

Your solution seems te work very well (thanks voor de demo video). Is it easy to turn the knob to the exact number you want, or do you have to slow down towards the target and end up sending 2 or 3 PCs after all?

I find it pretty easy to target and hit a number via knob twist, especially given that the delay interval can be set to individual preference. However, as is clear, this is not a “guaranteed” final-value-only solution. I think such might require psychic abilities on the part of the computer. :wink:

Not shown in the video is that the “<” and “>” buttons decrement and increment the value by 1. So, you won’t ever have to get stuck trying and failing to move the knob by a tiny increment to get a -1 or +1 change.

Since widget values save with rackspaces and song parts, once you have found a desired patch for a specific situation it is subsequently “just there”. So the widgets both assist in discovery and then save vetted results of discovery.

In my case, I’ve built and MIDI mapped a bank in BIAS FX where all my chosen clean patches are in a range, all the distorted or high gain patches are in another range, and all the weird FX in yet another range. I do discovery by twisting the knob into the desired range, then use the buttons to move one-by-one for auditions to find my final best choice for a given situation.

By the way, for those who just need a way to type in a specific program change number, there is an easier way. Simply map a label to the PC parameter the MIDI block of interest and unlock the label. You can then just click on it to see the underlying value, click again to get a touchpad through which you can enter a specific MIDI program change number.

445C527C-E45D-4C7D-B8EE-41BC057DFD5A-15194-000E54AD4D186B75

2 Likes