# Graphically-adjustable Contour Maps



## Big Bob

*EDIT: 7-27-13
I have replaced the zip file with the latest V102 of the GMaps Module. If you have put a copy of this module aside for future reference, you may want to DL the latest.*

A while back I dropped a post about combining a ui_table and a mapping array so that you could adjust the contour of the map graphically by clicking or dragging on the curve itself. I just completed coding a GMaps module that can be imported into any application script to easily add this functionality. These maps can be used to apply a custom contour to things like velocity and other MIDI CCs as well as any 128-level data that you might want to shape.

I'm attaching a little Demo Instrument with 5 GMaps that you can play around with. :lol: (See screen shot below)

The contour of any map can be changed by simply clicking on the xy point you want the curve to pass through. You can also drag the curve if you drag slowly**. The curves are actually very easy to adjust. If you want to bend a curve quite a distance from where it is now, the fastest way is to first click approximately where you want the new curve and then slowly drag the curve to refine the final position/shape.

The default start and end point height of the curve is set to 0 and 127 respectively however, you can easily change this by simply clicking/dragging bar 0 or bar 127 to the desired height. 

I have also attached well-commented source code for both the Demo script and the GMaps Module. By reading the Demo script's comments as you study the examples presented, it should be clear how you can use the module's interface directives to add GMaps to any application. All the heavy lifting is done by the GMaps Module.

Also take a look at the Note and Controller callback handlers provided with the Demo Script for an illustration of how you can use GMaps to shape velocity and expression.

The compiled code is very compact and efficient . The basic support code added to your script is only 137 lines plus 18 additional lines per GMap you declare.

I would appreciate any feedback pro or con and please let me know if you experience any problems. 

Enjoy,

Bob

BTW The Demo Instrument can be loaded into either K4 or K5 and the source code can be compiled with KScript Editor V151 or V152.

** Unfortunately Kontakt's ui_table element locks out script updates totally when dragging rapidly.


----------



## coraxcorax

Oh this looks like fun :D


----------



## polypx

Very interesting Bob!


----------



## Raptor4

Very nice idea and work indeed!
If I understand correctly, the idea is when you click any Table Position (0-128) "Pos" ones your code locks all other "Pos" ones and makes Math "loop" processing according to the current "Pos" value tweaking?
I have noted some slight CPU spikes (up to 10%) during the left mouse button curve tweaking and this is understandable for continuous "while" loop operation.
*Hint:* If you try to tweak the table using your right mouse button (the mouse cursor switches to "+" line tool) you can draw a short line anywhere in the table so the curve will be made immediately without any CPU spikes.
Thanks for sharing that great code Bob!

God Bless

____________________
www.audiogrocery.com


----------



## Big Bob

Hi Ivan,



> Hint: If you try to tweak the table using your right mouse button (the mouse cursor switches to "+" line tool) you can draw a short line anywhere in the table so the curve will be made immediately without any CPU spikes.



Thanks for sharing that. I didn't realize that the right mouse button did anything (I guess I never tried it before). I'll have to look into how the callback behaves (when the right button is being used) to see if there is anything special I can exploit to improve things a bit.



> If I understand correctly, the idea is when you click any Table Position (0-128) "Pos" ones your code locks all other "Pos" ones and makes Math "loop" processing according to the current "Pos" value tweaking?



I'm not sure I follow what you are saying here about 'all the bar 1s' etc. So, I'll just describe what I am doing in response to a single point click.

1.* read_px()* scans all the bar heights from left to right to find the first one (left-most) that has changed since the Map was last updated. The bar index is put in *px* and that bar's height is put in *py*.

2. If *px* is in the range 1..126, *adjust_cxy* is invoked.

3. *adjust_cxy* begins by computing the distance from Map[px] to py. This is the initial error put in the variable *err*.

4. *adjust_cxy* then starts a servo loop to drive this error to zero. This is done by:

4.1 Moving the Bezier control point (CX,CY) to reduce the error
4.2 The Map is then updated by recomputing all 128 points by calling on the *bezier* function.
4.3 The error (which is now smaller) is updated.
4.4 The servo loop body is then repeated until the error is driven to zero.

5. When *adjust_cxy* exits, the ui_table is updated to again match the Map.

The servo loop usually exits in less than 3 or 4 passes although it can sometimes take a few more. The bezier function itself is the slowest component in this process and it typically takes about 300 micro seconds to execute. So, for a typical situation, *adjust_cxy* can be expected to take about a millisecond or so to run.



> I have noted some slight CPU spikes (up to 10%) during the left mouse button curve tweaking and this is understandable for continuous "while" loop operation.



When dragging slowly, there will be a series of px,py values input and each one (that can be detected in the callback) will take about 1 msec of processing time. Generally, if you just single-click a point, the curve will immediately jump there and you usually won't see a CPU spike. When dragging, there may be an occasional spike. However, I'm not too concerned about this sort of thing because in real-life applications, your script will only be reading from the Map and the Maps will be changed only sparsely.



> Thanks for sharing that great code Bob!
> 
> God Bless



Always glad to share whatever comes my way and,

God Bless you too.

Bob


----------



## PMortise

@ Bob: Very kind (and clever) of you - thanks for sharing this!

@ Ivan: Thanks for the helpful hint!

You guys are like KSP Jedis. =o


----------



## Raptor4

Hi Bob,

Thanks for the detailed information! It matches at least 90% the construction principles I was thinking about - off course I'd do my way :D .



> I'll have to look into how the callback behaves (when the right button is being used) to see if there is anything special I can exploit to improve things a bit.


The right mouse button operates as "on mouse button release" as one shot CB.



> Generally, if you just single-click a point, the curve will immediately jump there and you usually won't see a CPU spike. When dragging, there may be an occasional spike.


As you mentioned before a single left mouse click works better without spike - that's true and quite understandable. Have you thought to "Time" limit the last CB - something like this:



Code:


on init
message("")
declare ui_table %table[128](5,6, 127)
declare $last_tbl_CB
end on

on ui_control(%table)
message("Hello") 
$last_tbl_CB := $NI_CALLBACK_ID 
wait(250000)
if ($NI_CALLBACK_ID = $last_tbl_CB)
message("")
end if 
end on


You can set a suitable custom wait time which will prevent multiple CBs - just an idea.

My best,

Ivan
_____________________
www.audiogrocery.com


----------



## Big Bob

Thanks for your suggestion Ivan.

When I analyzed table callback activity, it was my observation that for a single click, a pair of callback triggers occur (separated by a very short time period -- typically less than 100 us). However, when dragging, new data changes are clocked out with a period around 50 ms (along with some interspersed high-speed doubles).

The problem is that when dragging rapidly, even though all the data changes are given to the scrip, when the script tries to react by writing to the table, the data is just lost. :( I think I concluded that NI uses a single buffer for both reads and writes. If the script writes to the buffer it will only be transfered to the display if no mouse changes occur for one clock period. If another mouse change to the table is made within 50 ms from when the script writes to the buffer, the buffer is overwritten with the new mouse changes and the script-written data is lost. Thus when dragging rapidly, script updates to the display can only occur 'in the cracks' so to speak (usually near the start or end of the drag or when the user drags kind of jerky).

Using some form of time-dependent callback lockout will not address this issue. The best you can do is to discard redundant callback triggers to keep the CPU drain down during rapid dragging. I experimented with all manner of such things and finally concluded that simply locking out triggers occuring very closely spaced to each other provided the easiest and most effective weeding technique. This is why I am simply discarding any callbacks that occur within 1 ms of the last one. Generally, I try to avoid using a *wait* statement in a callback unless absolutely necessary. If you *don't* use a wait, the pre-emptive multi-tasking model used by the KSP insures that the callback will always be finished before another can run. Such of course is not the case when *waits* are involved. The re-entrance potential this creates is often tricky to analyze when trouble strikes.

However, I am going to try to spend some time in the next day or two with the right mouse button callback behaviour just in case it can somehow be exploited in some way. I sort of doubt it but, you never know until you try :lol: 

To be continued ...

Bob


----------



## Big Bob

This Post deleted, see original post.


----------



## Raptor4

Hi Robert,

I did not believe the you can live without any update :D .
Thanks for V101 by the way !
Could you explain in short what's new in that update cause I could not find much difference when compared to V100 - I'm sorry.
Anyway you kick me out to do some more tests so I found a few interesting behaviors/hints which I want to share here if you do not mind ?

Table Shapes Making Hints

Let's unify the Table elements terminology first.
The white arrows in the image below show the table monitor (in the left upper corner), so let's define the left value labeled with # symbol as "Pos" (Table/Map position), and the right value as "Val" (Map position Value).

1. In Step 1 I have tweaked Pos# 0 and Pos# 127 to shape the GMap table as "Reversed" one. Doing that you will reset the curve to "line"!

2. In Step 2 you can draw a custom Exponent curve inside Pos# 1-126.

Hope this hints make sens :roll: ?

_EDIT: I decided to share these hints cause I tried to draw a reversed Exponent/Curve in one go by drawing a reverse curve staring from Pos# 0 and ending to Pos#127, but it did not work._

Regards
_____________________
www.audiogrocery.com


----------



## Big Bob

Hi Ivan,

Thanks for posting that clarification. I guess I should have posted a set of guidelines for setting up and adjusting curves because my initial description was probably not as clear as could be :wink: 

Here is all the pertinent info needed to set up and adjust a curve.

1. Curves always begin at bar 0 and end at bar 127 (by bar I'm referring to what you are calling Pos #).
2. The height of bar 0 and/or bar 127 can be altered by clicking on the bar at the new height you want. For example, you can invert the curve by clicking bar 0 at max height (127) and bar 127 at min height (0).
3. Whenever you change the height of bar 0 or bar 127, the curve is reset to linear to start with. You can use this to advantage whenever you want to reset a contour to linear. All you have to do is wiggle bar 0 or bar 127 (leaving it where it was) and the shape will revert to linear.
4. To alter the curvature (contour) of the GMap, click on any point on any bar *other than bar 0 or bar 127*. The curve will reshape itself to pass through the point you clicked (if possible)
5. If you click a point outside the limit of the Bezier function, the curve will assume its maximum convex or concave shape. You can use this behaviour see the max convex or concave shape available (simply click in the upper-left (but not on bar 0) corner or the lower-right corner (but not on bar 127).
6. You can drag the curve (or the height of either end bar) but you must drag rather slowly.

Perhaps I should also emphasize that each GMap has an associated array that can be read by an application script. I've had several emails about this so perhaps others are wondering about this aspect of GMaps.

Instead of several separate 128-element arrays each with their own name, there is essentially one large 2-dimensional array named *Map* and any GMap can be accessed with a standard 2-dimensional array syntax like this:

mapped_output := *Map*[map_input,mx] 

where mapped_input and mapped_output are values from 0 to 127 and mx is the assigned index of the specific GMap you want to use. Map access is very efficient, almost as efficient as reading a single-dimension mapping array.

Changes since V100 have been relatively minor, here is a summary.

1. I removed the 'overtravel allowance' for the CX,CY bezier control points. There were some undesired artifacts associated with this and its only purpose was to allow a slightly exaggerated convex/concave shape to be accommodated. 
2. Tightened up the coding for *update_map* and *bezier*.
3. Added, embellished, or corrected header comments

Nothing Earth shaking but you know me, I never know when to quit. :lol: In fact, I hate to tell you this but updated V101 to V102 this morning (there were a few things that should have been changed for V101 but I missed them yesterday). So, you may want to DL it again 8) .

Rejoice,

Bob

BTW I think I'm done twiddling with it now, but then I always think that just before the next change :lol:


----------



## Raptor4

> I guess I should have posted a set of guidelines for setting up and adjusting curves because my initial description was probably not as clear as could be


Hi Robert,

Thanks for posting the details! Yes, it is a good idea if you gather all of your guidance notes into a PDF or any documentation type.
Regards,
Ivan
____________________
www.audiogrocery.com


----------

