title: Smooth Audio with PyAudio
body: <p>I am trying do some sound experiments with Python and I need a decent implementation of a <code>play_tone(freq, dur)</code> function. I have looked far and wide over a long period of time and have so far found three implementations, only one of which approaches the basic smoothness of sound that I require when playing rapid sequences of notes.</p>
  
  <p>I have not studied the maths/physics of sound generation so am taking a lot of the code on trust - once I know why the best one of these approaches works best, I will use that as a staring point to study further.</p>
  
  <p>So I'm looking for an explanation of why the two "worst" version have so much clipping and clicking while the best version (which uses <code>struct</code>) is so much smoother. I expect <a href="https://stackoverflow.com/a/35075923/3042018">this answer</a> has something to do with it, but I'm not sure how.</p>
  
  <p>Also, I would love to find a way to make the last note of the smoothest version not click on the last note - i.e I want it to end smoothly.</p>
  
  <pre><code># This is the smoothest version I can find
  
  import math
  import struct
  import pyaudio
  
  def play_tone(frequency, duration, stream, amplitude=0.5, fs=44100):
      N = int(fs / frequency)
      T = int(frequency * duration)  # repeat for T cycles
      dt = 1.0 / fs
      # 1 cycle
      tone = (amplitude * math.sin(2 * math.pi * frequency * n * dt)
              for n in range(N))
      # Notice the b to transform the operation in a bytes operation
      data = b''.join(struct.pack('f', samp) for samp in tone)
      for n in range(T):
          stream.write(data)
  
  #Usage
  fs = 48000
  p = pyaudio.PyAudio()
  stream = p.open(
      format=pyaudio.paFloat32,
      channels=1,
      rate=fs,
      output=True)
  a = 2 ** (1 / 24)
  f0 = 110
  qts = [f0 * a ** p for p in range(96)]
  for i in range(0, len(qts) - 24, 3):
      for j in range(i, i + 24, 4):
          play_tone(qts[j], 0.1, stream)
  
  stream.close()
  p.terminate()
  </code></pre>
  
  <hr>
  
  <pre><code># This is the second smoothest version I can find
  
  import math
  import numpy
  import pyaudio
  
  def sine(frequency, length, rate):
      length = int(length * rate)
      factor = float(frequency) * (math.pi * 2) / rate
      return numpy.sin(numpy.arange(length) * factor)
  
  
  def play_tone(stream, frequency=440, length=1, rate=44100):
      chunks = []
      chunks.append(sine(frequency, length, rate))
  
      chunk = numpy.concatenate(chunks) * 0.25
  
      stream.write(chunk.astype(numpy.float32).tostring())
  
  #Usage
  fs = 48000
  p = pyaudio.PyAudio()
  stream = p.open(
      format=pyaudio.paFloat32,
      channels=1,
      rate=fs,
      output=True)
  a = 2 ** (1 / 24)
  f0 = 110
  qts = [f0 * a ** p for p in range(96)]
  for i in range(0, len(qts) - 24, 3):
      for j in range(i, i + 24, 4):
          play_tone(stream, qts[j], 0.1)
  
  stream.close()
  p.terminate()
  </code></pre>
  
  <hr>
  
  <pre><code># This is the least smooth version I can find
  
  import numpy as np
  import pyaudio
  
  def play_tone(freq, dur, stream, fs=44100):
  
    volume = 0.5     # range [0.0, 1.0]
    duration = dur   # in seconds, may be float
    f = freq       # sine frequency, Hz, may be float
  
    # generate samples, note conversion to float32 array
    samples = (np.sin(2*np.pi*np.arange(fs*duration)*f/fs)).astype(np.float32)
  
    # play. May repeat with different volume values (if done interactively)
    stream.write(volume*samples)
  
  #Usage
  fs = 48000
  p = pyaudio.PyAudio()
  stream = p.open(
      format=pyaudio.paFloat32,
      channels=1,
      rate=fs,
      output=True)
  a = 2 ** (1 / 24)
  f0 = 110
  qts = [f0 * a ** p for p in range(96)]
  for i in range(0, len(qts) - 24, 3):
      for j in range(i, i + 24, 4):
          play_tone(qts[j], 0.5, stream)
  
  stream.close()
  p.terminate()
  </code></pre>