Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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'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?

like image 213
Danny Meyer Avatar asked Feb 06 '23 02:02

Danny Meyer


2 Answers

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

like image 61
Ehz Avatar answered Feb 07 '23 16:02

Ehz


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()
like image 33
Danny Meyer Avatar answered Feb 07 '23 16:02

Danny Meyer