/// <summary>Reads a new WAVE format section from the specified stream.</summary> /// <param name="preRead">Pre-parsed RIFF chunk header.</param> /// <param name="source">Source stream to read data from.</param> /// <param name="waveFormat">Format of the data section to be parsed.</param> /// <exception cref="InvalidOperationException">WAVE format or extra parameters section too small, wave file corrupted.</exception> public WaveDataChunk(RiffChunk preRead, Stream source, WaveFormatChunk waveFormat) : base(preRead, RiffTypeID) { m_waveFormat = waveFormat; m_sampleBlocks = new List<LittleBinaryValue[]>(); int blockSize = waveFormat.BlockAlignment; int sampleSize = waveFormat.BitsPerSample / 8; byte[] buffer = new byte[blockSize]; int channels = waveFormat.Channels; TypeCode sampleTypeCode = m_waveFormat.GetSampleTypeCode(); LittleBinaryValue[] sampleBlock; int bytesRead = source.Read(buffer, 0, blockSize); while (bytesRead == blockSize) { // Create a new sample block, one little-endian formatted binary sample value for each channel sampleBlock = new LittleBinaryValue[channels]; for (int x = 0; x < channels; x++) { sampleBlock[x] = new LittleBinaryValue(sampleTypeCode, buffer, x * sampleSize, sampleSize); } m_sampleBlocks.Add(sampleBlock); bytesRead = source.Read(buffer, 0, blockSize); } }
/// <summary>Reads a new WAVE format section from the specified stream.</summary> /// <param name="preRead">Pre-parsed <see cref="RiffChunk"/> header.</param> /// <param name="source">Source stream to read data from.</param> /// <param name="waveFormat">Format of the data section to be parsed.</param> /// <exception cref="InvalidOperationException">WAVE format or extra parameters section too small, wave file corrupted.</exception> public WaveDataChunk(RiffChunk preRead, Stream source, WaveFormatChunk waveFormat) : base(preRead, RiffTypeID) { m_waveFormat = waveFormat; m_sampleBlocks = new List <LittleBinaryValue[]>(); m_chunkSize = -1; int blockSize = waveFormat.BlockAlignment; int sampleSize = waveFormat.BitsPerSample / 8; int channels = waveFormat.Channels; TypeCode sampleTypeCode = m_waveFormat.GetSampleTypeCode(); LittleBinaryValue[] sampleBlock; byte[] buffer = new byte[blockSize]; int bytesRead = source.Read(buffer, 0, blockSize); while (bytesRead == blockSize) { // Create a new sample block, one little-endian formatted binary sample value for each channel sampleBlock = new LittleBinaryValue[channels]; for (int x = 0; x < channels; x++) { sampleBlock[x] = new LittleBinaryValue(sampleTypeCode, buffer, x * sampleSize, sampleSize); } m_sampleBlocks.Add(sampleBlock); bytesRead = source.Read(buffer, 0, blockSize); } }
private void WaveIn_DataAvailable(object sender, WaveInEventArgs waveInEventArgs) { int index = 0; int numSamples = waveInEventArgs.BytesRecorded / m_sampleSize / m_channels; List <IMeasurement> measurements = new List <IMeasurement>(); long sampleTime; LittleBinaryValue sampleValue; // Get the timestamp for the first recorded sample if (Interlocked.Read(ref m_lastSampleTime) == 0L) { Interlocked.Exchange(ref m_lastSampleTime, DateTime.UtcNow.Ticks - (m_ticksPerSample * numSamples)); } // Get the timestamp for the first sample in this block of data sampleTime = Interlocked.Read(ref m_lastSampleTime) + m_ticksPerSample; // Parse each recorded sample in this block of data for (int sampleIndex = 0; sampleIndex < numSamples; sampleIndex++) { // Parse one value per channel per sample for (int channelIndex = 0; channelIndex < m_channels; channelIndex++) { if (channelIndex < OutputMeasurements.Length) { // Create a measurement for this value sampleValue = new LittleBinaryValue(m_sampleTypeCode, waveInEventArgs.Buffer, index + (channelIndex * m_sampleSize), m_sampleSize); measurements.Add(Measurement.Clone(OutputMeasurements[channelIndex], ConvertToPCM16(sampleValue.ConvertToType(TypeCode.Double).ToDouble()), sampleTime)); } } // Update the last sample time Interlocked.Exchange(ref m_lastSampleTime, sampleTime); // Get the timestamp for the next sample in this block of data sampleTime += m_ticksPerSample; // Update the index to the next sample in this block of data index += m_sampleSize * m_channels; } // Publish streaming microphone data OnNewMeasurements(measurements); m_samplesProcessed += numSamples; }
/// <summary> /// Reads and returns the next sample from the stream. /// </summary> /// <returns> /// The next sample from the stream. The sample is returned as /// an array of <see cref="LittleBinaryValue"/>s. Each /// LittleBinaryValue represents a different channel. /// A return value of null indicates the end of stream has been reached. /// </returns> public LittleBinaryValue[] GetNextSample() { byte[] buffer = new byte[m_blockSize]; int bytesRead = m_waveStream.Read(buffer, 0, m_blockSize); LittleBinaryValue[] sample; if (bytesRead == m_blockSize) { sample = new LittleBinaryValue[m_channels]; for (int i = 0; i < m_channels; i++) { sample[i] = new LittleBinaryValue(m_sampleType, buffer, i * m_sampleSize, m_sampleSize); } return(sample); } return(null); }
/// <summary> /// Add the sample to the wave file. /// </summary> /// <param name="sample">Sample to add to the wave file.</param> /// <remarks> /// <para> /// Sample is applied to all channels and cast to the appropriate size. Sample should be scaled /// by <see cref="AmplitudeScalar"/> for integer based wave file formats to make sure sample will /// fit into <see cref="BitsPerSample"/> defined by wave file. /// </para> /// <para> /// If you have samples to apply to individual channels (e.g., for a stereo format), use the /// <see cref="AddSamples"/> method instead. /// </para> /// </remarks> public void AddSample(double sample) { LittleBinaryValue[] binaryValues; // Create a new sample block for wave file binaryValues = new LittleBinaryValue[m_waveFormat.Channels]; // Iterate through each channel in WaveFile applying same value to each channel for (int x = 0; x < m_waveFormat.Channels; x++) { // Cast sample value to appropriate data type based on bit size binaryValues[x] = m_waveFormat.CastSample(sample); } // Add sample block to WaveFile AddSampleBlock(binaryValues); }
// Data type independent binary sample combination private static LittleBinaryValue CombineBinarySamples(int bitsPerSample, LittleBinaryValue value1, LittleBinaryValue value2, double value2Volume) { // Algorithm: // 1) Convert value into double data type to prevent arithmetic overflow. Note that you // cannot directly cast to a double as there are only enough bytes in the binary // value to cast it to its native data type. // 2) Add both data samples together, appling volume adjustment to second value to make // sure values get desired amplitude weight so as to not adversely affect the volume // of the resultant sample. // 3) Convert arithmetic result back to proper data type for wave file and return. LittleBinaryValue result; // Must handle 24-bit as a special case since there is no 24-bit type code in .NET if (bitsPerSample == 24) { Int24 _value1 = value1; Int24 _value2 = value1; result = (double)_value1 + (double)_value2 * value2Volume; } else { result = (double)value1.ConvertToType(TypeCode.Double) + (double)value2.ConvertToType(TypeCode.Double) * value2Volume; } return result.ConvertToType(value1.TypeCode); }
/// <summary> /// Performs a deep clone of all the channel samples in a sample block. /// </summary> /// <param name="samples">Sample block to clone.</param> /// <returns>A deep clone of all the channel samples in a sample block.</returns> public static LittleBinaryValue[] CloneSampleBlock(LittleBinaryValue[] samples) { LittleBinaryValue[] clonedSamples = new LittleBinaryValue[samples.Length]; byte[] copiedBytes; // Perform a deep clone of source samples for (int x = 0; x < samples.Length; x++) { copiedBytes = new byte[samples[x].Buffer.Length]; Buffer.BlockCopy(samples[x].Buffer, 0, copiedBytes, 0, copiedBytes.Length); clonedSamples[x] = new LittleBinaryValue(samples[x].TypeCode, copiedBytes); } return clonedSamples; }
/// <summary> /// Combines wave files together, all starting at the same time, into a single file. /// This has the effect of playing two sound tracks simultaneously. /// </summary> /// <param name="waveFiles">Wave files to combine</param> /// <param name="volumes">Volume for each wave file (0.0 to 1.0)</param> /// <returns>Combined wave files.</returns> /// <remarks> /// <para> /// Cumulatively, volumes cannot exceed 1.0 - these volumes represent a fractional percentage /// of volume to be applied to each wave file. /// </para> /// <para> /// Resulting sounds will overlap; no truncation is performed. Final wave file length will equal length of /// longest source file. /// </para> /// <para> /// Combining sounds files with non-PCM based audio formats will have unexpected results. /// </para> /// </remarks> public static WaveFile Combine(WaveFile[] waveFiles, double[] volumes) { if (waveFiles.Length > 1) { // Validate volumes if (volumes.Length != waveFiles.Length) throw new ArgumentOutOfRangeException("volumes", "There must be one volume per each wave file"); if (volumes.Sum() > 1.0D) throw new ArgumentOutOfRangeException("volumes", "Cumulatively, volumes cannot exceed 1.0"); // Deep clone first wave file - this will become the base of the new combined wave file WaveFile next, source = waveFiles[0].Clone(); int maxLength, nextLength, sourceLength = source.SampleBlocks.Count; // Validate compatibility of wave files to be combined for (int x = 1; x < waveFiles.Length; x++) { next = waveFiles[x]; if (source.AudioFormat != next.AudioFormat || source.SampleRate != next.SampleRate || source.BitsPerSample != next.BitsPerSample || source.Channels != next.Channels) throw new ArgumentException("All wave files to be combined must have the same audio format, sample rate, bits per sample and number of channels"); } // Apply volume adjustment to source file for (int x = 0; x < sourceLength; x++) { for (int y = 0; y < source.Channels; y++) { // Apply volume adjustment to source source.SampleBlocks[x][y] = CombineBinarySamples(source.BitsPerSample, 0, source.SampleBlocks[x][y], volumes[0]); } } // Combine subsequent wave files for (int x = 1; x < waveFiles.Length; x++) { next = waveFiles[x]; nextLength = next.SampleBlocks.Count; maxLength = sourceLength > nextLength ? sourceLength : nextLength; for (int y = 0; y < maxLength; y++) { if (y < sourceLength && y < nextLength) { for (int z = 0; z < source.Channels; z++) { // Combine each data channel from the source and current wave files source.SampleBlocks[y][z] = CombineBinarySamples(source.BitsPerSample, source.SampleBlocks[y][z], next.SampleBlocks[y][z], volumes[x]); } } else { // Extend source if necessary - note that extended samples still need to be equalized if (nextLength > sourceLength) { LittleBinaryValue[] samples = new LittleBinaryValue[source.Channels]; for (int z = 0; z < source.Channels; z++) { // Combine extended samples with "0" from source to maintain amplitude equalization samples[z] = CombineBinarySamples(source.BitsPerSample, 0, next.SampleBlocks[y][z], volumes[x]); } source.SampleBlocks.Add(samples); } else break; } } } return source; } else throw new ArgumentException("You must provide at least two wave files to combine.", "waveFiles"); }
/// <summary> /// Adds a series of samples, one per channel, to the wave file. /// </summary> /// <param name="samples">Samples to add to the wave file.</param> /// <remarks> /// <para> /// You need to pass in one sample for each defined channel (e.g., if wave is configured for stereo /// you will need to pass in two parameters). /// </para> /// Each sample will be cast to the appropriate size. Samples should be scaled by <see cref="AmplitudeScalar"/> /// for integer based wave file formats to make sure samples will fit into <see cref="BitsPerSample"/> defined /// by wave file. /// </remarks> public void AddSamples(params double[] samples) { // Validate number of samples if (samples.Length != m_waveFormat.Channels) throw new ArgumentOutOfRangeException("samples", "You must provide one sample for each defined channel"); LittleBinaryValue[] binaryValues; // Create a new sample block for wave file binaryValues = new LittleBinaryValue[samples.Length]; // Iterate through each channel in provided data samples for (int x = 0; x < samples.Length; x++) { // Cast sample value to appropriate data type based on bit size binaryValues[x] = m_waveFormat.CastSample(samples[x]); } // Add sample block to WaveFile AddSampleBlock(binaryValues); }
private void WaveIn_DataAvailable(object sender, WaveInEventArgs waveInEventArgs) { int index = 0; int numSamples = waveInEventArgs.BytesRecorded / m_sampleSize / m_channels; List<IMeasurement> measurements = new List<IMeasurement>(); long sampleTime; LittleBinaryValue sampleValue; // Get the timestamp for the first recorded sample if (Interlocked.Read(ref m_lastSampleTime) == 0L) Interlocked.Exchange(ref m_lastSampleTime, DateTime.UtcNow.Ticks - (m_ticksPerSample * numSamples)); // Get the timestamp for the first sample in this block of data sampleTime = Interlocked.Read(ref m_lastSampleTime) + m_ticksPerSample; // Parse each recorded sample in this block of data for (int sampleIndex = 0; sampleIndex < numSamples; sampleIndex++) { // Parse one value per channel per sample for (int channelIndex = 0; channelIndex < m_channels; channelIndex++) { if (channelIndex < OutputMeasurements.Length) { // Create a measurement for this value sampleValue = new LittleBinaryValue(m_sampleTypeCode, waveInEventArgs.Buffer, index + (channelIndex * m_sampleSize), m_sampleSize); measurements.Add(Measurement.Clone(OutputMeasurements[channelIndex], ConvertToPCM16(sampleValue.ConvertToType(TypeCode.Double).ToDouble()), sampleTime)); } } // Update the last sample time Interlocked.Exchange(ref m_lastSampleTime, sampleTime); // Get the timestamp for the next sample in this block of data sampleTime += m_ticksPerSample; // Update the index to the next sample in this block of data index += m_sampleSize * m_channels; } // Publish streaming microphone data OnNewMeasurements(measurements); m_samplesProcessed += numSamples; }
/// <summary> /// Reads and returns the next sample from the stream. /// </summary> /// <returns> /// The next sample from the stream. The sample is returned as /// an array of <see cref="LittleBinaryValue"/>s. Each /// LittleBinaryValue represents a different channel. /// A return value of null indicates the end of stream has been reached. /// </returns> public LittleBinaryValue[] GetNextSample() { byte[] buffer = new byte[m_blockSize]; int bytesRead = m_waveStream.Read(buffer, 0, m_blockSize); LittleBinaryValue[] sample; if (bytesRead == m_blockSize) { sample = new LittleBinaryValue[m_channels]; for (int i = 0; i < m_channels; i++) { sample[i] = new LittleBinaryValue(m_sampleType, buffer, i * m_sampleSize, m_sampleSize); } return sample; } return null; }