Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Hotfix: excessive CPU use in the MIDI SenderThread #24

Open
wants to merge 4 commits into
base: master
Choose a base branch
from

Conversation

dgslomin
Copy link
Contributor

@dgslomin dgslomin commented Sep 6, 2019

I ran a profiler to determine where the runaway CPU usage appeared, and it
showed up as dominated by calls to msleep() in SenderThread. I did the
standard replacement of the busy loop with a mutex and wait condition, which
solved the problem. Initial interactive testing showed no degradation of
timing accuracy in playback.

I ran a profiler to determine where the runaway CPU usage appeared, and it
showed up as dominated by calls to msleep() in SenderThread.  I did the
standard replacement of the busy loop with a mutex and wait condition, which
solved the problem.  Initial interactive testing showed no degradation of
timing accuracy in playback.
@dgslomin
Copy link
Contributor Author

dgslomin commented Sep 7, 2019

This fixed the problem on Mac but there's still runaway CPU usage on Windows. The profiler I have there isn't as good, but it thinks the hot spot is somewhere in the graphics rendering (somewhere in quartz.dll which is part of DirectShow, called via dsengine.dll which is Qt's bridge to DirectShow). Still looking...

@dgslomin
Copy link
Contributor Author

dgslomin commented Sep 7, 2019

Ok, the problem on Windows is definitely coming from the QMediaPlayer used in the metronome, even when the metronome is disabled. The problem goes away if you take out the line that loads the WAV file but of course the metronome is useless then. Removing the low latency flag doesn't help.

I checked the WAV file to see if it was actually some weird format that was making QMediaPlayer do extra work (WAV is actually a container format that can hold lots of different encodings of audio inside, including MP3), but it's a plain old 44/16/mono PCM audio file, which shouldn't cause any problems.

I wonder if it's worth ditching QMediaPlayer and adding a third party dependency for platform-neutral audio playback. There are lots of them, although dependencies are a pain and it's nice that the program has so few of them.

Another alternative without any dependencies would be to do metronome clicks via MIDI output -- just play a closed high hat on the drum channel. That would have some interaction with actual drum tracks, though. What do you think?

@dgslomin
Copy link
Contributor Author

dgslomin commented Sep 7, 2019

Aha! QMediaPlayer misbehaves like this when you tell it a path to a non-existent audio file. Maybe it's polling the file system to wait for the file to arrive. When it finds the file, the performance is fine.

The metronome code refers to the file via a relative local path and my local directory layout didn't match the code's assumption (since I'm running in the build directory rather than using an installer). The WAV file is already embedded in the executable as a resource. I'll try changing the code to load it from there rather than making assumptions about paths.

Div Slomin and others added 3 commits September 7, 2019 11:47
This means that the program doesn't need to look for the file in a particular
local path since it's embedded in the executable (it was already there anyway).
QMediaPlayer doesn't reliably support loading from resources but QSoundEffect
does, and the Qt docs recommend using the latter for this kind of purpose
anyway.
…plicitly; also tightened timing of the metronome by removing silence from the start of the click file
@markusschwenk
Copy link
Owner

What do the 500ms in the waitCondition indicate?
Before, we were waiting for 1ms each iteration.

@Spire42
Copy link

Spire42 commented Feb 2, 2020

What do the 500ms in the waitCondition indicate?
Before, we were waiting for 1ms each iteration.

That specifies the maximum amount of time to wait before timing out and returning to the caller. (On the other hand, it will return instantly as soon as there's an event in the queue.) This is much more efficient than unconditionally sleeping for a fixed duration of 1 ms.

The reason there's a timeout value (instead of waiting forever, which can be done by omitting the parameter) is so that the thread can be terminated cleanly — for example, if the application is shutting down).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants