psychopy 2024.2.5__py3-none-any.whl → 2025.1.1__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 (863) hide show
  1. psychopy/CHANGELOG.txt +4 -4
  2. psychopy/GIT_SHA +1 -1
  3. psychopy/LICENSE.txt +1 -1
  4. psychopy/VERSION +1 -1
  5. psychopy/__init__.py +10 -7
  6. psychopy/alerts/__init__.py +1 -1
  7. psychopy/alerts/_alerts.py +53 -17
  8. psychopy/alerts/_errorHandler.py +3 -4
  9. psychopy/alerts/alertsCatalogue/3210.yaml +27 -0
  10. psychopy/alerts/alertsCatalogue/3610.yaml +19 -0
  11. psychopy/alerts/alertsCatalogue/4130.yaml +19 -0
  12. psychopy/alerts/alertsCatalogue/alertCategories.yaml +8 -1
  13. psychopy/alerts/alertsCatalogue/alertmsg.py +1 -1
  14. psychopy/alerts/alerttools.py +0 -16
  15. psychopy/app/Resources/betasplash.png +0 -0
  16. psychopy/app/Resources/betasplash@2x.png +0 -0
  17. psychopy/app/Resources/classic/case.png +0 -0
  18. psychopy/app/Resources/classic/case@2x.png +0 -0
  19. psychopy/app/Resources/classic/fileaudio.png +0 -0
  20. psychopy/app/Resources/classic/fileaudio@2x.png +0 -0
  21. psychopy/app/Resources/classic/filecss.png +0 -0
  22. psychopy/app/Resources/classic/filecss@2x.png +0 -0
  23. psychopy/app/Resources/classic/filecsv.png +0 -0
  24. psychopy/app/Resources/classic/filecsv@2x.png +0 -0
  25. psychopy/app/Resources/classic/filedesign.png +0 -0
  26. psychopy/app/Resources/classic/filedesign@2x.png +0 -0
  27. psychopy/app/Resources/classic/filefont.png +0 -0
  28. psychopy/app/Resources/classic/filefont@2x.png +0 -0
  29. psychopy/app/Resources/classic/filegit.png +0 -0
  30. psychopy/app/Resources/classic/filegit@2x.png +0 -0
  31. psychopy/app/Resources/classic/filehtml.png +0 -0
  32. psychopy/app/Resources/classic/filehtml@2x.png +0 -0
  33. psychopy/app/Resources/classic/fileimage.png +0 -0
  34. psychopy/app/Resources/classic/fileimage@2x.png +0 -0
  35. psychopy/app/Resources/classic/fileinfo.png +0 -0
  36. psychopy/app/Resources/classic/fileinfo@2x.png +0 -0
  37. psychopy/app/Resources/classic/filejs.png +0 -0
  38. psychopy/app/Resources/classic/filejs@2x.png +0 -0
  39. psychopy/app/Resources/classic/filejson.png +0 -0
  40. psychopy/app/Resources/classic/filejson@2x.png +0 -0
  41. psychopy/app/Resources/classic/filepkg.png +0 -0
  42. psychopy/app/Resources/classic/filepkg@2x.png +0 -0
  43. psychopy/app/Resources/classic/filepsyexp.png +0 -0
  44. psychopy/app/Resources/classic/filepsyexp@2x.png +0 -0
  45. psychopy/app/Resources/classic/filepy.png +0 -0
  46. psychopy/app/Resources/classic/filepy@2x.png +0 -0
  47. psychopy/app/Resources/classic/filetxt.png +0 -0
  48. psychopy/app/Resources/classic/filetxt@2x.png +0 -0
  49. psychopy/app/Resources/classic/fileunknown.png +0 -0
  50. psychopy/app/Resources/classic/fileunknown@2x.png +0 -0
  51. psychopy/app/Resources/classic/filevideo.png +0 -0
  52. psychopy/app/Resources/classic/filevideo@2x.png +0 -0
  53. psychopy/app/Resources/classic/find.png +0 -0
  54. psychopy/app/Resources/classic/find@2x.png +0 -0
  55. psychopy/app/Resources/classic/loop.png +0 -0
  56. psychopy/app/Resources/classic/loop@2x.png +0 -0
  57. psychopy/app/Resources/classic/regex.png +0 -0
  58. psychopy/app/Resources/classic/regex@2x.png +0 -0
  59. psychopy/app/Resources/dark/case.png +0 -0
  60. psychopy/app/Resources/dark/case@2x.png +0 -0
  61. psychopy/app/Resources/dark/fileaudio.png +0 -0
  62. psychopy/app/Resources/dark/fileaudio@2x.png +0 -0
  63. psychopy/app/Resources/dark/filecss.png +0 -0
  64. psychopy/app/Resources/dark/filecss@2x.png +0 -0
  65. psychopy/app/Resources/dark/filecsv.png +0 -0
  66. psychopy/app/Resources/dark/filecsv@2x.png +0 -0
  67. psychopy/app/Resources/dark/filedesign.png +0 -0
  68. psychopy/app/Resources/dark/filedesign@2x.png +0 -0
  69. psychopy/app/Resources/dark/filefont.png +0 -0
  70. psychopy/app/Resources/dark/filefont@2x.png +0 -0
  71. psychopy/app/Resources/dark/filegit.png +0 -0
  72. psychopy/app/Resources/dark/filegit@2x.png +0 -0
  73. psychopy/app/Resources/dark/filehtml.png +0 -0
  74. psychopy/app/Resources/dark/filehtml@2x.png +0 -0
  75. psychopy/app/Resources/dark/fileimage.png +0 -0
  76. psychopy/app/Resources/dark/fileimage@2x.png +0 -0
  77. psychopy/app/Resources/dark/fileinfo.png +0 -0
  78. psychopy/app/Resources/dark/fileinfo@2x.png +0 -0
  79. psychopy/app/Resources/dark/filejs.png +0 -0
  80. psychopy/app/Resources/dark/filejs@2x.png +0 -0
  81. psychopy/app/Resources/dark/filejson.png +0 -0
  82. psychopy/app/Resources/dark/filejson@2x.png +0 -0
  83. psychopy/app/Resources/dark/filepkg.png +0 -0
  84. psychopy/app/Resources/dark/filepkg@2x.png +0 -0
  85. psychopy/app/Resources/dark/filepsyexp.png +0 -0
  86. psychopy/app/Resources/dark/filepsyexp@2x.png +0 -0
  87. psychopy/app/Resources/dark/filepy.png +0 -0
  88. psychopy/app/Resources/dark/filepy@2x.png +0 -0
  89. psychopy/app/Resources/dark/filetxt.png +0 -0
  90. psychopy/app/Resources/dark/filetxt@2x.png +0 -0
  91. psychopy/app/Resources/dark/fileunknown.png +0 -0
  92. psychopy/app/Resources/dark/fileunknown@2x.png +0 -0
  93. psychopy/app/Resources/dark/filevideo.png +0 -0
  94. psychopy/app/Resources/dark/filevideo@2x.png +0 -0
  95. psychopy/app/Resources/dark/find.png +0 -0
  96. psychopy/app/Resources/dark/find@2x.png +0 -0
  97. psychopy/app/Resources/dark/loop.png +0 -0
  98. psychopy/app/Resources/dark/loop@2x.png +0 -0
  99. psychopy/app/Resources/dark/regex.png +0 -0
  100. psychopy/app/Resources/dark/regex@2x.png +0 -0
  101. psychopy/app/Resources/light/case.png +0 -0
  102. psychopy/app/Resources/light/case@2x.png +0 -0
  103. psychopy/app/Resources/light/fileaudio.png +0 -0
  104. psychopy/app/Resources/light/fileaudio@2x.png +0 -0
  105. psychopy/app/Resources/light/filecss.png +0 -0
  106. psychopy/app/Resources/light/filecss@2x.png +0 -0
  107. psychopy/app/Resources/light/filecsv.png +0 -0
  108. psychopy/app/Resources/light/filecsv@2x.png +0 -0
  109. psychopy/app/Resources/light/filedesign.png +0 -0
  110. psychopy/app/Resources/light/filedesign@2x.png +0 -0
  111. psychopy/app/Resources/light/filefont.png +0 -0
  112. psychopy/app/Resources/light/filefont@2x.png +0 -0
  113. psychopy/app/Resources/light/filegit.png +0 -0
  114. psychopy/app/Resources/light/filegit@2x.png +0 -0
  115. psychopy/app/Resources/light/filehtml.png +0 -0
  116. psychopy/app/Resources/light/filehtml@2x.png +0 -0
  117. psychopy/app/Resources/light/fileimage.png +0 -0
  118. psychopy/app/Resources/light/fileimage@2x.png +0 -0
  119. psychopy/app/Resources/light/fileinfo.png +0 -0
  120. psychopy/app/Resources/light/fileinfo@2x.png +0 -0
  121. psychopy/app/Resources/light/filejs.png +0 -0
  122. psychopy/app/Resources/light/filejs@2x.png +0 -0
  123. psychopy/app/Resources/light/filejson.png +0 -0
  124. psychopy/app/Resources/light/filejson@2x.png +0 -0
  125. psychopy/app/Resources/light/filepkg.png +0 -0
  126. psychopy/app/Resources/light/filepkg@2x.png +0 -0
  127. psychopy/app/Resources/light/filepsyexp.png +0 -0
  128. psychopy/app/Resources/light/filepsyexp@2x.png +0 -0
  129. psychopy/app/Resources/light/filepy.png +0 -0
  130. psychopy/app/Resources/light/filepy@2x.png +0 -0
  131. psychopy/app/Resources/light/filetxt.png +0 -0
  132. psychopy/app/Resources/light/filetxt@2x.png +0 -0
  133. psychopy/app/Resources/light/fileunknown.png +0 -0
  134. psychopy/app/Resources/light/fileunknown@2x.png +0 -0
  135. psychopy/app/Resources/light/filevideo.png +0 -0
  136. psychopy/app/Resources/light/filevideo@2x.png +0 -0
  137. psychopy/app/Resources/light/find.png +0 -0
  138. psychopy/app/Resources/light/find@2x.png +0 -0
  139. psychopy/app/Resources/light/loop.png +0 -0
  140. psychopy/app/Resources/light/loop@2x.png +0 -0
  141. psychopy/app/Resources/light/regex.png +0 -0
  142. psychopy/app/Resources/light/regex@2x.png +0 -0
  143. psychopy/app/Resources/routine_templates/Basic.psyexp +0 -1
  144. psychopy/app/Resources/routine_templates/Misc.psyexp +0 -1
  145. psychopy/app/Resources/routine_templates/Online.psyexp +0 -2
  146. psychopy/app/Resources/routine_templates/Trials.psyexp +0 -1
  147. psychopy/app/Resources/splash.png +0 -0
  148. psychopy/app/Resources/splash@2x.png +0 -0
  149. psychopy/app/__init__.py +49 -8
  150. psychopy/app/__main__.py +3 -0
  151. psychopy/app/_psychopyApp.py +134 -125
  152. psychopy/app/builder/builder.py +42 -22
  153. psychopy/app/builder/dialogs/__init__.py +44 -11
  154. psychopy/app/builder/dialogs/dlgsCode.py +1 -1
  155. psychopy/app/builder/dialogs/dlgsConditions.py +1 -1
  156. psychopy/app/builder/dialogs/findDlg.py +106 -20
  157. psychopy/app/builder/dialogs/paramCtrls.py +42 -1
  158. psychopy/app/builder/validators.py +1 -1
  159. psychopy/app/coder/codeEditorBase.py +8 -8
  160. psychopy/app/coder/coder.py +32 -29
  161. psychopy/app/coder/fileBrowser.py +68 -22
  162. psychopy/app/coder/folding.py +1 -1
  163. psychopy/app/coder/psychoParser.py +1 -1
  164. psychopy/app/coder/repl.py +1 -1
  165. psychopy/app/coder/sourceTree.py +1 -1
  166. psychopy/app/connections/__init__.py +1 -1
  167. psychopy/app/connections/news.py +1 -1
  168. psychopy/app/connections/sendusage.py +1 -1
  169. psychopy/app/connections/updates.py +1 -1
  170. psychopy/app/console.py +1 -1
  171. psychopy/app/errorDlg.py +1 -1
  172. psychopy/app/frametracker.py +1 -1
  173. psychopy/app/idle.py +1 -1
  174. psychopy/app/jobs.py +5 -2
  175. psychopy/app/linuxconfig/__init__.py +1 -1
  176. psychopy/app/locale/ar_001/LC_MESSAGE/messages.mo +0 -0
  177. psychopy/app/locale/ar_001/LC_MESSAGE/messages.po +4 -4
  178. psychopy/app/locale/cs_CZ/LC_MESSAGE/messages.mo +0 -0
  179. psychopy/app/locale/da_DK/LC_MESSAGE/messages.mo +0 -0
  180. psychopy/app/locale/de_DE/LC_MESSAGE/messages.mo +0 -0
  181. psychopy/app/locale/de_DE/LC_MESSAGE/messages.po +3 -3
  182. psychopy/app/locale/el_GR/LC_MESSAGE/messages.mo +0 -0
  183. psychopy/app/locale/en_NZ/LC_MESSAGE/messages.mo +0 -0
  184. psychopy/app/locale/en_US/LC_MESSAGE/messages.mo +0 -0
  185. psychopy/app/locale/es_CO/LC_MESSAGE/messages.mo +0 -0
  186. psychopy/app/locale/es_CO/LC_MESSAGE/messages.po +4 -4
  187. psychopy/app/locale/es_ES/LC_MESSAGE/messages.mo +0 -0
  188. psychopy/app/locale/es_ES/LC_MESSAGE/messages.po +4 -4
  189. psychopy/app/locale/es_US/LC_MESSAGE/messages.mo +0 -0
  190. psychopy/app/locale/es_US/LC_MESSAGE/messages.po +4 -4
  191. psychopy/app/locale/et_EE/LC_MESSAGE/messages.mo +0 -0
  192. psychopy/app/locale/et_EE/LC_MESSAGE/messages.po +2 -2
  193. psychopy/app/locale/fa_IR/LC_MESSAGE/messages.mo +0 -0
  194. psychopy/app/locale/fa_IR/LC_MESSAGE/messages.po +1 -1
  195. psychopy/app/locale/fi_FI/LC_MESSAGE/messages.mo +0 -0
  196. psychopy/app/locale/fr_FR/LC_MESSAGE/messages.mo +0 -0
  197. psychopy/app/locale/fr_FR/LC_MESSAGE/messages.po +2 -2
  198. psychopy/app/locale/he_IL/LC_MESSAGE/messages.mo +0 -0
  199. psychopy/app/locale/he_IL/LC_MESSAGE/messages.po +2 -2
  200. psychopy/app/locale/hi_IN/LC_MESSAGE/messages.mo +0 -0
  201. psychopy/app/locale/hi_IN/LC_MESSAGE/messages.po +2 -2
  202. psychopy/app/locale/hu_HU/LC_MESSAGE/messages.mo +0 -0
  203. psychopy/app/locale/it_IT/LC_MESSAGE/messages.mo +0 -0
  204. psychopy/app/locale/it_IT/LC_MESSAGE/messages.po +2 -2
  205. psychopy/app/locale/ja_JP/LC_MESSAGE/messages.mo +0 -0
  206. psychopy/app/locale/ja_JP/LC_MESSAGE/messages.po +3421 -2396
  207. psychopy/app/locale/ko_KR/LC_MESSAGE/messages.mo +0 -0
  208. psychopy/app/locale/ms_MY/LC_MESSAGE/messages.mo +0 -0
  209. psychopy/app/locale/ms_MY/LC_MESSAGE/messages.po +2 -2
  210. psychopy/app/locale/nl_NL/LC_MESSAGE/messages.mo +0 -0
  211. psychopy/app/locale/nn_NO/LC_MESSAGE/messages.mo +0 -0
  212. psychopy/app/locale/pl_PL/LC_MESSAGE/messages.mo +0 -0
  213. psychopy/app/locale/pt_PT/LC_MESSAGE/messages.mo +0 -0
  214. psychopy/app/locale/pt_PT/LC_MESSAGE/messages.po +4 -4
  215. psychopy/app/locale/ro_RO/LC_MESSAGE/messages.mo +0 -0
  216. psychopy/app/locale/ru_RU/LC_MESSAGE/messages.mo +0 -0
  217. psychopy/app/locale/sv_SE/LC_MESSAGE/messages.mo +0 -0
  218. psychopy/app/locale/sv_SE/LC_MESSAGE/messages.po +2 -2
  219. psychopy/app/locale/tr_TR/LC_MESSAGE/messages.mo +0 -0
  220. psychopy/app/locale/tr_TR/LC_MESSAGE/messages.po +2 -2
  221. psychopy/app/locale/zh_CN/LC_MESSAGE/messages.mo +0 -0
  222. psychopy/app/locale/zh_CN/LC_MESSAGE/messages.po +3 -3
  223. psychopy/app/locale/zh_TW/LC_MESSAGE/messages.mo +0 -0
  224. psychopy/app/locale/zh_TW/LC_MESSAGE/messages.po +2 -2
  225. psychopy/app/{builder/localizedStrings.py → localizedStrings.py} +173 -26
  226. psychopy/app/pavlovia_ui/__init__.py +1 -1
  227. psychopy/app/pavlovia_ui/_base.py +1 -1
  228. psychopy/app/pavlovia_ui/functions.py +1 -1
  229. psychopy/app/pavlovia_ui/menu.py +1 -1
  230. psychopy/app/pavlovia_ui/project.py +1 -1
  231. psychopy/app/pavlovia_ui/search.py +1 -1
  232. psychopy/app/pavlovia_ui/sync.py +1 -1
  233. psychopy/app/pavlovia_ui/user.py +1 -1
  234. psychopy/app/plugin_manager/dialog.py +34 -96
  235. psychopy/app/plugin_manager/packages.py +10 -14
  236. psychopy/app/plugin_manager/plugins.py +64 -4
  237. psychopy/app/preferencesDlg.py +12 -37
  238. psychopy/app/psychopyApp.py +130 -44
  239. psychopy/app/ribbon.py +1 -0
  240. psychopy/app/runner/runner.py +19 -7
  241. psychopy/app/runner/scriptProcess.py +11 -6
  242. psychopy/app/stdout/stdOutRich.py +9 -2
  243. psychopy/app/themes/fonts.py +1 -1
  244. psychopy/app/themes/icons.py +2 -38
  245. psychopy/app/ui/__init__.py +1 -1
  246. psychopy/app/utils.py +3 -3
  247. psychopy/assets/default.mp3 +0 -0
  248. psychopy/assets/fonts/NotoSans-Bold.ttf +0 -0
  249. psychopy/assets/fonts/NotoSans-BoldItalic.ttf +0 -0
  250. psychopy/assets/fonts/NotoSans-Italic.ttf +0 -0
  251. psychopy/assets/fonts/NotoSans-Regular.ttf +0 -0
  252. psychopy/assets/voicekeyThresholdStim.wav +0 -0
  253. psychopy/clock.py +4 -1
  254. psychopy/colors.py +12 -5
  255. psychopy/core.py +1 -1
  256. psychopy/data/base.py +1 -1
  257. psychopy/data/experiment.py +151 -46
  258. psychopy/data/routine.py +27 -1
  259. psychopy/data/staircase.py +2 -1
  260. psychopy/data/trial.py +62 -14
  261. psychopy/data/utils.py +2 -2
  262. psychopy/demos/builder/Design Templates/branchedExperiment/branchedExperiment.psyexp +333 -218
  263. psychopy/demos/builder/Design Templates/psychophysicsStaircase/psychophysicsStaircase.psyexp +261 -239
  264. psychopy/demos/builder/Design Templates/psychophysicsStairsInterleaved/psychophysicsStaircaseInterleaved.psyexp +319 -180
  265. psychopy/demos/builder/Design Templates/randomisedBlocks/randomisedBlocks.psyexp +204 -116
  266. psychopy/demos/builder/Experiments/BART/assets/background.jpg +0 -0
  267. psychopy/demos/builder/Experiments/BART/assets/blueBalloon.png +0 -0
  268. psychopy/demos/builder/Experiments/BART/assets/greenBalloon.png +0 -0
  269. psychopy/demos/builder/Experiments/BART/assets/redBalloon.png +0 -0
  270. psychopy/demos/builder/Experiments/BART/bart.psyexp +779 -866
  271. psychopy/demos/builder/Experiments/BigFiveInventory/BFI.psyexp +242 -180
  272. psychopy/demos/builder/Experiments/GoNoGo/gng.psyexp +419 -406
  273. psychopy/demos/builder/Experiments/dragAndDrop/README.md +2 -37
  274. psychopy/demos/builder/Experiments/dragAndDrop/drag_and_drop.psyexp +460 -1204
  275. psychopy/demos/builder/Experiments/dragAndDrop/stimuli/blank_grid.png +0 -0
  276. psychopy/demos/builder/Experiments/dragAndDrop/stimuli/make_shapes.psyexp +221 -0
  277. psychopy/demos/builder/Experiments/dragAndDrop/stimuli/readme.md +4 -0
  278. psychopy/demos/builder/Experiments/dragAndDrop/stimuli/solution_1.png +0 -0
  279. psychopy/demos/builder/Experiments/dragAndDrop/stimuli/solution_10.png +0 -0
  280. psychopy/demos/builder/Experiments/dragAndDrop/stimuli/solution_2.png +0 -0
  281. psychopy/demos/builder/Experiments/dragAndDrop/stimuli/solution_3.png +0 -0
  282. psychopy/demos/builder/Experiments/dragAndDrop/stimuli/solution_4.png +0 -0
  283. psychopy/demos/builder/Experiments/dragAndDrop/stimuli/solution_5.png +0 -0
  284. psychopy/demos/builder/Experiments/dragAndDrop/stimuli/solution_6.png +0 -0
  285. psychopy/demos/builder/Experiments/dragAndDrop/stimuli/solution_7.png +0 -0
  286. psychopy/demos/builder/Experiments/dragAndDrop/stimuli/solution_8.png +0 -0
  287. psychopy/demos/builder/Experiments/dragAndDrop/stimuli/solution_9.png +0 -0
  288. psychopy/demos/builder/Experiments/dragAndDrop/stimuli/solutions.xlsx +0 -0
  289. psychopy/demos/builder/Experiments/mentalRotation/MentalRotation.psyexp +583 -542
  290. psychopy/demos/builder/Experiments/navon/NavonTask.psyexp +458 -427
  291. psychopy/demos/builder/Experiments/sternberg/sternberg.psyexp +588 -550
  292. psychopy/demos/builder/Experiments/stroop/stroop.psyexp +303 -207
  293. psychopy/demos/builder/Experiments/stroopExtended/stroop.psyexp +390 -215
  294. psychopy/demos/builder/Experiments/stroopExtended/stroopReverse.psyexp +390 -215
  295. psychopy/demos/builder/Experiments/stroopVoice/stroopVoice.psyexp +357 -331
  296. psychopy/demos/builder/Feature Demos/buttonBox/buttonBoxDemo.psyexp +3 -2
  297. psychopy/demos/builder/Feature Demos/counterbalance/counterbalance.psyexp +287 -277
  298. psychopy/demos/builder/Feature Demos/gratings/gratings.psyexp +370 -320
  299. psychopy/demos/builder/Feature Demos/noise/noise.psyexp +452 -399
  300. psychopy/demos/builder/Feature Demos/panorama/panorama.psyexp +168 -133
  301. psychopy/demos/builder/Feature Demos/pilotMode/pilotMode.psyexp +4 -3
  302. psychopy/demos/builder/Feature Demos/progress/progressBar.psyexp +420 -392
  303. psychopy/demos/builder/Feature Demos/sliders/sliders.psyexp +917 -871
  304. psychopy/demos/builder/Feature Demos/visualValidator/readme.md +7 -0
  305. psychopy/demos/builder/Feature Demos/visualValidator/visualValidator.psyexp +200 -0
  306. psychopy/demos/builder/Hardware/EEG_parallel_component/EEG_triggers_parallel_comp.psyexp +25 -9
  307. psychopy/demos/builder/Hardware/EEG_serial_code/EEG_triggers_serial_code.psyexp +372 -361
  308. psychopy/demos/builder/Hardware/EEG_serial_component/EEG_triggers_serial_comp.psyexp +25 -9
  309. psychopy/demos/builder/Hardware/EGI_netstation/stroop.psyexp +320 -235
  310. psychopy/demos/builder/Hardware/Eyetracking_visual_search/visualSearch.psyexp +790 -651
  311. psychopy/demos/builder/Hardware/camera/camera.psyexp +326 -246
  312. psychopy/demos/builder/Hardware/eyetracking/eyetracking.psyexp +432 -327
  313. psychopy/demos/builder/Hardware/eyetracking_custom_cal/eyetracking_custom_cal.psyexp +440 -321
  314. psychopy/demos/builder/Hardware/fMRI/fMRI_demo.psyexp +190 -181
  315. psychopy/demos/builder/Hardware/lab_streaming_layer/lsl_triggers_demo.psyexp +323 -312
  316. psychopy/demos/builder/Hardware/lab_streaming_layer/lsl_triggers_demo_legacy.psyexp +360 -0
  317. psychopy/demos/builder/Hardware/lab_streaming_layer/lsl_triggers_demo_legacy_legacy.psyexp +312 -0
  318. psychopy/demos/builder/Hardware/lab_streaming_layer_legacy/lsl_triggers_demo_legacy.psyexp +329 -273
  319. psychopy/demos/builder/Hardware/lab_streaming_layer_legacy/lsl_triggers_demo_legacy_legacy.psyexp +360 -0
  320. psychopy/demos/builder/Hardware/lab_streaming_layer_legacy/lsl_triggers_demo_legacy_legacy_legacy.psyexp +312 -0
  321. psychopy/demos/builder/Hardware/microphone/microphone.psyexp +450 -404
  322. psychopy/demos/builder/Hardware/pump/pump.psyexp +593 -329
  323. psychopy/demos/builder/Helper Tools/achorVSalignment/FlowCircular-Regular.ttf +0 -0
  324. psychopy/demos/builder/Helper Tools/achorVSalignment/anchorAlignment.psyexp +336 -274
  325. psychopy/demos/builder/Helper Tools/clockFace/clockFace.psyexp +200 -149
  326. psychopy/demos/builder/Helper Tools/colors/colors.psyexp +300 -279
  327. psychopy/demos/builder/Helper Tools/drawPolygon/drawPolygon.psyexp +677 -564
  328. psychopy/demos/builder/Helper Tools/keyNameFinder/keyNameFinder.psyexp +214 -158
  329. psychopy/demos/builder/Helper Tools/spatialUnits/unitDemo.psyexp +195 -146
  330. psychopy/demos/coder/experiment control/runtimeInfo.py +1 -1
  331. psychopy/devices/__init__.py +1 -1
  332. psychopy/event.py +1 -1
  333. psychopy/exceptions.py +1 -1
  334. psychopy/experiment/__init__.py +1 -1
  335. psychopy/experiment/_experiment.py +30 -9
  336. psychopy/experiment/components/__init__.py +1 -6
  337. psychopy/experiment/components/_base.py +44 -19
  338. psychopy/experiment/components/aperture/__init__.py +1 -1
  339. psychopy/experiment/components/brush/__init__.py +2 -2
  340. psychopy/experiment/components/button/__init__.py +24 -28
  341. psychopy/experiment/components/camera/__init__.py +38 -39
  342. psychopy/experiment/components/code/__init__.py +1 -1
  343. psychopy/experiment/components/dots/__init__.py +1 -1
  344. psychopy/experiment/components/eyetracker_record/__init__.py +7 -3
  345. psychopy/experiment/components/form/__init__.py +3 -3
  346. psychopy/experiment/components/grating/__init__.py +1 -1
  347. psychopy/experiment/components/image/__init__.py +1 -1
  348. psychopy/experiment/components/joyButtons/__init__.py +2 -2
  349. psychopy/experiment/components/joyButtons/virtualJoyButtons.py +1 -1
  350. psychopy/experiment/components/joystick/__init__.py +3 -3
  351. psychopy/experiment/components/joystick/virtualJoystick.py +1 -1
  352. psychopy/experiment/components/keyboard/__init__.py +98 -122
  353. psychopy/experiment/components/microphone/__init__.py +54 -98
  354. psychopy/experiment/components/mouse/__init__.py +92 -93
  355. psychopy/experiment/components/movie/__init__.py +28 -50
  356. psychopy/experiment/components/parallelOut/__init__.py +3 -3
  357. psychopy/experiment/components/polygon/__init__.py +2 -3
  358. psychopy/experiment/components/roi/__init__.py +2 -2
  359. psychopy/experiment/components/routineSettings/__init__.py +2 -0
  360. psychopy/experiment/components/serialOut/__init__.py +7 -7
  361. psychopy/experiment/components/settings/__init__.py +317 -313
  362. psychopy/experiment/components/settings/eyetracking.py +108 -0
  363. psychopy/experiment/components/slider/__init__.py +5 -5
  364. psychopy/experiment/components/sound/__init__.py +168 -78
  365. psychopy/experiment/components/soundsensor/__init__.py +361 -0
  366. psychopy/experiment/components/soundsensor/classic/soundsensor.png +0 -0
  367. psychopy/experiment/components/soundsensor/classic/soundsensor@2x.png +0 -0
  368. psychopy/experiment/components/soundsensor/dark/soundsensor.png +0 -0
  369. psychopy/experiment/components/soundsensor/dark/soundsensor@2x.png +0 -0
  370. psychopy/experiment/components/soundsensor/light/soundsensor.png +0 -0
  371. psychopy/experiment/components/soundsensor/light/soundsensor@2x.png +0 -0
  372. psychopy/experiment/components/static/__init__.py +59 -33
  373. psychopy/experiment/components/text/__init__.py +2 -6
  374. psychopy/experiment/components/textbox/__init__.py +3 -7
  375. psychopy/experiment/components/unknown/__init__.py +2 -0
  376. psychopy/experiment/components/unknownPlugin/__init__.py +2 -0
  377. psychopy/experiment/exports.py +1 -1
  378. psychopy/experiment/flow.py +2 -2
  379. psychopy/experiment/localization.py +1 -1
  380. psychopy/experiment/loops.py +43 -10
  381. psychopy/experiment/params.py +6 -4
  382. psychopy/experiment/plugins.py +8 -1
  383. psychopy/experiment/py2js.py +1 -1
  384. psychopy/experiment/py2js_transpiler.py +1 -1
  385. psychopy/experiment/routines/__init__.py +1 -1
  386. psychopy/experiment/routines/_base.py +23 -24
  387. psychopy/experiment/routines/audioValidator/__init__.py +343 -0
  388. psychopy/experiment/routines/audioValidator/classic/audio_validator.png +0 -0
  389. psychopy/experiment/routines/audioValidator/classic/audio_validator@2x.png +0 -0
  390. psychopy/experiment/routines/audioValidator/dark/audio_validator.png +0 -0
  391. psychopy/experiment/routines/audioValidator/dark/audio_validator@2x.png +0 -0
  392. psychopy/experiment/routines/audioValidator/light/audio_validator.png +0 -0
  393. psychopy/experiment/routines/audioValidator/light/audio_validator@2x.png +0 -0
  394. psychopy/experiment/routines/eyetracker_calibrate/__init__.py +76 -17
  395. psychopy/experiment/routines/eyetracker_validate/__init__.py +1 -1
  396. psychopy/experiment/routines/unknown/__init__.py +2 -0
  397. psychopy/experiment/routines/{photodiodeValidator → visualValidator}/__init__.py +89 -120
  398. psychopy/experiment/routines/visualValidator/classic/visual_validator.png +0 -0
  399. psychopy/experiment/routines/visualValidator/classic/visual_validator@2x.png +0 -0
  400. psychopy/experiment/routines/visualValidator/dark/visual_validator.png +0 -0
  401. psychopy/experiment/routines/visualValidator/dark/visual_validator@2x.png +0 -0
  402. psychopy/experiment/routines/visualValidator/light/visual_validator.png +0 -0
  403. psychopy/experiment/routines/visualValidator/light/visual_validator@2x.png +0 -0
  404. psychopy/experiment/utils.py +1 -1
  405. psychopy/gui/__init__.py +1 -1
  406. psychopy/gui/qtgui.py +15 -6
  407. psychopy/gui/wxgui.py +1 -1
  408. psychopy/hardware/__init__.py +0 -1
  409. psychopy/hardware/base.py +147 -16
  410. psychopy/hardware/bbtk/__init__.py +10 -16
  411. psychopy/hardware/brainproducts.py +7 -13
  412. psychopy/hardware/button.py +21 -2
  413. psychopy/hardware/buttonbox/__init__.py +1 -1
  414. psychopy/hardware/camera/__init__.py +18 -5
  415. psychopy/hardware/cedrus.py +5 -16
  416. psychopy/hardware/crs/__init__.py +1 -1
  417. psychopy/hardware/crs/bits.py +36 -33
  418. psychopy/hardware/crs/colorcal.py +8 -11
  419. psychopy/hardware/crs/optical.py +7 -10
  420. psychopy/hardware/crs/shaders.py +18 -5
  421. psychopy/hardware/emotiv.py +1 -1
  422. psychopy/hardware/emulator.py +11 -5
  423. psychopy/hardware/exceptions.py +86 -0
  424. psychopy/hardware/forp.py +18 -23
  425. psychopy/hardware/gammasci.py +8 -3
  426. psychopy/hardware/iolab.py +8 -19
  427. psychopy/hardware/joystick/__init__.py +865 -266
  428. psychopy/hardware/joystick/_base.py +251 -0
  429. psychopy/hardware/joystick/backend_glfw.py +306 -0
  430. psychopy/hardware/joystick/backend_pyglet.py +309 -0
  431. psychopy/hardware/joystick/mappings.py +287 -0
  432. psychopy/hardware/keyboard.py +4 -2
  433. psychopy/hardware/labhackers.py +1 -1
  434. psychopy/hardware/labjacks.py +9 -13
  435. psychopy/hardware/{photodiode.py → lightsensor.py} +146 -203
  436. psychopy/hardware/listener.py +9 -8
  437. psychopy/hardware/manager.py +24 -35
  438. psychopy/hardware/microphone.py +535 -155
  439. psychopy/hardware/minolta.py +14 -4
  440. psychopy/hardware/mouse/__init__.py +1 -1
  441. psychopy/hardware/photometer/__init__.py +2 -2
  442. psychopy/hardware/pr.py +14 -4
  443. psychopy/hardware/qmix.py +18 -27
  444. psychopy/hardware/serialdevice.py +43 -12
  445. psychopy/hardware/soundsensor.py +473 -0
  446. psychopy/hardware/spatial/__init__.py +231 -0
  447. psychopy/hardware/speaker.py +298 -36
  448. psychopy/hardware/triggerbox/__init__.py +1 -1
  449. psychopy/hardware/triggerbox/base.py +1 -1
  450. psychopy/hardware/triggerbox/parallel.py +1 -1
  451. psychopy/info.py +1 -1
  452. psychopy/iohub/devices/eyetracker/__init__.py +10 -18
  453. psychopy/iohub/devices/eyetracker/calibration/procedure.py +15 -33
  454. psychopy/iohub/devices/eyetracker/hw/gazepoint/__init__.py +2 -2
  455. psychopy/iohub/devices/eyetracker/hw/gazepoint/gp3/__init__.py +1 -0
  456. psychopy/iohub/devices/eyetracker/hw/gazepoint/gp3/eyetracker.py +10 -0
  457. psychopy/iohub/devices/eyetracker/hw/pupil_labs/neon/__init__.py +1 -0
  458. psychopy/iohub/devices/mouse/linux2.py +2 -1
  459. psychopy/layout.py +1 -1
  460. psychopy/liaison.py +91 -39
  461. psychopy/locale_setup.py +11 -1
  462. psychopy/localization/__init__.py +1 -1
  463. psychopy/localization/_localization.py +1 -1
  464. psychopy/localization/messages.pot +2 -2
  465. psychopy/logging.py +14 -12
  466. psychopy/microphone.py +4 -3
  467. psychopy/misc.py +1 -1
  468. psychopy/monitors/MonitorCenter.py +3 -3
  469. psychopy/monitors/__init__.py +1 -1
  470. psychopy/monitors/calibData.py +1 -1
  471. psychopy/monitors/calibTools.py +3 -2
  472. psychopy/platform_specific/__init__.py +1 -1
  473. psychopy/platform_specific/darwin.py +1 -1
  474. psychopy/platform_specific/linux.py +1 -1
  475. psychopy/platform_specific/win32.py +1 -1
  476. psychopy/plugins/__init__.py +26 -17
  477. psychopy/plugins/util.py +65 -0
  478. psychopy/preferences/Darwin.spec +11 -3
  479. psychopy/preferences/FreeBSD.spec +11 -3
  480. psychopy/preferences/Linux.spec +11 -3
  481. psychopy/preferences/Windows.spec +11 -3
  482. psychopy/preferences/__init__.py +1 -1
  483. psychopy/preferences/baseNoArch.spec +11 -3
  484. psychopy/preferences/hints.py +78 -61
  485. psychopy/preferences/preferences.py +82 -13
  486. psychopy/projects/__init__.py +1 -1
  487. psychopy/projects/pavlovia.py +29 -13
  488. psychopy/scripts/psyexpCompile.py +1 -1
  489. psychopy/session.py +81 -8
  490. psychopy/sound/__init__.py +25 -10
  491. psychopy/sound/_base.py +134 -34
  492. psychopy/sound/audioclip.py +38 -15
  493. psychopy/sound/audiodevice.py +1 -1
  494. psychopy/sound/backend_ptb.py +53 -321
  495. psychopy/sound/backend_pygame.py +7 -3
  496. psychopy/sound/backend_pyo.py +53 -22
  497. psychopy/sound/backend_pysound.py +10 -27
  498. psychopy/sound/backend_sounddevice.py +33 -21
  499. psychopy/sound/exceptions.py +1 -1
  500. psychopy/sound/microphone.py +83 -5
  501. psychopy/sound/transcribe.py +3 -3
  502. psychopy/tests/data/test_basic_run.py +1 -0
  503. psychopy/tests/data/test_sounds/default_16000.wav +0 -0
  504. psychopy/tests/data/test_sounds/default_192000.wav +0 -0
  505. psychopy/tests/data/test_sounds/default_22050.wav +0 -0
  506. psychopy/tests/data/test_sounds/default_32000.wav +0 -0
  507. psychopy/tests/data/test_sounds/default_44100.wav +0 -0
  508. psychopy/tests/data/test_sounds/default_48000.wav +0 -0
  509. psychopy/tests/data/test_sounds/default_8000.wav +0 -0
  510. psychopy/tests/data/test_sounds/default_96000.wav +0 -0
  511. psychopy/tests/test_alerts/test_alerttools.py +2 -1
  512. psychopy/tests/test_app/test_command_line.py +65 -0
  513. psychopy/tests/test_data/test_TrialHandler2.py +18 -7
  514. psychopy/tests/test_demos/test_builder_demos.py +1 -1
  515. psychopy/tests/test_experiment/needs_wx/componsTemplate.txt +5238 -4188
  516. psychopy/tests/test_experiment/needs_wx/test_components.py +1 -1
  517. psychopy/tests/test_experiment/test_components/test_RoutineSettingsComponent.py +16 -0
  518. psychopy/tests/test_experiment/test_components/test_all_components.py +1 -1
  519. psychopy/tests/test_experiment/test_components/test_base_components.py +26 -17
  520. psychopy/tests/test_experiment/test_loops.py +12 -4
  521. psychopy/tests/test_experiment/test_params.py +17 -4
  522. psychopy/tests/test_experiment/test_routines/test_PhotodiodeValidationRoutine.py +2 -2
  523. psychopy/tests/test_hardware/test_photodiode.py +66 -0
  524. psychopy/tests/test_liaison/test_Liaison.py +4 -4
  525. psychopy/tests/test_misc/test_clock.py +2 -0
  526. psychopy/tests/test_misc/test_color.py +2 -0
  527. psychopy/tests/test_misc/test_event.py +1 -0
  528. psychopy/tests/test_plugins/__init__.py +0 -0
  529. psychopy/tests/test_plugins/test_plugin_stubs.py +125 -0
  530. psychopy/tests/test_sound/test_sound.py +111 -40
  531. psychopy/tests/test_tools/test_colorspacetools.py +24 -23
  532. psychopy/tests/test_tools/test_mathtools.py +9 -9
  533. psychopy/tests/test_tools/test_viewtools.py +101 -0
  534. psychopy/tests/test_validators/__init__.py +0 -0
  535. psychopy/tests/test_validators/test_voicekeyValidator.py +95 -0
  536. psychopy/tests/test_visual/test_all_stimuli.py +2 -2
  537. psychopy/tests/test_visual/test_basevisual.py +37 -7
  538. psychopy/tests/test_visual/test_button.py +2 -2
  539. psychopy/tests/test_visual/test_circle.py +10 -2
  540. psychopy/tests/test_visual/test_dots.py +1 -1
  541. psychopy/tests/test_visual/test_form.py +13 -13
  542. psychopy/tests/test_visual/test_gamma.py +3 -3
  543. psychopy/tests/test_visual/test_image.py +2 -2
  544. psychopy/tests/test_visual/test_progress.py +2 -2
  545. psychopy/tests/test_visual/test_roi.py +8 -2
  546. psychopy/tests/test_visual/test_shape.py +2 -2
  547. psychopy/tests/test_visual/test_slider.py +2 -2
  548. psychopy/tests/test_visual/test_target.py +2 -2
  549. psychopy/tests/test_visual/test_textbox.py +6 -8
  550. psychopy/tests/test_visual/test_winScalePos.py +6 -5
  551. psychopy/tests/test_visual/test_window.py +17 -0
  552. psychopy/tests/utils.py +51 -1
  553. psychopy/tools/__init__.py +1 -1
  554. psychopy/tools/arraytools.py +2 -2
  555. psychopy/tools/attributetools.py +52 -1
  556. psychopy/tools/audiotools.py +4 -1
  557. psychopy/tools/colorspacetools.py +6 -4
  558. psychopy/tools/coordinatetools.py +1 -1
  559. psychopy/tools/fileerrortools.py +21 -9
  560. psychopy/tools/filetools.py +2 -2
  561. psychopy/tools/fontmanager.py +47 -28
  562. psychopy/tools/gltools.py +2967 -559
  563. psychopy/tools/imagetools.py +1 -1
  564. psychopy/tools/mathtools.py +997 -127
  565. psychopy/tools/monitorunittools.py +7 -1
  566. psychopy/tools/movietools.py +1 -2
  567. psychopy/tools/pkgtools.py +157 -127
  568. psychopy/tools/plottools.py +1 -1
  569. psychopy/tools/rifttools.py +1 -1
  570. psychopy/tools/stereotools.py +1 -1
  571. psychopy/tools/stimulustools.py +172 -2
  572. psychopy/tools/stringtools.py +22 -2
  573. psychopy/tools/systemtools.py +1 -1
  574. psychopy/tools/typetools.py +1 -1
  575. psychopy/tools/unittools.py +1 -1
  576. psychopy/tools/versionchooser.py +3 -1
  577. psychopy/tools/viewtools.py +54 -70
  578. psychopy/tools/wizard.py +2 -2
  579. psychopy/validation/__init__.py +6 -0
  580. psychopy/validation/audio.py +74 -0
  581. psychopy/validation/visual.py +115 -0
  582. psychopy/visual/__init__.py +1 -4
  583. psychopy/visual/aperture.py +9 -6
  584. psychopy/visual/backends/__init__.py +1 -1
  585. psychopy/visual/backends/_base.py +1 -1
  586. psychopy/visual/backends/gamma.py +1 -1
  587. psychopy/visual/backends/glfwbackend.py +8 -12
  588. psychopy/visual/backends/pygamebackend.py +1 -1
  589. psychopy/visual/backends/pygletbackend.py +32 -11
  590. psychopy/visual/basevisual.py +93 -13
  591. psychopy/visual/brush.py +1 -1
  592. psychopy/visual/bufferimage.py +90 -10
  593. psychopy/visual/button.py +1 -1
  594. psychopy/visual/circle.py +12 -20
  595. psychopy/visual/custommouse.py +1 -1
  596. psychopy/visual/dot.py +80 -14
  597. psychopy/visual/elementarray.py +84 -10
  598. psychopy/visual/filters.py +1 -1
  599. psychopy/visual/form.py +43 -28
  600. psychopy/visual/grating.py +105 -25
  601. psychopy/visual/helpers.py +4 -2
  602. psychopy/visual/image.py +98 -20
  603. psychopy/visual/line.py +15 -25
  604. psychopy/visual/movie.py +1 -1
  605. psychopy/visual/movie2.py +4 -3
  606. psychopy/visual/movie3.py +4 -3
  607. psychopy/visual/movies/__init__.py +1 -1
  608. psychopy/visual/movies/frame.py +1 -1
  609. psychopy/visual/movies/metadata.py +1 -1
  610. psychopy/visual/movies/players/__init__.py +1 -1
  611. psychopy/visual/movies/players/_base.py +1 -1
  612. psychopy/visual/movies/players/ffpyplayer_player.py +2 -3
  613. psychopy/visual/nnlvs.py +9 -6
  614. psychopy/visual/noise.py +4 -16
  615. psychopy/visual/patch.py +4 -3
  616. psychopy/visual/pie.py +3 -14
  617. psychopy/visual/polygon.py +21 -28
  618. psychopy/visual/radial.py +4 -18
  619. psychopy/visual/ratingscale.py +4 -3
  620. psychopy/visual/rect.py +16 -25
  621. psychopy/visual/rift.py +8 -2
  622. psychopy/visual/secondorder.py +4 -16
  623. psychopy/visual/shaders.py +620 -286
  624. psychopy/visual/shape.py +281 -90
  625. psychopy/visual/simpleimage.py +1 -1
  626. psychopy/visual/slider.py +78 -25
  627. psychopy/visual/stim3d.py +5 -608
  628. psychopy/visual/text.py +13 -3
  629. psychopy/visual/textbox2/textbox2.py +188 -56
  630. psychopy/visual/vlcmoviestim.py +1 -1
  631. psychopy/visual/window.py +374 -201
  632. psychopy/web.py +1 -1
  633. {psychopy-2024.2.5.dist-info → psychopy-2025.1.1.dist-info}/METADATA +13 -12
  634. {psychopy-2024.2.5.dist-info → psychopy-2025.1.1.dist-info}/RECORD +647 -624
  635. {psychopy-2024.2.5.dist-info → psychopy-2025.1.1.dist-info}/WHEEL +1 -1
  636. psychopy/.DS_Store +0 -0
  637. psychopy/__init__.py.orig +0 -65
  638. psychopy/app/.DS_Store +0 -0
  639. psychopy/app/Resources/.DS_Store +0 -0
  640. psychopy/app/Resources/classic/filecsv16.png +0 -0
  641. psychopy/app/Resources/classic/fileimage16.png +0 -0
  642. psychopy/app/Resources/classic/fileunknown16.png +0 -0
  643. psychopy/app/Resources/dark/filecsv16.png +0 -0
  644. psychopy/app/Resources/dark/filecsv16@2x.png +0 -0
  645. psychopy/app/Resources/dark/fileimage16.png +0 -0
  646. psychopy/app/Resources/dark/fileimage16@2x.png +0 -0
  647. psychopy/app/Resources/dark/fileunknown16.png +0 -0
  648. psychopy/app/Resources/dark/fileunknown16@2x.png +0 -0
  649. psychopy/app/Resources/fonts/OpenSans-Bold.ttf +0 -0
  650. psychopy/app/Resources/fonts/OpenSans-BoldItalic.ttf +0 -0
  651. psychopy/app/Resources/fonts/OpenSans-ExtraBold.ttf +0 -0
  652. psychopy/app/Resources/fonts/OpenSans-ExtraBoldItalic.ttf +0 -0
  653. psychopy/app/Resources/fonts/OpenSans-Italic.ttf +0 -0
  654. psychopy/app/Resources/fonts/OpenSans-Light.ttf +0 -0
  655. psychopy/app/Resources/fonts/OpenSans-LightItalic.ttf +0 -0
  656. psychopy/app/Resources/fonts/OpenSans-Regular.ttf +0 -0
  657. psychopy/app/Resources/fonts/OpenSans-SemiBold.ttf +0 -0
  658. psychopy/app/Resources/fonts/OpenSans-SemiBoldItalic.ttf +0 -0
  659. psychopy/app/Resources/light/filecsv16.png +0 -0
  660. psychopy/app/Resources/light/filecsv16@2x.png +0 -0
  661. psychopy/app/Resources/light/fileimage16.png +0 -0
  662. psychopy/app/Resources/light/fileimage16@2x.png +0 -0
  663. psychopy/app/Resources/light/fileunknown16.png +0 -0
  664. psychopy/app/Resources/light/fileunknown16@2x.png +0 -0
  665. psychopy/app/Resources/psychopySplash.png +0 -0
  666. psychopy/app/Resources/psychopySplash@2x.png +0 -0
  667. psychopy/app/builder/builder.py.orig +0 -3932
  668. psychopy/app/builder/dialogs/__init__.py.orig +0 -1679
  669. psychopy/app/builder/dialogs/paramCtrls.py.orig +0 -713
  670. psychopy/app/colorpicker/__init__.py.orig +0 -411
  671. psychopy/app/locale/ar_001/.DS_Store +0 -0
  672. psychopy/app/locale/ar_001/LC_MESSAGE/.DS_Store +0 -0
  673. psychopy/core.py.orig +0 -169
  674. psychopy/demos/builder/.DS_Store +0 -0
  675. psychopy/demos/builder/Design Templates/randomisedBlocks/randomisedBlocks_lastrun.py +0 -330
  676. psychopy/demos/builder/Experiments/.DS_Store +0 -0
  677. psychopy/demos/builder/Experiments/dragAndDrop/archived_conditions.xlsx +0 -0
  678. psychopy/demos/builder/Experiments/dragAndDrop/draw grid stim.py +0 -61
  679. psychopy/demos/builder/Experiments/dragAndDrop/shapeMaker.psyexp +0 -91
  680. psychopy/demos/builder/Experiments/dragAndDrop/stimuli/grid_image_1.png +0 -0
  681. psychopy/demos/builder/Experiments/dragAndDrop/stimuli/grid_image_10.png +0 -0
  682. psychopy/demos/builder/Experiments/dragAndDrop/stimuli/grid_image_2.png +0 -0
  683. psychopy/demos/builder/Experiments/dragAndDrop/stimuli/grid_image_3.png +0 -0
  684. psychopy/demos/builder/Experiments/dragAndDrop/stimuli/grid_image_4.png +0 -0
  685. psychopy/demos/builder/Experiments/dragAndDrop/stimuli/grid_image_5.png +0 -0
  686. psychopy/demos/builder/Experiments/dragAndDrop/stimuli/grid_image_6.png +0 -0
  687. psychopy/demos/builder/Experiments/dragAndDrop/stimuli/grid_image_7.png +0 -0
  688. psychopy/demos/builder/Experiments/dragAndDrop/stimuli/grid_image_8.png +0 -0
  689. psychopy/demos/builder/Experiments/dragAndDrop/stimuli/grid_image_9.png +0 -0
  690. psychopy/demos/builder/Experiments/dragAndDrop/updated_conditions.xlsx +0 -0
  691. psychopy/demos/builder/Tools/.DS_Store +0 -0
  692. psychopy/demos/builder/Tools/gammaCalibration/.DS_Store +0 -0
  693. psychopy/demos/builder/Tools/gammaCalibration/data/.DS_Store +0 -0
  694. psychopy/demos/builder/Tools/gammaCalibration/data/_gamma_correction_visual_2022-05-18_14h18.29.439.csv +0 -38
  695. psychopy/demos/builder/Tools/gammaCalibration/data/_gamma_correction_visual_2022-05-18_14h18.29.439.log +0 -3418
  696. psychopy/demos/builder/Tools/gammaCalibration/data/_gamma_correction_visual_2022-05-18_14h18.29.439.psydat +0 -0
  697. psychopy/demos/builder/Tools/gammaCalibration/data/x1_gamma_correction_visual_2022-05-17_13h59.42.928.csv +0 -2
  698. psychopy/demos/builder/Tools/gammaCalibration/data/x1_gamma_correction_visual_2022-05-17_13h59.42.928.log +0 -15
  699. psychopy/demos/builder/Tools/gammaCalibration/data/x1_gamma_correction_visual_2022-05-17_13h59.42.928.psydat +0 -0
  700. psychopy/demos/builder/Tools/gammaCalibration/gamma_correction_visual_lastrun.py +0 -562
  701. psychopy/demos/coder/.DS_Store +0 -0
  702. psychopy/demos/coder/experiment control/info_gamma.pickle +0 -0
  703. psychopy/demos/coder/iohub/.iohpid +0 -1
  704. psychopy/demos/coder/iohub/eyetracking/.iohpid +0 -1
  705. psychopy/demos/coder/iohub/wintab/.DS_Store +0 -0
  706. psychopy/demos/coder/stimuli/.DS_Store +0 -0
  707. psychopy/experiment/_experiment.py.orig +0 -1032
  708. psychopy/experiment/components/.DS_Store +0 -0
  709. psychopy/experiment/components/_base.py.orig +0 -823
  710. psychopy/experiment/components/form/.DS_Store +0 -0
  711. psychopy/experiment/components/microphone/__init__.py.orig +0 -490
  712. psychopy/experiment/components/settings/__init__.py.orig +0 -1337
  713. psychopy/experiment/components/textbox/__init__.py.orig +0 -310
  714. psychopy/experiment/components/webcam/.DS_Store +0 -0
  715. psychopy/experiment/components/webcam/light/.DS_Store +0 -0
  716. psychopy/experiment/loops.py.orig +0 -829
  717. psychopy/experiment/params.py.orig +0 -408
  718. psychopy/experiment/routine.py.orig +0 -503
  719. psychopy/experiment/routines/photodiodeValidator/classic/photodiode_validator.png +0 -0
  720. psychopy/experiment/routines/photodiodeValidator/classic/photodiode_validator@2x.png +0 -0
  721. psychopy/experiment/routines/photodiodeValidator/dark/photodiode_validator.png +0 -0
  722. psychopy/experiment/routines/photodiodeValidator/dark/photodiode_validator@2x.png +0 -0
  723. psychopy/experiment/routines/photodiodeValidator/light/photodiode_validator.png +0 -0
  724. psychopy/experiment/routines/photodiodeValidator/light/photodiode_validator@2x.png +0 -0
  725. psychopy/hardware/.DS_Store +0 -0
  726. psychopy/hardware/brainproducts.py.orig +0 -680
  727. psychopy/hardware/iolab.py.orig +0 -238
  728. psychopy/iohub/datastore/__init__.py.orig +0 -443
  729. psychopy/iohub/datastore/util.py.orig +0 -692
  730. psychopy/iohub/devices/mouse/darwin.py.orig +0 -427
  731. psychopy/iohub/devices/mouse/linux2.py.orig +0 -198
  732. psychopy/preferences/.DS_Store +0 -0
  733. psychopy/projects/pavlovia.py.orig +0 -1295
  734. psychopy/tests/.DS_Store +0 -0
  735. psychopy/tests/data/.DS_Store +0 -0
  736. psychopy/tests/data/TestCircle_fill_local.png +0 -0
  737. psychopy/tests/data/aperture1_normHexbackground_local.png +0 -0
  738. psychopy/tests/data/aperture1_norm_local.png +0 -0
  739. psychopy/tests/data/aperture2_normHexbackground_local.png +0 -0
  740. psychopy/tests/data/beatandrcos_height_local.png +0 -0
  741. psychopy/tests/data/beatandrcos_normAddBlend_local.png +0 -0
  742. psychopy/tests/data/beatandrcos_normHexbackground_local.png +0 -0
  743. psychopy/tests/data/beatandrcos_norm_local.png +0 -0
  744. psychopy/tests/data/beatandrcos_stencil_local.png +0 -0
  745. psychopy/tests/data/blend_add_height_local.png +0 -0
  746. psychopy/tests/data/blend_add_normAddBlend_local.png +0 -0
  747. psychopy/tests/data/blend_add_normHexbackground_local.png +0 -0
  748. psychopy/tests/data/blend_add_normNoShade_local.png +0 -0
  749. psychopy/tests/data/blend_add_norm_local.png +0 -0
  750. psychopy/tests/data/blend_add_stencil_local.png +0 -0
  751. psychopy/tests/data/bufferimg_gabor_height_local.png +0 -0
  752. psychopy/tests/data/bufferimg_gabor_normAddBlend_local.png +0 -0
  753. psychopy/tests/data/bufferimg_gabor_normHexbackground_local.png +0 -0
  754. psychopy/tests/data/bufferimg_gabor_normNoShade_local.png +0 -0
  755. psychopy/tests/data/bufferimg_gabor_norm_local.png +0 -0
  756. psychopy/tests/data/bufferimg_gabor_stencil_local.png +0 -0
  757. psychopy/tests/data/circleHex_height_local.png +0 -0
  758. psychopy/tests/data/circleHex_normAddBlend_local.png +0 -0
  759. psychopy/tests/data/circleHex_normHexbackground_local.png +0 -0
  760. psychopy/tests/data/circleHex_normNoShade_local.png +0 -0
  761. psychopy/tests/data/circleHex_norm_local.png +0 -0
  762. psychopy/tests/data/circleHex_stencil_local.png +0 -0
  763. psychopy/tests/data/color_comparison_local.png +0 -0
  764. psychopy/tests/data/correctScript/.DS_Store +0 -0
  765. psychopy/tests/data/dots_height_local.png +0 -0
  766. psychopy/tests/data/dots_normAddBlend_local.png +0 -0
  767. psychopy/tests/data/dots_normHexbackground_local.png +0 -0
  768. psychopy/tests/data/dots_normNoShade_local.png +0 -0
  769. psychopy/tests/data/dots_norm_local.png +0 -0
  770. psychopy/tests/data/dots_stencil_local.png +0 -0
  771. psychopy/tests/data/elarray1_height_local.png +0 -0
  772. psychopy/tests/data/elarray1_normAddBlend_local.png +0 -0
  773. psychopy/tests/data/elarray1_normHexbackground_local.png +0 -0
  774. psychopy/tests/data/elarray1_norm_local.png +0 -0
  775. psychopy/tests/data/elarray1_stencil_local.png +0 -0
  776. psychopy/tests/data/envelopeandrcos_height_local.png +0 -0
  777. psychopy/tests/data/envelopeandrcos_normAddBlend_local.png +0 -0
  778. psychopy/tests/data/envelopeandrcos_normHexbackground_local.png +0 -0
  779. psychopy/tests/data/envelopeandrcos_norm_local.png +0 -0
  780. psychopy/tests/data/envelopeandrcos_stencil_local.png +0 -0
  781. psychopy/tests/data/envelopepowerandrcos_height_local.png +0 -0
  782. psychopy/tests/data/envelopepowerandrcos_normAddBlend_local.png +0 -0
  783. psychopy/tests/data/envelopepowerandrcos_normHexbackground_local.png +0 -0
  784. psychopy/tests/data/envelopepowerandrcos_norm_local.png +0 -0
  785. psychopy/tests/data/envelopepowerandrcos_stencil_local.png +0 -0
  786. psychopy/tests/data/gabor1_height_local.png +0 -0
  787. psychopy/tests/data/gabor1_normAddBlend_local.png +0 -0
  788. psychopy/tests/data/gabor1_normHexbackground_local.png +0 -0
  789. psychopy/tests/data/gabor1_normNoShade_local.png +0 -0
  790. psychopy/tests/data/gabor1_norm_local.png +0 -0
  791. psychopy/tests/data/gabor1_stencil_local.png +0 -0
  792. psychopy/tests/data/greyscale_normHexbackground_local.png +0 -0
  793. psychopy/tests/data/imageAndGauss_height_local.png +0 -0
  794. psychopy/tests/data/imageAndGauss_normAddBlend_local.png +0 -0
  795. psychopy/tests/data/imageAndGauss_normHexbackground_local.png +0 -0
  796. psychopy/tests/data/imageAndGauss_normNoShade_local.png +0 -0
  797. psychopy/tests/data/imageAndGauss_norm_local.png +0 -0
  798. psychopy/tests/data/imageAndGauss_stencil_local.png +0 -0
  799. psychopy/tests/data/movFrame1_stencil_local.png +0 -0
  800. psychopy/tests/data/noiseAndRcos_height_local.png +0 -0
  801. psychopy/tests/data/noiseAndRcos_normAddBlend_local.png +0 -0
  802. psychopy/tests/data/noiseAndRcos_normHexbackground_local.png +0 -0
  803. psychopy/tests/data/noiseAndRcos_normNoShade_local.png +0 -0
  804. psychopy/tests/data/noiseAndRcos_norm_local.png +0 -0
  805. psychopy/tests/data/noiseAndRcos_stencil_local.png +0 -0
  806. psychopy/tests/data/noiseFiltersAndRcos_height_local.png +0 -0
  807. psychopy/tests/data/noiseFiltersAndRcos_normAddBlend_local.png +0 -0
  808. psychopy/tests/data/noiseFiltersAndRcos_normHexbackground_local.png +0 -0
  809. psychopy/tests/data/noiseFiltersAndRcos_normNoShade_local.png +0 -0
  810. psychopy/tests/data/noiseFiltersAndRcos_norm_local.png +0 -0
  811. psychopy/tests/data/noiseFiltersAndRcos_stencil_local.png +0 -0
  812. psychopy/tests/data/numpyImage_height_local.png +0 -0
  813. psychopy/tests/data/numpyImage_normAddBlend_local.png +0 -0
  814. psychopy/tests/data/numpyImage_normHexbackground_local.png +0 -0
  815. psychopy/tests/data/numpyImage_normNoShade_local.png +0 -0
  816. psychopy/tests/data/numpyImage_norm_local.png +0 -0
  817. psychopy/tests/data/numpyImage_stencil_local.png +0 -0
  818. psychopy/tests/data/shape2_1_normAddBlend_local.png +0 -0
  819. psychopy/tests/data/shape2_1_normHexbackground_local.png +0 -0
  820. psychopy/tests/data/shape2_1_normNoShade_local.png +0 -0
  821. psychopy/tests/data/shape2_1_norm_local.png +0 -0
  822. psychopy/tests/data/shape2_1_stencil_local.png +0 -0
  823. psychopy/tests/data/text1_height_local.png +0 -0
  824. psychopy/tests/data/text1_normAddBlend_local.png +0 -0
  825. psychopy/tests/data/text1_normHexbackground_local.png +0 -0
  826. psychopy/tests/data/text1_norm_local.png +0 -0
  827. psychopy/tests/data/text1_stencil_local.png +0 -0
  828. psychopy/tests/data/wedge1_height_local.png +0 -0
  829. psychopy/tests/data/wedge1_normAddBlend_local.png +0 -0
  830. psychopy/tests/data/wedge1_normHexbackground_local.png +0 -0
  831. psychopy/tests/data/wedge1_normNoShade_local.png +0 -0
  832. psychopy/tests/data/wedge1_norm_local.png +0 -0
  833. psychopy/tests/data/wedge1_stencil_local.png +0 -0
  834. psychopy/tests/test_app/.DS_Store +0 -0
  835. psychopy/tests/test_app/test_builder/.DS_Store +0 -0
  836. psychopy/tests/test_app/test_builder/data/_2021_ 5_03_1206.log +0 -177
  837. psychopy/tests/test_app/test_builder/data/_2021_ 5_03_1206.psydat +0 -0
  838. psychopy/tests/test_app/test_builder/data/_2021_ 5_03_1206.xlsx +0 -0
  839. psychopy/tests/test_app/test_builder/data/_2021_ 5_03_1324.log +0 -168
  840. psychopy/tests/test_app/test_builder/data/_2021_ 5_03_1324.psydat +0 -0
  841. psychopy/tests/test_app/test_builder/data/_2021_ 5_03_1324.xlsx +0 -0
  842. psychopy/tests/test_data/.DS_Store +0 -0
  843. psychopy/tests/test_hardware/test_CRS_BitsSharp.py +0 -67
  844. psychopy/tests/test_hardware/test_CRS_BitsSharp.py.orig +0 -68
  845. psychopy/tests/test_hardware/test_CRS_bitsShaders.py +0 -110
  846. psychopy/tests/test_visual/test_image.py.orig +0 -219
  847. psychopy/visual/basevisual.py.orig +0 -1723
  848. psychopy/visual/form.py.orig +0 -1181
  849. psychopy/visual/text.py.orig +0 -752
  850. psychopy/visual/textbox2/textbox2.py.orig +0 -1315
  851. psychopy/visual/windowwarp.py.orig +0 -463
  852. /psychopy/{app/cortex.log → alerts/alertsCatalogue/3600.yaml} +0 -0
  853. /psychopy/{app/Resources → assets}/fonts/Arvo-Bold.ttf +0 -0
  854. /psychopy/{app/Resources → assets}/fonts/Arvo-BoldItalic.ttf +0 -0
  855. /psychopy/{app/Resources → assets}/fonts/Arvo-Italic.ttf +0 -0
  856. /psychopy/{app/Resources → assets}/fonts/Arvo-Regular.ttf +0 -0
  857. /psychopy/{app/Resources → assets}/fonts/DejaVuSerif.ttf +0 -0
  858. /psychopy/{app/Resources → assets}/fonts/IndieFlower-Regular.ttf +0 -0
  859. /psychopy/{app/Resources → assets}/fonts/JetBrainsMono-Italic-VariableFont_wght.ttf +0 -0
  860. /psychopy/{app/Resources → assets}/fonts/JetBrainsMono-VariableFont_wght.ttf +0 -0
  861. /psychopy/demos/builder/Experiments/{GoNoGo → goNoGo}/readme.md +0 -0
  862. {psychopy-2024.2.5.dist-info → psychopy-2025.1.1.dist-info}/entry_points.txt +0 -0
  863. {psychopy-2024.2.5.dist-info → psychopy-2025.1.1.dist-info}/licenses/LICENSE +0 -0
@@ -3,7 +3,8 @@ import time
3
3
 
4
4
  import numpy as np
5
5
  from psychtoolbox import audio as audio
6
- from psychopy import logging as logging, prefs
6
+ from psychopy import logging as logging, prefs, core
7
+ from psychopy.hardware.exceptions import DeviceNotConnectedError
7
8
  from psychopy.localization import _translate
8
9
  from psychopy.constants import NOT_STARTED
9
10
  from psychopy.hardware import BaseDevice, BaseResponse, BaseResponseDevice
@@ -60,7 +61,14 @@ class MicrophoneDevice(BaseDevice, aliases=["mic", "microphone"]):
60
61
  size of the recording buffer to ensure that the application does not run
61
62
  out of memory. By default, the recording buffer is set to 24000 KB (or
62
63
  24 MB). At a sample rate of 48kHz, this will result in 62.5 seconds of
63
- continuous audio being recorded before the buffer is full.
64
+ continuous audio being recorded before the buffer is full. You may
65
+ specify how to handle the buffer when it is full using the
66
+ `policyWhenFull` parameter.
67
+ policyWhenFull : str
68
+ Policy to use when the recording buffer is full. Options are:
69
+ - "ignore": When full, just don't record any new samples
70
+ - "warn"/"warning": Same as ignore, but will log a warning
71
+ - "error": When full, will raise an error
64
72
  audioLatencyMode : int or None
65
73
  Audio latency mode to use, values range between 0-4. If `None`, the
66
74
  setting from preferences will be used. Using `3` (exclusive mode) is
@@ -81,7 +89,7 @@ class MicrophoneDevice(BaseDevice, aliases=["mic", "microphone"]):
81
89
  Capture 10 seconds of audio from the primary microphone::
82
90
 
83
91
  import psychopy.core as core
84
- import psychopy.sound.Microphone as Microphone
92
+ import psychopy.sound.microphone.Microphone as Microphone
85
93
 
86
94
  mic = Microphone(bufferSecs=10.0) # open the microphone
87
95
  mic.start() # start recording
@@ -114,15 +122,19 @@ class MicrophoneDevice(BaseDevice, aliases=["mic", "microphone"]):
114
122
  # other instances of MicrophoneDevice, stored by index
115
123
  _streams = {}
116
124
 
117
- def __init__(self,
118
- index=None,
119
- sampleRateHz=None,
120
- channels=None,
121
- streamBufferSecs=2.0,
122
- maxRecordingSize=24000,
123
- policyWhenFull='warn',
124
- audioLatencyMode=None,
125
- audioRunMode=0):
125
+ def __init__(
126
+ self,
127
+ index=None,
128
+ sampleRateHz=None,
129
+ channels=None,
130
+ streamBufferSecs=2.0,
131
+ maxRecordingSize=-1,
132
+ policyWhenFull='warn',
133
+ exclusive=False,
134
+ audioRunMode=1,
135
+ # legacy
136
+ audioLatencyMode=None,
137
+ ):
126
138
 
127
139
  if not _hasPTB: # fail if PTB is not installed
128
140
  raise ModuleNotFoundError(
@@ -147,10 +159,13 @@ class MicrophoneDevice(BaseDevice, aliases=["mic", "microphone"]):
147
159
  _devices = MicrophoneDevice.getDevices()
148
160
  # if there are none, error
149
161
  if not len(_devices):
150
- raise AudioInvalidCaptureDeviceError(_translate(
151
- "Could not choose default recording device as no recording "
152
- "devices are connected."
153
- ))
162
+ raise DeviceNotConnectedError(
163
+ _translate(
164
+ "Could not choose default recording device as no recording "
165
+ "devices are connected."
166
+ ),
167
+ deviceClass=MicrophoneDevice
168
+ )
154
169
 
155
170
  # Try and get the best match which are compatible with the user's
156
171
  # specified settings.
@@ -191,8 +206,11 @@ class MicrophoneDevice(BaseDevice, aliases=["mic", "microphone"]):
191
206
  if isinstance(device, MicrophoneDevice):
192
207
  self._device = device._device
193
208
  else:
194
- raise KeyError(
195
- f"Could not find MicrophoneDevice named {index}"
209
+ # if not found, find best match
210
+ self._device = self.findBestDevice(
211
+ index=index,
212
+ sampleRateHz=sampleRateHz,
213
+ channels=channels
196
214
  )
197
215
  else:
198
216
  # get best match
@@ -226,15 +244,13 @@ class MicrophoneDevice(BaseDevice, aliases=["mic", "microphone"]):
226
244
  self._sampleRateHz))
227
245
 
228
246
  # set the audio latency mode
229
- if audioLatencyMode is None:
230
- self._audioLatencyMode = int(prefs.hardware["audioLatencyMode"])
247
+ if exclusive:
248
+ self._audioLatencyMode = 2
231
249
  else:
232
- self._audioLatencyMode = audioLatencyMode
233
-
234
- logging.debug('Set audio latency mode to {}'.format(
235
- self._audioLatencyMode))
236
-
237
- assert 0 <= self._audioLatencyMode <= 4 # sanity check for pref
250
+ self._audioLatencyMode = 1
251
+ logging.debug(
252
+ 'Set audio latency mode to {}'.format(self._audioLatencyMode)
253
+ )
238
254
 
239
255
  # internal recording buffer size in seconds
240
256
  assert isinstance(streamBufferSecs, (float, int))
@@ -243,60 +259,28 @@ class MicrophoneDevice(BaseDevice, aliases=["mic", "microphone"]):
243
259
  # PTB specific stuff
244
260
  self._mode = 2 # open a stream in capture mode
245
261
 
246
- # Handle for the recording stream, should only be opened once per
247
- # session
248
- logging.debug('Opening audio stream for device #{}'.format(
249
- self._device.deviceIndex))
250
- if self._device.deviceIndex not in MicrophoneDevice._streams:
251
- MicrophoneDevice._streams[self._device.deviceIndex] = audio.Stream(
252
- device_id=self._device.deviceIndex,
253
- latency_class=self._audioLatencyMode,
254
- mode=self._mode,
255
- freq=self._device.defaultSampleRate,
256
- channels=self._device.inputChannels)
257
- logging.debug('Stream opened')
258
- else:
259
- logging.debug(
260
- "Stream already created for device at index {}, using created stream.".format(
261
- self._device.deviceIndex
262
- )
263
- )
264
-
265
- # store reference to stream in this instance
266
- self._stream = MicrophoneDevice._streams[self._device.deviceIndex]
267
-
262
+ # get audio run mode
268
263
  assert isinstance(audioRunMode, (float, int)) and \
269
264
  (audioRunMode == 0 or audioRunMode == 1)
270
265
  self._audioRunMode = int(audioRunMode)
271
- self._stream.run_mode = self._audioRunMode
272
-
273
- logging.debug('Set run mode to `{}`'.format(
274
- self._audioRunMode))
275
-
276
- # set latency bias
277
- self._stream.latency_bias = 0.0
278
-
279
- logging.debug('Set stream latency bias to {} ms'.format(
280
- self._stream.latency_bias))
281
-
282
- # pre-allocate recording buffer, called once
283
- self._stream.get_audio_data(self._streamBufferSecs)
284
266
 
285
- logging.debug(
286
- 'Allocated stream buffer to hold {} seconds of data'.format(
287
- self._streamBufferSecs))
267
+ # open stream
268
+ self._stream = None
269
+ self._opening = self._closing = False
270
+ self.open()
288
271
 
289
272
  # status flag for Builder
290
273
  self._statusFlag = NOT_STARTED
291
274
 
292
- # setup recording buffer
293
- self._recording = RecordingBuffer(
294
- sampleRateHz=self._sampleRateHz,
295
- channels=self._channels,
296
- maxRecordingSize=maxRecordingSize,
297
- policyWhenFull=policyWhenFull
298
- )
275
+ # recording buffer information
276
+ self._recording = [] # use a list
277
+ self._totalSamples = 0
278
+ self._maxRecordingSize = (
279
+ -1 if maxRecordingSize is None else int(maxRecordingSize))
280
+ self._policyWhenFull = policyWhenFull
299
281
 
282
+ # internal state
283
+ self._possiblyAsleep = False
300
284
  self._isStarted = False # internal state
301
285
 
302
286
  logging.debug('Audio capture device #{} ready'.format(
@@ -304,6 +288,46 @@ class MicrophoneDevice(BaseDevice, aliases=["mic", "microphone"]):
304
288
 
305
289
  # list to store listeners in
306
290
  self.listeners = []
291
+
292
+ @property
293
+ def maxRecordingSize(self):
294
+ """
295
+ Until a file is saved, the audio data from a Microphone needs to be stored in RAM. To avoid
296
+ a memory leak, we limit the amount which can be stored by a single Microphone object. The
297
+ `maxRecordingSize` parameter defines what this limit is.
298
+
299
+ Parameters
300
+ ----------
301
+ value : int
302
+ How much data (in kb) to allow, default is 24mb (so 24,000kb)
303
+ """
304
+ return self._maxRecordingSize
305
+
306
+ @maxRecordingSize.setter
307
+ def maxRecordingSize(self, value):
308
+ self._maxRecordingSize = int(value)
309
+
310
+ @property
311
+ def policyWhenFull(self):
312
+ """
313
+ Until a file is saved, the audio data from a Microphone needs to be stored in RAM. To avoid
314
+ a memory leak, we limit the amount which can be stored by a single Microphone object. The
315
+ `policyWhenFull` parameter tells the Microphone what to do when it's reached that limit.
316
+
317
+ Parameters
318
+ ----------
319
+ value : str
320
+ One of:
321
+ - "ignore": When full, just don't record any new samples
322
+ - "warn"/"warning": Same as ignore, but will log a warning
323
+ - "error": When full, will raise an error
324
+ - "roll"/"rolling": When full, clears the start of the buffer to make room for new samples
325
+ """
326
+ return self._policyWhenFull
327
+
328
+ @policyWhenFull.setter
329
+ def policyWhenFull(self, value):
330
+ self._policyWhenFull = value
307
331
 
308
332
  def findBestDevice(self, index, sampleRateHz, channels):
309
333
  """
@@ -337,11 +361,11 @@ class MicrophoneDevice(BaseDevice, aliases=["mic", "microphone"]):
337
361
  # iterate through device profiles
338
362
  for profile in self.getDevices():
339
363
  # if same index, keep as fallback
340
- if profile.deviceIndex == index:
364
+ if index in (profile.deviceIndex, profile.deviceName):
341
365
  fallbackDevice = profile
342
366
  # if same everything, we got it!
343
367
  if all((
344
- profile.deviceIndex == index,
368
+ index in (profile.deviceIndex, profile.deviceName),
345
369
  profile.defaultSampleRate == sampleRateHz,
346
370
  profile.inputChannels == channels,
347
371
  )):
@@ -353,14 +377,18 @@ class MicrophoneDevice(BaseDevice, aliases=["mic", "microphone"]):
353
377
  f"Could not find exact match for specified parameters (index={index}, sampleRateHz="
354
378
  f"{sampleRateHz}, channels={channels}), falling back to best approximation ("
355
379
  f"index={fallbackDevice.deviceIndex}, "
380
+ f"name={fallbackDevice.deviceName},"
356
381
  f"sampleRateHz={fallbackDevice.defaultSampleRate}, "
357
382
  f"channels={fallbackDevice.inputChannels})"
358
383
  )
359
384
  chosenDevice = fallbackDevice
360
385
  elif chosenDevice is None:
361
386
  # if no index match found, raise error
362
- raise KeyError(
363
- f"Could not find any device with index {index}"
387
+ raise DeviceNotConnectedError(
388
+ _translate(
389
+ "Could not find any audio recording device with index {index}",
390
+ ).format(index=index),
391
+ deviceClass=MicrophoneDevice
364
392
  )
365
393
 
366
394
  return chosenDevice
@@ -391,7 +419,7 @@ class MicrophoneDevice(BaseDevice, aliases=["mic", "microphone"]):
391
419
  # if the other object is the wrong type or doesn't have an index, it's not this
392
420
  return False
393
421
 
394
- return self.index == index
422
+ return index in (self.index, self._device.deviceName)
395
423
 
396
424
  @staticmethod
397
425
  def getDevices():
@@ -429,9 +457,13 @@ class MicrophoneDevice(BaseDevice, aliases=["mic", "microphone"]):
429
457
  def getAvailableDevices():
430
458
  devices = []
431
459
  for profile in st.getAudioCaptureDevices():
460
+ # get index as a name if possible
461
+ index = profile.get('device_name', None)
462
+ if index is None:
463
+ index = profile.get('index', None)
432
464
  device = {
433
465
  'deviceName': profile.get('device_name', "Unknown Microphone"),
434
- 'index': profile.get('index', None),
466
+ 'index': index,
435
467
  'sampleRateHz': profile.get('defaultSampleRate', None),
436
468
  'channels': profile.get('inputChannels', None),
437
469
  }
@@ -454,6 +486,24 @@ class MicrophoneDevice(BaseDevice, aliases=["mic", "microphone"]):
454
486
  # self._stream.start()
455
487
  # self._stream.stop()
456
488
 
489
+ @property
490
+ def channels(self):
491
+ """Number of audio channels to record samples to (`int`).
492
+ """
493
+ return self._channels
494
+
495
+ @property
496
+ def sampleRateHz(self):
497
+ """Sampling rate for audio recording in Hertz (`int`).
498
+ """
499
+ return self._sampleRateHz
500
+
501
+ @property
502
+ def device(self):
503
+ """Audio device descriptor (`AudioDeviceInfo`).
504
+ """
505
+ return self._device
506
+
457
507
  @property
458
508
  def recording(self):
459
509
  """Reference to the current recording buffer (`RecordingBuffer`)."""
@@ -462,28 +512,7 @@ class MicrophoneDevice(BaseDevice, aliases=["mic", "microphone"]):
462
512
  @property
463
513
  def recBufferSecs(self):
464
514
  """Capacity of the recording buffer in seconds (`float`)."""
465
- return self.recording.bufferSecs
466
-
467
- @property
468
- def maxRecordingSize(self):
469
- """Maximum recording size in kilobytes (`int`).
470
-
471
- Since audio recordings tend to consume a large amount of system memory,
472
- one might want to limit the size of the recording buffer to ensure that
473
- the application does not run out. By default, the recording buffer is
474
- set to 64000 KB (or 64 MB). At a sample rate of 48kHz, this will result
475
- in about. Using stereo audio (``nChannels == 2``) requires twice the
476
- buffer over mono (``nChannels == 2``) for the same length clip.
477
-
478
- Setting this value will allocate another recording buffer of appropriate
479
- size. Avoid doing this in any time sensitive parts of your application.
480
-
481
- """
482
- return self._recording.maxRecordingSize
483
-
484
- @maxRecordingSize.setter
485
- def maxRecordingSize(self, value):
486
- self._recording.maxRecordingSize = value
515
+ return self._totalSamples / float(self._sampleRateHz)
487
516
 
488
517
  @property
489
518
  def latencyBias(self):
@@ -550,7 +579,7 @@ class MicrophoneDevice(BaseDevice, aliases=["mic", "microphone"]):
550
579
  lost.
551
580
 
552
581
  """
553
- return self._recording.isFull
582
+ return self._totalSamples >= self._maxRecordingSize
554
583
 
555
584
  @property
556
585
  def isStarted(self):
@@ -645,9 +674,11 @@ class MicrophoneDevice(BaseDevice, aliases=["mic", "microphone"]):
645
674
 
646
675
  if self._stream is None:
647
676
  raise AudioStreamError("Stream not ready.")
648
-
649
- # reset the writing 'head'
650
- self._recording.seek(0, absolute=True)
677
+ # reset timer for possibly asleep
678
+ self._possiblyAsleep = False
679
+ # reset the recording buffer
680
+ self._recording = []
681
+ self._totalSamples = 0
651
682
 
652
683
  # reset warnings
653
684
  # self._warnedRecBufferFull = False
@@ -722,12 +753,11 @@ class MicrophoneDevice(BaseDevice, aliases=["mic", "microphone"]):
722
753
  """
723
754
  # This function must be idempotent since it can be invoked at any time
724
755
  # whether a stream is started or not.
725
- if not self.isStarted:
756
+ if not self.isStarted or self._stream._closed:
726
757
  return
727
758
 
728
759
  # poll remaining samples, if any
729
- if not self.isRecBufferFull:
730
- self.poll()
760
+ self.poll()
731
761
 
732
762
  startTime, endPositionSecs, xruns, estStopTime = self._stream.stop(
733
763
  block_until_stopped=int(blockUntilStopped),
@@ -766,16 +796,103 @@ class MicrophoneDevice(BaseDevice, aliases=["mic", "microphone"]):
766
796
  """
767
797
  return self.stop(blockUntilStopped=blockUntilStopped, stopTime=stopTime)
768
798
 
799
+ def open(self):
800
+ """
801
+ Open the audio stream.
802
+ """
803
+ # do nothing if stream is already open
804
+ if self._stream is not None and not self._stream._closed:
805
+ return
806
+ # set flag that it's mid-open
807
+ self._opening = True
808
+ # search for open streams and if there is one, use it
809
+ if self._device.deviceIndex in MicrophoneDevice._streams:
810
+ logging.debug(
811
+ f"Assigning audio stream for device #{self._device.deviceIndex} to a new "
812
+ f"MicrophoneDevice object."
813
+ )
814
+ self._stream = MicrophoneDevice._streams[self._device.deviceIndex]
815
+ return
816
+
817
+ # if no open streams, make one
818
+ logging.debug(
819
+ f"Opening new audio stream for device #{self._device.deviceIndex}."
820
+ )
821
+ self._stream = MicrophoneDevice._streams[self._device.deviceIndex] = audio.Stream(
822
+ device_id=self._device.deviceIndex,
823
+ latency_class=self._audioLatencyMode,
824
+ mode=self._mode,
825
+ freq=self._device.defaultSampleRate,
826
+ channels=self._device.inputChannels
827
+ )
828
+ # set run mode
829
+ self._stream.run_mode = self._audioRunMode
830
+ logging.debug('Set run mode to `{}`'.format(
831
+ self._audioRunMode))
832
+ # set latency bias
833
+ self._stream.latency_bias = 0.0
834
+ logging.debug('Set stream latency bias to {} ms'.format(
835
+ self._stream.latency_bias))
836
+ # pre-allocate recording buffer, called once
837
+ self._stream.get_audio_data(self._streamBufferSecs)
838
+ logging.debug(
839
+ 'Allocated stream buffer to hold {} seconds of data'.format(
840
+ self._streamBufferSecs))
841
+ # set flag that it's done opening
842
+ self._opening = False
843
+
769
844
  def close(self):
770
- """Close the stream.
771
-
772
- Should not be called until you are certain you're done with it. Ideally,
773
- you should never close and reopen the same stream within a single
774
- session.
775
-
776
845
  """
846
+ Close the audio stream.
847
+ """
848
+ # clear any attached listeners
849
+ self.clearListeners()
850
+ # do nothing further if already closed
851
+ if self._stream._closed:
852
+ return
853
+ # set flag that it's mid-close
854
+ self._closing = True
855
+ # remove ref to stream
856
+ if self._device.deviceIndex in MicrophoneDevice._streams:
857
+ MicrophoneDevice._streams.pop(self._device.deviceIndex)
858
+ # close stream
777
859
  self._stream.close()
778
860
  logging.debug('Stream closed')
861
+ # set flag that it's done closing
862
+ self._closing = False
863
+
864
+ def reopen(self):
865
+ """
866
+ Calls self.close() then self.open() to reopen the stream.
867
+ """
868
+ # get status at close
869
+ status = self.isStarted
870
+ # start timer
871
+ start = time.time()
872
+ # close then open
873
+ self.close()
874
+ self.open()
875
+ # log time it took
876
+ logging.info(
877
+ f"Reopened microphone #{self.index}, took {time.time() - start:.3f}s"
878
+ )
879
+ # if mic was running beforehand, start it back up again now
880
+ if status:
881
+ self.start()
882
+
883
+ @property
884
+ def recordingEmpty(self):
885
+ """`True` if the recording buffer is empty (`bool`).
886
+ """
887
+ return len(self._recording) == 0
888
+
889
+ @property
890
+ def recordingFull(self):
891
+ """`True` if the recording buffer is full (`bool`).
892
+ """
893
+ if self._maxRecordingSize < 0:
894
+ return False
895
+ return self._totalSamples >= self._maxRecordingSize
779
896
 
780
897
  def poll(self):
781
898
  """Poll audio samples.
@@ -796,12 +913,46 @@ class MicrophoneDevice(BaseDevice, aliases=["mic", "microphone"]):
796
913
 
797
914
  """
798
915
  if not self.isStarted:
799
- raise AudioStreamError(
800
- "Cannot poll samples from audio device, not started.")
916
+ logging.warning(
917
+ "Attempted to poll samples from mic which hasn't started."
918
+ )
919
+ return
920
+ if self._stream._closed:
921
+ logging.warning(
922
+ "Attempted to poll samples from mic which has been closed."
923
+ )
924
+ return
925
+ if self._opening or self._closing:
926
+ action = "opening" if self._opening else "closing"
927
+ logging.warning(
928
+ f"Attempted to poll microphone while the stream was still {action}. Samples will be "
929
+ f"lost."
930
+ )
931
+ return
801
932
 
802
933
  # figure out what to do with this other information
803
934
  audioData, absRecPosition, overflow, cStartTime = \
804
935
  self._stream.get_audio_data()
936
+
937
+ if len(audioData):
938
+ # if we got samples, the device is awake, so stop figuring out if it's asleep
939
+ self._possiblyAsleep = False
940
+ elif self._possiblyAsleep is False:
941
+ # if it was awake and now we've got no samples, store the time
942
+ self._possiblyAsleep = time.time()
943
+ elif self._possiblyAsleep + 1 < time.time():
944
+ # if we've not had any evidence of it being awake for 1s, reopen
945
+ logging.error(
946
+ f"Microphone device appears to have gone to sleep, reopening to wake it up."
947
+ )
948
+ # mark as stopped so we don't recursively poll forever when stopping
949
+ self._isStarted = False
950
+ # reopen
951
+ self.reopen()
952
+ # start again
953
+ self.start()
954
+ # mark as not asleep so we don't restart again if the first poll is empty
955
+ self._possiblyAsleep = False
805
956
 
806
957
  if overflow:
807
958
  logging.warning(
@@ -810,9 +961,102 @@ class MicrophoneDevice(BaseDevice, aliases=["mic", "microphone"]):
810
961
  "called often enough, or increase the size of the audio buffer "
811
962
  "with `bufferSecs`.")
812
963
 
813
- overruns = self._recording.write(audioData)
964
+ # add samples to recording buffer
965
+ if len(audioData):
966
+ # add samples to recording buffer
967
+ self._recording.append(
968
+ AudioClip(audioData, sampleRateHz=self._sampleRateHz))
969
+ self._totalSamples += audioData.shape[0]
970
+
971
+ if self.recordingFull and not self._policyWhenFull == 'ignore':
972
+ if self._policyWhenFull == 'warn':
973
+ logging.warning(
974
+ "Recording buffer is full, no more samples will be added.")
975
+ elif self._policyWhenFull == 'error':
976
+ raise AudioStreamError(
977
+ "Recording buffer is full, no more samples will be added.")
978
+
979
+ return 0
980
+
981
+ def _mergeAudioFragments(self):
982
+ """Merge audio fragments into a single segment.
983
+
984
+ This merges all audio fragments in the recoding buffer into a single
985
+ `AudioClip` object. The recording buffer is then cleared and the first
986
+ element is set to the merged segment.
987
+
988
+ Returns
989
+ -------
990
+ bool
991
+ `False` if the recording buffer has no fragments to merge, `True`
992
+ otherwise.
993
+
994
+ """
995
+ if len(self._recording) < 2:
996
+ return False
997
+
998
+ # get the sum of all audio samples in the recording buffer
999
+ totalSamples = sum(
1000
+ [segment.samples.shape[0] for segment in self._recording])
1001
+
1002
+ # create a new array to hold all samples
1003
+ fullSegment = np.zeros(
1004
+ (totalSamples, self._recording[0].channels),
1005
+ dtype=np.float32,
1006
+ order='C')
1007
+
1008
+ # copy samples from each segment into the full segment
1009
+ idx = 0
1010
+ for segment in self._recording:
1011
+ nSamples = segment.samples.shape[0]
1012
+ fullSegment[idx:idx + nSamples, :] = segment.samples
1013
+ idx += nSamples
1014
+
1015
+ # set the recording
1016
+ self._recording = [
1017
+ AudioClip(fullSegment, sampleRateHz=self._sampleRateHz)]
1018
+
1019
+ return True
1020
+
1021
+ def _getSegment(self, start=0, end=None):
1022
+ """Get a segment of audio samples from the recording buffer.
1023
+
1024
+ Parameters
1025
+ ----------
1026
+ start : float
1027
+ Start time of the segment in seconds.
1028
+ end : float or None
1029
+ End time of the segment in seconds. If `None`, the segment will
1030
+ extend to the end of the recording buffer.
814
1031
 
815
- return overruns
1032
+ Returns
1033
+ -------
1034
+ AudioClip or None
1035
+ Segment of the recording buffer. Returns `None` if the recording
1036
+ buffer is empty.
1037
+
1038
+ """
1039
+ if self.recordingEmpty:
1040
+ return None
1041
+
1042
+ self._mergeAudioFragments() # merge audio fragments
1043
+
1044
+ if not len(self._recording[0].samples):
1045
+ raise AudioStreamError(
1046
+ "Could not access recording as microphone has sent no samples."
1047
+ )
1048
+
1049
+ if start == 0 and end is None: # return full recording
1050
+ return self._recording[0]
1051
+
1052
+ # get a range of samples within the recording buffer
1053
+ idxStart = int(start * self._sampleRateHz)
1054
+ idxEnd = -1 if end is None else int(end * self._sampleRateHz)
1055
+
1056
+ return AudioClip(
1057
+ np.array(self._recording[0].samples[idxStart:idxEnd, :],
1058
+ dtype=np.float32, order='C'),
1059
+ sampleRateHz=self._sampleRateHz)
816
1060
 
817
1061
  def getRecording(self):
818
1062
  """Get audio data from the last microphone recording.
@@ -828,12 +1072,15 @@ class MicrophoneDevice(BaseDevice, aliases=["mic", "microphone"]):
828
1072
 
829
1073
  """
830
1074
  if self.isStarted:
831
- raise AudioStreamError(
832
- "Cannot get audio clip, recording was in progress. Be sure to "
833
- "call `Microphone.stop` first.")
834
-
835
- return self._recording.getSegment() # full recording
1075
+ logging.warn(
1076
+ "Cannot get audio clip while recording is in progress, so "
1077
+ "stopping recording now."
1078
+ )
1079
+ self.stop()
836
1080
 
1081
+ # get the segment
1082
+ return self._getSegment() # full recording
1083
+
837
1084
  def getCurrentVolume(self, timeframe=0.2):
838
1085
  """
839
1086
  Get the current volume measured by the mic.
@@ -846,20 +1093,45 @@ class MicrophoneDevice(BaseDevice, aliases=["mic", "microphone"]):
846
1093
  Returns
847
1094
  -------
848
1095
  float
849
- Current volume registered by the mic, will depend on relative volume of the mic but
850
- should mostly be between 0 (total silence) and 1 (very loud).
1096
+ Current volume registered by the mic, will depend on relative volume
1097
+ of the mic but should mostly be between 0 (total silence) and 1
1098
+ (very loud).
1099
+
851
1100
  """
852
1101
  # if mic hasn't started yet, return 0 as it's recorded nothing
853
- if not self.isStarted:
1102
+ if not self.isStarted or self._stream._closed:
854
1103
  return 0
1104
+
855
1105
  # poll most recent samples
856
1106
  self.poll()
857
- # get last 0.1sas a clip
858
- clip = self._recording.getSegment(
859
- max(self._recording.lastSample / self._sampleRateHz - timeframe, 0)
860
- )
861
1107
 
862
- return clip.rms() * 10
1108
+ if self.recordingEmpty:
1109
+ return 0.0
1110
+
1111
+ # merge last few recording fragments into a single segment
1112
+ requiredSamples = int(timeframe * self._sampleRateHz)
1113
+ sampleBuffers = []
1114
+ for segment in reversed(self._recording):
1115
+ nSamples = segment.samples.shape[0]
1116
+ requiredSamples -= nSamples
1117
+ if requiredSamples < 0:
1118
+ sampleBuffers.insert(0, segment.samples[requiredSamples:, :])
1119
+ break
1120
+ sampleBuffers.insert(0, segment.samples)
1121
+
1122
+ # merge the samples
1123
+ sampleBuffer = np.concatenate(
1124
+ sampleBuffers, axis=0, dtype=np.float32)
1125
+
1126
+ clip = AudioClip(sampleBuffer, sampleRateHz=self._sampleRateHz)
1127
+
1128
+ # get average volume
1129
+ rms = clip.rms() * 10
1130
+
1131
+ # round
1132
+ rms = np.round(rms.astype(np.float64), decimals=3)
1133
+
1134
+ return rms
863
1135
 
864
1136
  def addListener(self, listener, startLoop=False):
865
1137
  """
@@ -876,10 +1148,12 @@ class MicrophoneDevice(BaseDevice, aliases=["mic", "microphone"]):
876
1148
  If True, then upon adding the listener, start up an asynchronous loop to dispatch messages.
877
1149
  """
878
1150
  # add listener as normal
879
- BaseResponseDevice.addListener(self, listener, startLoop=startLoop)
1151
+ listener = BaseResponseDevice.addListener(self, listener, startLoop=startLoop)
880
1152
  # if we're starting a listener loop, start recording
881
1153
  if startLoop:
882
1154
  self.start()
1155
+
1156
+ return listener
883
1157
 
884
1158
  def clearListeners(self):
885
1159
  """
@@ -891,10 +1165,12 @@ class MicrophoneDevice(BaseDevice, aliases=["mic", "microphone"]):
891
1165
  True if completed successfully
892
1166
  """
893
1167
  # clear listeners as normal
894
- BaseResponseDevice.clearListeners(self)
1168
+ resp = BaseResponseDevice.clearListeners(self)
895
1169
  # stop recording
896
1170
  self.stop()
897
1171
 
1172
+ return resp
1173
+
898
1174
  def dispatchMessages(self, clear=True):
899
1175
  """
900
1176
  Dispatch current volume as a MicrophoneResponse object to any attached listeners.
@@ -905,27 +1181,112 @@ class MicrophoneDevice(BaseDevice, aliases=["mic", "microphone"]):
905
1181
  If True, will clear the recording up until now after dispatching the volume. This is
906
1182
  useful if you're just sampling volume and aren't wanting to store the recording.
907
1183
  """
1184
+ # if mic is not recording, there's nothing to dispatch
1185
+ if not self.isStarted:
1186
+ return
1187
+
1188
+ # poll the mic now
1189
+ self.poll()
908
1190
  # create a response object
909
1191
  message = MicrophoneResponse(
910
1192
  logging.defaultClock.getTime(),
911
- self.getCurrentVolume()
1193
+ self.getCurrentVolume(),
1194
+ device=self,
912
1195
  )
913
- # clear recording if requested (helps with continuous running)
914
- if clear and self.isRecBufferFull:
915
- # work out how many samples is 0.1s
916
- toSave = min(
917
- int(0.2 * self._sampleRateHz),
918
- int(self.maxRecordingSize / 2)
919
- )
920
- # get last 0.1s so we still have enough for volume measurement
921
- savedSamples = self._recording._samples[-toSave:, :]
922
- # clear samples
923
- self._recording.clear()
924
- # reassign saved samples
925
- self._recording.write(savedSamples)
926
1196
  # dispatch to listeners
927
1197
  for listener in self.listeners:
928
1198
  listener.receiveMessage(message)
1199
+
1200
+ return message
1201
+
1202
+ def findSpeakers(self, allowedSpeakers=None, threshold=0.01):
1203
+ """
1204
+ Find speakers which this microphone can hear.
1205
+
1206
+ Parameters
1207
+ ----------
1208
+ allowedSpeakers : list[SpeakerDevice or dict] or None
1209
+ List of speakers to test, or leave as None to test all speakers. If speakers are given
1210
+ as a dict, SpeakerDevice objects will be created via DeviceManager.
1211
+ threshold : float
1212
+ Necessary difference in volume (dB) between sound playing and not playing to conclude
1213
+ that this mic can hear the given speaker.
1214
+
1215
+ Returns
1216
+ -------
1217
+ list[SpeakerDevice]
1218
+ List of speakers which this MicrophoneDevice can hear
1219
+ """
1220
+ from psychopy import sound
1221
+ from psychopy.hardware import DeviceManager
1222
+
1223
+ def _takeReading(dur):
1224
+ """
1225
+ Take a reading from this MicrophoneDevice and return the average volume.
1226
+
1227
+ Parameters
1228
+ ----------
1229
+ dur : float
1230
+ Time (s) to read for
1231
+
1232
+ Returns
1233
+ -------
1234
+ float
1235
+ Average volume across samples and channels during the reading
1236
+ """
1237
+ # countdown for the duration
1238
+ countdown = core.CountdownTimer(dur)
1239
+ # start recording
1240
+ self.start()
1241
+ # poll while active
1242
+ while countdown.getTime() > 0:
1243
+ self.poll()
1244
+ # get volume
1245
+ vol = self.getCurrentVolume(timeframe=dur)
1246
+ # if multi-channel, take the max
1247
+ try:
1248
+ vol = max(vol)
1249
+ except TypeError:
1250
+ pass
1251
+ # stop recording
1252
+ self.stop()
1253
+
1254
+ return vol
1255
+
1256
+ # if no allowed speakers given, use all
1257
+ if allowedSpeakers is None:
1258
+ allowedSpeakers = DeviceManager.getAvailableDevices(
1259
+ "psychopy.hardware.speaker.SpeakerDevice"
1260
+ )
1261
+ # list of found speakers
1262
+ foundSpeakers = []
1263
+ # iterate through allowed speakers
1264
+ for speaker in allowedSpeakers:
1265
+ # if given a dict, actualise it
1266
+ if isinstance(speaker, dict):
1267
+ speakerProfile = speaker
1268
+ speaker = DeviceManager.getDevice(speakerProfile['deviceName'])
1269
+ if speaker is None:
1270
+ speaker = DeviceManager.addDevice(**speakerProfile)
1271
+ # generate a sound for this speaker
1272
+ try:
1273
+ snd = sound.Sound("A", stereo=True, speaker=speaker)
1274
+ except:
1275
+ # silently skip on error
1276
+ continue
1277
+ # get a baseline volume
1278
+ baseline = _takeReading(1)
1279
+ # start playing a beep
1280
+ snd.play()
1281
+ # get an active volume
1282
+ active = _takeReading(1)
1283
+ # stop the beep
1284
+ snd.stop()
1285
+ # if the difference is above the threshold, speaker is good
1286
+ if active - baseline > threshold:
1287
+ foundSpeakers.append(speaker)
1288
+
1289
+ return foundSpeakers
929
1290
 
930
1291
 
931
1292
  class RecordingBuffer:
@@ -962,20 +1323,16 @@ class RecordingBuffer:
962
1323
 
963
1324
  """
964
1325
  def __init__(self, sampleRateHz=SAMPLE_RATE_48kHz, channels=2,
965
- maxRecordingSize=24000, policyWhenFull='ignore'):
1326
+ maxRecordingSize=None, policyWhenFull='ignore'):
966
1327
  self._channels = channels
967
1328
  self._sampleRateHz = sampleRateHz
968
- self._maxRecordingSize = maxRecordingSize
1329
+ self._maxRecordingSize = maxRecordingSize or 64000
969
1330
  self._samples = None # `ndarray` created in _allocRecBuffer`
970
1331
  self._offset = 0 # recording offset
971
1332
  self._lastSample = 0 # offset of the last sample from stream
972
1333
  self._spaceRemaining = None # set in `_allocRecBuffer`
973
1334
  self._totalSamples = None # set in `_allocRecBuffer`
974
1335
 
975
- # check if the value is valid
976
- if policyWhenFull not in ['ignore', 'warn', 'error']:
977
- raise ValueError("Invalid value for `policyWhenFull`.")
978
-
979
1336
  self._policyWhenFull = policyWhenFull
980
1337
  self._warnedRecBufferFull = False
981
1338
  self._loops = 0
@@ -1126,9 +1483,8 @@ class RecordingBuffer:
1126
1483
  """
1127
1484
  nSamples = len(samples)
1128
1485
  if self.isFull:
1129
- if self._policyWhenFull == 'ignore':
1130
- return nSamples # samples lost
1131
- elif self._policyWhenFull == 'warn':
1486
+ if self._policyWhenFull in ('warn', 'warning'):
1487
+ # if policy is warn, we log a warning then proceed as if ignored
1132
1488
  if not self._warnedRecBufferFull:
1133
1489
  logging.warning(
1134
1490
  f"Audio recording buffer filled! This means that no "
@@ -1139,10 +1495,29 @@ class RecordingBuffer:
1139
1495
  self._warnedRecBufferFull = True
1140
1496
  return nSamples
1141
1497
  elif self._policyWhenFull == 'error':
1498
+ # if policy is error, we fully error
1142
1499
  raise AudioRecordingBufferFullError(
1143
1500
  "Cannot write samples, recording buffer is full.")
1501
+ elif self._policyWhenFull == ('rolling', 'roll'):
1502
+ # if policy is rolling, we clear the first half of the buffer
1503
+ toSave = self._totalSamples - len(samples)
1504
+ # get last 0.1s so we still have enough for volume measurement
1505
+ savedSamples = self._recording._samples[-toSave:, :]
1506
+ # log
1507
+ if not self._warnedRecBufferFull:
1508
+ logging.warning(
1509
+ f"Microphone buffer reached, as policy when full is 'roll'/'rolling' the "
1510
+ f"oldest samples will be cleared to make room for new samples."
1511
+ )
1512
+ logging.flush()
1513
+ self._warnedRecBufferFull = True
1514
+ # clear samples
1515
+ self._recording.clear()
1516
+ # reassign saved samples
1517
+ self._recording.write(savedSamples)
1144
1518
  else:
1145
- return nSamples # whatever
1519
+ # if policy is to ignore, we simply don't write new samples
1520
+ return nSamples
1146
1521
 
1147
1522
  if not nSamples: # no samples came out of the stream, just return
1148
1523
  return
@@ -1197,6 +1572,11 @@ class RecordingBuffer:
1197
1572
  idxStart = int(start * self._sampleRateHz)
1198
1573
  idxEnd = self._lastSample if end is None else int(
1199
1574
  end * self._sampleRateHz)
1575
+
1576
+ if not len(self._samples):
1577
+ raise AudioStreamError(
1578
+ "Could not access recording as microphone has sent no samples."
1579
+ )
1200
1580
 
1201
1581
  return AudioClip(
1202
1582
  np.array(self._samples[idxStart:idxEnd, :],