0

I tried different ways to play sine waves in sounddevice, and they worked fine, until I tried to overlay multiple frequencies at once. I also get loud scratching noises in my speaker whenever there are no frequencies to play. I've simplified my code to this:

import sounddevice
import numpy


SAMPLE_RATE = 44100
frequencies = {440: 0, 550: 0, 660: 0}
# hz: start_index


def callback(outdata: numpy.ndarray, frames: int, time, status) -> None:
    """writes sound output to 'outdata' from sound_queue."""
    # params may need annotations... :/

    result = None

    for frequency, start_index in frequencies.items():
        t = (start_index + numpy.arange(frames)) / SAMPLE_RATE
        t = t.reshape(-1, 1)

        wave = numpy.sin(2 * numpy.pi * frequency * t)

        if result is None:
            result = wave
        else:
            result += wave

        frequencies[frequency] += frames

    if result is None:
        result = numpy.arange(frames) / SAMPLE_RATE
        result = result.reshape(-1, 1)

    outdata[:] = result


stream = sounddevice.OutputStream(channels=1, samplerate=SAMPLE_RATE, callback=callback)
stream.start()

while True:
    pass

  • Not a direct duplicate, since it involves a different library, but some discussion [here](https://stackoverflow.com/questions/19230983/prevent-alsa-underruns-with-pyaudio) about using threading or buffering to prevent underruns – G. Anderson Aug 25 '22 at 23:54
  • Thank you, will try not to use silence. – Gabriel Fernández Aug 26 '22 at 00:00

2 Answers2

0

I found that this code works if you make two changes to it.

The first change is to this while loop:

while True:
    pass

This loop will use nearly 100% of CPU, leaving little time for the callback function to run.

I changed this to repeatedly sleep instead:

while True:
    time.sleep(1)

This reduces CPU usage.

I also increased the number of frames requested at once.

I changed this line:

stream = sounddevice.OutputStream(channels=1, samplerate=SAMPLE_RATE, callback=callback)

into this:

stream = sounddevice.OutputStream(channels=1, blocksize=SAMPLE_RATE, samplerate=SAMPLE_RATE, callback=callback)

I did this because I found that the frames parameter to your callback was 15. (This is on Mac, might be different for you.) I found that requesting one second's worth of audio made numpy able to more efficiently generate it, and prevented a weird glitch noise.

After I do that, I get a dial-tone noise. (I assume that's what you were going for.)

Nick ODell
  • 15,465
  • 3
  • 32
  • 66
0

It looks like you are adding three unattenuated sine functions which will produce an output with amplitude reaching as high -3 to +3 at the points where the phases of the frequencies are close to the same.

  • I believe sounddevice is designed to handle data varying between -1 and 1
  • As written, your audio will be distorted due to clipping - I suggest scaling each sine wave so that the total stays (at least mostly) in the -1 to 1 range.

Separately, you may be generating popping sounds if you change the frequencies.

  • I don't see the part of your program that changes the frequencies over time, but if you just add and drop frequencies, you could be instantaneously switching from a sine wave to zero at different points - this will cause popping - particularly if the signal was at full amplitude
  • If this is what you are experiencing, then you will likely have to add some sort of envelope to ramp up and down the amplitude as you start and stop different tones.

I hope this helps.

DonH
  • 1
  • 1
  • 2