psychopy 2025.1.0__py3-none-any.whl → 2025.2.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 (226) hide show
  1. psychopy/VERSION +1 -1
  2. psychopy/alerts/alertsCatalogue/4810.yaml +19 -0
  3. psychopy/alerts/alertsCatalogue/alertCategories.yaml +4 -0
  4. psychopy/alerts/alertsCatalogue/alertmsg.py +15 -1
  5. psychopy/alerts/alertsCatalogue/generateAlertmsg.py +2 -2
  6. psychopy/app/Resources/classic/add_many.png +0 -0
  7. psychopy/app/Resources/classic/add_many@2x.png +0 -0
  8. psychopy/app/Resources/classic/devices.png +0 -0
  9. psychopy/app/Resources/classic/devices@2x.png +0 -0
  10. psychopy/app/Resources/classic/photometer.png +0 -0
  11. psychopy/app/Resources/classic/photometer@2x.png +0 -0
  12. psychopy/app/Resources/dark/add_many.png +0 -0
  13. psychopy/app/Resources/dark/add_many@2x.png +0 -0
  14. psychopy/app/Resources/dark/devices.png +0 -0
  15. psychopy/app/Resources/dark/devices@2x.png +0 -0
  16. psychopy/app/Resources/dark/photometer.png +0 -0
  17. psychopy/app/Resources/dark/photometer@2x.png +0 -0
  18. psychopy/app/Resources/light/add_many.png +0 -0
  19. psychopy/app/Resources/light/add_many@2x.png +0 -0
  20. psychopy/app/Resources/light/devices.png +0 -0
  21. psychopy/app/Resources/light/devices@2x.png +0 -0
  22. psychopy/app/Resources/light/photometer.png +0 -0
  23. psychopy/app/Resources/light/photometer@2x.png +0 -0
  24. psychopy/app/_psychopyApp.py +35 -13
  25. psychopy/app/builder/builder.py +88 -35
  26. psychopy/app/builder/dialogs/__init__.py +69 -220
  27. psychopy/app/builder/dialogs/dlgsCode.py +29 -8
  28. psychopy/app/builder/dialogs/paramCtrls.py +1468 -904
  29. psychopy/app/builder/validators.py +25 -17
  30. psychopy/app/coder/coder.py +12 -1
  31. psychopy/app/coder/repl.py +5 -2
  32. psychopy/app/colorpicker/__init__.py +1 -1
  33. psychopy/app/deviceManager/__init__.py +1 -0
  34. psychopy/app/deviceManager/addDialog.py +218 -0
  35. psychopy/app/deviceManager/dialog.py +185 -0
  36. psychopy/app/deviceManager/panel.py +191 -0
  37. psychopy/app/deviceManager/utils.py +60 -0
  38. psychopy/app/idle.py +7 -0
  39. psychopy/app/locale/ar_001/LC_MESSAGE/messages.mo +0 -0
  40. psychopy/app/locale/ar_001/LC_MESSAGE/messages.po +12695 -10592
  41. psychopy/app/locale/cs_CZ/LC_MESSAGE/messages.mo +0 -0
  42. psychopy/app/locale/cs_CZ/LC_MESSAGE/messages.po +10199 -24
  43. psychopy/app/locale/da_DK/LC_MESSAGE/messages.mo +0 -0
  44. psychopy/app/locale/da_DK/LC_MESSAGE/messages.po +10199 -24
  45. psychopy/app/locale/de_DE/LC_MESSAGE/messages.mo +0 -0
  46. psychopy/app/locale/de_DE/LC_MESSAGE/messages.po +11221 -9712
  47. psychopy/app/locale/el_GR/LC_MESSAGE/messages.mo +0 -0
  48. psychopy/app/locale/el_GR/LC_MESSAGE/messages.po +10200 -25
  49. psychopy/app/locale/en_NZ/LC_MESSAGE/messages.mo +0 -0
  50. psychopy/app/locale/en_NZ/LC_MESSAGE/messages.po +10200 -25
  51. psychopy/app/locale/en_US/LC_MESSAGE/messages.mo +0 -0
  52. psychopy/app/locale/en_US/LC_MESSAGE/messages.po +10195 -18
  53. psychopy/app/locale/es_CO/LC_MESSAGE/messages.mo +0 -0
  54. psychopy/app/locale/es_CO/LC_MESSAGE/messages.po +11917 -9101
  55. psychopy/app/locale/es_ES/LC_MESSAGE/messages.mo +0 -0
  56. psychopy/app/locale/es_ES/LC_MESSAGE/messages.po +11924 -9103
  57. psychopy/app/locale/es_US/LC_MESSAGE/messages.mo +0 -0
  58. psychopy/app/locale/es_US/LC_MESSAGE/messages.po +11917 -9101
  59. psychopy/app/locale/et_EE/LC_MESSAGE/messages.mo +0 -0
  60. psychopy/app/locale/et_EE/LC_MESSAGE/messages.po +11084 -9569
  61. psychopy/app/locale/fa_IR/LC_MESSAGE/messages.mo +0 -0
  62. psychopy/app/locale/fa_IR/LC_MESSAGE/messages.po +11590 -5806
  63. psychopy/app/locale/fi_FI/LC_MESSAGE/messages.mo +0 -0
  64. psychopy/app/locale/fi_FI/LC_MESSAGE/messages.po +10199 -24
  65. psychopy/app/locale/fr_FR/LC_MESSAGE/messages.mo +0 -0
  66. psychopy/app/locale/fr_FR/LC_MESSAGE/messages.po +11091 -9577
  67. psychopy/app/locale/he_IL/LC_MESSAGE/messages.mo +0 -0
  68. psychopy/app/locale/he_IL/LC_MESSAGE/messages.po +11072 -9549
  69. psychopy/app/locale/hi_IN/LC_MESSAGE/messages.mo +0 -0
  70. psychopy/app/locale/hi_IN/LC_MESSAGE/messages.po +11071 -9559
  71. psychopy/app/locale/hu_HU/LC_MESSAGE/messages.mo +0 -0
  72. psychopy/app/locale/hu_HU/LC_MESSAGE/messages.po +10200 -25
  73. psychopy/app/locale/it_IT/LC_MESSAGE/messages.mo +0 -0
  74. psychopy/app/locale/it_IT/LC_MESSAGE/messages.po +11072 -9560
  75. psychopy/app/locale/ja_JP/LC_MESSAGE/messages.mo +0 -0
  76. psychopy/app/locale/ja_JP/LC_MESSAGE/messages.po +1485 -1137
  77. psychopy/app/locale/ko_KR/LC_MESSAGE/messages.mo +0 -0
  78. psychopy/app/locale/ko_KR/LC_MESSAGE/messages.po +10199 -24
  79. psychopy/app/locale/ms_MY/LC_MESSAGE/messages.mo +0 -0
  80. psychopy/app/locale/ms_MY/LC_MESSAGE/messages.po +11463 -8757
  81. psychopy/app/locale/nl_NL/LC_MESSAGE/messages.mo +0 -0
  82. psychopy/app/locale/nl_NL/LC_MESSAGE/messages.po +10200 -25
  83. psychopy/app/locale/nn_NO/LC_MESSAGE/messages.mo +0 -0
  84. psychopy/app/locale/nn_NO/LC_MESSAGE/messages.po +10200 -25
  85. psychopy/app/locale/pl_PL/LC_MESSAGE/messages.mo +0 -0
  86. psychopy/app/locale/pl_PL/LC_MESSAGE/messages.po +10200 -25
  87. psychopy/app/locale/pt_PT/LC_MESSAGE/messages.mo +0 -0
  88. psychopy/app/locale/pt_PT/LC_MESSAGE/messages.po +11288 -9434
  89. psychopy/app/locale/ro_RO/LC_MESSAGE/messages.mo +0 -0
  90. psychopy/app/locale/ro_RO/LC_MESSAGE/messages.po +10200 -25
  91. psychopy/app/locale/ru_RU/LC_MESSAGE/messages.mo +0 -0
  92. psychopy/app/locale/ru_RU/LC_MESSAGE/messages.po +10199 -24
  93. psychopy/app/locale/sv_SE/LC_MESSAGE/messages.mo +0 -0
  94. psychopy/app/locale/sv_SE/LC_MESSAGE/messages.po +11441 -8747
  95. psychopy/app/locale/tr_TR/LC_MESSAGE/messages.mo +0 -0
  96. psychopy/app/locale/tr_TR/LC_MESSAGE/messages.po +11069 -9545
  97. psychopy/app/locale/zh_CN/LC_MESSAGE/messages.mo +0 -0
  98. psychopy/app/locale/zh_CN/LC_MESSAGE/messages.po +12085 -8268
  99. psychopy/app/locale/zh_TW/LC_MESSAGE/messages.mo +0 -0
  100. psychopy/app/locale/zh_TW/LC_MESSAGE/messages.po +11929 -8022
  101. psychopy/app/plugin_manager/dialog.py +12 -3
  102. psychopy/app/plugin_manager/packageIndex.py +303 -0
  103. psychopy/app/plugin_manager/packages.py +203 -63
  104. psychopy/app/plugin_manager/plugins.py +120 -240
  105. psychopy/app/preferencesDlg.py +6 -1
  106. psychopy/app/psychopyApp.py +16 -4
  107. psychopy/app/runner/runner.py +10 -2
  108. psychopy/app/runner/scriptProcess.py +8 -3
  109. psychopy/app/stdout/stdOutRich.py +11 -4
  110. psychopy/app/themes/icons.py +3 -0
  111. psychopy/app/utils.py +61 -0
  112. psychopy/colors.py +10 -5
  113. psychopy/data/experiment.py +133 -23
  114. psychopy/data/routine.py +12 -0
  115. psychopy/data/staircase.py +42 -20
  116. psychopy/data/trial.py +20 -12
  117. psychopy/data/utils.py +43 -3
  118. psychopy/demos/builder/Experiments/dragAndDrop/drag_and_drop.psyexp +22 -5
  119. psychopy/demos/builder/Experiments/dragAndDrop/stimuli/solutions.xlsx +0 -0
  120. psychopy/demos/builder/Experiments/stroopVoice/stroopVoice.psyexp +2 -12
  121. psychopy/demos/builder/Feature Demos/buttonBox/buttonBoxDemo.psyexp +3 -8
  122. psychopy/demos/builder/Feature Demos/movies/movie.psyexp +220 -0
  123. psychopy/demos/builder/Feature Demos/movies/readme.md +3 -0
  124. psychopy/demos/builder/Feature Demos/visualValidator/visualValidator.psyexp +1 -2
  125. psychopy/demos/builder/Hardware/camera/camera.psyexp +3 -16
  126. psychopy/demos/builder/Hardware/microphone/microphone.psyexp +3 -16
  127. psychopy/demos/coder/hardware/hdf5_extract.py +133 -0
  128. psychopy/event.py +20 -15
  129. psychopy/experiment/_experiment.py +86 -10
  130. psychopy/experiment/components/__init__.py +3 -10
  131. psychopy/experiment/components/_base.py +9 -20
  132. psychopy/experiment/components/button/__init__.py +1 -1
  133. psychopy/experiment/components/buttonBox/__init__.py +50 -54
  134. psychopy/experiment/components/camera/__init__.py +137 -359
  135. psychopy/experiment/components/keyboard/__init__.py +17 -24
  136. psychopy/experiment/components/microphone/__init__.py +61 -110
  137. psychopy/experiment/components/movie/__init__.py +2 -3
  138. psychopy/experiment/components/serialOut/__init__.py +192 -93
  139. psychopy/experiment/components/settings/__init__.py +45 -27
  140. psychopy/experiment/components/sound/__init__.py +82 -73
  141. psychopy/experiment/components/soundsensor/__init__.py +43 -80
  142. psychopy/experiment/devices.py +303 -0
  143. psychopy/experiment/exports.py +20 -18
  144. psychopy/experiment/flow.py +7 -0
  145. psychopy/experiment/loops.py +47 -29
  146. psychopy/experiment/monitor.py +74 -0
  147. psychopy/experiment/params.py +48 -10
  148. psychopy/experiment/plugins.py +28 -108
  149. psychopy/experiment/py2js_transpiler.py +1 -1
  150. psychopy/experiment/routines/__init__.py +1 -1
  151. psychopy/experiment/routines/_base.py +59 -24
  152. psychopy/experiment/routines/audioValidator/__init__.py +19 -155
  153. psychopy/experiment/routines/visualValidator/__init__.py +25 -25
  154. psychopy/hardware/__init__.py +20 -57
  155. psychopy/hardware/button.py +15 -2
  156. psychopy/hardware/camera/__init__.py +2237 -1394
  157. psychopy/hardware/joystick/__init__.py +1 -1
  158. psychopy/hardware/keyboard.py +5 -8
  159. psychopy/hardware/listener.py +4 -1
  160. psychopy/hardware/manager.py +75 -35
  161. psychopy/hardware/microphone.py +53 -7
  162. psychopy/hardware/monitor.py +144 -0
  163. psychopy/hardware/photometer/__init__.py +156 -117
  164. psychopy/hardware/serialdevice.py +16 -2
  165. psychopy/hardware/soundsensor.py +4 -1
  166. psychopy/iohub/devices/deviceConfigValidation.py +2 -1
  167. psychopy/iohub/devices/eyetracker/hw/gazepoint/__init__.py +2 -2
  168. psychopy/iohub/devices/eyetracker/hw/gazepoint/gp3/__init__.py +1 -0
  169. psychopy/iohub/devices/eyetracker/hw/gazepoint/gp3/eyetracker.py +10 -0
  170. psychopy/iohub/devices/keyboard/darwin.py +8 -5
  171. psychopy/iohub/util/__init__.py +7 -8
  172. psychopy/localization/generateTranslationTemplate.py +208 -116
  173. psychopy/localization/messages.pot +4305 -3502
  174. psychopy/monitors/MonitorCenter.py +174 -74
  175. psychopy/plugins/__init__.py +6 -4
  176. psychopy/preferences/devices.py +80 -0
  177. psychopy/preferences/generateHints.py +2 -1
  178. psychopy/preferences/preferences.py +35 -11
  179. psychopy/scripts/psychopy-pkgutil.py +969 -0
  180. psychopy/scripts/psyexpCompile.py +1 -1
  181. psychopy/session.py +34 -38
  182. psychopy/sound/__init__.py +6 -260
  183. psychopy/sound/audioclip.py +164 -0
  184. psychopy/sound/backend_ptb.py +8 -0
  185. psychopy/sound/backend_pygame.py +10 -0
  186. psychopy/sound/backend_pysound.py +9 -0
  187. psychopy/sound/backends/__init__.py +0 -0
  188. psychopy/sound/microphone.py +3 -0
  189. psychopy/sound/sound.py +58 -0
  190. psychopy/tests/data/correctScript/python/correctNoiseStimComponent.py +1 -1
  191. psychopy/tests/data/duplicateHeaders.csv +2 -0
  192. psychopy/tests/test_app/test_builder/test_BuilderFrame.py +22 -7
  193. psychopy/tests/test_app/test_builder/test_CompileFromBuilder.py +0 -2
  194. psychopy/tests/test_data/test_utils.py +5 -1
  195. psychopy/tests/test_experiment/test_components/test_ButtonBoxComponent.py +22 -2
  196. psychopy/tests/test_hardware/test_ports.py +0 -12
  197. psychopy/tests/test_tools/test_stringtools.py +1 -1
  198. psychopy/tools/attributetools.py +12 -5
  199. psychopy/tools/fontmanager.py +17 -14
  200. psychopy/tools/gltools.py +4 -2
  201. psychopy/tools/movietools.py +43 -2
  202. psychopy/tools/stringtools.py +33 -8
  203. psychopy/tools/versionchooser.py +1 -1
  204. psychopy/validation/audio.py +5 -1
  205. psychopy/validation/visual.py +5 -1
  206. psychopy/visual/basevisual.py +8 -7
  207. psychopy/visual/circle.py +2 -2
  208. psychopy/visual/helpers.py +3 -1
  209. psychopy/visual/image.py +29 -109
  210. psychopy/visual/movies/__init__.py +1800 -313
  211. psychopy/visual/polygon.py +4 -0
  212. psychopy/visual/shape.py +2 -2
  213. psychopy/visual/window.py +35 -12
  214. psychopy/voicekey/__init__.py +41 -669
  215. psychopy/voicekey/labjack_vks.py +7 -48
  216. psychopy/voicekey/parallel_vks.py +7 -42
  217. psychopy/voicekey/vk_tools.py +114 -263
  218. {psychopy-2025.1.0.dist-info → psychopy-2025.2.1.dist-info}/METADATA +20 -13
  219. {psychopy-2025.1.0.dist-info → psychopy-2025.2.1.dist-info}/RECORD +222 -190
  220. {psychopy-2025.1.0.dist-info → psychopy-2025.2.1.dist-info}/WHEEL +1 -1
  221. psychopy/visual/movies/players/__init__.py +0 -62
  222. psychopy/visual/movies/players/ffpyplayer_player.py +0 -1401
  223. psychopy/voicekey/demo_vks.py +0 -12
  224. psychopy/voicekey/signal.py +0 -42
  225. {psychopy-2025.1.0.dist-info → psychopy-2025.2.1.dist-info}/entry_points.txt +0 -0
  226. {psychopy-2025.1.0.dist-info → psychopy-2025.2.1.dist-info}/licenses/LICENSE +0 -0
@@ -71,6 +71,11 @@ participantIdAliases = ('participant', 'Participant', 'Subject', 'Observer')
71
71
  # pass
72
72
 
73
73
 
74
+ def getSoundBackends():
75
+ from psychopy.sound.sound import Sound
76
+ return list(Sound.getBackends())
77
+
78
+
74
79
  class SettingsComponent:
75
80
  """This component stores general info about how to run the experiment
76
81
  """
@@ -250,8 +255,8 @@ class SettingsComponent:
250
255
  "winBackend",
251
256
  "Screen",
252
257
  "Full-screen window",
253
- "Show mouse",
254
258
  "Window size (pixels)",
259
+ "Show mouse",
255
260
  "Units",
256
261
  "color",
257
262
  "blendMode",
@@ -266,16 +271,36 @@ class SettingsComponent:
266
271
  fullScr, valType='bool', inputType="bool", allowedTypes=[],
267
272
  hint=_translate("Run the experiment full-screen (recommended)"),
268
273
  label=_translate("Full-screen window"), categ='Screen')
274
+ self.params['Window size (pixels)'] = Param(
275
+ winSize, valType='list', inputType="single", allowedTypes=[],
276
+ hint=_translate("Size of window (if not fullscreen)"),
277
+ label=_translate("Window size (pixels)"), categ='Screen'
278
+ )
279
+ self.depends.append({
280
+ 'dependsOn': "Full-screen window", # if...
281
+ 'condition': "", # matches
282
+ 'param': "Window size (pixels)", # then...
283
+ 'true': "hide", # should...
284
+ 'false': "show", # otherwise...
285
+ })
286
+ self.params['Show mouse'] = Param(
287
+ showMouse, valType='bool', inputType="bool", allowedTypes=[],
288
+ hint=_translate("Should the mouse be visible on screen? Only applicable for fullscreen experiments."),
289
+ label=_translate("Show mouse"), categ='Screen'
290
+ )
291
+ self.depends.append({
292
+ 'dependsOn': "Full-screen window", # if...
293
+ 'condition': "", # matches
294
+ 'param': "Show mouse", # then...
295
+ 'true': "show", # should...
296
+ 'false': "hide", # otherwise...
297
+ })
269
298
  self.params['winBackend'] = Param(
270
299
  winBackend, valType='str', inputType="choice", categ="Screen",
271
300
  allowedVals=plugins.getWindowBackends(),
272
301
  hint=_translate("What Python package should be used behind the scenes for drawing to the window?"),
273
302
  label=_translate("Window backend")
274
- )
275
- self.params['Window size (pixels)'] = Param(
276
- winSize, valType='list', inputType="single", allowedTypes=[],
277
- hint=_translate("Size of window (if not fullscreen)"),
278
- label=_translate("Window size (pixels)"), categ='Screen')
303
+ )
279
304
  self.params['Screen'] = Param(
280
305
  screen, valType='num', inputType="spin", allowedTypes=[],
281
306
  hint=_translate("Which physical screen to run on (1 or 2)"),
@@ -323,10 +348,6 @@ class SettingsComponent:
323
348
  hint=_translate("Should new stimuli be added or averaged with "
324
349
  "the stimuli that have been drawn already"),
325
350
  label=_translate("Blend mode"), categ='Screen')
326
- self.params['Show mouse'] = Param(
327
- showMouse, valType='bool', inputType="bool", allowedTypes=[],
328
- hint=_translate("Should the mouse be visible on screen? Only applicable for fullscreen experiments."),
329
- label=_translate("Show mouse"), categ='Screen')
330
351
  self.params['measureFrameRate'] = Param(
331
352
  measureFrameRate, valType="bool", inputType="bool", categ="Screen",
332
353
  label=_translate("Measure frame rate?"),
@@ -381,7 +402,7 @@ class SettingsComponent:
381
402
  label=_translate("Force stereo"))
382
403
  self.params['Audio lib'] = Param(
383
404
  'ptb', valType='str', inputType="choice",
384
- allowedVals=['ptb', 'pyo', 'sounddevice', 'pygame'],
405
+ allowedVals=getSoundBackends,
385
406
  hint=_translate("Which Python sound engine do you want to play your sounds?"),
386
407
  label=_translate("Audio library"), categ='Audio')
387
408
 
@@ -906,13 +927,6 @@ class SettingsComponent:
906
927
  "from psychopy import prefs\n"
907
928
  "from psychopy import plugins\n"
908
929
  "plugins.activatePlugins()\n" # activates plugins
909
- )
910
- # adjust the prefs for this study if needed
911
- if self.params['Audio lib'].val.lower() != 'use prefs':
912
- buff.writelines(
913
- "prefs.hardware['audioLib'] = {}\n".format(self.params['Audio lib'])
914
- )
915
- buff.write(
916
930
  "from psychopy import %s\n" % ', '.join(psychopyImports) +
917
931
  "from psychopy.tools import environmenttools\n"
918
932
  "from psychopy.constants import (\n"
@@ -926,7 +940,8 @@ class SettingsComponent:
926
940
  "from numpy.random import %s\n" % ', '.join(_numpyRandomImports) +
927
941
  "import os # handy system and path functions\n" +
928
942
  "import sys # to get file system encoding\n"
929
- "\n")
943
+ "\n"
944
+ )
930
945
 
931
946
  if not self.params['eyetracker'] == "None" or self.params['keyboardBackend'] == "ioHub":
932
947
  code = (
@@ -1768,14 +1783,17 @@ class SettingsComponent:
1768
1783
  " )\n"
1769
1784
  )
1770
1785
  buff.writeIndentedLines(code % inits)
1771
- # write any device setup code required by a component
1772
- for rt in self.exp.flow:
1773
- if isinstance(rt, Routine):
1774
- for comp in rt:
1775
- if hasattr(comp, "writeDeviceCode"):
1776
- comp.writeDeviceCode(buff)
1777
- elif isinstance(rt, BaseStandaloneRoutine):
1778
- rt.writeDeviceCode(buff)
1786
+ # setup devices from config
1787
+ for deviceName in self.exp.getRequiredDeviceNames():
1788
+ if deviceName in prefs.devices:
1789
+ # write device setup if possile
1790
+ prefs.devices[deviceName].writeDeviceCode(buff)
1791
+ elif deviceName is None:
1792
+ # if default, let init code handle device
1793
+ pass
1794
+ else:
1795
+ # alert if not
1796
+ alert(4810, strFields={'deviceName': deviceName})
1779
1797
 
1780
1798
  code = (
1781
1799
  "# return True if completed successfully\n"
@@ -11,6 +11,7 @@ from pathlib import Path
11
11
  from psychopy.alerts._alerts import alert
12
12
  from psychopy.experiment.components import BaseDeviceComponent, Param, getInitVals, \
13
13
  _translate
14
+ from psychopy.experiment.devices import DeviceBackend
14
15
  from psychopy.experiment.utils import canBeNumeric
15
16
  from psychopy.tools.audiotools import knownNoteNames
16
17
 
@@ -23,6 +24,12 @@ class SoundComponent(BaseDeviceComponent):
23
24
  tooltip = _translate('Sound: play recorded files or generated sounds', )
24
25
  deviceClasses = ["psychopy.hardware.speaker.SpeakerDevice"]
25
26
  validatorClasses = ["AudioValidatorRoutine"]
27
+ legacyParams = [
28
+ # old device setup params, no longer needed as this is handled by DeviceManager
29
+ "speakerIndex",
30
+ "resample",
31
+ "latencyClass",
32
+ ]
26
33
 
27
34
  def __init__(
28
35
  self,
@@ -72,7 +79,7 @@ class SoundComponent(BaseDeviceComponent):
72
79
  hnt = _translate("A sound can be a note name (e.g. A or Bf), a number"
73
80
  " to specify Hz (e.g. 440) or a filename")
74
81
  self.params['sound'] = Param(
75
- sound, valType='str', inputType="file", allowedTypes=[], updates='constant', categ='Basic',
82
+ sound, valType='str', inputType="soundFile", allowedTypes=[], updates='set every repeat', categ='Basic',
76
83
  allowedUpdates=['set every repeat'],
77
84
  hint=hnt,
78
85
  label=_translate("Sound"))
@@ -118,61 +125,6 @@ class SoundComponent(BaseDeviceComponent):
118
125
  ),
119
126
  label=_translate("Force end of Routine"))
120
127
 
121
- # --- Device params ---
122
- self.order += [
123
- "speakerIndex",
124
- "resample",
125
- "latencyClass",
126
- ]
127
- def getSpeakerLabels():
128
- from psychopy.hardware.speaker import SpeakerDevice
129
- labels = [_translate("From preferences")]
130
- for profile in SpeakerDevice.getAvailableDevices():
131
- labels.append(profile['deviceName'])
132
-
133
- return labels
134
-
135
- def getSpeakerValues():
136
- from psychopy.hardware.speaker import SpeakerDevice
137
- vals = [""]
138
- for profile in SpeakerDevice.getAvailableDevices():
139
- vals.append(profile['index'])
140
-
141
- return vals
142
-
143
- self.params['speakerIndex'] = Param(
144
- speakerIndex, valType="str", inputType="choice", categ="Device",
145
- allowedVals=getSpeakerValues,
146
- allowedLabels=getSpeakerLabels,
147
- hint=_translate(
148
- "What speaker to play this sound on"
149
- ),
150
- label=_translate("Speaker"))
151
- self.params['resample'] = Param(
152
- resample, valType="str", inputType="bool", categ="Device",
153
- label=_translate("Resample"),
154
- hint=_translate(
155
- "If the sample rate of a clip doesn't match the sample rate of the speaker, should "
156
- "we resample it to match?"
157
- )
158
- )
159
- self.params['latencyClass'] = Param(
160
- latencyClass, valType="code", inputType="choice", categ="Device",
161
- allowedVals=[0, 1, 2, 3, 4],
162
- allowedLabels=[
163
- _translate("Shared"),
164
- _translate("Shared low-latency (recommended)"),
165
- _translate("Exclusive low-latency"),
166
- _translate("Exclusive aggressive low-latency (with fallback)"),
167
- _translate("Exclusive aggressive low-latency (no fallback)"),
168
- ],
169
- label=_translate("Latency/exclusivity mode"),
170
- hint=_translate(
171
- "How should PsychoPy handle latency? Some options will result in other apps being "
172
- "denied access to the speaker while your experiment is running."
173
- )
174
- )
175
-
176
128
  # --- Testing ---
177
129
  self.params['validator'] = Param(
178
130
  validator, valType="code", inputType="choice", categ="Testing",
@@ -184,25 +136,13 @@ class SoundComponent(BaseDeviceComponent):
184
136
  )
185
137
  )
186
138
 
187
- def writeDeviceCode(self, buff):
188
- inits = getInitVals(self.params)
189
- # alert user to inefficient configuration
190
- if self.params['resample'] and self.params['latencyClass'] == 2:
191
- alert(3210, strFields={'deviceName': inits['deviceLabel']})
192
- # initialise speaker
139
+ def writeInitCode(self, buff):
140
+ # set sound backend (only once per exp)
193
141
  code = (
194
- "# create speaker %(deviceLabel)s\n"
195
- "deviceManager.addDevice(\n"
196
- " deviceName=%(deviceLabel)s,\n"
197
- " deviceClass='psychopy.hardware.speaker.SpeakerDevice',\n"
198
- " index=%(speakerIndex)s,\n"
199
- " resample=%(resample)s,\n"
200
- " latencyClass=%(latencyClass)s,\n"
201
- ")\n"
142
+ "# set audio backend\n"
143
+ "sound.Sound.backend = %(Audio lib)s\n"
202
144
  )
203
- buff.writeOnceIndentedLines(code % inits)
204
-
205
- def writeInitCode(self, buff):
145
+ buff.writeOnceIndentedLines(code % self.exp.settings.params)
206
146
  # replaces variable params with sensible defaults
207
147
  inits = getInitVals(self.params)
208
148
  if not canBeNumeric(inits['stopVal'].val):
@@ -430,3 +370,72 @@ class SoundComponent(BaseDeviceComponent):
430
370
  params=params,
431
371
  target=target,
432
372
  )
373
+
374
+
375
+ class SpeakerDeviceBackend(DeviceBackend):
376
+ # name of this backend to display in Device Manager
377
+ backendLabel = "Speaker"
378
+ # class of the device which this backend corresponds to
379
+ deviceClass = "psychopy.hardware.speaker.SpeakerDevice"
380
+ # icon to show in device manager
381
+ icon = "light/sound.png"
382
+
383
+ def __init__(self, profile):
384
+ # init parent class
385
+ DeviceBackend.__init__(self, profile)
386
+
387
+ # add params
388
+ self.order += [
389
+ "latencyClass",
390
+ "resample",
391
+ ]
392
+ self.params['latencyClass'] = Param(
393
+ 1, valType="code", inputType="choice",
394
+ allowedVals=[0, 1, 2, 3, 4],
395
+ allowedLabels=[
396
+ _translate("Shared"),
397
+ _translate("Shared low-latency (recommended)"),
398
+ _translate("Exclusive low-latency"),
399
+ _translate("Exclusive aggressive low-latency (with fallback)"),
400
+ _translate("Exclusive aggressive low-latency (no fallback)"),
401
+ ],
402
+ label=_translate("Latency/exclusivity mode"),
403
+ hint=_translate(
404
+ "How should PsychoPy handle latency? Some options will result in other apps being "
405
+ "denied access to the speaker while your experiment is running."
406
+ )
407
+ )
408
+ self.params['resample'] = Param(
409
+ True, valType="str", inputType="bool",
410
+ label=_translate("Resample"),
411
+ hint=_translate(
412
+ "If the sample rate of a clip doesn't match the sample rate of the speaker, should "
413
+ "we resample it to match?"
414
+ )
415
+ )
416
+
417
+ def writeDeviceCode(self, buff):
418
+ """
419
+ Code to setup a device with this backend.
420
+
421
+ Parameters
422
+ ----------
423
+ buff : io.StringIO
424
+ Text buffer to write code to.
425
+ """
426
+ # alert user to inefficient configuration
427
+ if self.params['resample'] and self.params['latencyClass'] == 2:
428
+ alert(3210, strFields={'deviceName': self.params['deviceLabel']})
429
+ # write basic code
430
+ self.writeBaseDeviceCode(buff, close=False)
431
+ # add exclusive param and close
432
+ code = (
433
+ " resample=%(resample)s,\n"
434
+ " latencyClass=%(latencyClass)s,\n"
435
+ ")\n"
436
+ )
437
+ buff.writeIndentedLines(code % self.params)
438
+
439
+
440
+ # register backend with Component
441
+ SoundComponent.registerBackend(SpeakerDeviceBackend)
@@ -1,10 +1,10 @@
1
1
  from pathlib import Path
2
+ from psychopy.experiment.devices import DeviceBackend
2
3
  from psychopy.experiment.components import BaseComponent, BaseDeviceComponent, Param, getInitVals
3
- from psychopy.experiment.plugins import PluginDevicesMixin, DeviceBackend
4
4
  from psychopy.localization import _translate
5
5
 
6
6
 
7
- class SoundSensorComponent(BaseDeviceComponent, PluginDevicesMixin):
7
+ class SoundSensorComponent(BaseDeviceComponent):
8
8
  """
9
9
  Component for getting button presses from a button box device.
10
10
  """
@@ -13,6 +13,14 @@ class SoundSensorComponent(BaseDeviceComponent, PluginDevicesMixin):
13
13
  iconFile = Path(__file__).parent / 'soundsensor.png'
14
14
  tooltip = _translate('Voice Key: Get input from a microphone as simple true/false values')
15
15
  beta = True
16
+ legacyParams = [
17
+ # old device setup params, no longer needed as this is handled by DeviceManager
18
+ "deviceBackend",
19
+ "meMicrophone",
20
+ "meThreshold",
21
+ "meRange",
22
+ "meSamplingWindow",
23
+ ]
16
24
 
17
25
  def __init__(
18
26
  self, exp, parentName,
@@ -112,25 +120,6 @@ class SoundSensorComponent(BaseDeviceComponent, PluginDevicesMixin):
112
120
  ),
113
121
  label=_translate("Correct answer"), direct=False)
114
122
 
115
- # --- Device params ---
116
- self.order += [
117
- "deviceBackend",
118
- ]
119
-
120
- self.params['deviceBackend'] = Param(
121
- deviceBackend, valType="str", inputType="choice", categ="Device",
122
- allowedVals=self.getBackendKeys,
123
- allowedLabels=self.getBackendLabels,
124
- label=_translate("Device backend"),
125
- hint=_translate(
126
- "What kind of sound sensor is it? What package/plugin should be used to talk to it?"
127
- ),
128
- direct=False
129
- )
130
-
131
- # add params for any backends
132
- self.loadBackends()
133
-
134
123
  def writeInitCode(self, buff):
135
124
  inits = getInitVals(self.params)
136
125
  # code to create object
@@ -267,17 +256,13 @@ class SoundSensorComponent(BaseDeviceComponent, PluginDevicesMixin):
267
256
 
268
257
 
269
258
  class MicrophoneSoundSensorBackend(DeviceBackend):
270
- """
271
- Adds a basic microphone emulation backend for SoundSensorComponent, as well as acting as an example
272
- for implementing other SoundSensorBackends.
273
- """
274
-
275
- key = "microphone"
276
- label = _translate("Microphone emulator")
277
- component = SoundSensorComponent
278
- deviceClasses = ['psychopy.hardware.soundsensor.MicrophoneSoundSensorEmulator']
259
+ backendLabel = "Microphone Sound Sensor"
260
+ deviceClass = "psychopy.hardware.soundsensor.MicrophoneSoundSensor"
261
+ icon = "light/soundsensor.png"
279
262
 
280
- def getParams(self: SoundSensorComponent):
263
+ def __init__(self, profile):
264
+ # init parent class
265
+ DeviceBackend.__init__(self, profile)
281
266
  # define order
282
267
  order = [
283
268
  "meMicrophone",
@@ -286,31 +271,8 @@ class MicrophoneSoundSensorBackend(DeviceBackend):
286
271
  "meSamplingWindow",
287
272
  ]
288
273
  # define params
289
- params = {}
290
- def getDeviceIndices():
291
- from psychopy.hardware.microphone import MicrophoneDevice
292
- profiles = MicrophoneDevice.getAvailableDevices()
293
-
294
- return [None] + [profile['index'] for profile in profiles]
295
-
296
- def getDeviceNames():
297
- from psychopy.hardware.microphone import MicrophoneDevice
298
- profiles = MicrophoneDevice.getAvailableDevices()
299
-
300
- return ["default"] + [profile['deviceName'] for profile in profiles]
301
-
302
- params['meMicrophone'] = Param(
303
- None, valType="str", inputType="choice", categ="Device",
304
- updates="constant", allowedUpdates=None,
305
- allowedVals=getDeviceIndices,
306
- allowedLabels=getDeviceNames,
307
- label=_translate("Microphone"),
308
- hint=_translate(
309
- "What microphone device to take volume readings from?"
310
- )
311
- )
312
- params['meThreshold'] = Param(
313
- 0.5, valType="code", inputType="single", categ="Device",
274
+ self.params['threshold'] = Param(
275
+ 0.5, valType="code", inputType="single",
314
276
  updates="constant", allowedUpdates=None,
315
277
  label=_translate("Threshold (0-1)"),
316
278
  hint=_translate(
@@ -318,17 +280,17 @@ class MicrophoneSoundSensorBackend(DeviceBackend):
318
280
  "register a sound sensor response"
319
281
  )
320
282
  )
321
- params['meRange'] = Param(
322
- (0, 1), valType="list", inputType="single", categ="Device",
283
+ self.params['dbRange'] = Param(
284
+ (0, 1), valType="list", inputType="single",
323
285
  updates="constant", allowedUpdates=None,
324
286
  label=_translate("Decibel range"),
325
287
  hint=_translate(
326
288
  "What kind of values (dB) would you expect to receive from this device? In other "
327
- "words, how many dB does a threshold of 0 and of 255 correspond to?"
289
+ "words, how many dB does a threshold of 0 and 1 correspond to?"
328
290
  )
329
291
  )
330
- params['meSamplingWindow'] = Param(
331
- 0.03, valType="code", inputType="single", categ="Device",
292
+ self.params['samplingWindow'] = Param(
293
+ 0.03, valType="code", inputType="single",
332
294
  updates="constant", allowedUpdates=None,
333
295
  label=_translate("Sampling window (s)"),
334
296
  hint=_translate(
@@ -336,26 +298,27 @@ class MicrophoneSoundSensorBackend(DeviceBackend):
336
298
  "precise, but also less subject to random noise."
337
299
  )
338
300
  )
301
+
302
+ def writeDeviceCode(self, buff):
303
+ """
304
+ Code to setup a device with this backend.
339
305
 
340
- return params, order
341
-
342
- def addRequirements(self: SoundSensorComponent):
343
- self.exp.requireImport(
344
- importName="microphone", importFrom="psychopy.hardware"
345
- )
346
-
347
- def writeDeviceCode(self: SoundSensorComponent, buff):
348
- # get inits
349
- inits = getInitVals(self.params)
350
- # make ButtonGroup object
306
+ Parameters
307
+ ----------
308
+ buff : io.StringIO
309
+ Text buffer to write code to.
310
+ """
311
+ # write basic code
312
+ self.writeBaseDeviceCode(buff, close=False)
313
+ # add exclusive param and close
351
314
  code = (
352
- "deviceManager.addDevice(\n"
353
- " deviceClass='psychopy.hardware.soundsensor.MicrophoneSoundSensorEmulator',\n"
354
- " deviceName=%(deviceLabel)s,\n"
355
- " device=%(meMicrophone)s,\n"
356
- " threshold=%(meThreshold)s, \n"
357
- " dbRange=%(meRange)s, \n"
358
- " samplingWindow=%(meSamplingWindow)s, \n"
315
+ " threshold=%(threshold)s, \n"
316
+ " dbRange=%(dbRange)s, \n"
317
+ " samplingWindow=%(samplingWindow)s,\n"
359
318
  ")\n"
360
319
  )
361
- buff.writeOnceIndentedLines(code % inits)
320
+ buff.writeIndentedLines(code % self.params)
321
+
322
+
323
+ # register backend with Component
324
+ SoundSensorComponent.registerBackend(MicrophoneSoundSensorBackend)