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
"""
ndarray
"""
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 """ ndarray """ 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. CheckSound.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.
var raw_ndarray
-
ndarray