spatialaudio / python-sounddevice

:sound: Play and Record Sound with Python :snake:
https://python-sounddevice.readthedocs.io/
MIT License
982 stars 145 forks source link

sounddevice.play() with blocking=False don't play sound #342

Open bactone opened 3 years ago

bactone commented 3 years ago

I am trying to realize asynchronously play and record using play() and rec() methods, but when I set blocking=False for play(), it doesn't play sound anymore, here bellow is my demo-code, please help:

import sounddevice as sd
import numpy as np
import plotly.graph_objects as go
from plotly.offline import plot
from scipy.io import wavfile

fs = 44100  # sampling rate
f = 1000  # frequency
length = 4.5  # s,
padding = 5  # s,
duration = length + padding  # s

sd.default.device = 'ASIO Fireface USB', 'ASIO Fireface USB'
t1 = np.arange(fs * length)
pt1 = 0.005 * np.sin(2 * np.pi * f / fs * t1)

sd.play(pt1, samplerate=fs, mapping=1, blocking=False)  # when blocking=False, it doesn't play sound anymore

myrecording = sd.rec(frames=int(duration * fs), samplerate=fs, blocking=False,
                     channels=1, mapping=1, dtype="int16")

x = np.arange(fs * duration) / fs
y = myrecording.T

trace = [None]*len(y)
for i in range(len(y)):
    trace[i] = go.Scatter(x=x, y=y[i])
fig = plot(trace) 
wavfile.write('recording.wav', fs, myrecording)
HaHeho commented 3 years ago

You probably want to use playrec() for that.

mgeier commented 3 years ago

Yes, exactly.

The behavior of sd.rec() is described at https://python-sounddevice.readthedocs.io/en/latest/api/convenience-functions.html#sounddevice.rec.

And according to that, the first thing it does, is calling sd.stop(), which immediately stops the playback of sd.play().

At some point you should also wait for your recording to be finished, otherwise myrecording will not contain the expected data.

bactone commented 3 years ago

You probably want to use playrec() for that.

@HaHeho while, playrec() is not what I want. my application scenario is like this: first, I want to use play() to play a period of sound as the background sound, like 100 seconds. when I call play() with blocking=False, i expect it to return immediately but playback continues in the background, like the doc https://python-sounddevice.readthedocs.io/en/0.4.1/api/convenience-functions.html explained, so that the subsequent code (rec() or any other code) can be executed immediately, but when I set blocking=False, it quit playing sound. here bellow is a new demo code:

  1. set blocking=False, "returns immediately" will be print immediately, but no sound is played
    
    import numpy as np
    import sounddevice as sd

fs = 44100 # sampling rate f = 1000 # frequency length = 3 # play time t = np.arange(fs length) # pt = 0.005 np.sin(2 np.pi f / fs * t) # data

sd.default.device = 'ASIO Fireface USB' asio_out = sd.AsioSettings(channel_selectors=[0])

sd.play(pt, fs, blocking=False, extra_settings=asio_out, loop=False) print("returns immediately")


2. set blocking=True,  the sound is played, and then "returns immediately" is printed.
```python
import numpy as np
import sounddevice as sd

fs = 44100  # sampling rate
f = 1000  # frequency
length = 3  # play time
t = np.arange(fs * length)  #
pt = 0.005 * np.sin(2 * np.pi * f / fs * t)  # data

sd.default.device = 'ASIO Fireface USB'
asio_out = sd.AsioSettings(channel_selectors=[0])

sd.play(pt, fs, blocking=True, extra_settings=asio_out, loop=False)
print("returns immediately")
bactone commented 3 years ago

Yes, exactly.

The behavior of sd.rec() is described at https://python-sounddevice.readthedocs.io/en/latest/api/convenience-functions.html#sounddevice.rec.

And according to that, the first thing it does, is calling sd.stop(), which immediately stops the playback of sd.play().

At some point you should also wait for your recording to be finished, otherwise myrecording will not contain the expected data.

@mgeier thanks, I notice that every time play(), rec(), or playrec() is called, it will stop the current running play or record. but even if I don't call rec() after play() with blocking=False, the play() don't play the sound, pls refer to my reply to @HaHeho

mgeier commented 3 years ago

but even if I don't call rec() after play() with blocking=False, the play() don't play the sound

I guess this happens at the end of your Python script?

In this case, sd.play() returns immediately, and since it is the end of the script, the Python interpreter shuts down, which automatically stops the playback (probably before it even starts playing).

You'll have to do something to keep the Python interpreter running. For example, you could run your script with python3 my_script.py -i, which would keep the interpreter running after the end of the script. Or you use sd.wait() or blocking=True.

bactone commented 3 years ago

I guess this happens at the end of your Python script?

And according to that, the first thing it does, is calling sd.stop(), which immediately stops the playback of sd.play().

@mgeier thanks, I just tried your suggestion and it works. so my question now is : is it ok to quit calling sd.stop() internally when calling play(), rec(), or playrec() , so that we can use play() and rec() (rather than playrec()) with parameter blocking=False asynchronously, besides we may set an ID for each play(), rec() or playrec() instance, and then we can call sd.stop(ID1), sd.wait(ID1) manually etc, i need your suggestions , thanks.

mgeier commented 3 years ago

is it ok to quit calling sd.stop() internally when calling play(), rec(), or playrec()

This would have been possible, but I decided against it.

For me, the main use case for play(), rec() and playrec() is in an interactive Python session. The API is optimized for that.

They can of course also be used in non-interactive scripts, but when the situation becomes more complicated, the "stream" API should be used instead.

we may set an ID for each play(), rec() or playrec() instance, and then we can call sd.stop(ID1), sd.wait(ID1) manually etc,

Again, this would have been possible, but I decided against it to keep the interactive use simple.

If you want such a behavior, you can implement it yourself based on the "stream" API.