Remove/control clicking sound using PyAudio as an oscillator
Asked Answered
T

4

5

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'd like to be able to get rid of this clicking sound when I don't want it.
  • Better yet, it would be nice to be able to control the clicking sound in some way - volume, etc.

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?

Tawnatawney answered 12/2, 2017 at 19:34 Comment(2)
You should try plotting the generated wave form. This way you can probably see the discontinuities that cause the clicks.Velites
Since you now know the cause of the problem and also know that it doesn't have anything to do with PyAudio nor PortAudio, you should abandon this question and ask a new one describing your actual problem. You should try to reduce your example code to the relevant parts. You should also consider asking at dsp.stackexchange.com, since it is actually a DSP question.Velites
T
3

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()
Tawnatawney answered 18/2, 2017 at 4:2 Comment(0)
B
3

As seen in the two waveform images, you are getting a clicking noise due to the rapid change of the waveform amplitude when you switch between frequencies. To get around this you need to maintain the phase of the waveform when you change frequencies. I think the simplest way to do that is to add a variable that records the last location in waveform cycle after each sine call. The end location can be used as the start location in the next sine call.

Something like:

phase_start = phase_position
phase_end = phase_start + length
waveform = numpy.sin(numpy.arange(phase_start, phase_end) * factor)
phase_position = phase_end

Pitch shift maintaining cycle position

Note: I think this is the simplest answer that could work, but I'd recommend using the info in the question you referenced. You should maintain the phase of the played sine wave in radians. How to remove pops from concatented sound data in PyAudio

Bitchy answered 14/2, 2017 at 22:1 Comment(0)
T
3

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()
Tawnatawney answered 18/2, 2017 at 4:2 Comment(0)
B
2

The clicking is due to an ending wave phase of one frequency differing from the starting wave phase of the following frequency. Refer to the two images below: inspecting the first wave's graph shows an ending phase value of around -0.96. The second image shows the next frequency starting at an amplitude of around .85. You will hear a noticeable click between frequencies if you don't shift each new wave accordingly. It turns out there's a very simple solution for this. Use numpy.arcsin() to calculate and store the required phase shift in order keep the waves operating in harmony:

wave_delta_arcsin = 0.0

def sine(frequency, length):
    global wave_delta_arcsin
    length = int(length * rate)
    factor = (math.pi * 2) * float(frequency) / rate
    wave = numpy.sin(numpy.arange(length) * factor + wave_delta_arcsin)
    wave_delta_arcsin = numpy.arcsin(wave[-1])
    return wave

enter image description here

enter image description here

Bathometer answered 9/4, 2020 at 22:20 Comment(0)
D
0

When you generate the signal, it's not hard to fix it, just apply some window function to the signal (https://docs.scipy.org/doc/scipy/reference/signal.windows.html). Some windows will work better then others, in my tests, I got good results with: barthann and blackman. For more exotic sounds, you should go with flattop. For more attenuation precision, you should get chebwin.

On the following example, I just modified your sine function to apply the barthann window function:

from scipy import signal

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

    # window function applied to remove the click noise
    window = signal.windows.barthann(len(waveform))

    return waveform * window

In depth: Why the window function remove the click noises?

The explanation is not too hard, the windows function are special function that have the beginning and the ending attenuated using some type of curve. Also, all the windows functions has the max value of 1 and minimum of 0 to make our life more easy! The clicks on audio signals are generated by a computer is consequence of the abrupt transient change present on the begging and ending of the audio signal. When you multiply you signal by a windows function, you are just attenuating the beginning and the ending of you sinal, this makes your transition more smooth.

Dannydannye answered 7/6, 2022 at 19:43 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.