/// <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); }
/// <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")); } }