What's new

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

OP
Fredeke

Fredeke

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

Fredeke

Active Member
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.

Code:
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 :))
 
Last edited:

EvilDragon

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

Code:
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!
 
OP
Fredeke

Fredeke

Active Member
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 :sad:]

[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.)
 
Last edited:

EvilDragon

KSP Wizard
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.
 
OP
Fredeke

Fredeke

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

Thanks for the help !!!
 

P.N.

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

Code:
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)
Code:
  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
 
OP
Fredeke

Fredeke

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

Code:
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)
Code:
  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
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.

Senior Member
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.
 
OP
Fredeke

Fredeke

Active Member
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:

Code:
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 during that time 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 :grin:. And, probably because I am a beginner, I am still amazed at what it can do !
 
Last edited:

geronimo

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

Code:
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

KSP Wizard
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. :)
 
OP
Fredeke

Fredeke

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

Code:
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
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?)
 
Last edited:

Lindon

VST/AU Developer
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.
 
OP
Fredeke

Fredeke

Active Member
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.
Indeed! Thanks.
 

geronimo

Active Member
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.


Code:
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. :sad:
 
Top Bottom