From 818d8fc068f4e6f27982751ff996ca857fb00bbb Mon Sep 17 00:00:00 2001 From: pushfoo <36696816+pushfoo@users.noreply.github.com> Date: Mon, 22 Jul 2024 01:19:11 -0400 Subject: [PATCH 1/3] Convert arcade.sound to Google docstrings --- arcade/sound.py | 98 +++++++++++++++++++++++++------------------------ 1 file changed, 51 insertions(+), 47 deletions(-) diff --git a/arcade/sound.py b/arcade/sound.py index c6484a0130..e8a3edcb67 100644 --- a/arcade/sound.py +++ b/arcade/sound.py @@ -1,6 +1,4 @@ -""" -Sound Library. -""" +"""Sound Library.""" from __future__ import annotations @@ -57,13 +55,14 @@ def play( loop: bool = False, speed: float = 1.0, ) -> media.Player: - """ - Play the sound. - - :param volume: Volume, from 0=quiet to 1=loud - :param pan: Pan, from -1=left to 0=centered to 1=right - :param loop: Loop, false to play once, true to loop continuously - :param speed: Change the speed of the sound which also changes pitch, default 1.0 + """Play the sound. + + Args: + volume: Volume, from 0=quiet to 1=loud + pan: Pan, from -1=left to 0=centered to 1=right + loop: Loop, false to play once, true to loop continuously + speed: Change the speed of the sound which also changes + pitch, default 1.0 """ if isinstance(self.source, media.StreamingSource) and self.source.is_player_source: raise RuntimeError( @@ -105,9 +104,7 @@ def _on_player_eos(): return player def stop(self, player: media.Player) -> None: - """ - Stop a currently playing sound. - """ + """Stop a currently playing sound.""" player.pause() player.delete() if player in media.Source._players: @@ -124,53 +121,58 @@ def is_complete(self, player: media.Player) -> bool: return player.time >= self.source.duration # type: ignore def is_playing(self, player: media.Player) -> bool: - """ - Return if the sound is currently playing or not + """Return if the sound is currently playing or not - :param player: Player returned from :func:`play_sound`. - :returns: A boolean, ``True`` if the sound is playing. + Args: + player: Player returned from :func:`play_sound`. + Returns: + A boolean, ``True`` if the sound is playing. """ return player.playing def get_volume(self, player: media.Player) -> float: - """ - Get the current volume. + """Get the current volume. - :param player: Player returned from :func:`play_sound`. - :returns: A float, 0 for volume off, 1 for full volume. + Args: + player: Player returned from :func:`play_sound`. + + Returns: + A float, 0 for volume off, 1 for full volume. """ return player.volume # type: ignore # pending https://github.com/pyglet/pyglet/issues/847 def set_volume(self, volume, player: media.Player) -> None: - """ - Set the volume of a sound as it is playing. + """Set the volume of a sound as it is playing. - :param volume: Floating point volume. 0 is silent, 1 is full. - :param player: Player returned from :func:`play_sound`. + Args: + volume: Floating point volume. 0 is silent, 1 is full. + player: Player returned from :func:`play_sound`. """ player.volume = volume def get_stream_position(self, player: media.Player) -> float: - """ - Return where we are in the stream. This will reset back to + """Return where we are in the stream. This will reset back to zero when it is done playing. - :param player: Player returned from :func:`play_sound`. - + Args: + player: Player returned from :func:`play_sound`. """ return player.time def load_sound(path: str | Path, streaming: bool = False) -> Sound: - """ - Load a sound. + """Load a sound. + + Args: + path: Name of the sound file to load. + streaming: Boolean for determining if we stream the sound or + load it all into memory. Set to ``True`` for long sounds to + save memory, ``False`` for short sounds to speed playback. - :param path: Name of the sound file to load. - :param streaming: Boolean for determining if we stream the sound - or load it all into memory. Set to ``True`` for long sounds to save - memory, ``False`` for short sounds to speed playback. - :returns: Sound object which can be used by the :func:`play_sound` function. + Returns: + Sound object which can be used by the :func:`play_sound` + function. """ # Initialize the audio driver if it hasn't been already. # This call is to avoid audio driver initialization @@ -194,14 +196,16 @@ def play_sound( loop: bool = False, speed: float = 1.0, ) -> media.Player | None: - """ - Play a sound. - - :param sound: Sound loaded by :func:`load_sound`. Do NOT use a string here for the filename. - :param volume: Volume, from 0=quiet to 1=loud - :param pan: Pan, from -1=left to 0=centered to 1=right - :param loop: Should we loop the sound over and over? - :param speed: Change the speed of the sound which also changes pitch, default 1.0 + """Play a sound. + + Args: + sound: Sound loaded by :func:`load_sound`. Do NOT use a string + here for the filename. + volume: Volume, from 0=quiet to 1=loud + pan: Pan, from -1=left to 0=centered to 1=right + loop: Should we loop the sound over and over? + speed: Change the speed of the sound which also changes pitch, + default 1.0 """ if sound is None: logger.warning("Unable to play sound, no data passed in.") @@ -222,10 +226,10 @@ def play_sound( def stop_sound(player: media.Player): - """ - Stop a sound that is currently playing. + """Stop a sound that is currently playing. - :param player: Player returned from :func:`play_sound`. + Args: + player: Player returned from :func:`play_sound`. """ if not isinstance(player, media.Player): From 301d62dbf66fd748c71060ee0aa42f266b224e21 Mon Sep 17 00:00:00 2001 From: pushfoo <36696816+pushfoo@users.noreply.github.com> Date: Mon, 22 Jul 2024 05:46:13 -0400 Subject: [PATCH 2/3] Fix issues from Xero's feedback in the Sound guide * Focus the basics section more tightly on basics and less on theory * Use coin sounds instead of hurt sounds to promote G-rated content * Add centralized argument table for sound loading as prototype for playback args --- doc/programming_guide/sound.rst | 223 +++++++++++++++++++------------- 1 file changed, 133 insertions(+), 90 deletions(-) diff --git a/doc/programming_guide/sound.rst b/doc/programming_guide/sound.rst index 0f4a424734..1915231693 100644 --- a/doc/programming_guide/sound.rst +++ b/doc/programming_guide/sound.rst @@ -74,39 +74,58 @@ Sound Basics Loading Sounds ^^^^^^^^^^^^^^ -Before you can play a sound, you need to load its data into memory. +To play audio, you must first load its data into a :py:class:`~arcade.sound.Sound` +object. -Arcade provides two ways to do this. Both accept the same arguments and -return an :py:class:`arcade.Sound` instance. +Arcade has two ways to do this: -The easiest way is to use :py:func:`arcade.load_sound`: +* :py:func:`arcade.load_sound` +* :py:class:`arcade.Sound` + +Both provide a :py:class:`arcade.Sound` instance and accept the same arguments: + +.. list-table:: + :header-rows: 1 + + * - Argument + - Type + - Meaning + * - ``path`` + - :py:class:`str` **|** :py:class:`~pathlib.Path` + - A sound file (may use :ref:`resource_handles`) + * - ``streaming`` + - :py:class:`bool` + - * ``True`` streams from disk + * ``False`` loads the whole file + +The simplest option is to use :py:func:`arcade.load_sound`: .. code-block:: python + from pathlib import Path import arcade - # You can pass strings containing a built-in resource handle, - hurt_sound = arcade.load_sound(":resources:sounds/hurt1.wav") - # a pathlib.Path, - pathlib_sound = arcade.load_sound(Path("imaginary\\windows\\path\\file.wav")) - # or an ordinary string describing a path. - string_path_sound = arcade.load_sound("imaginary/mac/style/path.wav") + # The path argument accepts paths prefixed with resources handle, + from_handle_prefix = arcade.load_sound(":resources:sounds/hurt1.wav") + # Windows-style backslash paths, + from_windows_path = arcade.load_sound(Path(r"sounds\windows\file.wav")) + # or pathlib.Path objects: + from_pathlib_path = arcade.load_sound(Path("imaginary/mac/style/path.wav")) -If you prefer a more object-oriented style, you can create -:py:class:`~arcade.Sound` instances directly: +For an object-oriented approach, create :py:class:`~arcade.Sound` instances +directly: .. code-block:: python from arcade import Sound # You can also use arcade.Sound directly - # Although Sound accepts the same arguments as load_sound, - # only the built-in resource handle is shown here. - hurt_sound = Sound(":resources:sounds/hurt1.wav") + # For music files and ambiance tracks, streaming=True is usually best + streaming_music_file = Sound(":resources:music/1918.mp3", streaming=True) -See the following to learn more: +To learn more, please see the following: #. :ref:`resources` -#. :py:mod:`pathlib` +#. Python's built-in :py:class:`pathlib.Path` #. :ref:`sound-loading-modes` .. _sound-basics-playing: @@ -114,98 +133,107 @@ See the following to learn more: Playing Sounds ^^^^^^^^^^^^^^ -There are two easy ways to play a :py:class:`~arcade.Sound` object. +Arcade has two easy ways to play loaded :py:class:`~arcade.Sound` data. -One is to call :py:meth:`Sound.play ` directly: +Imagine you've loaded the following built-in sound file: .. code-block:: python - self.hurt_player = hurt_sound.play() + COIN_SOUND = arcade.load_sound(":resources:sounds/coin1.wav") -The other is to pass a :py:class:`~arcade.Sound` instance as the first -argument of :py:func:`arcade.play_sound`: +The first way to play it is passing it to :py:func:`arcade.play_sound`: .. code-block:: python - # Important: this *must* be a Sound instance, not a path or string! - self.hurt_player = arcade.play_sound(hurt_sound) - -Both return a :py:class:`pyglet.media.player.Player`. You should store -it somewhere if you want to be able to stop or alter a specific playback of -a :py:class:`~arcade.Sound`'s data. - -``arcade.Sound`` vs pyglet's ``Player`` -""""""""""""""""""""""""""""""""""""""" + self.coin_playback = arcade.play_sound(COIN_SOUND) -This is a very important distinction: +We store the return value because it is a special object which lets us +control this specific playback of the :py:class:`~arcade.Sound` data. -* An :py:class:`arcade.Sound` is a source of audio data in memory -* Starting a playback of audio data returns a new pyglet - :py:class:`~pyglet.media.player.Player` which controls that - specific playback +.. important:: You **must** pass a :py:class:`Sound`, not a path! -Imagine you have two non-player characters (NPCs) in a game which -both play the same selection of :py:class:`~arcade.Sound` data. Since -they are separate characters in the world, their playbacks of the data -must be independent. To do this, each NPC will keep the pyglet -:py:class:`~pyglet.media.player.Player` returned when they start -playing a sound. + If you pass :py:func:`arcade.play_sound` anything other + than a :py:class:`~arcade.sound.Sound` or ``None``, it + will raise a :py:class:`TypeError`. -For example, an NPC may get close enough to the user's character to -talk, attack, or perform some other action which requires playing -a different sound. You would handle this as follows: +To avoid making this mistake, you can call the :py:class:`~arcade.Sound` +data's :py:meth:`~arcade.Sound.play` method instead: -#. Use the approaching NPC's pyglet :py:class:`~pyglet.media.player.Player` - to stop its current playback -#. If the NPC starts playing a different sound, store the returned - pyglet :py:class:`~pyglet.media.player.Player` +.. code-block:: python -This is especially important when a dangerous NPC or other hazard can -be invisible. Making invisible hazards play sounds is one of the easiest -and most popular ways of making their gameplay feel balanced, fair, and -fun. + self.coin_playback = COIN_SOUND.play() -See the following to learn more: -#. :ref:`sound-why-important` -#. :ref:`sound_demo` +In each case, the returned object allows stopping and changing a specific playback +of a sound before it finishes. We'll cover this in depth below. .. _sound-basics-stopping: Stopping Sounds ^^^^^^^^^^^^^^^ -Arcade's helper functions are the easiest way to stop playback. To use them: +.. _sound-basics-sound_vs_player: -#. Do one of the following: +Sound data vs Playbacks +""""""""""""""""""""""" - * Pass the stored pyglet :py:class:`~pyglet.media.player.Player` to - :py:func:`arcade.stop_sound`: +Arcade uses the :py:mod:`pyglet` multimedia library to handle sound. - .. code-block:: python +Since pyglet allows playing multiple copies of the same non-streaming +audio at once, each playback has a :py:class:`~pyglet.media.player.Player` +object to control it: - arcade.stop_sound(self.current_playback) +.. code-block:: python - * Pass the stored pyglet :py:class:`~pyglet.media.player.Player` to the - sound's :py:meth:`~arcade.Sound.stop` method: + # We can play the same Sound one, two, or many more times at once + self.coin_playback_1 = arcade.play_sound(COIN_SOUND) + self.coin_playback_2 = COIN_SOUND.play() + self.coin_playback_3 = COIN_SOUND.play() + ... - .. code-block:: python - self.hurt_sound.stop(self.current_playback) +We can create and control a very large number of separate playbacks. +The specific upper limit is usually high enough to be irrelevant. -#. Clear any references to the player to allow its memory to be freed: +Stopping a Specific Playback +"""""""""""""""""""""""""""" - .. code-block:: python +There are two easy ways of stopping a playback of a :py:class:`~arcade.Sound`. - # For each object, Python tracks how many other objects use it. If - # nothing else uses an object, it will be marked as garbage which - # Python can delete automatically to free memory. - self.current_playback = None +The first is to choose which function we'll pass its +:py:class:`~pyglet.media.player.Player` object to: -See the following to learn more: +* :py:func:`arcade.stop_sound`: + + .. code-block:: python + + arcade.stop_sound(self.coin_playback_1) + + +* The :py:class:`~arcade.sound.Sound` data's :py:meth:`~arcade.Sound.stop` + method: + + .. code-block:: python + + self.COIN_SOUND.stop(self.coin_playback_1) + +The last step is to clean up by removing all remaining references to it: + +.. code-block:: python + + # Overwriting them with None is the clearest option + self.current_playback = None + +By default, Python automatically counts how many places use an object. +When there are zero of these "references" left, Python will mark an +object as "garbage" and delete it automatically. This is called "garbage +collection." We'll cover it further in the advanced sections below. + +To learn more about playback limits and stopping, please see the following: * :ref:`sound-compat-easy` * :ref:`sound-advanced-playback` +* :ref:`sound_demo` .. _sound-loading-modes: @@ -214,6 +242,8 @@ Streaming or Static Loading? .. _keyword argument: https://docs.python.org/3/glossary.html#term-argument +The streaming option is important for music and ambiance tracks. + .. list-table:: :header-rows: 1 @@ -232,10 +262,11 @@ Streaming or Static Loading? - Predicted data - 1 copy & file at a time, long, uninterrupted -By default, Arcade decompresses the entirety of each sound into memory. - -This is the best option for most game sound effects. It's called -"static" [#staticsourcefoot]_ audio because the data never changes. +By default, Arcade uses **static** loading: it decompresses the whole +sound file into memory. This is called static [#staticsourcefoot]_ audio' +because the data in memory never changes. It is the default because it +offers multiple benefits to games, especially multiple playbacks at the +same time. The alternative is streaming. Enable it by passing ``True`` through the ``streaming`` `keyword argument`_ when you :ref:`load a sound @@ -248,7 +279,7 @@ The alternative is streaming. Enable it by passing ``True`` through the For an interactive example, see the :ref:`music_control_demo`. -The following subheadings will explain each option in detail. +The subheadings below will explain each option in detail. .. [#meaningbestformatheader] See :ref:`sound-compat-easy` to learn more. @@ -311,11 +342,13 @@ time. Even on the slowest recent hardware, this usually works if: When to Stream """""""""""""" -The best way to use streaming is to only use it when you need it. +In general, avoid streaming things other than music and ambiance. -Advanced users may be able to handle streaming multiple tracks at a -time. However, issues with synchronization & interruptions will grow -with the quantity and quality of the audio tracks involved. +In addition to disabling features you may need for other types of +audio, it can also introduce complications when streaming multiple +tracks. For example, you may face issues with synchronization and +interruptions. These may worsen as the quantity and quality of the +audio tracks involved increases. If you're unsure, avoid streaming unless you can say yes to all of the following: @@ -360,16 +393,24 @@ Advanced Playback Control .. _pyglet_controlling_playback: https://pyglet.readthedocs.io/en/latest/programming_guide/media.html#controlling-playback .. _inconsistency_loop_issue: https://github.com/pythonarcade/arcade/issues/1915 -Arcade's functions for :ref:`sound-basics-stopping` are convenience -wrappers around the passed pyglet :py:class:`~pyglet.media.player.Player`. +Arcade's :ref:`sound-basics-stopping` functions are imprecise wrappers +around pyglet :py:class:`~pyglet.media.player.Player` features. + +If you need better control over :py:class:`~arcade.Sound` playback, multiple +aspects can be controlled in the following ways: -You can alter a playback of :py:class:`~arcade.Sound` data with more precision -by: +.. list-table:: + :header-rows: 1 + + * - How + - When + + * - Properties and methods + - Any time before a :py:class:`~pyglet.media.player.Player` finishes + + * - Keyword arguments + - When starting to :ref:`play the sound ` -* Using the properties and methods of its :py:class:`~pyglet.media.player.Player` - any time before playback has finished -* Passing keyword arguments with the same (or similar) names as the - Player's properties when :ref:`playing the sound `. Stopping via the Player Object ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -410,6 +451,8 @@ After you've paused a player, you can stop playback permanently: # Python will delete the pyglet Player once there are 0 references to it self.current_player = None +.. note:: This is how :py:class:`Sound.stop ` works internally. + For a more in-depth explanation of references and auto-deletion, skim the start of Python's page on `garbage collection`_. Reading the Abstract section of this page should be enough to get started. From f3440391ba92f3445ccc841e0b76a1e8baf7249d Mon Sep 17 00:00:00 2001 From: pushfoo <36696816+pushfoo@users.noreply.github.com> Date: Mon, 22 Jul 2024 08:14:18 -0400 Subject: [PATCH 3/3] Finish Google style, cross-refs, and annotations * Convert to Google style docstrings * Add heavy cross-references * Add missing annotations --- arcade/sound.py | 182 +++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 148 insertions(+), 34 deletions(-) diff --git a/arcade/sound.py b/arcade/sound.py index e8a3edcb67..c51b1b531c 100644 --- a/arcade/sound.py +++ b/arcade/sound.py @@ -27,7 +27,42 @@ class Sound: - """This class represents a sound you can play.""" + """Holds :ref:`playable ` loaded audio data. + + .. important:: :ref:`Streaming ` disables features! + + When ``streaming=True``, :py:meth:`.play` and :py:func:`play_sound`: + + * raise a :py:class:`RuntimeError` if there is already another + active playback + * do not support looping + + To learn about the restrictions on :ref:`streaming `, + please see: + + * :ref:`sound-loading-modes-streaming` + * The py:class:`pyglet.media.codes.base.StreamingSource` class used + internally + + To learn about cross-platform loading and file format concerns, + please see: + + * Arcade's sound documentation: + + * :ref:`sound-loading-modes` + * :ref:`sound-compat-easy` + * :ref:`sound-compat-loading` + + * The pyglet guide to :external+pyglet:ref:`guide-media` + + Args: + file_name: + The path of a file to load, optionally prefixed with a + :ref:`resource handle `. + streaming: + If ``True``, attempt to load data from ``file_path`` via + via :ref:`streaming `. + """ def __init__(self, file_name: str | Path, streaming: bool = False): self.file_name: str = "" @@ -55,14 +90,26 @@ def play( loop: bool = False, speed: float = 1.0, ) -> media.Player: - """Play the sound. + """Try to play this :py:class:`Sound` and return a :py:class:`~pyglet.media.player.Player`. + + .. important:: Any :py:class:`Sound` with ``streaming=True`` loses features! + + ``loop`` will not work and simultaneous playbacks raise + a :py:class:`RuntimeError`. + + See the following to learn more about the keywords and restrictions: + + * :py:class:`Sound` + * :ref:`sound-advanced-playback-change-aspects-ongoing` + * :ref:`sound-advanced-playback-change-aspects-new` Args: - volume: Volume, from 0=quiet to 1=loud - pan: Pan, from -1=left to 0=centered to 1=right - loop: Loop, false to play once, true to loop continuously - speed: Change the speed of the sound which also changes - pitch, default 1.0 + volume: Volume (``0.0`` is silent, ``1.0`` is loudest). + pan: Left / right channel balance (``-1`` is left, ``0.0`` is + center, and ``1.0`` is right). + loop: ``True`` attempts to restart playback after finishing. + speed: Change the speed (and pitch) of the sound. Default speed is + ``1.0``. """ if isinstance(self.source, media.StreamingSource) and self.source.is_player_source: raise RuntimeError( @@ -104,30 +151,40 @@ def _on_player_eos(): return player def stop(self, player: media.Player) -> None: - """Stop a currently playing sound.""" + """Permanently stop and :py:meth:`~pyglet.media.player.Player.delete` ``player``. + + All references in the :py:class:`pyglet.media.Source` player table + will be deleted. + + Args: + player: A pyglet :py:class:`~pyglet.media.player.Player` + returned from :func:`play_sound` or :py:meth:`Sound.play`. + """ player.pause() player.delete() if player in media.Source._players: media.Source._players.remove(player) def get_length(self) -> float: - """Get length of audio in seconds""" + """Get length of the loaded audio in seconds""" # We validate that duration is known when loading the source return self.source.duration # type: ignore def is_complete(self, player: media.Player) -> bool: - """Return true if the sound is done playing.""" + """``True`` if the sound is done playing.""" # We validate that duration is known when loading the source return player.time >= self.source.duration # type: ignore def is_playing(self, player: media.Player) -> bool: - """Return if the sound is currently playing or not + """``True`` if ``player`` is currently playing, otherwise ``False``. Args: - player: Player returned from :func:`play_sound`. + player: A pyglet :py:class:`~pyglet.media.player.Player` + returned from :py:meth:`Sound.play <.Sound.play>` or + :func:`play_sound`. Returns: - A boolean, ``True`` if the sound is playing. + ``True`` if the passed pyglet player is playing. """ return player.playing @@ -135,19 +192,21 @@ def get_volume(self, player: media.Player) -> float: """Get the current volume. Args: - player: Player returned from :func:`play_sound`. - + player: A pyglet :py:class:`~pyglet.media.player.Player` + returned from :py:meth:`Sound.play <.Sound.play>` or + :func:`play_sound`. Returns: - A float, 0 for volume off, 1 for full volume. + A volume between ``0.0`` (silent) and ``1.0`` (full volume). """ return player.volume # type: ignore # pending https://github.com/pyglet/pyglet/issues/847 - def set_volume(self, volume, player: media.Player) -> None: + def set_volume(self, volume: float, player: media.Player) -> None: """Set the volume of a sound as it is playing. Args: volume: Floating point volume. 0 is silent, 1 is full. - player: Player returned from :func:`play_sound`. + player: A pyglet :py:class:`~pyglet.media.player.Player` + returned from :func:`play_sound` or :py:meth:`Sound.play`. """ player.volume = volume @@ -162,17 +221,23 @@ def get_stream_position(self, player: media.Player) -> float: def load_sound(path: str | Path, streaming: bool = False) -> Sound: - """Load a sound. + """Load a file as a :py:class:`Sound` data object. + + .. important:: Using ``streaming=True`` disables certain features! + + These include looping and multiple playbacks. Please + see :py:class:`Sound` to learn more. Args: - path: Name of the sound file to load. + path: a path which may be prefixed with a + :ref:`resource_handle `. streaming: Boolean for determining if we stream the sound or load it all into memory. Set to ``True`` for long sounds to save memory, ``False`` for short sounds to speed playback. Returns: - Sound object which can be used by the :func:`play_sound` - function. + A :ref:playable ` instance of a + :py:class:`Sound` object. """ # Initialize the audio driver if it hasn't been already. # This call is to avoid audio driver initialization @@ -190,22 +255,69 @@ def load_sound(path: str | Path, streaming: bool = False) -> Sound: def play_sound( - sound: Sound, + sound: Sound | None, volume: float = 1.0, pan: float = 0.0, loop: bool = False, speed: float = 1.0, ) -> media.Player | None: - """Play a sound. + """Try to play the ``sound`` and return a :py:class:`~pyglet.media.player.Player`. + + .. note:: The ``sound`` **must** be a :py:class:`Sound` object! + + See the following to load audio from file paths: + + * :ref:`sound-basics-loading` + * :ref:`sound-loading-modes` + * :py:func:`load_sound` + * :py:class:`Sound` + + The output and return value depend on whether playback succeeded: + + .. list-table:: + :header-rows: 1 + + * - Success? + - Console output + - Return value + + * - No / ``sound`` is ``None`` + - Log a warning + - ``None`` + + * - Yes + - N/A + - A pyglet :py:class:`~pyglet.media.player.Player` + + See the following to learn more: + + * :ref:`sound-basics-sound_vs_player` + * :ref:`sound-advanced-playback` + + .. important:: Any :py:class:`Sound` with ``streaming=True`` loses features! + + ``loop`` will not work and simultaneous playbacks raise + a :py:class:`RuntimeError`. + + To learn more about the ``streaming`` keyword and restrictions, please see: + + * :py:class:`Sound` + * :ref:`sound-advanced-playback-change-aspects-ongoing` + * :ref:`sound-advanced-playback-change-aspects-new` Args: - sound: Sound loaded by :func:`load_sound`. Do NOT use a string - here for the filename. - volume: Volume, from 0=quiet to 1=loud - pan: Pan, from -1=left to 0=centered to 1=right - loop: Should we loop the sound over and over? - speed: Change the speed of the sound which also changes pitch, - default 1.0 + sound: A :py:class:`Sound` instance or ``None``. + volume: From ``0.0`` (silent) to ``1.0`` (max volume). + pan: The left / right ear balance (``-1`` is left, ``0`` is center, + and ``1`` is right) + loop: ``True`` makes playback restart each time it reaches the end. + speed: How fast to play. Slower than ``1.0`` deepens sound while + values higher than ``1.0`` raise the pitch. + + Returns: + A :py:class:`pyglet.media.Player` instance for the playback or + ``None`` if playback failed. + """ if sound is None: logger.warning("Unable to play sound, no data passed in.") @@ -225,11 +337,13 @@ def play_sound( return None -def stop_sound(player: media.Player): - """Stop a sound that is currently playing. +def stop_sound(player: media.Player) -> None: + """Stop a pyglet player for a which is currently playing. Args: - player: Player returned from :func:`play_sound`. + player: A pyglet :py:class:`~pyglet.media.player.Player` + returned from :py:meth:`Sound.play <.Sound.play>` or + :func:`play_sound`. """ if not isinstance(player, media.Player):