title: PyAudio - Synchronizing playback and record
body: <p>I'm currently using PyAudio to work on a lightweight recording utility that fits a specific need of an application I'm planning.  I am working with an ASIO audio interface.  What I'm writing the program to do is play a wav file through the interface, while simultaneously recording the output from the interface.  The interface is processing the signal onboard in realtime and altering the audio.  As I'm intending to import this rendered output into a DAW, I need the output to be perfectly synced with the input audio.  Using a DAW I can simultaneously play audio into my interface and record the output.  It is perfectly synced in the DAW when I do this.  The purpose of my utility is to be able to trigger this from a python script.</p>
  
  <p>Through a brute-force approach I've come up with a solution that works, but I'm now stuck with a magic number and I'm unsure of whether this is some sort of constant or something I can calculate.  If it is a number I can calculate that would be ideal, but I still would like to understand where it is coming from either way.</p>
  
  <p>My callback is as follows:</p>
  
  <pre><code>def testCallback(in_data, frame_count, time_info, status):
      #read data from wave file
      data = wave_file.readframes(frame_count)
      #calculate number of latency frames for playback and recording
      #1060 is my magic number
      latencyCalc = math.ceil((stream.get_output_latency() + stream.get_input_latency()) * wave_file.getframerate()) + 1060
  
      #no more data in playback file
      if data == "":
          #this is the number of times we must keep the loop alive to capture all playback
          recordEndBuffer = latencyCalc / frame_count
          if lastCt &lt; recordEndBuffer:
              #return 0-byte data to keep callback alive
              data = b"0"*wave_file.getsampwidth()*frame_count
              lastCt += 1
      #we start recording before playback, so this accounts for the initial "pre-playback" data in the output file
      if firstCt &gt; (latencyCalc/frame_count):
          wave_out.writeframes(in_data)
      else:
         firstCt += 1
      return (data, pyaudio.paContinue)
  </code></pre>
  
  <p>My concern is in the function:</p>
  
  <pre><code>latencyCalc = math.ceil((stream.get_output_latency() + stream.get_input_latency()) * wave_file.getframerate()) + 1060
  </code></pre>
  
  <p>I put this calculation together by observing the offset of my output file in comparison to the original playback file.  Two things were occurring, my output file was starting later than the original file when played simultaneously, and it would also end early.  Through trial and error I determined it was a specific number of frames extra at the beginning and missing at the end.  This calculates those number of frames.  I do understand the first piece, it is the input/output latencies (provided in second/subsecond accuracy) converted to frames using the sample rate.  But I'm not quite sure how to fill in the 1060 value as I'm not sure where it comes from.</p>
  
  <p>I've found that by playing with the latency settings on my ASIO driver, my application continues to properly sync the recorded file even as the output/input latencies above change due to the adjustment (input/output latencies are always the same value), so the 1060 appears to be consistent on my machine.  However, I simply don't know whether this is a value that can be calculated. Or if it is a specific constant, I'm unsure what exactly it represents.</p>
  
  <p>Any help in better understanding these values would be appreciated.  I'm happy my utility is now working properly, but would like to fully understand what is happening here, as I suspect potentially using a different interface would likely no longer work correctly (I would like to support this down the road for a few reasons).</p>
  
  <p><strong>EDIT 4/8/2014</strong> in response to Roberto:
  The value I receive for
      latencyCalc = math.ceil((stream.get_output_latency() + stream.get_input_latency()) * wave_file.getframerate()) + 1060
  is 8576, with the extra 1060 bringing to total latency to 9636 frames.  You are correct in your assumption of why I added the 1060 frames.  I am playing the file through the external ASIO interface, and the processing I'm hoping to capture in my recorded file is the result of the processing that occurs on the interface (not something I have coded).  To compare the outputs, I simply played the test file and recorded the interface's output without any of the processing effects engaged on the interface.  I then examined the two tracks in Audacity, and by trial and error determined that 1060 was the closest I could get the two to align.  I have since realized it is still not exactly perfect, but it is incredibly close and audibly undetectable when played simulataneously (which is not true when the 1060 offset is removed, there is a noticeable delay).  Adding/removing an additional frame is too much compensation in comparison to 1060 as well.</p>
  
  <p>I do believe you are correct that the additional latency is from the external interface.  I was initially wondering if it was something I could calculate with the numerical info I had at hand, but I am concluding it's just a constant in the interface.  I feel this is true as I have determined that if I remove the 1060, the offset of the file is exactly the same as performing the same test but manually in Reaper (this is exactly the process I'm automating).  I am getting much better latency than I would in reaper with my new brute force offset, so I'm going to call this a win.  In my application, the goal is to completely replace the original file with the newly processed file, so the absolute minimum latency between the two is desired.</p>
  
  <p>In response to your question about ASIO in PyAudio, the answer is fortunately yes.  You must compile PortAudio using the ASIO SDK for PortAudio to function with ASIO, and then update the PyAudio setup to compile this way.  Fortunately I'm working on windows, <a href="http://www.lfd.uci.edu/~gohlke/pythonlibs/#pyaudio" rel="nofollow">http://www.lfd.uci.edu/~gohlke/pythonlibs/#pyaudio</a> which has ASIO support built in, and the devices will then be accessible through ASIO.</p>