Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test_sound_normal_load_and_playback error on MacOS #2533

Open
cdeil opened this issue Jan 31, 2025 · 2 comments
Open

test_sound_normal_load_and_playback error on MacOS #2533

cdeil opened this issue Jan 31, 2025 · 2 comments
Labels
help wanted tests Everything related to testing

Comments

@cdeil
Copy link
Contributor

cdeil commented Jan 31, 2025

The test_sound_normal_load_and_playback fails on MacOS (see full output below) with Python 3.13.

Can this be fixed or Is OGG not supported on MacOS?

Suggestion: remove the try/except here in load_sound:

arcade/arcade/sound.py

Lines 242 to 247 in 333ce6e

try:
return Sound(file_name, streaming)
except Exception as ex:
raise FileNotFoundError(
f'Unable to load sound file: "{file_name}". Exception: {ex}'
) from ex

Forcing the exception to change type to FileNotFoundError is incorrect and thus confusing in this case. It's also not necessary since the Sound(...) init will raise a FileNotFoundError already if the file isn't there.

Would it be easy / desired to add MacOS testing to Github CI?

(.env) cdeil@Kryvoff-MBP arcade % pytest tests/unit/test_sound.py::test_sound_normal_load_and_playback
================================ test session starts =================================
platform darwin -- Python 3.13.1, pytest-8.3.4, pluggy-1.5.0
rootdir: /Users/cdeil/code/oss/arcade
configfile: pyproject.toml
plugins: cov-5.0.0, anyio-4.8.0, mock-3.14.0
collected 1 item                                                                     

tests/unit/test_sound.py F                                                     [100%]

====================================== FAILURES ======================================
________________________ test_sound_normal_load_and_playback _________________________

path = ':resources:sounds/laser1.ogg', streaming = False

    def load_sound(path: str | Path, streaming: bool = False) -> Sound:
        """Load a file as a :py:class:`Sound` data object.
    
        .. important:: A :py:class:`Sound` with ``streaming=True`` loses features!
    
                       Neither ``loop`` nor simultaneous playbacks will work. See
                       :py;class:`Sound` and :ref:`sound-loading-modes`.
    
        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
        # the first time a sound is played.
        # This call is inexpensive if the driver is already initialized.
        media.get_audio_driver()
    
        file_name = str(path)
        try:
>           return Sound(file_name, streaming)

arcade/sound.py:243: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
arcade/sound.py:73: in __init__
    self.source: Source = media.load(self.file_name, streaming=streaming)
.env/lib/python3.13/site-packages/pyglet/media/__init__.py:85: in load
    return _codec_registry.decode(filename, file, streaming=streaming)
.env/lib/python3.13/site-packages/pyglet/util.py:175: in decode
    return decoder.decode(filename, file, **kwargs)
.env/lib/python3.13/site-packages/pyglet/media/codecs/coreaudio.py:177: in decode
    return StaticSource(CoreAudioSource(filename, file))
.env/lib/python3.13/site-packages/pyglet/media/codecs/coreaudio.py:56: in __init__
    err_check(ca.ExtAudioFileOpenURL(url_ref, byref(audref)))
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

err = 1954115647

    def err_check(err: int) -> None:
        """Raise an exception of somethings wrong, otherwise return None.
    
        Raises:
             CoreAudioException
        """
        if err != 0:
>           raise CoreAudioException(err, err_str_db.get(err, "Unknown Error"))
E           pyglet.libs.darwin.coreaudio.CoreAudioException: (1954115647, 'The file type is not supported.')

.env/lib/python3.13/site-packages/pyglet/libs/darwin/coreaudio.py:239: CoreAudioException

The above exception was the direct cause of the following exception:

window = Window=(width=1280, height=720)

    def test_sound_normal_load_and_playback(window):
        global frame_count, player
    
        laser_wav = arcade.load_sound(":resources:sounds/laser1.wav")
        laser_mp3 = arcade.load_sound(":resources:sounds/laser1.mp3")
>       laser_ogg = arcade.load_sound(":resources:sounds/laser1.ogg")

tests/unit/test_sound.py:16: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

path = ':resources:sounds/laser1.ogg', streaming = False

    def load_sound(path: str | Path, streaming: bool = False) -> Sound:
        """Load a file as a :py:class:`Sound` data object.
    
        .. important:: A :py:class:`Sound` with ``streaming=True`` loses features!
    
                       Neither ``loop`` nor simultaneous playbacks will work. See
                       :py;class:`Sound` and :ref:`sound-loading-modes`.
    
        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
        # the first time a sound is played.
        # This call is inexpensive if the driver is already initialized.
        media.get_audio_driver()
    
        file_name = str(path)
        try:
            return Sound(file_name, streaming)
        except Exception as ex:
>           raise FileNotFoundError(
                f'Unable to load sound file: "{file_name}". Exception: {ex}'
            ) from ex
E           FileNotFoundError: Unable to load sound file: ":resources:sounds/laser1.ogg". Exception: (1954115647, 'The file type is not supported.')

arcade/sound.py:245: FileNotFoundError
------------------------------- Captured stderr setup --------------------------------
2025-01-31 19:12:34.004 Python[56782:13820327] ApplePersistenceIgnoreState: Existing state will not be touched. New state will be written to /var/folders/q6/tz487k3j71x0cn2yn_3y144h0000gn/T/org.python.python.savedState
============================== short test summary info ===============================
FAILED tests/unit/test_sound.py::test_sound_normal_load_and_playback - FileNotFoundError: Unable to load sound file: ":resources:sounds/laser1.ogg". Exc...
================================= 1 failed in 0.20s ==================================
@cdeil
Copy link
Contributor Author

cdeil commented Jan 31, 2025

One more sound-related issue I noticed on MacOS is that sound playback like this:

import time
import arcade
s = arcade.load_sound(":resources:sounds/coin1.wav")
arcade.play_sound(s)
s.play()
time.sleep(3)

at the end of the playback will crash like this (the time.sleep is to avoid Python exit before the sound finishes):

.venvcdeil@Kryvoff-MBP cgames % python arcade_sound_crash.py
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'API misuse: setting the main menu on a non-main thread. Main menu contents should only be modified from the main thread.'
*** First throw call stack:
(
        0   CoreFoundation                      0x000000018f372e80 __exceptionPreprocess + 176
        1   libobjc.A.dylib                     0x000000018ee5acd8 objc_exception_throw + 88
        2   Foundation                          0x0000000190581978 -[NSCalendarDate initWithCoder:] + 0
        3   AppKit                              0x0000000192e46060 -[NSMenu _setMenuName:] + 468
        4   AppKit                              0x0000000192e5abb8 -[NSApplication setMainMenu:] + 372
        5   libffi.dylib                        0x00000001a0fd1050 ffi_call_SYSV + 80
        6   libffi.dylib                        0x00000001a0fd9b04 ffi_call_int + 1208
        7   _ctypes.cpython-313-darwin.so       0x00000001031c32f0 _ctypes_callproc + 780
        8   _ctypes.cpython-313-darwin.so       0x00000001031c090c PyCFuncPtr_call + 260
        9   Python                              0x0000000102ba32f8 _PyObject_Call + 128
        10  Python                              0x0000000102ccba2c _PyEval_EvalFrameDefault + 13676
        11  Python                              0x0000000102ba2228 _PyObject_VectorcallDictTstate + 96
        12  Python                              0x0000000102ba39f4 _PyObject_Call_Prepend + 136
        13  Python                              0x0000000102c34ee0 slot_tp_call + 128
        14  Python                              0x0000000102ba32f8 _PyObject_Call + 128
        15  Python                              0x0000000102ccba2c _PyEval_EvalFrameDefault + 13676
        16  Python                              0x0000000102ba2228 _PyObject_VectorcallDictTstate + 96
        17  Python                              0x0000000102ba39f4 _PyObject_Call_Prepend + 136
        18  Python                              0x0000000102c34ee0 slot_tp_call + 128
        19  Python                              0x0000000102ba2424 _PyObject_MakeTpCall + 124
        20  Python                              0x0000000102ccaa94 _PyEval_EvalFrameDefault + 9684
        21  Python                              0x0000000102ba2228 _PyObject_VectorcallDictTstate + 96
        22  Python                              0x0000000102c35f74 slot_tp_init + 196
        23  Python                              0x0000000102c2ba60 type_call + 148
        24  Python                              0x0000000102ba2424 _PyObject_MakeTpCall + 124
        25  Python                              0x0000000102ccaa94 _PyEval_EvalFrameDefault + 9684
        26  Python                              0x0000000102cc8268 PyEval_EvalCode + 200
        27  Python                              0x0000000102cc38b4 builtin_exec + 444
        28  Python                              0x0000000102bffca8 cfunction_vectorcall_FASTCALL_KEYWORDS + 88
        29  Python                              0x0000000102ccba2c _PyEval_EvalFrameDefault + 13676
        30  Python                              0x0000000102ba4afc object_vacall + 268
        31  Python                              0x0000000102ba4978 PyObject_CallMethodObjArgs + 108
        32  Python                              0x0000000102d12e74 PyImport_ImportModuleLevelObject + 3244
        33  Python                              0x0000000102cc206c builtin___import__ + 172
        34  Python                              0x0000000102ccb780 _PyEval_EvalFrameDefault + 12992
        35  Python                              0x0000000102ba317c PyObject_Vectorcall + 92
        36  Python                              0x0000000102c323b8 _Py_slot_tp_getattr_hook + 384
        37  Python                              0x0000000102cd176c _PyEval_EvalFrameDefault + 37548
        38  Python                              0x0000000102ba5c7c method_vectorcall + 396
        39  Python                              0x0000000102dc12c4 thread_run + 152
        40  Python                              0x0000000102d4d28c pythread_wrapper + 48
        41  libsystem_pthread.dylib             0x000000018f2182e4 _pthread_start + 136
        42  libsystem_pthread.dylib             0x000000018f2130fc thread_start + 8
)
libc++abi: terminating due to uncaught exception of type NSException
zsh: abort      python arcade_sound_crash.py

When playing the sound like this it works fine (no crash):

class TheView(arcade.Window):
    def __init__(self):
        super().__init__()
        self.coin_sound = arcade.load_sound(":resources:sounds/coin1.wav")

    def on_update(self, delta_time):
        # arcade.play_sound(self.coin_sound)
        self.coin_sound.play()
        time.sleep(10)


view = TheView()
view.run()

Is this a bug or something to improve? Or is playing sound like that illegal and should simply be avoided?

@einarf
Copy link
Member

einarf commented Feb 1, 2025

  • The first issue is probably that you need PyOGG to play ogg files on mac for some reason.
  • The second crash is likely an issue related to the sound thread in pyglet. I*m guessing something goes wrong during cleanup? Worth investigating.

Stalling the event loop or not having an event loop running is not recommended when playing sound with pyglet / arcade because the sound thread needs to keep refilling the sound buffers periodically. It's probably something we should mention in docs.

@einarf einarf added help wanted tests Everything related to testing labels Feb 1, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
help wanted tests Everything related to testing
Projects
None yet
Development

No branches or pull requests

2 participants