1+ """SDL2 audio playback and recording tools.
2+
3+ .. versionadded:: unreleased
4+ """
15from __future__ import annotations
26
37import enum
812
913import numpy as np
1014from numpy .typing import ArrayLike , DTypeLike , NDArray
11- from typing_extensions import Literal
15+ from typing_extensions import Final , Literal
1216
1317import tcod .sdl .sys
1418from tcod .loader import ffi , lib
@@ -52,7 +56,10 @@ def _dtype_from_format(format: int) -> np.dtype[Any]:
5256
5357
5458class AudioDevice :
55- """An SDL audio device."""
59+ """An SDL audio device.
60+
61+ Open new audio devices using :any:`tcod.sdl.audio.open`.
62+ """
5663
5764 def __init__ (
5865 self ,
@@ -63,20 +70,30 @@ def __init__(
6370 assert device_id >= 0
6471 assert ffi .typeof (spec ) is ffi .typeof ("SDL_AudioSpec*" )
6572 assert spec
66- self .device_id = device_id
67- self .spec = spec
68- self .frequency = spec .freq
69- self .is_capture = capture
70- self .format = _dtype_from_format (spec .format )
71- self .channels = int (spec .channels )
72- self .silence = int (spec .silence )
73- self .samples = int (spec .samples )
74- self .buffer_size = int (spec .size )
73+ self .device_id : Final [int ] = device_id
74+ """The SDL device identifier used for SDL C functions."""
75+ self .spec : Final [Any ] = spec
76+ """The SDL_AudioSpec as a CFFI object."""
77+ self .frequency : Final [int ] = spec .freq
78+ """The audio device sound frequency."""
79+ self .is_capture : Final [bool ] = capture
80+ """True if this is a recording device instead of an output device."""
81+ self .format : Final [np .dtype [Any ]] = _dtype_from_format (spec .format )
82+ """The format used for audio samples with this device."""
83+ self .channels : Final [int ] = int (spec .channels )
84+ """The number of audio channels for this device."""
85+ self .silence : float = int (spec .silence )
86+ """The value of silence, according to SDL."""
87+ self .buffer_samples : Final [int ] = int (spec .samples )
88+ """The size of the audio buffer in samples."""
89+ self .buffer_bytes : Final [int ] = int (spec .size )
90+ """The size of the audio buffer in bytes."""
7591 self ._handle : Optional [Any ] = None
7692 self ._callback : Callable [[AudioDevice , NDArray [Any ]], None ] = self .__default_callback
7793
7894 @property
7995 def callback (self ) -> Callable [[AudioDevice , NDArray [Any ]], None ]:
96+ """If the device was opened with a callback enabled, then you may get or set the callback with this attribute."""
8097 if self ._handle is None :
8198 raise TypeError ("This AudioDevice was opened without a callback." )
8299 return self ._callback
@@ -89,6 +106,7 @@ def callback(self, new_callback: Callable[[AudioDevice, NDArray[Any]], None]) ->
89106
90107 @property
91108 def _sample_size (self ) -> int :
109+ """The size of a sample in bytes."""
92110 return self .format .itemsize * self .channels
93111
94112 @property
@@ -119,9 +137,15 @@ def _convert_array(self, samples_: ArrayLike) -> NDArray[Any]:
119137 return np .ascontiguousarray (np .broadcast_to (samples , (samples .shape [0 ], self .channels )), dtype = self .format )
120138
121139 @property
122- def queued_audio_bytes (self ) -> int :
140+ def _queued_bytes (self ) -> int :
141+ """The current amount of bytes remaining in the audio queue."""
123142 return int (lib .SDL_GetQueuedAudioSize (self .device_id ))
124143
144+ @property
145+ def queued_samples (self ) -> int :
146+ """The current amount of samples remaining in the audio queue."""
147+ return self ._queued_bytes // self ._sample_size
148+
125149 def queue_audio (self , samples : ArrayLike ) -> None :
126150 """Append audio samples to the audio data queue."""
127151 assert not self .is_capture
@@ -132,7 +156,7 @@ def queue_audio(self, samples: ArrayLike) -> None:
132156 def dequeue_audio (self ) -> NDArray [Any ]:
133157 """Return the audio buffer from a capture stream."""
134158 assert self .is_capture
135- out_samples = self .queued_audio_bytes // self ._sample_size
159+ out_samples = self ._queued_bytes // self ._sample_size
136160 out = np .empty ((out_samples , self .channels ), self .format )
137161 buffer = ffi .from_buffer (out )
138162 bytes_returned = lib .SDL_DequeueAudio (self .device_id , buffer , len (buffer ))
@@ -144,11 +168,11 @@ def __del__(self) -> None:
144168 self .close ()
145169
146170 def close (self ) -> None :
147- """Close this audio device."""
148- if not self . device_id :
171+ """Close this audio device. Using this object after it has been closed is invalid. """
172+ if not hasattr ( self , " device_id" ) :
149173 return
150174 lib .SDL_CloseAudioDevice (self .device_id )
151- self .device_id = 0
175+ del self .device_id
152176
153177 @staticmethod
154178 def __default_callback (device : AudioDevice , stream : NDArray [Any ]) -> None :
@@ -218,9 +242,11 @@ def __init__(self, device: AudioDevice):
218242 self .device = device
219243
220244 def run (self ) -> None :
221- buffer = np .full ((self .device .samples , self .device .channels ), self .device .silence , dtype = self .device .format )
245+ buffer = np .full (
246+ (self .device .buffer_samples , self .device .channels ), self .device .silence , dtype = self .device .format
247+ )
222248 while True :
223- if self .device .queued_audio_bytes > 0 :
249+ if self .device ._queued_bytes > 0 :
224250 time .sleep (0.001 )
225251 continue
226252 self .on_stream (buffer )
0 commit comments