# Timing weirdness with wait_ticks?



## Mike Greene (Sep 19, 2020)

I had no idea we had wait_ticks until Mario mentioned it in another thread a few weeks ago. I plugged it into my RealiDrums pattern player and now it follows host tempo changes much more smoothly. Very cool. However ...

For certain beats, the timing can be a bit "jerky." As the beat plays, I'll notice hits are often off by 50 ms or so. I would think this was because of something in my script methodology, but I checked and I can't see anything that could be causing this. (It's long with a million cases, but it's very straightforward.) More to the point, this jerkiness only occurred _after_ I switched to wait_ticks. In fact, if I switch between these two sections:

Regular wait version:

```
if (($Pattern_Tick_Position - $Previous_Note_Tick_Position) > 0)
    $Wait_Increment := ($Pattern_Tick_Position - $Previous_Note_Tick_Position) * $DURATION_SIXTEENTH / 240
    wait ($Wait_Increment)
end if
```

Wait_ticks version:

```
if (($Pattern_Tick_Position - $Previous_Note_Tick_Position) > 0)
    $Wait_Increment := ($Pattern_Tick_Position - $Previous_Note_Tick_Position)
    wait_ticks ($Wait_Increment)
end if
```

Switching between those two versions fixes/breaks it. Just those lines, leaving everything else as is, makes all the difference. (I've gone back and forth a dozen times to confirm.) Which is weird, because they're functionally identical, except I do the math with regular wait by multiplying by the microseconds to ticks factor ($DURATION_SIXTEENTH / 240). Kontakt 5.6.8 and 6.4.1 both give the same results.

Any ideas what this could be? On the bright side, I could just stick with regular wait, but wait_ticks is so slick, plus it's so much more stable with tempo changes. I'd hate to not use it.

Also, since I was testing the timing of pretty much every factor I could think of, I wanted to confirm that $NI_SIGNAL_TIMING_BEAT is triggering properly. I used $KSP_TIMER and spit out a message every beat for how much time transpired since the previous beat. AT 120 bpm, we would expect 500,000 microseconds per beat, but I was getting about 425,000 microseconds ... and every 15th beat (consistently 15), I would get around 510,000 microseconds. So every 15th beat has a 10ms lag. Weird.

This happens even when the beat is not playing, plus the timer message is the first line after _"case $NI_SIGNAL_TIMING_BEAT,"_ so it's not because of my script. (How dare anyone think it might be! ) Not a huge deal, at least not at the moment, since the timing jerkiness I described is more than the 10ms difference (plus it's more than just once every 15 beats), but weird nonetheless. This happens on Kontakt 5.6.8 as well as Kontakt 6.4.1, both on a Mac.


----------



## EvilDragon (Sep 20, 2020)

Mike Greene said:


> Also, since I was testing the timing of pretty much every factor I could think of, I wanted to confirm that $NI_SIGNAL_TIMING_BEAT is triggering properly. I used $KSP_TIMER and spit out a message every beat for how much time transpired since the previous beat. AT 120 bpm, we would expect 500,000 microseconds per beat, but I was getting about 425,000 microseconds ... and every 15th beat (consistently 15), I would get around 510,000 microseconds. So every 15th beat has a 10ms lag. Weird.



That is quite weird. I am testing in standalone with this code:


```
on init
    set_listener($NI_SIGNAL_TIMER_BEAT, 1)
    reset_ksp_timer
end on

on listener
    message($KSP_TIMER)
    reset_ksp_timer
end on
```

and this is what I roughly get:






Which is not nearly as bad, sort of expected jitter considering myriad of other processing that's being done on the computer (and within Kontakt itself).


----------



## Mike Greene (Sep 21, 2020)

Ugh, I just realized I made a typo - I should have said 499,250 and not 425,000. (My mistake. 425,000 would be insanely bad.). The 510,000 figure is correct, though.

Mario, it’s interesting that you get a difference of 3 or 4 milliseconds, but it happens every other beat, instead of the correction every 15 beats. 3.5 is a pretty big difference, given that it equates to a little over 3 ticks. Not necessarily easy to discern by ear, but given that Kontakt operates in the world of microseconds, it’s a lot higher than I’d expect.


----------



## EvilDragon (Sep 21, 2020)

Rock steady timing in a non-realtime OS is a pipe dream 


BTW I get exactly the same results if I do my test in any DAW I have here, too.


----------



## wst3 (Sep 21, 2020)

EvilDragon said:


> Rock steady timing in a non-realtime OS is a pipe dream



I'm not sure a true Realtime OS is necessary, but an OS that supports realtime threads would be awfully nice. It is entirely possible that most people would never hear the difference, but for those that would...

BeOS was an attempt at a media friendly operating system, and when run on the proprietary hardware the timing was rock solid. It was not a realtime OS, but it supported pre-emptive multi-tasking, and a priority list (quality of service?). The last version I played with on a Wintel box was cool enough, but there was so little software available I gave up. (Even the Amiga had better media creation tools<G>).

BeOS still exists, for those that are curious - it is called Haiku, and it is an open source project. Development is still active, and you can run it off a USB drive, which is a lot more fun than running it from a "Live CD". For a time I had a machine in the studio that hosted BeOS and Redhat Linux so I could use PlanetCCRMA, for which development has slowed significantly. I've pretty much given up on alternate operating systems since most of the really cool "experimental" tools are now available for all platforms.


----------



## EvilDragon (Sep 21, 2020)

Well, Windows and macOS are also preemptive multitasking operating systems, with thread priority options too.


----------



## wst3 (Sep 21, 2020)

True today, not so back in the early 1990s when BeOS was started. Sorry if my wording was clumsy (which upon re-reading it was!)

That was, in an awkward way, my point - I'm not sure that a true realtime OS is really necessary today. Clock speeds are insanely fast, and operating systems have become more efficient in scheduling tasks. (pSOS is the only one I've worked with, but there are variations of Linux/Unix that are supposed to support true realtime scheduling)

I haven't written code for Windows or Mac, but I believe both now support a "Quality of Service" like metric? (Most of my coding was done in the days of Sun Microsystems, which probably dates me! I did have the opportunity to work with pSOS back then, and Xmos more recently.)


----------



## polypx (Sep 21, 2020)

On my system, I get 2 early, one late pattern.


----------



## EvilDragon (Sep 21, 2020)

Yep so it's quite obvious this is gonna be system specific.


----------



## EvilDragon (Sep 21, 2020)

wst3 said:


> True today, not so back in the early 1990s when BeOS was started.



Errrm, Windows has been preemptive since W95 (and kernel and virtual device drivers were also preempted back in 3.1 too)? 


(Man, this ED dude is great at googling, haha.)


----------



## Mike Greene (Sep 21, 2020)

polypx said:


> On my system, I get 2 early, one late pattern.


Depending on tempo, I'm getting all sorts of combinations, too. It's only at 120 bpm that I get the one out of every 15 pattern. (With my script, not Mario's.) At other tempos, I'll get one early, one late, or two early, one late, or all sorts of combinations, although interestingly ... never one early and _more_ than one late. It seems that early is a sort of default, and then a late one is thrown in at some point as an error correction.

The takeaway from all this for me is that I now realize I've been going overboard at trying to make my patterns accurate to a fraction of a millisecond, eeking out every microsecond so my timing will be as tight as possible. I literally set a standard of half a millisecond being the absolute maximum amount of timing slop I would accept in my code.

All to find out that one beat to the next may be 3 milliseconds (let alone half a millisecond) off before it even gets to my script.  It's kind of liberating, actually, because now I know I don't need to drive myself so crazy eeking out every microsecond of accuracy.


----------



## EvilDragon (Sep 21, 2020)

Yeah, also don't forget that wait() is inherently imprecise, and extremely short wait times are more CPU intensive.


----------



## polypx (Sep 21, 2020)

The listener is loose and funky, although in my experience it's tighter using BEAT than MS mode. For one project, I ended up building my own MS listener using a blip release trigger, and it was much much tighter than the listener CB doing the same job.


----------



## Mike Greene (Sep 21, 2020)

EvilDragon said:


> Yeah, also don't forget that wait() is inherently imprecise, and extremely short wait times are more CPU intensive.


Part 1:

I didn't know that. That's good to know.

My original issue was that wait_ticks seemed less accurate than regular wait. In my RealiDrums script, I can definitely hear it, but for more definitive proof, I wrote this script:

```
on init
    set_ui_height_px (200)
    make_perfview
    set_listener ($NI_SIGNAL_TIMER_BEAT,1)
    declare $Wait_Increment
    declare $beatcounter
    declare $Start_Timer
    declare %Array[1000]


    declare ui_text_edit @Readout
    set_control_par (get_ui_id (@Readout),$CONTROL_PAR_WIDTH,620)
    @Readout := "Readout"
    move_control_px (@Readout,5,50)

end on

on note
    if ($EVENT_NOTE = 36)
        @Readout := %Array[ 0] & "," & %Array[ 1] & "," & %Array[ 2] & "," & %Array[ 3] & "," & %Array[ 4] & "," & %Array[ 5] & "," & %Array[ 6] & "," & %Array[ 7] & "," & %Array[ 8] & "," & %Array[ 9] &...
              "," & %Array[10] & "," & %Array[11] & "," & %Array[12] & "," & %Array[13] & "," & %Array[14] & "," & %Array[15] & "," & %Array[16] & "," & %Array[17] & "," & %Array[18] & "," & %Array[19] &...
              "," & %Array[20] & "," & %Array[21] & "," & %Array[22] & "," & %Array[23] & "," & %Array[24] & "," & %Array[25] & "," & %Array[26] & "," & %Array[27] & "," & %Array[28] & "," & %Array[29] &...
              "," & %Array[30] & "," & %Array[31] & "," & %Array[32] & "," & %Array[33] & "," & %Array[34] & "," & %Array[35] & "," & %Array[36] & "," & %Array[37] & "," & %Array[38] & "," & %Array[39]
    end if
end on

on listener
    select ($NI_SIGNAL_TYPE)
        case $NI_SIGNAL_TIMER_BEAT
            inc(beatcounter)
            if (in_range(beatcounter, 4, 7))
                if ($beatcounter = 4)
                    $Start_Timer := $KSP_TIMER
                end if
                play_note(60, 120, 0, 0)
                %Array[10 * ($beatcounter - 4) + 0] := ($KSP_TIMER - $Start_Timer) / 1000

                $Wait_Increment :=  96 * $DURATION_SIXTEENTH / 240
                wait ($Wait_Increment)
                play_note(62, 120, 0, 0)
                %Array[10 * ($beatcounter - 4) + 1] := ($KSP_TIMER - $Start_Timer) / 1000

                $Wait_Increment :=  96 * $DURATION_SIXTEENTH / 240
                wait ($Wait_Increment)
                play_note(64, 120, 0, 0)
                %Array[10 * ($beatcounter - 4) + 2] := ($KSP_TIMER - $Start_Timer) / 1000

                $Wait_Increment :=  96 * $DURATION_SIXTEENTH / 240
                wait ($Wait_Increment)
                play_note(65, 120, 0, 0)
                %Array[10 * ($beatcounter - 4) + 3] := ($KSP_TIMER - $Start_Timer) / 1000

                $Wait_Increment :=  96 * $DURATION_SIXTEENTH / 240
                wait ($Wait_Increment)
                play_note(60, 120, 0, 0)
                %Array[10 * ($beatcounter - 4) + 4] := ($KSP_TIMER - $Start_Timer) / 1000

                $Wait_Increment :=  96 * $DURATION_SIXTEENTH / 240
                wait ($Wait_Increment)
                play_note(62, 120, 0, 0)
                %Array[10 * ($beatcounter - 4) + 5] := ($KSP_TIMER - $Start_Timer) / 1000

                $Wait_Increment :=  4 * $DURATION_SIXTEENTH / 240
                wait ($Wait_Increment)
                play_note(64, 120, 0, 0)
                %Array[10 * ($beatcounter - 4) + 6] := ($KSP_TIMER - $Start_Timer) / 1000

                $Wait_Increment := 96 * $DURATION_SIXTEENTH / 240
                wait ($Wait_Increment)
                play_note(65, 120, 0, 0)
                %Array[10 * ($beatcounter - 4) + 7] := ($KSP_TIMER - $Start_Timer) / 1000

                $Wait_Increment := 96 * $DURATION_SIXTEENTH / 240
                wait ($Wait_Increment)
                play_note(65, 120, 0, 0)
                %Array[10 * ($beatcounter - 4) + 8] := ($KSP_TIMER - $Start_Timer) / 1000

                $Wait_Increment := 96 * $DURATION_SIXTEENTH / 240
                wait ($Wait_Increment)
                play_note(65, 120, 0, 0)
                %Array[10 * ($beatcounter - 4) + 9] := ($KSP_TIMER - $Start_Timer) / 1000
            end if
    end select
end on
```

Once you update the script into Kontakt, it waits until beats 4 through 7 (I didn't want to start right away, just in case there are initial jitters.) Once beat 7 ends, the MIDI activity light stops flashing, so hit key 36 (C1) and it updates the GUI with the new timings readout.

This particular script spits out elapsed time (in milliseconds) for 10 equally spaced notes per beat. Each note play at 96 ticks (1/10 beat), which equates to 50ms at 120 bpm, so it's easy to see how far off the timings are.

Note that the code above is for regular wait (in microseconds.) To switch to a wait_ticks version so we can compare, just get rid of the _$DURATION_SIXTEENTH / 240_ multiplier and change "wait" to "wait_ticks".

The results for this test were pretty good, off by maybe a few milliseconds on each note. Interestingly, though, and seemingly contradictory to my OP question, was that the wait version and the wait_ticks version both gave identical times. Hmmm...


----------



## Mike Greene (Sep 21, 2020)

Part 2:

10 evenly spaced notes per beat revealed no difference between wait and wait_ticks timings, but my RealiDrums script has lots of snare ghost notes, as well as lots of times where a downbeat kick and hihat (for instance) might not play exactly together, so they're a few ticks off. In fact, it's the busier and ghost-notier beats that have the most apparent difference between wait and wait_ticks versions. So I modified the script so that the first 7 notes play 4 ticks apart, then the remaining three notes land on even hundreds (192 ticks):

```
on init
    set_ui_height_px (200)
    make_perfview
    set_listener ($NI_SIGNAL_TIMER_BEAT,1)
    declare $Wait_Increment
    declare $beatcounter
    declare $Start_Timer
    declare %Array[1000]


    declare ui_text_edit @Readout
    set_control_par (get_ui_id (@Readout),$CONTROL_PAR_WIDTH,620)
    @Readout := "Readout"
    move_control_px (@Readout,5,50)

end on

on note
    if ($EVENT_NOTE = 36)
        @Readout := %Array[ 0] & "," & %Array[ 1] & "," & %Array[ 2] & "," & %Array[ 3] & "," & %Array[ 4] & "," & %Array[ 5] & "," & %Array[ 6] & "," & %Array[ 7] & "," & %Array[ 8] & "," & %Array[ 9] &...
              "," & %Array[10] & "," & %Array[11] & "," & %Array[12] & "," & %Array[13] & "," & %Array[14] & "," & %Array[15] & "," & %Array[16] & "," & %Array[17] & "," & %Array[18] & "," & %Array[19] &...
              "," & %Array[20] & "," & %Array[21] & "," & %Array[22] & "," & %Array[23] & "," & %Array[24] & "," & %Array[25] & "," & %Array[26] & "," & %Array[27] & "," & %Array[28] & "," & %Array[29] &...
              "," & %Array[30] & "," & %Array[31] & "," & %Array[32] & "," & %Array[33] & "," & %Array[34] & "," & %Array[35] & "," & %Array[36] & "," & %Array[37] & "," & %Array[38] & "," & %Array[39]
    end if
end on

on listener
    select ($NI_SIGNAL_TYPE)
        case $NI_SIGNAL_TIMER_BEAT
            inc(beatcounter)
            if (in_range(beatcounter, 4, 7))
                if ($beatcounter = 4)
                    $Start_Timer := $KSP_TIMER
                end if
                play_note(60, 120, 0, 0)
                %Array[10 * ($beatcounter - 4) + 0] := ($KSP_TIMER - $Start_Timer) / 1000

                $Wait_Increment :=  4 * $DURATION_SIXTEENTH / 240
                wait ($Wait_Increment)
                play_note(62, 120, 0, 0)
                %Array[10 * ($beatcounter - 4) + 1] := ($KSP_TIMER - $Start_Timer) / 1000

                $Wait_Increment :=  4 * $DURATION_SIXTEENTH / 240
                wait ($Wait_Increment)
                play_note(64, 120, 0, 0)
                %Array[10 * ($beatcounter - 4) + 2] := ($KSP_TIMER - $Start_Timer) / 1000

                $Wait_Increment :=  4 * $DURATION_SIXTEENTH / 240
                wait ($Wait_Increment)
                play_note(65, 120, 0, 0)
                %Array[10 * ($beatcounter - 4) + 3] := ($KSP_TIMER - $Start_Timer) / 1000

                $Wait_Increment :=  4 * $DURATION_SIXTEENTH / 240
                wait ($Wait_Increment)
                play_note(60, 120, 0, 0)
                %Array[10 * ($beatcounter - 4) + 4] := ($KSP_TIMER - $Start_Timer) / 1000

                $Wait_Increment :=  4 * $DURATION_SIXTEENTH / 240
                wait ($Wait_Increment)
                play_note(62, 120, 0, 0)
                %Array[10 * ($beatcounter - 4) + 5] := ($KSP_TIMER - $Start_Timer) / 1000

                $Wait_Increment :=  4 * $DURATION_SIXTEENTH / 240
                wait ($Wait_Increment)
                play_note(64, 120, 0, 0)
                %Array[10 * ($beatcounter - 4) + 6] := ($KSP_TIMER - $Start_Timer) / 1000

                $Wait_Increment := 360 * $DURATION_SIXTEENTH / 240
                wait ($Wait_Increment)
                play_note(65, 120, 0, 0)
                %Array[10 * ($beatcounter - 4) + 7] := ($KSP_TIMER - $Start_Timer) / 1000

                $Wait_Increment := 192 * $DURATION_SIXTEENTH / 240
                wait ($Wait_Increment)
                play_note(65, 120, 0, 0)
                %Array[10 * ($beatcounter - 4) + 8] := ($KSP_TIMER - $Start_Timer) / 1000

                $Wait_Increment := 192 * $DURATION_SIXTEENTH / 240
                wait ($Wait_Increment)
                play_note(65, 120, 0, 0)
                %Array[10 * ($beatcounter - 4) + 9] := ($KSP_TIMER - $Start_Timer) / 1000
            end if
    end select
end on
```

The results are here, which I put in script form, so the formatting is easier to compare:

```
//What the timings should be:
0, 4, 8, 12, 16, 20, 25,200,300,400,  500,504,508,512,516,520,525,700,800,900,  1000,1004,1008,1012,1016,1020,1025,1200,1300,1400,  1500,1504,1508,1512,1516,1520,1525,1700,1800,1900
//Regular wait
0, 0, 0,   0,   0, 11, 11,197,301,394,  499,499,499,499,510,510,510,696,801,893,   998, 998, 998, 998,1010,1010,1010,1195,1300,1393,  1497,1497,1497,1509,1509,1509,1509,1695,1799,1892
//Using wait_ticks
0, 0, 0,   0,   0,   0,   0,185,278,383,  499,499,499,499,499,499,499,685,777,882,   998, 998, 998, 998,998, 998, 998, 1184,1277,1381,  1497,1497,1497,1497,1497,1497,1497,1683,1776,1880
```

The top line is what we _should_ see. (A note every 4ms, then on the hundreds.)
Second line is the regular wait (in microseconds) version.
Third line is wait_ticks version.

What's interesting is that for most of the initial 4 tick waits at the start of each beat, Kontakt seems to not have waited at all. Also of note is that with these quick notes, the wait_ticks version is considerably less accurate. Given that my RealiDrums beats are busier than this, that seems to explain the drunken timings I was hearing.


----------



## Mike Greene (Sep 21, 2020)

polypx said:


> The listener is loose and funky, although in my experience it's tighter using BEAT than MS mode. For one project, I ended up building my own MS listener using a blip release trigger, and it was much much tighter than the listener CB doing the same job.


That's interesting. How do you trigger the blip note to be in sync with each beat? Is that triggered in the listener callback, and then all the details for each beat are handled in the release callback?


----------



## polypx (Sep 22, 2020)

It's not beat or tempo synced at all, it was simply to count absolute time, ms ... so I start it once and form then on it's just ticking away in it's own thread. But that thread is tight.


----------



## derstefmitf (Feb 15, 2021)

Hi Guys, I am also looking into the possibility to change from the old blip note trick and my arpeggiator script running in a while loop triggered from the RCB to using the LCB and NI_SIGNAL_TIMER_MS (I need to use this listener type for other reasons). You findings kind of make me concern. Would you still recommend using the LCB despite the loose and sloppy behaviour? Kinda weird that this is the case as the LCB is designed to be as precise as possible. Cheers!


----------



## Mike Greene (Feb 15, 2021)

I always use LCB. (Although triggered by BEAT, not MS.) There are some quirks, but for the most part, it works well.


----------



## polypx (Feb 15, 2021)

I'd say the LCB is fine if you can work in Beat mode... but that's 24 ticks per quarter maximum resolution.


----------



## derstefmitf (Feb 16, 2021)

Thanks for the response. The problem is that I need to trigger things before a beat. Hm I could try to use Beat mode, look before the "right" beat in the LCB with an offset measured in beats and from this point use wait_ticks to get to the exact point in time when things need to happen 🤔
I will try that, but at the same time have a look at my old blip note solution and see how things improve there when I use wait_ticks.


----------



## Mike Greene (Feb 16, 2021)

For things that happen right before the beat, I'll put those into the previous beat of the LCB. Obviously that adds some extra challenges in the coding, and as this thread points out, the timing isn't always *exactly* what I might have expected ... although it's usually close enough, plus doing all of it in the LCB does make things nice and neat, so I'm sticking with it.

That's just me, of course, and it's not to say that's what you should do. Please report back how things work out with your method.


----------

