This project has moved and is read-only. For the latest updates, please go here.

LoopStream with WaveProvider

Sep 23, 2014 at 11:29 AM
Hi!

I need to loop AudioFiles that will be played along with other audio sources via a MultiplexingWaveProvider (and then Asio). So I need to convert the AudioFile Stream to the same format as all the other audio sources have. They have IeeeFloat Mono.

My code for now looks like this:
switch (Path.GetExtension(filename))
         {
            case ".mp3":
               _noiseStream = new Mp3FileReader(filename);
               break;
            case ".wav":
               _noiseStream = new WaveFileReader(filename);
               break;
         }

         _noiseStereoToMono = new StereoToMonoProvider16(_noiseStream) { LeftVolume = 1, RightVolume = 1 };
         _noiseW16ToFloat = new Wave16ToFloatProvider(_noiseStereoToMono);
         _noiseProviderToStream = new WaveProviderToWaveStream(_noiseW16ToFloat);
         _noiseLoopStream = new LoopStream(_noiseProviderToStream, () =>
         {
            Console.WriteLine("looping");
            //https://naudio.codeplex.com/discussions/406515
            _noiseStream.Position = 0;
         });
As you can see, I have a WaveProvider inside the LoopStream (from here). WaveProviders don't have a Position-Property to set. I found this thread (https://naudio.codeplex.com/discussions/406515) where Mark says that you have to modify the position of the source stream for getting this to work. I did this with invoking an action at the looping-point.

My modified Read-Method from the LoopStream:
public override int Read(byte[] buffer, int offset, int count)
      {
         int totalBytesRead = 0;

         while (totalBytesRead < count)
         {
            int bytesRead = sourceStream.Read(buffer, offset + totalBytesRead, count - totalBytesRead);
            if (bytesRead == 0)
            {
               if (sourceStream.Position == 0 || !EnableLooping)
               {
                  // something wrong with the source stream
                  break;
               }
               // loop
               //https://naudio.codeplex.com/discussions/406515
               if (loopingAction == null)
               {
                  sourceStream.Position = 0;
               }
               else
               {
                  loopingAction.Invoke();
               }
            }
            totalBytesRead += bytesRead;
         }
         return totalBytesRead;
      }
The Action which will be called is in the previous code block (_noiseStream.Position = 0;). Doing this, sadly leads to Exceptions.
IndexOutOfRange-Exception in the Read-Method of the StereoToMonoProvider16 at line
short left = sourceWaveBuffer.ShortBuffer[sample];
I think I need some sort of locking. What is the best way to do it? Or would you suggest a complete different way to loop back to position 0 (instead of the action.invoke())?


Stefan
Oct 27, 2014 at 12:02 PM
Any ideas?
Nov 11, 2014 at 5:39 PM
I can't quite understand how this could happen. The reposition should be happening on the playback thread so locking should not be the issue. I also can't see how that line could return an index out of range exception unless the source provider returned more from it's Read method than was required.

If you are able to debug into NAudio, then it would be interesting to know what the values of sourceBytesRead and sourceBytesRequired are.
Apr 23, 2015 at 1:55 PM
Sorry. It has been quite a while, but now this problem is getting acute again (I'm refactoring some old code).

Here are some values from the read methode of the StereoToMonoProvider16:
Read is called with the parameters buffer.length=2048, offset=324, count=1886

when the IndexOutOfRange-Exception happens
sourceBytesRead = 3772
sourceBytesRequired = 3772
samplesRead = 1886
in the for loop: sample = 1734, destOffset = 1029
Apr 23, 2015 at 2:27 PM
well, actually I can just add the LoopStream right after the FileReader and THEN add the StereoToMono and the other things after that. I think thats the right way to do it ;)

Like this:
         switch (Path.GetExtension(filename))
         {
            case ".mp3":
               _noiseStream = new Mp3FileReader(filename);
               break;
            case ".wav":
               _noiseStream = new WaveFileReader(filename);
               break;
         }

         _noiseLoopStream = new LoopStream(_noiseStream);

         _noiseStereoToMono = new StereoToMonoProvider16(_noiseLoopStream) { LeftVolume = 1, RightVolume = 1 };
         _noiseW16ToFloat = new Wave16ToFloatProvider(_noiseStereoToMono);
         _noiseProviderToStream = new WaveProviderToWaveStream(_noiseW16ToFloat);
Marked as answer by d03090 on 4/23/2015 at 6:27 AM
Apr 30, 2015 at 6:43 PM
Edited Apr 30, 2015 at 6:44 PM
Loopstream Needs a Wavestream as Input, because it uses the Position Property for Looping (which a sampleprovider doesn´t supply).