What's new

Delay Onset of Groups

polypx

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:

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

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

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

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

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'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
 
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.
 
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
 
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!!

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