Search code examples
c#.netaudiowavnaudio

naudio WaveOut not playing all data provided by iSampleProvider


I am working on a click generator using nAudio. As a test, I created a ISampleProvidor class to read/play from an audio file. The iSampleProvider reads in a PCM (32-bit ieee) wav file and then plays it back using the WaveOut player. The WaveOut plays only about 1/4 of the audio passed via the IsampleProvidor Read() method. This results in a choppy playback. The ISampleProvider read() method requests the correct amount of data at the correct time intervals, but the WaveOut only plays the first 25% of the samples provided back to the interface. Any Idea how to address this, or am I using the wrong classes to build a click track (the BufferedWaveProvider might also work, but it only buffers 5 seconds of audio)?

 public void TestSampleProvider()
        {
            ISampleProvider mySamples = new MySamples();
            var _waveOut = new WaveOut(WaveCallbackInfo.FunctionCallback()) {DeviceNumber = 0};
            _waveOut.Init(mySamples);
            _waveOut.Play();
            Console.ReadLine();
        }

  public class MySamples : ISampleProvider
    {

        private float[] samplesFloats;
        private int position;
        private WaveFileReader clicksound;
        public int Read(float[] buffer, int offset, int count)
        {

            var copyCount = count;
            if (position + copyCount > samplesFloats.Count())
            {
                copyCount = samplesFloats.Count() - position;
            }

            Console.WriteLine("samplesFloats {0} position {1}  copyCount {2} offset {3} time {4}", samplesFloats.Count(), position, copyCount, offset, DateTime.Now.Millisecond);
            Buffer.BlockCopy(samplesFloats, position, buffer, offset, copyCount);

            position += copyCount;

          return copyCount;
        }

        public MySamples()
        {
            clicksound = new WaveFileReader(@"C:\temp\sample.wav");
            WaveFormat = clicksound.WaveFormat;
            samplesFloats = new float[clicksound.SampleCount];
            for (int i = 0; i < clicksound.SampleCount; i++)
            {
                samplesFloats[i] = clicksound.ReadNextSampleFrame()[0];//it;s a mono file
            }
        }
        public WaveFormat WaveFormat { get; private set; }
    }

Solution

  • I think there may be an issue with the WaveOut using the ISampleProvider, so I used the IWaveProvider interface to do the same thing. In fact, here's a bare bones class for sending a non-ending click to the waveout. This might run into memory issues if you let it run a long time but for pop songs it should be fine. Also this will only work for 32-bit files (note the *4 on the byte buffer)

      public class MyClick : IWaveProvider
        {
            private int position;
            private WaveFileReader clicksound;
            private byte[] samplebuff;
            MemoryStream _byteStream = new System.IO.MemoryStream();
            public MyClick(float bpm=120)
            {
                clicksound = new WaveFileReader(@"click_sample.wav");
                var bpmsampleslen = (60 / bpm) * clicksound.WaveFormat.SampleRate;
                samplebuff = new byte[(int) bpmsampleslen*4];
                clicksound.Read(samplebuff, 0,(int) clicksound.Length);
                _byteStream.Write(samplebuff, 0, samplebuff.Length);
                _byteStream.Position = 0;
                WaveFormat = clicksound.WaveFormat;
    
            }
            public int Read(byte[] buffer, int offset, int count)
            {
             //we reached the end of the stream add another one to the end and keep playing
                if (count + _byteStream.Position > _byteStream.Length)
                {
                    var holdpos = _byteStream.Position;
                    _byteStream.Write(samplebuff, 0, samplebuff.Length);
                    _byteStream.Position = holdpos;
                }
            return _byteStream.Read(buffer, offset, count);
    
            }
    
            public WaveFormat WaveFormat { get; private set; }
        }