# Off ui control



## jdawg (Jul 14, 2013)

hellos again, 

so if there is on ui_control, then what is the version of off ui_control. how can you make something happen on the release of a dial or slider? 

thanking you and as always appreciative.


----------



## Big Bob (Jul 14, 2013)

Buttons trigger a callback on their release, switches just the opposite. Unfortunately, there is no callback triggering for Button down or Switch Up.

Knob and slider callbacks are triggered any time a change in their value occurs (more or less ** :lol: ) But currently there is no simple way to detect mouse over, mouse down, or mouse up. At least none that I know of :roll: 

Usually you have to 'fake' this kind of thing some other way depending on the details of what you are trying to do.

Rejoice,

Bob

** Actually, the situation is a little more complex in that most such controls generate double callbacks (spaced a little in time) per change in value. ui_table generates a very complex, time-encoded series of callback triggers when you are dragging across the XY display area.


----------



## mk282 (Jul 15, 2013)

Yep, there's no way to detect mouse over/down/up.


----------



## jdawg (Jul 15, 2013)

Interesting, 

I am trying to show the values of a sliders, and then change those values back to the name of the slider when not being used. 

so when using the control the value is visible, and when you stop moving the control, the value goes back to NAME OF SLIDER. 


(o) (o)


----------



## polypx (Jul 15, 2013)

We usually do that with a wait statement, at the end of your attack slider... something like:

set_text($attack_label, get_engine_par_disp($ENGINE_PAR_ATTACK,0,0,-1) & " ms")
wait($wait_time)
set_text($attack_label, "ATTACK")


----------



## jdawg (Jan 6, 2014)

I've been dabbling with this for a while

and it works my only problem is if i keep touching or moving the dial eventually the wait statement runs out and the value starts flickering on and off / basically glitching. any thoughts how to counter this? 

i guess its because 

moving the dial starts the wait sequence every time, so its constantly thinking the time is starting and ending onto of one another o/~


----------



## Big Bob (Jan 6, 2014)

You can do something like this:

*on init*
``message('')
``*declare* ui_slider Slider(0,1000)
``*declare* ui_label SliderLabel (1,1)
*end* on

*on ui_control* (Slider)
``set_text(SliderLabel,Slider)``_{ Display value of slider }_
``*call* gate_time``_{ Keep value display for 1.5 secs after last change made }_
*end* on

*function* gate_time```_{ hold displayed value for DispTime after last change }_
``*declare* *const* DispTime := 15``_{ For 1.5 sec gate time }_
``*declare* active
``*declare* tick
``*if* active = 0``````````_{ gate is currently closed }_
````active := 1``````````_{ open a new time gate }_
````tick := DispTime
````*while* tick > 0
``````wait(100000)```````_{ 0.1 sec per tick }_
``````dec(tick)
````*end while*
````active := 0`````````````````````_{ Gate has timed out, close it and }_
````set_text(SliderLabel,'Slider')``_{ replace value display with caption }_
``*else*``_{ gate still open from prior change }_
````tick := DispTime`````_{ renew the full gate time }_ 
``*end* *if*
*end* *function*

The above code will display the current value of the slider until 1.5 secs after the last change made to the slider. When no change to the slider's position is made for 1.5 secs, the label reverts to the caption instead of the value. The active flag prevents wait re-entrance and subsequent build up.

Of course you can make the 'show' time any value you desire by changing the constant named DispTime.

Rejoice,

Bob


----------



## Raptor4 (Jan 7, 2014)

Some time ago I developed a "Dynamic Labeling" for my sample library products as well. My method is similar to the Bob's one (thanks for sharing that Big brother), and probably a little bit more simple I guess :roll: .
Here it is:

```
on init
  declare ui_slider $Slider(0, 127) 
  declare ui_label $Value(1, 1) 
  declare $last_cb
end on

on ui_control($Slider)
  set_text($Value,$Slider)
  $last_cb := $NI_CALLBACK_ID
  wait(1000000)
  if ($NI_CALLBACK_ID=$last_cb)
    set_text($Value,"Value")
  end if
end on
```
Cheers
____________________
www.audiogrocery.com


----------



## Big Bob (Jan 7, 2014)

That's a neat way to do it Ivan, Kudos. o-[][]-o 

About the only downside to doing it your way is that it will cause multiple threads to be generated unnecessarily and thus be a little more CPU demanding. However, I think in the big scheme of things that should be a minor issue. And, your method has the advantage of being more concise.

Another advantage of your approach is that it will handle mulitiple labeled sliders with no additional baggage. My suggestion would require a little more code to have the 'next' slider cancel the last active slider display.

So. overall, I vote for Ivan's suggestion. 8) 

Rejoice,

Bob


----------



## Raptor4 (Jan 7, 2014)

Big Bob @ Tue Jan 07 said:


> So. overall, I vote for Ivan's suggestion. 8)


Hey Bob, here (in Bulgaria) is St.Ivan's day - all mans whose name is Ivan celebrate today. Is you vote is some kind of intellectual gift for my name day :D ? 
God bless!


----------



## Mike Greene (Jan 7, 2014)

Well, in that case, Happy St. Ivan's Day! o-[][]-o


----------



## Big Bob (Jan 7, 2014)

I'll second that with another 'Happy St Ivan's day' wish.

BTW Rosie and I celabrate St Bob's day on May 4 :lol: 

God Bless

Bob


----------



## Raptor4 (Jan 7, 2014)

Thanks for the nice wishes chaps!
It is nice to know that May 4 is St.Bob's day - I'll mark it in my Orthodox calendar as the Master scripters day o-[][]-o .


----------



## Lindon (Jan 10, 2014)

I think there may be a (small) flaw in Ivan's approach, unless I'm not reading it correctly....

I think using $NI_CALLBACK_ID leaves this hole:

If I change a value in a knob01, and then quickly change one in knob02 NI_CALLBACK_ID gets overwritten and knob01 never resets properly....

I think this is what happens, anyway this is what I do:


```
on init
   declare %knob_timers[1]
  {need a slot for each knob you want to add this functionality to..}
  declare const $display_wait_time := 100000 

   declare ui_label $Value(1,1)
   declare ui_slider $Slider(0,10000)

end on



on ui_control($Slider)
  
   set_text($Value,$Slider)
   
   %knob_timers[0] := $KSP_TIMER +  $display_wait_time
    
    while $KSP_TIMER <  %knob_timers[0]
        wait(2000)
    end while
    
     set_text($Value,"Value")

end on
```

Seems to work pretty reliably for me....


----------



## kotori (Jan 10, 2014)

Big Bob @ Tue Jan 07 said:


> That's a neat way to do it Ivan, Kudos. o-[][]-o
> 
> About the only downside to doing it your way is that it will cause multiple threads to be generated unnecessarily and thus be a little more CPU demanding.



Lindon's solution has the same problem. Here's a simple fix to avoid parallel threads (Kontakt actually has a max limit for the number of "threads" so getting too high is a bit dangerous):


```
on init 
  declare ui_slider $Slider(0, 127) 
  declare ui_label $Value(1, 1)   
  declare $last_cb 
  declare $time_remaining
end on 

on ui_control($Slider) 
  set_text($Value,$Slider) 
  $last_cb := $NI_CALLBACK_ID 
  $time_remaining := 1000000
  while $time_remaining > 0 and $NI_CALLBACK_ID = $last_cb
    $time_remaining := $time_remaining - 10000    
    wait(10000)
  end while  
  if ($NI_CALLBACK_ID = $last_cb) 
    set_text($Value,"Value") 
  end if 
end on
```


----------



## Big Bob (Jan 10, 2014)

As long as we are going to beat this thing to death, I guess I should expand on my prior comments in this thread.

There are actually two side issues connected with this:

1. Whether or not to allow unnecessary parallel callbacks to be generated.
2. Whether of not there will be more than one slider like this used and if so, what additional baggage will have to be added to the code to accommodate this so that the prior active slider doesn't get stuck (as Lindon pointed out).

As Nils pointed out, issue #1 should probably not be treated too lightly because it is one of these 'behind the scenes' things that may 'bite' you some day and may be very difficult to diagnose what is happening.

As to issue #2, I intentionally left this out in order to not obscure the main thought involved. I did mention it when I responded to Ivan's post (see my comment on 'extra baggage'). This problem generally can be avoided by simply dedicating a 'private' copy of some key variable for each slider to be accommodated. For example, Lindon is doing this very thing by using an array for $knob_timers so that each control has its own copy.

This same thing can be done for Ivan's method by making the $last_cb variable an array to give each slider its own copy of this id. For the original code I posted, you could make the active and tick variables arrays. Alternatively, what I usually do is to index the sliders and when the slider callback detects a change in the index, it first cancels the last active index.

And, Nils, unless I'm mistaken (which I often am), your posted code doesn't address issue #2 either.

So, to summarize, if issue #1 is given importance, either Nils approach or my approach or something similar should be used. If issue #1 is not important to you, Ivan's approach is the most concise.

To solve issue #2 with Ivan's technique, simply define a dedicated last_cb variable with each control.

BTW I wonder what the OP is thinking about all this :lol: 

Rejoice,

Bob


----------



## Raptor4 (Jan 10, 2014)

Big Bob @ Fri Jan 10 said:


> To solve issue #2 with Ivan's technique, simply define a dedicated last_cb variable with each control.



Thanks for the clarification Bob !
It's my fault... When I posted my technology above I wanted to show the main idea only. Originally I developed the "dynamic labeling" for my Orpheus instrument so the code is done the way you say.
Looking at the main complex code now I have declared unique variables for each UI parameter such as LP Cutoff, LP Reso etc like this:
declare $last_cblLPcut
declare $last_cblLPres
I show the dynamic labeling in the Orpheus Video 2 and I confirm it is rock solid - no errors or CPU boosting. By the way during the Orpheus tests I learned multiple UI parameters to my external controller faders and tweaked them all together to check up the "dynamic labeling" multiple control - no problems at all. 

Regards
____________________
www.audiogrocery.com


----------



## kotori (Jan 10, 2014)

Big Bob @ Fri Jan 10 said:


> And, Nils, unless I'm mistaken (which I often am), your posted code doesn't address issue #2 either.



No, it doesn't. One solution is to use a single hint ui_label and move it around as needed. In that case there is no need to address #2. The advantages of this are easy implementation, cleaner code and lower control count (which could potentially affect performance in the case of heavy UIs with many knobs/sliders). On the other hand one might want to add some code to stop the hint from jumping around in the case of automation and this is not easy to do 100% right.


----------



## Big Bob (Jan 10, 2014)

Nils,

That's an interesting approach using a single dynamically positioned label to display the value of the currently active slider. 

But I'm not sure I see how it lowers the ui count unless you would also give up all static labeling of the sliders. If static captions for each slider are needed (or desired), then using those labels to also display the slider's value doesn't raise the ui count does it? Or am I misunderstanding something here? :roll: 



> It's my fault... When I posted my technology above I wanted to show the main idea only. Originally I developed the "dynamic labeling" for my Orpheus instrument so the code is done the way you say.



I presumed that was the case Ivan. I also purposely omitted the extra code for dealing with multiple controls with my posted code (for the same reason). 

I really think that issue #1 is the more important topic here. Both your code and Lindon's code can create a flurry of unnecessary callback threads whenever a slider is adjusted. This may or may not cause any obvious problem but I think it would be safer to avoid such things. As Nils stated, the number of parallel callbacks might exceed some limit NI imposes resulting in some intermittent behavior that might be very hard to diagnose among other things.

Rejoice,

Bob


----------



## kotori (Jan 10, 2014)

Big Bob @ Fri Jan 10 said:


> But I'm not sure I see how it lowers the ui count unless you would also give up all static labeling of the sliders



If the parameter names are integrated in the background image there is no need to represent them using label controls. Whether such a solution is practical and worth the effort of course varies from case to case.


----------



## Big Bob (Jan 10, 2014)

Oh certainly, if one can count on customized wallpaper to provide the static labeling, control count would be reduced using a single, dynamically-positioned value label.

BTW Nils, are you going to make your Sublime plugin generally available for download (and maybe 'versionized') like the KSE?

Rejoice,

Bob


----------



## Raptor4 (Jan 10, 2014)

Big Bob @ Fri Jan 10 said:


> Both your code and Lindon's code can create a flurry of unnecessary callback threads whenever a slider is adjusted.



Thanks for the comments Bob - I see. Anyway the real tests are good idea so I'm used to trust them...  
I just exported a crappy demo (non-musical) video where I tried to force the all UIs which form the ORPHEUS soundsacpe. Right now I'm on a mobile poor machine where the Video capture app got more resources than the Daw.
It's a plain test showing multiple "dynamic labeling" automaton only.
PS. Nils I see that your method is good as well - I'll put it in my records, so thanks for sharing it!

*Dynamic Labeling Multiple Control Automation Test VIDEO (16Mb)* - DOWNLOAD 

____________________
www.audiogrocery.com


----------



## Big Bob (Jan 10, 2014)

Hi Ivan,

As long as you are comfortable with it that's fine. However, just to clarify, the problem doesn't arise because of numerous sliders all moving at once. Rather, just rapidly twitching a single slider could conceivably launch a lot of parallel callback threads the way you have it coded.

Look at it this way. The first callback triggered will run for about one second. During that time, you might be able to retrigger the same callback numerous times and each such trigger will result in another callback thread being generated. After a second, the first callback drops out but you may still be generating new triggers.

What might save the day is that NI probably has a max rate for launching new threads. For example, let's say that if you jiggle a slider rapidly, you might be able to trigger several hundred potential callbacks in one second. However, NI might only poll for value changes 50 times per second. If so, that would be about the limit of the parallel threads that could be generated if you keep jiggling the slider. Add to this all the other callback threads generated by note messages, etc. As long as the grand total doesn't exceed NI's overall thread-count limit, you will be OK.

But if one day if someone twitches one of your sliders in the middle of a sequencer burst of midi messages, NIs thread-count limit could be exceeded and then 'You will have trouble right here in River City' as the Music Man once said :shock: 

You may already fully understand the situation but I thought perhaps it might be beneficial to clarify this point for others that may read this thread, especially the OP who is, by now, probably wondering what in the world he touched off here :lol: 

Rejoice,

Bob


----------



## kotori (Jan 11, 2014)

This thread may be of interest to anyone wondering about the stack limit. That case is however a bit special, so it could be that Kontakt in the nomal case silently stops the oldest running callback when the limit is reached. An experiment would be required to establish this.

/Nils


----------



## Raptor4 (Jan 11, 2014)

Big Bob @ Fri Jan 10 said:


> What might save the day is that NI probably has a max rate for launching new threads. For example, let's say that if you jiggle a slider rapidly, you might be able to trigger several hundred potential callbacks in one second. However, NI might only poll for value changes 50 times per second. If so, that would be about the limit of the parallel threads that could be generated if you keep jiggling the slider.



I have another theory about that Bob which is some sort of hypothesis based on some tests... :shock: 
I agree that NI probably has a max rate for launching new threads - it must be I guess.
My theory is that the "Max" in question can not be exceeded cause Kontakt optimizes the NI_CBs automatically according to the "Max". 


> For example, let's say that if you jiggle a slider rapidly, you might be able to trigger several hundred potential callbacks in one second. However, NI might only poll for value changes 50 times per second.


OK, let's do a simple test (using a slider range 0,127) just to check it up:

```
on init
message("") 
declare ui_slider $Slider(0, 127)
end on
on ui_control($Slider)
message($NI_CALLBACK_ID & " - " & $Slider)
end on
```
1. Click the Slider (do not move it) - the CB_ID# increases with 1 value (the slider reacts on mouse_click).
2. Click the Slider (do not move it) and de-click it - the CB_ID# increases with 2 values (the slider reacts on mouse_click and mouse release as well).
3. Click the Slider and move/increase it very slowly - the CB_ID# register each value (if start with 106000 you can run it precisely to 106127 in theory). By the way if you hold down the computer "Shift" during the slider twitching Kotakt will generate additional (micro) CB_IDs between the slider values 1,2,3 etc.
4. Click & hold to generate new ID (remember or type the ID#) - let's say it shows 106756. Push the slider rapidly from 0 to 127. What you see - the ID# has been increased with a few values only - 106756 to 106763 for example.

It seems that Kontakt controls the CB amount according to the different conditions (speed, message density etc) - mostly with continuous events such as UI parameters or midi Controllers. For example in JavaScript we use Math.floor() to optimize the message density etc.
I suspect NI controls the number of threads the same way regarding the "Max" point. More threads less CB registrations, respectively less triggering which reduces the threads amount something like that so everything fits magically to the "Max" ... 
Just a theory :D

Regards
____________________
www.audiogrocery.com


----------



## Big Bob (Jan 11, 2014)

Hi Ivan,

That's interesting data and, it could well be that NI does something similar to what you have hypothesized. Of course how well they executed their intention may be another matter :roll: 

In any case, it's entirely possible that issue #1 is not really an issue; after all I did say that 'in the big scheme of things' ... And, your method (even with issue #2 addressed) is still probably the most compact code. So I guess it will all depend on one's level of paranoia whether to guard against the* big thread overload* problem :lol: 

God Bless,

Bob


----------



## Raptor4 (Jan 11, 2014)

Big Bob @ Sat Jan 11 said:


> So I guess it will all depend on one's level of paranoia whether to guard against the* big thread overload* problem :lol:


Heh, it's a common obsession +1 !  

God Bless


----------



## Lindon (Jan 13, 2014)

OK so if we want to fix issue 1 and issue 2 we do this right?


```
on init
  declare ui_slider $Slider(0, 127) 
  declare ui_label $Value(1, 1) 
  declare %control_cb_ids[20]
  declare $time_remaining
end on

on ui_control($Slider)
  set_text($Value,$Slider)
  %control_cb_ids[0] := $NI_CALLBACK_ID
  $time_remaining := 1000000
  while ($time_remaining>0 and ($NI_CALLBACK_ID=%control_cb_ids[0]))
    $time_remaining := $time_remaining-10000
    wait(10000)
  end while
  if ($NI_CALLBACK_ID=%control_cb_ids[0])
    set_text($Value,"Value")
  end if
end on
```


----------



## Big Bob (Jan 13, 2014)

Hi Lindon,

I think you will also need to make $time_remaining an array. With either Nils method or my earlier example, there will generally be *two* variables that require 'priviate' copies for each instance of the slider.

Alternatively, if you index the sliders some way (which you have to do anyway in order to use the array method), you can just test the current index against the prior index and if it has changed, first finish up the last active slider so it won't get stuck when you start using the two variables in a different callback. 

If I get a little time later, I'll try to post an example of that method.

Rejoice,

Bob


----------



## jdawg (Jan 13, 2014)

this thread has become an interesting read. and completely over my head. (o) 

hopefully there is a nice solution out there


----------



## Big Bob (Jan 13, 2014)

Hi Lindon,

As promised, here's a code example that handles both issue #1 and issue #2 and uses the 'last-active cancel' method so that the timing variables can be re-used. This code uses the method I first posted however this idea could also be used with Nils' method as well. 

*on init* 
``message('') 
``*declare* *const* DispTime := 15``_{ Display Hold time in deci-seconds, ie 1.5 seconds }_ 
``*declare* *const* MAX_SLIDERS := 10```````_{ max sliders to be declared }_
``*declare* !slider_caption[MAX_SLIDERS]``_{ static captions for each slider }_
``*declare* slider_label_id[MAX_SLIDERS]``_{ corresponding label ids for each slider }_
``*declare* slider_index``````````_{ currently active slider index, 0..MAX_SLIDERS-1 }_
``*declare* last_used_index```````_{ previously active slider index }_
``*declare* slider_value``````````_{ active slider's value }_
```````````````_{ Declare 4 sliders }_
``declare_slider(General_Slider,0,500,66,1,'General Slider')
``declare_slider(InstVolume,0,1000000,158,1,'Inst. Volume')
``declare_slider(InstPan,0,1000000,250,1,'Inst. Panning')
``declare_slider(InstTune,0,1000000,342,1,'Inst. Tuning')
*end* on 
``````````````_{ Generate callback handlers }_
on_slider(General_Slider,ShowSliderVal)``
on_slider(InstVolume,ShowVolume)
on_slider(InstPan,ShowPanning)
on_slider(InstTune,ShowTuning)

_{************************************* Support functions and macros ************************************************}_

_{ Action routines to control and format display of controlled parameter }_
*function* ShowSliderVal``_{ display slider's actual value }_
``slider_label_id[slider_index] -> text := '' & slider_value
*end* *function*

*function* ShowVolume _{ use slider value to control and display inst volume }_
``set_engine_par(ENGINE_PAR_VOLUME,slider_value,-1,-1,-1)
``slider_label_id[slider_index] -> text := get_engine_par_disp(ENGINE_PAR_VOLUME,-1,-1,-1) & ' dB'
*end* *function*

*function* ShowPanning _{ use slider value to control and display inst panning }_
``set_engine_par(ENGINE_PAR_PAN,slider_value,-1,-1,-1)
``slider_label_id[slider_index] -> text := get_engine_par_disp(ENGINE_PAR_PAN,-1,-1,-1)
*end* *function*

*function* ShowTuning _{ use slider value to control and display inst tuning }_
``set_engine_par(ENGINE_PAR_TUNE,slider_value,-1,-1,-1)
``slider_label_id[slider_index] -> text := get_engine_par_disp(ENGINE_PAR_TUNE,-1,-1,-1) & ' Semitones'
*end* *function*

_{ Macros to declare sliders and to generate their callback handlers }_
*macro* on_slider(#name#,show_val)
``*on ui_control*(#name#)
````slider_index := #name#.index``_{ active slider index }_
````slider_value := #name#````````_{ active slider value }_
````*call* show_val`````````````````_{ format and display current value }_
````*call* hold_time````````````````_{ hold display for 1.5 sec after last change }_
``*end* on
*end* *macro*

*macro* declare_slider(#name#,min,max,x,y,caption)
``*declare* ui_slider #name# (min,max)```_{ the slider itself }_
````move_control_px(#name#,x,y)
``*declare* ui_label #name#.label (1,1)``_{ caption/value label }_
````move_control_px(#name#.label,x,y+18 )
````#name#.label->hide := 1```````````_{ center text on transparent bkg }_
````#name#.label -> text_alignment := 1``
````set_text(#name#.label,caption)
``*declare* #name#.index``_{ compile-time index assigned to this slider }_
````#name#.index := slider_index
````slider_caption[slider_index] := caption
````slider_label_id[slider_index] := get_ui_id(#name#.label)
````last_used_index := slider_index
````inc(slider_index)
*end* *macro*

_{ on_slider support function }_
*function* hold_time```_{ hold displayed value for DispTime/10 seconds after last change }_ 
``*declare* active 
``*declare* tick 
``*if* last_used_index # slider_index``_{ if this is a new slider changing }_
````slider_label_id[last_used_index] -> text := slider_caption[last_used_index]
````last_used_index := slider_index``_{ cancel last active slider value display }_
``*end* *if*
``*if* active = 0``````_{ no hold currently active }_ 
````active := 1``````_{ start a new hold period }_ 
````tick := DispTime 
````*while* tick > 0 
``````wait(100000)```_{ 0.1 sec per tick }_ 
``````dec(tick) 
````*end while* 
````active := 0```_{ Hold time has expired, replace value with caption }_
````slider_label_id[slider_index] -> text := slider_caption[slider_index] 
``*else*````````````_{ hold still active from prior change }_ 
````tick := DispTime _{ renew the full hold time for each new change }_ 
``*end* *if* 
*end* *function* 



By using callable functions, each callback handler needs only 4 lines of code. I have illustrated 4 different types of display formatting routines however, you might have more than one slider that can use the same format routine by simply calling it for each instance that requires it.

And, since I see that the Original Poster has checked in, jdawg, the above code can actually be used pretty much as is. Of course you will need to compile it in Nils' editor before you can paste it into Kontakt. But each slider you want to add requires only two lines of additional code. You simply use* declare_slider* in the init callback and *on_slider* to generate the corresponding callback handler.

The only other thing you need to do is write whatever additional functions you need to display the parameter value (pattern it along the lines of the four Show functions. So, it's pretty easy to use even if you may not understand how it works. :lol: 

Rejoice,

Bob


----------



## Lindon (Jan 14, 2014)

Bob, 

Thanks for this effort. Interesting approach, and should I need to work with a fixed set of variables then I'm sure it will be very useful. I take your point about needing an array of counters, In my second example I decided against this as I didnt mind the counter being re-set in some other UICB. But on thinking about it I believe it would be a better UI experience if the UI displayed for a "per slider time" in each case and thus I implemented your suggestion. You will note that's what I had originally implemented to start with in my first approach.

Regards

LMP


```
oon init 
  declare ui_slider $Slider(0, 127) 
  declare ui_label $Value(1, 1) 
  declare const $COUNT_OF_SLIDERS := 20
  declare %control_cb_ids[$COUNT_OF_SLIDERS] 
  declare %time_remaining[$COUNT_OF_SLIDERS] 
end on 

on ui_control($Slider) 
  set_text($Value,$Slider) 
  %control_cb_ids[0] := $NI_CALLBACK_ID 
  %time_remaining[0] := 1000000 
  while (%time_remaining[0] > 0 and ($NI_CALLBACK_ID=%control_cb_ids[0])) 
    %time_remaining[0] := %time_remaining[0]-10000 
    wait(10000) 
  end while 
  
  
  if ($NI_CALLBACK_ID=%control_cb_ids[0]) 
    set_text($Value,"Value") 
  end if  
  
end on
```


----------



## Big Bob (Jan 14, 2014)

Hi Lindon,



> You will note that's what I had originally implemented to start with in my first approach.



Yes I did notice that and perhaps it might be more user-friendly if each slider display did behave independently from the others. If so, using something like arrays to provide dedicated vars for each slider would be the way to go. Of course for anyone using the *Task Control Module*, you could just use *Task Variables* (these are similar to poly vars but they are not restricted to Note/Release callbacks). In other words, *control_cb_id* and* time_remaining* with Nils' method (or *Active* and *Tick* with my method) could each be a task variable. Of course you probably aren't using the TCM but I should at least mention this option.

Just out of curiosity though, are you literally using the code as you posted it? I mean do you actually write that much custom code for each callback handler? I would think you might want to use more indexing so you could use a common, called function for the bulk of the code (rather than expanding it out inline with literal array indices and caption text, etc).

In summary though, as the OP just said, this has been an interesting and informative thread and has supplied several novel approaches to dealing with the *Big Slider Value Display* problem :lol:

Rejoice,

Bob


----------



## Lindon (Jan 16, 2014)

Well generally I use a bunch of macros for common stuff.... so no I dont write stuff as literally shown in the examples...


----------



## jdawg (Jan 16, 2014)

Now the question is, which method is best. 

 the initial "Raptor4" method seemed to work fine, is the mounting CPU issues really a problem? 

lets say if 5-10 dials are being automated at the same time would this explode? 

it just seemed extremely clean and simple, where as the further methods are leading to some head scratching o-[][]-o


----------



## Raptor4 (Jan 16, 2014)

jdawg @ Thu Jan 16 said:


> the initial "Raptor4" method seemed to work fine, is the mounting CPU issues really a problem?
> lets say if 5-10 dials are being automated at the same time would this explode?


There is no CPU problem with 10 or more deals (it is some sort of paranoia as Bob says). I posted a Demo Video above where I demonstrate that - you probably miss that video !
The topic became too complicated so here is the direct link with the video, click it below and wait 3-4 sec to let the browser to redirect to the topic sub link location:
http://www.vi-control.net/forum/viewtop ... 16#3760816
_____________________
www.audiogrocery.com


----------



## Big Bob (Jan 16, 2014)

Hey jdawg,

It probably won't blow up but we don't know for sure. The potential is there but there is also some evidence that the KSP may not let it happen in any kind of disastrous way.

In your case, since the methods that protect against issue #1 seem to kind of scare you, perhaps you shouldn't worry about this potential problem and simply use R4's method.

However, keep in mind that the way he posted it doesn't include what is needed to handle issue#2. So, if you will be using more than one slider, you will need additional code.

Ivan, perhaps you should post an example illustrating how to do say 3 sliders with your method for the benefit of the OP?

Rejoice,

Bob


----------



## Raptor4 (Jan 16, 2014)

Big Bob @ Thu Jan 16 said:


> Ivan, perhaps you should post an example illustrating how to do say 3 sliders with your method for the benefit of the OP?


OK Bob, I post a very simple example which must behave as "Dyn. Labeling" tutor so the other guys can get the fundamental and update it regarding their own taste (if I have time I can post more complex version later):

```
on init
  declare const $Wait_time := 1000000
  declare %NI_CB[3]
  declare ui_slider $Slider_A(0, 127) 
  declare ui_label $Value_A(1, 1) 
  move_control($Slider_A,1,1)
  move_control($Value_A,1,2)
  declare ui_slider $Slider_B(0, 127) 
  declare ui_label $Value_B(1, 1) 
  move_control($Slider_B,2,1)
  move_control($Value_B,2,2)
  declare ui_slider $Slider_C(0, 127) 
  declare ui_label $Value_C(1, 1) 
  move_control($Slider_C,3,1)
  move_control($Value_C,3,2)
end on

on ui_control($Slider_A)
  set_text($Value_A,$Slider_A)
  %NI_CB[0] := $NI_CALLBACK_ID
  wait($Wait_time)
  if ($NI_CALLBACK_ID=%NI_CB[0])
    set_text($Value_A,"Value_A")
  end if
end on

on ui_control($Slider_B)
  set_text($Value_B,$Slider_B)
  %NI_CB[1] := $NI_CALLBACK_ID
  wait($Wait_time)
  if ($NI_CALLBACK_ID=%NI_CB[1])
    set_text($Value_B,"Value_B")
  end if
end on

on ui_control($Slider_C)
  set_text($Value_C,$Slider_C)
  %NI_CB[2] := $NI_CALLBACK_ID
  wait($Wait_time)
  if ($NI_CALLBACK_ID=%NI_CB[2])
    set_text($Value_C,"Value_C")
  end if
end on
```

Regards
____________________
www.audiogrocery.com


----------



## Big Bob (Jan 16, 2014)

Ivan, that's a nice, no-frills illustration of a multi-slider implementation.

Jdawg, the important thing to notice is that there must be a separate (dedicated) copy of the NI_CB variable for each instance of the slider declared. Ivan is doing this by making NI_CB an array and assigning an indexed slot in that array for each slider instance.

BTW jdawg, are you using Nil's editor or are you pasting code straight into the KSP editor?

Rejoice,

Bob


----------



## pocoapoco (Jan 17, 2014)

What about this approach? Bypass the callback and control id's alltogether. It seems pretty simple to me. It avoids multiple control use issues and multiple thread overrun issues.


```
on init
declare ui_slider $slider(0,127)
declare ui_label $value(1,1)
declare ui_slider $slider2(0,127) {to verify multple control use issues}
declare $last_slider
declare $wait_active
declare const $yes:=1
declare const $no:=0
end on

on ui_control($slider)
set_text($value,$slider)
$last_slider:=$ENGINE_UPTIME 
if ($wait_active=$no)
	$wait_active:=$yes
	while ($ENGINE_UPTIME-$last_slider<1000) {if the current time is less than one second later than the last slider movement}
		wait(100000) {one tenth of a second}
		if ($ENGINE_UPTIME-$last_slider>=1000)
			set_text($value,"you betcha")
			$wait_active:=$no
			end if
		end while
	end if
end on
```


----------



## Raptor4 (Jan 17, 2014)

pocoapoco @ Fri Jan 17 said:


> It seems pretty simple to me.


To me too :D .
Could you post a version with two or three sliders & two/three labels where each label shows the corespondent slider value/label toggling.
I guess you will need one more $last_slider2 variable for the Slider2. Even that if you tweak the all sliders simultaneously and release one for a while, the gear may work a few times but in some cases you may find out that one of the labels will stack showing the Slider value instead of switching back to Label name after 1sec. Something like that...
____________________
www.audiogrocery.com


----------



## Lindon (Jan 17, 2014)

OK this is more complex than it first appears...but first a caveat:

This is all based on how I think KSP callbacks work, I know Bob and Nils have done lots of work in this are so I might 
get corrected pretty quickly..

Here are the scenrios as I see them:

Scenario 1. The user touches a slider (Slider1) once and its label(label1) gets set to the slider value..when the time reaches (time) the label is reset to its name
(this nearly never happens, what actually happens is...)

Scenario 2. The user touches slider1 many time adjusting to get to the value they want, label1 keeps showing these new values, but waits until (time) after the last slider touch to reset the label to its name

Scenario 3. The user touches slider 1 many times (like Scenario 2) but then moves to Slider2, as soon as the user moves to slider2 we want label1 to revert to its name

Here's some code based on how I think KSP works, and as I say I've been known to be radically wrong about that....


```
on init
    declare ui_slider $slider1(1,100)
    declare ui_label $label1(1,1)
    
    declare ui_slider $slider2(1,100)
    declare ui_label $label2(1,1)
    
    declare %control_cb_ids[2]
    declare %knob_timers[2]
    
    declare $display_wait_time := 1000000
    
end on

function manage_display(id,counter,text)
    %control_cb_ids[counter] := $NI_CALLBACK_ID 
    %knob_timers[counter] := $display_wait_time + $KSP_TIMER
    while (%knob_timers[counter] > $KSP_TIMER and ($NI_CALLBACK_ID=%control_cb_ids[counter])) 
      wait(10000) 
    end while 
    
    if ($NI_CALLBACK_ID # %control_cb_ids[counter]) or (%knob_timers[counter] <= $KSP_TIMER)  
        id->text:= text
    end if
end function

on ui_control($slider1)
    set_text($label1,$slider1)
    manage_display(get_ui_id($label1),0,"label 1 name")
end on

on ui_control($slider2)
    set_text($label2,$slider2)
    manage_display(get_ui_id($label2),1,"label 2 name")
end on
```

Lets see how it does:

Scenario 1. 
- We enter the CB, set the timer and the internal CB id, and proceed to the while loop...
- After we reach (time) we exit the loop and reset the label to its name

OK, so as we say this is not such a big deal as it hardly ever happens..

Scenario 2.
- We enter(and keep re-entering) the CB generating new threads of code...and keep resetting the internal CB and internal timer values
- each time we then proceed to the while loop where all the CBs continue to execute
- eventually the user settles on some value they like and all CBs wait until the last defined (time) and then exit
- all CBs then reset the label name 
Not great, we have quite a few redundant CBs here, but I cant find a way to get rid of them and it works...

Scenario 3.
- We enter the CB as per Scenario 2, building up CBs that are waiting for (time) to be reached
- the user goes and fiddles with slider2, at this point the internal CB id and the NI_CALLBACK_ID dont match 
and all running CBs for Slider1 exit the while loop and all of them reset the label to its name
Well better, as we terminate CBs when the user moves somewhere else and this control set looses focus...

OK so the tricky bit here is why do this:


%knob_timers[counter] := $display_wait_time + $KSP_TIMER
while (%knob_timers[counter] > $KSP_TIMER and ($NI_CALLBACK_ID=%control_cb_ids[counter])) 
wait(10000) 
end while 


and not this


%knob_timers[counter] := $display_wait_time
while (%knob_timers[counter] > 0 and ($NI_CALLBACK_ID=%control_cb_ids[counter])) 
$display_wait_time:= $display_wait_time - 10000
wait(10000) 
end while 


Well its because in the second snippet whilst many CBs are running (Scenario2) they would all be reducing $display_wait_time by 10K each
thus reducing the actual time to some (probably) very short period. Not what we want.

As I say all based on concurrently running CBs each addressing the same variable(s) and I'm more than willing to accept some smarter persons model of how call backs work....


----------



## pocoapoco (Jan 17, 2014)

Here it is. I tried everything I could think of to try to get it to do something unpleasant, but it seems totally stable to me.

```
on init 
declare ui_slider $slider(0,127) 
declare ui_label $value(1,1) 
declare ui_slider $slider2(0,127)
declare ui_label $value2(1,1)
declare ui_slider $slider3(0,127)
declare ui_label $value3(1,1)
move_control ($value,1,1)
move_control ($value2,2,1)
move_control($value3,3,1)
move_control ($slider,1,2)
move_control ($slider2,2,2)
move_control($slider3,3,2)
set_text($value,"something")
set_text($value2,"or")
set_text($value3,"other")
declare $last_slider
declare $last_slider2
declare $last_slider3
declare $wait_active
declare $wait_active2
declare $wait_active3
declare const $yes:=1 
declare const $no:=0
declare const $delay:=3000
declare const $tenthsec:=100000
end on 

on ui_control($slider) 
set_text($value,$slider) 
$last_slider:=$ENGINE_UPTIME 
if ($wait_active=$no) 
   $wait_active:=$yes 
   while ($ENGINE_UPTIME-$last_slider<$delay) 
      wait($tenthsec)
      if ($ENGINE_UPTIME-$last_slider>=$delay) 
         set_text($value,"something") 
         $wait_active:=$no 
         end if 
      end while 
   end if 
end on    

on ui_control($slider2) 
set_text($value2,$slider2) 
$last_slider2:=$ENGINE_UPTIME 
if ($wait_active2=$no) 
   $wait_active2:=$yes 
   while ($ENGINE_UPTIME-$last_slider2<$delay) 
      wait($tenthsec)
      if ($ENGINE_UPTIME-$last_slider2>=$delay) 
         set_text($value2,"or") 
         $wait_active2:=$no 
         end if 
      end while 
   end if 
end on    

on ui_control($slider3) 
set_text($value3,$slider3) 
$last_slider3:=$ENGINE_UPTIME 
if ($wait_active3=$no) 
   $wait_active3:=$yes 
   while ($ENGINE_UPTIME-$last_slider3<$delay) 
      wait($tenthsec)
      if ($ENGINE_UPTIME-$last_slider3>=$delay) 
         set_text($value3,"other") 
         $wait_active3:=$no 
         end if 
      end while 
   end if 
end on
```


----------



## Big Bob (Jan 17, 2014)

Hi Lindon,

I think your general description of the problem and how the KSP behaves is quite accurate. 

I also think your 3 scenarios pretty well cover the situation insofar as testing the logic of any proposed implementation. However, for Scenario 3, the last phrase


> *as soon as* the user moves to slider2 we want label1 to revert to its name


 is only one of several possible objectives one might want or settle for. For example, I think it was you who first suggested that it might be nicer if each slider behaved independently with their own time-out period. Another possibility might be, no sliders revert to their caption until all sliders are inactive for some period. I'm sure there are other ideas one might come up with as desirable or tolerable :lol: 

However, I think that the way you stated Scenario 3 is just fine to convey the general idea of what we are probably trying to accomplish. At least insofar as the question posed by the OP. Of course Nils mentioned some other considerations that should also be considered for the overall design. If your script uses custom wallpaper to provide the static captions, you could simply use one floating caption to display the value of the currently active slider. Similarly to the way NI displays Instrument Volume for example. If you use that approach, it can greatly simplifiy the design insofar as my Issue #2 is concerned.

Of course there are other details that eventually need to be addressed such as the fact that we quite often will want to display something other than the slider's actual value. For example, if the slider is used to control volume via an engine parameter, we probably won't want the slider to display 0..10000000 but rather the controlled volume level in db. This part of the callback handler could simply be considered as the pre-amble and therefore outside the timing module. But, if each slider label has to be restored after a timed display, then each callback handler has to be more or less customized. This makes it difficult to use a common, callable function to provide the value display timing. And the more elaborate the timing function is, the more inline code that needs to be used for each callback handler.

So, one worthwhile endevour would be to arrange it so the bulk of the timing code could be common to all callback handlers.

Now regarding your latest posted code and commentary:



> Scenario 2.
> - We enter(and keep re-entering) the CB generating new threads of code...and keep resetting the internal CB and internal timer values
> - each time we then proceed to the while loop where all the CBs continue to execute
> - eventually the user settles on some value they like and all CBs wait until the last defined (time) and then exit
> ...



The last line above is a little puzzling because there are of course several ways to rid yourself of the 'redundant CBs. Nils illustrated one way and I illustrated another with my initial post.

So, let me do a walk through of my initial post for this problem which I'll repeat here:

*on init* 
``message('') 
``*declare* ui_slider Slider(0,1000) 
``*declare* ui_label SliderLabel (1,1) 
*end* on 

*on ui_control* (Slider) 
``set_text(SliderLabel,Slider)``_{ Display value of slider }_ 
``*call* gate_time``_{ Keep value display for 1.5 secs after last change made }_ 
*end* on 

*function* gate_time```_{ hold displayed value for DispTime after last change }_ 
``*declare* *const* DispTime := 15``_{ For 1.5 sec gate time }_ 
``*declare* active 
``*declare* tick 
``*if* active = 0``````````_{ gate is currently closed }_ 
````active := 1``````````_{ open a new time gate }_ 
````tick := DispTime 
````*while* tick > 0 
``````wait(100000)```````_{ 0.1 sec per tick }_ 
``````dec(tick) 
````*end while* 
````active := 0`````````````````````_{ Gate has timed out, close it and }_ 
````set_text(SliderLabel,'Slider')``_{ replace value display with caption }_ 
``*else*``_{ gate still open from prior change }_ 
````tick := DispTime`````_{ renew the full gate time }_ 
``*end* *if* 
*end* *function* 

*Now, Scenario #1*. 
- We enter the CB, set the active flag and set the 'tick' value to 15 periods before entering the while loop... 
- After 15 periods of 0.1 secs each (1.5 secs total) we exit the loop, turn off the active flag and reset the label to its name 

*Scenario #2.*
- We enter(and keep re-entering) the CB. However, after the first callback, the active flag forces us to the else clause where the 'tick' value is pumped back up to 15 *but no waits are encountered* so the callback dismisses without keeping another thread alive. But, since the 'tick' value has been pumped up, the time-out of the first callback is extended.
- eventually the user settles on some value they like and stops fiddling with the slider. 1.5 secs after that, the first callback exits the while loop and restores the sliders caption and resets the active flag ready for more slider twiddling.

Note that this method does not allow multiple callback threads to be generated.

Of course what I posted doesn't by itself handle Scenario #3. However there are several ways to do that which I already discussed in several prior posts. Generally, the solution to the multi-slider problem (which I called *issue #2* in a prior post) falls into two categories. You can either give each callback its own copy of 'active' and 'tick' (this achieves totally independent slider timing) or you can detect when a new slider is adjusted and simply cancel the time-out for the last active slider. I've already illustrated that technique so I won't repeat it here.

And *pocoapoco*, your proposal is basically the same as mine and your solution to Scenario #3 (which is my issue #2 that I raised a while back) is using dedicated variables for each callback instance. This has pretty well been discussed already in this thread.

Rejoice, 

Bob


----------



## Big Bob (Jan 17, 2014)

Hi Guys,

I've come up with a pretty nice implementation for the timed slider. Here are the features:

1. Handles Issue #1. No unnecessary callback threads are generated. 
2. Handles Issue #2. Wiggling a second slider before the first one times out results in cancelling the prior slider's display.
3. All the timing management code is concentrated in a single, callable function so the callback handlers are very compact

The handlers only require 3 lines of code plus what you need to format the value display itself. This would be one line of code for simply displaying the slider's actual value and typically two lines of code when the slider is used to control some engine parameter. These would be routines you need to write but they are exemplified by fmt_slider, fmt_volume, and fmt_tune.

I'm attaching a little Demo instrument (along with the KScript source code). Slider_A just displays its own value. Slider_B and Slider_C control instrument volume and tuning. You can download the nki and run this demo instrument in either K4 or K5.

I'll also post the KScript source code here:

_{ Demo Script with three timed-display sliders. All the timing and display management stuff is implemented
with a single callable function invoked by the on_slider macro. The callback handler that's generated
requires only 3 lines of code (plus whatever your custom formatting routine requires). 

For example, if you only want to display the slider's value (as for Slider_A), the callback handler only
uses 4 lines of code total. If the slider controls an engine par such as instrument volume, you can
use a two-line routine like fmt_volume or fmt_tune for a total of 5 code lines for the callback handler
(as implemented for Slider_B and Slider_C) }_ 
``
*on init*
``message('')
``make_perfview
``*declare* new_id````_{ uid of current slider }_
``*declare* old_id````_{ uid of prior slider }_
``*declare* @new_cap``_{ static caption of current slider }_
``*declare* @old_cap``_{ static caption of prior slider }_
``*declare* @value````_{ formatted value of slider or what it is controlling }_
``declare_slider(Slider_A,0,127,66,5,'Slider_A')
``declare_slider(Slider_B,0,1000000,158,5,'Inst Volume')
``declare_slider(Slider_C,0,1000000,250,5,'Inst Tuning')
*end* on

on_slider(Slider_A,fmt_slider)
on_slider(Slider_B,fmt_volume)
on_slider(Slider_C,fmt_tune)

_{************** Custom display routines, you write these *********************}_

*function* fmt_slider(slider)
``value := '' & slider``_{ value of slider itself as a text string }_
*end* *function*

*function* fmt_tune(slider)
``set_engine_par(ENGINE_PAR_TUNE,slider,-1,-1,-1)
``value := get_engine_par_disp(ENGINE_PAR_TUNE,-1,-1,-1) & ' Semitones' 
*end* *function*

*function* fmt_volume(slider)
``set_engine_par(ENGINE_PAR_VOLUME,slider,-1,-1,-1)```_{ use slider to control inst. volume }_
``value := get_engine_par_disp(ENGINE_PAR_VOLUME,-1,-1,-1) & ' dB'``_{ volume shown by Kontakt }_
*end* *function*

_{******************************* End of Main Script *********************************}_

*macro* declare_slider(#name#,min,max,x,y,caption)
``*declare* ui_slider #name# (min,max) _{ create the slider itself }_
````move_control_px(#name#,x,y)
``*declare* ui_label #name#.lbl (1,1)``_{ create the caption/value label }_
````move_control_px(#name#.lbl,x,y+18 )
````set_text(#name#.lbl,caption)
````#name#.lbl->hide := 1````````````_{ center text on transparent bkg }_
````#name#.lbl -> text_alignment := 1 
````#name#.lbl -> font_type := 12
``*declare* @#name#.cap
````#name#.cap := caption ```````````_{ remember caption for restoring later }_
*end* *macro*

*macro* on_slider(#name#,fmt_val)``_{ name of slider, name of scripter-written display routine }_
``*on ui_control*(#name#)
````fmt_val(#name#)``````````````_{ user-supplied function to format the value for display}_
````new_id := get_ui_id(#name#)``_{ current slider's id and static caption }_
````new_cap := #name#.cap
````*call* show_value``````````````_{ display value for 1.5 secs }_`````
``*end* on
*end* *macro*

_{***************************** Macro Support functions ********************************}_

*function* restore_old_caption
``set_control_par_str(old_id+1,CONTROL_PAR_TEXT,old_cap)
*end* *function*

*function* show_value``_{ display value for 1.5 secs after last change }_
``*declare* *const* DispTime := 15``_{ #of 0.1 second ticks to hold display, 1.5 seconds }_
``*declare* active
``*declare* tick
``set_control_par_str(new_id+1,CONTROL_PAR_TEXT,value) _{ display formatted value }_
``*if* new_id # old_id* and *old_id # 0```_{ If this is a different slider from prior, }_
````restore_old_caption ``````````````_{ restore caption of prior slider }_
``*end* *if*
``old_id := new_id```````_{ now make active slider the prior slider as well }_
``old_cap:= new_cap
``tick := DispTime```````_{ pump up the display time to full 1.5 seconds from now }_
``*if* active = 0``````````_{ if no slider is currently active (ie displaying a value) }_
````active := 1``````````_{ activate timer }_
````*while* tick > 0```````_{ keep timer going until 1.5 seconds after last change }_
``````wait(100000)
``````dec(tick)
````*end while*
````active := 0``````````_{ no changes for the last 1.5 seconds }_
````restore_old_caption _{ restore caption of current slider }_
``*end* *if*
*end* *function*


Rejoice,

Bob


----------



## Raptor4 (Jan 18, 2014)

Hi Bob,
Thanks for the nice implementation! I was curious about your new approach so I just tried it out. Looking at the compiled code I noted some shared details which push me to test the all 3 sliders with my external controller. I'm not wrong... When you twitch each slider alone it is OK. But when you try to twitch them simultaneously the labels start to flick (not showing the values continuously as should be). After the twitching stop they do not wait - just switch back to label show immediately. I guess it's sort of variable sharing issue... :? 
Regards
____________________
www.audiogrocery.com


----------



## pocoapoco (Jan 18, 2014)

Big Bob @ Fri Jan 17 said:


> And *pocoapoco*, your proposal is basically the same as mine and your solution to Scenario #3 (which is my issue #2 that I raised a while back) is using dedicated variables for each callback instance. This has pretty well been discussed already in this thread.
> 
> Bob



Yeah, well I came to the forum looking for an answer to a problem I have and got a bit side tracked. I didn't read through the entire thread.
I recently upgraded from Kontakt 2 to Kontakt 5. There's definitely a lot of improvements in going from 2 to 5 like much faster load times, significantly better polyphony, (about 30% better. That's a lot of extra voices) and improvements in KSP like a change in the maximum array size from 512 to 32768 elements. Still no multidimensional arrays, but with the increased size I can at least configure dimensions manually.
Anyways, my main beef is that version 5 handles modulation as it applies to times i.e. envelope times differently than version 2. And this has turned an orchestra patch that I have spent many, MANY hours on into a big mess. So I'm looking for a way to download the modulation tables to an external program, apply the necessary mathematical transformation, and resave and apply the new, transformed table over the old one for each instance in each instrument.
Might you have any Ideas?


----------



## mk282 (Jan 18, 2014)

This is an undocumented trick you can try:

1. Open your modulation shaper table.
2. Shift+click the Active button. You will get a save dialog which enables you to store this table to a .txt file.
3. Now you can edit this .txt file however you like.
4. Ctrl+Shift+click to import the file back.

Still can't do it in batch for multiple tables/NKIs at once, but at least it helps somewhat, no?


----------



## Big Bob (Jan 18, 2014)

Hi Ivan,



> When you twitch each slider alone it is OK. But when you try to twitch them simultaneously the labels start to flick (not showing the values continuously as should be). After the twitching stop they do not wait - just switch back to label show immediately. I guess it's sort of variable sharing issue...



Leave it to you to come up with something like this :lol: 

When more than one slider is twitched 'simultaneously', it probably results in rapid alternation back and forth and so the active slider keeps changing faster than the hold time. The way this code is designed, there can only be one active slider at a time so the next twitched slider always 'takes over'. I would imagine that would explain the flickering value displays.

However, I should also think that when you stop all the twitching, the last active slider should run its full period. So, that part of your observation is a little puzzling. I don't have too convenient a way to twitch multiple sliders right now but I'll try to set something up later today and get back with you. Right now, Rosie is calling me for breakfast :lol: 

God Bless,

Bob


----------



## mk282 (Jan 18, 2014)

I think it should be as easy as turning the active and tick variables into arrays, and set the array sizes according to the number of sliders. That should do it.


EDIT: Checking the code more clearly, it has been conceived with only one value label in mind. Scratch my idea then, it's for individual labels per slider.


----------



## pocoapoco (Jan 18, 2014)

If you have the sliders attached to separate midi controllers introducing a small wait immediately after the midi controller value is fetched and assigned to the slider may cure that problem e.g.

```
$my_slider:=%CC[23]
wait(1) {yes, really just 1}
```


----------



## pocoapoco (Jan 18, 2014)

That might be helpful if I can get the .txt file loaded into a spread sheet in a meaningful way. Otherwise hand editing each individual value for better than 100 different tables could very well take a month to do. I'll have to experiment with this. Thanks for the suggestion.


----------



## Raptor4 (Jan 18, 2014)

Big Bob @ Sat Jan 18 said:


> However, I should also think that when you stop all the twitching, the last active slider should run its full period.



That's correct Bob - I could see that the last twitched slider works as should be today, though I did not reported the details  .

Take care and have a nice weekend,

Ivan
____________________
www.audiogrocery.com


----------



## Big Bob (Jan 18, 2014)

Hi Mario,



> I think it should be as easy as turning the active and tick variables into arrays, and set the array sizes according to the number of sliders. That should do it.



and 



> EDIT: Checking the code more clearly, it has been conceived with only one value label in mind. Scratch my idea then, it's for individual labels per slider.



I think your first thought is correct. During breakfast I started thinking about what IVan has reported. The basic problem with using the method as I posted it is that when another slider takes over the active role, it restores the caption of the prior slider but it doesn't cancel the wait for the prior slider. BTW This would be a nice application for the stop_wait function :lol: 

*Lindon*, maybe we also need a Scenario #4 for testing various strategies? Specifically: *Scenario #4* What happens when more than one slider is moved simultaneously.

This situation might well be the case for only using the fully independent slider timing solution (ie using dedicated variables for each instance).

I think I will recast my latest post to use dedicated variables per instance. There is a way to do this so that common code can still be used for the callback handlers so they will remain lean and mean :lol: 

To be continued ...

Bob

EDIT: Hi Ivan, your post crossed mine. But, your discovery leads me to think that we should have* Lindon* add a *Scenario #4* to his testing criteria as I indicated above. As Nils previously pointed out, this is also one weakness of using a common floating caption in that when a number of such sliders are controlled by automation the possibility exists that the caption will start flying around wildly :lol: 

Maybe the best overall solution then is to have each slider operate independently. I'll post such an example later today that will solve all these potential issues (no needless callback threads, and proper behavior for both Scenario #3 and the new Scenario #4 as well.


----------



## mk282 (Jan 18, 2014)

pocoapoco @ 18.1.2014 said:


> That might be helpful if I can get the .txt file loaded into a spread sheet in a meaningful way. Otherwise hand editing each individual value for better than 100 different tables could very well take a month to do. I'll have to experiment with this. Thanks for the suggestion.



Shouldn't be a problem.

You will see that .txt is formatted like 001 -> value. It is easy to use find&replace to swap " -> " with a \t (tab character), which Excel will interpret as a new column. From there on it's a simple copypasta to Excel and off you go!


----------



## Big Bob (Jan 18, 2014)

Hi Guys,

As advertised, I'm attaching a second Demo Instrument.

This version uses dedicated variables for each slider instance so that each slider operates independently (using Lindon's *Scenario #*3 and the new *Scenario #4*). And *Ivan*, I assigned three MIDI controllers to the three sliders and wiggled them all at once without any problem that I could see. But, you'll probably find something wrong :lol: 

While dedicated 'instance' variables have been illustrated in prior posts to this thread, what is unique about this version is that it is done with a common callback handler for compact code. In fact, the callback handlers are even smaller than Demo#1. Each handler now only requires 2 lines of code for the timing machinery plus your custom routine for formatting the display text the way you wish.

Prior posted versions using arrays have also used literal, compile-time indexing that doesn't lend itself very well to using common code. To use a common handler, it is necessary to use thread-safe, run-time indexing. As a result the coding of *show_value* is a little tricky to analyze. But, keep in mind that once written, there is nothing tricky about using these macros in your code. So, you may find these useful even if you don't quite understand how they work :lol: 

Following is the new KSE source code (which is also included with the attached Demo Instrument).

_{ Timed Slider Demo#3

Demo Script with three timed-display sliders. All the timing and display management stuff is implemented
with a single callable function invoked by the on_slider macro. The callback handler that's generated
requires only 2 lines of code (plus whatever your custom formatting routine requires). 

For example, if you only want to display the slider's value (as for Slider_A), the callback handler only
uses 3 lines of code total. If the slider controls an engine par such as instrument volume, you can
use a two-line routine like fmt_volume or fmt_tune for a total of 4 code lines for the callback handlers
(as implemented for Slider_B and Slider_C). }_
``
*on init*
``message('')
``make_perfview
``*declare* *const* MAX_SLIDERS := 10``_{ max #of sliders that will be declared }_
``*declare* *const* SLIDER_HOLD := 15``_{ #of 0.1 second periods to hold value display after last change }_
``*declare* active[MAX_SLIDERS]``````_{ timer state, 0 = inactive, initiating callback id = active }_
``*declare* label_id[MAX_SLIDERS]````_{ slider's display label uid }_
``*declare* !slider_cap[MAX_SLIDERS] _{ slider's static caption text }_
``*declare* tick[MAX_SLIDERS]````````_{ 0.1 sec periods to go before display reverts to caption }_
``*declare* slider_index`````````````_{ active slider's assigned index }_
``*declare* @value```````````````````_{ formatted value of slider or what it is controlling }_

``declare_slider(Slider_A,0,127,66,5,'Slider_A')
``declare_slider(Slider_B,0,1000000,158,5,'Inst Volume')
``declare_slider(Slider_C,0,1000000,250,5,'Inst Tuning')
*end* on

on_slider(Slider_A,fmt_slider)
on_slider(Slider_B,fmt_volume)
on_slider(Slider_C,fmt_tune)

_{************** Custom display routines, you write these as you wish *********************}_

_{ These KSE routines should use the slider's value to control whatever parameter you
want and then set @value to the corresponding text string you want displayed. }_

*function* fmt_slider(slider)
``value := '' & slider``_{ value of slider itself as a text string }_
*end* *function*

*function* fmt_tune(slider)
``set_engine_par(ENGINE_PAR_TUNE,slider,-1,-1,-1)``_{ use slider to control inst. tuning }_
``value := get_engine_par_disp(ENGINE_PAR_TUNE,-1,-1,-1) & ' Semitones'``_{ display text }_
*end* *function*

*function* fmt_volume(slider)
``set_engine_par(ENGINE_PAR_VOLUME,slider,-1,-1,-1)`````````_{ use slider to control inst. volume }_
``value := get_engine_par_disp(ENGINE_PAR_VOLUME,-1,-1,-1) & ' dB'``_{ corresponding display text }_
*end* *function*

_{******************************* End of Main Script *********************************}_

*macro* declare_slider(#name#,min,max,x,y,caption)
``*declare* ui_slider #name# (min,max)```_{ create the slider itself }_
````move_control_px(#name#,x,y)
``*declare* ui_label #name#.lbl (1,1)````_{ create the caption/value label }_
````move_control_px(#name#.lbl,x,y+18 )
````set_text(#name#.lbl,caption)
````#name#.lbl->hide := 1``````````````_{ center text on transparent bkg }_
````#name#.lbl -> text_alignment := 1 
````#name#.lbl -> font_type := 12
````label_id[slider_index] := get_ui_id(#name#.lbl)```_{ uid for label }_
``*declare* #name#.sx````````````````````_{ this slider's assigned index }_
````#name#.sx := slider_index
``slider_cap[slider_index] := caption _{ remember caption for restoring later }_
``inc(slider_index) _{ next declare_slider index to be assigned at compile time }_
*end* *macro*

*macro* on_slider(#name#,fmt_val)``_{ name of slider, name of your display routine }_
``*on ui_control*(#name#)
````fmt_val(#name#)``````````````_{ text string you want displayed }_
````slider_index := #name#.sx````_{ slider index associated with this callback handler }_
````*call* show_value``````````````_{ display @value for 1.5 secs }_`````
``*end* on
*end* *macro*

_{***************************** Macro Support Function ********************************}_

*function* show_value``_{ display value for 1.5 secs after last change }_
``set_control_par_str(label_id[slider_index],CONTROL_PAR_TEXT,value)``_{ display @value }_
``tick[slider_index] := SLIDER_HOLD````_{ pump up this slider's hold time left to 1.5 seconds from now }_
``*if* active[slider_index] = 0``````````_{ if this slider's timer is not active }_
````active[slider_index] := NI_CALLBACK_ID _{ callback id that is starting the timer }_
````*while* tick[slider_index] > 0````````_{ keep timer going until 1.5 seconds after last change }_
``````wait(100000)
``````slider_index := search(active,NI_CALLBACK_ID) _{ restore index after each wait }_
``````dec(tick[slider_index])
````*end while*
````active[slider_index] := 0``_{ no value change for last 1.5 seconds, restore caption and de-activate timer }_
````set_control_par_str(label_id[slider_index],CONTROL_PAR_TEXT,slider_cap[slider_index])
``*end* *if*
*end* *function*


The features of this version are:

1. No needless callback threads.
2. Each slider times-out independently from the other sliders.
3. Very compact callback handlers due to using a common, callable function
4. Very easy to use:

Just create all the sliders you want using the declare_slider macro and create the corresponding callback handler for them using the on_slider macro. You can control the details of how you format the value display by simply providing a custom KSE function to format how the value is displayed.

Of course you can edit the declare_slider macro to add anything you might want like default value or custom skinning, etc.

Please let me know if anyone finds something amiss with this version.

Rejoice,

Bob


----------



## jdawg (Feb 2, 2014)

Now, here is the biggest question, 

so lets say you have a label showing text from a slider, 

what do you do so that is the slider is midi learned, it NO longer shows that text? so that if you controlling the slider through a midi controller, the text no longer appear, only when you use a mouse?


----------



## Big Bob (Feb 2, 2014)

The script I posted last,* TimedSliderDemo#3* can be automated without any problems that I can see.

Try downloading the demo and then assign 3 MIDI CCs to the three sliders. You can move the CCs all at the same time and each slider will display its value while the CCs are in motion and all three sliders will revert to their caption about 1 sec after you stop moving the CCs.

So, I'm not sure just what you are saying doesn't work right about the automation, please elaborate.

Rejoice,

Bob

*EDIT: I think I read your post wrong. I guess you want to keep displaying the static caption when the the slider is moved by an assigned CC ?

That may not be easy to arrange because I think automation changes result in the same callback triggering as when the slider is moved with your mouse.*

I guess if you know which CC will be assigned in advance, you could include code in the callback handler to check for changes in the controlling CC and react accordingly.
But, if the user is free to assign any CC without your script knowing about it, I don't think there will be a way to distinguish a CC move from a mouse move.


----------



## jdawg (Feb 3, 2014)

Ahhhh, thanks Bob, was worth a try : ) 
it is not a big deal, I just thought it might start looking messy with everything showing at once under CC. But this is starting to get very over complicated just for showing some values.


This actually bring me to a quick question, the delay effect, When you set this to tempo sync in the effect, I find it a little limiting. Is there any way to keep the delay time free (ms) but then under script, set it to tempo sync?

For instance, get the host bpm ($DURATION_SIXTEENTH etc etc) and then divide the delay time parameter value by some relevant amount so that if tempo syncs?


----------



## Big Bob (Feb 3, 2014)

> Ahhhh, thanks Bob, was worth a try : )
> it is not a big deal, I just thought it might start looking messy with everything showing at once under CC. But this is starting to get very over complicated just for showing some values.



Again, let me emphasize, it isn't too complicated to suppress value display when a slider is moved by automation provided that your script somehow knows the CC number assigned to do the automation.

However, the process of detecting which CC (or CCs) may be assigned to each slider is another matter. That can be very complicated.



> This actually bring me to a quick question, the delay effect, When you set this to tempo sync in the effect, I find it a little limiting. Is there any way to keep the delay time free (ms) but then under script, set it to tempo sync?
> 
> For instance, get the host bpm ($DURATION_SIXTEENTH etc etc) and then divide the delay time parameter value by some relevant amount so that if tempo syncs?



Someone else will have to comment about this question because I'm not into the beat music scene :lol: 

Rejoice,

Bob


----------



## Big Bob (Feb 4, 2014)

Hey jdawg,

I just thought of a rather easy way you could modify the Timed Sliders so they won't display their value when moved by an automation CC.

I'm attaching a Demo#5 instrument you can download and try. Assign a CC to automate one or more of the three sliders. The sliders will only display their value when moved by the mouse but won't display their value when moved by the CCs.

If this is what you wanted to do, let me know and I'll post the necessary modifications to the script to implement this.

Rejoice,

Bob

BTW I only tested superficially in standalone mode so there might be a 'joker in the deck' somewhere :lol:


EDIT: I should have mentioned that this method *does not require* that your script know what CC is assigned to the sliders by the user. The algorithm used automatically detects what it needs to know to differentiate between automation moves and mouse moves.


----------



## jdawg (Feb 5, 2014)

Thats totally it Bob, 

is there an easy way to explain what I can do to incorporate this?

I think I may be basically understanding it


----------



## Big Bob (Feb 5, 2014)

Hi jdawg,

The mouse-only display option is slider-specific so you can have a mixture if you want. The attached Demo#6 illustrates this with Slider_A and Slider_B being mouse only and Slider_C being mouse + CC.



> is there an easy way to explain what I can do to incorporate this?



I decided to put all the heavy lifting in an *Import Module* named *TimedSlidersV101*. You can simply 'import' this module into your application scripts. So, all you have to understand is how to *interface* with this module. *It will not be necessary for you to understand how the module does the work* (unless you want to).

Interfacing with this module is very easy so do not be apprehensive about it :lol: . I'm posting the source code for Demo#6 below to illustrate the relatively few lines of code you need to write to add timed sliders to your application script. Study this script and read the associated comments carefully. Hopefully, you should be able to see how to use this import module in your own scripts.

_{ Timed Slider Demo#6

This demo script illustrates how to use the TimedSliders import module to add three timed sliders to
your application script. Slider_A only displays its value (0 to 127) without controlling anything.
Slider_B controls instrument volume and displays it in dB. Slider_C controls instrument tuning and
displays it in Semitones. 

The default slider action provided by the module simply displays the slider's own value. However, you
can easily specify whatever you want the slider to control and how its value will be displayed by
writing suitable action code. Since Slider_A simply displays its value without controlling anything,
we don't need any action code for it. For Slider_B and Slider_C however, we need to write some action
code to control instrument volume and tuning and display that properly.

Action code is simply written between the keywords action_code and end_action as shown below in this
demo script. Each action code body should generally do two things.

(1) Use the slider's current value to control the desired parameter.

(2) Put the formatted text you want to display in the string variable named @value (if you want to
display something other than the slider's own value).

The generated callback handlers are very compact, requiring only three lines of code plus whatever
action code you write. 

When you declare a new slider with (the last parameter) amode = 0, it will display its value when
it is moved with the mouse or with an assigned automation CC. However, if you declare a slider with
amode = 1, it will only display its value when moved by the mouse.

If you declare one or more sliders with amode = 1, your script must also invoke hide_auto from its
Controller callback. Alternatively, if your script does not use a Controller callback, you can
simply invoke the on_cc_hide_auto macro as illustrated with this demo script. }_

*import* "TimedSlidersV101.ksp"``_{ Add timed slider capability to your application script }_

*on init*
``message('')````````_{ Clear any left-over status line messages }_
``make_perfview
``MaxSliders(3)``````_{ Specify the max number of sliders your application will use }_
``_{ Declare three sliders, specifying a suitable set of parameters for each consiting of:
name of the slider, min,max range, x,y pixel position, static caption, and amode flag }_
``declare_slider(Slider_A,0,127,66,5,'Slider_A',1)```````````_{ Display value only with mouse }_
``declare_slider(Slider_B,0,1000000,158,5,'Inst Volume',1)```_{ Display value only with mouse }_
``declare_slider(Slider_C,0,1000000,250,5,'Inst Tuning',0)```_{ Also display value when CC controlled }_
``_{ NOTE: If you need additional attributes such as persistence or a ctl-click default value,
you can simply write the additional code after each declaration. Alternatively,
you can just edit the declare_macro definition (if you know what you are doing). }_
*end* on

on_cc_hide_auto````_{ this is needed because one or more sliders specify amode = 1 }_

_{ Create the corresponding callback handlers for the three sliders }_```
on_slider(Slider_A) 
on_slider(Slider_B) 
on_slider(Slider_C)

_{***************** Write the action code you want performed by each slider *********************}_ 

action_code(Slider_B)
``set_engine_par(ENGINE_PAR_VOLUME,Slider_B,-1,-1,-1) _{ use slider to control inst. volume }_ 
``@value := get_engine_par_disp(ENGINE_PAR_VOLUME,-1,-1,-1) & ' dB'``_{ volume text to display }_``
end_action

action_code(Slider_C)
``set_engine_par(ENGINE_PAR_TUNE,Slider_C,-1,-1,-1)```_{ use slider to control inst. tuning }_ 
``@value := get_engine_par_disp(ENGINE_PAR_TUNE,-1,-1,-1) & ' Semitones'``_{ tuning text to display }_
end_action 

_{********************* End of Application Script **************************}_ 



Of course if there are any specific areas of difficulty please let me know and I'll try to elaborate.

I'll also be glad to answer any questions about the import module code itself but, please understand that it is not at all necessary to understand that code in order to use the module beneficially in your scripts.

Rejoice,

Bob


----------



## Big Bob (Feb 6, 2014)

I made a few improvements to the timed sliders import module that should make it even easier to use. This latest version also eliminates an unnecessary line of code from the callback handlers reducing them to two lines of code for the display timing control (plus any app-specific code snippet required).
 
I changed a few keywords and cleaned up the comments in both the import module and the demo script. The new import module is now named *SlidersV102.ksp*

I'm posting the *Demo#7* source code and attaching the updated package for download.

_{ Timed Slider Demo#7

This demo script illustrates how to use the Sliders import module to add three timed sliders
to your application script. Slider_A only displays its value (0 to 127) without controlling
anything. Slider_B controls instrument volume and displays it in dB. Slider_C controls
instrument tuning and displays it in Semitones. 

The basic slider generated by the module simply displays the slider's own value. However, you
can easily specify whatever action you want the slider to perform and how its value will be
displayed by using the application-specific constructors named slider_action and slider_display.
Since Slider_A is to simply display its own value without controlling anything, we don't need
to write any app-specific code for it. For Slider_B and Slider_C however, we need to write some
simple code snippets to control instrument volume and tuning and display that properly. To see
how this is done, see the slider_action and slider_display code snippets near the end of this
demo script.

When you declare a new slider with amode = 0, it will display its value when moved with either
the mouse or with an assigned automation CC. However, if you declare a slider with amode = 1,
it will only display its value when moved by the mouse. If you declare one or more sliders
with amode = 1, your script must also invoke cc_notify from your Controller Callback. Or, if
your script doesn't use a Controller Callback, you can simply invoke the on_cc_notify macro as
illustrated with this demo script. }_

*import* "SlidersV102.ksp"``_{ Add timed slider capability to your application script }_

*on init*
``message('')````````_{ Clear any left-over status line messages }_
``make_perfview
``MaxSliders(3)``````_{ Allow for at least 3 sliders in your application }_
``_{ Declare three sliders, specifying a suitable set of parameters for each consisting of:
name of the slider, min/max range, x/y pixel position, static caption, and amode flag }_
``declare_slider(Slider_A,0,127,66,5,'Slider_A',1) _{ amode = 1, show value only for mouse changes }_
``declare_slider(Slider_B,0,1000000,158,5,'Inst Volume',1) _{ amode = 1, same as Slider_A }_ 
``declare_slider(Slider_C,0,1000000,250,5,'Inst Tuning',0) _{ amode = 0, show mouse and CC changes }_
``_{ NOTE: If you need additional attributes such as persistence or a ctl-click default value,
you can simply write the additional code after each declaration in the ICB. Or, you
can just edit the declare_macro definition (if you know what you are doing). }_
*end* on

on_cc_notify````_{ this is needed because one or more sliders specify amode = 1 }_

_{ Create the corresponding callback handlers for the three sliders }_```
on_slider(Slider_A) 
on_slider(Slider_B) 
on_slider(Slider_C)

_{***************** Write application-specific slider action/display code *********************}_ 

slider_action(Slider_B)``_{ Use Slider_B to control instrument volume }_
``set_engine_par(ENGINE_PAR_VOLUME,Slider_B,-1,-1,-1) 
end_code_block

slider_display(Slider_B) _{ Display formatted instrument volume reported by NI }_
``@value := get_engine_par_disp(ENGINE_PAR_VOLUME,-1,-1,-1) & ' dB' 
end_code_block

slider_action(Slider_C)``_{ Use Slider_C to control instrument tuning }_
``set_engine_par(ENGINE_PAR_TUNE,Slider_C,-1,-1,-1) 
end_code_block``

slider_display(Slider_C) _{ Display formatted instrument tuning reported by NI }_
``@value := get_engine_par_disp(ENGINE_PAR_TUNE,-1,-1,-1) & ' Semitones' 
end_code_block 

_{********************* End of Application Script **************************}_ 



Please let me know if you have any problems with using this module in your application scripts.

Rejoice,

Bob


----------



## jdawg (Feb 7, 2014)

this is amazing, 
thanks bob

i have actually never seen the IMPORT function used before, does it simply look for a text file located in the resource folder?


----------



## Big Bob (Feb 7, 2014)

Hi jdawg,

No, the import file must be positioned *alongside* your application source code file, in this case the TimedSlidersDemo#7.ksp file. Actually, you can put the import file anywhere you want but then you must specify the full path to it in the *import* statement.

You realize I hope that all the source code I have been posting must be compiled first with Nils' KScript Editor (KSE)? See the header comments of the *SlidersV102* module for the option settings you need to use. All you need do is hit F5 and the compiled, Kontakt-ready source code file is deposited into your clipboard for pasting into Kontakt.

The* import* directive is not a native KSP thing. If you haven't been using the KSE (or the equivalent Sublime Text 3 plugin), you certainly should be. Why do things the hard way? :lol: 

Rejoice,

Bob


----------



## Phonographic (Mar 1, 2022)

Its amazing to me that you guys give so freely. I have been trying to solve this for days. Thank you Thank you Thank you.


----------



## Lindon (Mar 1, 2022)

Phonographic said:


> Its amazing to me that you guys give so freely. I have been trying to solve this for days. Thank you Thank you Thank you.


Yeah Big Bob was an *awesome* human being, he set the tone for this sub-forum for years - and it was all the better for it.


----------

