Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Crackling/clicking at the end of signal when using playback without callback #482

Open
kbasaran opened this issue Aug 16, 2023 · 6 comments

Comments

@kbasaran
Copy link

kbasaran commented Aug 16, 2023

import numpy as np
import sounddevice as sd
import matplotlib.pyplot as plt
plt.rcParams["figure.dpi"] = 150

class SoundEngine():
    def __init__(self):
        self.FS = sd.query_devices(
            device=sd.default.device,
            kind='output',
            )["default_samplerate"]
        self.start_stream()
        self.test_beep()
        
    def start_stream(self):
        self.stream = sd.OutputStream(samplerate=self.FS, channels=2, latency='high')
        self.stream.start()

    def beep(self, T, freq):
        t = np.arange(T * self.FS) / self.FS
        y = 0.1 * np.sin(t * 2 * np.pi * freq)

        # pad = np.zeros(100)
        # y = np.concatenate([pad, y, pad])

        y = np.tile(y, self.stream.channels)
        y = y.reshape((len(y) // self.stream.channels, self.stream.channels), order='F')
        y = np.ascontiguousarray(y, self.stream.dtype)
        plt.plot(y[-200:, :]); plt.grid()
        underflowed = self.stream.write(y)
        print("Underflowed: ", underflowed)

    def test_beep(self):
        self.beep(1, 200)

sound_engine = SoundEngine()

It seems like somehow the end of my signal is being clipped which causes an immediate jump to zero amplitude and a click sound.

What I have tested

  • Different sampling rates: 44100/48000
  • Different audio devices (USB wireless headset, onboard digital out, onboard analog out)
  • using play() method instead of Stream.write()
  • Changing amplitude of the beep (0.1 == -20dB above)
    • Loudness of the click at the end changes proportionally

My system

  • Ubuntu 22.04 LTS with Pulseaudio
    • Everything stock and I do not have such issues in any other app - see update
  • libportaudio2/jammy,now 19.6.0-1.1 amd64 [installed]
  • Python 3.11

Thank you already for the help.
Edit: Addition to "What I have tested"

kbasaran pushed a commit to kbasaran/Speaker-Stuff-Calculator that referenced this issue Aug 16, 2023
@mgeier
Copy link
Member

mgeier commented Sep 2, 2023

You should always check the return value of write(), see https://python-sounddevice.readthedocs.io/en/0.4.6/api/streams.html#sounddevice.Stream.write

@kbasaran
Copy link
Author

kbasaran commented Sep 3, 2023

I updated the code to print out the return value "underflowed (bool)". It returns False. Yet the audio click is clearly audible.

@mgeier
Copy link
Member

mgeier commented Sep 3, 2023

OK, that was an important first step.

Now, listening to the output and looking at your code more closely, I have noticed that you don't do any fade in nor fade out, right?

It is expected that you hear a click if you abruptly start or stop a sine wave, which creates a discontinuity in the signal. In signal processing terms, you are applying a "rectangular window" to the sine signal.

You should hear the same click in the play_sine.py example (you may hear it more clearly at lower sine frequencies, as in your example).

To avoid the click, you should create a fade in/out, see e.g. https://nbviewer.org/github/spatialaudio/communication-acoustics-exercises/blob/master/intro.ipynb#Listening-to-the-Signal

A simple linear fade normally suffices.

I guess this should solve the problem, but if not, there can be another reason for audible clicks when starting and stopping a stream, see #455.

@kbasaran
Copy link
Author

kbasaran commented Sep 3, 2023

I do not do fade in/out but I was careful to make the sine wave end at a zero point. This does make a soft pop. But what I hear is a loud click, which seems to have another cause.

To verify this I added 100 samples of zeros to the end of my signal (updated code above with "pad"). I'd expect that I hear the same click if it were about the sine wave's ending. Yet this fixed my problem and the loud click is gone.

I also did the same experiment in Audacity. If I simply generate a sine wave that ends at a zero, I hear the exact same loud click. If I add silence at the end of it, the click is gone.

This I believe points to the issue not being about python-sounddevice.

@mgeier
Copy link
Member

mgeier commented Sep 6, 2023

I do not do fade in/out but I was careful to make the sine wave end at a zero point.

Yeah, stopping at a zero crossing isn't enough to avoid audible artifacts, as you have witnessed:

This does make a soft pop.

With an appropriate fade, this should also go away.

But what I hear is a loud click, which seems to have another cause.

OK, that's good to know. Did you look at #455 then?

@kbasaran
Copy link
Author

kbasaran commented Sep 6, 2023

Yes I did. Similar conclusion there also, it seems to be an issue with host API. Suggestion is to separate playback endings from stream endings. So I'll add padding to my signals as in the example above. That is a good workaround. Thank you for the help.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants