# Delay Onset of Groups



## polypx (May 15, 2012)

Hi Guys,

Trying to solve a problem in which I want to delay various groups by different amounts.

Due to the serial nature of the on_note callback, if I do this:


```
on init
message("")
make_perfview
declare ui_knob $Delay1 (1, 1000, 1)
declare ui_knob $Delay2 (1, 1000, 1)
make_persistent ($Delay1)
make_persistent ($Delay2)
set_knob_unit ($Delay1,$KNOB_UNIT_MS)
set_knob_unit ($Delay2,$KNOB_UNIT_MS)
end on

on note
ignore_event($EVENT_ID)
disallow_group($ALL_GROUPS)
allow_group(0)
wait ($Delay1 * 1000)
play_note($EVENT_NOTE,$EVENT_VELOCITY,0,1000)
disallow_group($ALL_GROUPS)
allow_group(1)
wait ($Delay2 * 1000)
play_note($EVENT_NOTE,$EVENT_VELOCITY,0,1000)
disallow_group($ALL_GROUPS)
end on
```

I can successfully delay the second group, but the second group is always delayed by the wait used for the first group. ie. I can't delay the first group without delaying the second.

Can anyone think of a workaround that would allow me to delay groups independently of one another?

cheers
Dan


----------



## Big Bob (May 15, 2012)

Hi Dan,

I think _one_ way you could do it (not necessarily the best or easiest way) would be to sort the group indices in the order of their associated delay times using pair of arrays along with the sort command. Once the array indices are in time start order, you could enable the groups one at a time like your example, but for the 2nd and subsequent groups, use the time difference between each group. By adding time differences each time, the cumulative delay will be the actual desired delay, no?

If I can think of a better way, I'll let you know, but the foregoing at least should work.

Rejoice,

Bob


----------



## Mike Greene (May 15, 2012)

I'm guessing in your real version, you'll have more than two groups, in which case I think Bob's way is the way to go. But if it's just two groups, I'd use:


```
if ($Delay1 < $Delay2)
    wait ($Delay1 * 1000) 
    play_note($EVENT_NOTE,$EVENT_VELOCITY,0,1000) 
    disallow_group($ALL_GROUPS) 
    allow_group(1) 
    wait (($Delay2 - $Delay1) * 1000) 
    play_note($EVENT_NOTE,$EVENT_VELOCITY,0,1000) 
    disallow_group($ALL_GROUPS) 
    allow_group(2) 
    else
        wait ($Delay2 * 1000) 
        play_note($EVENT_NOTE,$EVENT_VELOCITY,0,1000) 
        disallow_group($ALL_GROUPS) 
        allow_group(2) 
        wait (($Delay1 - $Delay2) * 1000) 
        play_note($EVENT_NOTE,$EVENT_VELOCITY,0,1000) 
        disallow_group($ALL_GROUPS) 
        allow_group(1)
end if
```

Hmmm, that looks a lot clumsier than it did in my head, but I already typed it, so I'm posting it anyway! :mrgreen:


----------



## polypx (May 15, 2012)

Thanks Mike and Bob!

It's slightly confusing, but I think I might be able to sort it, as you suggest Bob.

Mike, you're right I'm intending it for use with several channels... btw, I believe you've implemented using Bob's idea, but with an array of just 2 elements.

cheers!
Dan


----------



## Big Bob (May 15, 2012)

> It's slightly confusing, but I think I might be able to sort it, as you suggest Bob.



Yes, it may be a little tricky to code efficiently but I'm sure it can be done. I have to dash out to run some errands right now so I'll be gone for a couple of hours.

However, when I get back I could try to concoct something for you if you want. If you want me to do that, just leave a post. I'll check when I get back.

Rejoice,

Bob


----------



## polypx (May 15, 2012)

Thanks Bob. I do understand what I need to do to sort the groups by delay time, so don't worry about that. (It's just that I'm already sorting arrays for lots of other things with respect to each group, so it's going to get very multi-dimensional.)

If you happen to think of a different way though, let me know.

cheers
Dan


----------



## Big Bob (May 15, 2012)

Hi Dan,

I just got back but now Rosie is calling me for lunch. However, while I was waiting for her to finish up some shopping, I thought of another way you might be able to do this so, after lunch, I'll flesh it out a little and then drop a post. If this idea works out, you might like it better than the cumulative time method.

Rejoice,

Bob


----------



## Big Bob (May 15, 2012)

Hi Dan,

I'm back from lunch. I think that something like this might work.

*on init* 
``message("") 
``make_perfview 
``*declare* ui_knob Delay0 (1, 1000, 1) 
``*declare* ui_knob Delay1 (1, 1000, 1) 
``*declare* ui_knob Delay2 (1, 1000, 1) 
``*declare* ui_knob Delay3 (1, 1000, 1) 
``*declare* ui_knob Delay4 (1, 1000, 1) 
``*declare* ui_knob Delay5 (1, 1000, 1) 
``*declare* kid
``kid := get_ui_id(Delay0)

``*declare* *const* GMAX := 5``
``*declare* in_note
``*declare* in_vel
``*declare* n
``*declare* tid
``message('')
*end on* 

*on note* 
``ignore_event(EVENT_ID)
``in_note := EVENT_NOTE 
``in_vel := EVENT_VELOCITY
``*for* n := 0 *to* GMAX 
````tid := play_note(n,1,0,1) 
````ignore_event(tid)
``*end for*
*end on*

*on release*
``*if* in_range(EVENT_NOTE,0,GMAX)
````disallow_group(ALL_GROUPS)
````allow_group(EVENT_NOTE)
````wait(get_control_par(kid+EVENT_NOTE,CONTROL_PAR_VALUE)*1000)
````play_note(in_note,in_vel,0,1000)
``*end if*
*end on*



The idea is to trigger a set of GMAX+1 release callbacks each tagged in some way to associate the callback instance with one of the Delay knobs. For the above example, I just used the MIDI notes from 0 to 5 but you could get by with just one (unused note) if you want by 'tagging' the events with an index from 0 to 5. 

I also just assumed for now that the groups you want to enable are one to one related with the knobs, i.e. Delay0 controls Group 0, etc. If the groups are scattered, then of course you will need another mapping array. I also assumed that the Delay knobs will be declared contiguously so their ids will be consecutive. If not, you will need to put the knob ids into an array.

In any case, when the release callback is triggered and you can determine that it is one of your special triggers, then you decode or extract the index and use it to address the appropriate delay knob and read the desired delay. With this scheme, you don't have to use differential time because Kontakt's multi-tasking system will see that each thread has the correct total time (at least approximately).

I didn't include any persistence, etc because I didn't want to obscure the main idea. 

I also didn't attempt to provide for any special note off situations or the case when multiple notes can arrive at the NCB before the last RCB times out, etc. Since your example just generated notes of 1 ms each, I did the same. However, if you want the delayed notes to stop in some way synchronous with the incoming note stream, you will need to fancy this up a bit.

But, hopefully, what I have posted illustrates the general idea and you can take it from here. However, please let me know if I need to clarify something.

I think this might be a better approach than the differential time scheme because that gets pretty messy when two or more delay times are equal.

Rejoice,

Bob


----------



## polypx (May 15, 2012)

Most Genius Bob,

I have no idea how you come up with this stuff! What is Rosie feeding you? 

I'm guessing there's something special about the Release callback that makes this work? But for my current purposes that's no problem at all. I managed to move my various robin arrays to the release CB as well, and I can't believe it, everything works properly.

It's late here, so I'm not going to push my luck trying to add anything more tonight, but I'm saving this version, which has now been well commented, and will come back to it tomorrow.

All the best my friend. I hope you have an inspiring supper as well.

And thank you so much for your insight and generosity,

Dan


----------



## Big Bob (May 16, 2012)

Hi Dan,



> I'm guessing there's something special about the Release callback that makes this work?



The thing that's 'special' about the RCB is it's the one, multi-theaded callback that can be triggered from a different callback in the same script. While the pgs callback can be triggered from any other callback (including the ICB), it is not multi-threaded.

By playing a simple 'blip' note in the NCB, it will trigger the RCB when the blip note ends. We used to use this trick back in the K2 days to synthesize callable subroutines. But in your case, it can be used to effectively trigger umpteen individual delay periods prior to then generating umpteen play notes each enabling their associated groups.

Anyway, I'm glad it seems to be working out for you.

You have a lovely day my friend,

Bob


----------



## Mike Greene (May 16, 2012)

This is insanely clever. (At least to me, it is.) First, getting my head wrapped around the concept of using RCB callbacks took me some time. But then, using the new event notes as identifiers for each of the delays is something I would never have thought of. I actually thought it was an error at first! _(How can the "note" be between 0 and 5???)_

It wasn't until I came back to this thread this morning that I finally grasped what's going on. My hat's off to you, Bob.

I love this forum. 8)


----------



## Big Bob (May 16, 2012)

> This is insanely clever.



Wow! That's quite a compliment (I think :lol: ). Dan says it's that stuff Rosie has been feeding me (maybe the FDA ought to put it on the controlled substance list).

Actually, using a short non-existent note to trigger the RCB is a trick that some of us have been using for some time now. But, apparently this is a new idea for some of you so maybe I should elaborate a little because this general technique can be very useful for many kinds of situations.

I just used notes 0 to 5 for the convenience of having the note numbers track with the target groups and/or Delay knob indices. That way I didn't have to 'tag' the notes.

Actually, you can use any single note that is guaranteed not to be included in the instrument's playable range (since you don't want it to sound). You can then 'tag' the note at the point where it is generated, passing an index in say ep3, so you can recognize it when it triggers the RCB. For example:


```
id := play_note(0,1,0,1)
set_event_par(id,EVENT_PAR_3,n)  { n is the tag index }
```

Then in the RCB you can use something like this:


```
on release
  if EVENT_NOTE = 0
    n := get_event_par(EVENT_ID,EVENT_PAR_3)  { retrieve the index }
    { now do whatever needs to be done for index n }
  end if
end on
```

If you 'tag' the note with a unique code in addition to the index tag, then you can even use a note within the instrument's playable range (just mute it when you generate it) by testing all note events in the RCB and separating out those that have your special tag.

We used to refer to this technique as 'pseudo calling' because we would often use it to execute a lengthy routine in more than one place without having to replicate the code body. Another use was when we needed to execute something like a allow_group command inside of a ui callback. Since, allow_group couldn't be used directly in a ui callback, we would use the ui callback to trigger the RCB with the pseudo-call trick and then execute the allow_group in the RCB where it is allowed.

Ah, those were the fun days when we had to try to make a silk purse out of a sows ear. :lol: 

Rejoice,

Bob


----------



## polypx (May 17, 2012)

I might re-examine this with EVENT_PARs, because it does seem a more "bullet-proof" way of dealing with the issue. But the simplicity of using notes 0-5 to sort groups was so elegant, I'm moving ahead with that at the moment. 

I do get a bit of release rubbish if I choose to transpose my keyboard down to the lower depths of MIDI. But within the context of my current application, that's not really going to come up.

What's really fabulous is discovering this release callback characteristic of multi threading... I know I've been hanging around here for years already, but I never grasped the relevance of that before.

"Insanely clever" indeed!

cheers,
Dan


----------



## UCAudio (May 21, 2012)

Big Bob @ Tue May 15 said:


> Hi Dan,
> 
> I think _one_ way you could do it (not necessarily the best or easiest way) would be to sort the group indices in the order of their associated delay times using pair of arrays along with the sort command. Once the array indices are in time start order, you could enable the groups one at a time like your example, but for the 2nd and subsequent groups, use the time difference between each group. By adding time differences each time, the cumulative delay will be the actual desired delay, no?
> 
> ...



I'd be curious to see an example of how you'd do it this way as well, or any other way that would work without using up notes.


----------



## Big Bob (May 21, 2012)

> I'd be curious to see an example of how you'd do it this way as well, or any other way that would work without using up notes.



I would need a more explicity definition of what you mean by 'using up notes' :roll: 

Are you refering to the blip note 0? Or for the series of notes generated after their initial delay? For the 2nd case, the series of notes are generated with either method. For the first case, what's the problem with generating a one microsecond blip note that goes nowhere?

The biggest problem with the incremental time, sorted approach that I see is the computational complexity. Why would you want to impose that rather than using a blip note?

Maybe I'm not understanding what your concern is?

Rejoice,

Bob


----------



## UCAudio (May 21, 2012)

Hey Bob, it's more likely me not understanding. I have instruments where all 128 notes contain zones with samples and I thought I'd have to delete one of those/free up a note in order to use the approach you outlined. I'll study your example a bit more and try to understand how this is working.


----------



## Big Bob (May 21, 2012)

In that case, you could use the 'full tag' suggestion I made in one of my prior posts in this thead 8) 



> If you 'tag' the note with a unique code in addition to the index tag, then you can even use a note within the instrument's playable range (just mute it when you generate it) by testing all note events in the RCB and separating out those that have your special tag.



Rejoice,

Bob


----------



## UCAudio (May 25, 2012)

Ok great, thank you.


----------



## Blackster (May 7, 2014)

Hi folks, 

I have a question for you since I'm trying to use a modified version of your explanation above. 

Before I'll post a script example, please let me explain in brief what I'm trying to achieve here. Bob's idea for the wait-statement is really great and it works fine. I'd like to add a random amount of velocity to the notes which are created in the ORC. I have different temp IDs in the ONC, so I get the same amount of triggers of the RC, right? Unfortunately, all notes created in the RC carry the same (randomized) velocity. I hope that you guys can help me out and point me to the thing I'm doing wrong here!

Please focus only on the velocity, everything else works as intended  .... 

Many thanks in advance!!


```
on note
    ignore_event($EVENT_ID)
    $note := $EVENT_NOTE
    $velocity := $EVENT_VELOCITY

    {creating IDs for group1}
    $i1 := 1
    while ($i1 <= $group_1_sounds)
        %temp_ID_group_1[$i1] := play_note(0,1,0,1)
        ignore_event(%temp_ID_group_1[$i1])
        inc($i1)
    end while
end on

on release
    select ($note)
        case 48 {this is group1}
            if ($group_1_sounds > 0)
                note_off($ALL_EVENTS)
                disallow_group($ALL_GROUPS)

                {randomize velocity}
                $temp_velocity := random($group_1_random_velocity*(-1),$group_1_random_velocity)
                $random_value_velocity := $velocity+$temp_velocity
                if ($random_value_velocity < 1)
                    $random_value_velocity := 1
                end if
                if ($random_value_velocity > 127)
                    $random_value_velocity := 127
                end if

                {randomize offset}
                $random_value_offset := random(0,$group_1_random_offset)
                wait(($random_value_offset*1000 {this is max. of 100ms} *3) + 1)
                $new_ID_group_1 := play_note(23+$group_1_rr_start+$rr, $random_value_velocity, 0, -1)

                {randomize pitch}
                $random_value_pitch := random($group_1_random_pitch*(-1),$group_1_random_pitch)
                change_tune($new_ID_group_1,$random_value_pitch*1000 {this is max. one half note} *7,1)

                {input for func_allow_groups}
                @group := "1_"
                $group_x_distance := $group_1_distance
                $new_ID_group_x := $new_ID_group_1
                call func_allow_group
                
                inc($rr)
                if ($rr = 60)
                    $rr := 0
                end if
            end if
    end select
end on
```


----------



## Big Bob (May 7, 2014)

Hi Blackster,

Something seems to be missing here. I realize you didn't post your full script but I do need to see the key fragments to understand what you are trying to do. I don't see any testing for the blip note ids in your RCB. How does the RCB know it was triggered by a blip note?

Moreover, your NCB seems to generate a series of blip notes regardless of what note number is received so why is note 48 special then in the RCB?

BTW: Please, at this late stage in the game, don't make up any new TLAs like ORC (I guess that means 'on release callback'?) :lol: 

To be continued ...

Rejoice,

Bob


----------



## Blackster (May 7, 2014)

Hi Bob, 

thanks for your fast reply. Ok, I won't make up any TLAs anymore  ... it's part of the language I'm used to speak with my developer partner, so it just came to my mind without any further thinking ... sorry  

I see your points, you are right. A short clarification:
Note number 48 is the only key which is playable in the instrument, therefore it is special. And yes, one hit on note 48 can create up to 20 artificial IDs (it's a percussion instrument with an ensemble function). Those individual IDs are necessary because I can't control the velocity of each one separately afterwards, right?

Before I waste too much of your time, I'll give it a shot with tagging each ID with a unique value and try to solve the problem this way. That was a good hint already 

I'll report back ASAP. Thanks again!


----------



## Blackster (May 8, 2014)

Ok, everything works fine now! I tagged each ID separately. In order to make the velocity issue bulletproof, I create the random velocity along with the blip ID in the NCB. This way I can read the velocity array easily in the RCB. 

Btw, to me it makes absolutely no sense that a command like change_tune() is allowed in the RCB but change_velo is not. Both are part of EVENT_ID so where is the difference? .... *weired*

Anyway, problem's solved!  thanks Bob!


----------



## Big Bob (May 8, 2014)

> Btw, to me it makes absolutely no sense that a command like change_tune() is allowed in the RCB but change_velo is not. Both are part of EVENT_ID so where is the difference? .... *weired*



Well, in a way it makes sense. Velocity can only be changed before the note starts to sound (or at least it will be ignored after that). In the NCB, you can change it because it hasn't sounded yet. In the RCB, such is not the case (at least not with the EVENT_ID note because it *has* already sounded).

Of course, if you are generating a new note with the play_note command, then you are in control of what velocity you specify when you issue the play_note so it probably seemed to NI not too important to be able to use change_velo in the RCB.

However, if you really would like to create a note in the RCB with play_note and then change the velo before the RCB exits (and the note sounds), are you aware that you *can do it* with:

set_event_par(id,EVENT_PAR_VELOCITY,velo)

This form of change_velo *is* allowed in the RCB.

Rejoice,

Bob


----------



## mk282 (May 8, 2014)

change_velo would definitely also make sense in RCB because there ARE keyboards that also transmit and respond release velocity. My Kurzweil PC3K8 is a good example of that.


----------



## Big Bob (May 8, 2014)

Hey Mario,

That's a good point, there is a MIDI note_off velocity (seldom used but I guess a few people have keyboards that make use of it) and, that would be one reason you might want to change velo for the current EVENT_ID note.

I presume however that one could then just use: 

set_event_par(EVENT_ID,EVENT_PAR_VELOCITY,velo)

instead of:

change_velo(EVENT_ID,velo)

I think this basically accomplishes the same thing doesn't it?
Or, is there some reason why this doesn't do the job either?


----------

