# While loops - How much before they bog down?



## Mike Greene (Dec 3, 2013)

I am writing a script that will do scripted legato. It involves a while loop where I incrementally change tunings and volumes of the old note and new note every 500 microseconds (half a millisecond.) That part is easy, and I assume isn't very taxing on the CPU.

I have a number of other features (besides legato) that I want to include that will need to run inside this same while loop, though. What complicates this further is that these other processes will start at variable times inside the while loop, so there will need to be a whole slew of compound "if" and "select" statements inside the while loop to determine which processes to do at any particular time.

It's going to get very, very complicated to write. I'm up for the challenge, but before I start this brain twister, I would like to be sure that I won't wind up with a script that Kontakt on an average computer can't handle. My guess is that at each 500 microsecond increment, there will be around 20 "if" statements (with arrays involved) for the script processor to process, along with various basic processing commands, depending on which if statements are true.

I've always assumed that our scripts are a fairly tiny part of the CPU demand, since other things like envelopes and reverbs and EQs must be much more demanding on the CPU each microsecond. (Or in my case, each 500 microseconds.) But I don't really know that for a fact.

Soooo . . . do you guys have a sense for whether a while loop as I've described will be a problem? For that matter, do you think 500 microseconds is overkill for a legato script?


----------



## mk282 (Dec 4, 2013)

While loops stop at 50000 iterations (I think that's internal hard-coded limit).

For your various processes inside this loop, use functions as much as possible, that'll help I think (because, somebody correct me if I'm wrong, when you call a function it gets executed in its own thread).

Also if some things are not bound to be necessary to be refreshed every 500 usec, have a counter there and act only upon certain time values. For example, let's say you need something to be done less often, say every 5 milliseconds, that is 10 * 500 usec, which means your counter has gone from 0 to 9, which means if you do 

if (counter mod 10 = 0)
do stuff
end if

you will do what is within that if clause only every 5 ms, not on each while loop increment. I mean sure, you will do the actual if-else testing, but that's not a big deal - the big deal might be the "do stuff" part.


----------



## kotori (Dec 4, 2013)

mk282 @ Wed Dec 04 said:


> when you call a function it gets executed in its own thread


I don't think that's correct.


----------



## kb123 (Dec 4, 2013)

I think its related to whether the function you call has a wait in it, if it has, then things can get horribly out of sync, without a wait, the script "should" run in sequence. 

I've done those types of things in quicker loops than that, so you should be ok.


----------



## mk282 (Dec 4, 2013)

kotori @ 4.12.2013 said:


> mk282 @ Wed Dec 04 said:
> 
> 
> > when you call a function it gets executed in its own thread
> ...



All good. I doubted myself as I typed that. :lol:


----------



## Mike Greene (Dec 4, 2013)

Thanks guys. This gives me the confidence to go ahead and start writing this thing.

Regarding functions playing in their own threads, that would really cool. But alas, I did a little test and sadly, the functions get called sequentially. Function 2 doesn't get called until _after_ all the "waits" in function 1 get completed. Too bad, because if I could get those waits to occur in parallel, this script would be a million times easier.


----------



## Big Bob (Dec 4, 2013)

Hey Mike,

I think this will depend on how you structure your code. If the functions are called sequentially with intervening waits then what you said will happen. However, if you arrange it so that the functions are called from a separate thread they will each run in their own thread. I don't think the function call/return mechanism has anything to do with the threading. It's actually waits (in conjunction with queued events) that determine when another thread will be created and run.

What callback does this beast have to run in?

Rejoice,

Bob


----------



## Mike Greene (Dec 4, 2013)

It happens in an "on note" callback. The foundation is a legato process sorta like this:


```
on note
    ignore ($EVENT_ID)
    if (legato situation)
        $newnote := play_note ($EVENT_NOTE, 100, 0, -1)
        change_tune ($newnote)  {so it starts at old note pitch} 
        $counter := 0
        while ($counter < 60)
            change_tune and change_vol ($oldnote)     {fade out}
            change_tune and change_vol ($newnote)    {fade in}
            wait (500)
        end while
    end if
    $oldnote := $newnote
end on
```

That part's easy enough. The complication comes in because I'm also including an "ensemble" option where I'm adding doubled and tripled voices (by borrowing adjacent samples), like this:


```
on note
    ignore ($EVENT_ID)
    if ("ensemble" situation)
        $newnote := play_note ($EVENT_NOTE, 100, 0, -1)
        wait ($delayknob1)
        $newdoublenote := play_note ($EVENT_NOTE + 1, 100, 0, -1)
        change_tune ($newdoublenote, -100000, 1)
        wait ($delayknob2)
        $newtriplenote := play_note ($EVENT_NOTE - 1, 100, 0, -1)
        change_tune ($newtriplenote, 100000, 1)
    end if
end on
```
So we have three notes playing the same pitch, to thicken the sound Again, easy enough. But . . .

Notice that there's a slight delay before the double, and then another slight delay before the triple. That's so that the ensemble will sound more human. (I don't want all three voices to start at exactly the same time.)

The problem is that when I combine this ensemble option with the legato situation, those wait statements in the double/triple section are going to overlap with the waits in the legato. It's a mess. Especially because there are still _more_ details I haven't mentioned yet that add a few other waits into the equation.

I can make it work, it's just that it will be a zillion "if" processes to handle all the timing possibilities. But . . . if you know a sneaky way to get those double and triple notes to be in their own independent threads, I'm all ears. 8)


----------



## Big Bob (Dec 5, 2013)

Hi Mike,

I think it should be possible to run each generated note in its own thread. I'm thinking you could use the RCB triggering trick (the note 0 thing) in the NCB and then do the while loop(s) in the RCB instead of in the NCB.

This is just off the top of my head and maybe I'm overlooking something but, if I get a little time later, I'll try to sketch something out that you can throw darts at :lol: 

Rejoice,

Bob


----------



## Mike Greene (Dec 5, 2013)

I completely forgot about the RCB triggering trick! By golly, I think that just might work. 

I've read about this trick before, but never actually tried it. I'm guessing that I'd handle the "Ensemble" section (where the double and triple notes get created) in the on note callback, and then the legato part in the release callback.

I can probably muddle through this, but if you do get a chance to do a basic sketch, that would be really helpful. Thanks, Bob! 8)


----------



## syashdown (Dec 5, 2013)

I got no clue what the hell any of you guys are talking about. Good luck!


----------



## Big Bob (Dec 5, 2013)

Hi Mike,

What I think might work would be to use the NCB to start 3 RCB threads for each new played note. By suitably tagging these blip notes, the RCB can easily distinguish between when the played key is released and when a blip-note thread is started.

Thus, everything would actually run in the RCB where Waits can effectively run concurrently to avoid some of the timing issues you have cited.

I'm sorry but I've been a little bogged down today so far. But be patient and I will sketch something out for you and post it. I think this might be kind of a fun thing to cook up :lol: 

Rejoice,

Bob

Hi syashdown:

The RCB (release callback) can be triggered from the NCB (note callback) by the simple expedient of generating a very short blip note (usually note 0 for 1 microsecond). When that (hopefully silent) note ends, the RCB is triggered. If the RCB contains wait statements, it can be re-entered during those waits and thus several threads can execute in the RCB 'concurrently'. Stay tuned until I can post a sketch and maybe the idea will become clearer. :roll:


----------



## Mike Greene (Dec 5, 2013)

I appreciate that you're doing this, so no hurry at all, Bob. I'm looking (patiently) forward to it!  

In a semi-related question, what information about a note gets carried over from the note callback into the release callback? (Besides the note id and the user $EVENT_PAR_3 parameters.) Is the $EVENT_NOTE the only default thing about the note that still exists, or is the $EVENT_VELOCITY still there as well?


----------



## Big Bob (Dec 5, 2013)

Hi Mike,

The following code should illustrate the general idea I had in mind. id0 always contains the id for the current target note while id1 contains the id of the prior note. Each of these ids are implemented as a three-element array where index 0 is for the primary note, index 1 is for the double note, and index 2 is for the triple note.

For each played note, the NCB starts three RCB threads using tagged blip notes. The 'tag' code passed in ep 0, is 1 for the primary note, 2 for the double, and 3 for the triple. This allows usage of the default 0 value for ep 0 as a tag meaning 'not a blip note'. 

On entry to the RCB, the tag is extracted and mapped by subtracting one. Thus if the mapped tag is -1, it means the RCB was triggered by something other than a blip note. If the RCB was triggered by a blip note, the mapped tag will be either 0, 1, or 2. The RCB uses this mapped tag index to determine whether it is working on the primary, double, or triple note crossfade.

Note that for the double and triple threads, an onset delay can be provided. Also note that instead of a simple 'wait' statement, you will need to use something like the pause function. The reason for this is that whenever a 'wait' expires, you have to determine what the corresponding tag was when the thread went to sleep.

I have not made any attempt to deal with secondary issues such as what happens when new note is played during an onset delay, etc. I'm sure you can work all that out and I didn't want to obscure the main idea.

The simple simon legato function is not intended to be useful, I just wanted to illustrate how a while loop can be made to run concurrently with onset delays, etc.

Please let me know if I need to further clarify anything.

Rejoice,

Bob 

*on init*
``message('')
``*declare* ui_knob Dly_1 (1000,1000000,1000)
``*declare* ui_knob Dly_2 (1000,1000000,1000)``
``*declare* last_played_id
``*declare* last_played_note
``*declare* last_played_vel
``*declare* id0[3]``_{ new target note id }_
``*declare* id1[3]``_{ prior note id }_
``*declare* k[3]````_{ loop counter for legato transition }_
``*declare* j
``*declare* tag`````_{ 0 = not a blip note, 1 = primary note, 2 = double, 3 = triple }_
``*declare* note_offset[3] := (0,1,-1)
*end* on

*on note*
``last_played_id := EVENT_ID
``last_played_note := EVENT_NOTE
``last_played_vel := EVENT_VELOCITY
``ignore_event(last_played_id)
``set_event_par(play_note(0,1,0,1),EVENT_PAR_0,1)``_{ tagged blip note for primary }_
``set_event_par(play_note(0,1,0,1),EVENT_PAR_0,2)``_{ tagged blip note for double }_
``set_event_par(play_note(0,1,0,1),EVENT_PAR_0,3)``_{ tagged blip note for triple }_
*end* on

*on release*
``*if* EVENT_ID = last_played_id``_{ end phrase }_
````*for* j := 0 *to* 2
``````note_off(id0[j])
``````note_off(id1[j])
``````id0[j] := 0
``````id1[j] := 0
````*end* *for*
``*else*``_{ may be a blip note }_
````tag := get_event_par(EVENT_ID,EVENT_PAR_0) - 1``_{ mapped tag, -1 means not a blip note }_
````*if* tag >= 0``````````_{ start another thread for primary, double, or triple if tag = 0, 1, or 2 }_
``````*select* tag
````````*case* 1
``````````pause(Dly_1)```_{ onset delay for double }_
````````*case* 2
``````````pause(Dly_2)```_{ onset delay for triple }_
``````*end select*
``````note_off(id1[tag])`````_{ kill oldest note }_
``````id1[tag] := id0[tag]```_{ update prior note }_
``````_{ generate new target note with id0 }_
``````id0[tag] := play_note(last_played_note+note_offset[tag],last_played_vel,0,0)
``````do_legato`````_{ make transition from id1 to id0 }_
````*end* *if*
``*end* *if*
*end* on

*function* do_legato _{ simple/stupid legato loop }_
``*if* id1[tag] # 0
````change_vol(id0[tag],-20000,1)````_{ start target note at -20db }_
````*for* k[tag] := 1 *to* 20````````````_{ crossfade from prior to target }_
``````change_vol(id0[tag],1000,1)}
``````change_vol(id1[tag],-1000,1)
``````pause(50000)
````*end* *for*
``*end* *if*`````
*end* *function*

*function* pause(time)``_{ 'wait' that restores mapped tag before continuing }_
``wait(time)
``tag := get_event_par(EVENT_ID,EVENT_PAR_0) - 1``
*end* *function*





> In a semi-related question, what information about a note gets carried over from the note callback into the release callback? (Besides the note id and the user $EVENT_PAR_3 parameters.) Is the $EVENT_NOTE the only default thing about the note that still exists, or is the $EVENT_VELOCITY still there as well?


I'm about to quit for the day Mike so I'll try to answer the above questions tomorrow morning, the Good Lord willing.


----------



## Mike Greene (Dec 5, 2013)

I had to study this for a while to completely understand it all, but now that I've got it, I gotta say, this is really slick! Thank you very much for doing this. This is going to be infinitely easier than my original plan. Night and day. :shock: 

Plus I'm also learning a few unexpected things from this. For instance, I would have put the _id1[tag] := id0[tag]_ line *after* the legato was finished, rather than before. I think I'll get fewer errors doing it your way.

I have a couple academic questions: 

First, if I made the "tag" variable into a _polyphonic_ variable, would that take away the need to use our pause function? In other words, would we then no longer need "tag" to get reset after each wait?

Similarly, in the legato function, if I made "k" a polyphonic variable, would that eliminate the need to use "k[tag]"? A trivial point, I know, but I'm wondering if there's some reasoning I'm not thinking of.

Again, thank you, Bob. This is incredibly helpful. 8)


----------



## Big Bob (Dec 6, 2013)

Hi Mike,



> In a semi-related question, what information about a note gets carried over from the note callback into the release callback? (Besides the note id and the user $EVENT_PAR_3 parameters.) Is the $EVENT_NOTE the only default thing about the note that still exists, or is the $EVENT_VELOCITY still there as well?



Essentially all note information is 'carried over' to the RCB. All the event variables like $EVENT_NOTE, $EVENT_VELOCITY, as well as all the event parameters, etc. However, they are all pinned to the note id found in $EVENT_ID. So, you have to be careful when writing RCB code to insure that you are in the right context.



> First, if I made the "tag" variable into a polyphonic variable, would that take away the need to use our pause function? In other words, would we then no longer need "tag" to get reset after each wait?
> 
> Similarly, in the legato function, if I made "k" a polyphonic variable, would that eliminate the need to use "k[tag]"? A trivial point, I know, but I'm wondering if there's some reasoning I'm not thinking of.



Yes, you can use polyphonic variables but again you have to be careful about context. For example, you can probably make 'tag' a polyvar but trying the same trick with K could be problematic depending on how you handle some of the secondary issues I mentioned.

The way the code is now, it is quite possible for there to be several threads with the same tag index. With K implemented as an array, there is only one K per thread type. But, if you make K a polyvar, there could be several values of K for each thread type.

Polyvars can be useful but you really have to 'be sure you know what you're doing' :lol: In fact it would be nice to make id0 and id1 polyvars but, one weakness of polyvars is that you can't reference them outside the context of their own thread. If you implement the 'end of phrase' in some manner similar to my example, you need to end active notes from an outside context.

In any case, keep in mind that I threw this example together rather hurriedly and I wanted to be sure to convey the main idea. If I get a little more 'fun time' later today, maybe I'll make another stab at it and if that results in anything useful, I'll post it.

Rejoice,

Bob


----------



## Mike Greene (Dec 6, 2013)

That all makes sense, Bob. I think I might make "tag" a polyvar, but I might leave "K" the way you have it.

Regarding taking another stab at this, your example may have been put together "rather hurriedly," but it looks pretty dog gone slick to me! 8) The legato function is obviously intentionally rudimentary, but that actually works for the best to keep it ultra-simple, since I obviously need to plug my own method into the legato function anyway.

So please don't feel you need to work more on this. My questions are already answered. Thank you!

Although . . . if you have more tricks up your sleeve, I'm always open to learning more. :mrgreen:


----------



## Big Bob (Dec 6, 2013)

> So please don't feel you need to work more on this. My questions are already answered. Thank you!



Ok my friend, I guess I'll quit while I'm ahead then :lol: 

Rejoice,

Bob


----------

