psychopy 2024.1.3__py3-none-any.whl → 2024.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of psychopy might be problematic. Click here for more details.
- psychopy/.DS_Store +0 -0
- psychopy/CHANGELOG.txt +206 -0
- psychopy/GIT_SHA +1 -0
- psychopy/VERSION +1 -0
- psychopy/__init__.py +77 -15
- psychopy/app/Resources/classic/plugin16.png +0 -0
- psychopy/app/Resources/classic/plugin16@2x.png +0 -0
- psychopy/app/Resources/dark/plugin16.png +0 -0
- psychopy/app/Resources/dark/plugin16@2x.png +0 -0
- psychopy/app/Resources/light/plugin16.png +0 -0
- psychopy/app/Resources/light/plugin16@2x.png +0 -0
- psychopy/app/__init__.py +76 -2
- psychopy/app/_psychopyApp.py +126 -101
- psychopy/app/builder/builder.py +14 -10
- psychopy/app/builder/dialogs/__init__.py +8 -8
- psychopy/app/builder/dialogs/dlgsConditions.py +12 -13
- psychopy/app/builder/dialogs/paramCtrls.py +24 -57
- psychopy/app/builder/localizedStrings.py +11 -9
- psychopy/app/builder/validators.py +2 -2
- psychopy/app/coder/codeEditorBase.py +8 -8
- psychopy/app/coder/coder.py +4 -4
- psychopy/app/connections/sendusage.py +2 -2
- psychopy/app/connections/updates.py +9 -9
- psychopy/app/dialogs.py +34 -2
- psychopy/app/idle.py +31 -0
- psychopy/app/jobs.py +21 -3
- psychopy/app/linuxconfig/__init__.py +9 -0
- psychopy/app/locale/ar_001/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/ar_001/LC_MESSAGE/messages.po +4602 -2540
- psychopy/app/locale/es_CO/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/es_CO/LC_MESSAGE/messages.po +56 -54
- psychopy/app/locale/es_ES/LC_MESSAGE/messages.po +53 -43
- psychopy/app/locale/es_US/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/es_US/LC_MESSAGE/messages.po +56 -54
- psychopy/app/locale/ja_JP/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/ja_JP/LC_MESSAGE/messages.po +1258 -1176
- psychopy/app/locale/pt_PT/LC_MESSAGE/messages.po +9415 -5
- psychopy/app/pavlovia_ui/_base.py +33 -3
- psychopy/app/pavlovia_ui/search.py +0 -1
- psychopy/app/plugin_manager/dialog.py +104 -51
- psychopy/app/plugin_manager/packages.py +5 -0
- psychopy/app/plugin_manager/plugins.py +152 -67
- psychopy/app/preferencesDlg.py +8 -8
- psychopy/app/psychopyApp.py +11 -5
- psychopy/app/ribbon.py +124 -14
- psychopy/app/runner/runner.py +6 -1
- psychopy/app/stdout/stdOutRich.py +27 -11
- psychopy/app/themes/icons.py +52 -2
- psychopy/assets/__init__.py +0 -0
- psychopy/assets/click.png +0 -0
- psychopy/assets/clicknext.png +0 -0
- psychopy/assets/next.png +0 -0
- psychopy/assets/psychopy.ico +0 -0
- psychopy/assets/psychopy.png +0 -0
- psychopy/assets/templates/__init__.py +0 -0
- psychopy/assets/touch.png +0 -0
- psychopy/assets/touchnext.png +0 -0
- psychopy/assets/window.ico +0 -0
- psychopy/changes/2023.1.0.md +9 -0
- psychopy/changes/2024.1.0.md +16 -0
- psychopy/changes/__init__.py +0 -0
- psychopy/clock.py +2 -2
- psychopy/colors.py +2 -1
- psychopy/compatibility.py +53 -1
- psychopy/contrib/.DS_Store +0 -0
- psychopy/contrib/configobj/__init__.py +10 -8
- psychopy/data/__init__.py +3 -2
- psychopy/data/base.py +5 -5
- psychopy/data/experiment.py +130 -4
- psychopy/data/routine.py +56 -0
- psychopy/data/staircase.py +2 -2
- psychopy/data/trial.py +559 -97
- psychopy/data/utils.py +56 -21
- psychopy/demos/.DS_Store +0 -0
- psychopy/demos/builder/.DS_Store +0 -0
- psychopy/demos/builder/Design Templates/.DS_Store +0 -0
- psychopy/demos/builder/Experiments/.DS_Store +0 -0
- psychopy/demos/builder/Feature Demos/.DS_Store +0 -0
- psychopy/demos/builder/Feature Demos/buttonBox/buttonBoxDemo.psyexp +375 -0
- psychopy/demos/builder/Feature Demos/buttonBox/readme.md +5 -0
- psychopy/demos/builder/Feature Demos/pilotMode/pilotMode.psyexp +433 -0
- psychopy/demos/builder/Feature Demos/pilotMode/readme.md +7 -0
- psychopy/demos/builder/Feature Demos/progress/progressBar.psyexp +4 -4
- psychopy/demos/builder/Hardware/.DS_Store +0 -0
- psychopy/demos/builder/Helper Tools/.DS_Store +0 -0
- psychopy/demos/coder/.DS_Store +0 -0
- psychopy/demos/coder/hardware/testSoundLatency.py +2 -2
- psychopy/demos/coder/iohub/.DS_Store +0 -0
- psychopy/demos/coder/misc/hdf5_2_csv +33 -0
- psychopy/event.py +30 -29
- psychopy/experiment/.DS_Store +0 -0
- psychopy/experiment/_experiment.py +6 -6
- psychopy/experiment/components/.DS_Store +0 -0
- psychopy/experiment/components/__init__.py +6 -3
- psychopy/experiment/components/_base.py +286 -131
- psychopy/experiment/components/aperture/.DS_Store +0 -0
- psychopy/experiment/components/brush/.DS_Store +0 -0
- psychopy/experiment/components/button/.DS_Store +0 -0
- psychopy/experiment/components/button/__init__.py +5 -1
- psychopy/experiment/components/buttonBox/.DS_Store +0 -0
- psychopy/experiment/components/buttonBox/__init__.py +21 -12
- psychopy/experiment/components/camera/.DS_Store +0 -0
- psychopy/experiment/components/code/.DS_Store +0 -0
- psychopy/experiment/components/dots/.DS_Store +0 -0
- psychopy/experiment/components/eyetracker_record/.DS_Store +0 -0
- psychopy/experiment/components/eyetracker_record/__init__.py +92 -30
- psychopy/experiment/components/form/.DS_Store +0 -0
- psychopy/experiment/components/form/__init__.py +6 -2
- psychopy/experiment/components/grating/.DS_Store +0 -0
- psychopy/experiment/components/grating/__init__.py +14 -3
- psychopy/experiment/components/image/.DS_Store +0 -0
- psychopy/experiment/components/image/__init__.py +14 -3
- psychopy/experiment/components/joyButtons/.DS_Store +0 -0
- psychopy/experiment/components/joystick/.DS_Store +0 -0
- psychopy/experiment/components/keyboard/.DS_Store +0 -0
- psychopy/experiment/components/keyboard/__init__.py +22 -10
- psychopy/experiment/components/microphone/.DS_Store +0 -0
- psychopy/experiment/components/microphone/__init__.py +59 -39
- psychopy/experiment/components/mouse/.DS_Store +0 -0
- psychopy/experiment/components/mouse/__init__.py +44 -29
- psychopy/experiment/components/movie/.DS_Store +0 -0
- psychopy/experiment/components/movie/__init__.py +1 -1
- psychopy/experiment/components/panorama/.DS_Store +0 -0
- psychopy/experiment/components/parallelOut/.DS_Store +0 -0
- psychopy/experiment/components/patch/.DS_Store +0 -0
- psychopy/experiment/components/polygon/.DS_Store +0 -0
- psychopy/experiment/components/polygon/__init__.py +26 -6
- psychopy/experiment/components/progress/.DS_Store +0 -0
- psychopy/experiment/components/progress/__init__.py +1 -1
- psychopy/experiment/components/ratingScale/.DS_Store +0 -0
- psychopy/experiment/components/resourceManager/.DS_Store +0 -0
- psychopy/experiment/components/roi/.DS_Store +0 -0
- psychopy/experiment/components/roi/__init__.py +5 -0
- psychopy/experiment/components/routineSettings/.DS_Store +0 -0
- psychopy/experiment/components/routineSettings/__init__.py +57 -10
- psychopy/experiment/components/serialOut/.DS_Store +0 -0
- psychopy/experiment/components/settings/.DS_Store +0 -0
- psychopy/experiment/components/settings/__init__.py +117 -42
- psychopy/experiment/components/slider/.DS_Store +0 -0
- psychopy/experiment/components/sound/.DS_Store +0 -0
- psychopy/experiment/components/sound/__init__.py +54 -19
- psychopy/experiment/components/static/.DS_Store +0 -0
- psychopy/experiment/components/static/__init__.py +1 -1
- psychopy/experiment/components/text/.DS_Store +0 -0
- psychopy/experiment/components/text/__init__.py +28 -3
- psychopy/experiment/components/textbox/.DS_Store +0 -0
- psychopy/experiment/components/textbox/__init__.py +12 -2
- psychopy/experiment/components/unknown/.DS_Store +0 -0
- psychopy/experiment/components/unknown/__init__.py +1 -2
- psychopy/experiment/components/unknownPlugin/.DS_Store +0 -0
- psychopy/experiment/components/unknownPlugin/__init__.py +2 -2
- psychopy/experiment/components/variable/.DS_Store +0 -0
- psychopy/experiment/flow.py +11 -4
- psychopy/experiment/loops.py +85 -37
- psychopy/experiment/params.py +74 -32
- psychopy/experiment/py2js_transpiler.py +8 -1
- psychopy/experiment/routines/.DS_Store +0 -0
- psychopy/experiment/routines/_base.py +102 -22
- psychopy/experiment/routines/counterbalance/.DS_Store +0 -0
- psychopy/experiment/routines/counterbalance/__init__.py +5 -1
- psychopy/experiment/routines/eyetracker_calibrate/.DS_Store +0 -0
- psychopy/experiment/routines/eyetracker_validate/.DS_Store +0 -0
- psychopy/experiment/routines/pavlovia_survey/.DS_Store +0 -0
- psychopy/experiment/routines/photodiodeValidator/.DS_Store +0 -0
- psychopy/experiment/routines/photodiodeValidator/__init__.py +7 -6
- psychopy/experiment/routines/unknown/.DS_Store +0 -0
- psychopy/gui/wxgui.py +4 -4
- psychopy/hardware/.DS_Store +0 -0
- psychopy/hardware/__init__.py +1 -1
- psychopy/hardware/base.py +12 -0
- psychopy/hardware/camera/__init__.py +1 -15
- psychopy/hardware/cedrus.py +10 -11
- psychopy/hardware/crs/colorcal.py +13 -22
- psychopy/hardware/crs/optical.py +10 -20
- psychopy/hardware/emulator.py +17 -14
- psychopy/hardware/eyetracker.py +42 -118
- psychopy/hardware/gammasci.py +4 -15
- psychopy/hardware/keyboard.py +102 -11
- psychopy/hardware/listener.py +3 -0
- psychopy/hardware/microphone.py +148 -18
- psychopy/hardware/minolta.py +8 -15
- psychopy/hardware/photodiode.py +191 -16
- psychopy/hardware/photometer/__init__.py +11 -19
- psychopy/hardware/pr.py +8 -15
- psychopy/hardware/speaker.py +39 -4
- psychopy/info.py +0 -71
- psychopy/iohub/.DS_Store +0 -0
- psychopy/iohub/__init__.py +1 -1
- psychopy/iohub/client/__init__.py +30 -20
- psychopy/iohub/client/keyboard.py +24 -24
- psychopy/iohub/datastore/__init__.py +2 -2
- psychopy/iohub/datastore/util.py +2 -2
- psychopy/iohub/default_config.yaml +1 -1
- psychopy/iohub/devices/.DS_Store +0 -0
- psychopy/iohub/devices/__init__.py +112 -25
- psychopy/iohub/devices/deviceConfigValidation.py +2 -1
- psychopy/iohub/devices/experiment/default_experiment.yaml +12 -1
- psychopy/iohub/devices/experiment/supported_config_settings.yaml +5 -1
- psychopy/iohub/devices/eyetracker/.DS_Store +0 -0
- psychopy/iohub/devices/eyetracker/__init__.py +46 -0
- psychopy/iohub/devices/eyetracker/calibration/procedure.py +2 -2
- psychopy/iohub/devices/eyetracker/hw/gazepoint/__init__.py +14 -2
- psychopy/iohub/devices/eyetracker/hw/mouse/eyetracker.py +3 -4
- psychopy/iohub/server.py +2 -2
- psychopy/iohub/start_iohub_process.py +3 -0
- psychopy/iohub/util/__init__.py +62 -70
- psychopy/layout.py +5 -5
- psychopy/logging.py +8 -1
- psychopy/microphone.py +10 -37
- psychopy/platform_specific/__init__.py +0 -2
- psychopy/platform_specific/darwin.py +1 -3
- psychopy/platform_specific/linux.py +31 -33
- psychopy/platform_specific/win32.py +38 -13
- psychopy/plugins/__init__.py +148 -116
- psychopy/plugins/util.py +39 -0
- psychopy/preferences/Darwin.spec +4 -2
- psychopy/preferences/FreeBSD.spec +4 -2
- psychopy/preferences/Linux.spec +4 -2
- psychopy/preferences/Windows.spec +4 -2
- psychopy/preferences/baseNoArch.spec +4 -2
- psychopy/preferences/preferences.py +47 -24
- psychopy/projects/pavlovia.py +47 -4
- psychopy/scripts/psyexpCompile.py +0 -4
- psychopy/session.py +153 -21
- psychopy/sound/__init__.py +31 -21
- psychopy/sound/_base.py +20 -3
- psychopy/sound/audioclip.py +320 -33
- psychopy/sound/backend_ptb.py +47 -58
- psychopy/sound/backend_pygame.py +1 -1
- psychopy/sound/backend_pysound.py +6 -15
- psychopy/sound/transcribe.py +53 -0
- psychopy/tests/.DS_Store +0 -0
- psychopy/tests/data/.DS_Store +0 -0
- psychopy/tests/data/TestUnknownPluginComponent_load_resave.psyexp +135 -0
- psychopy/tests/data/Test_textbox/test_ori_0_bottom right.png +0 -0
- psychopy/tests/data/Test_textbox/test_ori_0_center.png +0 -0
- psychopy/tests/data/Test_textbox/test_ori_0_top left.png +0 -0
- psychopy/tests/data/Test_textbox/test_ori_120_bottom right.png +0 -0
- psychopy/tests/data/Test_textbox/test_ori_120_center.png +0 -0
- psychopy/tests/data/Test_textbox/test_ori_120_top left.png +0 -0
- psychopy/tests/data/Test_textbox/test_ori_180_bottom right.png +0 -0
- psychopy/tests/data/Test_textbox/test_ori_180_center.png +0 -0
- psychopy/tests/data/Test_textbox/test_ori_180_top left.png +0 -0
- psychopy/tests/data/Test_textbox/test_ori_240_bottom right.png +0 -0
- psychopy/tests/data/Test_textbox/test_ori_240_center.png +0 -0
- psychopy/tests/data/Test_textbox/test_ori_240_top left.png +0 -0
- psychopy/tests/data/correctScript/.DS_Store +0 -0
- psychopy/tests/data/test_components/testClearKeyboard/testClearKeyboard.psyexp +200 -0
- psychopy/tests/data/test_session/.DS_Store +0 -0
- psychopy/tests/data/test_session/root/testFutureTrials/testFutureTrials.psyexp +155 -0
- psychopy/tests/data/test_session/root/testTrialNav/trialNav.psyexp +158 -0
- psychopy/tests/test_app/.DS_Store +0 -0
- psychopy/tests/test_app/conftest.py +2 -2
- psychopy/tests/test_app/test_speed.py +4 -1
- psychopy/tests/test_data/test_TrialHandler2.py +146 -1
- psychopy/tests/test_experiment/.DS_Store +0 -0
- psychopy/tests/test_experiment/needs_wx/genComponsTemplate.py +3 -3
- psychopy/tests/test_experiment/needs_wx/test_components.py +2 -2
- psychopy/tests/test_experiment/test_components/test_KeyboardComponent.py +28 -0
- psychopy/tests/test_experiment/test_components/test_UnknownPluginComponent.py +27 -0
- psychopy/tests/test_experiment/test_components/test_base_components.py +58 -0
- psychopy/tests/test_experiment/test_py2js.py +1 -1
- psychopy/tests/test_hardware/test_keyboard.py +184 -16
- psychopy/tests/test_hardware/test_ports.py +1 -11
- psychopy/tests/test_liaison/test_Liaison.py +47 -0
- psychopy/tests/test_misc/test_core.py +5 -0
- psychopy/tests/test_session/test_Session.py +5 -1
- psychopy/tests/test_tools/test_versionchooser.py +39 -8
- psychopy/tests/test_visual/test_all_stimuli.py +0 -97
- psychopy/tests/test_visual/test_image.py +6 -5
- psychopy/tests/test_visual/test_textbox.py +36 -0
- psychopy/tests/utils.py +4 -0
- psychopy/tools/filetools.py +1 -1
- psychopy/tools/pkgtools.py +160 -137
- psychopy/tools/versionchooser.py +10 -10
- psychopy/tools/wizard.py +3 -3
- psychopy/visual/.DS_Store +0 -0
- psychopy/visual/backends/pygletbackend.py +24 -13
- psychopy/visual/basevisual.py +5 -11
- psychopy/visual/button.py +2 -14
- psychopy/visual/helpers.py +5 -5
- psychopy/visual/line.py +1 -2
- psychopy/visual/movie2.py +7 -816
- psychopy/visual/movie3.py +7 -589
- psychopy/visual/movies/__init__.py +8 -11
- psychopy/visual/movies/frame.py +5 -2
- psychopy/visual/movies/players/ffpyplayer_player.py +5 -2
- psychopy/visual/noise.py +8 -7
- psychopy/visual/patch.py +7 -16
- psychopy/visual/progress.py +1 -1
- psychopy/visual/radial.py +9 -7
- psychopy/visual/ratingscale.py +8 -1415
- psychopy/visual/secondorder.py +10 -9
- psychopy/visual/shape.py +7 -2
- psychopy/visual/text.py +1 -1
- psychopy/visual/textbox2/textbox2.py +28 -5
- psychopy/web.py +5 -2
- {psychopy-2024.1.3.dist-info → psychopy-2024.2.0.dist-info}/METADATA +8 -13
- {psychopy-2024.1.3.dist-info → psychopy-2024.2.0.dist-info}/RECORD +313 -219
- {psychopy-2024.1.3.dist-info → psychopy-2024.2.0.dist-info}/WHEEL +1 -1
- psychopy/app/Resources/click.png +0 -0
- psychopy/app/Resources/next.png +0 -0
- psychopy/experiment/components/patch/__init__.py +0 -121
- psychopy/experiment/components/patch/classic/patch.png +0 -0
- psychopy/experiment/components/patch/dark/patch.png +0 -0
- psychopy/experiment/components/patch/dark/patch@2x.png +0 -0
- psychopy/experiment/components/patch/light/patch.png +0 -0
- psychopy/experiment/components/patch/light/patch@2x.png +0 -0
- psychopy/experiment/components/ratingScale/__init__.py +0 -337
- psychopy/experiment/components/ratingScale/classic/ratingscale.png +0 -0
- psychopy/experiment/components/ratingScale/classic/ratingscale@2x.png +0 -0
- psychopy/experiment/components/ratingScale/dark/ratingScale@2x.png +0 -0
- psychopy/experiment/components/ratingScale/dark/ratingscale.png +0 -0
- psychopy/experiment/components/ratingScale/light/ratingScale@2x.png +0 -0
- psychopy/experiment/components/ratingScale/light/ratingscale.png +0 -0
- psychopy/platform_specific/posix.py +0 -16
- psychopy/tests/test_sound/test_microphone.py +0 -217
- psychopy/tests/test_visual/test_ratingScale.py +0 -299
- /psychopy/{app/Resources → assets}/Psychopy Window Favicon@16w.png +0 -0
- /psychopy/{app/Resources → assets}/Psychopy Window Favicon@32w.png +0 -0
- /psychopy/{app/Resources → assets}/USB-C.png +0 -0
- /psychopy/{app/Resources → assets}/USB.png +0 -0
- /psychopy/{app/Resources → assets}/creditCard.png +0 -0
- /psychopy/{app/Resources → assets}/default.mp3 +0 -0
- /psychopy/{app/Resources → assets}/default.mp4 +0 -0
- /psychopy/{app/Resources → assets}/default.png +0 -0
- /psychopy/{app/Resources → assets/templates}/instruct1.png +0 -0
- /psychopy/{app/Resources → assets/templates}/instruct2.png +0 -0
- {psychopy-2024.1.3.dist-info → psychopy-2024.2.0.dist-info}/entry_points.txt +0 -0
- {psychopy-2024.1.3.dist-info → psychopy-2024.2.0.dist-info}/licenses/AUTHORS.md +0 -0
- {psychopy-2024.1.3.dist-info → psychopy-2024.2.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
# display tips when starting PsychoPy
|
|
57
57
|
showStartupTips = boolean(default='True')
|
|
58
58
|
# what windows to display when PsychoPy starts
|
|
59
|
-
defaultView = option('builder', 'coder', 'runner', 'all', default='
|
|
59
|
+
defaultView = option('last', 'builder', 'coder', 'runner', 'all', default='last')
|
|
60
60
|
# reset preferences to defaults on next restart of PsychoPy
|
|
61
61
|
resetPrefs = boolean(default='False') # default must be False!
|
|
62
62
|
# save any unsaved preferences before closing the window
|
|
@@ -149,8 +149,10 @@
|
|
|
149
149
|
forceWindowed = boolean(default=True)
|
|
150
150
|
# What window size to use when forced to windowed mode
|
|
151
151
|
forcedWindowSize = list(default=list(800, 600))
|
|
152
|
-
# How much output to include in the log
|
|
152
|
+
# How much output to include in the log file when piloting ('error' is fewest messages, 'debug' is most)
|
|
153
153
|
pilotLoggingLevel = option('error', 'warning', 'data', 'exp', 'info', 'debug', default='debug')
|
|
154
|
+
# How much output to display in the console / app when piloting ('error' is fewest messages, 'debug' is most).
|
|
155
|
+
pilotConsoleLoggingLevel = option('error', 'warning', 'data', 'exp', 'info', 'debug', default='warning')
|
|
154
156
|
# Show an orange border around the window when in piloting mode
|
|
155
157
|
showPilotingIndicator = boolean(default=True)
|
|
156
158
|
# Prevent experiment from enabling rush mode when piloting
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
# display tips when starting PsychoPy
|
|
53
53
|
showStartupTips = boolean(default='True')
|
|
54
54
|
# what windows to display when PsychoPy starts
|
|
55
|
-
defaultView = option('builder', 'coder', 'runner', 'all', default='
|
|
55
|
+
defaultView = option('last', 'builder', 'coder', 'runner', 'all', default='last')
|
|
56
56
|
# reset preferences to defaults on next restart of PsychoPy
|
|
57
57
|
resetPrefs = boolean(default='False') # default must be False!
|
|
58
58
|
# save any unsaved preferences before closing the window
|
|
@@ -145,8 +145,10 @@
|
|
|
145
145
|
forceWindowed = boolean(default=True)
|
|
146
146
|
# What window size to use when forced to windowed mode
|
|
147
147
|
forcedWindowSize = list(default=list(800, 600))
|
|
148
|
-
# How much output to include in the log
|
|
148
|
+
# How much output to include in the log file when piloting ('error' is fewest messages, 'debug' is most)
|
|
149
149
|
pilotLoggingLevel = option('error', 'warning', 'data', 'exp', 'info', 'debug', default='debug')
|
|
150
|
+
# How much output to display in the console / app when piloting ('error' is fewest messages, 'debug' is most).
|
|
151
|
+
pilotConsoleLoggingLevel = option('error', 'warning', 'data', 'exp', 'info', 'debug', default='warning')
|
|
150
152
|
# Show an orange border around the window when in piloting mode
|
|
151
153
|
showPilotingIndicator = boolean(default=True)
|
|
152
154
|
# Prevent experiment from enabling rush mode when piloting
|
|
@@ -8,13 +8,13 @@ import platform
|
|
|
8
8
|
from pathlib import Path
|
|
9
9
|
from .. import __version__
|
|
10
10
|
|
|
11
|
-
from
|
|
11
|
+
from packaging.version import Version
|
|
12
12
|
import shutil
|
|
13
13
|
|
|
14
14
|
try:
|
|
15
15
|
import configobj
|
|
16
16
|
if (sys.version_info.minor >= 7 and
|
|
17
|
-
|
|
17
|
+
Version(configobj.__version__) < Version('5.1.0')):
|
|
18
18
|
raise ImportError('Installed configobj does not support Python 3.7+')
|
|
19
19
|
_haveConfigobj = True
|
|
20
20
|
except ImportError:
|
|
@@ -101,6 +101,14 @@ class Preferences:
|
|
|
101
101
|
self.loadAll() # reloads, now getting all from .spec
|
|
102
102
|
|
|
103
103
|
def getPaths(self):
|
|
104
|
+
"""Get the paths to various directories and files used by PsychoPy.
|
|
105
|
+
|
|
106
|
+
If the paths are not found, they are created. Usually, this is only
|
|
107
|
+
necessary on the first run of PsychoPy. However, if the user has
|
|
108
|
+
deleted or moved the preferences directory, this method will recreate
|
|
109
|
+
those directories.
|
|
110
|
+
|
|
111
|
+
"""
|
|
104
112
|
# on mac __file__ might be a local path, so make it the full path
|
|
105
113
|
thisFileAbsPath = os.path.abspath(__file__)
|
|
106
114
|
prefSpecDir = os.path.split(thisFileAbsPath)[0]
|
|
@@ -119,6 +127,7 @@ class Preferences:
|
|
|
119
127
|
self.paths['appFile'] = join(dirApp, 'PsychoPy.py')
|
|
120
128
|
self.paths['demos'] = join(dirPsychoPy, 'demos')
|
|
121
129
|
self.paths['resources'] = dirResources
|
|
130
|
+
self.paths['assets'] = join(dirPsychoPy, "assets")
|
|
122
131
|
self.paths['tests'] = join(dirPsychoPy, 'tests')
|
|
123
132
|
# path to libs/frameworks
|
|
124
133
|
if 'PsychoPy.app/Contents' in exePath:
|
|
@@ -148,6 +157,7 @@ class Preferences:
|
|
|
148
157
|
'themes', # define theme path
|
|
149
158
|
'fonts', # find / copy fonts
|
|
150
159
|
'packages', # packages and plugins
|
|
160
|
+
'configs', # config files for plugins
|
|
151
161
|
'cache', # cache for downloaded and other temporary files
|
|
152
162
|
)
|
|
153
163
|
|
|
@@ -164,42 +174,55 @@ class Preferences:
|
|
|
164
174
|
except OSError as err:
|
|
165
175
|
if err.errno != errno.EEXIST:
|
|
166
176
|
raise
|
|
167
|
-
|
|
177
|
+
|
|
168
178
|
# root site-packages directory for user-installed packages and add it
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
179
|
+
userPkgRoot = Path(self.paths['packages'])
|
|
180
|
+
|
|
181
|
+
# Package paths for custom user site-packages, these should be compliant
|
|
182
|
+
# with platform specific conventions.
|
|
183
|
+
if sys.platform == 'win32':
|
|
184
|
+
pyDirName = "Python" + sys.winver.replace(".", "")
|
|
185
|
+
userPackages = userPkgRoot / pyDirName / "site-packages"
|
|
186
|
+
userInclude = userPkgRoot / pyDirName / "Include"
|
|
187
|
+
userScripts = userPkgRoot / pyDirName / "Scripts"
|
|
188
|
+
elif sys.platform == 'darwin' and sys._framework: # macos + framework
|
|
189
|
+
pyVersion = sys.version_info
|
|
190
|
+
pyDirName = "python{}.{}".format(pyVersion[0], pyVersion[1])
|
|
191
|
+
userPackages = userPkgRoot / "lib" / "python" / "site-packages"
|
|
192
|
+
userInclude = userPkgRoot / "include" / pyDirName
|
|
193
|
+
userScripts = userPkgRoot / "bin"
|
|
194
|
+
else: # posix (including linux and macos without framework)
|
|
195
|
+
pyVersion = sys.version_info
|
|
196
|
+
pyDirName = "python{}.{}".format(pyVersion[0], pyVersion[1])
|
|
197
|
+
userPackages = userPkgRoot / "lib" / pyDirName / "site-packages"
|
|
198
|
+
userInclude = userPkgRoot / "include" / pyDirName
|
|
199
|
+
userScripts = userPkgRoot / "bin"
|
|
173
200
|
|
|
174
201
|
# populate directory structure for user-installed packages
|
|
175
|
-
if not
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
if not
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
# Scripts directory for user-installed packages
|
|
183
|
-
userScriptsDir = prefixRootDir / 'Scripts'
|
|
184
|
-
if not userScriptsDir.is_dir():
|
|
185
|
-
userScriptsDir.mkdir()
|
|
202
|
+
if not userPackages.is_dir():
|
|
203
|
+
userPackages.mkdir(parents=True)
|
|
204
|
+
if not userInclude.is_dir():
|
|
205
|
+
userInclude.mkdir(parents=True)
|
|
206
|
+
if not userScripts.is_dir():
|
|
207
|
+
userScripts.mkdir(parents=True)
|
|
186
208
|
|
|
187
209
|
# add paths from plugins/packages (installed by plugins manager)
|
|
188
|
-
self.paths['userPackages'] =
|
|
189
|
-
self.paths['
|
|
190
|
-
|
|
210
|
+
self.paths['userPackages'] = userPackages
|
|
211
|
+
self.paths['userInclude'] = userInclude
|
|
212
|
+
self.paths['userScripts'] = userScripts
|
|
213
|
+
|
|
191
214
|
# Get dir for base and user themes
|
|
192
215
|
baseThemeDir = Path(self.paths['appDir']) / "themes" / "spec"
|
|
193
216
|
userThemeDir = Path(self.paths['themes'])
|
|
194
217
|
# Check what version user themes were last updated in
|
|
195
218
|
if (userThemeDir / "last.ver").is_file():
|
|
196
219
|
with open(userThemeDir / "last.ver", "r") as f:
|
|
197
|
-
lastVer =
|
|
220
|
+
lastVer = Version(f.read())
|
|
198
221
|
else:
|
|
199
222
|
# if no version available, assume it was the first version to have themes
|
|
200
|
-
lastVer =
|
|
223
|
+
lastVer = Version("2020.2.0")
|
|
201
224
|
# If version has changed since base themes last copied, they need updating
|
|
202
|
-
updateThemes = lastVer <
|
|
225
|
+
updateThemes = lastVer < Version(__version__)
|
|
203
226
|
# Copy base themes to user themes folder if missing or need update
|
|
204
227
|
for file in baseThemeDir.glob("*.json"):
|
|
205
228
|
if updateThemes or not (Path(self.paths['themes']) / file.name).is_file():
|
psychopy/projects/pavlovia.py
CHANGED
|
@@ -17,7 +17,7 @@ import subprocess
|
|
|
17
17
|
import traceback
|
|
18
18
|
|
|
19
19
|
import pandas
|
|
20
|
-
from
|
|
20
|
+
from packaging.version import Version
|
|
21
21
|
|
|
22
22
|
from psychopy import logging, prefs, exceptions
|
|
23
23
|
from psychopy.tools.filetools import DictStorage, KnownProjects
|
|
@@ -52,7 +52,9 @@ urlencode = parse.quote
|
|
|
52
52
|
|
|
53
53
|
pavloviaPrefsDir = os.path.join(prefs.paths['userPrefsDir'], 'pavlovia')
|
|
54
54
|
rootURL = "https://gitlab.pavlovia.org"
|
|
55
|
-
client_id = '
|
|
55
|
+
client_id = '944b87ee0e6b4f510881d6f6bc082f64c7bba17d305efdb829e6e0e7ed466b34'
|
|
56
|
+
code_challenge = None
|
|
57
|
+
code_verifier = None
|
|
56
58
|
scopes = []
|
|
57
59
|
redirect_url = 'https://gitlab.pavlovia.org/'
|
|
58
60
|
|
|
@@ -78,13 +80,50 @@ OK = 1
|
|
|
78
80
|
|
|
79
81
|
|
|
80
82
|
def getAuthURL():
|
|
83
|
+
# starting state
|
|
81
84
|
state = str(uuid4()) # create a private "state" based on uuid
|
|
85
|
+
# code challenge and verifier need to be global so we can access them later
|
|
86
|
+
global code_challenge, code_verifier
|
|
87
|
+
# generate code challenge and corresponding verifier
|
|
88
|
+
code_verifier, code_challenge = generateCodeChallengePair()
|
|
89
|
+
# construct auth url
|
|
82
90
|
auth_url = ('https://gitlab.pavlovia.org/oauth/authorize?client_id={}'
|
|
83
|
-
'&redirect_uri={}&response_type=
|
|
84
|
-
.format(client_id, redirect_url, state))
|
|
91
|
+
'&redirect_uri={}&response_type=code&state={}&code_challenge={}&code_challenge_method=S256'
|
|
92
|
+
.format(client_id, redirect_url, state, code_challenge))
|
|
93
|
+
|
|
85
94
|
return auth_url, state
|
|
86
95
|
|
|
87
96
|
|
|
97
|
+
def generateCodeChallengePair():
|
|
98
|
+
"""
|
|
99
|
+
Create a unique random string and its corresponding encoded challenge.
|
|
100
|
+
|
|
101
|
+
Returns
|
|
102
|
+
-------
|
|
103
|
+
str
|
|
104
|
+
A code verifier - a random collection of characters
|
|
105
|
+
str
|
|
106
|
+
A code challenge - the code verifier transformed using a particular algorithm
|
|
107
|
+
"""
|
|
108
|
+
from numpy.random import randint, choice as randchoice
|
|
109
|
+
import hashlib
|
|
110
|
+
import base64
|
|
111
|
+
# characters valid for a code verifier...
|
|
112
|
+
validChars = list("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~")
|
|
113
|
+
# first make the answer - pick random alphanumeric chars
|
|
114
|
+
code_verifier = ""
|
|
115
|
+
for n in range(randint(44, 127)):
|
|
116
|
+
code_verifier += randchoice(validChars)
|
|
117
|
+
# transform to make code_challenge
|
|
118
|
+
code_challenge = code_verifier
|
|
119
|
+
# SHA-256 digest
|
|
120
|
+
code_verifier_hash = hashlib.sha256(code_verifier.encode("utf-8")).digest()
|
|
121
|
+
# Base64 urlsafe encode without padding
|
|
122
|
+
code_challenge = base64.urlsafe_b64encode(code_verifier_hash).decode("utf-8").rstrip("=")
|
|
123
|
+
|
|
124
|
+
return code_verifier, code_challenge
|
|
125
|
+
|
|
126
|
+
|
|
88
127
|
def login(tokenOrUsername, rememberMe=True):
|
|
89
128
|
"""Sets the current user by means of a token
|
|
90
129
|
|
|
@@ -619,6 +658,10 @@ class PavloviaProject(dict):
|
|
|
619
658
|
try:
|
|
620
659
|
value = dict.__getitem__(self, key)
|
|
621
660
|
except KeyError:
|
|
661
|
+
# if no project, return None
|
|
662
|
+
if self.project is None:
|
|
663
|
+
return None
|
|
664
|
+
# otherwise, get from attributes
|
|
622
665
|
if key in self.project.attributes:
|
|
623
666
|
value = self.project.attributes[key]
|
|
624
667
|
elif hasattr(self, "_info") and key in self._info:
|
|
@@ -58,10 +58,6 @@ def generateScript(exp, outfile, target="PsychoPy"):
|
|
|
58
58
|
# make sure we have a legacy save file
|
|
59
59
|
if not Path(exp.legacyFilename).is_file():
|
|
60
60
|
exp.saveToXML(filename=exp.filename)
|
|
61
|
-
# if compiling to JS, js file needs to have legacy filename
|
|
62
|
-
_stem, _ext = os.path.splitext(outfile)
|
|
63
|
-
if _ext == ".js":
|
|
64
|
-
outfile = _stem + "_legacy" + _ext
|
|
65
61
|
# generate command to run compile from requested version
|
|
66
62
|
cmd = [
|
|
67
63
|
pythonExec, '-m', compiler, str(exp.legacyFilename), '-o', outfile
|
psychopy/session.py
CHANGED
|
@@ -236,21 +236,29 @@ class Session:
|
|
|
236
236
|
file, contained somewhere within the folder supplied for `root`. Paths can be absolute or
|
|
237
237
|
relative to the root folder. Leave as None for a blank dict, experiments can be added
|
|
238
238
|
later on via `addExperiment()`.
|
|
239
|
+
|
|
240
|
+
restMsg : str
|
|
241
|
+
Message to display inbetween experiments.
|
|
239
242
|
"""
|
|
240
243
|
|
|
241
|
-
def __init__(
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
244
|
+
def __init__(
|
|
245
|
+
self,
|
|
246
|
+
root,
|
|
247
|
+
dataDir=None,
|
|
248
|
+
clock="iso",
|
|
249
|
+
win=None,
|
|
250
|
+
experiments=None,
|
|
251
|
+
loggingLevel="info",
|
|
252
|
+
priorityThreshold=constants.priority.EXCLUDE+1,
|
|
253
|
+
params=None,
|
|
254
|
+
liaison=None,
|
|
255
|
+
restMsg="Rest..."
|
|
256
|
+
):
|
|
251
257
|
# Store root and add to Python path
|
|
252
258
|
self.root = Path(root)
|
|
253
259
|
sys.path.insert(1, str(self.root))
|
|
260
|
+
# store rest message
|
|
261
|
+
self.restMsg = restMsg
|
|
254
262
|
# Create data folder
|
|
255
263
|
if dataDir is None:
|
|
256
264
|
dataDir = self.root / "data" / str(core.Clock().getTime(format="%Y-%m-%d_%H-%M-%S-%f"))
|
|
@@ -336,9 +344,7 @@ class Session:
|
|
|
336
344
|
"""
|
|
337
345
|
if self.win is not None and not self.win._closed:
|
|
338
346
|
# Show waiting message
|
|
339
|
-
self.win.showMessage(
|
|
340
|
-
"Waiting to start..."
|
|
341
|
-
))
|
|
347
|
+
self.win.showMessage(self.restMsg)
|
|
342
348
|
self.win.color = "grey"
|
|
343
349
|
# Flip the screen
|
|
344
350
|
self.win.flip()
|
|
@@ -760,9 +766,7 @@ class Session:
|
|
|
760
766
|
# If win is None, make a Window
|
|
761
767
|
from psychopy.visual import Window
|
|
762
768
|
self.win = Window(**params)
|
|
763
|
-
self.win.showMessage(
|
|
764
|
-
"Waiting to start..."
|
|
765
|
-
))
|
|
769
|
+
self.win.showMessage(self.restMsg)
|
|
766
770
|
else:
|
|
767
771
|
# otherwise, just set the attributes which are safe to set
|
|
768
772
|
self.win.color = params.get('color', self.win.color)
|
|
@@ -1051,9 +1055,7 @@ class Session:
|
|
|
1051
1055
|
# Mark ExperimentHandler as no longer current
|
|
1052
1056
|
self.currentExperiment = None
|
|
1053
1057
|
# Display waiting text
|
|
1054
|
-
self.win.showMessage(
|
|
1055
|
-
"Waiting to start..."
|
|
1056
|
-
))
|
|
1058
|
+
self.win.showMessage(self.restMsg)
|
|
1057
1059
|
self.win.color = "grey"
|
|
1058
1060
|
# Raise any errors now
|
|
1059
1061
|
if err is not None:
|
|
@@ -1074,6 +1076,102 @@ class Session:
|
|
|
1074
1076
|
|
|
1075
1077
|
return True
|
|
1076
1078
|
|
|
1079
|
+
def getAllTrials(self):
|
|
1080
|
+
"""
|
|
1081
|
+
Returns all trials (elapsed, current and upcoming) with an index indicating which trial is
|
|
1082
|
+
the current trial.
|
|
1083
|
+
|
|
1084
|
+
Returns
|
|
1085
|
+
-------
|
|
1086
|
+
list[Trial]
|
|
1087
|
+
List of trials, in order (oldest to newest)
|
|
1088
|
+
int
|
|
1089
|
+
Index of the current trial in this list
|
|
1090
|
+
"""
|
|
1091
|
+
# return None if there's no current experiment
|
|
1092
|
+
if self.currentExperiment is None:
|
|
1093
|
+
return None
|
|
1094
|
+
# get trials from current experiment
|
|
1095
|
+
trials, i = self.currentExperiment.getAllTrials()
|
|
1096
|
+
|
|
1097
|
+
return trials, i
|
|
1098
|
+
|
|
1099
|
+
def getCurrentTrial(self, asDict=False):
|
|
1100
|
+
"""
|
|
1101
|
+
Returns the current trial (`.thisTrial`)
|
|
1102
|
+
|
|
1103
|
+
Returns
|
|
1104
|
+
-------
|
|
1105
|
+
Trial
|
|
1106
|
+
The current trial
|
|
1107
|
+
"""
|
|
1108
|
+
# return None if there's no current experiment
|
|
1109
|
+
if self.currentExperiment is None:
|
|
1110
|
+
return None
|
|
1111
|
+
# get trial from current experiment
|
|
1112
|
+
trial = self.currentExperiment.getCurrentTrial()
|
|
1113
|
+
# convert to dict if needed
|
|
1114
|
+
if asDict and trial is not None:
|
|
1115
|
+
trial = trial.getDict()
|
|
1116
|
+
|
|
1117
|
+
return trial
|
|
1118
|
+
|
|
1119
|
+
def getFutureTrial(self, n=1, asDict=False):
|
|
1120
|
+
"""
|
|
1121
|
+
Returns the condition for n trials into the future, without
|
|
1122
|
+
advancing the trials. Returns 'None' if attempting to go beyond
|
|
1123
|
+
the last trial in the current loop, if there is no current loop
|
|
1124
|
+
or if there is no current experiment.
|
|
1125
|
+
|
|
1126
|
+
Parameters
|
|
1127
|
+
----------
|
|
1128
|
+
n : int
|
|
1129
|
+
Number of places into the future to look
|
|
1130
|
+
asDict : bool
|
|
1131
|
+
If True, convert Trial object to a dict before returning (useful for Liaison)
|
|
1132
|
+
"""
|
|
1133
|
+
# return None if there's no current experiment
|
|
1134
|
+
if self.currentExperiment is None:
|
|
1135
|
+
return None
|
|
1136
|
+
# get future trial from current experiment
|
|
1137
|
+
trial = self.currentExperiment.getFutureTrial(n)
|
|
1138
|
+
# convert to dict if needed
|
|
1139
|
+
if asDict and trial is not None:
|
|
1140
|
+
trial = trial.getDict()
|
|
1141
|
+
|
|
1142
|
+
return trial
|
|
1143
|
+
|
|
1144
|
+
def getFutureTrials(self, n=1, start=0, asDict=False):
|
|
1145
|
+
"""
|
|
1146
|
+
Returns Trial objects for a given range in the future. Will start looking at `start` trials
|
|
1147
|
+
in the future and will return n trials from then, so e.g. to get all trials from 2 in the
|
|
1148
|
+
future to 5 in the future you would use `start=2` and `n=3`.
|
|
1149
|
+
|
|
1150
|
+
Parameters
|
|
1151
|
+
----------
|
|
1152
|
+
n : int, optional
|
|
1153
|
+
How many trials into the future to look, by default 1
|
|
1154
|
+
start : int, optional
|
|
1155
|
+
How many trials into the future to start looking at, by default 0
|
|
1156
|
+
asDict : bool
|
|
1157
|
+
If True, convert Trial objects to a dict before returning (useful for Liaison)
|
|
1158
|
+
|
|
1159
|
+
Returns
|
|
1160
|
+
-------
|
|
1161
|
+
list[Trial or dict or None]
|
|
1162
|
+
List of Trial objects n long. Any trials beyond the last trial are None.
|
|
1163
|
+
"""
|
|
1164
|
+
# blank list to store trials in
|
|
1165
|
+
trials = []
|
|
1166
|
+
# iterate through n trials
|
|
1167
|
+
for i in range(n):
|
|
1168
|
+
# add each to the list
|
|
1169
|
+
trials.append(
|
|
1170
|
+
self.getFutureTrial(start + i, asDict=asDict)
|
|
1171
|
+
)
|
|
1172
|
+
|
|
1173
|
+
return trials
|
|
1174
|
+
|
|
1077
1175
|
def pauseExperiment(self):
|
|
1078
1176
|
"""
|
|
1079
1177
|
Pause the currently running experiment.
|
|
@@ -1137,8 +1235,42 @@ class Session:
|
|
|
1137
1235
|
|
|
1138
1236
|
return True
|
|
1139
1237
|
|
|
1140
|
-
|
|
1141
|
-
|
|
1238
|
+
def skipTrials(self, n=1):
|
|
1239
|
+
"""
|
|
1240
|
+
Skip ahead n trials - the trials inbetween will be marked as "skipped". If you try to
|
|
1241
|
+
skip past the last trial, will log a warning and skip *to* the last trial.
|
|
1242
|
+
|
|
1243
|
+
Parameters
|
|
1244
|
+
----------
|
|
1245
|
+
n : int
|
|
1246
|
+
Number of trials to skip ahead
|
|
1247
|
+
"""
|
|
1248
|
+
# return if there's no current experiment
|
|
1249
|
+
if self.currentExperiment is None:
|
|
1250
|
+
return
|
|
1251
|
+
# skip trials in current loop
|
|
1252
|
+
self.currentExperiment.skipTrials(n)
|
|
1253
|
+
|
|
1254
|
+
def rewindTrials(self, n=1):
|
|
1255
|
+
"""
|
|
1256
|
+
Skip ahead n trials - the trials inbetween will be marked as "skipped". If you try to
|
|
1257
|
+
skip past the last trial, will log a warning and skip *to* the last trial.
|
|
1258
|
+
|
|
1259
|
+
Parameters
|
|
1260
|
+
----------
|
|
1261
|
+
n : int
|
|
1262
|
+
Number of trials to skip ahead
|
|
1263
|
+
|
|
1264
|
+
Returns
|
|
1265
|
+
-------
|
|
1266
|
+
bool or None
|
|
1267
|
+
True if the operation completed/queued successfully
|
|
1268
|
+
"""
|
|
1269
|
+
# return if there's no current experiment
|
|
1270
|
+
if self.currentExperiment is None:
|
|
1271
|
+
return
|
|
1272
|
+
# rewind trials in current loop
|
|
1273
|
+
self.currentExperiment.rewindTrials(n)
|
|
1142
1274
|
|
|
1143
1275
|
def saveExperimentData(self, key, thisExp=None, blocking=True):
|
|
1144
1276
|
"""
|
psychopy/sound/__init__.py
CHANGED
|
@@ -15,7 +15,7 @@ a given time in the future.
|
|
|
15
15
|
By default PsychoPy will try to use the following Libs, in this order, for
|
|
16
16
|
sound reproduction but you can alter the order in
|
|
17
17
|
preferences > hardware > audioLib:
|
|
18
|
-
['sounddevice', '
|
|
18
|
+
['sounddevice', 'pyo', 'pygame']
|
|
19
19
|
For portaudio-based backends (all except for pygame) there is also a
|
|
20
20
|
choice of the underlying sound driver (e.g. ASIO, CoreAudio etc).
|
|
21
21
|
|
|
@@ -66,10 +66,11 @@ pyoSndServer = None
|
|
|
66
66
|
Sound = None
|
|
67
67
|
audioLib = None
|
|
68
68
|
audioDriver = None
|
|
69
|
+
backend = None
|
|
69
70
|
|
|
70
|
-
# These are the names that can be used in the prefs to specifiy audio libraries.
|
|
71
|
-
# The available libraries are hard-coded at this point until we can overhaul
|
|
72
|
-
# the sound library to be more modular.
|
|
71
|
+
# These are the names that can be used in the prefs to specifiy audio libraries.
|
|
72
|
+
# The available libraries are hard-coded at this point until we can overhaul
|
|
73
|
+
# the sound library to be more modular.
|
|
73
74
|
_audioLibs = ['PTB', 'sounddevice', 'pyo', 'pysoundcard', 'pygame']
|
|
74
75
|
failed = [] # keep track of audio libs that failed to load
|
|
75
76
|
|
|
@@ -90,12 +91,12 @@ for thisLibName in prefs.hardware['audioLib']:
|
|
|
90
91
|
logging.info(f"Trying to load audio library: {thisLibName}")
|
|
91
92
|
|
|
92
93
|
# Iterate over the list of audioLibs and try to load the first one that
|
|
93
|
-
# is supported. If none are supported, load PTB as a fallback. If PTB isn't
|
|
94
|
+
# is supported. If none are supported, load PTB as a fallback. If PTB isn't
|
|
94
95
|
# installed, raise an error.
|
|
95
96
|
thisLibName = thisLibName.lower()
|
|
96
97
|
|
|
97
98
|
# lowercased list of valid audio libraries for safe comparisons
|
|
98
|
-
validLibs = [libName.lower() for libName in _audioLibs]
|
|
99
|
+
validLibs = [libName.lower() for libName in _audioLibs]
|
|
99
100
|
|
|
100
101
|
# check if `thisLibName` is a valid audio library
|
|
101
102
|
if thisLibName not in validLibs:
|
|
@@ -106,7 +107,7 @@ for thisLibName in prefs.hardware['audioLib']:
|
|
|
106
107
|
|
|
107
108
|
# select the backend and set the Sound class
|
|
108
109
|
if thisLibName == 'ptb':
|
|
109
|
-
# The Psychtoolbox backend is
|
|
110
|
+
# The Psychtoolbox backend is preferred, provides the best performance
|
|
110
111
|
# and is the only one that supports low-latency scheduling. If no other
|
|
111
112
|
# audio backend can be loaded, we will use PTB.
|
|
112
113
|
if not bits32:
|
|
@@ -118,7 +119,7 @@ for thisLibName in prefs.hardware['audioLib']:
|
|
|
118
119
|
failed.append(thisLibName)
|
|
119
120
|
continue
|
|
120
121
|
else:
|
|
121
|
-
break
|
|
122
|
+
break
|
|
122
123
|
else:
|
|
123
124
|
logging.warning("PTB backend is not supported on 32-bit Python. "
|
|
124
125
|
"Trying another backend...")
|
|
@@ -127,6 +128,8 @@ for thisLibName in prefs.hardware['audioLib']:
|
|
|
127
128
|
# pyo is a wrapper around PortAudio, which is a cross-platform audio
|
|
128
129
|
# library. It is the recommended backend for Windows and Linux.
|
|
129
130
|
try:
|
|
131
|
+
# Caution: even import failed inside, we still get a module object.
|
|
132
|
+
# This is not the case for other backends and may not be desired.
|
|
130
133
|
from . import backend_pyo as backend
|
|
131
134
|
Sound = backend.SoundPyo
|
|
132
135
|
pyoSndServer = backend.pyoSndServer
|
|
@@ -140,6 +143,8 @@ for thisLibName in prefs.hardware['audioLib']:
|
|
|
140
143
|
# sounddevice is a wrapper around PortAudio, which is a cross-platform
|
|
141
144
|
# audio library. It is the recommended backend for Windows and Linux.
|
|
142
145
|
try:
|
|
146
|
+
# Caution: even import failed inside, we still get a module object.
|
|
147
|
+
# This is not the case for other backends and may not be desired.
|
|
143
148
|
from . import backend_sounddevice as backend
|
|
144
149
|
Sound = backend.SoundDeviceSound
|
|
145
150
|
except Exception:
|
|
@@ -149,7 +154,7 @@ for thisLibName in prefs.hardware['audioLib']:
|
|
|
149
154
|
break
|
|
150
155
|
elif thisLibName == 'pygame':
|
|
151
156
|
# pygame is a cross-platform audio library. It is no longer supported by
|
|
152
|
-
# PsychoPy, but we keep it here for backwards compatibility until
|
|
157
|
+
# PsychoPy, but we keep it here for backwards compatibility until
|
|
153
158
|
# something breaks.
|
|
154
159
|
try:
|
|
155
160
|
from . import backend_pygame as backend
|
|
@@ -163,7 +168,7 @@ for thisLibName in prefs.hardware['audioLib']:
|
|
|
163
168
|
# pysoundcard is a wrapper around PortAudio, which is a cross-platform
|
|
164
169
|
# audio library.
|
|
165
170
|
try:
|
|
166
|
-
from . import
|
|
171
|
+
from . import backend_pysound as backend
|
|
167
172
|
Sound = backend.SoundPySoundCard
|
|
168
173
|
except Exception:
|
|
169
174
|
failed.append(thisLibName)
|
|
@@ -173,11 +178,11 @@ for thisLibName in prefs.hardware['audioLib']:
|
|
|
173
178
|
else:
|
|
174
179
|
# Catch-all for invalid audioLib prefs.
|
|
175
180
|
msg = ("audioLib pref should be one of {!r}, not {!r}"
|
|
176
|
-
|
|
181
|
+
.format(_audioLibs, thisLibName))
|
|
177
182
|
raise ValueError(msg)
|
|
178
183
|
else:
|
|
179
184
|
# if we get here, there is no audioLib that is supported, try for PTB
|
|
180
|
-
msg = ("Failed to load any of the audioLibs: {!r}. Falling back to "
|
|
185
|
+
msg = ("Failed to load any of the audioLibs: {!r}. Falling back to "
|
|
181
186
|
"PsychToolbox ('ptb') backend for sound. Be sure to add 'ptb' to "
|
|
182
187
|
"preferences to avoid seeing this message again.".format(failed))
|
|
183
188
|
logging.error(msg)
|
|
@@ -200,7 +205,7 @@ else:
|
|
|
200
205
|
# if we get here, there is no audioLib that is supported
|
|
201
206
|
logging.error(
|
|
202
207
|
"No audioLib could be loaded. Tried: {}\n Check whether the necessary "
|
|
203
|
-
"audioLibs are installed".format(prefs.hardware['audioLib']))
|
|
208
|
+
"audioLibs are installed.".format(prefs.hardware['audioLib']))
|
|
204
209
|
|
|
205
210
|
# warn the user
|
|
206
211
|
if audioLib is not None:
|
|
@@ -228,12 +233,12 @@ def setDevice(dev, kind=None):
|
|
|
228
233
|
if dev is None:
|
|
229
234
|
# if given None, do nothing
|
|
230
235
|
return
|
|
231
|
-
|
|
236
|
+
|
|
232
237
|
global backend # pull from module namespace
|
|
233
238
|
if not hasattr(backend, 'defaultOutput'):
|
|
234
239
|
raise IOError("Attempting to SetDevice (audio) but not supported by "
|
|
235
240
|
"the current audio library ({!r})".format(audioLib))
|
|
236
|
-
|
|
241
|
+
|
|
237
242
|
if hasattr(dev, 'name'):
|
|
238
243
|
dev = dev['name']
|
|
239
244
|
|
|
@@ -247,23 +252,28 @@ def setDevice(dev, kind=None):
|
|
|
247
252
|
if systemtools.isVM_CI(): # no audio device on CI, ignore
|
|
248
253
|
return
|
|
249
254
|
else:
|
|
250
|
-
raise TypeError("`kind` should be one of [None, 'output', 'input']"
|
|
255
|
+
raise TypeError("`kind` should be one of [None, 'output', 'input'] "
|
|
251
256
|
"not {!r}".format(kind))
|
|
252
257
|
|
|
253
258
|
|
|
254
259
|
# Set the device according to user prefs (if current lib allows it)
|
|
255
260
|
deviceNames = []
|
|
256
|
-
if
|
|
261
|
+
if backend is None:
|
|
262
|
+
raise ImportError("None of the audio library backends could be imported. "
|
|
263
|
+
"Tried: {}\n Check whether the necessary audioLibs are "
|
|
264
|
+
"installed and can be imported successfully."
|
|
265
|
+
.format(prefs.hardware['audioLib']))
|
|
266
|
+
elif hasattr(backend, 'defaultOutput'):
|
|
257
267
|
pref = prefs.hardware['audioDevice']
|
|
258
268
|
# is it a list or a simple string?
|
|
259
|
-
if
|
|
269
|
+
if isinstance(pref, list):
|
|
260
270
|
# multiple options so use zeroth
|
|
261
|
-
dev =
|
|
271
|
+
dev = pref[0]
|
|
262
272
|
else:
|
|
263
273
|
# a single option
|
|
264
|
-
dev =
|
|
274
|
+
dev = pref
|
|
265
275
|
# is it simply "default" (do nothing)
|
|
266
|
-
if dev=='default' or systemtools.isVM_CI():
|
|
276
|
+
if dev == 'default' or systemtools.isVM_CI():
|
|
267
277
|
pass # do nothing
|
|
268
278
|
elif dev not in backend.getDevices(kind='output'):
|
|
269
279
|
deviceNames = sorted(backend.getDevices(kind='output').keys())
|