Skip to content

Commit 3126dfb

Browse files
authored
Refactored audio system for improved macOS ARM compatibility; Updated Psychopy version to 2025.1.1; Switched to sounddevice library for macOS ARM, and added cffi for sound support. (#301)
Updated Psychopy version to 2025.1.1 and added cffi dependency in conda environments to fix MacOS ARM sound issues. Removed Psychopy PTB audio preferences and updated sound configuration for compatibility with macOS ARM and sounddevice backend. Use forked version of psychopy-soundevice until patches are merged into official repo.
1 parent b16796b commit 3126dfb

File tree

20 files changed

+1661
-34
lines changed

20 files changed

+1661
-34
lines changed

eegnb/experiments/Experiment.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,7 @@
1010

1111
from abc import abstractmethod
1212
from typing import Callable
13-
from psychopy import prefs
1413
from psychopy.visual.rift import Rift
15-
#change the pref libraty to PTB and set the latency mode to high precision
16-
prefs.hardware['audioLib'] = 'PTB'
17-
prefs.hardware['audioLatencyMode'] = 3
1814

1915
from time import time
2016
import random

eegnb/experiments/__init__.py

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,26 @@
22
from .visual_p300.p300 import VisualP300
33
from .visual_ssvep.ssvep import VisualSSVEP
44

5-
# PTB does not yet support macOS Apple Silicon,
6-
# this experiment needs to run as i386 if on macOS.
7-
import sys
5+
from psychopy import sound, plugins, prefs
86
import platform
9-
if sys.platform != 'darwin' or platform.processor() != 'arm':
10-
from .auditory_oddball.aob import AuditoryOddball
7+
8+
# PTB does not yet support macOS Apple Silicon freely, need to fall back to sounddevice.
9+
if platform.system() == 'Darwin' and platform.machine() == 'arm64':
10+
# import psychopy_sounddevice.backend_sounddevice
11+
plugins.scanPlugins()
12+
success = plugins.loadPlugin('psychopy-sounddevice')
13+
print(f"psychopy_sounddevice plugin loaded: {success}")
14+
15+
# Force reload sound module
16+
import importlib
17+
importlib.reload(sound)
18+
# setting prefs.hardware['audio_device'] still falls back to a default device, need to use setDevice.
19+
audio_device = prefs.hardware.get('audioDevice', 'default')
20+
if audio_device and audio_device != 'default':
21+
sound.setDevice(audio_device)
22+
else:
23+
#change the pref library to PTB and set the latency mode to high precision
24+
prefs.hardware['audioLib'] = 'PTB'
25+
prefs.hardware['audioLatencyMode'] = 3
26+
27+
from .auditory_oddball.aob import AuditoryOddball

eegnb/experiments/auditory_oddball/auditory_erp_arrayin.py

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,6 @@
11
"""Generate sound-only auditory oddball stimulus presentation.
22
"""
33

4-
from psychopy import prefs
5-
#change the pref libraty to PTB and set the latency mode to high precision
6-
prefs.hardware['audioLib'] = 'PTB'
7-
prefs.hardware['audioLatencyMode'] = 3
8-
94
import time
105
from optparse import OptionParser
116

eegnb/experiments/visual_n170/n170.py

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,5 @@
11
""" eeg-notebooks/eegnb/experiments/visual_n170/n170.py """
22

3-
from psychopy import prefs
4-
#change the pref libraty to PTB and set the latency mode to high precision
5-
prefs.hardware['audioLib'] = 'PTB'
6-
prefs.hardware['audioLatencyMode'] = 3
7-
83
import os
94
from time import time
105
from glob import glob

eegnb/experiments/visual_n170/n170_fixedstimorder.py

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,6 @@
66
77
"""
88

9-
from psychopy import prefs
10-
#change the pref libraty to PTB and set the latency mode to high precision
11-
prefs.hardware['audioLib'] = 'PTB'
12-
prefs.hardware['audioLatencyMode'] = 3
13-
149
from time import time
1510
from optparse import OptionParser
1611
import os

environments/eeg-expy-full.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ dependencies:
1010
- liblsl # install liblsl to prevent error on macOS and Ubuntu: "RuntimeError: LSL binary library file was not found."
1111
- wxpython>=4.0 # install wxpython to prevent error on macOS arm64: "site-packages/wx/_core.cpython-38-darwin.so, 0x0002): symbol not found in flat namespace '__ZN10wxBoxSizer20InformFirstDirectionEiii'"
1212
- html2text # avoid building wheel
13+
- cffi # Fix sound ffi.callback() issue with sounddevice on macOS: https://github.com/spatialaudio/python-sounddevice/issues/397
1314
- pip
1415
- pip:
1516
# Install package with only Analysis requirements

environments/eeg-expy-stimpres.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ dependencies:
66
- python>=3.8,<=3.10 # psychopy <= 3.10
77
- dukpy==0.2.3 # psychopy dependency, avoid failing due to building wheel on win 3.9.
88
- wxpython>=4.0 # install wxpython to prevent error on macOS arm64: "site-packages/wx/_core.cpython-38-darwin.so, 0x0002): symbol not found in flat namespace '__ZN10wxBoxSizer20InformFirstDirectionEiii'"
9+
- cffi # Fix sound ffi.callback() issue with sounddevice on macOS: https://github.com/spatialaudio/python-sounddevice/issues/397
910
- pip
1011
- pip:
1112
# Install package with Analysis + Streaming requirements

psychopy-sounddevice/.gitignore

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
# Byte-compiled / optimized / DLL files
2+
__pycache__/
3+
*.py[cod]
4+
*$py.class
5+
6+
# C extensions
7+
*.so
8+
9+
# Distribution / packaging
10+
.Python
11+
build/
12+
develop-eggs/
13+
dist/
14+
downloads/
15+
eggs/
16+
.eggs/
17+
lib/
18+
lib64/
19+
parts/
20+
sdist/
21+
var/
22+
wheels/
23+
pip-wheel-metadata/
24+
share/python-wheels/
25+
*.egg-info/
26+
.installed.cfg
27+
*.egg
28+
MANIFEST
29+
30+
# PyInstaller
31+
# Usually these files are written by a python script from a template
32+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
33+
*.manifest
34+
*.spec
35+
36+
# Installer logs
37+
pip-log.txt
38+
pip-delete-this-directory.txt
39+
40+
# Unit test / coverage reports
41+
htmlcov/
42+
.tox/
43+
.nox/
44+
.coverage
45+
.coverage.*
46+
.cache
47+
nosetests.xml
48+
coverage.xml
49+
*.cover
50+
*.py,cover
51+
.hypothesis/
52+
.pytest_cache/
53+
54+
# Translations
55+
*.mo
56+
*.pot
57+
58+
# Django stuff:
59+
*.log
60+
local_settings.py
61+
db.sqlite3
62+
db.sqlite3-journal
63+
64+
# Flask stuff:
65+
instance/
66+
.webassets-cache
67+
68+
# Scrapy stuff:
69+
.scrapy
70+
71+
# Sphinx documentation
72+
docs/_build/
73+
74+
# PyBuilder
75+
target/
76+
77+
# Jupyter Notebook
78+
.ipynb_checkpoints
79+
80+
# IPython
81+
profile_default/
82+
ipython_config.py
83+
84+
# pyenv
85+
.python-version
86+
87+
# pipenv
88+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
89+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
90+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
91+
# install all needed dependencies.
92+
#Pipfile.lock
93+
94+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
95+
__pypackages__/
96+
97+
# Celery stuff
98+
celerybeat-schedule
99+
celerybeat.pid
100+
101+
# SageMath parsed files
102+
*.sage.py
103+
104+
# Environments
105+
.env
106+
.venv
107+
env/
108+
venv/
109+
ENV/
110+
env.bak/
111+
venv.bak/
112+
113+
# Spyder project settings
114+
.spyderproject
115+
.spyproject
116+
117+
# Rope project settings
118+
.ropeproject
119+
120+
# mkdocs documentation
121+
/site
122+
123+
# mypy
124+
.mypy_cache/
125+
.dmypy.json
126+
dmypy.json
127+
128+
# Pyre type checker
129+
.pyre/

psychopy-sounddevice/CHANGELOG.txt

Whitespace-only changes.

0 commit comments

Comments
 (0)