/// <summary>
        /// Reads a single sample from a wav stream.
        /// </summary>
        /// <param name="wavStream">Wave Stream.</param>
        /// <param name="wavInfo">Wave Info.</param>
        /// <returns>Sample read from stream.</returns>
        private static double ReadSample(Stream wavStream, WavAudioInfo wavInfo)
        {
            double sample;

            switch (wavInfo.BitsPerSample)
            {
            case 8:
                sample = wavStream.ReadByte() / 128.0;

                int remainingChannels8 = wavInfo.Channels - 1;
                wavStream.Skip(remainingChannels8);

                break;

            case 16:
                var buffer = new byte[2];
                wavStream.Read(buffer, 0, 2);
                var value = BitConverter.ToInt16(buffer, 0);

                sample = value / 32768.0;

                // two bytes per sample
                int remainingChannels16 = wavInfo.Channels - 1;
                wavStream.Skip(remainingChannels16 * 2);

                break;

            default:
                throw new NotSupportedException("Bits per sample other than 8 and 16.");
            }

            return(sample);
        }
        /// <summary>
        /// Get samples from the wav stream. Start from <paramref name="firstSampleIndex"/> (zero indexed),
        /// and read <paramref name="numberOfSamplesToRead"/>.
        /// </summary>
        /// <param name="firstSampleIndex">
        /// The first Sample Index.
        /// </param>
        /// <param name="numberOfSamplesToRead">
        /// The number Of Samples To Read.
        /// </param>
        /// <returns>
        /// <paramref name="numberOfSamplesToRead"/> starting from <paramref name="firstSampleIndex"/>.
        /// </returns>
        public double[] GetSamples(long firstSampleIndex, long numberOfSamplesToRead)
        {
            var dataChunk = this.GetDataChunk;

            var sampleLength = (int)numberOfSamplesToRead;
            var samples      = new double[sampleLength];

            WavAudioInfo wavInfo = this.AudioInfo;

            using (Stream fileStream = this.GetFileStream)
            {
                // set stream to correct location
                fileStream.Position = GetCalculatedPosition(wavInfo, dataChunk.Position, firstSampleIndex);

                for (var index = 0; index < sampleLength; index++)
                {
                    samples[index] = ReadSample(fileStream, wavInfo);
                }
            }

            // if samples is odd, padding of 1 byte
            ////if (sampleLength % 2 != 0)
            ////{
            ////    fileStream.Position++;
            ////}

            return(samples);
        }
        /// <summary>
        /// Get position (number of bytes from start of file) in wav stream of <paramref name="sampleIndex"/>.
        /// </summary>
        /// <param name="wavInfo">Wave Info.</param>
        /// <param name="dataChunkPosition">Data Chunk Position.</param>
        /// <param name="sampleIndex">Index of Sample.</param>
        /// <returns>Position of <paramref name="sampleIndex"/>.</returns>
        private static long GetCalculatedPosition(WavAudioInfo wavInfo, long dataChunkPosition, long sampleIndex)
        {
            // each sample takes wavInfo.BytesPerSample, want to skip sampleIndex samples.
            var amountToSkip = wavInfo.BytesPerSample * sampleIndex;

            // position =  start of datachunk + start of sample
            var newPosition = dataChunkPosition + amountToSkip;

            return(newPosition);
        }
        /// <summary>
        /// Get samples per channel. The first channel (mono if it is the only channel) is usually the left.
        /// </summary>
        /// <param name="wavSource">
        /// The wav Source.
        /// </param>
        /// <param name="wavInfo">
        /// The wav Info.
        /// </param>
        /// <param name="duration">
        /// The duration.
        /// </param>
        /// <exception cref="NotSupportedException">
        /// Bits per sample other than 8 and 16.
        /// </exception>
        /// <returns>
        /// Samples divided into channels.
        /// </returns>
        public static double[][] SplitChannels(this Stream wavSource, WavAudioInfo wavInfo, TimeSpan?duration)
        {
            short numberOfChannels = wavInfo.Channels;
            short bitsPerSample    = wavInfo.BitsPerSample;
            long  numberOfFrames   = wavInfo.Frames;

            var sampleLength = (int)wavInfo.Frames;
            var samples      = new double[sampleLength][];

            if (duration.HasValue)
            {
                var    sampleRate    = (double)wavInfo.SampleRate;
                double givenDuration = duration.Value.TotalSeconds;

                var framesForDuration = (long)Math.Floor(sampleRate * givenDuration);
                numberOfFrames = Math.Min(framesForDuration, numberOfFrames);
            }

            for (int channel = 0; channel < numberOfChannels; channel++)
            {
                samples[channel] = new double[numberOfFrames];
            }

            for (int frame = 0; frame < numberOfFrames; frame++)
            {
                for (int channel = 0; channel < numberOfChannels; channel++)
                {
                    switch (bitsPerSample)
                    {
                    case 8:
                        samples[channel][frame] = wavSource.ReadByte() / 128.0;

                        break;

                    case 16:
                        var buffer = new byte[2];
                        wavSource.Read(buffer, 0, 2);
                        var value = BitConverter.ToInt16(buffer, 0);
                        samples[channel][frame] = value / 32768.0;

                        break;

                    default:
                        throw new NotSupportedException("Bits per sample other than 8 and 16.");
                    }
                }
            }

            return(samples);
        }
        /// <summary>
        /// Get a single sample. Do not use this method in a loop.
        /// </summary>
        /// <param name="sampleIndex">
        /// The sample index (zero-based).
        /// </param>
        /// <returns>
        /// Read sample.
        /// </returns>
        public double GetSample(long sampleIndex)
        {
            WavAudioInfo wavInfo   = this.AudioInfo;
            var          dataChunk = this.GetDataChunk;

            using (Stream fileStream = this.GetFileStream)
            {
                // set stream to correct location
                fileStream.Position = GetCalculatedPosition(wavInfo, dataChunk.Position, sampleIndex);

                double sample = ReadSample(fileStream, wavInfo);

                return(sample);
            }
        }
        /// <summary>
        /// Read first channel samples as doubles.
        /// Set the SampleStream position before using this method.
        /// </summary>
        /// <param name="wavSource">
        /// The wav source.
        /// </param>
        /// <param name="wavInfo">
        /// The wav info.
        /// </param>
        /// <returns>
        /// Samples of first channel.
        /// </returns>
        /// <exception cref="NotSupportedException">
        /// </exception>
        public static double[] ReadSamples(this Stream wavSource, WavAudioInfo wavInfo)
        {
            var sampleLength = (int)wavInfo.Frames;
            var samples      = new double[sampleLength];

            switch (wavInfo.BitsPerSample)
            {
            case 8:
                for (int i = 0; i < sampleLength; i++)
                {
                    samples[i] = wavSource.ReadByte() / 128.0;

                    int remainingChannels = wavInfo.Channels - 1;
                    wavSource.Skip(remainingChannels);
                }

                break;

            case 16:
                for (int i = 0; i < sampleLength; i++)
                {
                    var buffer = new byte[2];
                    wavSource.Read(buffer, 0, 2);
                    var value = BitConverter.ToInt16(buffer, 0);
                    samples[i] = value / 32768.0;

                    // two bytes per sample
                    int remainingChannels = wavInfo.Channels - 1;
                    wavSource.Skip(remainingChannels * 2);
                }

                break;

            default:
                throw new NotSupportedException("Bits per sample other than 8 and 16.");
            }

            // if samples is odd, padding of 1 byte
            if (sampleLength % 2 != 0)
            {
                wavSource.Position++;
            }

            return(samples);
        }
Exemple #7
0
        /// <summary>
        /// Get samples from the wav stream. Start from <paramref name="firstSampleIndex"/> (zero indexed),
        /// and read <paramref name="numberOfSamplesToRead"/>.
        /// </summary>
        /// <param name="wavStream">
        /// The wav Stream.
        /// </param>
        /// <param name="dataChunk">
        /// The data Chunk.
        /// </param>
        /// <param name="wavInfo">
        /// The wav Info.
        /// </param>
        /// <param name="firstSampleIndex">
        /// The first Sample Index.
        /// </param>
        /// <param name="numberOfSamplesToRead">
        /// The number Of Samples To Read.
        /// </param>
        /// <returns>
        /// <paramref name="numberOfSamplesToRead"/> starting from <paramref name="firstSampleIndex"/>.
        /// </returns>
        public static double[] GetSamples(Stream wavStream, WavChunk dataChunk, WavAudioInfo wavInfo, long firstSampleIndex, long numberOfSamplesToRead)
        {
            var samples = new double[numberOfSamplesToRead];

            // set stream to correct location
            wavStream.Position = GetCalculatedPosition(wavInfo, dataChunk.Position, firstSampleIndex);

            for (long index = 0; index < numberOfSamplesToRead; index++)
            {
                samples[index] = ReadSample(wavStream, wavInfo);
            }

            // if samples is odd, padding of 1 byte
            ////if (sampleLength % 2 != 0)
            ////{
            ////    fileStream.Position++;
            ////}

            return(samples);
        }
        /// <summary>
        /// Reads the wave header and stores info in WavAudioInfo.
        /// </summary>
        /// <param name="stream">
        /// The stream.
        /// </param>
        /// <exception cref="InvalidDataException">
        /// <c>InvalidDataException</c>.
        /// </exception>
        private void ReadHeader(Stream stream)
        {
            var wavInfo = new WavAudioInfo();

            const string ChunkIdRiff   = "RIFF";
            const string ChunkIdData   = "data";
            const string ChunkIdFormat = "fmt ";

            int chunkDataSizeData = 0;

            var chunks = new List <WavChunk>();

            using (var reader = new BinaryReader(new NonClosingStreamWrapper(stream)))
            {
                long streamLength   = reader.BaseStream.Length;
                long streamPosition = reader.BaseStream.Position;

                try
                {
                    while (streamPosition < streamLength)
                    {
                        // deal with next chunk
                        var chunkName = new string(reader.ReadChars(4));
                        int chunkSize = reader.ReadInt32();
                        chunks.Add(new WavChunk {
                            Position = reader.BaseStream.Position, Name = chunkName, Length = chunkSize
                        });

                        switch (chunkName)
                        {
                        case ChunkIdRiff:
                            ReadRiffChunk(reader);
                            break;

                        case ChunkIdFormat:
                            ReadFmtChunk(reader, wavInfo, chunkSize);
                            break;

                        case ChunkIdData:

                            // chunk name: "data"
                            // chunk size: size of audio data.
                            chunkDataSizeData = chunkSize;

                            // skip over data chunk
                            reader.Skip(chunkSize);
                            break;

                        default:
                            // skip over chunk
                            reader.Skip(chunkSize);
                            break;
                        }

                        streamPosition = reader.BaseStream.Position;
                    }
                }
                catch (EndOfStreamException)
                {
                }

                // make sure riff,format and data chunks were found
                if (chunks.Where(c => c.Name == ChunkIdRiff || c.Name == ChunkIdFormat || c.Name == ChunkIdData).Count() != 3)
                {
                    throw new InvalidDataException("Did not find one or more required chunks.");
                }

                // frames and sample count are the same thing?
                // they should be equal
                long calc1 = chunkDataSizeData * (long)WavUtils.BitsPerByte;
                long calc2 = wavInfo.Channels * (long)wavInfo.BitsPerSample;
                long calc3 = calc1 / calc2;

                wavInfo.Frames = calc3;

                double sampleCount = chunkDataSizeData / (double)wavInfo.BytesPerSample;
                wavInfo.Duration = TimeSpan.FromSeconds(sampleCount / wavInfo.SampleRate);

                // verify frame/sample count
                if (wavInfo.Frames != sampleCount)
                {
                    throw new InvalidDataException(string.Format(ErrorMsg, wavInfo.Frames, sampleCount, "frames and samples"));
                }
            }

            this.AudioInfo = wavInfo;

            this.Chunks = chunks;

            this.SampleStream = stream;
        }
        /// <summary>
        /// Read format chunk. Assumes <paramref name="reader"/> is correctly positioned.
        /// </summary>
        /// <param name="reader">
        /// Binary Reader.
        /// </param>
        /// <param name="wavInfo">
        /// Wav audio info.
        /// </param>
        /// <param name="chunkSize">
        /// The chunk Size.
        /// </param>
        /// <exception cref="InvalidDataException">
        /// <c>InvalidDataException</c>.
        /// </exception>
        private static void ReadFmtChunk(BinaryReader reader, WavAudioInfo wavInfo, int chunkSize)
        {
            /*
             * chunk name: "fmt " - the chunk ID string ends with the space character (0x20).
             * chunk size: size of format data (16 bytes for compression code 1).
             */

            // the size of the standard wave format data (16 bytes)
            // plus the size of any extra format bytes needed for
            // the specific Wave format, if it does not contain
            // uncompressed PCM data.
            const int ChunkDataSizeFormat     = 16;
            int       chunkDataSizeFormatRead = chunkSize;

            // The first word of format data specifies
            // the type of compression used on the Wave
            // data included in the Wave chunk found in this "RIFF" chunk.
            // 1 for PCM/uncompressed
            const short CompressionCode = 1;

            wavInfo.CompressionCode = reader.ReadInt16();

            if (wavInfo.CompressionCode != CompressionCode)
            {
                throw new InvalidDataException(string.Format(ErrorMsg, CompressionCode, wavInfo.CompressionCode, "compression code"));
            }

            // how many separate audio signals that are
            // encoded in the wave data chunk. A value of
            // 1 means a mono signal, a value of 2 means a stereo signal.
            wavInfo.Channels = reader.ReadInt16();

            // The number of sample slices per second.
            // This value is unaffected by the number of channels.
            wavInfo.SampleRate = reader.ReadInt32();

            // Average Bytes Per Second
            // This value indicates how many bytes of wave
            // data must be streamed to a D/A converter per
            // second in order to play the wave file. This
            // information is useful when determining if
            // data can be streamed from the source fast
            // enough to keep up with playback. This value
            // can be easily calculated with the formula:
            // AvgBytesPerSec = SampleRate * BlockAlign
            wavInfo.BytesPerSecond = reader.ReadInt32();

            // Block Align / bytes per sample. (frame)
            // The number of bytes per sample slice. This value
            // is not affected by the number of channels and can be
            // calculated with the formula:
            // BlockAlign = SignificantBitsPerSample / 8 * NumChannels
            // or
            // short frameSize = (short)(channels * ((wavInfo.BitsPerSample + 7) / 8));
            wavInfo.BytesPerSample = reader.ReadInt16();

            // Significant Bits Per Sample
            // This value specifies the number of bits used to define each sample.
            // This value is usually 8, 16, 24 or 32. If the number of bits is not
            // byte aligned (a multiple of 8) then the number of bytes used per sample
            // is rounded up to the nearest byte size and the unused bytes are set to 0 and ignored.
            wavInfo.BitsPerSample = reader.ReadInt16();

            var remainder = WavUtils.MaxBitsPerSample % wavInfo.BitsPerSample;

            if (remainder != 0)
            {
                throw new InvalidDataException(
                          "The input stream uses an unhandled SignificantBitsPerSample parameter. " +
                          string.Format(ErrorMsg, "0 (" + WavUtils.MaxBitsPerSample + ")", remainder + "(" + wavInfo.BitsPerSample + ")", "bits per sample"));
            }

            // extra format bytes
            reader.ReadChars(chunkDataSizeFormatRead - ChunkDataSizeFormat);

            // verify bytes per second
            int checkBytesPerSecond = wavInfo.SampleRate * wavInfo.BytesPerSample;

            if (wavInfo.BytesPerSecond != checkBytesPerSecond)
            {
                throw new InvalidDataException(string.Format(ErrorMsg, wavInfo.BytesPerSecond, checkBytesPerSecond, "bytes per second check"));
            }

            // verify bytes per sample
            int checkBytesPerSample = wavInfo.BitsPerSample / WavUtils.BitsPerByte * wavInfo.Channels;

            if (wavInfo.BytesPerSample != checkBytesPerSample)
            {
                throw new InvalidDataException(string.Format(ErrorMsg, wavInfo.BytesPerSample, checkBytesPerSample, "bytes per sample check"));
            }
        }