# LFO from different groups slightly out of phase [SOLVED via KSP]



## Fredeke (Jun 1, 2019)

Hi again.

I set up an LFO modulating the pitch in the source module of every group (using Edit All Groups) in order to create a synth-like vibrato effect.

Is that the right way to do it ? Because I ran into the following issue:

When several groups are playing at once, their LFOs are very slightly out of phase, which results in a fat, highly detuned, unison effect. 

Setting the LFO to retrigger gets rid of the problem, but I don't want it to retrigger: I want a free-running LFO, like on vintage synths.

What did I do wrong?

(Did I mention I have hundreds of groups, though obviously only a few play at once? And I use rates in the order of 5 Hz btw)


----------



## P.N. (Jun 2, 2019)

Fredeke said:


> Setting the LFO to retrigger gets rid of the problem, but I don't want it to retrigger: I want a free-running LFO, like on vintage synths.



Hum... i see what you mean.
I think for this this, you should probably script an LFO to change_tune() "simultaneously" for all the groups you wish to affect.

The minimum listener time in ms is 1, so i'm not sure if it will still result in phasing issues, though.
But maybe you can call a function from listener to loop the change_tune. That might work.

Cheers.


----------



## Fredeke (Jun 2, 2019)

I'll try that.
But doesn't change_tune() work at the note event level, instead of the group level ?

The up side of that would be that it'll affect any group in the event.

But wouldn't the downside be that new note events can't be created by the unison script that comes downstream, or they woudn't be affected by vibrato? So I would need to change the order of the scripts, which amounts to redesigning a considerable part of the instrument...


----------



## P.N. (Jun 2, 2019)

Ah, you have this in multiple scripts? I see.
If you didn't you could add all the current events (including the unisons) into the array that would be used in the change tune function.
Still, you could probably do this with PGS keys, i imagine. 
Probably best to wait and see if someone else has a better suggestion.


----------



## Fredeke (Jun 2, 2019)

P.N. said:


> Ah, you have this in multiple scripts? I see.
> If you didn't you could add all the current events (including the unisons) into the array that would be used in the change tune function.
> Still, you could probably do this with PGS keys, i imagine.
> Probably best to wait and see if someone else has a better suggestion.


Either solution will entail more work than anticipated, and now that I think of it some architecture redesign might not hurt. Only it will probably end up in partial redesign of the UI and I hoped I was done with drawing graphics. Well...

And since no one proposed another solution, I suppose I'll try change_tune() in either a loop or a (bunch of?) listener(s).[EDIT: my bad - there's only one listener]

Can we really change the tune fast enough on many simultaneous note events for the change to sound smooth, without slowing things down too much? I guess I'm about to find out...


----------



## Fredeke (Jun 2, 2019)

Actually I found the solution and it's surprisingly simple - or short a script, anyway. Thanks to your idea of using change_tune(), I did this:

```
on init
  declare ~vibrato_hertz := 5.0
  declare $vibrato_cents := 15
  set_listener ($NI_SIGNAL_TIMER_MS, 10000)
end on

on listener
  change_tune ($ALL_EVENTS,  ...
               real_to_int (sin ( (int_to_real ($ENGINE_UPTIME) * ~vibrato_hertz / 1000.0) * 2.0 * ~NI_MATH_PI ) * 1000.0) * $vibrato_cents  ...
               ,1)
end on
```
Just place this script in the last slot, and it will affect all playing sounds. Note that it requires a version of Kontakt dealing with real numbers and trigonometric functions, which version 5.7 does. Otherwise, it should be possible to devise a triangle-shaped vibrato instead of this sine-shaped one... But I don't need to bother with that.

Note that it is possible to change the resolution of this virtual LFO by changing the listener's interval from 10000 microseconds to something else, but 14ms being the average temporal resolution of Human hearing, an interval slightly below that seemed sensible.

Now in my case, things might still get a bit more complicated because, as I forgot to mention, I'd like to be able to fade in the vibrato on each note. 
But at least this answers the question I asked in my original post.


----------



## EvilDragon (Jun 2, 2019)

Do note that the solution you did will glitch the vibrato out if you change the frequency while playing any notes. But if you just need a fixed frequency vibrato, it'll be fine


----------



## Fredeke (Jun 2, 2019)

EvilDragon said:


> Do note that the solution you did will glitch the vibrato out if you change the frequency while playing any notes. But if you just need a fixed frequency vibrato, it'll be fine


Indeed! I hadn't thought of that. Well, it's only a minor inconvenience for the instrument I am working on, but if you see a better method, I'm open to suggestions.
As I see it, I could store a "phase checkpoint" every time the frequency is changed, and count time only from the last checkpoint, but that might be unnecessarily complicated...
(I am surprised I needed to do this in KSP in the first place)

Btw, do you think parsing all current note events and working on each individually (in order to introduce vibrato delay/fadein) would significantly affect performances ?


----------



## EvilDragon (Jun 2, 2019)

Yes, you should go with a phasor instead, because phase increment is what really matters.

https://vi-control.net/community/threads/syncing-cos-x-period-to-tempo.74035/#post-4301628


----------



## Fredeke (Jun 2, 2019)

EvilDragon said:


> Yes, you should go with a phasor instead, because phase increment is what really matters.
> 
> https://vi-control.net/community/threads/syncing-cos-x-period-to-tempo.74035/#post-4301628


Waw, yes, of course! I'll try scripting something like that.

In fact, I think I'll just reset the timer in the listener (which in this case means store its value at the pseudo-reset time for substraction to the next reads).

It will be a quick and dirty trick, often resulting in a slightly distorted sine wave (because there's no reason the period should be an exact multiple of the listener's interval) - which wouldn't be suitable for audio generation, but is probably good enough for an LFO. Especially a low-amplitude one like a vibrato.

In fact, I can even restrict the vibrato period to multiples of the listener's interval (10ms here), et voila.


----------



## Fredeke (Jun 3, 2019)

Ok here it is, taking EvilDragon's remark into consideration :

```
on init
  declare ~lfo := 0.0
  declare $vibrato_period := 1000                 { expressed in listener's cycles }
  declare ui_slider $vibrato_rate (0,127)
  declare ui_slider $vibrato_cents (0,120)
  $vibrato_cents := 30
  set_listener ($NI_SIGNAL_TIMER_MS, 10000)
end on

on ui_control ($vibrato_rate)
  $vibrato_period := 5 + real_to_int (995.0 * pow ((129.0-int_to_real($vibrato_rate)) / 129.0 ,1.75) )
end on

on listener
  ~lfo := ~lfo + (2.0 * ~NI_MATH_PI / int_to_real ($vibrato_period))

  if (~lfo >= 2.0 * ~NI_MATH_PI)                  { could as well be "=" }
    ~lfo := ~lfo - 2.0 * ~NI_MATH_PI              { could as well be ":= 0" }
  end if

  change_tune ($ALL_EVENTS,  real_to_int (sin (~lfo) * 1000.0) * $vibrato_cents  ,0)
end on
```
Note: never mind the $vibrato_period formula. It's just something I tweaked empirically until it mapped a 0-127 slider to an LFO range going from 0.1Hz to 20Hz, with the most precision where it matters most, and each slider value yielding a different result.

Now there's one remaining problem: unlike the previous script, this one sets the tuning absolutely, instead of relatively. This means it can't be used in conjunction with another tune-altering script.

Normally I should have been able to keep it relative: each change would be cumulative, but since the derivative of a sine is just a cosine, which is the same wave form, the pitch would still oscillate around the base note (that's what happened in the previous script). However here, in some instances the pitch just went out of control, which I suppose is due to rounding errors...?

I'd appreciate any insight on the matter.


----------



## P.N. (Jun 3, 2019)

Fredeke said:


> Now there's one remaining problem: unlike the previous script, this one sets the tuning absolutely, instead of relatively. This means it can't be used in conjunction with another tune-altering script.



Maybe you can set_event_par(<id>, $EVENT_PAR_TUNE) in your first tune function, then use get_event_par(<id>, $EVENT_PAR_TUNE) to retrieve the previously set tuning.
That might allow you to do relative tune changes...
The tuning would need to be refreshed on the NCB, but i'm not 100% this will work or how much more tweaks it would need. 
Also, with $ALL_EVENTS set, the tuning would remain global. 
You'd need an an id array to affect each note independently, so your LFO would need to be set in a loop, in a function called by the LCB.
I didn't test any of this, so i'm honestly just brainstorming here.


----------



## Fredeke (Jun 3, 2019)

P.N. said:


> Maybe you can set_event_par(<id>, $EVENT_PAR_TUNE) in your first tune function, then use get_event_par(<id>, $EVENT_PAR_TUNE) to retrieve the previously set tuning.
> That might allow you to do relative tune changes...
> The tuning would need to be refreshed on the NCB, but i'm not 100% this will work or how much more tweaks it would need.
> Also, with $ALL_EVENTS set, the tuning would remain global.
> ...



Yes I ended up doing something similar. But there's no need to read values from the engine - the listener just has to remember what it did just before:

```
on init
  declare ~lfo := 0.0
  declare $buffer
  declare $detune
  declare $vibrato_period := 1000                  { expressed in listener's cycles }
  declare ui_slider $vibrato_rate (0,127)
  declare ui_slider $vibrato_cents (0,120)
  $vibrato_cents := 30
  set_listener ($NI_SIGNAL_TIMER_MS, 10000)
end on

on ui_control ($vibrato_rate)
  $vibrato_period := 5 + real_to_int (995.0 * pow ((129.0-int_to_real($vibrato_rate)) / 129.0 ,1.75) )
end on

on listener
  ~lfo := ~lfo + (2.0 * ~NI_MATH_PI / int_to_real ($vibrato_period))
  if (~lfo >= 2.0 * ~NI_MATH_PI)                  { could as well be "=" }
    ~lfo := ~lfo - 2.0 * ~NI_MATH_PI              { could as well be ":= 0.0" }
  end if

  $buffer := $detune
  $detune := real_to_int (sin (~lfo) * 1000.0) * $vibrato_cents

  change_tune ($ALL_EVENTS,  $detune - $buffer  , 1)
end on
```
in which the difference between the listener's current and previous calculation is set as relative tuning, instead of just using its current one as an absolute.

It works.

But look at the difference in length with my very first script ! (And I haven't integrated the per-note vibrato delay yet  )


----------



## EvilDragon (Jun 3, 2019)

Just because a script is longer doesn't mean it's horribly slower

But yeah you'd probably just want to reset to 0.0 explicitly rather than doing any extra math operations.


----------



## Fredeke (Jun 3, 2019)

Btw, now I'm trying to script the per-note vibrato fade-in.

I though all I had to do is parse all active note events (instead of addressing $ALL_EVENTS) and apply vibrato to each proportionally to how long ago it was triggered. But it turns out there's no easy way of parsing all currently playing note events... Or is it? (It would be nice of it was as easy as it is to parse groups, but apparently it's not.)

How would _you_ proceed?


----------



## EvilDragon (Jun 3, 2019)

I wouldn't, doing this per note in listener callback sounds like a major pain to me...

You can't have a per-note fade in on a global LFO like this. Or you can have it on first sounding note coming in from silence (that's how it would work on synths).


----------



## Fredeke (Jun 3, 2019)

EvilDragon said:


> I wouldn't, doing this per note in listener callback sounds like a major pain to me...
> 
> You can't have a per-note fade in on a global LFO like this. Or you can have it on first sounding note coming in from silence (that's how it would work on synths).



Mmmh... You make an interesting point. Are there analog synths (because with the _appropriate_ digital engine, everything's possible) which do LFO fade-in polyphonically?

The Oberheim Xpander, to name one, but the modulators are all digital so it doesn't count. Yet I would have liked to do that here. Bummer.

I can of course make it so that the fade-in feature is only available in mono mode... Or starting from the first note of a polyphonic group, as you suggest... Ok, half-bummer.


----------



## EvilDragon (Jun 3, 2019)

Yes sure, there are synths with polyphonic LFOs. But you're not supposed to do polyphonic LFOs in a monophonic callback. Sounds like a lot of pain


----------



## Fredeke (Jun 3, 2019)

EvilDragon said:


> Yes sure, there are synths with polyphonic LFOs. But you're not supposed to do polyphonic LFOs in a monophonic callback. Sounds like a lot of pain


The idea is that the LFO would be global, but its intensity would be polyphonic. But I get your point.

What do you think would happen if I applied the vibrato in an infinite while loop in the note callback ? Ah, let me guess: Kontakt would break the loop after a few thousand iterations, as a safety measure, right?

Ok, (hopefully) last question: is there a way to know whether a note is the first one to play after a period of silence?


----------



## EvilDragon (Jun 3, 2019)

Well no. KSP jumps out of loops without increments but which are supposed to increment after that many iterations. If you're doing stuff like while NOTE_HELD = 1 or while event_status(EVENT_ID) # EVENT_STATUS_INACTIVE, it should work.



Fredeke said:


> is there a way to know whether a note is the first one to play after a period of silence?



Sure, you should track how many keys were pressed and do stuff only when key pressed count is < 1. Maybe also take PLAYED_VOICES_INST into account.


----------



## Fredeke (Jun 3, 2019)

Thanks. Now I've got enough matter to fuel more experiments...
I'll post my solution here later, in the spirit of sharing.


----------



## Fredeke (Jun 3, 2019)

Ok, I'm now calculating the vibrato's detune factor globally, but applying it polyphonically. I haven't programmed the fade-in yet, but that should be easy.


```
on init
  declare ~lfo := 0.0
  declare polyphonic $buffer
  declare $detune
  declare ~vibrato_period := 200.0                    { expressed in listener's cycles }
  declare ui_slider $vibrato_rate (0,127)
  declare ui_slider $vibrato_cents (0,120)
  set_listener ($NI_SIGNAL_TIMER_MS, 10000)
end on

on ui_control ($vibrato_rate)
  ~vibrato_period := 5.0 + 295.0 * pow ((127.0-int_to_real($vibrato_rate)) / 127.0, 4.0)
  end on

on listener
  ~lfo := ~lfo + 2.0 * ~NI_MATH_PI / ~vibrato_period
  if (~lfo >= 2.0 * ~NI_MATH_PI)
    ~lfo := ~lfo - 2.0 * ~NI_MATH_PI
  end if

  $detune := real_to_int (sin (~lfo) * 1000.0) * $vibrato_cents
end on

on note
  while (event_status ($EVENT_ID) # $EVENT_STATUS_INACTIVE)
    change_tune ($EVENT_ID,  $detune - $buffer  , 1)
    $buffer := $detune
    wait (10000)
  end while
end on
```

It sounds like what I want, and the tune interval between notes even remains constant. Swell!

However, there's a major problem: it would seem the event status never becomes inactive. (According to another test, it remains equal to $EVENT_STATUS_NOTE_QUEUE indefinitely.) So after many notes, the system chokes up and can't generate audio in real time anymore. [UPDATE: as explained in the followings posts, there might be a bug in my particular version of Kontakt. Tests in other versions show that this script works well in fact.]

I noticed this problem because at first I erroneously wrote _wait (10)_ instead of _wait (10000)_, which made the choke occur after only a few dozen notes 

Replacing the while loop's condition with _while ($NOTE_HELD = 1)_ avoids that problem, but then the vibrato doesn't continue during the volume envelope's release phase, if there's one. So it's not a very satisfying alternative.

I suppose I could fade the vibrato out at a chosen rate after note off, but I wonder: What could be the reason(s) why every note remains active forever (and I actually mean forever)?

I greatly appreciate the help, btw.

(And btw2, I realize this method will limit the overall polyphony quite much, as it requires much processing... But it does sound good )


----------



## EvilDragon (Jun 3, 2019)

Hmmm doesn't make sense that an event would never be set as inactive... This simple test shows that it's working:


```
on init
   declare $ID
   set_listener($NI_SIGNAL_TIMER_MS, 1000)
end on

on note
   $ID := $EVENT_ID
end on

on listener
   message(event_status($ID))
end on
```


I suppose it might depend if there is some note processing happening in other script slots, hmmm... But still, weird. I'm trying it with play_note() in preceeding script slot, it's working fine, regardless of play_note() mode (-1, 0 or timed length)... Hmmmm!


----------



## Fredeke (Jun 3, 2019)

I've just implemented your status debugger and... Even with all other scripts bypassed, the status remains active forever.

I suppose there's something in my groups that makes this happen, but I can't see what. I have hundred of groups, but they're all pretty basic.

Normally, the status should toggle to inactive when either all envelopes have been played out, or all samples have been played to their end - whatever happens first, right? (and my samples have no loop)

[UPDATE: I've just used your script in a newly created instrument with only one group and one single-shot sample, and the status still remains active forever, even though the voice count quickly gets back from one to zero. My Kontakt version is 5.7.0]

[UPDATE: I just tried it in an old Kontakt version 5.2 I still keep installed for debugging purposes, and there it works as it should. So the problem may be that I've got a buggy build of 5.7 ]

[One more UPDATE: Now when I load the test nki I generated in 5.2 into 5.7, it works all right. Weird... Anyway the conclusion of the story is: the posted script does in fact work.)


----------



## EvilDragon (Jun 3, 2019)

I'm trying this in 5.8.1 and 6. Works fine in both.

Also tried in 5.7.3 and it works as well. In a minimal instrument, too.


----------



## Fredeke (Jun 3, 2019)

Ok, let's leave it at that. I'll update, that's all there is to it.

Thanks for the help !!!


----------



## P.N. (Jun 3, 2019)

Alright. Sorry, i may have lost focus on the issues, but...


```
on note
  while (event_status ($EVENT_ID) # $EVENT_STATUS_INACTIVE)
    change_tune ($EVENT_ID,  $detune - $buffer  , 1)
    $buffer := $detune
    wait (10000)
  end while
end on
```

...has a bug?

And _($NOTE_HELD = 1) _will break the release stage, which is expected.

So, maybe set a flag on the note callback and copy the $EVENT_ID to a new variable.

Then use the listener to set the tuning.(after the lfo calculations)

```
if (event_status ($EVENT_ID) # $EVENT_STATUS_INACTIVE)
    change_tune ($ID,  $detune - $buffer  , 1)
    $buffer := $detune
end if
end on
```

EDIT: Sorry, missed the previous posts. Please ignore as there's probably nothing to gain from this.
Cheers


----------



## Fredeke (Jun 3, 2019)

P.N. said:


> Alright. Sorry, i may have lost focus on the issues, but...
> 
> 
> ```
> ...



Yes, something like that should work.

However, I'll do a variant: in the note callback, I'll put a second loop after the _while($NOTE_HELD=1)_ loop, which will fade out the LFO for a preset amount of time, before breaking into oblivion. That way, I'll have vibrato fade in _and_ fade out . It wasn't really in the plans, and it's probably unnecessary as a workaround since the bug is only anecdotic, but it will actually be an improvement, so... Cheers!


----------



## P.N. (Jun 3, 2019)

Fredeke said:


> in the note callback, I'll put a second loop after the _while($NOTE_HELD=1)_ loop, which will fade out the LFO for a preset amount of time, before breaking into oblivion.



I see. Any reason why you can't do this on RCB?
Having sequential loops on the NCB has sometimes resulted in unexpected results for me in the past.


----------



## Fredeke (Jun 3, 2019)

P.N. said:


> I see. Any reason why you can't do this on RCB?


I suppose I could, but doing everything in the same callback guarantees continuity... And, because of the bug I encountered earlier, I choose to monitor the release from within the note callback anyway.

And here it is. Finally! - Tadaa:


```
on init
  declare ~lfo := 0.0
  declare $detune
  declare ~vibrato_period := 50.0                     { expressed in listener's cycles (centiseconds) }
  declare ui_slider $vibrato_rate (0,127)
  declare ui_slider $vibrato_cents (0,120)
  set_listener ($NI_SIGNAL_TIMER_MS, 10000)
  declare $fade_in := 300                             { expressed in listener's cycles }
  declare $fade_out := 150                            { expressed in listener's cycles }
  declare ~fade_factor
  declare polyphonic $fade_counter
  declare polyphonic $detune_buffer
  declare polyphonic $note_detune
end on

on ui_control ($vibrato_rate)
  ~vibrato_period := 5.0 + 295.0 * pow ((127.0-int_to_real($vibrato_rate)) / 127.0, 4.0)
end on

on listener
  ~lfo := ~lfo + 2.0 * ~NI_MATH_PI / ~vibrato_period
  if (~lfo >= 2.0 * ~NI_MATH_PI)
    ~lfo := ~lfo - 2.0 * ~NI_MATH_PI
  end if
  $detune := real_to_int (sin (~lfo) * 1000.0) * $vibrato_cents
end on

on note
  while ($NOTE_HELD = 1)
    $detune_buffer := $note_detune
    ~fade_factor := int_to_real ($fade_counter) / int_to_real ($fade_in)
    $note_detune := real_to_int (int_to_real ($detune) * ~fade_factor)
    change_tune ($EVENT_ID,  $note_detune - $detune_buffer , 1)
    wait (10000)
    if ($fade_counter < $fade_in)
      inc ($fade_counter)
    end if
  end while
  $fade_counter := $fade_out * $fade_counter / $fade_in
  while ($fade_counter >= 0)
    $detune_buffer := $note_detune
    ~fade_factor := int_to_real ($fade_counter) / int_to_real ($fade_out)
    $note_detune := real_to_int (int_to_real ($detune) * ~fade_factor)
    change_tune ($EVENT_ID,  $note_detune - $detune_buffer , 1)
    wait (10000)
    dec ($fade_counter)
  end while
end on
```
[EDIT: the variable ~fade_factor should really be polyphonic, but there are no such thing as polyphonic reals [[EDIT: See @Lindon 's solution below]]. It's ok though, because there's such a short time between write and read that the chances of it being overwritten by another note callback is negligible. But note that this variable is not even necessary: it was just meant to split a calculation over two lines of code, to make it more readable to Humans. You can just as well condense the calculation into one single line and get rid of ~fade_factor.]

Note that when I started this thread I had no intention of doing any of this in KSP . And, probably because I am a beginner, I am still amazed at what it can do !


----------



## geronimo (Jun 3, 2019)

While following this interesting subject, I will have added a label to view the LFO's rate but can n't. 


```
declare ui_label $rate_vibrato_label (1,1)
    set_text ($rate_vibrato_label,"")
    set_control_par (get_ui_id($rate_vibrato_label), $CONTROL_PAR_TEXT_ALIGNMENT, 1)
    set_control_par (get_ui_id($rate_vibrato_label), $CONTROL_PAR_FONT_TYPE, 24)
    set_control_par (get_ui_id($rate_vibrato_label), $CONTROL_PAR_WIDTH, 75)
    set_control_par (get_ui_id($rate_vibrato_label), $CONTROL_PAR_TEXTPOS_Y,2)
    set_text ($rate_vibrato_label, "LFO RATE")

on ui_control ($vibrato_rate)
  $vibrato_period := 5 + real_to_int (995.0 * pow ((129.0-int_to_real($vibrato_rate)) / 129.0 ,1.75) )
  set_text ($rate_vibrato_label, get_engine_par_disp ($ENGINE_PAR_INTMOD_FREQUENCY ,-1, 0, -1))
  wait (2500000)
  set_text ($rate_vibrato_label, "LFO RATE")

end on
```


----------



## EvilDragon (Jun 3, 2019)

Use find_mod() to point to the correct modulator. Read my sticky thread about modulation in KSP. 

Also not sure why you're using that vibrato_period math and then getting an engine parameter... do you even have an LFO modulator in the instrument, or?

If you want to use get_engine_par_disp() then you have to set the LFO frequency with set_engine_par(), obviously.


----------



## Fredeke (Jun 3, 2019)

geronimo said:


> While following this interesting subject, I will have added a label to view the LFO's rate but can n't.
> 
> 
> ```
> ...


Rate=1/Period
Of course here the period is expressed in listener's cycles, which are 0.01s appart. So to get the rate in seconds, do:
Rate=100/Period (I think)

Btw, you may want to use the last version of the script I posted: The period is now a real number, which makes it both more precise and more flexible (@EvilDragon : that's why I kept resetting the phase counter with -2PI rather than =0. Because now the period is not necessarily a multiple of the listener's interval anymore)

And I realise we could slightly simplify the math in the listener by declaring _~TAU := 2.0 * ~NI_MATH_PI_ once and for all, in the ICB.

Btw2, does anyone know a way to convert a real number to a string - or any other way to display a real number in a label? 
Just _set_text($anywhere,~whatever)_ seems to display gibberish. (Or is it a problem with my version again?)


----------



## Lindon (Jun 4, 2019)

Well you seem not to need this: " the variable ~fade_factor should really be polyphonic, but there are no such thing as polyphonic reals." from your explanation, but as you are in the note call back when you use ~fade_factor...

you could do this on those occasions where you do need it:

?fade_factor[$EVENT_NOTE]

to effectively get polyphonic versions of reals.


----------



## Fredeke (Jun 4, 2019)

Lindon said:


> Well you seem not to need this: " the variable ~fade_factor should really be polyphonic, but there are no such thing as polyphonic reals." from your explanation, but as you are in the note call back when you use ~fade_factor...
> 
> you could do this on those occasions where you do need it:
> 
> ...


Indeed! Thanks.


----------



## geronimo (Jun 4, 2019)

EvilDragon said:


> Use find_mod() to point to the correct modulator. Read my sticky thread about modulation in KSP.
> 
> Also not sure why you're using that vibrato_period math and then getting an engine parameter... do you even have an LFO modulator in the instrument, or?
> 
> If you want to use get_engine_par_disp() then you have to set the LFO frequency with set_engine_par(), obviously.



OK, if I understand fine, your pedagogic thread available for internal modulators already assigned: because currently it is a purely "KSP" modulation.
I tried adding but this is valid for a modulation already declared, open internally in the instrument.



```
declare $lfo_idx
$lfo_idx := find_mod(1,"LFO_SINE_VOLUME")

on ui_control ($vibrato_rate)
  $vibrato_period := 5 + real_to_int (995.0 * pow ((129.0-int_to_real($vibrato_rate)) / 129.0 ,1.75) )
  set_text ($rate_vibrato_label, get_engine_par_disp ($ENGINE_PAR_INTMOD_FREQUENCY ,0, $lfo_idx, -1))
  wait (2500000)
  set_text ($rate_vibrato_label, "LFO RATE")
```

I will re-read your answer because don't understand everything.


----------



## EvilDragon (Jun 4, 2019)

If you're using your own scripted modulator, then it makes no sense to use get_engine_par_disp() at all?


----------



## Fredeke (Feb 26, 2020)

Lately, @thecld asked me for some clarification about the code I posted in this thread, in a private conversation. Since my answer was enxtensive and -dare i say- potentially useful to others, I'm posting both it and his leading question here, with his permission.

All the following is a "condensing" of my earlier posts in this thread, and the code it refers to is the one from this post: https://vi-control.net/community/th...ut-of-phase-solved-via-ksp.82645/post-4396351



thecld said:


> Hi Fredeke,
> 
> I'm trying to learn / understand how to build a scripted LFO - I've come across your thread about LFOs being out of sync and I'm trying to understand how do you created that formula for phase increment (I believe that that's the part that I should be interested in if I want to avoid glitches when changing the LFO rate?):
> 
> ...



Hi

You understand the principle well: add the period to a counter, and derive pitch changes from a sine function of that counter (or a triangle function, or whatever you want your LFO's shape to be).
Also, set your counter back -2PI every time it gets greater than 2PI, just to avoid its value getting too large for KSP to handle over time.

But all that is in the _listener_ callback, not here.

This is the _ui_control_ callback. It's really a sophistication you don't need: It's not part of the LFO engine, it's just my overcomplicated way of controlling the LFO's speed.

Apparently my LFO engine needs a period rather than a rate - I can't remember why. So if you want to adapt it, I suggest you start with a simple "period" ui control and no ui_control callback.

So, all I'll be explaining from now on is dispensable, and can be implemented later if at all.
I'll explain it anyway, but only because I like the sound of my own voice.

Me, I wanted to provide users with a "rate" knob instead of a "period" knob (to emulate vintage synthesizers).

Remember: Period = 1 / Rate

So if you want to provide a "rate" control instead of a "period" control, you could just do that. Just make sure your "rate" knob can't reach 0, because you can't divide by 0.

Here's why, in my case, the formula became much more complicated:

In my instrument, all controls used 0-to-127 values to mimic MIDI. They didn't necessarily have to, but I had decided that early on in development, and it was too late to change it. So the whole formula you mention is just meant to translate a 0-to-127 _rate_ into a time unit based _period_.

At this point, you can probably stop reading. But I'll explain the formula anyway:

Let's read that line from $vibrato_rate (the knob) outwards to $vibrato_period (the value needed in the actual LFO engine which is in the listener callback):

- The knob has values from 0 to 127, so (127-$vibrato_rate) would invert it, and I guess I wanted it inverted. I suppose that constant 127 became 128 because I wanted to avoid a zero result and then it became 129 just because I liked the feel of that response better.

- Then there's that division by 129 which is meant to get the knob's position in form of a real number between 0 and 1, instead of an integer from 0 to 127, just for my sanity's sake.

- Then I though the knob's response was too sensitive to my taste at one end of its course, and not precise enough at the other end, so I applied a squared (= power of 2) response to it. But then it felt overcorrected so I settled on a power of 1.75. (The neat thing is that when you elevate a number comprised between 0 and 1 to any power, the result is still comprised between 0 and 1)

- Then I needed a value in seconds, or milliseconds, or microseconds - I can't remember which - for the period. Anyway, it required multiplying that 0<x<1 number by 1000. I split 1000 into 995 and 5, because I needed a tiny offset (5), because really, a period of 0 ms would make no sense and have no use. Of course, a multiplier of 995 is not 1000 (and I suppose it could just as well have been 1000), but I'm guessing either it made no difference in practice, or maybe I even liked it better that way.

Again, I arrived at some of those numbers by trial and error.
I hope this helps.


----------

