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.

Files changed (331) hide show
  1. psychopy/.DS_Store +0 -0
  2. psychopy/CHANGELOG.txt +206 -0
  3. psychopy/GIT_SHA +1 -0
  4. psychopy/VERSION +1 -0
  5. psychopy/__init__.py +77 -15
  6. psychopy/app/Resources/classic/plugin16.png +0 -0
  7. psychopy/app/Resources/classic/plugin16@2x.png +0 -0
  8. psychopy/app/Resources/dark/plugin16.png +0 -0
  9. psychopy/app/Resources/dark/plugin16@2x.png +0 -0
  10. psychopy/app/Resources/light/plugin16.png +0 -0
  11. psychopy/app/Resources/light/plugin16@2x.png +0 -0
  12. psychopy/app/__init__.py +76 -2
  13. psychopy/app/_psychopyApp.py +126 -101
  14. psychopy/app/builder/builder.py +14 -10
  15. psychopy/app/builder/dialogs/__init__.py +8 -8
  16. psychopy/app/builder/dialogs/dlgsConditions.py +12 -13
  17. psychopy/app/builder/dialogs/paramCtrls.py +24 -57
  18. psychopy/app/builder/localizedStrings.py +11 -9
  19. psychopy/app/builder/validators.py +2 -2
  20. psychopy/app/coder/codeEditorBase.py +8 -8
  21. psychopy/app/coder/coder.py +4 -4
  22. psychopy/app/connections/sendusage.py +2 -2
  23. psychopy/app/connections/updates.py +9 -9
  24. psychopy/app/dialogs.py +34 -2
  25. psychopy/app/idle.py +31 -0
  26. psychopy/app/jobs.py +21 -3
  27. psychopy/app/linuxconfig/__init__.py +9 -0
  28. psychopy/app/locale/ar_001/LC_MESSAGE/messages.mo +0 -0
  29. psychopy/app/locale/ar_001/LC_MESSAGE/messages.po +4602 -2540
  30. psychopy/app/locale/es_CO/LC_MESSAGE/messages.mo +0 -0
  31. psychopy/app/locale/es_CO/LC_MESSAGE/messages.po +56 -54
  32. psychopy/app/locale/es_ES/LC_MESSAGE/messages.po +53 -43
  33. psychopy/app/locale/es_US/LC_MESSAGE/messages.mo +0 -0
  34. psychopy/app/locale/es_US/LC_MESSAGE/messages.po +56 -54
  35. psychopy/app/locale/ja_JP/LC_MESSAGE/messages.mo +0 -0
  36. psychopy/app/locale/ja_JP/LC_MESSAGE/messages.po +1258 -1176
  37. psychopy/app/locale/pt_PT/LC_MESSAGE/messages.po +9415 -5
  38. psychopy/app/pavlovia_ui/_base.py +33 -3
  39. psychopy/app/pavlovia_ui/search.py +0 -1
  40. psychopy/app/plugin_manager/dialog.py +104 -51
  41. psychopy/app/plugin_manager/packages.py +5 -0
  42. psychopy/app/plugin_manager/plugins.py +152 -67
  43. psychopy/app/preferencesDlg.py +8 -8
  44. psychopy/app/psychopyApp.py +11 -5
  45. psychopy/app/ribbon.py +124 -14
  46. psychopy/app/runner/runner.py +6 -1
  47. psychopy/app/stdout/stdOutRich.py +27 -11
  48. psychopy/app/themes/icons.py +52 -2
  49. psychopy/assets/__init__.py +0 -0
  50. psychopy/assets/click.png +0 -0
  51. psychopy/assets/clicknext.png +0 -0
  52. psychopy/assets/next.png +0 -0
  53. psychopy/assets/psychopy.ico +0 -0
  54. psychopy/assets/psychopy.png +0 -0
  55. psychopy/assets/templates/__init__.py +0 -0
  56. psychopy/assets/touch.png +0 -0
  57. psychopy/assets/touchnext.png +0 -0
  58. psychopy/assets/window.ico +0 -0
  59. psychopy/changes/2023.1.0.md +9 -0
  60. psychopy/changes/2024.1.0.md +16 -0
  61. psychopy/changes/__init__.py +0 -0
  62. psychopy/clock.py +2 -2
  63. psychopy/colors.py +2 -1
  64. psychopy/compatibility.py +53 -1
  65. psychopy/contrib/.DS_Store +0 -0
  66. psychopy/contrib/configobj/__init__.py +10 -8
  67. psychopy/data/__init__.py +3 -2
  68. psychopy/data/base.py +5 -5
  69. psychopy/data/experiment.py +130 -4
  70. psychopy/data/routine.py +56 -0
  71. psychopy/data/staircase.py +2 -2
  72. psychopy/data/trial.py +559 -97
  73. psychopy/data/utils.py +56 -21
  74. psychopy/demos/.DS_Store +0 -0
  75. psychopy/demos/builder/.DS_Store +0 -0
  76. psychopy/demos/builder/Design Templates/.DS_Store +0 -0
  77. psychopy/demos/builder/Experiments/.DS_Store +0 -0
  78. psychopy/demos/builder/Feature Demos/.DS_Store +0 -0
  79. psychopy/demos/builder/Feature Demos/buttonBox/buttonBoxDemo.psyexp +375 -0
  80. psychopy/demos/builder/Feature Demos/buttonBox/readme.md +5 -0
  81. psychopy/demos/builder/Feature Demos/pilotMode/pilotMode.psyexp +433 -0
  82. psychopy/demos/builder/Feature Demos/pilotMode/readme.md +7 -0
  83. psychopy/demos/builder/Feature Demos/progress/progressBar.psyexp +4 -4
  84. psychopy/demos/builder/Hardware/.DS_Store +0 -0
  85. psychopy/demos/builder/Helper Tools/.DS_Store +0 -0
  86. psychopy/demos/coder/.DS_Store +0 -0
  87. psychopy/demos/coder/hardware/testSoundLatency.py +2 -2
  88. psychopy/demos/coder/iohub/.DS_Store +0 -0
  89. psychopy/demos/coder/misc/hdf5_2_csv +33 -0
  90. psychopy/event.py +30 -29
  91. psychopy/experiment/.DS_Store +0 -0
  92. psychopy/experiment/_experiment.py +6 -6
  93. psychopy/experiment/components/.DS_Store +0 -0
  94. psychopy/experiment/components/__init__.py +6 -3
  95. psychopy/experiment/components/_base.py +286 -131
  96. psychopy/experiment/components/aperture/.DS_Store +0 -0
  97. psychopy/experiment/components/brush/.DS_Store +0 -0
  98. psychopy/experiment/components/button/.DS_Store +0 -0
  99. psychopy/experiment/components/button/__init__.py +5 -1
  100. psychopy/experiment/components/buttonBox/.DS_Store +0 -0
  101. psychopy/experiment/components/buttonBox/__init__.py +21 -12
  102. psychopy/experiment/components/camera/.DS_Store +0 -0
  103. psychopy/experiment/components/code/.DS_Store +0 -0
  104. psychopy/experiment/components/dots/.DS_Store +0 -0
  105. psychopy/experiment/components/eyetracker_record/.DS_Store +0 -0
  106. psychopy/experiment/components/eyetracker_record/__init__.py +92 -30
  107. psychopy/experiment/components/form/.DS_Store +0 -0
  108. psychopy/experiment/components/form/__init__.py +6 -2
  109. psychopy/experiment/components/grating/.DS_Store +0 -0
  110. psychopy/experiment/components/grating/__init__.py +14 -3
  111. psychopy/experiment/components/image/.DS_Store +0 -0
  112. psychopy/experiment/components/image/__init__.py +14 -3
  113. psychopy/experiment/components/joyButtons/.DS_Store +0 -0
  114. psychopy/experiment/components/joystick/.DS_Store +0 -0
  115. psychopy/experiment/components/keyboard/.DS_Store +0 -0
  116. psychopy/experiment/components/keyboard/__init__.py +22 -10
  117. psychopy/experiment/components/microphone/.DS_Store +0 -0
  118. psychopy/experiment/components/microphone/__init__.py +59 -39
  119. psychopy/experiment/components/mouse/.DS_Store +0 -0
  120. psychopy/experiment/components/mouse/__init__.py +44 -29
  121. psychopy/experiment/components/movie/.DS_Store +0 -0
  122. psychopy/experiment/components/movie/__init__.py +1 -1
  123. psychopy/experiment/components/panorama/.DS_Store +0 -0
  124. psychopy/experiment/components/parallelOut/.DS_Store +0 -0
  125. psychopy/experiment/components/patch/.DS_Store +0 -0
  126. psychopy/experiment/components/polygon/.DS_Store +0 -0
  127. psychopy/experiment/components/polygon/__init__.py +26 -6
  128. psychopy/experiment/components/progress/.DS_Store +0 -0
  129. psychopy/experiment/components/progress/__init__.py +1 -1
  130. psychopy/experiment/components/ratingScale/.DS_Store +0 -0
  131. psychopy/experiment/components/resourceManager/.DS_Store +0 -0
  132. psychopy/experiment/components/roi/.DS_Store +0 -0
  133. psychopy/experiment/components/roi/__init__.py +5 -0
  134. psychopy/experiment/components/routineSettings/.DS_Store +0 -0
  135. psychopy/experiment/components/routineSettings/__init__.py +57 -10
  136. psychopy/experiment/components/serialOut/.DS_Store +0 -0
  137. psychopy/experiment/components/settings/.DS_Store +0 -0
  138. psychopy/experiment/components/settings/__init__.py +117 -42
  139. psychopy/experiment/components/slider/.DS_Store +0 -0
  140. psychopy/experiment/components/sound/.DS_Store +0 -0
  141. psychopy/experiment/components/sound/__init__.py +54 -19
  142. psychopy/experiment/components/static/.DS_Store +0 -0
  143. psychopy/experiment/components/static/__init__.py +1 -1
  144. psychopy/experiment/components/text/.DS_Store +0 -0
  145. psychopy/experiment/components/text/__init__.py +28 -3
  146. psychopy/experiment/components/textbox/.DS_Store +0 -0
  147. psychopy/experiment/components/textbox/__init__.py +12 -2
  148. psychopy/experiment/components/unknown/.DS_Store +0 -0
  149. psychopy/experiment/components/unknown/__init__.py +1 -2
  150. psychopy/experiment/components/unknownPlugin/.DS_Store +0 -0
  151. psychopy/experiment/components/unknownPlugin/__init__.py +2 -2
  152. psychopy/experiment/components/variable/.DS_Store +0 -0
  153. psychopy/experiment/flow.py +11 -4
  154. psychopy/experiment/loops.py +85 -37
  155. psychopy/experiment/params.py +74 -32
  156. psychopy/experiment/py2js_transpiler.py +8 -1
  157. psychopy/experiment/routines/.DS_Store +0 -0
  158. psychopy/experiment/routines/_base.py +102 -22
  159. psychopy/experiment/routines/counterbalance/.DS_Store +0 -0
  160. psychopy/experiment/routines/counterbalance/__init__.py +5 -1
  161. psychopy/experiment/routines/eyetracker_calibrate/.DS_Store +0 -0
  162. psychopy/experiment/routines/eyetracker_validate/.DS_Store +0 -0
  163. psychopy/experiment/routines/pavlovia_survey/.DS_Store +0 -0
  164. psychopy/experiment/routines/photodiodeValidator/.DS_Store +0 -0
  165. psychopy/experiment/routines/photodiodeValidator/__init__.py +7 -6
  166. psychopy/experiment/routines/unknown/.DS_Store +0 -0
  167. psychopy/gui/wxgui.py +4 -4
  168. psychopy/hardware/.DS_Store +0 -0
  169. psychopy/hardware/__init__.py +1 -1
  170. psychopy/hardware/base.py +12 -0
  171. psychopy/hardware/camera/__init__.py +1 -15
  172. psychopy/hardware/cedrus.py +10 -11
  173. psychopy/hardware/crs/colorcal.py +13 -22
  174. psychopy/hardware/crs/optical.py +10 -20
  175. psychopy/hardware/emulator.py +17 -14
  176. psychopy/hardware/eyetracker.py +42 -118
  177. psychopy/hardware/gammasci.py +4 -15
  178. psychopy/hardware/keyboard.py +102 -11
  179. psychopy/hardware/listener.py +3 -0
  180. psychopy/hardware/microphone.py +148 -18
  181. psychopy/hardware/minolta.py +8 -15
  182. psychopy/hardware/photodiode.py +191 -16
  183. psychopy/hardware/photometer/__init__.py +11 -19
  184. psychopy/hardware/pr.py +8 -15
  185. psychopy/hardware/speaker.py +39 -4
  186. psychopy/info.py +0 -71
  187. psychopy/iohub/.DS_Store +0 -0
  188. psychopy/iohub/__init__.py +1 -1
  189. psychopy/iohub/client/__init__.py +30 -20
  190. psychopy/iohub/client/keyboard.py +24 -24
  191. psychopy/iohub/datastore/__init__.py +2 -2
  192. psychopy/iohub/datastore/util.py +2 -2
  193. psychopy/iohub/default_config.yaml +1 -1
  194. psychopy/iohub/devices/.DS_Store +0 -0
  195. psychopy/iohub/devices/__init__.py +112 -25
  196. psychopy/iohub/devices/deviceConfigValidation.py +2 -1
  197. psychopy/iohub/devices/experiment/default_experiment.yaml +12 -1
  198. psychopy/iohub/devices/experiment/supported_config_settings.yaml +5 -1
  199. psychopy/iohub/devices/eyetracker/.DS_Store +0 -0
  200. psychopy/iohub/devices/eyetracker/__init__.py +46 -0
  201. psychopy/iohub/devices/eyetracker/calibration/procedure.py +2 -2
  202. psychopy/iohub/devices/eyetracker/hw/gazepoint/__init__.py +14 -2
  203. psychopy/iohub/devices/eyetracker/hw/mouse/eyetracker.py +3 -4
  204. psychopy/iohub/server.py +2 -2
  205. psychopy/iohub/start_iohub_process.py +3 -0
  206. psychopy/iohub/util/__init__.py +62 -70
  207. psychopy/layout.py +5 -5
  208. psychopy/logging.py +8 -1
  209. psychopy/microphone.py +10 -37
  210. psychopy/platform_specific/__init__.py +0 -2
  211. psychopy/platform_specific/darwin.py +1 -3
  212. psychopy/platform_specific/linux.py +31 -33
  213. psychopy/platform_specific/win32.py +38 -13
  214. psychopy/plugins/__init__.py +148 -116
  215. psychopy/plugins/util.py +39 -0
  216. psychopy/preferences/Darwin.spec +4 -2
  217. psychopy/preferences/FreeBSD.spec +4 -2
  218. psychopy/preferences/Linux.spec +4 -2
  219. psychopy/preferences/Windows.spec +4 -2
  220. psychopy/preferences/baseNoArch.spec +4 -2
  221. psychopy/preferences/preferences.py +47 -24
  222. psychopy/projects/pavlovia.py +47 -4
  223. psychopy/scripts/psyexpCompile.py +0 -4
  224. psychopy/session.py +153 -21
  225. psychopy/sound/__init__.py +31 -21
  226. psychopy/sound/_base.py +20 -3
  227. psychopy/sound/audioclip.py +320 -33
  228. psychopy/sound/backend_ptb.py +47 -58
  229. psychopy/sound/backend_pygame.py +1 -1
  230. psychopy/sound/backend_pysound.py +6 -15
  231. psychopy/sound/transcribe.py +53 -0
  232. psychopy/tests/.DS_Store +0 -0
  233. psychopy/tests/data/.DS_Store +0 -0
  234. psychopy/tests/data/TestUnknownPluginComponent_load_resave.psyexp +135 -0
  235. psychopy/tests/data/Test_textbox/test_ori_0_bottom right.png +0 -0
  236. psychopy/tests/data/Test_textbox/test_ori_0_center.png +0 -0
  237. psychopy/tests/data/Test_textbox/test_ori_0_top left.png +0 -0
  238. psychopy/tests/data/Test_textbox/test_ori_120_bottom right.png +0 -0
  239. psychopy/tests/data/Test_textbox/test_ori_120_center.png +0 -0
  240. psychopy/tests/data/Test_textbox/test_ori_120_top left.png +0 -0
  241. psychopy/tests/data/Test_textbox/test_ori_180_bottom right.png +0 -0
  242. psychopy/tests/data/Test_textbox/test_ori_180_center.png +0 -0
  243. psychopy/tests/data/Test_textbox/test_ori_180_top left.png +0 -0
  244. psychopy/tests/data/Test_textbox/test_ori_240_bottom right.png +0 -0
  245. psychopy/tests/data/Test_textbox/test_ori_240_center.png +0 -0
  246. psychopy/tests/data/Test_textbox/test_ori_240_top left.png +0 -0
  247. psychopy/tests/data/correctScript/.DS_Store +0 -0
  248. psychopy/tests/data/test_components/testClearKeyboard/testClearKeyboard.psyexp +200 -0
  249. psychopy/tests/data/test_session/.DS_Store +0 -0
  250. psychopy/tests/data/test_session/root/testFutureTrials/testFutureTrials.psyexp +155 -0
  251. psychopy/tests/data/test_session/root/testTrialNav/trialNav.psyexp +158 -0
  252. psychopy/tests/test_app/.DS_Store +0 -0
  253. psychopy/tests/test_app/conftest.py +2 -2
  254. psychopy/tests/test_app/test_speed.py +4 -1
  255. psychopy/tests/test_data/test_TrialHandler2.py +146 -1
  256. psychopy/tests/test_experiment/.DS_Store +0 -0
  257. psychopy/tests/test_experiment/needs_wx/genComponsTemplate.py +3 -3
  258. psychopy/tests/test_experiment/needs_wx/test_components.py +2 -2
  259. psychopy/tests/test_experiment/test_components/test_KeyboardComponent.py +28 -0
  260. psychopy/tests/test_experiment/test_components/test_UnknownPluginComponent.py +27 -0
  261. psychopy/tests/test_experiment/test_components/test_base_components.py +58 -0
  262. psychopy/tests/test_experiment/test_py2js.py +1 -1
  263. psychopy/tests/test_hardware/test_keyboard.py +184 -16
  264. psychopy/tests/test_hardware/test_ports.py +1 -11
  265. psychopy/tests/test_liaison/test_Liaison.py +47 -0
  266. psychopy/tests/test_misc/test_core.py +5 -0
  267. psychopy/tests/test_session/test_Session.py +5 -1
  268. psychopy/tests/test_tools/test_versionchooser.py +39 -8
  269. psychopy/tests/test_visual/test_all_stimuli.py +0 -97
  270. psychopy/tests/test_visual/test_image.py +6 -5
  271. psychopy/tests/test_visual/test_textbox.py +36 -0
  272. psychopy/tests/utils.py +4 -0
  273. psychopy/tools/filetools.py +1 -1
  274. psychopy/tools/pkgtools.py +160 -137
  275. psychopy/tools/versionchooser.py +10 -10
  276. psychopy/tools/wizard.py +3 -3
  277. psychopy/visual/.DS_Store +0 -0
  278. psychopy/visual/backends/pygletbackend.py +24 -13
  279. psychopy/visual/basevisual.py +5 -11
  280. psychopy/visual/button.py +2 -14
  281. psychopy/visual/helpers.py +5 -5
  282. psychopy/visual/line.py +1 -2
  283. psychopy/visual/movie2.py +7 -816
  284. psychopy/visual/movie3.py +7 -589
  285. psychopy/visual/movies/__init__.py +8 -11
  286. psychopy/visual/movies/frame.py +5 -2
  287. psychopy/visual/movies/players/ffpyplayer_player.py +5 -2
  288. psychopy/visual/noise.py +8 -7
  289. psychopy/visual/patch.py +7 -16
  290. psychopy/visual/progress.py +1 -1
  291. psychopy/visual/radial.py +9 -7
  292. psychopy/visual/ratingscale.py +8 -1415
  293. psychopy/visual/secondorder.py +10 -9
  294. psychopy/visual/shape.py +7 -2
  295. psychopy/visual/text.py +1 -1
  296. psychopy/visual/textbox2/textbox2.py +28 -5
  297. psychopy/web.py +5 -2
  298. {psychopy-2024.1.3.dist-info → psychopy-2024.2.0.dist-info}/METADATA +8 -13
  299. {psychopy-2024.1.3.dist-info → psychopy-2024.2.0.dist-info}/RECORD +313 -219
  300. {psychopy-2024.1.3.dist-info → psychopy-2024.2.0.dist-info}/WHEEL +1 -1
  301. psychopy/app/Resources/click.png +0 -0
  302. psychopy/app/Resources/next.png +0 -0
  303. psychopy/experiment/components/patch/__init__.py +0 -121
  304. psychopy/experiment/components/patch/classic/patch.png +0 -0
  305. psychopy/experiment/components/patch/dark/patch.png +0 -0
  306. psychopy/experiment/components/patch/dark/patch@2x.png +0 -0
  307. psychopy/experiment/components/patch/light/patch.png +0 -0
  308. psychopy/experiment/components/patch/light/patch@2x.png +0 -0
  309. psychopy/experiment/components/ratingScale/__init__.py +0 -337
  310. psychopy/experiment/components/ratingScale/classic/ratingscale.png +0 -0
  311. psychopy/experiment/components/ratingScale/classic/ratingscale@2x.png +0 -0
  312. psychopy/experiment/components/ratingScale/dark/ratingScale@2x.png +0 -0
  313. psychopy/experiment/components/ratingScale/dark/ratingscale.png +0 -0
  314. psychopy/experiment/components/ratingScale/light/ratingScale@2x.png +0 -0
  315. psychopy/experiment/components/ratingScale/light/ratingscale.png +0 -0
  316. psychopy/platform_specific/posix.py +0 -16
  317. psychopy/tests/test_sound/test_microphone.py +0 -217
  318. psychopy/tests/test_visual/test_ratingScale.py +0 -299
  319. /psychopy/{app/Resources → assets}/Psychopy Window Favicon@16w.png +0 -0
  320. /psychopy/{app/Resources → assets}/Psychopy Window Favicon@32w.png +0 -0
  321. /psychopy/{app/Resources → assets}/USB-C.png +0 -0
  322. /psychopy/{app/Resources → assets}/USB.png +0 -0
  323. /psychopy/{app/Resources → assets}/creditCard.png +0 -0
  324. /psychopy/{app/Resources → assets}/default.mp3 +0 -0
  325. /psychopy/{app/Resources → assets}/default.mp4 +0 -0
  326. /psychopy/{app/Resources → assets}/default.png +0 -0
  327. /psychopy/{app/Resources → assets/templates}/instruct1.png +0 -0
  328. /psychopy/{app/Resources → assets/templates}/instruct2.png +0 -0
  329. {psychopy-2024.1.3.dist-info → psychopy-2024.2.0.dist-info}/entry_points.txt +0 -0
  330. {psychopy-2024.1.3.dist-info → psychopy-2024.2.0.dist-info}/licenses/AUTHORS.md +0 -0
  331. {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='all')
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 files when piloting ('error' is fewest messages, 'debug' is most)
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='all')
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 files when piloting ('error' is fewest messages, 'debug' is most)
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 pkg_resources import parse_version
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
- parse_version(configobj.__version__) < parse_version('5.1.0')):
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
- pyVer = 'Python{}{}'.format(
170
- sys.version_info.major, sys.version_info.minor)
171
- prefixRootDir = Path(
172
- self.paths['userPrefsDir']) / 'packages' / pyVer
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 prefixRootDir.is_dir():
176
- prefixRootDir.mkdir()
177
-
178
- userSiteDir = prefixRootDir / 'site-packages'
179
- if not userSiteDir.is_dir():
180
- userSiteDir.mkdir()
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'] = userSiteDir
189
- self.paths['userScripts'] = userScriptsDir
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 = parse_version(f.read())
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 = parse_version("2020.2.0")
223
+ lastVer = Version("2020.2.0")
201
224
  # If version has changed since base themes last copied, they need updating
202
- updateThemes = lastVer < parse_version(__version__)
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():
@@ -17,7 +17,7 @@ import subprocess
17
17
  import traceback
18
18
 
19
19
  import pandas
20
- from pkg_resources import parse_version
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 = '4bb79f0356a566cd7b49e3130c714d9140f1d3de4ff27c7583fb34fbfac604e0'
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=token&state={}'
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__(self,
242
- root,
243
- dataDir=None,
244
- clock="iso",
245
- win=None,
246
- experiments=None,
247
- loggingLevel="info",
248
- priorityThreshold=constants.priority.EXCLUDE+1,
249
- params=None,
250
- liaison=None):
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(_translate(
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(_translate(
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(_translate(
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
- # def recycleTrial(self, thisExp, trial):
1141
- # pass
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
  """
@@ -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', 'pygame', 'pyo']
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 perfered, provides the best performance
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 backend_pysoundcard as backend
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
- .format(_audioLibs, thisLibName))
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 hasattr(backend, 'defaultOutput'):
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 type(prefs.hardware['audioDevice'])==list:
269
+ if isinstance(pref, list):
260
270
  # multiple options so use zeroth
261
- dev = prefs.hardware['audioDevice'][0]
271
+ dev = pref[0]
262
272
  else:
263
273
  # a single option
264
- dev = prefs.hardware['audioDevice']
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())