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
@@ -10,6 +10,7 @@ from pathlib import Path
10
10
 
11
11
  from psychopy import logging
12
12
  from psychopy.alerts import alert
13
+ from psychopy.experiment.devices import DeviceBackend
13
14
  from psychopy.tools import stringtools as st, systemtools as syst, audiotools as at
14
15
  from psychopy.experiment.components import (
15
16
  BaseComponent, BaseDeviceComponent, Param, getInitVals, _translate
@@ -24,7 +25,6 @@ class MicrophoneComponent(BaseDeviceComponent):
24
25
  iconFile = Path(__file__).parent / 'microphone.png'
25
26
  tooltip = _translate('Microphone: basic sound capture (fixed onset & '
26
27
  'duration), okay for spoken words')
27
- deviceClasses = ['psychopy.hardware.microphone.MicrophoneDevice']
28
28
 
29
29
  # dict of available transcribers (plugins can add entries to this)
30
30
  localTranscribers = {
@@ -37,14 +37,22 @@ class MicrophoneComponent(BaseDeviceComponent):
37
37
  transcriberPaths = {
38
38
  'google': "psychopy.sound.transcribe:GoogleCloudTranscriber"
39
39
  }
40
+ legacyParams = [
41
+ # old device setup params, no longer needed as this is handled by DeviceManager
42
+ "device",
43
+ "exclusive",
44
+ "sampleRate",
45
+ "channels",
46
+ "stereo",
47
+ "channel",
48
+ "maxSize"
49
+ ]
40
50
 
41
51
  def __init__(
42
52
  self, exp, parentName, name='mic',
43
53
  startType='time (s)', startVal=0.0,
44
54
  stopType='duration (s)', stopVal=2.0,
45
55
  startEstim='', durationEstim='',
46
- device=None,
47
- exclusive=False,
48
56
  outputType='default', speakTimes=False, trimSilent=False,
49
57
  policyWhenFull='warn',
50
58
  transcribe=False, transcribeBackend="none",
@@ -52,6 +60,8 @@ class MicrophoneComponent(BaseDeviceComponent):
52
60
  transcribeWhisperModel="base",
53
61
  transcribeWhisperDevice="auto",
54
62
  #legacy
63
+ device=None,
64
+ exclusive=False,
55
65
  sampleRate=48000,
56
66
  channels=2,
57
67
  stereo=None,
@@ -74,44 +84,6 @@ class MicrophoneComponent(BaseDeviceComponent):
74
84
  'The duration of the recording in seconds; blank = 0 sec')
75
85
  self.params['stopType'].hint = msg
76
86
 
77
- # --- Device params ---
78
- self.order += [
79
- "device",
80
- "exclusive",
81
- "maxSize",
82
- ]
83
-
84
- def getDeviceIndices():
85
- from psychopy.hardware.microphone import MicrophoneDevice
86
- profiles = MicrophoneDevice.getAvailableDevices()
87
-
88
- return ["$None"] + [profile['index'] for profile in profiles]
89
-
90
- def getDeviceNames():
91
- from psychopy.hardware.microphone import MicrophoneDevice
92
- profiles = MicrophoneDevice.getAvailableDevices()
93
-
94
- return ["default"] + [profile['deviceName'] for profile in profiles]
95
-
96
- self.params['device'] = Param(
97
- device, valType='str', inputType="choice", categ="Device",
98
- allowedVals=getDeviceIndices,
99
- allowedLabels=getDeviceNames,
100
- label=_translate("Device"),
101
- hint=_translate(
102
- "What microphone device would you like the use to record? This will only affect "
103
- "local experiments - online experiments ask the participant which mic to use."
104
- )
105
- )
106
- self.params['exclusive'] = Param(
107
- exclusive, valType="code", inputType="bool", categ="Device",
108
- label=_translate("Exclusive control"),
109
- hint=_translate(
110
- "Take exclusive control of the microphone, so other apps can't use it during your "
111
- "experiment."
112
- )
113
- )
114
-
115
87
  # --- Data params ---
116
88
  msg = _translate(
117
89
  "What file type should output audio files be saved as?")
@@ -268,32 +240,6 @@ class MicrophoneComponent(BaseDeviceComponent):
268
240
  """
269
241
  return {'None': "none", **self.localTranscribers, **self.onlineTranscribers}
270
242
 
271
- def writeDeviceCode(self, buff):
272
- """
273
- Code to setup the CameraDevice for this component.
274
-
275
- Parameters
276
- ----------
277
- buff : io.StringIO
278
- Text buffer to write code to.
279
- """
280
- inits = getInitVals(self.params)
281
-
282
- # --- setup mic ---
283
- # force index to str type (holdover from when we used numeric indices)
284
- inits['device'].valType = "str"
285
- # initialise mic device
286
- code = (
287
- "# initialise microphone\n"
288
- "deviceManager.addDevice(\n"
289
- " deviceClass='psychopy.hardware.microphone.MicrophoneDevice',\n"
290
- " deviceName=%(deviceLabel)s,\n"
291
- " index=%(device)s,\n"
292
- " exclusive=%(exclusive)s,\n"
293
- ")\n"
294
- )
295
- buff.writeOnceIndentedLines(code % inits)
296
-
297
243
  def writeStartCode(self, buff):
298
244
  inits = getInitVals(self.params)
299
245
  # Use filename with a suffix to store recordings
@@ -366,7 +312,7 @@ class MicrophoneComponent(BaseDeviceComponent):
366
312
  def writeInitCodeJS(self, buff):
367
313
  inits = getInitVals(self.params)
368
314
  # Alert user if non-default value is selected for device
369
- if inits['device'].val != '$None':
315
+ if inits['deviceLabel'].val not in (None, "", 'None'):
370
316
  alert(5055, strFields={'name': inits['name'].val})
371
317
  # Write code
372
318
  code = (
@@ -462,9 +408,7 @@ class MicrophoneComponent(BaseDeviceComponent):
462
408
  inits['transcribeBackend'].val = None
463
409
  # Warn user if their transcriber won't work locally
464
410
  if inits['transcribe'].val:
465
- if self.params['transcribeBackend'].val in self.localTranscribers:
466
- inits['transcribeBackend'].val = self.localTranscribers[self.params['transcribeBackend'].val]
467
- else:
411
+ if self.params['transcribeBackend'].val not in self.localTranscribers.values():
468
412
  default = list(self.localTranscribers.values())[0]
469
413
  alert(4610, strFields={"transcriber": inits['transcribeBackend'].val, "default": default})
470
414
  # Store recordings from this routine
@@ -608,42 +552,49 @@ class MicrophoneComponent(BaseDeviceComponent):
608
552
  buff.writeIndentedLines(code % inits)
609
553
 
610
554
 
611
- def getDeviceName(index):
612
- """
613
- Get device name from a given index
614
-
615
- Parameters
616
- ----------
617
- index : int or None
618
- Index of the device to use
619
- """
620
- name = "defaultMicrophone"
621
- if isinstance(index, str) and index.isnumeric():
622
- index = int(index)
623
- for dev in syst.getAudioCaptureDevices():
624
- if dev['index'] == index:
625
- name = dev['name']
626
-
627
- return name
628
-
629
-
630
- def getDeviceVarName(index, case="camel"):
631
- """
632
- Get device name from a given index and convert it to a valid variable name.
633
-
634
- Parameters
635
- ----------
636
- index : int or None
637
- Index of the device to use
638
- case : str
639
- Format of the variable name (see stringtools.makeValidVarName for info on accepted formats)
640
- """
641
- # Get device name
642
- name = getDeviceName(index)
643
- # If device name is just default, add "microphone" for clarity
644
- if name == "default":
645
- name += "_microphone"
646
- # Make valid
647
- varName = st.makeValidVarName(name, case=case)
648
-
649
- return varName
555
+ class MicrophoneDeviceBackend(DeviceBackend):
556
+ # name of this backend to display in Device Manager
557
+ backendLabel = "Microphone"
558
+ # class of the device which this backend corresponds to
559
+ deviceClass = "psychopy.hardware.microphone.MicrophoneDevice"
560
+ # icon to show in device manager
561
+ icon = "light/microphone.png"
562
+
563
+ def __init__(self, profile):
564
+ # init parent class
565
+ DeviceBackend.__init__(self, profile)
566
+
567
+ # add params
568
+ self.order += [
569
+ "exclusive",
570
+ ]
571
+ self.params['exclusive'] = Param(
572
+ False, valType="code", inputType="bool",
573
+ label=_translate("Exclusive control"),
574
+ hint=_translate(
575
+ "Take exclusive control of the microphone, so other apps can't use it during your "
576
+ "experiment."
577
+ )
578
+ )
579
+
580
+ def writeDeviceCode(self, buff):
581
+ """
582
+ Code to setup a device with this backend.
583
+
584
+ Parameters
585
+ ----------
586
+ buff : io.StringIO
587
+ Text buffer to write code to.
588
+ """
589
+ # write basic code
590
+ self.writeBaseDeviceCode(buff, close=False)
591
+ # add exclusive param and close
592
+ code = (
593
+ " exclusive=%(exclusive)s,\n"
594
+ ")\n"
595
+ )
596
+ buff.writeIndentedLines(code % self.params)
597
+
598
+
599
+ # register backend with Component
600
+ MicrophoneComponent.registerBackend(MicrophoneDeviceBackend)
@@ -281,15 +281,13 @@ class MovieComponent(BaseVisualComponent):
281
281
  code = (
282
282
  "%(name)s.setAutoDraw(False)\n"
283
283
  )
284
- if self.params['backend'].val not in ('moviepy', 'avbin', 'vlc'):
285
- code += "%(name)s.stop()\n"
286
284
  buff.writeIndentedLines(code % self.params)
287
285
  # to get out of the if statement
288
286
  buff.setIndentLevel(-indented, relative=True)
289
287
 
290
288
  # do force end of trial code
291
289
  if self.params['forceEndRoutine'].val is True:
292
- code = ("if %s.isFinished: # force-end the Routine\n"
290
+ code = ("if %s.status == FINISHED: # force-end the Routine\n"
293
291
  " continueRoutine = False\n" %
294
292
  self.params['name'])
295
293
  buff.writeIndentedLines(code)
@@ -336,6 +334,7 @@ class MovieComponent(BaseVisualComponent):
336
334
  if self.params['stopWithRoutine']:
337
335
  # stop at the end of the Routine, if requested
338
336
  code = (
337
+ "%(name)s.setAutoDraw(False)\n"
339
338
  "%(name)s.stop() # ensure movie has stopped at end of Routine\n"
340
339
  )
341
340
  buff.writeIndentedLines(code % self.params)
@@ -6,11 +6,12 @@
6
6
  # Distributed under the terms of the GNU General Public License (GPL).
7
7
  from copy import copy
8
8
  from pathlib import Path
9
+ from psychopy.experiment.devices import DeviceBackend
9
10
  from psychopy.tools import stringtools as st
10
- from psychopy.experiment.components import BaseComponent, Param, _translate, getInitVals
11
+ from psychopy.experiment.components import BaseDeviceComponent, Param, _translate, getInitVals
11
12
 
12
13
 
13
- class SerialOutComponent(BaseComponent):
14
+ class SerialOutComponent(BaseDeviceComponent):
14
15
  """A class for sending signals from the parallel port"""
15
16
 
16
17
  categories = ['I/O', 'EEG']
@@ -19,6 +20,19 @@ class SerialOutComponent(BaseComponent):
19
20
  iconFile = Path(__file__).parent / 'serial.png'
20
21
  tooltip = _translate('Serial out: send signals from a serial port')
21
22
  beta = False
23
+ legacyParams = [
24
+ # superceded by "startDataChar", "startDataCode", et al.
25
+ "startdata",
26
+ "stopdata",
27
+ # old device setup params, no longer needed as this is handled by DeviceManager
28
+ "port",
29
+ "baudrate",
30
+ "bytesize",
31
+ "stopbits",
32
+ "parity",
33
+ "timeout"
34
+ ]
35
+
22
36
 
23
37
  def __init__(self, exp, parentName, name='serialPort',
24
38
  startType='time (s)', startVal=0.0,
@@ -37,88 +51,106 @@ class SerialOutComponent(BaseComponent):
37
51
 
38
52
  self.type = 'SerialOut'
39
53
  self.url = "https://www.psychopy.org/builder/components/serialout.html"
40
- self.exp.requireImport('serial')
54
+
55
+ for prefix, label, titleLabel, default in (
56
+ ("start", _translate("start"), _translate("Start"), b"r"),
57
+ ("stop", _translate("stop"), _translate("Stop"), b"x"),
58
+ ):
41
59
 
42
- self.params['port'] = Param(
43
- port, valType='str', inputType="single", categ='Basic',
44
- hint=_translate("Serial port to connect to"),
45
- label=_translate("Port")
46
- )
47
- self.params['baudrate'] = Param(
48
- baudrate, valType='int', inputType="single", categ="Device",
49
- hint=_translate("The baud rate, or speed, of the connection."),
50
- label=_translate("Baud rate")
51
- )
52
- self.params['bytesize'] = Param(
53
- bytesize, valType='int', inputType="single", categ="Device",
54
- hint=_translate("Size of bits to be sent."),
55
- label=_translate("Data bits")
56
- )
57
- self.params['stopbits'] = Param(
58
- stopbits, valType='int', inputType="single", categ="Device",
59
- hint=_translate("Size of bits to be sent on stop."),
60
- label=_translate("Stop bits")
61
- )
62
- self.params['parity'] = Param(
63
- parity, valType='str', inputType="choice", categ="Device",
64
- allowedVals=('N', 'E', 'O', 'M', 'S'),
65
- allowedLabels=("None", "Even", "Off", "Mark", "Space"),
66
- hint=_translate("Parity mode."),
67
- label=_translate("Parity")
68
- )
60
+ self.params[prefix + 'DataType'] = Param(
61
+ "str", valType="str", inputType="choice", categ="Basic",
62
+ allowedVals=["str", "num", "binary", "char", "code"],
63
+ allowedLabels=[
64
+ _translate("String"), _translate("Numeric (0-255)"),
65
+ _translate("Binary"), _translate("Character (Byte)"), _translate("Code")
66
+ ],
67
+ hint=_translate(
68
+ "Type of data to be sent: A number, a binary sequence, a character byte, or custom code ($)"
69
+ ),
70
+ label=_translate("{} data type").format(titleLabel)
71
+ )
69
72
 
70
- self.params['timeout'] = Param(
71
- timeout, valType='int', inputType="single", allowedTypes=[], categ="Device",
72
- hint=_translate("Time at which to give up listening for a response (leave blank for no limit)"),
73
- label=_translate("Timeout"))
74
-
75
- self.params['startdata'] = Param(
76
- startdata, valType='str', inputType="single", allowedTypes=[], categ='Basic',
77
- hint=_translate("Data to be sent at start of pulse. Data will be converted to bytes, so to specify a"
78
- "numeric value directly use $chr(...)."),
79
- label=_translate("Start data"))
80
- self.params['stopdata'] = Param(
81
- stopdata, valType='str', inputType="single", allowedTypes=[], categ='Basic',
82
- hint=_translate("String data to be sent at end of pulse. Data will be converted to bytes, so to specify a"
83
- "numeric value directly use $chr(...)."),
84
- label=_translate("Stop data"))
73
+ self.params[prefix + 'DataStr'] = Param(
74
+ default.decode("utf-8"), valType="str", inputType="single", categ="Basic",
75
+ hint=_translate("Send a regular string (which will be converted to binary) on {}").format(label),
76
+ label=_translate("{} data (string)").format(titleLabel)
77
+ )
78
+ self.depends.append({
79
+ 'dependsOn': prefix + "DataType", # if...
80
+ 'condition': "== 'str'", # meets...
81
+ 'param': prefix + "DataStr", # then...
82
+ 'true': "show", # should...
83
+ 'false': "hide", # otherwise...
84
+ })
85
+
86
+ self.params[prefix + 'DataNumeric'] = Param(
87
+ ord(default), valType="code", inputType="single", categ="Basic",
88
+ hint=_translate("Send a number between 0-255 on {}").format(label),
89
+ label=_translate("{} data (numeric)").format(titleLabel)
90
+ )
91
+ self.depends.append({
92
+ 'dependsOn': prefix + "DataType", # if...
93
+ 'condition': "== 'num'", # meets...
94
+ 'param': prefix + "DataNumeric", # then...
95
+ 'true': "show", # should...
96
+ 'false': "hide", # otherwise...
97
+ })
98
+
99
+ self.params[prefix + 'DataBinary'] = Param(
100
+ bin(ord(default))[2:], valType="code", inputType="single", categ="Basic",
101
+ hint=_translate("Send a binary sequence (1s and 0s) on {}").format(label),
102
+ label=_translate("{} data (binary)").format(titleLabel)
103
+ )
104
+ self.depends.append({
105
+ 'dependsOn': prefix + "DataType", # if...
106
+ 'condition': "== 'binary'", # meets...
107
+ 'param': prefix + "DataBinary", # then...
108
+ 'true': "show", # should...
109
+ 'false': "hide", # otherwise...
110
+ })
111
+
112
+ self.params[prefix + 'DataChar'] = Param(
113
+ "\\x" + hex(ord(default))[2:], valType="str", inputType="single", categ="Basic",
114
+ hint=_translate("Send a character byte (e.g. \\x73) on {}").format(label),
115
+ label=_translate("{} data (char)").format(titleLabel)
116
+ )
117
+ self.depends.append({
118
+ 'dependsOn': prefix + "DataType", # if...
119
+ 'condition': "== 'char'", # meets...
120
+ 'param': prefix + "DataChar", # then...
121
+ 'true': "show", # should...
122
+ 'false': "hide", # otherwise...
123
+ })
124
+
125
+ self.params[prefix + 'DataCode'] = Param(
126
+ repr(default), valType="code", inputType="single", categ="Basic",
127
+ hint=_translate("Send custom code (e.g. from a variable) on {}").format(label),
128
+ label=_translate("{} data (code)").format(titleLabel)
129
+ )
130
+ self.depends.append({
131
+ 'dependsOn': prefix + "DataType", # if...
132
+ 'condition': "== 'code'", # meets...
133
+ 'param': prefix + "DataCode", # then...
134
+ 'true': "show", # should...
135
+ 'false': "hide", # otherwise...
136
+ })
137
+
85
138
  self.params['getResponse'] = Param(
86
139
  getResponse, valType='bool', inputType='bool', categ="Data",
87
140
  hint=_translate("After sending a signal, should PsychoPy read and record a response from the port?"),
88
141
  label=_translate("Get response?")
89
142
  )
90
143
 
91
- def writeRunOnceInitCode(self, buff):
92
- inits = getInitVals(self.params, "PsychoPy")
93
- # Get device-based variable name
94
- inits['varName'] = self.getDeviceVarName()
95
- # Create object for serial device
96
- code = (
97
- "# Create serial object for device at port %(port)s\n"
98
- "%(varName)s = serial.Serial(\n"
99
- )
100
- for key in ('port', 'baudrate', 'bytesize', 'parity', 'stopbits', 'timeout'):
101
- if self.params[key].val is not None:
102
- code += (
103
- f" {key}=%({key})s,\n"
104
- )
105
- code += (
106
- ")\n"
107
- )
108
- buff.writeOnceIndentedLines(code % inits)
109
-
110
144
  def writeInitCode(self, buff):
111
145
  inits = getInitVals(self.params, "PsychoPy")
112
- # Get device-based variable name
113
- inits['varName'] = self.getDeviceVarName()
114
- # Point component name to device object
146
+ # point component name to device object
115
147
  code = (
116
148
  "\n"
117
- "# point %(name)s to device at port %(port)s and make sure it's open\n"
118
- "%(name)s = %(varName)s\n"
149
+ "# point %(name)s to device named %(deviceLabel)s and make sure it's open\n"
150
+ "%(name)s = deviceManager.getDevice(%(deviceLabel)s)\n"
119
151
  "%(name)s.status = NOT_STARTED\n"
120
- "if not %(name)s.is_open:\n"
121
- " %(name)s.open()\n"
152
+ "if not %(name)s.com.is_open:\n"
153
+ " %(name)s.com.open()\n"
122
154
  )
123
155
  buff.writeIndentedLines(code % inits)
124
156
 
@@ -126,28 +158,48 @@ class SerialOutComponent(BaseComponent):
126
158
  params = copy(self.params)
127
159
  # Get containing loop
128
160
  params['loop'] = self.currentLoop
161
+
129
162
 
130
163
  # On component start, send start bits
131
164
  indented = self.writeStartTestCode(buff)
132
165
  if indented:
166
+ # get data string to write
167
+ if params['startDataType'] == "str":
168
+ params['startData'] = params['startDataStr']
169
+ elif params['startDataType'] == "num":
170
+ params['startData'] = "bytes(chr(%(startDataNumeric)s), 'utf-8')" % params
171
+ elif params['startDataType'] == "binary":
172
+ params['startData'] = "0b%(startDataBinary)s" % params
173
+ elif params['startDataType'] == "char":
174
+ params['startData'] = "b%(startDataChar)s" % params
175
+ elif params['startDataType'] == "code":
176
+ params['startData'] = params['startDataCode']
177
+ else:
178
+ raise TypeError(f"Unknown data type {params['startDataType']}")
179
+ # write code to send it (immediately or on refresh)
133
180
  if self.params['syncScreenRefresh']:
134
181
  code = (
135
- "win.callOnFlip(%(name)s.write, bytes(%(startdata)s, 'utf8'))\n"
182
+ "win.callOnFlip(%(name)s.sendMessage, %(startData)s)\n"
136
183
  )
137
184
  else:
138
185
  code = (
139
- "%(name)s.write(bytes(%(startdata)s, 'utf8'))\n"
186
+ "%(name)s.sendMessage(%(startData)s)\n"
140
187
  )
141
188
  buff.writeIndented(code % params)
142
- # Update status
189
+ # store code that was sent
190
+ code = (
191
+ "%(loop)s.addData('%(name)s.stopData', %(startData)s)\n"
192
+ )
193
+ buff.writeIndented(code % params)
194
+ # update status
143
195
  code = (
144
196
  "%(name)s.status = STARTED\n"
145
197
  )
146
198
  buff.writeIndented(code % params)
147
- # If we want responses, get them
199
+ # if we want responses, get them
148
200
  if self.params['getResponse']:
149
201
  code = (
150
- "%(loop)s.addData('%(name)s.startResp', %(name)s.read())\n"
202
+ "%(loop)s.addData('%(name)s.startResp', %(name)s.getResponse())\n"
151
203
  )
152
204
  buff.writeIndented(code % params)
153
205
  # Dedent
@@ -156,24 +208,43 @@ class SerialOutComponent(BaseComponent):
156
208
  # On component stop, send stop pulse
157
209
  indented = self.writeStopTestCode(buff)
158
210
  if indented:
211
+ # get data string to write
212
+ if params['stopDataType'] == "str":
213
+ params['stopData'] = params['stopDataStr']
214
+ elif params['stopDataType'] == "num":
215
+ params['stopData'] = "bytes(bytearray([%(stopDataNumeric)s]))" % params
216
+ elif params['stopDataType'] == "binary":
217
+ params['stopData'] = "0b%(stopDataBinary)s" % params
218
+ elif params['stopDataType'] == "char":
219
+ params['stopData'] = "b'%(stopDataChar)s'" % params
220
+ elif params['stopDataType'] == "code":
221
+ params['stopData'] = params['stopDataCode']
222
+ else:
223
+ raise TypeError(f"Unknown data type {params['stopDataType']}")
224
+ # write code to send it (immediately or on refresh)
159
225
  if self.params['syncScreenRefresh']:
160
226
  code = (
161
- "win.callOnFlip(%(name)s.write, bytes(%(stopdata)s, 'utf8'))\n"
227
+ "win.callOnFlip(%(name)s.sendMessage, %(stopData)s)\n"
162
228
  )
163
229
  else:
164
230
  code = (
165
- "%(name)s.write(bytes(%(stopdata)s, 'utf8'))\n"
231
+ "%(name)s.sendMessage(%(stopData)s)\n"
166
232
  )
167
233
  buff.writeIndented(code % params)
168
- # Update status
234
+ # store code that was sent
235
+ code = (
236
+ "%(loop)s.addData('%(name)s.stopData', %(stopData)s)\n"
237
+ )
238
+ buff.writeIndented(code % params)
239
+ # update status
169
240
  code = (
170
241
  "%(name)s.status = FINISHED\n"
171
242
  )
172
243
  buff.writeIndented(code % params)
173
- # If we want responses, get them
244
+ # if we want responses, get them
174
245
  if self.params['getResponse']:
175
246
  code = (
176
- "%(loop)s.addData('%(name)s.stopResp', %(name)s.read())\n"
247
+ "%(loop)s.addData('%(name)s.stopResp', %(name)s.getResponse())\n"
177
248
  )
178
249
  buff.writeIndented(code % params)
179
250
  # Dedent
@@ -182,24 +253,52 @@ class SerialOutComponent(BaseComponent):
182
253
  def writeExperimentEndCode(self, buff):
183
254
  # Close the port
184
255
  code = (
185
- "# Close %(name)s\n"
186
- "if %(name)s.is_open:\n"
187
- " %(name)s.close()\n"
256
+ "# close %(name)s\n"
257
+ "if %(name)s.com.is_open:\n"
258
+ " %(name)s.com.close()\n"
188
259
  )
189
260
  buff.writeIndentedLines(code % self.params)
190
261
 
191
- def getDeviceVarName(self, case="camel"):
262
+
263
+ class SerialDeviceBackend(DeviceBackend):
264
+ backendLabel = "Serial Device"
265
+ deviceClass = "psychopy.hardware.serialdevice.SerialDevice"
266
+ icon = "light/serial.png"
267
+
268
+ def __init__(self, profile):
269
+ # init parent class
270
+ DeviceBackend.__init__(self, profile)
271
+
272
+ # define order
273
+ self.order += [
274
+ "timeout",
275
+ ]
276
+
277
+ self.params['timeout'] = Param(
278
+ "", valType='code', inputType="single", allowedTypes=[],
279
+ hint=_translate("Time at which to give up listening for a response (leave blank for no limit)"),
280
+ label=_translate("Timeout")
281
+ )
282
+
283
+ def writeDeviceCode(self, buff):
192
284
  """
193
- Create a variable name from the port address of this component's device.
285
+ Code to setup a device with this backend.
194
286
 
195
287
  Parameters
196
288
  ----------
197
- case : str
198
- Format of the variable name (see stringtools.makeValidVarName for info on accepted formats)
289
+ buff : io.StringIO
290
+ Text buffer to write code to.
199
291
  """
200
- # Add "serial_" in case port name is all numbers
201
- name = "serial_%(port)s" % self.params
202
- # Make valid
203
- varName = st.makeValidVarName(name, case=case)
292
+ inits = getInitVals(self.params)
293
+ # write basic code
294
+ self.writeBaseDeviceCode(buff, close=False)
295
+ # add param and close
296
+ code = (
297
+ " pauseDuration=(%(timeout)s or 0.1) / 3,\n"
298
+ ")\n"
299
+ )
300
+ buff.writeIndentedLines(code % inits)
301
+
204
302
 
205
- return varName
303
+ # register backend with Component
304
+ SerialOutComponent.registerBackend(SerialDeviceBackend)