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

Stereo to Mono on-the-fly

Feb 6, 2011 at 4:55 PM
Edited Feb 6, 2011 at 4:56 PM

To earn a crust I type up audio recordings, both focus groups and interviews, into word documents. Sometimes I get files that are encoded in stereo, but only have a sound in one channel. I've looked over the many brilliant classes provided by NAudio but still cannot see how I might play the right channel, or left channel, across both.

I've created a player which (largely based on yuvalnv's Practice Sharp) uses an NAudio buffer interface to stream the file and apply effects. As things stand I either have to convert the file to mono beforehand using some freeware, or listen to up to three hours of chatter in one ear. Anything that might point me in the direction would be greatly appreciated.

Coordinator
Feb 6, 2011 at 6:46 PM

what you need is a custom WaveStream (or IWaveProvider) that takes a stereo PCM input and emits mono. To select just a single channel you throw away every other sample. So for example, in the Read method, if you are asked for 100 bytes, then you read 200 bytes from the stereo source. Then assuming the incoming samples are 16 bit, you pass 2 bytes through, throw away two, and so on.

This would probably be a useful addition to the NAudio toolkit, but I'm afraid that at the moment you have to write it yourself

Mark

Mar 12, 2011 at 11:29 AM
Edited Mar 12, 2011 at 11:37 AM

It's fascinating the way the steam holds channel data. However, I haven't had much luck getting it to work. I've modified the WaveChannel32 class to include getters and setters for a channel property-,

        private int playbackChanneling;
        public int PlaybackChanneling
        {
            get { return playbackChanneling; }
            set { playbackChanneling=value; }
        }

And then butchered your read method, like so:

        /// <summary>
        /// Reads bytes from this wave stream
        /// </summary>
        /// <param name="destBuffer">The destination buffer</param>
        /// <param name="offset">Offset into the destination buffer</param>
        /// <param name="numBytes">Number of bytes read</param>
        /// <returns>Number of bytes read.</returns>
        public override int Read(byte[] destBuffer, int offset, int numBytes)
        {
            if (playbackChanneling > 0) // read double the bytes, if playing only one channel of a stereo file
                numBytes = numBytes*2;

            int bytesWritten = 0;
            // 1. fill with silence
            if (position < 0)
            {
                bytesWritten = (int)Math.Min(numBytes, 0 - position);
                for (int n = 0; n < bytesWritten; n++)
                    destBuffer[n + offset] = 0;
            }
            if (bytesWritten < numBytes)
            {
                if (sourceStream.WaveFormat.Channels == 1)
                {
                    int sourceBytesRequired = (numBytes - bytesWritten) / 4;
                    byte[] sourceBuffer = GetSourceBuffer(sourceBytesRequired);
                    int read = sourceStream.Read(sourceBuffer, 0, sourceBytesRequired);
                    MonoToStereo(destBuffer, offset + bytesWritten, sourceBuffer, read);
                    bytesWritten += (read * 4);
                }
                else
                {
                switch (playbackChanneling)
                {
                    case 0: // for stereo playback
                        {
                            int sourceBytesRequired = (numBytes - bytesWritten) / 2;
                            byte[] sourceBuffer = GetSourceBuffer(sourceBytesRequired);
                            int read = sourceStream.Read(sourceBuffer, 0, sourceBytesRequired);
                            AdjustVolume(destBuffer, offset + bytesWritten, sourceBuffer, read);
                            bytesWritten += (read * 2);
                            break;
                        }
                    case 1: // for left channel playback only
                        {
                            int sourceBytesRequired = (numBytes - bytesWritten) / 2;
                            byte[] sourceBufferTemp = GetSourceBuffer(sourceBytesRequired);
                            int read = sourceStream.Read(sourceBufferTemp, 0, sourceBytesRequired);
                           
                            byte[] sourceBuffer = new byte[sourceBufferTemp.Length/2];
                            for (int c=0; c<(sourceBufferTemp.Length/4); c++) // copy every other byte pair starting on 0
                            {
                                sourceBuffer[(c * 2)]=sourceBufferTemp[(c * 4)];
                                sourceBuffer[(c * 2) + 1]=sourceBufferTemp[(c * 4) + 1];
                            }
                           
                            AdjustVolume(destBuffer, offset + bytesWritten, sourceBuffer, read);
                            bytesWritten += (read * 2);
                            break;
                        }
                    case 2: // for right channel playback only
                        {
                            int sourceBytesRequired = (numBytes - bytesWritten) / 2;
                            byte[] sourceBufferTemp = GetSourceBuffer(sourceBytesRequired);
                            int read = sourceStream.Read(sourceBufferTemp, 0, sourceBytesRequired);

                            byte[] sourceBuffer = new byte[sourceBufferTemp.Length / 2];
                            for (int c=0; c<(sourceBufferTemp.Length/4); c++) // copy away every other byte pair starting on 2
                            {
                                sourceBuffer[(c * 2)] = sourceBufferTemp[(c * 4) + 2];
                                sourceBuffer[(c * 2) + 1] = sourceBufferTemp[(c * 4) + 3];
                            }
                           
                            AdjustVolume(destBuffer, offset + bytesWritten, sourceBuffer, read);
                            bytesWritten += (read * 2);
                            break;
                        }
                    }
                }
            }
            // 3. Fill out with zeroes
            if (PadWithZeroes && bytesWritten < numBytes)
            {
                Array.Clear(destBuffer, offset + bytesWritten, numBytes - bytesWritten);
                bytesWritten = numBytes;
            }
            position += bytesWritten;
            return bytesWritten;
        }

 

Also added a constructor to set playbackChanneling to 0 by default. My application, using the modified class, works with playbackChanneling set to 0 (obviously, as that's what you've provided). But not 1 or 2. Any thoughts?

Coordinator
Mar 14, 2011 at 10:55 AM

if you look in the latest source code you will see I have added a StereoToMonoWaveProvider, which should give you an idea of how to get what you are after

Mark

Mar 15, 2011 at 5:11 PM

Thanks! That will learn me to check for updates.

After many hours pouring (and it shouldn't have taken that long, frankly, you've made it very clear) I've managed it.  NAudio is fantastic.

Coordinator
Mar 16, 2011 at 5:12 PM

yeah, lots of people ask about this, and your question nudged me to do it. I meant to blog about it, but haven't got round to it yet.

Mark