How do you remove \"popping\" and \"clicking\" sounds in audio constructed by concatenating sound tonal sound clips together?
I have this PyAudio code for generating
The answer you've written for yourself will do the trick but isn't really the correct way to do this type of thing.
One of the problems is your checking for the "tip" or peak of the sine wave by comparing against 1. Not all sine frequencies will hit that value or may require a large number of cycles to do so.
Mathematically speaking, the peak of the sine is at sin(pi/2 + 2piK) for all integer values of K.
To compute sine for a given frequency you use the formula y = sin(2pi * x * f0/fs) where x is the sample number, f0 is the sine frequency and fs is the sample rate.
For a nice number like 1kHz at 48kHz sample rate, when x=12 then:
sin(2pi * 12 * 1000/48000) = sin(2pi * 12/48) = sin(pi/2) = 1
However at a frequency like 997Hz then the true peak falls a fraction of a sample after sample 12.
sin(2pi * 12 * 997/48000) = 0.99087178042
sin(2pi * 12 * 997/48000) = 0.99998889671
sin(2pi * 12 * 997/48000) = 0.99209828673
A better method of stitching the waveforms together is to keep track of the phase from one tone and use that as the starting phase for the next.
First, for a given frequency you need to figure out the phase increment, notice it is the same as what you are doing with the sample factored out:
phInc = 2*pi*f0/fs
Next, compute the sine and update a variable representing the current phase.
for x in xrange(number_of_frames):
y = math.sin(self._phase);
self._phase += phaseInc;
Putting it all together:
def tone(self, frequency, length=1000, play=False, **kwargs):
number_of_frames = int(self.bitrate * length/1000.)
phInc = 2*math.pi*frequency/self.bitrate
for x in xrange(number_of_frames):
y = math.sin(self._phase)
_phase += phaseInc;
self._queue.append(chr(int(y)))