title: Audio 'Clipping' when Recording with NAudio or CSCore
body: <p>I've developed an application that allows users to dictate information to be transcribed by another user at a later date. For the audio recording, I've been using the NAudio and CSCore libraries (individually, not together - I'll explain more below).</p>
  
  <p>My first kick at the can was to use NAudio, which I was relatively successful with. Every so often, though, the application would crash with a message similar to the below:</p>
  
  <blockquote>
    <p>The process was terminated due to an internal error in the .NET Runtime at IP 791F9AAA (79140000) with exit code 80131506.</p>
  </blockquote>
  
  <p>Frustratingly, I'm unable to reproduce the crashing on my development machine. Since these were hard crashes related to the internals of the .NET Runtime, it would be difficult to diagnose exactly what was causing these crashes, but I believed that my use of NAudio might be causing the issue.</p>
  
  <p>So... I switched libraries to CSCore and the crashing issue resolved itself. Great, but I had one further issue - I was running into OutOfMemory exceptions when dealing with lengthy dictations. Originally I was storing the contents of the recorded audio in a MemoryStream, so I changed my code to use a FileStream instead, and write to disk rather than keeping the recorded audio in memory. </p>
  
  <p>This resolved all the OutOfMemory exceptions that I was seeing, however this introduced a new issue - the audio was now 'clipping'. I'm not sure if clipping describes the issue sufficiently, but essentially what I'm seeing (or rather hearing) is the recorded audio tripping up on itself and portions of the audio missing entirely. Once again, I'm not seeing this issue on my development machine, only on workstations where the application is actually being used.</p>
  
  <p>As an experiment, I switched back to NAudio (still using FileStreams instead of MemoryStreams) and found that the audio clipping issue was <em>nearly</em> fixed. Generally, the issue doesn't seem to present itself, but we're still seeing instances where the audio is clipping. The issue with the internal error in the .NET Runtime is still present as well.</p>
  
  <p>To summarize:</p>
  
  <p><strong>NAudio:</strong></p>
  
  <ul>
  <li>Causes crashes on occasion relating to an internal error in the .NET Runtime</li>
  <li>Doesn't experience audio clipping as much as CSCore, however the issue is still present</li>
  </ul>
  
  <p><strong>CSCore:</strong></p>
  
  <ul>
  <li>Is not causing any crashes, however the audio clipping issue is <em>very</em> evident since switching to using a FileStream rather than a MemoryStream</li>
  </ul>
  
  <p>I can't find any other cases of others experiencing these issues. Does anyone with experience in either library have some pointers as to where I'm going wrong? Alternatively, is there another method for recording audio using .NET?</p>
  
  <p>EDIT:</p>
  
  <p>Here's the code for my WaveRecorder control where all the recording is handled. Note that I'm using WPF.</p>
  
  <pre><code>partial class WaveRecorder : UserControl
  {
      const int samplesPerPoint = 50;
      const int numberOfPoints = 2000;
  
      private PowerMic powerMic;
  
      private IWaveIn waveIn;
      private WaveFileWriter writer;
  
      public bool IsListening
      {
          get { return (bool)GetValue(IsListeningProperty); }
          set { SetValue(IsListeningProperty, value); }
      }
      public static readonly DependencyProperty IsListeningProperty =
          DependencyProperty.Register("IsListening", typeof(bool), typeof(WaveRecorder), new PropertyMetadata(false));
  
  
  
      private PointCollection Points
      {
          get { return (PointCollection)GetValue(PointsProperty); }
          set { SetValue(PointsProperty, value); }
      }
      private static readonly DependencyProperty PointsProperty =
          DependencyProperty.Register("Points", typeof(PointCollection), typeof(WaveRecorder), new PropertyMetadata(new PointCollection()));
  
  
  
      public RecordingSession RecordedData
      {
          get { return (RecordingSession)GetValue(RecordedDataProperty); }
          set { SetValue(RecordedDataProperty, value); }
      }
      public static readonly DependencyProperty RecordedDataProperty =
          DependencyProperty.Register("RecordedData", typeof(RecordingSession), typeof(WaveRecorder), new PropertyMetadata(null, RecordedDataChanged));
  
  
  
      public bool RequiresWindowFocus
      {
          get { return (bool)GetValue(RequiresWindowFocusProperty); }
          set { SetValue(RequiresWindowFocusProperty, value); }
      }
      public static readonly DependencyProperty RequiresWindowFocusProperty =
          DependencyProperty.Register("RequiresWindowFocus", typeof(bool), typeof(WaveRecorder), new PropertyMetadata(true));
  
  
  
      public WaveRecorder()
      {
          InitializeComponent();
  
          if (DesignerProperties.GetIsInDesignMode(this))
          {
              return;
          }
  
          powerMic = PowerMic.TryFindPowerMic();
  
          if (powerMic != null)
          {
              PowerMicConnected(powerMic);
          }
          else
          {
              PowerMic.PowerMicConnected += PowerMicConnected;
          }
  
          Loaded += ControlLoaded;
          IsVisibleChanged += VisibilityChanged;
      }
  
      private void PowerMicConnected(PowerMic powerMic)
      {
          this.powerMic = powerMic;
          powerMic.MicrophoneOn += () =&gt; MicrophoneStateChanged();
          powerMic.MicrophoneOff += () =&gt; MicrophoneStateChanged();
  
          powerMic.Disconnected += () =&gt;
          {
              PowerMic.PowerMicConnected += PowerMicConnected;
          };
      }
  
      private void ControlLoaded(object sender, RoutedEventArgs e)
      {
          var parentWindow = Window.GetWindow(this);
  
          if (parentWindow != null)
          {
              parentWindow.Activated += ParentWindowGotFocus;
              parentWindow.Deactivated += ParentWindowLostFocus;
              parentWindow.Closing += ParentWindowClosing;
          }
  
          waveIn = new WaveIn();
          waveIn.DataAvailable += WaveInDataAvailable;
          waveIn.RecordingStopped += RecordingStopped;
      }
  
      private void ParentWindowClosing(object sender, EventArgs e)
      {
          if (waveIn != null)
          {
              waveIn.Dispose();
          }
  
          if (writer != null)
          {
              writer.Dispose();
          }
      }
  
      private void VisibilityChanged(object sender, DependencyPropertyChangedEventArgs e)
      {
          if (IsVisible)
          {
              if (ShouldRecord())
              {
                  StartRecording();
              }
          }
          else
          {
              if (IsListening)
              {
                  StopRecording();
              }
          }
      }
  
      private void ParentWindowGotFocus(object sender, EventArgs e)
      {
          if (ShouldRecord())
          {
              StartRecording();
          }
      }
  
      private void ParentWindowLostFocus(object sender, EventArgs e)
      {
          if (IsListening)
          {
              StopRecording();
          }
      }
  
      private bool ShouldRecord()
      {
          if (!IsVisible)
          {
              return false;
          }
  
          var parentWindow = Window.GetWindow(this);
  
          if (parentWindow != null &amp;&amp; !parentWindow.IsActive &amp;&amp; RequiresWindowFocus)
          {
              return false;
          }
  
          if (powerMic != null)
          {
              return powerMic.MicrophoneListening;
          }
  
          return false;
      }
  
      private void MicrophoneStateChanged()
      {
          if (!Dispatcher.CheckAccess())
          {
              Dispatcher.Invoke(() =&gt; MicrophoneStateChanged());
              return;
          }
  
          if (ShouldRecord())
          {
              StartRecording();
          }
          else
          {
              StopRecording();
          }
      }
  
      private void StartRecording()
      {
          if (RecordedData == null)
          {
              RecordedData = new RecordingSession(new MemoryStream());
          }
          else if (RecordedData.Stream == null)
          {
              RecordedData.Stream = new MemoryStream();
          }
  
          if (waveIn == null)
          {
              waveIn = new WaveIn();
              waveIn.DataAvailable += WaveInDataAvailable;
              waveIn.RecordingStopped += RecordingStopped;
          }
  
          if (writer == null)
          {
              writer = new WaveFileWriter(new IgnoreDisposeStream(RecordedData.Stream), waveIn.WaveFormat);
          }
  
          if (!IsListening)
          {
              waveIn.StartRecording();
  
              IsListening = true;
          }
      }
  
      private void RecordingStopped(object sender, StoppedEventArgs e)
      {
          if (e.Exception != null)
          {
              if (waveIn != null &amp;&amp; IsListening)
              {
                  waveIn.StartRecording();
              }
          }
      }
  
      private void StopRecording()
      {
          IsListening = false;
  
          if (waveIn != null)
          {
              waveIn.StopRecording();
          }
      }
  
      private void WaveInDataAvailable(object sender, WaveInEventArgs e)
      {
          writer.Write(e.Buffer, 0, e.BytesRecorded);
          writer.Flush();
  
          GenerateWaveFormForRecordedData(e.Buffer);
      }
  
      private static void RecordedDataChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
      {
          (d as WaveRecorder)?.OnRecordedDataChanged();
      }
  
      protected void OnRecordedDataChanged()
      {
          Points = new PointCollection();
  
          if (RecordedData?.Stream == null || RecordedData.Stream.Length == 0)
          {
              // No content.                
              return;
          }
  
          string temporaryFile = System.IO.Path.Combine(System.IO.Path.GetTempPath(), Guid.NewGuid().ToString());
  
          try
          {
              using (var fileStream = File.Create(temporaryFile))
              {
                  RecordedData.Stream.Position = 0;
                  RecordedData.Stream.CopyTo(fileStream);
  
                  fileStream.Position = 0;
                  RecordedData.Stream.Position = 0;
  
                  using (var reader = new WaveFileReader(fileStream))
                  {
                      writer = new WaveFileWriter(new IgnoreDisposeStream(RecordedData.Stream), reader.WaveFormat);
  
                      int read;
                      byte[] buffer = new byte[1024];
                      while ((read = reader.Read(buffer, 0, buffer.Length)) &gt; 0)
                      {
                          writer.Write(buffer, 0, read);
                          GenerateWaveFormForRecordedData(buffer);
                      }
  
                      writer.Flush();
                  }
              } 
          }
          finally
          {
              try { File.Delete(temporaryFile); } catch { }
          }            
      }
  
      private void GenerateWaveFormForRecordedData(byte[] data)
      {
          var newPoints = new List&lt;Tuple&lt;double, double&gt;&gt;();
  
          int bytesPerSample;
  
          if (waveIn == null)
          {
              /*using (var reader = new WaveFileReader(new MemoryStream(data)))
              {
                  bytesPerSample = (reader.WaveFormat.BitsPerSample / 8) * reader.WaveFormat.Channels;
              }*/
  
              bytesPerSample = 2;
          }
          else
          {
              bytesPerSample = (waveIn.WaveFormat.BitsPerSample / 8) * waveIn.WaveFormat.Channels;
          }
  
          byte[] waveData = new byte[bytesPerSample * samplesPerPoint];
  
          for (float x = 0; x &lt; data.Length; x += bytesPerSample * samplesPerPoint)
          {
              Array.Copy(data, (int)x, waveData, 0, (int)Math.Min(data.Length - x, waveData.Length));
              int bytesRead = waveData.Length;
  
              if (bytesRead == 0)
              {
                  break;
              }
  
              short low = 0;
              short high = 0;
  
              for (int n = 0; n &lt; bytesRead; n += 2)
              {
                  short sample = BitConverter.ToInt16(waveData, n);
                  if (sample &lt; low) low = sample;
                  if (sample &gt; high) high = sample;
              }
  
              float lowPercent = ((((float)low) - short.MinValue) / ushort.MaxValue);
              float highPercent = ((((float)high) - short.MinValue) / ushort.MaxValue);
              float lowValue = (float)WaveFormScrollViewer.ActualHeight * lowPercent;
              float highValue = (float)WaveFormScrollViewer.ActualHeight * highPercent;
  
              newPoints.Add(new Tuple&lt;double, double&gt;(highValue, lowValue));
          }
  
          var points = new PointCollection(Points);
          double index;
  
          if (points.Any())
          {
              index = points.Last().X + 1;
  
              int maxPoints = 5000;
  
              if (points.Count &gt; maxPoints)
              {
                  points = new PointCollection(points.Skip(points.Count - maxPoints));
              }
          }
          else
          {
              index = 0;
          }
  
          foreach (var newPoint in newPoints)
          {
              points.Add(new Point(index, newPoint.Item1));
              points.Add(new Point(index++, newPoint.Item2));
          }
  
          Points = points;
  
          WaveFormScrollViewer.ScrollToRightEnd();
      }
  }
  </code></pre>
  
  <p>}</p>