# Effectively smooth table "curve"



## Levitanus (Nov 27, 2018)

I'm working at EQ module, that draws a "curve" by the ui_table.
Now it's being rendered, by calculating the "weight" of every band for every cell in a cycle, but it looks... not like eq.





I'm thinking, what is the cheapest way to smooth it. I feel, it has to be something seemed to RMS with window-size of 10-15 cells, but not sure...


----------



## P.N. (Nov 27, 2018)

Hi.
Just a thought, as i have no idea how you're doing this...
But shouldn't you be calculating exponential functions in order to get the curves?
Maybe using sqrt to create the shape you require?


----------



## Levitanus (Nov 27, 2018)

P.N. said:


> i have no idea how you're doing this...


when I finish the library, I would to extract and make universal this module as part of pyksp))



P.N. said:


> Maybe using sqrt to create the shape you require?


well. The idea of RMS is about here: make an additional array for RMS

```
pseudocode:

for i in range(len(table)):
  sum = 0.0
  for i1 in range(i-5, i+5):
    sum += pow(table[i1], 2)
  temp[i] = sqrt(sum)
for i in range(len(table)):
  table[i] = temp[i]
```


----------



## P.N. (Nov 27, 2018)

Well, i think i lost you there. 

But one last thing that may be important.
If you're using pow, and then srqt, can't that be the reason your table results in straight, linear increments?


----------



## Levitanus (Nov 27, 2018)

P.N. said:


> If you're using pow,


i'm not using it. Just sit to implement)
the mean of pow and sqrt is getting RMS for the cell
RMS = sqrt(a^2 + b^2 + c^2)
Will write out in a hour)


----------



## P.N. (Nov 27, 2018)

Ah, alright. I don't have a lot of experience with this.
I was just trying to point out something you could have missed.
You obviously know a lot more about this than i do, so i'll refrain from offering more suggestions before i embarrass myself (any further). 

Paulo


----------



## Levitanus (Nov 27, 2018)

well, it looks better. But not without bugs. Try to resolve))


----------



## P.N. (Nov 27, 2018)

I have to say, what you're trying to achieve is pretty cool.
What's the CPU usage for something like this?


----------



## Levitanus (Nov 27, 2018)

P.N. said:


> what you're trying to achieve is pretty cool.


the original idea and implementation belongs to @EvgenyEmelyanov, I'm just porting concept and matching for my tasks it.


P.N. said:


> What's the CPU usage for something like this?


Well, now it's very bad. But there're many points to optimization:
- reduce cycles and clean up the algorithm
- unbound calculation from render (e.g. setting table array is not as efficent, as setting normal array). Also it will get off "sparks" we can see here
- extract render to the separate function, called from listener, updated ~30tps. and set the condition to be called only on xy was changed


----------



## Levitanus (Nov 27, 2018)

hey, I found the proper scheme)
https://en.wikipedia.org/wiki/Logarithmic_decrement


----------



## EvgenyEmelyanov (Nov 27, 2018)

Well, how it works right now in my first upcoming library:






@Levitanus , I'll be happy to share source code with you if needed after this library release. Maybe it might help.


----------



## Levitanus (Nov 30, 2018)

Finally, I get the curve I like))




not like ReaEQ, but seemed to internal Kontakt 3 band-eq


```
function __main__EQ__xy_callback_func
    {None}
    $xy_changed := 1
end function
on listener
    {EQ listener callback}
    if(($NI_SIGNAL_TYPE = $NI_SIGNAL_TIMER_MS) and ($xy_changed = 1))
        $xy_changed := 0
        {iterate over cells, calculating theirs weight}
        inc($_for_loop_curr_idx)
        %_for_loop_idx[$_for_loop_curr_idx] := 1
        while(%_for_loop_idx[$_for_loop_curr_idx] < 100)
            {calculate weight of every point and render cell}
            %q[0] := 1000000
            $dist_int := 0
            ~points_sum := 0.0
            ~points_touched := 0.0
            inc($_for_loop_curr_idx)
            %_for_loop_idx[$_for_loop_curr_idx] := 0
            while(%_for_loop_idx[$_for_loop_curr_idx] < 2)
                {get cell value for particular point}
                $freq := real_to_int(?EQ_xy[%_for_loop_idx[$_for_loop_curr_idx] * 2] * 1000000.0)
                $gain := real_to_int(?EQ_xy[%_for_loop_idx[$_for_loop_curr_idx] * 2 + 1] * 1000000.0)
                if(in_range(%_for_loop_idx[$_for_loop_curr_idx - 1] * 10000, $freq - %q[%_for_loop_idx[$_for_loop_curr_idx]] / 2, $freq + %q[%_for_loop_idx[$_for_loop_curr_idx]] / 2))
                    {if inside q-area of the point}
                    {get factor based on distance to point}
                    {get abs distance from freq}
                    ~pos_factor := int_to_real(%q[%_for_loop_idx[$_for_loop_curr_idx]] / 2 - abs(%_for_loop_idx[$_for_loop_curr_idx - 1] * 10000 - $freq))
                    {get multiplication value for smoothing sine curve}
                    ~pos_factor := ~pos_factor / int_to_real(%q[%_for_loop_idx[$_for_loop_curr_idx]] / 2)
                    {taking width of band in radians}
                    ~dist_real := 1.5707963267948966 * (1.0 / (int_to_real(%q[%_for_loop_idx[$_for_loop_curr_idx]] / 2) / 10000.0))
                    {getting sine value}
                    ~dist_real := sin(~dist_real * (int_to_real(%_for_loop_idx[$_for_loop_curr_idx - 1] * 10000 - $freq + %q[%_for_loop_idx[$_for_loop_curr_idx]] / 2) / 10000.0))
                    {rener cell value}
                    ~get_point_factor_return := ~dist_real * int_to_real($gain - 500000) * ~pos_factor
                else
                    {if outside q-area of the point}
                    ~get_point_factor_return := 0.0
                end if
                ~points_sum := ~points_sum + ~get_point_factor_return
                %_for_loop_idx[$_for_loop_curr_idx] := %_for_loop_idx[$_for_loop_curr_idx] + 1
            end while
            dec($_for_loop_curr_idx)
            $dist_int := real_to_int(~points_sum)
            %table_arr[%_for_loop_idx[$_for_loop_curr_idx]] := $dist_int + 500000
            %_for_loop_idx[$_for_loop_curr_idx] := %_for_loop_idx[$_for_loop_curr_idx] + 1
        end while
        dec($_for_loop_curr_idx)
        {apply array to table}
        inc($_for_loop_curr_idx)
        %_for_loop_idx[$_for_loop_curr_idx] := 0
        while(%_for_loop_idx[$_for_loop_curr_idx] < 100)
            %control0[%_for_loop_idx[$_for_loop_curr_idx]] := %table_arr[%_for_loop_idx[$_for_loop_curr_idx]]
            %_for_loop_idx[$_for_loop_curr_idx] := %_for_loop_idx[$_for_loop_curr_idx] + 1
        end while
        dec($_for_loop_curr_idx)
    end if
end on
```

how it looks in python

```
@ke.docstring
def listener(self)->None:
    """EQ listener callback"""
    with k.If((kb.NI_SIGNAL_TYPE == kb.NI_SIGNAL_TIMER_MS) &
              (self.xy_changed == 1)):
        self.xy_changed <<= 0
        self.render_table()

# @k.func
@ke.docstring
def render_table(self)->None:
    """iterate over cells, calculating theirs weight"""
    # k.logpr('render table')

    with k.For(1, len(self.table_arr)) as cells:
        for cell in cells:
            self.render_cell(cell)
    self.set_table(self.table_arr)

@k.logoff
@ke.docstring
def get_distance(self, q: int, freq: int, gain: int, pos: int) -> k.kInt:
    """get factor based on distance to point"""
    k.logpr('get_distance')
    k.logpr('pos:', pos)
    k.logpr('q:', q)
    ke.comment('get abs distance from freq')
    self.pos_factor <<= kb.int_to_real(q - kb.kabs(pos))
    k.logpr('q - abs(pos):', self.pos_factor)
    ke.comment('get multiplication value for smoothing sine curve')
    self.pos_factor <<= self.pos_factor / kb.int_to_real(q)
    k.logpr('self.pos_factor:', self.pos_factor)
    pos = pos + q
    ke.comment('taking width of band in radians')
    self.dist_real <<= (math.pi / 2) * \
        (1.0 / (kb.int_to_real(q) / 10000.0))
    ke.comment('getting sine value')
    self.dist_real <<= kb.sin(
        self.dist_real * (kb.int_to_real(pos) / 10000.0))
    ke.comment('rener cell value')
    return self.dist_real * kb.int_to_real(gain - 500000)\
        * self.pos_factor

@ke.docstring
def get_point_factor(self, q: int, index: k.kInt,
                     point_idx: k.kInt) -> k.kReal:
    """get cell value for particular point"""
    self.freq <<= kb.real_to_int(self.xy.var[point_idx] * 1000000.0)
    self.gain <<= kb.real_to_int(self.xy.var[point_idx + 1] * 1000000.0)
    with k.If(kb.in_range(index * 10000, self.freq - q, self.freq + q)):
        ke.comment('if inside q-area of the point')
        k.check()
        self.get_point_factor_return <<= \
            self.get_distance(q, self.freq, self.gain,
                              index * 10000 - self.freq)
    with k.Else():
        ke.comment('if outside q-area of the point')
        self.get_point_factor_return <<= 0.0
    return self.get_point_factor_return

@ke.docstring
def render_cell(self, index: k.kInt) -> None:
    """calculate weight of every point and render cell"""
    self.q[0] <<= 1000000
    self.dist_int <<= 0
    self.points_sum <<= 0.0
    self.points_touched <<= 0.0
    with k.For(len(self.xy.var) // 2) as points:
        for point in points:
            factor = self.get_point_factor(self.q[point] / 2,
                                           index, point * 2)
            self.points_sum += factor
    self.dist_int <<= kb.real_to_int(self.points_sum)
    self.table_arr[index] <<= self.dist_int + 500000

@ke.docstring
def xy_callback(self, control: k.kXy) -> None:
    """get needed control_params and invokate the function"""
    self.xy_ctrl <<= kb.get_control_par(control,
                                        kb.CONTROL_PAR_KEY_CONTROL)
    self.xy_shift <<= kb.get_control_par(control,
                                         kb.CONTROL_PAR_KEY_SHIFT)
    self.xy_index <<= kb.get_control_par(control,
                                         kb.CONTROL_PAR_ACTIVE_INDEX)
    self.xy_callback_func()

@k.func
@ke.docstring
def xy_callback_func(self)->None:
    self.xy_changed <<= 1

@ke.docstring
def set_table(self, array: k.kArrInt) -> None:
    """apply array to table"""
    assert len(array) == len(self.table.var),\
        f'incompatible length. desired: {len(self.table.var)}, \
        got: {len(array)}'
    with k.For(len(array)) as seq:
        for i in seq:
            self.table.var[i] <<= array[i]
```


----------



## P.N. (Nov 30, 2018)

Looks great!
Thank you for posting native code with comments. 
I'll surely be studying this - there's a lot of food for thought there.
Did you end up using the logarithmic decrement approach?

Again, kudos on these impressive results.


----------



## P.N. (Nov 30, 2018)

You probably shouldn't have called something the ~pos_factor, as that's already causing me to lose focus, though.


----------



## Levitanus (Dec 1, 2018)

P.N. said:


> that's already causing me to lose focus


sorry, I feel there is some sort of game of words here, but, probably, my English is too bad for getting it))

Sorry for the late answer, too many things to do...


P.N. said:


> Did you end up using the logarithmic decrement approach?


As I have understood, logarithmic decrement is a part of the formula to get the Q, and the full formula of getting the q value (one of them) is: Q = 2Pi / log(width of curve / 2 / speed of decay for the particular frequency). I spent the dammn day trying to get something useful from this information, but failed))
So, I just got the sine of width, based on cells involved to the curve, and make something, called ~pos_factor, which multiplies sine output within proportionally to distance from the point) Simple, but quite enough for just visualization)) without additional loops and artifacts, from the previous version within RMS.
And render happend30 times per seconds only if XY told that it has been changed. So render takes 2% CPU max on my machine.


----------



## P.N. (Dec 1, 2018)

Levitanus said:


> sorry, I feel there is some sort of game of words here, but, probably, my English is too bad for getting it))



Now i feel like i'm the ~pos_factor in this thread. 
You see, "pos" is an acronym for "piece of s#$%".
So i was constantly reading ~pos_factor as "real piece of s#$% factor"...
Nevermind...



Levitanus said:


> So render takes 2% CPU max on my machine.



Great stuff, Levitanus. I need to try this when i get the chance to decypher all that code.


----------



## Levitanus (Dec 3, 2018)

finally,



(clickable)

without code yet. Still, I'm not sure about integration to other modules and being thinking of the best way to unbend hands within connecting to various targets, engine_par part is ugly)

Including the filters bands caused a bit of refactoring, but, finally was not so scary as it looked at the morning when I thought, all the `render_cell` code has to be rewritten))


----------



## P.N. (Dec 4, 2018)

Levitanus said:


> Including the filters bands caused a bit of refactoring, but, finally was not so scary as it looked at the morning when I thought, all the `render_cell` code has to be rewritten))



Originally i thought you'd only be targeting the EQ3 or the SEQ.
I see you added another degree of complexity here.

I wasn't sure if shift and alt would work with XY pads, but it appears you're using those to activate and reset the bands/filters. Is that the case?


----------



## Levitanus (Dec 4, 2018)

P.N. said:


> Is that the case?


true)


----------

