Back in the old days (32bit) it was sufficient using Windows Multimedia API (MMSystem). It ran fast and stable, and did not get easily disturbed by performance load on a PC.
Since moving to 64bit the MMSystem is no longer stable when load is heavy on the CPU. I created a thread applying tpTimeCritical priority, but even that is challenged by heavy loads. That is not satisfactory when clocks are critical.
The next couple of weeks I will work on removing above issue by using the Real-Time Work Queue API. This API is covered by the Multimedia Class Scheduler service (MMCSS), and enables multimedia applications to ensure that their time-sensitive processing receives prioritized access to CPU resources.
This service enables multimedia applications to utilize as much of the CPU as possible without denying CPU resources to lower-priority applications. Hopefully the MIDI Clock component receive it most needed precise timing.
I will announce the result of this work on this topic.
Last edit: Normann Simplified 2021-05-04
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
After some research and time analysis it turns out priority is not the problem. Both PostMessage and TThread.Queue are having blocking issues, up until 40 milliseconds on a single clock event for short and high CPU loads, and probably a lot more for the sequencing engine. I don't know if this a change in the architecture of CPU's, Windows or Delphi, but this was not an issue with MMSystem, 32 bit and early versions of Delphi.
However, the messaging queue will be changed with a simplified lock-free queue. First preliminary analysis shows short blockings up until 0.07 milliseconds for a clock event, which is promising for the sequencing engine.
The engine need to send out MIDI messages to devices, but it may not delay any messages for more than 5 milliseconds, even on sudden high load in the CPU.
The next few weeks I will work on designing the lock-free queue, and have it on the next release, along with time analysis of the clock and sequencing engine.
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
After implementing a simple but effective lock-free queue for components, I went on measuring then time analysis and discovered another issue. To my surprise there is a big gap in performance in between different MIDI interfaces, presumably in the implementation of the drivers.
The time difference in sending MIDI messages through my ESI Mate and a semi-pro Yamaha PSR keyboard is huge. On sudden CPU load ESI could take up until 50 ms (!) transmitting messages, compared to same measurements for others topping at 0,25 ms using MMSystem threads.
I managed to implement a minimal RTWorkQ (Real-time Work Queue) solution using RtwqAddPeriodicCallback and RtwqRemovePeriodicCallback, but I did not manage to control the interval. It triggers every 15 ms, and seems stable.
The RTWorkQ header is not available for Delphi, and the APIs are not well-documented at Microsoft, but I will work on the other parts of the framework later.
The Winapi.SysCtrls.Timers.pas unit in this library will come with two options for the compiler; a directive for the MMSystem (default), and a directive for RTWorkQ. If none of the directives are set, a default threaded timer is created, which is similar to the RTWorkQ in resolution, but seem to be running in a lower priority setup.
Key figures (running on i7-4790 3.6Ghz) sequencing a MIDI file at Tempo = 118 and 480 clock resolution (Harpo - Movie Star, 99KB):
MMSystem: Triggered every 4.7 clock avg., using less than 0.275 ms sending messages.
RTWorkQ: Triggered every 14.8 clock avg., using less than 0.7 ms sending messages.
Default: Triggered every 14.75 clock avg., using less than 0.62 ms sending messages.
The next release will have the MMSystem as the default setting for the Windows platform, if I am not able to get the Real-Time Work Queue run with a shorter interval and proper priority. All platform will get the new lock-free message queue.
Last edit: Normann Simplified 2021-05-15
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
Back in the old days (32bit) it was sufficient using Windows Multimedia API (MMSystem). It ran fast and stable, and did not get easily disturbed by performance load on a PC.
Since moving to 64bit the MMSystem is no longer stable when load is heavy on the CPU. I created a thread applying tpTimeCritical priority, but even that is challenged by heavy loads. That is not satisfactory when clocks are critical.
The next couple of weeks I will work on removing above issue by using the Real-Time Work Queue API. This API is covered by the Multimedia Class Scheduler service (MMCSS), and enables multimedia applications to ensure that their time-sensitive processing receives prioritized access to CPU resources.
This service enables multimedia applications to utilize as much of the CPU as possible without denying CPU resources to lower-priority applications. Hopefully the MIDI Clock component receive it most needed precise timing.
I will announce the result of this work on this topic.
Last edit: Normann Simplified 2021-05-04
After some research and time analysis it turns out priority is not the problem. Both PostMessage and TThread.Queue are having blocking issues, up until 40 milliseconds on a single clock event for short and high CPU loads, and probably a lot more for the sequencing engine. I don't know if this a change in the architecture of CPU's, Windows or Delphi, but this was not an issue with MMSystem, 32 bit and early versions of Delphi.
However, the messaging queue will be changed with a simplified lock-free queue. First preliminary analysis shows short blockings up until 0.07 milliseconds for a clock event, which is promising for the sequencing engine.
The engine need to send out MIDI messages to devices, but it may not delay any messages for more than 5 milliseconds, even on sudden high load in the CPU.
The next few weeks I will work on designing the lock-free queue, and have it on the next release, along with time analysis of the clock and sequencing engine.
After implementing a simple but effective lock-free queue for components, I went on measuring then time analysis and discovered another issue. To my surprise there is a big gap in performance in between different MIDI interfaces, presumably in the implementation of the drivers.
The time difference in sending MIDI messages through my ESI Mate and a semi-pro Yamaha PSR keyboard is huge. On sudden CPU load ESI could take up until 50 ms (!) transmitting messages, compared to same measurements for others topping at 0,25 ms using MMSystem threads.
I managed to implement a minimal RTWorkQ (Real-time Work Queue) solution using RtwqAddPeriodicCallback and RtwqRemovePeriodicCallback, but I did not manage to control the interval. It triggers every 15 ms, and seems stable.
The RTWorkQ header is not available for Delphi, and the APIs are not well-documented at Microsoft, but I will work on the other parts of the framework later.
The Winapi.SysCtrls.Timers.pas unit in this library will come with two options for the compiler; a directive for the MMSystem (default), and a directive for RTWorkQ. If none of the directives are set, a default threaded timer is created, which is similar to the RTWorkQ in resolution, but seem to be running in a lower priority setup.
Key figures (running on i7-4790 3.6Ghz) sequencing a MIDI file at Tempo = 118 and 480 clock resolution (Harpo - Movie Star, 99KB):
MMSystem: Triggered every 4.7 clock avg., using less than 0.275 ms sending messages.
RTWorkQ: Triggered every 14.8 clock avg., using less than 0.7 ms sending messages.
Default: Triggered every 14.75 clock avg., using less than 0.62 ms sending messages.
The next release will have the MMSystem as the default setting for the Windows platform, if I am not able to get the Real-Time Work Queue run with a shorter interval and proper priority. All platform will get the new lock-free message queue.
Last edit: Normann Simplified 2021-05-15