I have found that since JUCE 7.0.3, the HighResolutionTimer is way off from what it should be, unlike JUCE 7.02 where it was rock-solid. (I’m using 7.0.3 at the moment but I just tested 7.0.5 and for this it is the same).
Since JUCE 7.0.3 and later it is not even close to accurate:
On Mac it is nearly 10% slower between callbacks.
On Windows it is nearly 56% slower between callbacks.
I have a MIDI-generating app that I have been working on with JUCE for over three years now. (Standalone GUI App. Not using AudioProcessor.) It’s sort of a super-arpeggiator phrase-generator that generates MIDI data in real-time. It depends on a solid 1 ms Timer callback. For this, I have been successfully using the HighResolutionTimer. But since JUCE 7.0.3, the timing is way off (slower on Mac, and even much worse on Windows.)
It’s likely this is related to the threading changes in 7.0.3 but I have no idea what to fool with.
So I created a simple test app that calls HighResolutionTimer::startTimer(1)
for a 1 ms callback. The app then has a button that turns on debugging printout, where it stores the start time and prints out some data every 1 second (1000 callbacks). Here it is:
HiResTimerTest.zip (9.7 KB)
(EDIT: updated to include both Xcode and VS2019 Exporters)
So these DBG printouts show 30 seconds of receiving 1ms TimerCallbacks, where the startTime is stored and subtracted from the current Time to show time from zero.
The first column shows my internal 1 ms counter, which is just incremented by 1 ms for each callback received.
The second column shows the actual value returned by Time::getMillisecondCounterHiRes()
(minus the start time).
The third column shows the accumulated difference between the two.
First, on the Mac:
JUCE 7.0.2 Mac
-internal 1 ms- -hiResTime ms- --difference--
sec ms micro sec ms micro sec ms micro
callback: 00.000 000 00.000 000 00.000 000
callback: 01.000 000 00.999 993 00.000 -06
callback: 02.000 000 02.000 002 00.000 002
callback: 03.000 000 02.999 997 00.000 -02
callback: 04.000 000 03.999 995 00.000 -04
callback: 05.000 000 04.999 994 00.000 -05
callback: 06.000 000 06.000 006 00.000 006
callback: 07.000 000 07.000 001 00.000 001
callback: 08.000 000 07.999 994 00.000 -05
callback: 09.000 000 09.000 004 00.000 004
callback: 10.000 000 10.000 016 00.000 016
callback: 11.000 000 11.000 001 00.000 001
callback: 12.000 000 12.000 006 00.000 006
callback: 13.000 000 13.000 002 00.000 002
callback: 14.000 000 14.000 008 00.000 008
callback: 15.000 000 15.000 014 00.000 014
callback: 16.000 000 15.999 994 00.000 -05
callback: 17.000 000 16.999 998 00.000 -01
callback: 18.000 000 17.999 995 00.000 -04
callback: 19.000 000 18.999 993 00.000 -06
callback: 20.000 000 20.000 004 00.000 004
callback: 21.000 000 20.999 993 00.000 -06
callback: 22.000 000 22.000 006 00.000 006
callback: 23.000 000 22.999 992 00.000 -07
callback: 24.000 000 24.000 010 00.000 010
callback: 25.000 000 24.999 996 00.000 -03
callback: 26.000 000 26.000 003 00.000 003
callback: 27.000 000 27.000 003 00.000 003
callback: 28.000 000 27.999 995 00.000 -04
callback: 29.000 000 28.999 997 00.000 -02
callback: 30.000 000 30.000 005 00.000 005
In JUCE 7.0.2 Mac, we can see that there is a very slight jitter of +/-20 microseconds, with the actual time keeping very accurately to the number of callbacks received (internal 1 ms column). At the end of 30 seconds, The MIDI data generated fills 30 seconds of time exactly. 1ms is 1ms.
(And previously, I have measured the jitter between each 1 ms callback and it is infinitesimal.)
JUCE 7.0.3 Mac
-internal 1 ms- -hiResTime ms- --difference--
sec ms micro sec ms micro sec ms micro
callback: 00.000 000 00.000 000 00.000 000
callback: 01.000 000 01.099 666 00.099 666
callback: 02.000 000 02.199 072 00.199 072
callback: 03.000 000 03.302 303 00.302 303
callback: 04.000 000 04.401 938 00.401 938
callback: 05.000 000 05.508 542 00.508 542
callback: 06.000 000 06.607 307 00.607 307
callback: 07.000 000 07.702 652 00.702 652
callback: 08.000 000 08.786 031 00.786 031
callback: 09.000 000 09.879 499 00.879 499
callback: 10.000 000 10.978 429 00.978 429
callback: 11.000 000 12.070 584 01.070 584
callback: 12.000 000 13.164 801 01.164 801
callback: 13.000 000 14.253 678 01.253 678
callback: 14.000 000 15.345 196 01.345 196
callback: 15.000 000 16.439 011 01.439 011
callback: 16.000 000 17.534 295 01.534 295
callback: 17.000 000 18.626 020 01.626 020
callback: 18.000 000 19.718 178 01.718 178
callback: 19.000 000 20.807 242 01.807 242
callback: 20.000 000 21.897 735 01.897 735
callback: 21.000 000 22.983 616 01.983 616
callback: 22.000 000 24.077 274 02.077 274
callback: 23.000 000 25.169 486 02.169 486
callback: 24.000 000 26.270 512 02.270 512
callback: 25.000 000 27.360 884 02.360 884
callback: 26.000 000 28.454 947 02.454 947
callback: 27.000 000 29.548 967 02.548 967
callback: 28.000 000 30.643 480 02.643 480
callback: 29.000 000 31.733 584 02.733 584
callback: 30.000 000 32.823 893 02.823 893 2.823 seconds behind
In JUCE 7.0.3 Mac, at the end of 30 seconds (30,000 1ms callbacks), my app has generated 30 seconds of MIDI, but in actuality, 32.83 seconds have elapsed. The HighResolutionTimer is running 2.83 seconds behind. I can hear it - it generates the MIDI slower and with less accuracy than before! 120 BPM is no longer 120 BPM. 1 ms is actually about 1.1 ms.
On WINDOWS, it’s far worse.
JUCE 7.0.2 WINDOWS
-internal 1 ms- -hiResTime ms- --difference--
sec ms micro sec ms micro sec ms micro
callback: 00.000 000 00.000 000 00.000 000
callback: 01.000 000 00.999 813 00.000 -186
callback: 02.000 000 02.000 017 00.000 017
callback: 03.000 000 02.999 955 00.000 -44
callback: 04.000 000 03.999 774 00.000 -225
callback: 05.000 000 04.999 447 00.000 -552
callback: 06.000 000 05.999 832 00.000 -167
callback: 07.000 000 06.999 667 00.000 -332
callback: 08.000 000 07.999 830 00.000 -169
callback: 09.000 000 08.999 293 00.000 -706
callback: 10.000 000 10.000 104 00.000 104
callback: 11.000 000 11.000 194 00.000 194
callback: 12.000 000 12.000 126 00.000 126
callback: 13.000 000 12.999 800 00.000 -199
callback: 14.000 000 14.000 096 00.000 096
callback: 15.000 000 14.999 835 00.000 -164
callback: 16.000 000 15.999 410 00.000 -589
callback: 17.000 000 16.999 909 00.000 -90
callback: 18.000 000 17.999 662 00.000 -337
callback: 19.000 000 19.000 088 00.000 088
callback: 20.000 000 19.999 376 00.000 -623
callback: 21.000 000 20.999 898 00.000 -101
callback: 22.000 000 21.999 248 00.000 -751
callback: 23.000 000 22.999 518 00.000 -481
callback: 24.000 000 23.999 934 00.000 -65
callback: 25.000 000 24.999 956 00.000 -43
callback: 26.000 000 25.999 819 00.000 -180
callback: 27.000 000 26.999 685 00.000 -314
callback: 28.000 000 27.999 619 00.000 -380
callback: 29.000 000 28.999 979 00.000 -20
callback: 30.000 000 30.000 108 00.000 108
In JUCE 7.0.2 Windows, we can see that it is still solid (more jitter than Mac, but still within +/-500 microseconds of where it should be, and after 30 seconds (30,000 1ms callbacks), 30 realtime seconds has occurred. 1 ms is 1 ms.
JUCE 7.0.3 WINDOWS
-internal 1 ms- -hiResTime ms- --difference--
sec ms micro sec ms micro sec ms micro
callback: 00.000 000 00.000 000 00.000 000
callback: 01.000 000 01.604 174 00.604 174
callback: 02.000 000 03.174 739 01.174 739
callback: 03.000 000 04.790 289 01.790 289
callback: 04.000 000 06.377 858 02.377 858
callback: 05.000 000 07.973 723 02.973 723
callback: 06.000 000 09.574 455 03.574 455
callback: 07.000 000 11.181 865 04.181 865
callback: 08.000 000 12.803 037 04.803 037
callback: 09.000 000 14.401 873 05.401 873
callback: 10.000 000 16.000 592 06.000 592
callback: 11.000 000 17.577 725 06.577 725
callback: 12.000 000 19.156 730 07.156 730
callback: 13.000 000 20.806 755 07.806 755
callback: 14.000 000 22.383 309 08.383 309
callback: 15.000 000 23.994 883 08.994 883
callback: 16.000 000 25.597 909 09.597 909
callback: 17.000 000 27.205 626 10.205 626
callback: 18.000 000 28.803 494 10.803 494
callback: 19.000 000 30.381 940 11.381 940
callback: 20.000 000 32.017 642 12.017 642
callback: 21.000 000 33.598 949 12.598 949
callback: 22.000 000 35.150 333 13.150 333
callback: 23.000 000 36.742 656 13.742 656
callback: 24.000 000 38.293 149 14.293 149
callback: 25.000 000 39.872 513 14.872 513
callback: 26.000 000 41.449 424 15.449 424
callback: 27.000 000 43.050 336 16.050 336
callback: 28.000 000 44.613 216 16.613 216
callback: 29.000 000 46.183 615 17.183 615
callback: 30.000 000 47.757 880 17.757 880. 17.757 seconds behind!
In JUCE 7.0.3 Windows, after 30 seconds, 47.757 realtime seconds have elapsed. The HighResolutionTimer is a whopping 17.757 seconds behind where it should be for 30,000 callbacks. My app sounds like it’s on drugs. 1 ms is like 1.59 ms.
I was busy working on the interface the last few months or I would have noticed this sooner; I haven’t been testing the stuff that already worked.
I’m really hoping that @reuk or someone from the juce team can look into this. My whole app depends on the HighResolutionTimer working solidly like it always has, since I started with JUCE 5.
My Mac System:
MacPro (Mid 2012) 12-core
2 x 3.06 GHz 6-Core Intel Xeon
MacOS 10.14.6 Mojave
My Windows System:
Dell XPS 8930
Intel Core i9-9900 CPU @ 3.10GHz
Windows 10 Home