# Module `pyboy.api.sound`

This class gives access to the sound buffer of PyBoy.

Expand source code

    #
    # License: See LICENSE.md file
    # GitHub: https://github.com/Baekalfen/PyBoy
    #
    """
    This class gives access to the sound buffer of PyBoy.
    """

    import numpy as np

    from pyboy import utils
    from pyboy.logging import get_logger

    logger = get_logger(__name__)

    class Sound:
        """
        As part of the emulation, we generate a sound buffer for each frame on the screen. This class has several helper
        methods to make it possible to read this buffer out.

        When the game enables/disables the LCD, the timing will be shorter than 70224 emulated cycles. Therefore the sound
        buffer will also be shorter than 16.667ms (60 FPS).

        Because the number of samples and the timing of frames don't match exactly, you can expect a little fluctuation in
        the number of samples per frame. Normally at a sample rate of 24,000Hz, it'll be 400 samples/second. But some times,
        it might become 401. As described above, when the LCD enables/disables, it might be even less -- maybe 30, 143,
        or 200 samples. This timespan represent what the real hardware would have shown.

        If you're working with encoding the screen and sound in a video stream, you could drop these shorter frames, if they
        cause problems. They usually only happen in transitions from menu to game or similar.
        """

        def __init__(self, mb):
            self.mb = mb

            self.sample_rate = self.mb.sound.sample_rate
            """
            Read-only. Changing this, will not change the sample rate. See `PyBoy` constructor instead.

            The sample rate is reported per second, while the frame rate of the Game Boy is ~60 frame per second.
            So expect the sound buffer to have 1/60 of this value in the buffer after every frame. Although it will
            fluctuate. See top of the page.

            ```python
            >>> pyboy.sound.sample_rate # in Hz
            48000
            >>> pyboy.sound.sample_rate // 60 # Expected samples per frame
            800
            >>> (800+1) * 2 # Minimum buffer size for you to prepare (2 channels, +1 for fluctuating lengths)
            1602
            >>> 1602 == pyboy.sound.raw_buffer_length # This is how the length is calculated at the moment
            True
            ```

            Returns
            -------
            int:
                The sample rate in Hz (samples per second)
            """

            self.raw_buffer_format = self.mb.sound.buffer_format
            """
            Returns the color format of the raw sound buffer. **This format is subject to change.**

            See how to interpret the format on: https://docs.python.org/3/library/struct.html#format-characters

            Example:
            ```python
            >>> pyboy.sound.raw_buffer_format
            'b'
            ```

            Returns
            -------
            str:
                Struct format of the raw sound buffer. E.g. 'b' for signed 8-bit
            """

            self.raw_buffer_length = self.mb.sound.audiobuffer_length
            """
            Read-only. Changing this, will not change the buffer length.

            This is the total length of the allocated raw buffer. Use this only to allocate an appropriate buffer in your
            script. The length of the valid data in the buffer is found using `Sound.raw_buffer_head`.

            Returns
            -------
            int:
                Total raw buffer length
            """

            self.raw_buffer = memoryview(self.mb.sound.audiobuffer).cast(
                self.raw_buffer_format, shape=(self.mb.sound.audiobuffer_length,)
            )
            """
            Provides a raw, unfiltered `memoryview` object with the data from sound buffer. Check
            `Sound.raw_buffer_format` to see which dataformat is used. **The returned type and dataformat are
            subject to change.** The sound buffer is in stereo format, so the odd indexes are the left channel,
            and even indexes are the right channel.

            Use this, only if you need to bypass the overhead of `Sound.ndarray`.

            Be aware to use the `Sound.raw_buffer_head`, as not all 'frames' are of equal length.

            Example:
            ```python
            >>> from array import array
            >>> sound_buffer = array(pyboy.sound.raw_buffer_format, pyboy.sound.raw_buffer[:pyboy.sound.raw_buffer_head])
            >>> sound_buffer
            array('b', [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ...])
            ```

            Returns
            -------
            memoryview:
                memoryview of sound data.
            """

            self.raw_ndarray = None
            if self.mb.sound.emulate:
                self.raw_ndarray = np.frombuffer(
                    self.mb.sound.audiobuffer,
                    dtype=np.int8,
                ).reshape(self.mb.sound.audiobuffer_length // 2, 2)
            else:
                self.raw_ndarray = utils.SoundEnabledError()

        @property
        def raw_buffer_head(self):
            """
            This returns the

            See the explanation at the top of the page.
            """
            return self.mb.sound.audiobuffer_head

        @property
        def ndarray(self):
            """
            References the sound data in NumPy format. **Remember to copy this object** if you intend to store it.
            The backing buffer will update, but it will be the same `ndarray` object.

            The format is given by `pyboy.api.sound.Sound.raw_buffer_format`. The sound buffer is in stereo format,
            so the first index is the left channel, and the second index is the right channel.

            This property returns an `ndarray` that is already accounting for the changing length of the sound buffer.
            See the explanation at the top of the page.

            Example:
            ```python
            >>> pyboy.sound.ndarray.shape # 401 samples, 2 channels (stereo)
            (801, 2)
            >>> pyboy.sound.ndarray
            array([[0, 0],
                   [0, 0],
                   ...
                   [0, 0],
                   [0, 0]], dtype=int8)

            ```

            Returns
            -------
            numpy.ndarray:
                Sound data in `ndarray` of bytes with shape given by sample rate
            """
            if self.mb.sound.emulate:
                return self.raw_ndarray[: self.mb.sound.audiobuffer_head]
            else:
                raise utils.PyBoyFeatureDisabledError("Sound is not enabled!")

## Classes

`class Sound (mb)`

  As part of the emulation, we generate a sound buffer for each frame on the screen. This class has several helper methods to make it possible to read this buffer out.

  When the game enables/disables the LCD, the timing will be shorter than 70224 emulated cycles. Therefore the sound buffer will also be shorter than 16.667ms (60 FPS).

  Because the number of samples and the timing of frames don't match exactly, you can expect a little fluctuation in the number of samples per frame. Normally at a sample rate of 24,000Hz, it'll be 400 samples/second. But some times, it might become 401. As described above, when the LCD enables/disables, it might be even less – maybe 30, 143, or 200 samples. This timespan represent what the real hardware would have shown.

  If you're working with encoding the screen and sound in a video stream, you could drop these shorter frames, if they cause problems. They usually only happen in transitions from menu to game or similar.

  Expand source code

      class Sound:
          """
          As part of the emulation, we generate a sound buffer for each frame on the screen. This class has several helper
          methods to make it possible to read this buffer out.

          When the game enables/disables the LCD, the timing will be shorter than 70224 emulated cycles. Therefore the sound
          buffer will also be shorter than 16.667ms (60 FPS).

          Because the number of samples and the timing of frames don't match exactly, you can expect a little fluctuation in
          the number of samples per frame. Normally at a sample rate of 24,000Hz, it'll be 400 samples/second. But some times,
          it might become 401. As described above, when the LCD enables/disables, it might be even less -- maybe 30, 143,
          or 200 samples. This timespan represent what the real hardware would have shown.

          If you're working with encoding the screen and sound in a video stream, you could drop these shorter frames, if they
          cause problems. They usually only happen in transitions from menu to game or similar.
          """

          def __init__(self, mb):
              self.mb = mb

              self.sample_rate = self.mb.sound.sample_rate
              """
              Read-only. Changing this, will not change the sample rate. See `PyBoy` constructor instead.

              The sample rate is reported per second, while the frame rate of the Game Boy is ~60 frame per second.
              So expect the sound buffer to have 1/60 of this value in the buffer after every frame. Although it will
              fluctuate. See top of the page.

              ```python
              >>> pyboy.sound.sample_rate # in Hz
              48000
              >>> pyboy.sound.sample_rate // 60 # Expected samples per frame
              800
              >>> (800+1) * 2 # Minimum buffer size for you to prepare (2 channels, +1 for fluctuating lengths)
              1602
              >>> 1602 == pyboy.sound.raw_buffer_length # This is how the length is calculated at the moment
              True
              ```

              Returns
              -------
              int:
                  The sample rate in Hz (samples per second)
              """

              self.raw_buffer_format = self.mb.sound.buffer_format
              """
              Returns the color format of the raw sound buffer. **This format is subject to change.**

              See how to interpret the format on: https://docs.python.org/3/library/struct.html#format-characters

              Example:
              ```python
              >>> pyboy.sound.raw_buffer_format
              'b'
              ```

              Returns
              -------
              str:
                  Struct format of the raw sound buffer. E.g. 'b' for signed 8-bit
              """

              self.raw_buffer_length = self.mb.sound.audiobuffer_length
              """
              Read-only. Changing this, will not change the buffer length.

              This is the total length of the allocated raw buffer. Use this only to allocate an appropriate buffer in your
              script. The length of the valid data in the buffer is found using `Sound.raw_buffer_head`.

              Returns
              -------
              int:
                  Total raw buffer length
              """

              self.raw_buffer = memoryview(self.mb.sound.audiobuffer).cast(
                  self.raw_buffer_format, shape=(self.mb.sound.audiobuffer_length,)
              )
              """
              Provides a raw, unfiltered `memoryview` object with the data from sound buffer. Check
              `Sound.raw_buffer_format` to see which dataformat is used. **The returned type and dataformat are
              subject to change.** The sound buffer is in stereo format, so the odd indexes are the left channel,
              and even indexes are the right channel.

              Use this, only if you need to bypass the overhead of `Sound.ndarray`.

              Be aware to use the `Sound.raw_buffer_head`, as not all 'frames' are of equal length.

              Example:
              ```python
              >>> from array import array
              >>> sound_buffer = array(pyboy.sound.raw_buffer_format, pyboy.sound.raw_buffer[:pyboy.sound.raw_buffer_head])
              >>> sound_buffer
              array('b', [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ...])
              ```

              Returns
              -------
              memoryview:
                  memoryview of sound data.
              """

              self.raw_ndarray = None
              if self.mb.sound.emulate:
                  self.raw_ndarray = np.frombuffer(
                      self.mb.sound.audiobuffer,
                      dtype=np.int8,
                  ).reshape(self.mb.sound.audiobuffer_length // 2, 2)
              else:
                  self.raw_ndarray = utils.SoundEnabledError()

          @property
          def raw_buffer_head(self):
              """
              This returns the

              See the explanation at the top of the page.
              """
              return self.mb.sound.audiobuffer_head

          @property
          def ndarray(self):
              """
              References the sound data in NumPy format. **Remember to copy this object** if you intend to store it.
              The backing buffer will update, but it will be the same `ndarray` object.

              The format is given by `pyboy.api.sound.Sound.raw_buffer_format`. The sound buffer is in stereo format,
              so the first index is the left channel, and the second index is the right channel.

              This property returns an `ndarray` that is already accounting for the changing length of the sound buffer.
              See the explanation at the top of the page.

              Example:
              ```python
              >>> pyboy.sound.ndarray.shape # 401 samples, 2 channels (stereo)
              (801, 2)
              >>> pyboy.sound.ndarray
              array([[0, 0],
                     [0, 0],
                     ...
                     [0, 0],
                     [0, 0]], dtype=int8)

              ```

              Returns
              -------
              numpy.ndarray:
                  Sound data in `ndarray` of bytes with shape given by sample rate
              """
              if self.mb.sound.emulate:
                  return self.raw_ndarray[: self.mb.sound.audiobuffer_head]
              else:
                  raise utils.PyBoyFeatureDisabledError("Sound is not enabled!")

  ### Instance variables

  `var raw_buffer_head`

    This returns the

    See the explanation at the top of the page.

    Expand source code

        @property
        def raw_buffer_head(self):
            """
            This returns the

            See the explanation at the top of the page.
            """
            return self.mb.sound.audiobuffer_head

  `var ndarray`

    References the sound data in NumPy format. Remember to copy this object if you intend to store it. The backing buffer will update, but it will be the same `ndarray` object.

    The format is given by `Sound.raw_buffer_format`. The sound buffer is in stereo format, so the first index is the left channel, and the second index is the right channel.

    This property returns an `ndarray` that is already accounting for the changing length of the sound buffer. See the explanation at the top of the page.

    Example:

        >>> pyboy.sound.ndarray.shape # 401 samples, 2 channels (stereo)
        (801, 2)
        >>> pyboy.sound.ndarray
        array([[0, 0],
               [0, 0],
               ...
               [0, 0],
               [0, 0]], dtype=int8)

    ## Returns

    `numpy.ndarray:`
      Sound data in `ndarray` of bytes with shape given by sample rate
    Expand source code

        @property
        def ndarray(self):
            """
            References the sound data in NumPy format. **Remember to copy this object** if you intend to store it.
            The backing buffer will update, but it will be the same `ndarray` object.

            The format is given by `pyboy.api.sound.Sound.raw_buffer_format`. The sound buffer is in stereo format,
            so the first index is the left channel, and the second index is the right channel.

            This property returns an `ndarray` that is already accounting for the changing length of the sound buffer.
            See the explanation at the top of the page.

            Example:
            ```python
            >>> pyboy.sound.ndarray.shape # 401 samples, 2 channels (stereo)
            (801, 2)
            >>> pyboy.sound.ndarray
            array([[0, 0],
                   [0, 0],
                   ...
                   [0, 0],
                   [0, 0]], dtype=int8)

            ```

            Returns
            -------
            numpy.ndarray:
                Sound data in `ndarray` of bytes with shape given by sample rate
            """
            if self.mb.sound.emulate:
                return self.raw_ndarray[: self.mb.sound.audiobuffer_head]
            else:
                raise utils.PyBoyFeatureDisabledError("Sound is not enabled!")

  `var sample_rate`

    Read-only. Changing this, will not change the sample rate. See `PyBoy` constructor instead.

    The sample rate is reported per second, while the frame rate of the Game Boy is ~60 frame per second. So expect the sound buffer to have 1/60 of this value in the buffer after every frame. Although it will fluctuate. See top of the page.

        >>> pyboy.sound.sample_rate # in Hz
        48000
        >>> pyboy.sound.sample_rate // 60 # Expected samples per frame
        800
        >>> (800+1) * 2 # Minimum buffer size for you to prepare (2 channels, +1 for fluctuating lengths)
        1602
        >>> 1602 == pyboy.sound.raw_buffer_length # This is how the length is calculated at the moment
        True

    ## Returns

    `int:`
      The sample rate in Hz (samples per second)
  `var raw_buffer_format`

    Returns the color format of the raw sound buffer. This format is subject to change.

    See how to interpret the format on: https://docs.python.org/3/library/struct.html#format-characters

    Example:

        >>> pyboy.sound.raw_buffer_format
        'b'

    ## Returns

    `str:`
      Struct format of the raw sound buffer. E.g. 'b' for signed 8-bit
  `var raw_buffer_length`

    Read-only. Changing this, will not change the buffer length.

    This is the total length of the allocated raw buffer. Use this only to allocate an appropriate buffer in your script. The length of the valid data in the buffer is found using `Sound.raw_buffer_head`.

    ## Returns

    `int:`
      Total raw buffer length
  `var raw_buffer`

    Provides a raw, unfiltered `memoryview` object with the data from sound buffer. Check `Sound.raw_buffer_format` to see which dataformat is used. The returned type and dataformat are subject to change. The sound buffer is in stereo format, so the odd indexes are the left channel, and even indexes are the right channel.

    Use this, only if you need to bypass the overhead of `Sound.ndarray`.

    Be aware to use the `Sound.raw_buffer_head`, as not all 'frames' are of equal length.

    Example:

        >>> from array import array
        >>> sound_buffer = array(pyboy.sound.raw_buffer_format, pyboy.sound.raw_buffer[:pyboy.sound.raw_buffer_head])
        >>> sound_buffer
        array('b', [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ...])

    ## Returns

    `memoryview:`
      memoryview of sound data.