0

I am trying to create a command-line metronome app in C using PlaySound() from windows API. I am using the following code to generate a ticking metronome with 120 bmp tempo. The sound plays perfectly with my local .wav files but the tempo is not consistent. Sometimes it is rushed sometimes it is delayed. Any solution on how to make it consistent?

#include <stdio.h>
#include <windows.h>

int main() {
    while (1) {
        PlaySound("lib\\tick.wav", NULL, SND_FILENAME | SND_NODEFAULT | SND_ASYNC);
        Sleep(500);
        for (int i = 0;i < 3;i++) {
            PlaySound("lib\\click.wav", NULL, SND_FILENAME | SND_NODEFAULT | SND_ASYNC);
            Sleep(500);
        }
    }
    return 0;
}
Ashis Paul
  • 33
  • 4
  • Well, I am not sure if this is the problem, but of course "Sleep" only guarantees that your application will not run for at least the given time, it does not guarantee that it will start as soon as the time specified is over. I can be more, the scheduler is the main player there. Here is a link that seems to talk about the same issue: https://blat-blatnik.github.io/computerBear/making-accurate-sleep-function/ – Marcell Juhász Oct 17 '21 at 06:52
  • A similar question was asked a while ago but with regard to Python. The problem is this is not how you would program a Metronome. The link provided in the previous comment provides some interesting approaches, but lacks the accuracy required for audio. If you’re goal is to learn more about programming, try the link techniques. If you’re goal is to learn about _audio_ programming, sadly you’ll have to go back to the drawing board. – fdcpp Oct 17 '21 at 11:46
  • Additionally, common desktop operation systems are no real-time systems. There are no guarantees for exact timings, not even quite exact timings. Especially on Windows I had some unpleasant experiences. -- You might get away if you play a sound stream that is continuously generated. – the busybee Oct 17 '21 at 12:24
  • 1
    Try using a [multimedia timer](https://learn.microsoft.com/en-us/windows/win32/multimedia/multimedia-timers) instead of a sleep loop. – Remy Lebeau Oct 17 '21 at 18:46
  • `PlaySound` trades ease of use for feature completeness. For accurate timing you will want to use a more capable [Audio API](https://learn.microsoft.com/en-us/windows/win32/audio-and-video). – IInspectable Oct 18 '21 at 14:35

1 Answers1

0

When I managed to get a very good metronome working in Java, I did not rely on the Thread.sleep() method. As others point out in the comments, applications don't give very precise real time guarantees like this.

IDK what the equivalent is, with C, but what eventually worked for me was to have a continually streaming audio signal (of silence) where I counted the individual frames as they were being sent to the output, and mixed in the "click" PCM at the calculated frames.

For example, if you have 120 beats per minute, and a format of 44100 fps, then on every 22050th frame, you would mix in your click sound to the outgoing line.

The class I use in Java is the SourceDataLine if it helps to look at how that is structured. Does C even have an equivalent? Maybe it depends on what libraries are in use for sound output? I was pretty bewildered when I was looking at possibly doing my audio coding in C.

The nice thing about SourceDataLine (if you can find an equivalent) is that it employs a blocking queue, so the write operations are forced to wait until the system is ready for the next buffer load, and is thus closely tied to the rate of data consumption which stays very steady for clean playback.

Phil Freihofner
  • 7,645
  • 1
  • 20
  • 41