Reputation: 21
I am trying to create a precise metronome in C# using NAudio, however there is always a 20 ms delay in either direction when playing the audio file. It is noticable both when listening to the metronome, as well as when recording and inspecting it in a software like audacity.
I took inspiration from this comment to put together the following code:
using System;
using NAudio.Wave;
namespace audio_timing_test
{
class Program
{
public static int bpm = 120;
public static DirectSoundOut player;
public static WaveFileReader waveReader;
public static AccurateTimer AudioTickTimer;
static void Main(string[] args)
{
player = new DirectSoundOut();
waveReader = new WaveFileReader(System.AppContext.BaseDirectory + "ticksound.wav");
player.Init(new WaveChannel32(waveReader));
AudioTickTimer = new AccurateTimer(new Action(AudioTick), (int)(60000 / bpm));
waveReader.CurrentTime = waveReader.TotalTime + TimeSpan.FromSeconds(1);
player.Play();
Console.WriteLine("Press key to exit.");
Console.ReadKey();
}
static void AudioTick()
{
waveReader.CurrentTime = TimeSpan.FromSeconds(0);
}
}
}
This plays back the audio at alternating intervals of exactly 480 and 520 ms, however it's supposed to be 500 ms every tick. The AccurateTimer method used can be found here. I also tried using System.Timers.Timer, as suggested here, which gives me the same results.
I should add that the reason I do "waveReader.CurrentTime = waveReader.TotalTime + TimeSpan.FromSeconds(1);" is to skip the first tick, which is how it is gonna be in the final program. Removing that line does not affect the result.
What is the issue here and how can I achieve a consistent and precise interval of 500 ms? Thank you!
Upvotes: 1
Views: 401
Reputation: 21
I figured out what the problem was:
As it turns out, keeping the audio running and resetting the position to 0 as a way to do a tick doesn't work well and you instead have to handle the end/stop of the audio and restart it properly every tick.
Here is the updated and working code:
using System;
using NAudio.Wave;
namespace audio_timing_test
{
class Program
{
public static double bpm = 100;
public static DirectSoundOut player;
public static WaveFileReader waveReader;
public static WaveChannel32 wave32;
public static AccurateTimer audioTickTimer;
static void Main(string[] args)
{
waveReader = new WaveFileReader(System.AppContext.BaseDirectory + "ticksound.wav");
wave32 = new WaveChannel32(waveReader);
wave32.PadWithZeroes = false;
player = new DirectSoundOut();
player.PlaybackStopped += new EventHandler<StoppedEventArgs>(StopAudio);
player.Init(wave32);
audioTickTimer = new AccurateTimer(new Action(AudioTick), (int)(60000 / bpm));
Console.WriteLine("Press key to exit.");
Console.ReadKey();
}
static void AudioTick()
{
player.Play();
}
static void StopAudio(object sender, StoppedEventArgs e)
{
player.Stop();
waveReader.CurrentTime = TimeSpan.FromSeconds(0);
}
}
}
Upvotes: 1