# Log() function



## thecld (Feb 25, 2021)

Hi there,

I wonder if someone could help me understand how to use the log() function in Kontakt to convert a variable that changes it value over time in linear scale (value 0 to 5000) to a variable that has the same range but has logarithmic scaling (so in the lower ranges it changes its value faster than in the upper range)?

Thanks!


----------



## Uncle Peter (Feb 25, 2021)

If you call this function *f(x)* then you want

f(5000) = 5000

Using the log function this can be achieved as follows

f(x) = log(x)/log(5000) * 5000

i.e. when x = 5000 then the first term cancels and you're left with 5000

Edit: you could dream up other formulations also involving damping factors to 'linearise' the curve more
Note log(1) = 0 so something involving log(x/5000) is also possible.


----------



## thecld (Mar 1, 2021)

Uncle Peter said:


> If you call this function *f(x)* then you want
> 
> f(5000) = 5000
> 
> ...


Thanks! I'm not sure how to freely use this function yet (so I could set the curve exactly as I want, set the ranges, etc.), but I understand it a little better now...  Maybe it's time to brush up my maths skill


----------



## Uncle Peter (Mar 2, 2021)

thecld said:


> Thanks! I'm not sure how to freely use this function yet (so I could set the curve exactly as I want, set the ranges, etc.), but I understand it a little better now...  Maybe it's time to brush up my maths skill


Another example which might help:

Take *f(x)* as specified above:
f(x) = log(x)/log(5000) * 5000 

Say
g(x) = x

Then blend f(x) and g(x) to straighten up the line as much as you want. i.e.

h(x) = a * f(x) + (1 - a) * g(x)

where 0 < a < 1

So an equation you could use is
*h(x) = a * log(x)/log(5000) * 5000 + (1 - a) * x*

In the example below - I set a = 0.5 (a = 0 => orange line, a = 1 => blue line)


----------



## thecld (Mar 3, 2021)

Thank you sir for such a brief & easy to understand explanation! That will definitely help, will try implementing it later today!


----------



## Kery Michael (Mar 3, 2021)

Don’t get too many math questions on this forum! Nice to see the diversity of information available. 🙂


----------



## polypx (Mar 3, 2021)

Is there a simple way to adjust the severity of the 'knee'?


----------



## Uncle Peter (Mar 3, 2021)

polypx said:


> Is there a simple way to adjust the severity of the 'knee'?


Hi, yes you could introduce a power to the f(x) function. Call this new function f2(x) then:

f2(x) = (log(x)/log(5000))^ n * 5000 
I'll re-order this slightly to avoid confusion with the power term:

f2(x) = 5000 * (log(x)/log(5000))^ n

Therefore the final function is now h2(x)

*h2(x) = a * 5000 * (log(x)/log(5000))^ n + (1 - a) * x*

I just experimented with this in a spreadsheet and it seems that n only gives decent results for 1< n < 7 ish.. 

To explain a bit further: you can think of the introduction of the power term (n) as essentially multiplying a fraction by itself. For fractional values closer to zero then the effect is more pronounced than those closer to 1 - and this affects the knee. Obviously when x = 5000 you have 5000 * (1/1) ^ n = 5000 regardless of the choice of n


----------



## polypx (Mar 3, 2021)

Beautiful! Thank you!


----------



## thecld (Mar 5, 2021)

Hm, I can't make it work correctly, have anyone tried it? Don't know if I'm messing something up with the variables or @Uncle Peter gave us equation for base 10 logarithm, but Kontakt uses natural logarithm (at least that's what I've found out) .

I try to use this equation: h(x) = a * log(x)/log(5000) * 5000 + (1 - a) * x

That's how I tried to convert linear range to log, in my case attack time of an envelope:


```
function norm_int_real(value, min, max) -> return
    return := (int_to_real(value) - int_to_real(min)) / (int_to_real(max) - int_to_real(min))
end function

function lerp_int(norm_real,min,max) -> return
    return := real_to_int((int_to_real(max) - int_to_real(min)) * norm_real + int_to_real(min))
end function

if (ENGINE_UPTIME-saved_time_env) <= sli_attack
attack_time_norm := norm_int_real((ENGINE_UPTIME-saved_time_env), 0, sli_attack)
attack_time_linear := lerp_int(attack_time_norm,0,150000)
log_func := log(int_to_real(attack_time_linear))/log(150000.0) * 150000.0
log_func_02 := 0.8 * log_func + (1.0-0.8) * int_to_real(attack_time_linear)
log_func_int := real_to_int(log_func_02) - 150000
end if

change_vol(by_marks(MARK_1),log_func_int,0)
```

It seems to work for the attack phase, but I get errors like 'The result of calculation is nan!'. I tried this method for the release phase and Kontakt simply shuts off its audio engine and I had to restart it every time :D.


----------



## Wavelore Insts (Mar 5, 2021)

I'm not yet familiar with the syntax extensions you're using, but I'll just shoot from the hip and ask:
Are you attempting at some point herein to return and use log(0)? That will return -inf. 

It also looks like you're subtracting 150dB from your ultimate output in change_vol(). I'm sure you have your reasons, but at a glance, it looks kind of questionable, though I'm not sure why any of this would cause kontakt's engine to shut off.


----------



## Uncle Peter (Mar 5, 2021)

Hi, I agree with Wavelore. It looks as though you may encounter some log(0) issues if the attack time variables are permitted to be zero. I would personally limit the range from 1 to max, or add 1 to the value at the appropriate point. 

nan as you probably know stands for 'Not a number' .. log(0) = -infinity or 'undefined' which isn't a number.

I'm not so familiar with the syntax of the code . Are your variables all numeric? i.e. not string. Sorry if this is stating the bleedin obvious.

P.S whether it's natural log or log base 10 doesn't make a difference to the formulation.


----------



## thecld (Mar 5, 2021)

Wavelore Insts said:


> I'm not yet familiar with the syntax extensions you're using, but I'll just shoot from the hip and ask:
> Are you attempting at some point herein to return and use log(0)? That will return -inf.
> 
> It also looks like you're subtracting 150dB from your ultimate output in change_vol(). I'm sure you have your reasons, but at a glance, it looks kind of questionable, though I'm not sure why any of this would cause kontakt's engine to shut off.





Uncle Peter said:


> Hi, I agree with Wavelore. It looks as though you may encounter some log(0) issues if the attack time variables are permitted to be zero. I would personally limit the range from 1 to max, or add 1 to the value at the appropriate point.
> 
> nan as you probably know stands for 'Not a number' .. log(0) = -infinity or 'undefined' which isn't a number.
> 
> ...


Yeah, it was the log(0) that caused the problem... Well, at least now I know! I've changed the range from 1 to 150000 and it seems to work right now. Thank you guys for the info!

I'm subtracting 150dB, because after monitoring the output of the log function the highest it subtracts right now is around 70dB. That first 80dB must happening so fast that my listener which is refreshed every 5ms is probably not catching it at all. I should probably mess with the curve of log function, but first I just wanted to make it work and sound good enough

Thank you guys again, hope I don't have to bother you again


----------



## Wavelore Insts (Mar 5, 2021)

All cool. Glad you sorted that. As for your lost decibels of attenuation, -70dB is pretty much silent volume in Kontakt anway. I'm wondering if you can't detect the other 80dB because they're effectively not possible. If I'm right about that, then shooting for anything quieter may also cost you resolution of your desired return values.


----------



## thecld (Mar 8, 2021)

Wavelore Insts said:


> All cool. Glad you sorted that. As for your lost decibels of attenuation, -70dB is pretty much silent volume in Kontakt anway. I'm wondering if you can't detect the other 80dB because they're effectively not possible. If I'm right about that, then shooting for anything quieter may also cost you resolution of your desired return values.


To be honest I was only monitoring the variable that was then used to feed the change_vol, so I don't know exactly how much dB it attenuated. Anyway, now I'm attenuating 80dB and it's still good, so you were right - 150dB was too much . I'm using this equation with power and it works great:

h2(x) = a * 5000 * (log(x)/log(5000))^ n + (1 - a) * x

What if I'd like to create a similar slope but inverted it should start slowly and get faster the bigger the number gets. I guess I'd have to use exp function instead and then invert it's output so it would go from 0 to -80000? I've tried manipulating the log equation from above but I've soon realised that exp function behaves very different from log function (for example log(1) = 0, but exp(1) = 2.718281828459 which is a base of the natural log if I'm correct?). Is there an elegant way to get an exp scaled range with extra things like configurable knee, by feeding it with linear range like that log equation above?

Hope you guys don't mind all those questions!


----------



## Wavelore Insts (Mar 8, 2021)

thecld said:


> What if I'd like to create a similar slope but inverted it should start slowly and get faster the bigger the number gets


Looks like I've got just the ticket for you - I made this little velocity converter using just that concept a while back. It should be easily re-purposed for whatever you like (I hope). There are UI element help descriptions, but I've also commented the dickens out of it, and used more variables and expressions than strictly necessary to avoid the confusion of excessive type conversions, real # function, order-of-operations clarity, and attendant parenthetical chaos. 
This is KSP source code, so you can paste right into Kontakt.

Hope it helps.
`on init
declare ui_table %display[128](6,8,127) {displays results}
declare ui_knob $logarithmic(-100,100,1) {<0: slow log curve, 0: linear, >0: fast log curve}
declare ui_knob $logarithmic_exp(1,100,1) {log^value/10; <10: augments knee 0: natural log, >10: diminishes knee}
declare ui_knob $quarter_trig(-100,100,1) {<0: slow sin curve, 0: linear, >0: fast sin curve}
declare ui_knob $quarter_trig_exp(1,100,1) {sin^value/10; <10: augments knee 0: unaltered sin, >10: diminishes knee}
declare ui_knob $mix(0,100,1) {blends the output of sin and log controls; 0: sin only -> 100: log only}
declare ui_switch $prohibit_zeros {ensures that log control returns 1 where non-zero input values are 1}
$logarithmic:=100
$logarithmic_exp:=10
$quarter_trig:=100
$quarter_trig_exp:=10
$mix:=100
$prohibit_zeros:=1
make_persistent(%display)
make_persistent($logarithmic)
make_persistent($logarithmic_exp)
make_persistent($quarter_trig)
make_persistent($mix)
make_persistent($quarter_trig_exp)
make_persistent($prohibit_zeros)
set_text($logarithmic,"log")
set_text($logarithmic_exp,"log^")
set_text($quarter_trig,"sin")
set_text($quarter_trig_exp,"sin^")
set_text($mix,"sin->log")
set_text($prohibit_zeros,"0->1")
set_control_par_str(get_ui_id($logarithmic),$CONTROL_PAR_HELP,"Logarithmic curve. -100=slow, +100=fast, 0=linear")
set_control_par_str(get_ui_id($logarithmic_exp),$CONTROL_PAR_HELP,"Logarithmic curve exponent. 10=unchanged, Experiment to affect knee.")
set_control_par_str(get_ui_id($quarter_trig),$CONTROL_PAR_HELP,"Sine curve. -100=slow, +100=fast, 0=linear")
set_control_par_str(get_ui_id($quarter_trig_exp),$CONTROL_PAR_HELP,"Sine curve exponent. 10=unchanged, Experiment to affect knee.")
set_control_par_str(get_ui_id($mix),$CONTROL_PAR_HELP,"Sine/Log blend. -100=All sine, no log; +100=All log, no sine, 0=equal blend.")
set_control_par_str(get_ui_id($prohibit_zeros),$CONTROL_PAR_HELP,"Prohibit Zero output values on non-zero input values.")
declare ~a {stores a 0.0-1.0 for log knob}
declare ~b {stores a 0.0-1.0 for sin knob}
declare ~c {stores a 0.0-1.0 for mix knob}
declare ~linear_out {conveniently stores linear response as real number, i.e. x=int_to_real(x)}
{MIDI range (0-127) scaled by sin/log values giving 0.0-127.0 for each}
declare ~log_out
declare ~log_out_MIDI
declare ~sin_out
declare ~sin_out_MIDI
declare $counter {counts loop iterations}
declare ~counter {conveniently stores $counters as real number, i.e. int_to_real($counter) same as ~linear_out, probably redundant}
declare ~counter_inv
set_ui_height(4)
make_perfview
message("")
end on
{Loop over MIDI range 0-127 & set corresponding table value to value determined by ui controls}
function display
message("")
$counter:=0
while($counter<128)
{store knobs as real numbers 0.0-0.1}
~a:=int_to_real(abs($logarithmic))/100.0 {log}
~b:=int_to_real(abs($quarter_trig))/100.0 {sin}
~c:=int_to_real(abs($mix))/100.0 {belnd of sin/log}
{shorthands to minimize parentheses/type conversions later}
~counter:=int_to_real($counter)
~counter_inv:=127.0-~counter {invert counter for use in "slow" curves}
~linear_out:=~counter
{sin values}
if ($quarter_trig>=0) {>=0 uses 1st 1/4 of sin function for "fast" curve}
~sin_out:=sin(2.0*~NI_MATH_PI*(~counter/4.0)/127.0)
else {< 0 flips this vertically and horizontally, reflecting last 1/4 normalized to 0.0-1.0 for a "slow" sin curve}
~sin_out:=1.0-sin(2.0*~NI_MATH_PI*(~counter_inv/4.0)/127.0)
end if
~sin_out:=pow(~sin_out,int_to_real($quarter_trig_exp)/10.0) {multiply sin by itself x/10.0 times to augment or diminish effect}
~sin_out_MIDI := ~sin_out*127.0 {get a MIDI value for calculated sin value. Leave as real# for now}
{log values}
if ($logarithmic>=0) {>=0 uses straight log function for "fast" curve, but...}
~log_out := log(~counter+1.0)/log(128.0) {we also add 1 to our input and the max so it's sure to return >0-1.0}
else {< 0 flips this vertically and horizontally,for a "slow" log curve, BUT...}
~log_out := 1.0-(log(~counter_inv+1.0)/log(128.0)) {as above, we add 1 to our input and the max so it's sure to return >0-1.0}
end if
~log_out:=pow(~log_out,int_to_real($logarithmic_exp)/10.0) {multiply log by itself x/10.0 times to augment or diminish effect}
~log_out_MIDI := ~log_out*127.0 {get a MIDI value for calculated cos value. Leave as real# for now}
{blend sin and log outputs per the "mix" knob}
%display[$counter] := real_to_int(round(...
((~a*~log_out_MIDI+((1.0-~a)*~linear_out))*~c)+((~b*~sin_out_MIDI+((1.0-~b)*~linear_out))*(1.0-~c))))
{allow output value of 1 on non-zero input values where they'd otherwise be zero, if switch engaged.}
if ($prohibit_zeros=1 and $counter>0)
%display[$counter]:=(%display[$counter]*(%display[$counter]/%display[$counter]))+(1-(%display[$counter]/%display[$counter]))
end if
inc($counter)
end while
end function
on note
{convert velocity of incoming notes as described by our table}
change_velo($EVENT_ID,%display[$EVENT_VELOCITY])
end on
on persistence_changed
call display
end on
on ui_control($logarithmic)
call display
end on
on ui_control($logarithmic_exp)
call display
end on
on ui_control($quarter_trig)
call display
end on
on ui_control($quarter_trig_exp)
call display
end on
on ui_control($mix)
call display
end on
on ui_control($prohibit_zeros)
call display
end on`


----------



## Uncle Peter (Mar 9, 2021)

thecld said:


> h2(x) = a * 5000 * (log(x)/log(5000))^ n + (1 - a) * x
> 
> What if I'd like to create a similar slope but inverted it should start slowly and get faster the bigger the number gets. I guess I'd have to use exp function instead and then invert it's output so it would go from 0 to -80000?


Hi, let's start at the beginning:

We have our original *f(x)* which is the log curve multiplied by a constant to arrive at the scaling you require (start without the power):

f(x) = log(x)/log(5000) * 5000
Whatever the end value is... 5000 or 2 let's just call this v
Note that logs of negative numbers are not defined hence v > 0
f(x) = log(x)/log(v)*v
(I will accommodate the request for a negative v at the end)

let's use *ln* instead of *log* to denote natural logarithms. (although programmatically you still type 'log' in Kontakt scripting):

f(x) = ln(x)/ln(v) * v

This can be reflected in the line y= x to get the shape that you want. To do this rename f(x) as y:

y = ln(x)/ln(v) * v

Then swap y and x and rearrange to get the new expression for y:

x = ln(y)/ln(v) * v
=> ln(y) = x * ln(v)/v

exponentiating both sides:
exp(ln(y)) = exp(x * ln(v)/v)
y = exp(x *ln(v)/v)

call this your new f(x)

As before blend to taste with the line g(x) = x

h(x) = a * f(x) + (1-a) * g(x)
i.e:

*h3(x) = a * exp(x * ln(v)/v) + (1 - a) * x*
Note this only works for v > 0 .

For v < 0 then feed in the positive value of v ( e.g. for -80000 then v = 80000) into f(x) and multiply f(x) by -1

hence for v < 0
*h4(x) = -a * exp(x * ln(|v|)/|v|) - (1 - a) * x*
*|v|* is the absolute value of *v *or the positive version of the number..

Note this blends with the line y = -x which I assume is what you'd want

The power version can be reached in a similar fashion..

so explicitly, your formulation is:

*h4(x) = -a * exp(x * ln(80000)/80000) - (1 - a) * x*
Where 0 < a < 1 is of your choice (e.g. 0.5)


----------



## thecld (Mar 19, 2021)

@Wavelore Insts @Uncle Peter thank you guys so much for the knowledge. Not only has it allowed me to move forward with my project, but also understand a little bit more 

I hope others will find this thread as useful!


----------

