2
votes

I am trying to write a simple audio function generator in Python, to be run on a Raspberry Pi (model 2). The code essentially does this:

  • Generate 1 second of the audio signal (say, a sine wave, or a square wave, etc)
  • Play it repeatedly in a loop

For example:

import pyaudio
from numpy import linspace,sin,pi,int16

def note(freq, len, amp=1, rate=44100):
 t = linspace(0,len,len*rate)
 data = sin(2*pi*freq*t)*amp
 return data.astype(int16) # two byte integers

RATE = 44100
FREQ = 261.6

pa = pyaudio.PyAudio()
s = pa.open(output=True,
            channels=2,
            rate=RATE,
            format=pyaudio.paInt16,
            output_device_index=2)

# generate 1 second of sound
tone = note(FREQ, 1, amp=10000, rate=RATE)

# play it forever    
while True:
  s.write(tone)

The problem is that every iteration of the loop results in an audible "tick" in the audio, even when using an external USB sound card. Is there any way to avoid this, rather than trying to rewrite everything in C?

I tried using the pyaudio callback interface, but that actually sounded worse (like maybe my Pi was flatulent).

The generated audio needs to be short because it will ultimately be adjusted dynamically with an external control, and anything more than 1 second latency on control changes just feels awkward. Is there a better way to produce these signals from within Python code?

2
Such clicks might be related to using blocking mode of PyAudio. For your purposes (small buffer/low latency) you'd better use callback mode anyway. Try following this example: people.csail.mit.edu/hubert/pyaudio/docs/…quasoft

2 Answers

6
votes

You're hearing a "tick" because there's a discontinuity in the audio you're sending. One second of 261.6 Hz contains 261.6 cycles, so you end up with about half a cycle left over at the end:

Waveform showing discontinuity at 1 second

You'll need to either change the frequency so that there are a whole number of cycles per second (e.g, 262 Hz), change the duration such that it's long enough for a whole number of cycles, or generate a new audio clip every second that starts in the right phase to fit where the last chunk left off.

2
votes

I was looking for a similar question to yours, and found a variation that plays a pre-calculated length by concatenating a bunch of pre-calculated chunks.

http://milkandtang.com/blog/2013/02/16/making-noise-in-python/

Using a for loop with a 1-second pre-calculated chunk "play_tone" function seems to generate a smooth sounding output, but this is on a PC. If this doesn't work for you, it may be that the raspberry pi has a different back-end implementation that doesn't like successive writes.