Source code for pyunity.audio
## Copyright (c) 2020-2022 The PyUnity Team
## This file is licensed under the MIT License.
## See https://docs.pyunity.x10.bz/en/latest/license.html
"""
Classes to manage the playback of audio.
It uses the sdl2.sdlmixer library.
A variable in the ``config`` module called
``audio`` will be set to ``False`` if the
mixer module cannot be initialized.
"""
__all__ = ["AudioSource", "AudioClip", "AudioListener"]
import warnings
import os
from . import config, Logger
from .core import Component, ShowInInspector, SingleComponent
channels = 0
if "PYUNITY_TESTING" in os.environ:
config.audio = False
Logger.LogLine(Logger.WARN, "Testing PyUnity, audio is disabled")
elif os.environ["PYUNITY_AUDIO"] == "0":
config.audio = False
Logger.LogLine(Logger.WARN, "Audio disabled via env var")
elif os.environ["PYUNITY_INTERACTIVE"] == "0":
config.audio = False
Logger.LogLine(Logger.WARN, "Non-interactive mode, audio is disabled")
else:
with warnings.catch_warnings():
warnings.filterwarnings("ignore")
try:
from sdl2 import sdlmixer as mixer
from sdl2 import SDL_GetError
except ImportError:
config.audio = False
Logger.LogLine(Logger.WARN,
"Failed to import PySDL2, your system may not support it.")
if config.audio:
if mixer.Mix_Init(mixer.MIX_INIT_MP3 | mixer.MIX_INIT_OGG) == 0:
config.audio = False
Logger.LogLine(Logger.WARN, "Cannot load sdlmixer, audio is disabled")
elif mixer.Mix_OpenAudio(22050, mixer.MIX_DEFAULT_FORMAT, 2, 4096) == -1:
config.audio = False
Logger.LogLine(Logger.WARN,
"SDL2_mixer could not be initialized: " + SDL_GetError().decode())
class _CustomMock:
def __getattr__(self, item):
Logger.LogLine(Logger.WARN, "Audio is currently disabled")
return _CustomMock()
def __setattr__(self, item, value):
Logger.LogLine(Logger.WARN, "Audio is currently disabled")
def __call__(self, *args, **kwargs):
return _CustomMock()
if not config.audio:
mixer = _CustomMock()
[docs]class AudioClip:
"""
Class to store information about an audio file.
Attributes
----------
path : str
Path to the file
music : sdl2.sdlmixer.mixer.Mix_Chunk
Sound chunk that can be played with
an SDL2 Mixer Channel.
Only set when the AudioClip is played
in an :py:class:`AudioSource`.
"""
def __init__(self, path):
self.path = str(path)
self.music = None
[docs]class AudioSource(Component):
"""
Manages playback on an AudioSource.
Attributes
----------
clip : AudioClip
Clip to play. Best way to set the clip
is to use the :meth:`SetClip` function.
playOnStart : bool
Whether it plays on start or not.
loop : bool
Whether it loops or not. This is not
fully supported.
"""
playOnStart = ShowInInspector(bool, False)
loop = ShowInInspector(bool, False)
clip = ShowInInspector(AudioClip)
def __init__(self, transform):
super(AudioSource, self).__init__(transform)
global channels
self.clip = None
self.channel = channels
channels += 1
mixer.Mix_AllocateChannels(channels)
[docs] def SetClip(self, clip):
"""
Sets a clip for the AudioSource to play.
Parameters
----------
clip : AudioClip
AudioClip to play
"""
self.clip = clip
[docs] def Play(self):
"""
Plays the AudioClip attached to the AudioSource.
"""
if self.clip is None:
Logger.LogLine(Logger.WARN, "AudioSource has no AudioClip")
return
if self.clip.music is None:
self.clip.music = mixer.Mix_LoadWAV(self.clip.path.encode())
if mixer.Mix_PlayChannel(self.channel, self.clip.music, 0) == -1:
Logger.LogLine(Logger.WARN, "Unable to play file: %s" %
mixer.Mix_GetError().decode())
[docs] def Stop(self):
"""
Stops playing the AudioClip attached to the AudioSource.
"""
if self.clip is None:
Logger.LogLine(Logger.WARN, "AudioSource has no AudioClip")
mixer.Mix_HaltChannel(self.channel)
[docs] def Pause(self):
"""
Pauses the AudioClip attached to the AudioSource.
"""
if self.clip is None:
Logger.LogLine(Logger.WARN, "AudioSource has no AudioClip")
mixer.Mix_Pause(self.channel)
[docs] def UnPause(self):
"""
Unpauses the AudioClip attached to the AudioSource.
"""
if self.clip is None:
Logger.LogLine(Logger.WARN, "AudioSource has no AudioClip")
mixer.Mix_Resume(self.channel)
@property
def Playing(self):
"""
Gets if the AudioSource is playing.
"""
if self.clip is None:
Logger.LogLine(Logger.WARN, "AudioSource has no AudioClip")
return mixer.Mix_Playing(self.channel)
[docs]class AudioListener(SingleComponent):
"""
Class to receive audio events and to base spatial
sound from. By default the Main Camera has an
AudioListener, but you can also remove it and
add a new one to another GameObject in a Scene.
There can only be one AudioListener, otherwise
sound is disabled.
"""
def __init__(self, transform):
super(AudioListener, self).__init__(transform)
self.opened = 0
[docs] def Init(self):
"""
Initializes the AudioListener.
"""
pass
[docs] def DeInit(self):
"""
Stops all AudioSources and frees memory that is used by
the AudioClips.
"""
for source in self.scene.FindComponents(AudioSource):
mixer.Mix_HaltChannel(source.channel)
mixer.Mix_FreeChunk(source.clip.music)