# The formula for getting the KSP modulator intensity value from desired intensity percentage



## aaronventure (Oct 17, 2019)

I didn't see this documented anywhere so I'll post it here. 

It appears that Kontakt's modulation intensity slider follows a power curve (y = ax^b). Here's a formula that will return a value from 0-1000000 when you put in a desired percentage.

`value = 100000 * percentage ^ 0.5`

So if you wish to get a value for 30%, you calculate 

`value = 100000 * 30^ 0.5 = 547722`

The code that you use instead of the actual value for the $ENGINE_PAR_MOD_TARGET_INTENSITY parameter is


```
real_to_int (100000.0 * pow(30.0, 0.5))
```

Write your arrays and variables accordingly. It's much easier to apply your own curves using the linear 0-100 scale.

If you're using an integer, don't forget `int_to_real`

```
declare $percentage := 30

real_to_int (100000.0 * pow(int_to_real($percentage), 0.5))
```


----------



## EvilDragon (Oct 17, 2019)

Useful. There was a function for this in Big Bob's math library that uses CORDIC math (since back then all we had were integers) to get to the same solution - I personally use lookup arrays for this.

Also don't forget that there's also $ENGINE_PAR_INTMOD_INTENSITY which is bipolar (goes -100.0% ... 100.0%). I wonder if it follows the same curve, but flipped over halfways?


ALSO - there is one modulation destination for which the original power curve scaling works better, and this is pitch. You get a lot more control in the first half of the UI control range (500000 amounts to 3 semitones), which is what is usually necessary.


----------



## EvilDragon (Oct 22, 2019)

The formula for $ENGINE_PAR_INTMOD_INTENSITY gets a bit more complicated because we don't have a command for 3rd order root, and if we use pow(x, 1.0/3.0) it will return NaN for negative x, which is what we don't want. So the formula is (with x in range of -1000 ... 1000 to cover +/- 100.0%):


```
~value := 50000.0 * pow(int_to_real(abs($amt)), 1.0 / 3.0)
if ($amt < 0)
    ~value := 500000.0 - ~value
else
    ~value := 500000.0 + ~value
end if
```


----------



## Fredeke (Oct 22, 2019)

@aaronventure : That's exactly right.
@EvilDragon : Thank for the tip !


----------



## Tod (Oct 23, 2019)

Quite a while back I put together a graph that gives an idea how the velocity intensity works in Kontakt.


----------



## aaronventure (Apr 27, 2021)

EvilDragon said:


> The formula for $ENGINE_PAR_INTMOD_INTENSITY gets a bit more complicated because we don't have a command for 3rd order root, and if we use pow(x, 1.0/3.0) it will return NaN for negative x, which is what we don't want. So the formula is (with x in range of -1000 ... 1000 to cover +/- 100.0%):
> 
> 
> ```
> ...


I never quite tackled this because I didn't need it until now. But here we go. 

The equation (symmetric sigmoid) for $ENGINE_PAR_INTMOD_INTENSITY that returns a value 0-1000000 from integer $x amount in cents (100 cents = 1 semitone) is: 

```
$y = 500000 + real_to_int(47181.4 * pow(int_to_real($x), 0.3330386))
```

So if you have a variable $x which stores your desired slider max in cents (even in negatives), then you can do:

```
if (x >= 0)
            set_engine_par($ENGINE_PAR_INTMOD_INTENSITY, 500000 + real_to_int(47181.4 * pow(int_to_real($x), 0.3330386)), $i, find_mod($i, "MOD_NAME"), -1)
        else 
            set_engine_par($ENGINE_PAR_INTMOD_INTENSITY, 500000 - real_to_int(47181.4 * pow(int_to_real(abs($x)), 0.3330386)), $i, find_mod($i, "MOD_NAME"), -1)
        end if
```

Also, the following formula (cubic polynomial) will return the amount of cents from a slider value (it will also take into account the invert button). There are 3 places you have to substitute X for. 

```
~slider_cents := -1200.0 + 0.0072 * int_to_real(x) - 1.44 * pow(10.0, -8.0) * pow(int_to_real(x), 2.0) + 9.6 * pow(10.0, -15.0) * pow(int_to_real(x), 3.0)
```

If you want to have it read a mod and return the amount of cents it's set to, then it's this ($i is group number)

```
$cents := real_to_int(-1200.0 + 0.0072 * int_to_real(get_engine_par($ENGINE_PAR_INTMOD_INTENSITY, $i, find_mod($i, "MOD_NAME"), -1)) - 1.44 * pow(10.0, -8.0) * pow(int_to_real(get_engine_par($ENGINE_PAR_INTMOD_INTENSITY, $i, find_mod($i, "MOD_NAME"), -1)), 2.0) + 9.6 * pow(10.0, -15.0) * pow(int_to_real(get_engine_par($ENGINE_PAR_INTMOD_INTENSITY, $i, find_mod($i, "MOD_NAME"), -1)), 3.0))
```


----------



## WocherMusic (Dec 27, 2021)

There are geniuses in this forum.
What would the Kontakt community be without all of you...
Thank you so much!


----------



## maxchristensenaudio (Mar 14, 2022)

Can someone tell me what the calculation needs to be for when you only need either positive or negative modulation? So in my case going from 500k - 1.000k ?

I've tried to adjust it myself but I'm not got enough at math to figure this out...

I actually still have my TI-nspire cas from math class that can draw curves and stuff but I completely forgot how to use it :D


----------



## aaronventure (Mar 14, 2022)

maxchristensenaudio said:


> Can someone tell me what the calculation needs to be for when you only need either positive or negative modulation? So in my case going from 500k - 1.000k ?
> 
> I've tried to adjust it myself but I'm not got enough at math to figure this out...
> 
> I actually still have my TI-nspire cas from math class that can draw curves and stuff but I completely forgot how to use it :D


It's been a while since I've been in this exact space so it'd take me a bit to get back into it, but I'll just assume this is what you mean?


```
set_engine_par($ENGINE_PAR_INTMOD_INTENSITY, 500000 + real_to_int(47181.4 * pow(int_to_real($x), 0.3330386)), $i, find_mod($i, "MOD_NAME"), -1)
```

Replace $x with your desired positive change in cents, $i with your group number. MOD_NAME with the modulator name, obviously. 



EvilDragon said:


> ```
> ~value := 50000.0 * pow(int_to_real(abs($amt)), 1.0 / 3.0)
> if ($amt < 0)
> ~value := 500000.0 - ~value
> ...



I guess you can also use the lower half of EvilDragon's code here, but I haven't checked (or at least don't remember checking) it out myself.


----------



## maxchristensenaudio (Mar 14, 2022)

Almost, sorry for not clarifying. It's not about pitch for me but LFO phase.
My slider goes from 500k-1000k and a graphic that goes from 0-360 degrees.
At 180 degrees, the modulation intensity has only reached 12,5 % and I need it to be 50%

For my modulation matrix I'll check if your first example works

```
value = 100000 * percentage ^ 0.5
```


----------



## EvilDragon (Mar 14, 2022)

Your slider should be 0-1000 and then you use this lookup table so that slider value of 50% means exactly 50% modulator amount. This trades a bit more memory for CPU efficiency (since you just do a single table lookup instead of any math calculations).


----------



## maxchristensenaudio (Mar 14, 2022)

Oh cool! ok that works too. Are tables like this available anywhere publicly? Or do you just have to hope that experienced forum people like you pass these on?

So in my case my slider needs to go from 500-1000 ? 
The passed engine values need to go from 500k - 1000k as I don't want negative modulation values in this case.

I would actually prefer the math method for various reasons (and I'm also curious how to calculate it) but I'll take the loop up method if it works!


----------



## EvilDragon (Mar 14, 2022)

Your slider needs to go 0-1000 if you want to cover phases from 0 to 360°. Use the array with $ENGINE_PAR_MOD_TARGET_INTENSITY, not $ENGINE_PAR_INTMOD_INTENSITY.


----------



## maxchristensenaudio (Mar 14, 2022)

But [0] of the array would give me -100% in engine would it not? 
I only want it to go from 0 - 100%

Also I'm using a constant modulator for this.


----------



## EvilDragon (Mar 14, 2022)

No, it wouldn't if you use $ENGINE_PAR_MOD_TARGET_INTENSITY, as I told you  That lookup array was created only for use with $ENGINE_PAR_MOD_TARGET_INTENSITY, not $ENGINE_PAR_INTMOD_INTENSITY (there's a different lookup array that linearizes bipolar mod amount when you use $ENGINE_PAR_INTMOD_INTENSITY).


----------



## maxchristensenaudio (Mar 14, 2022)

Ok... I'l be damned, it works.
But I REALLY don't get why...

Apparently I don't even need the lookup table as long as I change the INTMOD_TARGET to MOD_TARGET .... (though it seems to be a bit more accurate. Without the table I'm arriving at 57% at 180 degrees)

I thought the whole point of those two engine_pars was that they target different kinds of modulators.
I was originally mistaken in using INTMOD for targeting the constant because it's NOT an internal modulator. But I would have expected Kontakt to return an error in that case.

What's really going on here? Can I use INTMOD or MOD for everything and they just behave differently in the way they apply the value to the engine?


----------



## EvilDragon (Mar 14, 2022)

INTMOD_INTENSITY is a leftover from K2 times, kept for compatibility. There's also EXTMOD_INTENSITY which behaves the same. They are both undocumented in current KSP reference specifically because they're legacy engine pars. In K2 you had to target internal modulators with the former and external modulators with the latter. And if you see any Kontakt 2 screenshots lying around, you would see that modulation amount was a bipolar slider back then (hence the behavior of those engine parameters).



maxchristensenaudio said:


> What's really going on here? Can I use INTMOD or MOD for everything and they just behave differently in the way they apply the value to the engine?


That's exactly the case.


----------



## maxchristensenaudio (Mar 14, 2022)

wooow ok! That makes things much easier! 
Thanks a lot  

Sometimes I wish the Reference Manual was a public Git.
It's quite frustrating having to find out things like this through a forum instead of reading about it in the actual Manual where it SHOULD be.

I think during the whole development process of my current project I've come across 3-5 edge cases where things that I needed just weren't in the manual.


----------



## EvilDragon (Mar 14, 2022)

It's a reference manual, not a handholding guide.  And deprecated functions and commands are usually not something that should be documented for obvious reasons.

That said, there's real use in $ENGINE_PAR_INTMOD_INTENSITY, but it is unfortunately not named correctly/consistently. So having an alias for it, something like $ENGINE_PAR_MOD_TARGET_NP_INTENSITY (NP for negative-positive), would be useful. Let's see if that happens


----------



## maxchristensenaudio (Mar 15, 2022)

Ok so I applied your suggestions and now the modulation intensity scales much better with the slider!!

Small follow up math problem that I'm not sure how to tackle:
Additional to my modulation amounts being applied by a slider, I have a total modulation amount slider for all modulators letting the modulation through 0-100% 
Though it works correctly for both positive and negative modulation, it doesn't scale correctly.

Any ideas what I need to change to have the total modulation limiting work correctly at 50% ?


```
amt := slider
// ~value := conversion for internal power curve applied to bipolar mod targets
~value := 50000.0 * pow(int_to_real(abs(amt)), 1.0 / 3.0)

if ($amt > 0)
    // last_amount limits modulation by 0-100%
    ~value := 500000.0 + abs(int_to_real(arr_LFO_last_amount_slider_value[current_LFO]) / 100.0 * ~value)
else
    ~value := 500000.0 - abs(int_to_real(arr_LFO_last_amount_slider_value[current_LFO]) / 100.0 * ~value)
end if   
async_IDs[target] := set_engine_par(ENGINE_PAR_INTMOD_INTENSITY, abs(real_to_int(~value)), group_i, %arr_LFO_mod_idx[current_LFO], target)
```


----------



## EvilDragon (Mar 15, 2022)

You need to apply that limiting on the amt value, I think. So, inside that first pow().


----------



## maxchristensenaudio (Mar 15, 2022)

EvilDragon said:


> You need to apply that limiting on the amt value, I think. So, inside that first pow().


Yep that seems to do the trick!


----------



## davidnaroth (Oct 28, 2022)

what would the inverse of this be if I wanted to find out the percentage?
value = 100000 * x^ 0.5
I'm a bit stumped on working it out


----------



## aaronventure (Oct 29, 2022)

davidnaroth said:


> what would the inverse of this be if I wanted to find out the percentage?
> value = 100000 * x^ 0.5
> I'm a bit stumped on working it out


`percentage^0.5 = value/100000`
therefore 
`percentage = (value/100000)^2`


----------



## davidnaroth (Oct 29, 2022)

aaronventure said:


> `percentage^0.5 = value/100000`
> therefore
> `percentage = (value/100000)^2`


Greatly appreciate it! I'm going back studying all the math I've forgotten. If I may ask, how did you get the exponent 2 instead of 0.5? That was the missing key for me, I kept plugging in 0.5 and obv that wasnt working.


----------



## EvilDragon (Oct 29, 2022)

It's simply how the math works out when you move the ^0.5 exponent to the other side of the equation.

x^0.5 = y

is the same as

x = y^2


----------



## davidnaroth (Oct 29, 2022)

@EvilDragon Ah, I didnt realize that x^0.5 was the same as sqrt(x), like i said, I need a hard refresh of my math ha.


----------



## aaronventure (Oct 31, 2022)

To make it clearer, it's how exponentiation works. Consider for a second 10^2 * 10^3. 

`10^2 * 10^3 = 100 * 1000 = 100000 = 10^5 = 10^(2+3)`
When multiplying the numbers with the same base, the exponents add up. 

`(10^2)^3 = (100)^3 = 1000000 = 10^6 = 10^(2*3)`
In exponentiation where the base already has an exponent, the exponents multiply. 

In `percentage^0.5 = value/100000` we need to get just `percentage`. In order to make its exponent 1, it needs to be multiplied by 2. So we square both sides, and the exponents of the left one end up being `0.5 * 2 = 1`

I'm sure you remembered all of this by now.


----------



## Gablux (Oct 31, 2022)

Fantastic thread, I'm posting just to be able to come back to it easily in the future. Math = 💙


----------

