/// <summary> /// The heart of Practice# audio processing: /// 1. Reads chunks of uncompressed samples from the input file /// 2. Processes the samples using SoundTouch /// 3. Receive process samples from SoundTouch /// 4. Play the processed samples with NAudio /// /// Also handles logic required for dynamically changing values on-the-fly of: Volume, Loop, Cue, Current Play Position. /// </summary> private void ProcessAudio() { m_stopWorker = false; m_logger.Debug("ProcessAudio() started"); #region Setup WaveFormat format = m_waveChannel.WaveFormat; int bufferSecondLength = format.SampleRate * format.Channels; byte[] inputBuffer = new byte[BufferSamples * sizeof(float)]; byte[] soundTouchOutBuffer = new byte[BufferSamples * sizeof(float)]; ByteAndFloatsConverter convertInputBuffer = new ByteAndFloatsConverter { Bytes = inputBuffer }; ByteAndFloatsConverter convertOutputBuffer = new ByteAndFloatsConverter { Bytes = soundTouchOutBuffer }; uint outBufferSizeFloats = (uint)convertOutputBuffer.Bytes.Length / (uint)(sizeof(float) * format.Channels); int bytesRead; int floatsRead; uint samplesProcessed = 0; int bufferIndex = 0; TimeSpan actualEndMarker = TimeSpan.Zero; bool loop; #endregion while (!m_stopWorker && m_waveChannel.Position < m_waveChannel.Length) { lock (PropertiesLock) { m_waveChannel.Volume = m_volume; } #region Read samples from file // Change current play position lock (CurrentPlayTimeLock) { if (m_newPlayTimeRequested) { m_waveChannel.CurrentTime = m_newPlayTime; m_newPlayTimeRequested = false; } } // *** Read one chunk from input file *** bytesRead = m_waveChannel.Read(convertInputBuffer.Bytes, 0, convertInputBuffer.Bytes.Length); // ************************************** floatsRead = bytesRead / ((sizeof(float)) * format.Channels); #endregion #region Apply DSP Effects (Equalizer, Vocal Removal, etc.) ApplyDSPEffects(convertInputBuffer.Floats, floatsRead); #endregion #region Apply Time Stretch Profile lock (PropertiesLock) { if (m_timeStretchProfileChanged) { ApplySoundTouchTimeStretchProfile(); m_timeStretchProfileChanged = false; } } #endregion actualEndMarker = this.EndMarker; loop = this.Loop; if (!loop || actualEndMarker == TimeSpan.Zero) actualEndMarker = m_waveChannel.TotalTime; if (m_waveChannel.CurrentTime > actualEndMarker) { #region Flush left over samples // ** End marker reached ** // Now the input buffer is processed, 'flush' some last samples that are // hiding in the SoundTouch's internal processing pipeline. m_soundTouchSharp.Clear(); m_inputProvider.Flush(); m_waveChannel.Flush(); if (!m_stopWorker) { while (!m_stopWorker && samplesProcessed != 0) { SetSoundSharpValues(); samplesProcessed = m_soundTouchSharp.ReceiveSamples(convertOutputBuffer.Floats, outBufferSizeFloats); if (samplesProcessed > 0) { TimeSpan currentBufferTime = m_waveChannel.CurrentTime; m_inputProvider.AddSamples(convertOutputBuffer.Bytes, 0, (int)samplesProcessed * sizeof(float) * format.Channels, currentBufferTime); } samplesProcessed = m_soundTouchSharp.ReceiveSamples(convertOutputBuffer.Floats, outBufferSizeFloats); } } #endregion #region Handle Loop & Cue loop = this.Loop; if (loop) { m_waveChannel.CurrentTime = this.StartMarker; WaitForCue(); } else { break; } #endregion } #region Put samples in SoundTouch SetSoundSharpValues(); // *** Put samples in SoundTouch *** m_soundTouchSharp.PutSamples(convertInputBuffer.Floats, (uint)floatsRead); // ********************************************************************** #endregion #region Receive & Play Samples // Receive samples from SoundTouch do { // *** Receive samples back from SoundTouch *** // *** This is where Time Stretching and Pitch Changing are actually done ********* samplesProcessed = m_soundTouchSharp.ReceiveSamples(convertOutputBuffer.Floats, outBufferSizeFloats); // ********************************************************************** if (samplesProcessed > 0) { TimeSpan currentBufferTime = m_waveChannel.CurrentTime; // ** Play samples that came out of SoundTouch by adding them to AdvancedBufferedWaveProvider - the buffered player m_inputProvider.AddSamples(convertOutputBuffer.Bytes, 0, (int)samplesProcessed * sizeof(float) * format.Channels, currentBufferTime); // ********************************************************************** // Wait for queue to free up - only then add continue reading from the file // >> Note: when paused, loop runs infinitely while (!m_stopWorker && m_inputProvider.GetQueueCount() > BusyQueuedBuffersThreshold) { Thread.Sleep(10); } bufferIndex++; } } while (!m_stopWorker && samplesProcessed != 0); #endregion } // while #region Stop PlayBack m_logger.Debug("ProcessAudio() finished - stop playback"); m_waveOutDevice.Stop(); // Stop listening to PlayPositionChanged events m_inputProvider.PlayPositionChanged -= new EventHandler(inputProvider_PlayPositionChanged); // Fix to current play time not finishing up at end marker (Wave channel uses positions) if (!m_stopWorker && CurrentPlayTime < actualEndMarker) { lock (CurrentPlayTimeLock) { m_currentPlayTime = actualEndMarker; } } // Clear left over buffers m_soundTouchSharp.Clear(); // Playback status changed to -> Stopped ChangeStatus(Statuses.Stopped); #endregion }
/// <summary> /// The heart of Practice# audio processing: /// 1. Reads chunks of uncompressed samples from the input file /// 2. Processes the samples using SoundTouch /// 3. Receive process samples from SoundTouch /// 4. Play the processed samples with NAudio /// /// Also handles logic required for dynamically changing values on-the-fly of: Volume, Loop, Cue, Current Play Position. /// </summary> private void ProcessAudio() { m_stopWorker = false; m_logger.Debug("ProcessAudio() started"); #region Setup WaveFormat format = m_waveChannel.WaveFormat; int bufferSecondLength = format.SampleRate * format.Channels; byte[] inputBuffer = new byte[BufferSamples * sizeof(float)]; byte[] soundTouchOutBuffer = new byte[BufferSamples * sizeof(float)]; ByteAndFloatsConverter convertInputBuffer = new ByteAndFloatsConverter { Bytes = inputBuffer }; ByteAndFloatsConverter convertOutputBuffer = new ByteAndFloatsConverter { Bytes = soundTouchOutBuffer }; uint outBufferSizeFloats = (uint)convertOutputBuffer.Bytes.Length / (uint)(sizeof(float) * format.Channels); int bytesRead; int floatsRead; uint samplesProcessed = 0; int bufferIndex = 0; TimeSpan actualEndMarker = TimeSpan.Zero; bool loop; #endregion while (!m_stopWorker && m_waveChannel.Position < m_waveChannel.Length) { lock (PropertiesLock) { m_waveChannel.Volume = m_volume; } #region Read samples from file // Change current play position lock (CurrentPlayTimeLock) { if (m_newPlayTimeRequested) { m_waveChannel.CurrentTime = m_newPlayTime; m_newPlayTimeRequested = false; } } // *** Read one chunk from input file *** bytesRead = m_waveChannel.Read(convertInputBuffer.Bytes, 0, convertInputBuffer.Bytes.Length); // ************************************** floatsRead = bytesRead / ((sizeof(float)) * format.Channels); #endregion #region Apply DSP Effects (Equalizer, Vocal Removal, etc.) ApplyDSPEffects(convertInputBuffer.Floats, floatsRead); #endregion #region Apply Time Stretch Profile lock (PropertiesLock) { if (m_timeStretchProfileChanged) { ApplySoundTouchTimeStretchProfile(); m_timeStretchProfileChanged = false; } } #endregion actualEndMarker = this.EndMarker; loop = this.Loop; if (!loop || actualEndMarker == TimeSpan.Zero) { actualEndMarker = m_waveChannel.TotalTime; } if (m_waveChannel.CurrentTime > actualEndMarker) { #region Flush left over samples // ** End marker reached ** // Now the input buffer is processed, 'flush' some last samples that are // hiding in the SoundTouch's internal processing pipeline. m_soundTouchSharp.Clear(); m_inputProvider.Flush(); m_waveChannel.Flush(); if (!m_stopWorker) { while (!m_stopWorker && samplesProcessed != 0) { SetSoundSharpValues(); samplesProcessed = m_soundTouchSharp.ReceiveSamples(convertOutputBuffer.Floats, outBufferSizeFloats); if (samplesProcessed > 0) { TimeSpan currentBufferTime = m_waveChannel.CurrentTime; m_inputProvider.AddSamples(convertOutputBuffer.Bytes, 0, (int)samplesProcessed * sizeof(float) * format.Channels, currentBufferTime); } samplesProcessed = m_soundTouchSharp.ReceiveSamples(convertOutputBuffer.Floats, outBufferSizeFloats); } } #endregion #region Handle Loop & Cue loop = this.Loop; if (loop) { m_waveChannel.CurrentTime = this.StartMarker; WaitForCue(); } else { break; } #endregion } #region Put samples in SoundTouch SetSoundSharpValues(); // *** Put samples in SoundTouch *** m_soundTouchSharp.PutSamples(convertInputBuffer.Floats, (uint)floatsRead); // ********************************************************************** #endregion #region Receive & Play Samples // Receive samples from SoundTouch do { // *** Receive samples back from SoundTouch *** // *** This is where Time Stretching and Pitch Changing are actually done ********* samplesProcessed = m_soundTouchSharp.ReceiveSamples(convertOutputBuffer.Floats, outBufferSizeFloats); // ********************************************************************** if (samplesProcessed > 0) { TimeSpan currentBufferTime = m_waveChannel.CurrentTime; // ** Play samples that came out of SoundTouch by adding them to AdvancedBufferedWaveProvider - the buffered player m_inputProvider.AddSamples(convertOutputBuffer.Bytes, 0, (int)samplesProcessed * sizeof(float) * format.Channels, currentBufferTime); // ********************************************************************** // Wait for queue to free up - only then add continue reading from the file // >> Note: when paused, loop runs infinitely while (!m_stopWorker && m_inputProvider.GetQueueCount() > BusyQueuedBuffersThreshold) { Thread.Sleep(10); } bufferIndex++; } } while (!m_stopWorker && samplesProcessed != 0); #endregion } // while #region Stop PlayBack m_logger.Debug("ProcessAudio() finished - stop playback"); m_waveOutDevice.Stop(); // Stop listening to PlayPositionChanged events m_inputProvider.PlayPositionChanged -= new EventHandler(inputProvider_PlayPositionChanged); // Fix to current play time not finishing up at end marker (Wave channel uses positions) if (!m_stopWorker && CurrentPlayTime < actualEndMarker) { lock (CurrentPlayTimeLock) { m_currentPlayTime = actualEndMarker; } } // Clear left over buffers m_soundTouchSharp.Clear(); // Playback status changed to -> Stopped ChangeStatus(Statuses.Stopped); #endregion }