# Curves and Tables



## paoling (Jun 17, 2011)

Hi..! I've recently made a kind of table edit script. Since I can't read very well the standard Kontakt 4 "Change Velocity" algorithm, I decided to write it using the formula of quadratic bezier curves. It works, but I'm a bit limited about the curve parameter, because I have to restrict its range not to end out of the table values.
So I'm a bit dubious that the bezier curve is not the ideal for my job, or it could be if I could move the middle point on the x-axis (don't know how to do). Add: for doing the curve I have a kind of invisible middle point that "pulls" the function up and down.
Oh, maybe there are other curve types for this job, or a kind of recursive smoothing procedure that can affect the array... Well I need a matemagician 

on init {a kind of table edit script}
declare ui_table %table1[127] (2, 4, 127)
make_persistent (%table1)

declare ui_slider $min1(0, 127) 
make_persistent($min1)
declare ui_slider $max1(0, 127) 
make_persistent($max1)
declare ui_slider $curve1(0, 127) 
make_persistent($curve1)
declare ui_slider $offset1(-127, 127) 
make_persistent($offset1)
declare $counter1
declare $curve_range
declare ui_label $Min (1,1)
move_control ($Min,3,2)
declare ui_label $Max (1,1)
move_control ($Max,4,2)
declare ui_label $Curve (1,1)
move_control ($Curve,5,2)
declare ui_label $Offset (1,1)
move_control ($Offset,6,2)
$counter1 := 0
declare $max_range
$max_range:=127
declare $min_range
$min_range:=0
declare $range_value
end on
function range_value
if ($range_value>$max_range)
$range_value:=$max_range
end if
if ($range_value<$min_range)
$range_value:=$min_range
end if
end function

function table1_refresh
$curve_range:=($max1+$min1)/2+($curve1-67)*abs($max1-$min1)/(127)


$counter1 := 0
while ($counter1<127)
%table1[$counter1] := ((127-$counter1)*(127-$counter1)*$min1+(2*$counter1*(127-$counter1)*$curve_range)+($counter1*$counter1*$max1))/16129
$range_value:=%table1[$counter1] + $offset1
call range_value
%table1[$counter1]:= $range_value
inc($counter1)
end while
end function

on ui_control($offset1)
call table1_refresh
end on
on ui_control($curve1)
call table1_refresh
end on

on ui_control($min1)
call table1_refresh
end on

on ui_control($max1)
call table1_refresh
end on


----------



## paoling (Jun 18, 2011)

Done!
I added another quadratic bezier to modify the counter value, in this way curves are more sharp and this is a little but complete table curve edit script 


on init 
declare ui_table %table1[127] (2, 4, 127) 

make_persistent(%table1) 
declare ui_slider $min1(0, 127) 
make_persistent($min1) 
declare ui_slider $max1(0, 127) 
make_persistent($max1) 
declare ui_slider $curve1(0, 127) 
make_persistent($curve1) 
declare ui_slider $offset1(-127, 127) 
make_persistent($offset1) 
declare $counter1 
declare $curve_range 
declare ui_label $Min(1, 1) 
move_control($Min,3,2) 
declare ui_label $Max(1, 1) 
move_control($Max,4,2) 
declare ui_label $Curve(1, 1) 
move_control($Curve,5,2) 
declare ui_label $Offset(1, 1) 
move_control($Offset,6,2) 
$counter1 := 0 
declare $max_range 
$max_range := 127 
declare $min_range 
$min_range := 0 
declare $range_value 
declare $x_axis 
declare $x_axis_curve
end on 

function range_value 
if ($range_value>$max_range) 
$range_value := $max_range 
end if 
if ($range_value<$min_range) 
$range_value := $min_range 
end if 
end function 

function table1_refresh 
$curve_range := ($max1+$min1)/2+(($curve1-67)*abs($max1-$min1)/127) 
$counter1 := 0 
while ($counter1<127) 
if ($min1<$max1)
$x_axis_curve:= $curve_range
else
$x_axis_curve:= 127-$curve_range
end if

$x_axis := ((127-$counter1)*(127-$counter1)*1+(2*$counter1*(127-$counter1)... 
*($x_axis_curve))+($counter1*$counter1*127))/16129 
%table1[$counter1] := ((127-$x_axis)*(127-$x_axis)*$min1+(2*$x_axis*... 
(127-$x_axis)*$curve_range)+($x_axis*$x_axis*$max1))/16129 
$range_value := %table1[$counter1]+$offset1 
call range_value 
%table1[$counter1] := $range_value 
inc($counter1) 
end while 
end function 

on ui_control($offset1) 
call table1_refresh 
end on 

on ui_control($curve1) 
call table1_refresh 
end on 

on ui_control($min1) 
call table1_refresh 
end on 

on ui_control($max1) 
call table1_refresh 
end on


----------



## polypx (Jun 18, 2011)

Hey! Nice work Paolo, thanks for sharing it.

cheers
Dan


----------



## paoling (Jun 19, 2011)

I'm very happy that you like it..!


----------



## gregjazz (Jun 19, 2011)

To decrease CPU usage, try using lookup tables for exponents, for example squares or cubes--depending on what kind of bezier curve you want (i.e. quadratic, cubic, etc.)


----------



## paoling (Jun 19, 2011)

Thank you so much for your suggestion Greg!
I never thought about code optimization, but moving the sliders shows a little spike in CPU calculations (there are about 12*127 multiplications every change in parameter, a drag of the slider can really be CPU intensive!).

You really opened to me a little world, since I didn't know about lookup tables  
Thank you so much.


----------



## gregjazz (Jun 19, 2011)

Yeah, with lookup tables, you should be able to get it down to 4*127 multiplications...


----------



## mk282 (Jan 11, 2014)

Remember that sliders often cause callbacks, so if you add a persistent array to hold slider values, then if you test if each slider's value changed, you would get a dramatic decrease in generated callbacks - a callback would only be run if the slider value actually changed!


----------



## Mike Greene (Jan 11, 2014)

Mario, are you saying that if I use a slider variable somewhere in my "on note" callback, for instance like this:


```
on init
	declare $velocity_slider (1,127)
end on

on note
	ignore_event ($EVENT_ID)
	play_note ($EVENT_NOTE, $velocity_slider, 0, 0)
end on

on ui_control ($velocity_slider)
    *** Do complicated stuff that Mike never understands anyway ***
end on
```
then the ui_callback for the slider gets called whenever I play a note?


----------



## mk282 (Jan 11, 2014)

No, I'm not saying that. I'm just saying that to limit the flurry of callbacks created when you move a slider (and the value of the slider didn't necessarily change, this is more often happening when you have sliders that have 0-127, -50 to 50, -100 to 100 etc ranges, rather than the default 0-1000000 sliders), you use this kind of code in UICB:

No, you have to do it in the slider callback itself.


```
on ui_control (slider)
    if slider # prev_value
        do stuff
        prev_value := slider
    else
        slider := prev_value
    end if
end on
```


----------



## Big Bob (Jan 11, 2014)

I think you can just omit the 'else' clause :wink:


----------



## Mike Greene (Jan 11, 2014)

Ah, that makes sense, Mario. Thanks for explaining, because I do indeed have sliders with relatively small ranges, so this will keep CPU down a bit. 8) 

Funny Bob, I didn't notice that until you pointed it out. :mrgreen:


----------



## mk282 (Jan 11, 2014)

Big Bob @ 11.1.2014 said:


> I think you can just omit the 'else' clause :wink:



Depends on the usage case. Sometimes I really want this with custom knobs that have many animations to "snap" to just several steps instead.


----------



## Big Bob (Jan 11, 2014)

Oh my Goodness Mario,

I must be on Candid Camera or in Bizzaro World :shock: 

If the expression (slider # prev_value)* fails*, doesn't it mean that 'slider' must be equal to prev_value? If something is either Up or Down and it's not Down, then it must be Up, no? :roll: And, if so, what does the else clause assignment statement do for you; isn't it essentially setting slider := slider ?? 

So, other than some KSP bug (like the old problem with calls and empty case constructs, etc, etc), I fail to see that there should be any difference in behavior with or without the else clause.

So, I must be having a senior moment :lol:


----------



## mk282 (Jan 12, 2014)

You're not on candid camera 

"else" is really necessary here (in SOME cases)! Here's an example: I have a GUI using custom knobs. Let's say my default knob has 128 animated frames, which is great. Now, let's say I want to use the SAME knob design, but for a parameter that has only 5 values (for example - LFO waveform selector knob).

If I don't want to have two different variants of the same PNG image of the knob (one with 128 frames and one with 5 frames), I can use just the one with 128 frames for convenience. If I go your way and don't put the "else" clause, the knob will go through all 128 frames EVEN THOUGH the parameter itself only has 5 discrete positions! Which means it will show in-between positions which are irrelevant to the parameter. If I do put the "else" part here, the knob will snap to only the necessary 5 positions.

That's how it is. I wouldn't post it if I didn't extensively test it 

See this video proof of what I'm talking about:

http://i.imgur.com/EZh7l0I.gif


----------



## polypx (Jan 12, 2014)

That's really interesting MK. I had never run into this problem of using high resolution animations for lower resolution controls, but nor had I needed to use your "else check".

Look what happens... as soon as you set a label with the value, the snap happens automatically! This is why I never had to use your method I guess.

http://imgur.com/QVfakcb

cheers, Dan


----------



## Big Bob (Jan 12, 2014)

Ah Ha Mario! 

This* is* another NI Bizzaro World situation then. :lol: 

Before reading Dan's post I suspected the reason for the behavior you are illustrating with your LICE Cap is simply some kind of KSP timing issue. Dan's post more or less confirms this.

In other words it's not so much what the else clause does as it is the fact that it burns a little time. I suspect you could replace *slider := prev_value* with *slider := slider* for example and get pretty much the same result. Moreover, you probably don't need the else clause at all; just something to burn a little time when the if clause fails.

But since (for whatever Bizzaro World reason) NI seems to require something after the if clause fails, I guess using:


```
else
  slider := prev_value
```

isn't too bad because it does at least appear to be somewhat logical. :? 

However, so as not to obscure why it is there, a simple *nop* line with a comment for why it's there might be clearer don't you think? Otherwise there will always be someone like me who comes along and says this else clause is unnecessary :lol: 

Rejoice 

Bob


----------



## mk282 (Jan 12, 2014)

polypx @ 12.1.2014 said:


> That's really interesting MK. I had never run into this problem of using high resolution animations for lower resolution controls, but nor had I needed to use your "else check".
> 
> Look what happens... as soon as you set a label with the value, the snap happens automatically! This is why I never had to use your method I guess.
> 
> ...



Interesting. Good to know!


----------



## Big Bob (Jan 12, 2014)

Hey Mario,

Just for kicks, could you try using your graphics and just put a nop in front of the if clause (instead of using the else clause)? Perhaps something like this:


```
on ui_control (slider) 
    prev_value := prev_value  { nop to pacify the KSP }
    if slider # prev_value 
        do stuff 
        prev_value := slider 
    end if 
end on
```

I don't have any special graphics handy but I tried this with a standard knob (0,3,1) and it seems to 'snap' about the same with the nop as it does with the else clause.

If this works about the same for you it would seem to indicate that the KSP needs to execute at least one non-branch instruction in the callback handler to guarantee 'snapping' :roll: .

Rejoice,

Bob


----------



## mk282 (Jan 13, 2014)

Nope, doesn't snap the control with your code.


----------



## mk282 (Jan 13, 2014)

Nope, doesn't snap the control with your code.

I think this relates to ui_sliders only (since they're skinnable), not ui_knobs.


----------



## polypx (Jan 13, 2014)

Interestingly tho, this DOES cause a snap:


```
on ui_control (slider) 
slider := slider
end on
```

KSP is wonderfully quirky. 



EDIT
I wonder if Bob's code doesn't cause the snap because it's updating "previous" but not changing anything that's visible, the slider or a label. Changing something visible updates the GUI maybe? Causing snap?


----------



## Big Bob (Jan 13, 2014)

Hi Dan,

Actually I tried my experiment with both slider = slider and prev = prev and seemed to get about the same results. However, when I get some time later I'll try this with a re-skinned slider since Mario thinks that may make a difference.

I just assumed that a knob with many more animation states than its range required should behave about the same but perhaps not.

However, my main point is that the fix is not related to the semantics of the else clause but rather some Bizzaro World quirk of the KSP. :lol: 

To be continued ...

Bob

*EDIT:

Mario, did you try a few other kinds of nop, for example slider := slider? It could be like Dan suggested that the callback needs to make at least one reference to the slider itself. 

And, I remember now why I used a knob when I tested this, it's because I don't have any knob images with any kind of numerical display so the only thing I could do would be to watch for the pointer to twitch or not to twitch between the landings :lol: 

The exact behavior of this quirk may also be somewhat computer dependent.

I would kind of like to run this thing into the ground, Maybe you could email me a copy of your graphics?*


----------



## polypx (Jan 13, 2014)

I'm curious -- what defines a "nop" exactly?


----------



## Big Bob (Jan 13, 2014)

OH sorry, nop is the mnemonic usually used in assembly languages to mean 'no operation'. In the case of the KSP, it would be any kind command that would burn a little time or act as a 'placeholder' without actually doing anything useful.

Examples would be slider := slider or maybe some two-liners like inc(x) dec(x), etc

And, thanks for sending me your graphics. Indeed using slider = slider as a nop (in front of the if clause) makes it snap while using prev = prev doesn't. 

However, do you have any kind of knob graphics that displays its animation state numerically (like the one Mario used in his LICE cap)? I notice with your knob graphics (as well as when I did my tests with a standard knob) that the pointer still twitches a little at the 'detent' points. This happens on my computer regardless of whether I use Mario's dummy else clause or if I use slider = slider.

Maybe Mario will send me his graphic so I can compare it directly against the impotent else clause. I might be nice to discover just what we have to do to pacify the KSP in this regard :lol: 

Rejoice,

Bob


----------



## mk282 (Jan 13, 2014)

It's very easy to create such graphic with KnobMan. There's even an online version of it. 

http://www.g200kg.com/en/webknobman/?f= ... &n=233&m=1

Click on Export Exec and there you go. You'll need an accompanying text file of course - this one has 101 animations.


----------



## Big Bob (Jan 13, 2014)

You keep forgetting the difference in the age of our brains Mario :? 

I have knobman but I haven't used it in about a year and for me, that means the next time I use it I have to start all over. Hardly worth it just to pitch in here with trying to nail down what this behavior is all about.

I would be far easier if you would just send me a copy of your knob graphic which I would 'guard with my life' as I told Dan :lol: 

Alternatively, when you get some time for such things, why don't you conduct some tests to find out just what the KSP requires to provide snapping. The fact that it works with the nonsense else clause is fine as a 'seat of the pants' work-around but wouldn't it be worthwhile to characterize it better than that?

Rejoice,

Bob


----------



## mk282 (Jan 13, 2014)

```
on ui_control (slider) 
    slider := slider  { nop to pacify the KSP }
    if slider # prev_value 
        do stuff 
        prev_value := slider 
    end if 
end on
```

This makes the graphic to snap. So it seems there has to be a reference to the slider to actually cause the snapping to occur.



polypx @ 13.1.2014 said:


> Changing something visible updates the GUI maybe? Causing snap?



That seems to be it.


----------



## Big Bob (Jan 13, 2014)

Yes, Dan just sent me a numbered knob and on my system it snaps just as well with the slider = slider nop ahead of the if clause as it does with the else-clause nop.

I agree with your conclusion which I stated this way:

'The KSP requires at least one reference to the slider to be executed during the callback to insure snapping.' However, it might just be required to reference any ui component, I'll have to check that later.

Thanks for confirming.

Rejoice,

Bob

*EDIT: Confirmed. It does appear to be any ui component reference rather than the specific slider in question. So, it looks like Dan nailed it. Kudos Dan* o-[][]-o


----------



## jeorgia (Feb 1, 2020)

on note
change_velo($EVENT_ID, %table1[$EVENT_VELOCITY-1])
end on


----------



## jeorgia (Feb 1, 2020)

on init
message("")
set_script_title("Velocity") 
set_ui_height_px(169)
make_perfview

declare ui_label $text (1,1)
move_control_px($text,437,9)
set_text($text,"Min Curve Max")
set_control_par (get_ui_id($text),$CONTROL_PAR_WIDTH,590)
set_control_par(get_ui_id($text), $CONTROL_PAR_FONT_TYPE,16)
hide_part($text,$HIDE_PART_BG)

{ Velocity }

declare $new_vel

declare ui_table %table[128] (2,3,127)
set_control_help (%table,"Velocity Curve: Displays the velocity curve.")
make_persistent (%table)
set_control_par(get_ui_id(%table),$CONTROL_PAR_BAR_COLOR,4)
move_control(%table,5,4)
hide_part(%table,$HIDE_PART_BG)

declare ui_knob $MinS (1,127,1)
make_persistent($MinS)
set_text($MinS,"Min")
hide_part($MinS,$HIDE_PART_BG.or.$HIDE_PART_MOD_LIGHT.or.$HIDE_PART_TITLE.or.$HIDE_PART_VALUE)
move_control_px($MinS,438,22)
set_knob_defval ($MinS,1)
$MinS := 1

declare ui_knob $CShow (-50,50,1)
set_knob_defval ($Cshow,0)
make_persistent($CShow)
set_text($CShow,"Curve")
move_control_px($CShow,504,22)
hide_part($CShow,$HIDE_PART_BG.or.$HIDE_PART_MOD_LIGHT.or.$HIDE_PART_TITLE.or.$HIDE_PART_VALUE)
$CShow := 0

declare ui_knob $MaxS (1,127,1)
set_knob_defval($MaxS,127)
make_persistent($MaxS)
set_text($MaxS,"Max")
hide_part($MaxS,$HIDE_PART_BG.or.$HIDE_PART_MOD_LIGHT.or.$HIDE_PART_TITLE.or.$HIDE_PART_VALUE)
move_control_px($MaxS,570,22)
$MaxS := 127

declare $a
declare $Min
$Min := 1
declare $Max
$Max := 127
declare $Curve
make_persistent ($Curve)
$Curve := 0
declare $helper
declare $knob_helper
$a := 1
while ($a < 128)
%table[$a] := $a
inc ($a)
end while
declare %curve_form[128]
$a := 0
while($a < 128)
%curve_form[$a] := $a*100 
inc($a)
end while
make_persistent(%curve_form)
declare %curve_table_helper[128]
declare %curve_deriv[128]
declare $kurven_hoehe
end on

on note
$new_vel := %table[$EVENT_VELOCITY]
if ($new_vel < 1)
$new_vel := 1
end if
if ($new_vel > 127)
$new_vel := 127
end if
change_velo ($EVENT_ID, $new_vel)
end on

on ui_control ($CShow)
$curve := $CShow
$knob_helper := ($Curve*(-1) + 1000)
$a := 0
$helper := 1000
$kurven_hoehe := 0
while($a < 128)
$helper := ($helper * $knob_helper)/1000
%curve_deriv[$a] := $helper
$kurven_hoehe := $kurven_hoehe + %curve_deriv[$a]
inc($a)
end while
$a := 2
%curve_table_helper[0] := 0
%curve_table_helper[1] := 0
while ($a <128)
%curve_table_helper[$a] := ((%curve_table_helper[$a-1]+%curve_deriv[$a]))
inc($a)
end while
$a := 0
while ($a <128)
%curve_form[$a] := ((%curve_table_helper[$a]*128)/(($kurven_hoehe)/100))
inc($a)
end while
$a := 0
while ($a <128)
%curve_form[$a] := (%curve_form[$a]*12700)/%curve_form[127]
inc($a)
end while
$a := 0
while ($a <128)
%table[$a] := $Min + (((%curve_form[$a])*($Max-$Min))/12700)
inc($a)
end while
end on

on ui_control ($MinS)
$min := $MinS
$a := 0
while ($a <128)
%table[$a] := $Min + ((%curve_form[$a]*($Max-$Min))/12700)
inc($a)
end while 
end on

on ui_control ($MaxS)
$Max := $MaxS
$a := 0
while ($a <128)
%table[$a] := $Min + ((%curve_form[$a]*($Max-$Min))/12700)
inc($a)
end while 
end on


----------



## d.healey (Feb 1, 2020)

@jeorgia Is that a question?


----------



## Mike Greene (Feb 1, 2020)

This thread and the other one are really old, and not really the best places to ask new questions, so I'm going to lock them both.

jeorgia, if you have new questions, please start a new thread. Thanks!


----------

