Skip to content

Commit b069532

Browse files
committed
Document the SDL audio module.
1 parent e0da84a commit b069532

File tree

4 files changed

+63
-31
lines changed

4 files changed

+63
-31
lines changed

docs/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ Contents:
3939
tcod/render
4040
tcod/tileset
4141
libtcodpy
42+
sdl/audio
4243
sdl/render
4344
sdl/mouse
4445
sdl/video

docs/sdl/audio.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
tcod.sdl.audio - SDL Audio
2+
==========================
3+
4+
.. automodule:: tcod.sdl.audio
5+
:members:

tcod/sdl/audio.py

Lines changed: 44 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
"""SDL2 audio playback and recording tools.
2+
3+
.. versionadded:: unreleased
4+
"""
15
from __future__ import annotations
26

37
import enum
@@ -8,7 +12,7 @@
812

913
import numpy as np
1014
from numpy.typing import ArrayLike, DTypeLike, NDArray
11-
from typing_extensions import Literal
15+
from typing_extensions import Final, Literal
1216

1317
import tcod.sdl.sys
1418
from tcod.loader import ffi, lib
@@ -52,7 +56,10 @@ def _dtype_from_format(format: int) -> np.dtype[Any]:
5256

5357

5458
class 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)

tcod/sdl/sys.py

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,14 @@
99

1010

1111
class Subsystem(enum.IntFlag):
12-
TIMER = lib.SDL_INIT_TIMER or 0x00000001
13-
AUDIO = lib.SDL_INIT_AUDIO or 0x00000010
14-
VIDEO = lib.SDL_INIT_VIDEO or 0x00000020
15-
JOYSTICK = lib.SDL_INIT_JOYSTICK or 0x00000200
16-
HAPTIC = lib.SDL_INIT_HAPTIC or 0x00001000
17-
GAMECONTROLLER = lib.SDL_INIT_GAMECONTROLLER or 0x00002000
18-
EVENTS = lib.SDL_INIT_EVENTS or 0x00004000
19-
SENSOR = getattr(lib, "SDL_INIT_SENSOR", None) or 0x00008000 # SDL >= 2.0.9
12+
TIMER = 0x00000001
13+
AUDIO = 0x00000010
14+
VIDEO = 0x00000020
15+
JOYSTICK = 0x00000200
16+
HAPTIC = 0x00001000
17+
GAMECONTROLLER = 0x00002000
18+
EVENTS = 0x00004000
19+
SENSOR = 0x00008000
2020
EVERYTHING = lib.SDL_INIT_EVERYTHING or 0
2121

2222

@@ -49,11 +49,11 @@ def __exit__(self, *args: Any) -> None:
4949

5050

5151
class _PowerState(enum.IntEnum):
52-
UNKNOWN = getattr(lib, "SDL_POWERSTATE_UNKNOWN", 0)
53-
ON_BATTERY = getattr(lib, "SDL_POWERSTATE_ON_BATTERY", 0)
54-
NO_BATTERY = getattr(lib, "SDL_POWERSTATE_NO_BATTERY", 0)
55-
CHARGING = getattr(lib, "SDL_POWERSTATE_CHARGING", 0)
56-
CHARGED = getattr(lib, "SDL_POWERSTATE_CHARGED", 0)
52+
UNKNOWN = 0
53+
ON_BATTERY = enum.auto()
54+
NO_BATTERY = enum.auto()
55+
CHARGING = enum.auto()
56+
CHARGED = enum.auto()
5757

5858

5959
def _get_power_info() -> Tuple[_PowerState, int, int]:

0 commit comments

Comments
 (0)