# Scripted Round Robin



## jdawg (Dec 2, 2011)

I've got the following script to work out a x2 round robin using an else to allow and disallow groups, I was wondering how I could make this work with a x3, x4, x5, xwhatever round robin. Since else only works with 2 options. 

Anythoughts?

~o) ~o) 
:mrgreen: :mrgreen: 


> on init
> 
> declare $Status
> 
> ...


----------



## jdawg (Dec 2, 2011)

UPDATE:
Im starting to think the best way for me would be to make it zone based instead of group based.

Sadly the thread explaining that on this forum is incomplete so anyhelp would be great guys. 

Basically something that acts as round robin using different zones (presuming velocity)

any help would be grand. 

:mrgreen: :mrgreen: :mrgreen: :mrgreen:


----------



## andreasOL (Dec 2, 2011)

...something like this  


```
on init
	declare $Status
	declare const $RoundRobinGroups := 5 { if you have 5 groups }
end on

on note
	disallow_group($ALL_GROUPS)
	allow_group(find_group("RR_" & ($Status + 1)))
	$Status := ($Status + 1) mod $RoundRobinGroups
end on
```

Cheers,
Andreas


----------



## jdawg (Dec 2, 2011)

Ohh nice one, 
is there a simple way I could edit that so it would work with one single group and be velocity / zone based instead?

But so that played velocity still actually controls dynamics

just round robin triggers a different zone on the same key??


----------



## andreasOL (Dec 2, 2011)

Hmmm...not sure if I understand you correctly.

You put samples with different dynamics along the vertical axis in the mapping editor and then the proper dynamics, i.e. the proper sample, is selected by the velocity you play with. Round robin is a different "axis", to move through otherwise exactly structured groups to provide...well...round robin, i.e. audible variations if everything else (here: velocity and pitch of the note) is kept constant.


----------



## jdawg (Dec 2, 2011)

Sorry I will try make myself clearer, 

I was thinking of having the round robin script act so that instead of cycling through groups, (like your wonderful script did)
It cycles through velocities of a single group.

FOR EXAMPLE
So I have a group called "Variations"
Inside that group at "D2" At velocity 0-25 I have Snare_RR1, then at velocity 26-50 I have Snare_RR2, then from velocity 51-75 I have Snare_RR3 and so on and so on. 

So there would need to be a line telling which set of velocity to cycle through, and of course I would switch off the relation to volume inside kontakt so the lower round robins are now too quiet. 

So instead of your script going from one group to another, it goes from one gap of velocity (example 0-25) to another (26-50).

I hope this makes sense and that you are able to share your wisdom. 

I just made these figures up as examples. 


:mrgreen: :mrgreen:


----------



## andreasOL (Dec 2, 2011)

...very quick and only to show the principle:


```
on init
   declare $Status
   declare const $RoundRobinZones := 5 { if you have 5 equally spaced samples per zone }

   declare const $RoundRobinZoneHeight := 128 / $RoundRobinZones
end on

on note
   ignore_event($EVENT_ID)
   play_note($EVENT_NOTE, $RoundRobinZoneHeight * $Status + $RoundRobinZoneHeight / 2,0,-1)
   $Status := ($Status + 1) mod $RoundRobinZones
end on
```

The term "+ $RoundRobinZoneHeight / 2" makes sure that a velocity in the middle of each zone is triggered.


----------



## jdawg (Dec 2, 2011)

where 2,0,-1) means?

Trying to get this to work for only specified groups. 


thanking you for all the help


----------



## mk282 (Dec 2, 2011)

Read the definition of play_note in the KSP reference 


play_note(<MIDI note>,<velocity>,<sample offset>,<duration>)


To get this work with specific groups, you would use disallow_group() and allow_group() respectively, before this play_note.


----------



## mpalenik (Dec 4, 2011)

I just wanted to point out that instead of using:


```
allow_group(find_group("RR_" & ($Status + 1)))
```

it will almost certainly run more efficiently if you do something like


```
allow_group($RRStart + $Status)
```
(assuming your RR groups are sequential)

where $RRStart is the number representing the first round robin group. It will be most efficient if you just count the groups yourself and replace $RRStart with the actual numerical value of the first round robin group, or you could do something like this when you initialize:


```
on init

 $RRStart = find_group("RR_1")

end on
```

Doing it the other way probably won't create a noticeable performance hit, but there's almost no way that this won't require fewer clock cycles.

The reason is that every time you execute find_group, Kontakt has to loop through your list of groups and compare the string that you enter to the group names until it finds a match. If you simply call allow_group(number) it can immediately access that array element by index, instead of having to find the matching index first.


----------



## andreasOL (Dec 5, 2011)

...wasn't at a computer over the weekend. The indexing hint versus using find_group is a good detail of what scripts are made of. There is always a certain amount of structure needed if one writes a script that can grow and still remain maintainable. My examples were purely to demonstrate isolated issues.

I myself would put the indices of the groups in an array in the ICB and use this array. So I would not rely on the groups being ordered.

Cheers,
Andreas


----------



## Mike Greene (Dec 5, 2011)

andreasOL @ Mon Dec 05 said:


> I myself would put the indices of the groups in an array in the ICB and use this array. So I would not rely on the groups being ordered.


Really! I've always wanted to do that, but I've had problems using find_group to put values into an array. I always get a syntax error. For example:


```
on init
    declare $ooo
    $ooo := find_group("Ooo")
    declare $ahh
    $ahh := find_group("Ahh")
    declare %grouparray[2]
    %grouparray[2] := ($ooo,$ahh)
end on
```

Even if I say _%grouparray[2] := (find_group("Ooo"),find_group("Ahh"))_, I will still get an error.

I'm using KSE on a Mac, by the way. Is it possible that this in fact allowed, but it's just that the Mac version of Nils' editor doesn't like it?


----------



## andreasOL (Dec 5, 2011)

Hello Mike!

You cannot auto-initialize an array with non constant values, i.e. only literals and "declare const" variables are allowed at this point.

But you can treat single array elements the same way as scalar variables, e.g. assign them like this:


```
on init
    declare %grouparray[2]
    %grouparray[0] := find_group("Ooo")
    %grouparray[1] := find_group("Aaa")
end on
```


----------



## mpalenik (Dec 5, 2011)

andreasOL @ Mon Dec 05 said:


> ...wasn't at a computer over the weekend. The indexing hint versus using find_group is a good detail of what scripts are made of. There is always a certain amount of structure needed if one writes a script that can grow and still remain maintainable. My examples were purely to demonstrate isolated issues.



I know, I just wanted to point out that that method was a little bit slower than it needed to be, just because not everyone might be familiar enough with programming to realize it. If you don't really understand arrays, it might not be obvious why referring to an element by index rather than searching by name is faster (and to be fair, Kontakt uses a scripting language and also there are no such concepts as pointers and memory addresses, so this further obfuscates the issue).


----------



## Mike Greene (Dec 5, 2011)

Thanks, Andreas! That had been bugging me for years (not being able to fill my array with group indices in the ICB,) but I never got around to asking about it before. This will be a big help. o-[][]-o


----------



## Big Bob (Dec 5, 2011)

Yes, indeed. The usual time-saving 'tricks of the trade' pretty much go out the window with the KSP. Every time I've conducted a series of tests to determine relative execution time for various optional ways of skinning the same cat, if there was any single rule that summarises the results it would be:

*KSP execution time seems more dependent on the lines of code that have to be interpreted than what the lines of code contain *:lol: 

Rejoice,

Bob


----------



## mpalenik (Dec 5, 2011)

blakerobinson @ Mon Dec 05 said:


> You'll run into the 'too much iterations, no wait statement' error before reaching any kind of performance difference from using find_group (try it with 10,000+ find_groups in a while loop compared to 10,000 :='s). Your code will be much, much, much better structured with find_group, especially for more complex instruments. You'll also be able to write scripts that can be used over multiple instruments without a specifically ordered set of groups.



I have no doubt that there will be no noticeable performance difference, but I'll bet if you actually put in a clock and measured the time, you'd see the difference. I mean, even in C, technically x++ or x+=1 is faster than x=x+1, but in practice it doesn't create a measurable performance hit.

I don't really see why find_group = better structure than using a number--either creating you're round robin groups sequentially (which is a nice structure for your instrument) or creating an array to hold the indexes of all the round robin groups. To some extent, whether or not you consider it "better structure" is a matter of preference. Personally, I like to write everything to run as efficiently as possible even if it doesn't really make a noticeable difference.


----------



## mpalenik (Dec 5, 2011)

blakerobinson @ Mon Dec 05 said:


> [
> Doing a lookup of 20,000 find_groups on an i5 the difference is around 20,000 - 25,000 microseconds. A more realistic limit test of 50 find_groups is a _~75_ microsecond difference.
> 
> Try working with larger instruments where you have multiple articulations inside one instrument, or you have 64+ groups to manage through a development process where they may change/move, or where you have a single script that powers ~40 odd patches and you'll see how find_group makes for a better structured code.


On init, just create an array that stores the indices of the groups that you find by name. You get a 1 time performance hit when the instrument is loaded, then a nicely packaged and labeled array that contains all of the indices you need.

Try writing a program to calculate zero field splitting from electronic structures that has thousands of lines of code spread over 20 or so source files and as many header files. 64+ groups isn't that much to manage in comparison. . . This find by name thing is a convenient option, but hardly necessary for well structured code.


----------



## mpalenik (Dec 5, 2011)

blakerobinson @ Mon Dec 05 said:


> Not sure how that applies to KSP or applying it in this context, but congrats on the accomplishment - sounds complicated.



I'm just saying that when I've written big programs in C, functions like find_by_name, or something to that effect, don't exist naturally and aren't usually worth it to write on your own. It's still possible to have really well structured code, though, by doing things a little bit differently. For example, by creating an array RRGroups that stores the indices of all the round robin groups (which you could find by name on initialization). Personally, it's just more comfortable to me doing it that way, and for some reason, the tiny performance hit bugs me, but maybe that's just because I mostly work in C when I need to do coding, and there are certain routines I work with where for some reason almost insignificant changes to the code end up adding up to a huge performance hit by the time all the loops are finished.


----------



## MozillaUser (Dec 5, 2011)

Please don't take my contribution here as "trying to show my cleverness" or such. I'm only trying to help the man. I feel his question was very clear, and nobody quite answered it. Maybe I understand his question better, because I dealt with it extensively in the past, and because I'm no professional programmer. It's a hobby for me. I'm a professional musician and I don't want to be anything else. So, I say it again: I'm just trying to help the guy, period.

His last question was:
"Is there a simple way I could edit that so it would work with one single group and be velocity / zone based instead? 
But so that played velocity still actually controls dynamics. 
Just round robin triggers a different zone on the same key??"

Now, because in the beginning of the thread he worded it a bit differently - he seemed to look for a way to relate the round-robin to more different groups (hence the "allow / disallow group/s" theme). This is a different problem, and I'll deal with it separately.

So, jdawg, please look at this simple script ( I'm writing it in a hurry, so I might make some typos or mistakes):

on init 
declare polyphonic $active_id { this is an ID, you will need it for the "play_note(,,,) statement } 
declare $myRRobin { this is the RRobin counter, you will increment it each time you play a note } 
{ if you want to use different & independent RRobins for each note on the keyboard, you have to 
set up a 128-note array, and "declare polyphonic $myRRobin"}
{ you said you want 4 RRobin zones, so let's initialize the "MyRRobin" to the center of the first zone } 
$myRRobin:= 16
declare polyphonic $virt_vel {"Virtual velocity" - this will store the real velocity of your incoming note, for further 
use (since you will ignore the real velocity when triggering the RRobin-related zone}

{ I guess we're done with it so far, so we close the "on init" callback: }
end on


{ Now look what happens in the "on note" callback: }

on note
ignore_event($EVENT_ID)
$myRRobin := $myRRobin + 32 { now, increment the RRobin - that is, go to the center of the next zone}
if ($myRRobin > 113) { if greater than the upper "zone center"...} 
$myRRobin := 16 { ...reset the RRobin to the first zone's center} 
end if

{we better deal with the virtual velocity now, to have it handy when it comes to use it}
$virt_vel = ($EVENT_VELOCITY - 127) * 500 { 500 is an arbitrary value, it will give you a reasonable velocity range} 

{now here's the main trick:}
$active_id := play_note($EVENT_NOTE,$myRRobin,0,-1) {as you see, we use $myRRobin instead of 
$EVENT_VEL, and so we trigger the right RR zone } 
{and immediately after:} 
change_vol ($active_id, $virt_vel,0) { this changes the volume, in respect to the original event velocity} 

{and that'd be all, so we close the callback:} 
end on

--------------------------------------------------------------------------------------------------------------------------

I hope this was not too difficult to understand and implement, jdawg  .

Now, as I said before, let's deal with the other problem: When to use this way of round-robining by means of misusing the velocity zones? and when to use the classical implementation of the round-robin, by switching the groups?
I think it's not too difficult to see this fact: you can use the "velocity" approach ONLY when the velocity has no further implications in the timbre of the sound. I see some cases where this approach is not very useful: 1. - when you have many velocity zones which contain different timbres ( you talked about snare, so I'll go along: your velocity zones contain, say, snare ppp, snare pp, snare mp, snare p, snare mf, snare f, snare ff, snare fffffffff etc. (let alone the case where you have velocity-crossfades, which took you ages to set up the way you intended) ... 2. - when velocity modulates a filter or whatever else, to change the sound in respect to the velocity ... or 3. when it would be more economical to use the other approach (whatever your reasons would be)

How to switch the groups?
I hate having to disallow all, having to allow one group (after calculating which one, which can be complicated...and here I'd have something to say: it's far more economic to do the "find_group" thinggie in the init callback, setting the variables once for ever, than to find_group in the note callback where the time is so critical) - having to do the play_note thing and having to allow_all afterwards. To me, it seems complicated, so I usually don't bother to use this approach. 
My approach is very simple and problemless: 
Say I have 4 groups which I want to round-robinize: "snare rr1", snare rr2", "snare rr3" and "snare...err...I've forgotten which number comes next - becoming old is not funny, as you see 
I set a start condition for all four: say, all 4 groups start if CC 93 (or whatever CC you won't ever use in your patch) is equal to a VALUE. This VALUE is unique for each of these groups; say, I set the group "snare rr1" to start if CC93 = 1, group "snare rr2" starts if CC93 =2, etc.
All this is a "set and forget" thing, You set this once for ever. You don't need to "find group", you don't need to "disallow, allow" etc, it doesn't matter where these groups stay in the group list. They will be found automaticaly.
All you need, in the note callback, is to:
- increment the $RRobin (from 1 to 4) - or randomize it (if you want a random cycle), or whatever you're pleased to;
- set_controller(93,$RRobin)
- wait (1) = one microsec
- do the play_note
- live happy forever.

I hope it helps.
Mozil


----------



## mk282 (Dec 6, 2011)

mpalenik @ 5.12.2011 said:


> blakerobinson @ Mon Dec 05 said:
> 
> 
> > Not sure how that applies to KSP or applying it in this context, but congrats on the accomplishment - sounds complicated.
> ...



Yeah, but KSP is not C. KSP is an interpreted language, and as such it is very likely heavily optimized. Dwelling over a ~75 microseconds difference is both insane and unnecessary.

All hail find_group()!


----------



## jdawg (Dec 7, 2011)

Mozzila, Visually your stuff seems to work (as in it looks as though its working) but it will only sound a single zone repeatedly. Very strange indeed. 

Any thoughts?


----------



## MozillaUser (Dec 7, 2011)

Yes, definitely. I told you I wrote it in a hurry. So I forgot to insert one line, in the note callback. Please, insert it right after the line "on note":

ignore_event($EVENT_ID)

Now test it in edit mode, having the group editor and the mapping editor both open.
Each time you hit a note, you should see in the mapping editor the red line, jumping between zones (at values 16, 48, 80, 112).
Sorry for this. I hope it helps.
If not, my Latin ends here.
Mozil
P.S. I edited it in the former script too.


----------

