diff --git a/arcade/sound.py b/arcade/sound.py
index c6484a0130..c51b1b531c 100644
--- a/arcade/sound.py
+++ b/arcade/sound.py
@@ -1,6 +1,4 @@
-"""
-Sound Library.
-"""
+"""Sound Library."""
 
 from __future__ import annotations
 
@@ -29,7 +27,42 @@
 
 
 class Sound:
-    """This class represents a sound you can play."""
+    """Holds :ref:`playable <sound-basics-playing>` loaded audio data.
+
+    .. important:: :ref:`Streaming <sound-loading-modes>` 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 <sound-loading-modes>`,
+    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 <resource_handles>`.
+         streaming:
+            If ``True``, attempt to load data from ``file_path`` via
+            via :ref:`streaming <sound-loading-modes>`.
+    """
 
     def __init__(self, file_name: str | Path, streaming: bool = False):
         self.file_name: str = ""
@@ -57,13 +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`.
 
-        :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
+        .. 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 (``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(
@@ -105,8 +151,14 @@ 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()
@@ -114,63 +166,78 @@ def stop(self, player: media.Player) -> None:
             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``.
 
-        :param player: Player returned from :func:`play_sound`.
-        :returns: A boolean, ``True`` if the sound is playing.
+        Args:
+            player: A pyglet :py:class:`~pyglet.media.player.Player`
+                returned from :py:meth:`Sound.play <.Sound.play>` or
+                :func:`play_sound`.
 
+        Returns:
+            ``True`` if the passed pyglet player is playing.
         """
         return player.playing
 
     def get_volume(self, player: media.Player) -> float:
-        """
-        Get the current volume.
-
-        :param player: Player returned from :func:`play_sound`.
-        :returns: A float, 0 for volume off, 1 for full volume.
+        """Get the current volume.
+
+        Args:
+            player: A pyglet :py:class:`~pyglet.media.player.Player`
+                returned from :py:meth:`Sound.play <.Sound.play>` or
+                :func:`play_sound`.
+        Returns:
+            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:
-        """
-        Set the volume of a sound as it is playing.
+    def set_volume(self, volume: float, player: media.Player) -> None:
+        """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: A pyglet :py:class:`~pyglet.media.player.Player`
+                returned from :func:`play_sound` or :py:meth:`Sound.play`.
         """
         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 file as a :py:class:`Sound` data object.
+
+    .. important:: Using ``streaming=True`` disables certain features!
 
-    :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.
+                   These include looping and multiple playbacks. Please
+                   see :py:class:`Sound` to learn more.
+
+    Args:
+        path: a path which may be prefixed with a
+            :ref:`resource_handle <resource_handles>`.
+        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:
+        A :ref:playable <sound-basics-playing>` 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
@@ -188,20 +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: 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.
 
-    :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
     """
     if sound is None:
         logger.warning("Unable to play sound, no data passed in.")
@@ -221,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.
 
-    :param player: Player returned from :func:`play_sound`.
+    Args:
+        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):
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 <arcade.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 <sound-basics-playing>`
 
-* 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 <sound-basics-playing>`.
 
 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 <arcade.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.
