From 3ba91c66e9c3f8b2e167592cf229592f0f06d9ab Mon Sep 17 00:00:00 2001 From: Elias Date: Sat, 2 Dec 2023 20:36:52 -0500 Subject: [PATCH 1/4] Add timeout parameter to sounddevice.wait() function This commit introduces a new `timeout` parameter to the `wait()` function in the `sounddevice` module. The addition of the `timeout` argument enhances the flexibility of the `wait()` function, allowing users to specify a maximum duration to wait for audio playback/recording to finish. This feature is particularly useful in scenarios where blocking the thread indefinitely is not desirable or practical. Changes: - Modified the `wait()` function definition to include an optional `timeout` parameter. - Updated the `wait()` function's docstring to describe the new parameter and its behavior. - Adjusted the internal `_CallbackContext.wait()` method to handle the timeout functionality, ensuring that the function returns a boolean indicating whether the operation completed before the timeout or if the timeout was reached. - Ensured backward compatibility by setting the default value of `timeout` to `None`, preserving the existing behavior of waiting indefinitely until the operation completes. This update provides users with greater control over their audio operations without working directly with the stream, making the `sounddevice` module more versatile for a variety of applications. --- sounddevice.py | 54 ++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 41 insertions(+), 13 deletions(-) diff --git a/sounddevice.py b/sounddevice.py index e292c4f..daebf59 100644 --- a/sounddevice.py +++ b/sounddevice.py @@ -375,24 +375,35 @@ def callback(indata, outdata, frames, time, status): return out -def wait(ignore_errors=True): - """Wait for `play()`/`rec()`/`playrec()` to be finished. +def wait(ignore_errors=True, timeout=None): + """Wait for `play()`/`rec()`/`playrec()` to be finished with an optional timeout. - Playback/recording can be stopped with a `KeyboardInterrupt`. + Playback/recording can be stopped with a `KeyboardInterrupt`. If a timeout is specified, + the wait will return after the timeout period if playback/recording hasn't finished. + + Parameters + ---------- + timeout : float, optional + Maximum number of seconds to wait for `play()`/`rec()`/`playrec()` to be finished. + If None (default), waits until finished or exceptions are raised. + ignore_errors : bool, optional + Whether to ignore errors when closing the stream. Returns ------- - CallbackFlags or None - If at least one buffer over-/underrun happened during the last - playback/recording, a `CallbackFlags` object is returned. + bool or CallbackFlags or None + - CallbackFlags if at least one buffer over-/underrun happened during the last + playback/recording and no timeout was specified. + - None if the operation completes without buffer over-/underrun issues and no timeout is specified. + - True if timeout is specified and playback/recording finished and no exception raised. + - False if timeout is specified and timeout elapses before playback/recording finished and no exception raised. See Also -------- get_status - """ if _last_callback: - return _last_callback.wait(ignore_errors) + return _last_callback.wait(ignore_errors, timeout=timeout) def stop(ignore_errors=True): @@ -2612,16 +2623,33 @@ def start_stream(self, StreamClass, samplerate, channels, dtype, callback, if blocking: self.wait() - def wait(self, ignore_errors=True): - """Wait for finished_callback. + def wait(self, ignore_errors=True, timeout=None): + """ + Wait for a finished_callback with an optional timeout. + + This method waits for the event to be set or for the optional timeout to elapse. + If a timeout is specified and it is reached without the event being set, the method + returns False. Otherwise, it returns the stream status or None. The stream is closed + if it finishes normally or an exception occurs. - Can be interrupted with a KeyboardInterrupt. + Args: + ignore_errors (bool): Whether to ignore errors when closing the stream. + timeout (float, optional): Time in seconds to wait before timing out. None for no timeout. + Returns: + True if the wait completes before the timeout, False if it times out, + or the stream's status (or None) if no timeout is specified. """ + exception_raised = True + finished = False try: - self.event.wait() + finished = self.event.wait(timeout=timeout) + exception_raised = False finally: - self.stream.close(ignore_errors) + if finished or exception_raised: + self.stream.close(ignore_errors) + if timeout and not exception_raised: + return finished return self.status if self.status else None From bdc57ca8054b464820e368655104d88a55dece01 Mon Sep 17 00:00:00 2001 From: Elias Date: Sat, 2 Dec 2023 20:51:01 -0500 Subject: [PATCH 2/4] fixed wrapping of doc string in wait and _CallbackContext.wait() --- sounddevice.py | 39 +++++++++++++++++++++++---------------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/sounddevice.py b/sounddevice.py index daebf59..7a55817 100644 --- a/sounddevice.py +++ b/sounddevice.py @@ -376,15 +376,18 @@ def callback(indata, outdata, frames, time, status): def wait(ignore_errors=True, timeout=None): - """Wait for `play()`/`rec()`/`playrec()` to be finished with an optional timeout. + """Wait for `play()`/`rec()`/`playrec()` to be finished + with an optional timeout. - Playback/recording can be stopped with a `KeyboardInterrupt`. If a timeout is specified, - the wait will return after the timeout period if playback/recording hasn't finished. + Playback/recording can be stopped with a `KeyboardInterrupt`. If a timeout + is specified, the wait will return after the timeout period if + playback/recording hasn't finished. Parameters ---------- timeout : float, optional - Maximum number of seconds to wait for `play()`/`rec()`/`playrec()` to be finished. + Maximum number of seconds to wait for `play()`/`rec()`/`playrec()` + to be finished. If None (default), waits until finished or exceptions are raised. ignore_errors : bool, optional Whether to ignore errors when closing the stream. @@ -392,11 +395,14 @@ def wait(ignore_errors=True, timeout=None): Returns ------- bool or CallbackFlags or None - - CallbackFlags if at least one buffer over-/underrun happened during the last - playback/recording and no timeout was specified. - - None if the operation completes without buffer over-/underrun issues and no timeout is specified. - - True if timeout is specified and playback/recording finished and no exception raised. - - False if timeout is specified and timeout elapses before playback/recording finished and no exception raised. + - CallbackFlags if at least one buffer over-/underrun happened during + the last playback/recording and no timeout was specified. + - None if the operation completes without buffer over-/underrun issues + and no timeout is specified. + - True if timeout is specified and playback/recording finished and no + exception raised. + - False if timeout is specified and timeout elapses before + playback/recording finished and no exception raised. See Also -------- @@ -2627,17 +2633,18 @@ def wait(self, ignore_errors=True, timeout=None): """ Wait for a finished_callback with an optional timeout. - This method waits for the event to be set or for the optional timeout to elapse. - If a timeout is specified and it is reached without the event being set, the method - returns False. Otherwise, it returns the stream status or None. The stream is closed - if it finishes normally or an exception occurs. + This method waits for the event to be set or for the optional timeout + to elapse. If a timeout is specified, and it is reached without the + event being set, the method returns False. Otherwise, it returns the + stream status or None. The stream is closed if it finishes normally or + an exception occurs. Args: - ignore_errors (bool): Whether to ignore errors when closing the stream. - timeout (float, optional): Time in seconds to wait before timing out. None for no timeout. + ignore_errors (bool): Whether to ignore errors when closing stream. + timeout (float, optional): Time in seconds to wait before time out. Returns: - True if the wait completes before the timeout, False if it times out, + True if the wait completes before timeout, False if it times out, or the stream's status (or None) if no timeout is specified. """ exception_raised = True From 42dea616e21365966a0ceb8e9eaad0adab47aca8 Mon Sep 17 00:00:00 2001 From: Elias <56934916+elias-jhsph@users.noreply.github.com> Date: Tue, 5 Dec 2023 17:26:01 -0500 Subject: [PATCH 3/4] Update sounddevice.py fixing bullet list for sphinx --- sounddevice.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/sounddevice.py b/sounddevice.py index 7a55817..4d279e4 100644 --- a/sounddevice.py +++ b/sounddevice.py @@ -396,14 +396,14 @@ def wait(ignore_errors=True, timeout=None): ------- bool or CallbackFlags or None - CallbackFlags if at least one buffer over-/underrun happened during - the last playback/recording and no timeout was specified. + the last playback/recording and no timeout was specified. - None if the operation completes without buffer over-/underrun issues - and no timeout is specified. + and no timeout is specified. - True if timeout is specified and playback/recording finished and no - exception raised. + exception raised. - False if timeout is specified and timeout elapses before - playback/recording finished and no exception raised. - + playback/recording finished and no exception raised. + See Also -------- get_status From 62e0acbc863c979fb1f2dc202e1cc53a11846707 Mon Sep 17 00:00:00 2001 From: Elias <56934916+elias-jhsph@users.noreply.github.com> Date: Tue, 5 Dec 2023 17:27:25 -0500 Subject: [PATCH 4/4] Update sounddevice.py --- sounddevice.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sounddevice.py b/sounddevice.py index 4d279e4..b1252a1 100644 --- a/sounddevice.py +++ b/sounddevice.py @@ -403,7 +403,7 @@ def wait(ignore_errors=True, timeout=None): exception raised. - False if timeout is specified and timeout elapses before playback/recording finished and no exception raised. - + See Also -------- get_status