4747import sys
4848import threading
4949import time
50+ from types import TracebackType
5051from typing import Any , Callable , Hashable , Iterator
5152
5253import numpy as np
5354from numpy .typing import ArrayLike , DTypeLike , NDArray
54- from typing_extensions import Final , Literal
55+ from typing_extensions import Final , Literal , Self
5556
5657import tcod .sdl .sys
5758from tcod .loader import ffi , lib
@@ -110,6 +111,9 @@ def convert_audio(
110111
111112 .. versionadded:: 13.6
112113
114+ .. versionchanged:: Unreleased
115+ Now converts floating types to `np.float32` when SDL doesn't support the specific format.
116+
113117 .. seealso::
114118 :any:`AudioDevice.convert`
115119 """
@@ -123,8 +127,26 @@ def convert_audio(
123127 in_channels = in_array .shape [1 ]
124128 in_format = _get_format (in_array .dtype )
125129 out_sdl_format = _get_format (out_format )
126- if _check (lib .SDL_BuildAudioCVT (cvt , in_format , in_channels , in_rate , out_sdl_format , out_channels , out_rate )) == 0 :
127- return in_array # No conversion needed.
130+ try :
131+ if (
132+ _check (lib .SDL_BuildAudioCVT (cvt , in_format , in_channels , in_rate , out_sdl_format , out_channels , out_rate ))
133+ == 0
134+ ):
135+ return in_array # No conversion needed.
136+ except RuntimeError as exc :
137+ if ( # SDL now only supports float32, but later versions may add more support for more formats.
138+ exc .args [0 ] == "Invalid source format"
139+ and np .issubdtype (in_array .dtype , np .floating )
140+ and in_array .dtype != np .float32
141+ ):
142+ return convert_audio ( # Try again with float32
143+ in_array .astype (np .float32 ),
144+ in_rate ,
145+ out_rate = out_rate ,
146+ out_format = out_format ,
147+ out_channels = out_channels ,
148+ )
149+ raise
128150 # Upload to the SDL_AudioCVT buffer.
129151 cvt .len = in_array .itemsize * in_array .size
130152 out_buffer = cvt .buf = ffi .new ("uint8_t[]" , cvt .len * cvt .len_mult )
@@ -144,6 +166,9 @@ class AudioDevice:
144166
145167 When you use this object directly the audio passed to :any:`queue_audio` is always played synchronously.
146168 For more typical asynchronous audio you should pass an AudioDevice to :any:`BasicMixer`.
169+
170+ .. versionchanged:: Unreleased
171+ Can now be used as a context which will close the device on exit.
147172 """
148173
149174 def __init__ (
@@ -176,6 +201,23 @@ def __init__(
176201 self ._handle : Any | None = None
177202 self ._callback : Callable [[AudioDevice , NDArray [Any ]], None ] = self .__default_callback
178203
204+ def __repr__ (self ) -> str :
205+ """Return a representation of this device."""
206+ items = [
207+ f"{ self .__class__ .__name__ } (device_id={ self .device_id } )" ,
208+ f"frequency={ self .frequency } " ,
209+ f"is_capture={ self .is_capture } " ,
210+ f"format={ self .format } " ,
211+ f"channels={ self .channels } " ,
212+ f"buffer_samples={ self .buffer_samples } " ,
213+ f"buffer_bytes={ self .buffer_bytes } " ,
214+ ]
215+ if self .silence :
216+ items .append (f"silence={ self .silence } " )
217+ if self ._handle is not None :
218+ items .append (f"callback={ self ._callback } " )
219+ return f"""<{ " " .join (items )} >"""
220+
179221 @property
180222 def callback (self ) -> Callable [[AudioDevice , NDArray [Any ]], None ]:
181223 """If the device was opened with a callback enabled, then you may get or set the callback with this attribute."""
@@ -288,6 +330,16 @@ def close(self) -> None:
288330 lib .SDL_CloseAudioDevice (self .device_id )
289331 del self .device_id
290332
333+ def __enter__ (self ) -> Self :
334+ """Return self and enter a managed context."""
335+ return self
336+
337+ def __exit__ (
338+ self , type : type [BaseException ] | None , value : BaseException | None , traceback : TracebackType | None
339+ ) -> None :
340+ """Close the device when exiting the context."""
341+ self .close ()
342+
291343 @staticmethod
292344 def __default_callback (device : AudioDevice , stream : NDArray [Any ]) -> None :
293345 stream [...] = device .silence
@@ -487,7 +539,7 @@ class _AudioCallbackUserdata:
487539@ffi .def_extern () # type: ignore
488540def _sdl_audio_callback (userdata : Any , stream : Any , length : int ) -> None :
489541 """Handle audio device callbacks."""
490- data : _AudioCallbackUserdata = ffi .from_handle (userdata )()
542+ data : _AudioCallbackUserdata = ffi .from_handle (userdata )
491543 device = data .device
492544 buffer = np .frombuffer (ffi .buffer (stream , length ), dtype = device .format ).reshape (- 1 , device .channels )
493545 device ._callback (device , buffer )
0 commit comments