This project has moved. For the latest updates, please go here.

waveout get notification after elapsed time

Mar 9, 2016 at 6:46 PM
Hi,

i am working on a project where i need to play an audiofile (mp3) and after a specific amount of time i need to trigger some kind of a signal. Therefore i wrote my own AudioFileReader.
    public class TimeElapsedAudioFileReader : AudioFileReader
    {
        public event Action<TimeSpan> Elapsed;

        public TimeElapsedAudioFileReader(string filename):base(filename)
        {
            
        }

        public override int Read(byte[] buffer, int offset, int count)
        {
            if (Elapsed != null)
                Elapsed(this.CurrentTime);
            return base.Read(buffer, offset, count);
        }
    }
The Problem with that approach is that it is not precise enough. I wanna have the elapsed Signal triggered after each millisecond for example, therefore i thought i have to set the buffersize that is read but i cant find anything to set the buffersize. Maybe I am wrong with my thoughts? Hope for help!

KR Manuel
Mar 10, 2016 at 11:37 PM
If you want a sample-precise approach, then I´d use NotifyingSampleProvider. There you can compare each sample with the current time of your AudioFileReader and raise an event if so.
Mar 11, 2016 at 12:53 AM
Edited Mar 11, 2016 at 10:27 PM
Can you give me maybe a short example how to use NotifyingSampleProvider with AudioFileReader?

I guess i got it, but it's still not precise enought. I Need a precision to a hundreth of a second. Here is my Code:
  public class TimeElapsedAudioFileReader : AudioFileReader
    {
        public event Action<TimeSpan> Elapsed;
        public NotifyingSampleProvider Nsp { get; set; }

        public TimeElapsedAudioFileReader(string filename) : base(filename)
        {
            Nsp = new NotifyingSampleProvider(this.ToSampleProvider());
            Nsp.Sample += _nsp_Sample;
        }

        private void _nsp_Sample(object sender, SampleEventArgs e)
        {
                if (Elapsed != null)
                    Elapsed(this.CurrentTime);
        }
    }
When i increase the number of buffers on the AudioFileReader Object to like 100 it's working, but i dont think that's the right way or?
Mar 12, 2016 at 9:15 PM
Edited Mar 12, 2016 at 9:17 PM
Where is the Read call? Keep in mind, that you have to read from the NotifyingSampleProvider and not from the AudioFileReader.

Your approach is a good start, but I wouldn´t recommend you to fire an event on each sample. Better check if the time is greater or equal of the time you want to wait and only then raise the event.

The reason your code fails I believe, could have several reasons:

1) you fire events with an samplerate of 44100 and stereo channel count of 2 = 88200 times per second.
Not very performant I think, the frequent event processing might be an issue.

2) you probably have the false configuration for your WaveOut object. I recommend 150 (ms) latency and 3 buffers, what gave me the closest results for my media player so far. With this configuration the Read method of the sampleprovider requests 4410 samples, which is enough to display an fft with 4096 samples in real time.

-> So it really depends on your application. If you want a small duration for more precision the buffers would automatically have to be smaller, as the read method anyway reads a full package of samples into your buffer. But for low latency beyond 75ms you should use Asio or DirectSound, as the WaveOut crackles then (with my computer).
Mar 14, 2016 at 12:55 AM
First of all, thanks for your help!
Here is my full Code:
    public class TimeElapsedAudioFileReader : AudioFileReader
    {
        public event Action<TimeSpan> Elapsed;
        public NotifyingSampleProvider Nsp  { get; set; }
        public ETimeSpan SignalTime         { get; set; }

        public TimeElapsedAudioFileReader(string filename) : base(filename)
        {
            Nsp = new NotifyingSampleProvider(this.ToSampleProvider());
            Nsp.Sample += _nsp_Sample;
        }

        private bool _triggered = false;

        private void _nsp_Sample(object sender, SampleEventArgs e)
        {
            if (_triggered)
                return;
            if (this.CurrentTime >= SignalTime.BaseSpan)
            {
                _triggered = true;
                if (Elapsed != null)
                    Elapsed(this.CurrentTime);
            }
        }

        public override int Read(byte[] buffer, int offset, int count)
        {
            return base.Read(buffer, offset, count);
        }

        public void Reset()
        {
            this.Position = 0;
            _triggered = false;
        }
    }
And here how i Init the WaveOutEvent Object:
 private void InitAudioFile()
        {
            try {   
                _source = new TimeElapsedAudioFileReader(_app.Settings.AudioFilePath);
                _source.Elapsed += _audioreader_Elapsed;
                _waveout.DesiredLatency = 150;
                _waveout.NumberOfBuffers = 15;
                _waveout.Init(_source.Nsp);
                
            }
            catch (System.Exception ex)
            {
                ExceptionHandler.HandleException(ex, "Error while Initializing Audiofile", this);
            }
        }
Do I Need to override the Read Method as well?
With this Waveout Settings i can reach a precision to 1/100 of a second but not a 1/1000
Mar 14, 2016 at 7:18 PM
This code you provided should work. To increase accuracy you may want to try a latency ~ 50ms with directsound or asio.

Number of buffers also has some influence on how many samples are read on each calling, but don´t ask me how exactly, better look at the source code then.

For an accuracy of 1/1000 s you´d have to read 44 samples (SampleRate/1000) on each calling for "Read". Therefore you´d need a extremely low latency as well as small buffers.

For more details Mark could probably tell more.