spatialaudio / python-sounddevice

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

Enhancement Request: Integrate Playback Status Check into Simple `play`/`stop` API in `sounddevice` #507

Open elias-jhsph opened 7 months ago

elias-jhsph commented 7 months ago

Summary

This issue proposes integrating a feature for checking the playback status into the simpler play/stop API of the sounddevice module. While the module does offer more robust methods for handling audio playback, having this functionality readily available in the simpler API would greatly enhance usability for basic use cases.

Background

Currently, in my application, I use the sounddevice module's simple API for playing audio files. However, to check if the playback has finished, I have to resort to accessing a private member (_last_callback), as shown below:

import sounddevice as sd
import soundfile as sf

# ... middle of function doing other things
for loop in range(loops):
    data, fs = sf.read(file_path)
    sd.play(data, fs)
    while not sd._last_callback.event.is_set():
        if stop_event.is_set() or (added_stop_event and added_stop_event.is_set()):
            sd.stop()
            break
        if added_stop_event:
            added_stop_event.wait(timeout=.1)
        else:
            stop_event.wait(timeout=.1)

Recognizing the More Robust API

I am aware that sounddevice offers a more robust API that could potentially handle these scenarios more effectively. However, for simpler applications or for users who are not deeply versed in audio programming, the complexity of the robust API can be a barrier.

Proposal

I propose enhancing the simpler play/stop API by adding a method or property to check the playback status. This could be a method like is_playing() or a property like playback_status. This enhancement would not only make the code more readable and maintainable but also provide a more accessible way for users to handle basic audio playback tasks.

Example of Proposed Solution

With the proposed enhancement, the implementation would look like this:

import sounddevice as sd
import soundfile as sf

# ... middle of function doing other things
for loop in range(loops):
    data, fs = sf.read(file_path)
    sd.play(data, fs)
    while sd.is_playing():  # Proposed method
        if stop_event.is_set() or (added_stop_event and added_stop_event.is_set()):
            sd.stop()
            break
        if added_stop_event:
            added_stop_event.wait(timeout=.1)
        else:
            stop_event.wait(timeout=.1)

Conclusion

Integrating a playback status check into the simple play/stop API would not only cater to users seeking simplicity but also make the sounddevice module more versatile and accessible to a broader range of users, from beginners to advanced programmers.

mgeier commented 7 months ago

What about this:

while sd.get_stream().active:
    # ...

See https://github.com/spatialaudio/python-sounddevice/issues/69#issuecomment-274597728 and https://github.com/spatialaudio/python-sounddevice/pull/70.

elias-jhsph commented 7 months ago

That's much better than what I have currently, and maybe I'm off base... But I do think since there is a play, wait, and stop it really feels like it's intuitive to have some way to wait for a short time (maybe a timeout kwarg for wait to mirror threads approach). It just feels like this level of the api is so close to a full set.

If that is the best approach maybe it should be in an example, reason being that people that want that high level use probably need the help the most with that, though it has big readability upsides for simple uses even for more advanced developers.

mgeier commented 7 months ago

If that is the best approach

I think it is the best approach for what you ask for in the title of this issue and in your proposal above.

However, it's not the best approach for what you are asking now:

it really feels like it's intuitive to have some way to wait for a short time (maybe a timeout kwarg for wait to mirror threads approach).

I agree, a timeout argument would be a nice addition to sd.wait().

Do you want to make a PR for this?

elias-jhsph commented 7 months ago

Would love to!