pythonaudiosignal-processingpyaudioportaudio

Remove/control clicking sound using PyAudio as an oscillator


When this runs, there is a clicking sound between pitches. I don't mind the clicking sound too much - it's pleasantly rhythmic. That said...

I have seen this thread, but haven't figured out how to apply it to my problem: How to remove pops from concatented sound data in PyAudio

Any ideas? Thanks for your time!

import numpy
import pyaudio
import math
import random


def sine(frequency, length, rate):
    length = int(length * rate)
    factor = float(frequency) * (math.pi * 2) / rate
    waveform = numpy.sin(numpy.arange(length) * factor)
    return waveform


def play_tone(stream, frequency, length, rate=44100):
    chunks = []
    chunks.append(sine(frequency, length, rate))

    chunk = numpy.concatenate(chunks) * .25

    stream.write(chunk.astype(numpy.float32).tostring())


def bassline():
        frequency = 300
        for i in range(1000000):
            play_tone(stream, frequency, .15)
            change = random.choice([-75, -75, -10, 10, 2, 3, 100, -125])
            print (frequency)
            if frequency < 0:
                frequency = random.choice([100, 200, 250, 300])
            else:
                frequency = frequency + change 

if __name__ == '__main__':
    p = pyaudio.PyAudio()
    stream = p.open(format=pyaudio.paFloat32,
                    channels=1, rate=44100, output=4)

bassline()

/EDIT

I've plotted the tones and it looks like the discontinuity is in the relationship between the starting and ending phase of each tone.

First tone

Second tone

Any ideas how to remedy this?


Solution

  • Thank you Ehz and Matthias.

    In the end, I solved this by fading in and out each tone over the course of a couple hundred milliseconds. It's also a nice way to get control of clicking sound. The closer fade is to 0, the louder the clicking.

    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, length, rate=44100):
        chunks = [sine(frequency, length, rate)]
    
        chunk = numpy.concatenate(chunks) * 0.25
    
        fade = 200.
    
        fade_in = numpy.arange(0., 1., 1/fade)
        fade_out = numpy.arange(1., 0., -1/fade)
    
        chunk[:fade] = numpy.multiply(chunk[:fade], fade_in)
        chunk[-fade:] = numpy.multiply(chunk[-fade:], fade_out)
    
        stream.write(chunk.astype(numpy.float32).tostring())
    
    
    def test():
        test_freqs = [50, 100, 200, 400, 800, 1200, 2000, 3200]
    
        for i in range(2):
            for freq in test_freqs:
                play_tone(stream, freq, 1)
    
    
    if __name__ == '__main__':
        p = pyaudio.PyAudio()
        stream = p.open(format=pyaudio.paFloat32,
                        channels=1, rate=44100, output=1)
    
    
    test()