rbn42 / panon

An Audio Visualizer Widget in KDE Plasma
GNU General Public License v3.0
192 stars 31 forks source link

Consider using PulseAudio as an alternative to PortAudio #8

Closed kupiqu closed 5 years ago

kupiqu commented 5 years ago

Please see this comment from reddit (https://www.reddit.com/r/Python/comments/3k11g5/whats_a_good_sound_recording_library/cuu6gvy):

PyAudio is pretty clunky to use, I find. I would recommend sounddevice instead, which also interfaces with portaudio, but with a much nicer API and providing wheels (i.e. pip install sounddevice will work on Windows and OSX even though there are binary dependencies).

PyAudio is simply a wrapper for portaudio, sounddevice is a wrapper that tries to make portaudio pythonic. It provides a proper Stream class with play and record methods and callbacks for long-running interactive recordings. Additionally, there are some high level play and record standalone functions if all you need is play or record one short sample.

Finally, sounddevice uses CFFI, and is thus compatible with CPython and PyPy.

Full disclosure: I contributed to PyAudio back in the day (did the Py3 port), then built my own successor much like sounddevice, called PySoundCard, to which the creator of sounddevice contributed heavily. He then built sounddevice as a successor to PySoundCard since I didn't have enough time to develop it fast enough. I now heartily recommend you use sounddevice!

Perhaps sounddevice could solve some of the issues I'm having in issue #6

kupiqu commented 5 years ago

I tried it. While it works, the fifo introduces a delay of about 5-10 s in between of the sound and the spectrum display that makes it a non-suitable option, unless this can be somehow speeded up substantially.

rbn42 commented 5 years ago

Oh yes, it is an error that I can't sense when connecting panon to mpd. So this is the new script, and I will fix the error in panon.

from soundcard import pulseaudio as sc
import os
import numpy as np

SAMPLE_RATE = 44100 # [Hz]
SAMPLE_SIZE = 16 # [bit]
CHANNEL_COUNT = 2
BUFFER_SIZE = 5000 

blocksize=SAMPLE_RATE // 60  
#blocksize=256

path = "/tmp/my_program.fifo"
if not os.path.exists(path):
    os.mkfifo(path)

print('waiting')
f_fifo=open(path,'wb')
print('start')

l=sc.all_microphones(exclude_monitors=False)

mic0=l[0] # Replace it with the mic you want
stream0=mic0.recorder(SAMPLE_RATE,CHANNEL_COUNT,blocksize)
stream0.__enter__()

mic1=l[1] # Replace it with the mic you want
stream1=mic1.recorder(SAMPLE_RATE,CHANNEL_COUNT,blocksize)
stream1.__enter__()

while True:
    data=stream0.record(blocksize) +stream1.record(blocksize)
    data = np.asarray(data * (2**16), dtype='int16').tobytes()
    f_fifo.write(data)
kupiqu commented 5 years ago

It is slightly better with this change but unfortunately still too slow

rbn42 commented 5 years ago

Are you sure you checked out the latest master branch and restarted panon? I see no delay in my system.

kupiqu commented 5 years ago

Mmm, yes I did :/

Could it be that the delay is introduced when more than a signal is available?

kupiqu commented 5 years ago

How are you checking the delay? I do it with single tones

rbn42 commented 5 years ago

Maybe, but I won't know, I have only one signal here. But you can test. Do you have delay with only one signal?

How are you checking the delay? I do it with single tones

By playing / pausing music

kupiqu commented 5 years ago

Oh I see what's going on, I think single tones introduce a delay that probably is due to the appearing/disappearing of panon itself.

You're right, while panon is shown the delay is very short and pretty much acceptable.

kupiqu commented 5 years ago

I tested it again without panon auto-hide this time, and unfortunately I can still see the delay, so it's not that.

Doing with continuous music is not the best because you cannot perceive the delay, right?

I use the sound that plasma makes when changing the audio volume. And for that I use this sound (for the case you also have it):

/usr/share/sounds/freedesktop/stereo/audio-volume-change.oga

rbn42 commented 5 years ago

Do you see delay in these two gif?

https://files.catbox.moe/js00vd.webp

This one shows audio-volume-change

https://files.catbox.moe/567zev.webp

kupiqu commented 5 years ago

I watched the second one and it doesn't seem to be delay

I realize that delays in my system are cumulative, could that be the problem?

Could you keep the fifo going for a while and then test? In my case, the longer I wait until I do the test since I started the fifo, the longer the delay.

rbn42 commented 5 years ago

The fifo is still going here, no delay.

I realize that delays in my system are cumulative, could that be the problem?

Try replace line 64 in panon/source.py with

        data = self.stream.read(self.sample_rate // self.fps * self.channel_count * 2*8)    #int16 requires 2 bytes

This will help eliminate accumulated delay, but may introduce other problems.

rbn42 commented 5 years ago

Maybe we are in the wrong direction, you can forget about fifo, and modify SoundCardSource class in source.py directly, add your 2nd signal to SoundCardSource class.

kupiqu commented 5 years ago

Maybe we are in the wrong direction, you can forget about fifo, and modify SoundCardSource class in source.py directly, add your 2nd signal to SoundCardSource class.

If that would work, it would certainly be the best. Will check

kupiqu commented 5 years ago

Is there a reason why you use microphone instead of speakers in your code?

According to https://pypi.org/project/SoundCard :

import soundcard as sc

# get a list of all speakers:
speakers = sc.all_speakers()
# get the current default speaker on your system:
default_speaker = sc.default_speaker()
# get a list of all microphones:
mics = sc.all_microphones()
# get the current default microphone on your system:
default_mic = sc.default_microphone()

All of these functions return Speaker and Microphone objects, which can be used for playback and recording. All data passed in and out of these objects are frames × channels Numpy arrays.

That should be why by default panon displays input (micro) instead of output (speakers). And that could be a way to get both at once and add them together within panon for display...

rbn42 commented 5 years ago

All of these functions return Speaker and Microphone objects, which can be used for playback and recording

It means, a Speaker object has the play() function, while a Microphone object has the record() function. But a Speaker object does not have the record() function.

rbn42 commented 5 years ago

I mean, I cannot fetch data from a Speaker. SoundCard does not allow me to do that.

kupiqu commented 5 years ago

I mean, I cannot fetch data from a Speaker. SoundCard does not allow me to do that.

Yes right, you told me that before, sorry I forgot.

By the way this last commit is amazing! Kudos

rbn42 commented 5 years ago

Glad you like it