psychopy 2025.1.1__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.
- psychopy/VERSION +1 -1
- psychopy/alerts/alertsCatalogue/4810.yaml +19 -0
- psychopy/alerts/alertsCatalogue/alertCategories.yaml +4 -0
- psychopy/alerts/alertsCatalogue/alertmsg.py +15 -1
- psychopy/alerts/alertsCatalogue/generateAlertmsg.py +2 -2
- psychopy/app/Resources/classic/add_many.png +0 -0
- psychopy/app/Resources/classic/add_many@2x.png +0 -0
- psychopy/app/Resources/classic/devices.png +0 -0
- psychopy/app/Resources/classic/devices@2x.png +0 -0
- psychopy/app/Resources/classic/photometer.png +0 -0
- psychopy/app/Resources/classic/photometer@2x.png +0 -0
- psychopy/app/Resources/dark/add_many.png +0 -0
- psychopy/app/Resources/dark/add_many@2x.png +0 -0
- psychopy/app/Resources/dark/devices.png +0 -0
- psychopy/app/Resources/dark/devices@2x.png +0 -0
- psychopy/app/Resources/dark/photometer.png +0 -0
- psychopy/app/Resources/dark/photometer@2x.png +0 -0
- psychopy/app/Resources/light/add_many.png +0 -0
- psychopy/app/Resources/light/add_many@2x.png +0 -0
- psychopy/app/Resources/light/devices.png +0 -0
- psychopy/app/Resources/light/devices@2x.png +0 -0
- psychopy/app/Resources/light/photometer.png +0 -0
- psychopy/app/Resources/light/photometer@2x.png +0 -0
- psychopy/app/_psychopyApp.py +35 -13
- psychopy/app/builder/builder.py +88 -35
- psychopy/app/builder/dialogs/__init__.py +69 -220
- psychopy/app/builder/dialogs/dlgsCode.py +29 -8
- psychopy/app/builder/dialogs/paramCtrls.py +1468 -904
- psychopy/app/builder/validators.py +25 -17
- psychopy/app/coder/coder.py +12 -1
- psychopy/app/coder/repl.py +5 -2
- psychopy/app/colorpicker/__init__.py +1 -1
- psychopy/app/deviceManager/__init__.py +1 -0
- psychopy/app/deviceManager/addDialog.py +218 -0
- psychopy/app/deviceManager/dialog.py +185 -0
- psychopy/app/deviceManager/panel.py +191 -0
- psychopy/app/deviceManager/utils.py +60 -0
- psychopy/app/idle.py +7 -0
- psychopy/app/locale/ar_001/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/ar_001/LC_MESSAGE/messages.po +12695 -10592
- psychopy/app/locale/cs_CZ/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/cs_CZ/LC_MESSAGE/messages.po +10199 -24
- psychopy/app/locale/da_DK/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/da_DK/LC_MESSAGE/messages.po +10199 -24
- psychopy/app/locale/de_DE/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/de_DE/LC_MESSAGE/messages.po +11221 -9712
- psychopy/app/locale/el_GR/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/el_GR/LC_MESSAGE/messages.po +10200 -25
- psychopy/app/locale/en_NZ/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/en_NZ/LC_MESSAGE/messages.po +10200 -25
- psychopy/app/locale/en_US/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/en_US/LC_MESSAGE/messages.po +10195 -18
- psychopy/app/locale/es_CO/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/es_CO/LC_MESSAGE/messages.po +11917 -9101
- psychopy/app/locale/es_ES/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/es_ES/LC_MESSAGE/messages.po +11924 -9103
- psychopy/app/locale/es_US/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/es_US/LC_MESSAGE/messages.po +11917 -9101
- psychopy/app/locale/et_EE/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/et_EE/LC_MESSAGE/messages.po +11084 -9569
- psychopy/app/locale/fa_IR/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/fa_IR/LC_MESSAGE/messages.po +11590 -5806
- psychopy/app/locale/fi_FI/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/fi_FI/LC_MESSAGE/messages.po +10199 -24
- psychopy/app/locale/fr_FR/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/fr_FR/LC_MESSAGE/messages.po +11091 -9577
- psychopy/app/locale/he_IL/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/he_IL/LC_MESSAGE/messages.po +11072 -9549
- psychopy/app/locale/hi_IN/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/hi_IN/LC_MESSAGE/messages.po +11071 -9559
- psychopy/app/locale/hu_HU/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/hu_HU/LC_MESSAGE/messages.po +10200 -25
- psychopy/app/locale/it_IT/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/it_IT/LC_MESSAGE/messages.po +11072 -9560
- psychopy/app/locale/ja_JP/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/ja_JP/LC_MESSAGE/messages.po +1485 -1137
- psychopy/app/locale/ko_KR/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/ko_KR/LC_MESSAGE/messages.po +10199 -24
- psychopy/app/locale/ms_MY/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/ms_MY/LC_MESSAGE/messages.po +11463 -8757
- psychopy/app/locale/nl_NL/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/nl_NL/LC_MESSAGE/messages.po +10200 -25
- psychopy/app/locale/nn_NO/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/nn_NO/LC_MESSAGE/messages.po +10200 -25
- psychopy/app/locale/pl_PL/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/pl_PL/LC_MESSAGE/messages.po +10200 -25
- psychopy/app/locale/pt_PT/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/pt_PT/LC_MESSAGE/messages.po +11288 -9434
- psychopy/app/locale/ro_RO/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/ro_RO/LC_MESSAGE/messages.po +10200 -25
- psychopy/app/locale/ru_RU/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/ru_RU/LC_MESSAGE/messages.po +10199 -24
- psychopy/app/locale/sv_SE/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/sv_SE/LC_MESSAGE/messages.po +11441 -8747
- psychopy/app/locale/tr_TR/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/tr_TR/LC_MESSAGE/messages.po +11069 -9545
- psychopy/app/locale/zh_CN/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/zh_CN/LC_MESSAGE/messages.po +12085 -8268
- psychopy/app/locale/zh_TW/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/zh_TW/LC_MESSAGE/messages.po +11929 -8022
- psychopy/app/plugin_manager/dialog.py +12 -3
- psychopy/app/plugin_manager/packageIndex.py +303 -0
- psychopy/app/plugin_manager/packages.py +203 -63
- psychopy/app/plugin_manager/plugins.py +120 -240
- psychopy/app/preferencesDlg.py +6 -1
- psychopy/app/psychopyApp.py +16 -4
- psychopy/app/runner/runner.py +10 -2
- psychopy/app/runner/scriptProcess.py +8 -3
- psychopy/app/stdout/stdOutRich.py +11 -4
- psychopy/app/themes/icons.py +3 -0
- psychopy/app/utils.py +61 -0
- psychopy/data/experiment.py +133 -23
- psychopy/data/routine.py +12 -0
- psychopy/data/staircase.py +42 -20
- psychopy/data/trial.py +20 -12
- psychopy/data/utils.py +42 -2
- psychopy/demos/builder/Experiments/dragAndDrop/drag_and_drop.psyexp +22 -5
- psychopy/demos/builder/Experiments/dragAndDrop/stimuli/solutions.xlsx +0 -0
- psychopy/demos/builder/Experiments/stroopVoice/stroopVoice.psyexp +2 -12
- psychopy/demos/builder/Feature Demos/buttonBox/buttonBoxDemo.psyexp +3 -8
- psychopy/demos/builder/Feature Demos/movies/movie.psyexp +220 -0
- psychopy/demos/builder/Feature Demos/movies/readme.md +3 -0
- psychopy/demos/builder/Feature Demos/visualValidator/visualValidator.psyexp +1 -2
- psychopy/demos/builder/Hardware/camera/camera.psyexp +3 -16
- psychopy/demos/builder/Hardware/microphone/microphone.psyexp +3 -16
- psychopy/demos/coder/hardware/hdf5_extract.py +133 -0
- psychopy/event.py +20 -15
- psychopy/experiment/_experiment.py +86 -10
- psychopy/experiment/components/__init__.py +3 -10
- psychopy/experiment/components/_base.py +9 -20
- psychopy/experiment/components/button/__init__.py +1 -1
- psychopy/experiment/components/buttonBox/__init__.py +50 -54
- psychopy/experiment/components/camera/__init__.py +137 -359
- psychopy/experiment/components/keyboard/__init__.py +17 -24
- psychopy/experiment/components/microphone/__init__.py +61 -110
- psychopy/experiment/components/movie/__init__.py +2 -3
- psychopy/experiment/components/serialOut/__init__.py +192 -93
- psychopy/experiment/components/settings/__init__.py +45 -27
- psychopy/experiment/components/sound/__init__.py +82 -73
- psychopy/experiment/components/soundsensor/__init__.py +43 -80
- psychopy/experiment/devices.py +303 -0
- psychopy/experiment/exports.py +20 -18
- psychopy/experiment/flow.py +7 -0
- psychopy/experiment/loops.py +47 -29
- psychopy/experiment/monitor.py +74 -0
- psychopy/experiment/params.py +48 -10
- psychopy/experiment/plugins.py +28 -108
- psychopy/experiment/py2js_transpiler.py +1 -1
- psychopy/experiment/routines/__init__.py +1 -1
- psychopy/experiment/routines/_base.py +59 -24
- psychopy/experiment/routines/audioValidator/__init__.py +19 -155
- psychopy/experiment/routines/visualValidator/__init__.py +25 -25
- psychopy/hardware/__init__.py +20 -57
- psychopy/hardware/button.py +15 -2
- psychopy/hardware/camera/__init__.py +2237 -1394
- psychopy/hardware/joystick/__init__.py +1 -1
- psychopy/hardware/keyboard.py +5 -8
- psychopy/hardware/listener.py +4 -1
- psychopy/hardware/manager.py +75 -35
- psychopy/hardware/microphone.py +52 -6
- psychopy/hardware/monitor.py +144 -0
- psychopy/hardware/photometer/__init__.py +156 -117
- psychopy/hardware/serialdevice.py +16 -2
- psychopy/hardware/soundsensor.py +4 -1
- psychopy/iohub/devices/deviceConfigValidation.py +2 -1
- psychopy/iohub/devices/keyboard/darwin.py +8 -5
- psychopy/iohub/util/__init__.py +7 -8
- psychopy/localization/generateTranslationTemplate.py +208 -116
- psychopy/localization/messages.pot +4305 -3502
- psychopy/monitors/MonitorCenter.py +174 -74
- psychopy/plugins/__init__.py +6 -4
- psychopy/preferences/devices.py +80 -0
- psychopy/preferences/generateHints.py +2 -1
- psychopy/preferences/preferences.py +35 -11
- psychopy/scripts/psychopy-pkgutil.py +969 -0
- psychopy/scripts/psyexpCompile.py +1 -1
- psychopy/session.py +34 -38
- psychopy/sound/__init__.py +6 -260
- psychopy/sound/audioclip.py +164 -0
- psychopy/sound/backend_ptb.py +8 -0
- psychopy/sound/backend_pygame.py +10 -0
- psychopy/sound/backend_pysound.py +9 -0
- psychopy/sound/backends/__init__.py +0 -0
- psychopy/sound/microphone.py +3 -0
- psychopy/sound/sound.py +58 -0
- psychopy/tests/data/correctScript/python/correctNoiseStimComponent.py +1 -1
- psychopy/tests/data/duplicateHeaders.csv +2 -0
- psychopy/tests/test_app/test_builder/test_BuilderFrame.py +22 -7
- psychopy/tests/test_app/test_builder/test_CompileFromBuilder.py +0 -2
- psychopy/tests/test_data/test_utils.py +5 -1
- psychopy/tests/test_experiment/test_components/test_ButtonBoxComponent.py +22 -2
- psychopy/tests/test_hardware/test_ports.py +0 -12
- psychopy/tests/test_tools/test_stringtools.py +1 -1
- psychopy/tools/attributetools.py +12 -5
- psychopy/tools/fontmanager.py +17 -14
- psychopy/tools/movietools.py +43 -2
- psychopy/tools/stringtools.py +33 -8
- psychopy/tools/versionchooser.py +1 -1
- psychopy/validation/audio.py +5 -1
- psychopy/validation/visual.py +5 -1
- psychopy/visual/basevisual.py +8 -7
- psychopy/visual/circle.py +2 -2
- psychopy/visual/image.py +29 -109
- psychopy/visual/movies/__init__.py +1800 -313
- psychopy/visual/polygon.py +4 -0
- psychopy/visual/shape.py +2 -2
- psychopy/visual/window.py +34 -11
- psychopy/voicekey/__init__.py +41 -669
- psychopy/voicekey/labjack_vks.py +7 -48
- psychopy/voicekey/parallel_vks.py +7 -42
- psychopy/voicekey/vk_tools.py +114 -263
- {psychopy-2025.1.1.dist-info → psychopy-2025.2.1.dist-info}/METADATA +17 -11
- {psychopy-2025.1.1.dist-info → psychopy-2025.2.1.dist-info}/RECORD +216 -184
- {psychopy-2025.1.1.dist-info → psychopy-2025.2.1.dist-info}/WHEEL +1 -1
- psychopy/visual/movies/players/__init__.py +0 -62
- psychopy/visual/movies/players/ffpyplayer_player.py +0 -1401
- psychopy/voicekey/demo_vks.py +0 -12
- psychopy/voicekey/signal.py +0 -42
- {psychopy-2025.1.1.dist-info → psychopy-2025.2.1.dist-info}/entry_points.txt +0 -0
- {psychopy-2025.1.1.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['
|
|
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
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
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.
|
|
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
|
|
11
|
+
from psychopy.experiment.components import BaseDeviceComponent, Param, _translate, getInitVals
|
|
11
12
|
|
|
12
13
|
|
|
13
|
-
class SerialOutComponent(
|
|
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
|
-
|
|
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
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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
|
-
#
|
|
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
|
|
118
|
-
"%(name)s = %(
|
|
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.
|
|
182
|
+
"win.callOnFlip(%(name)s.sendMessage, %(startData)s)\n"
|
|
136
183
|
)
|
|
137
184
|
else:
|
|
138
185
|
code = (
|
|
139
|
-
"%(name)s.
|
|
186
|
+
"%(name)s.sendMessage(%(startData)s)\n"
|
|
140
187
|
)
|
|
141
188
|
buff.writeIndented(code % params)
|
|
142
|
-
#
|
|
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
|
-
#
|
|
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.
|
|
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.
|
|
227
|
+
"win.callOnFlip(%(name)s.sendMessage, %(stopData)s)\n"
|
|
162
228
|
)
|
|
163
229
|
else:
|
|
164
230
|
code = (
|
|
165
|
-
"%(name)s.
|
|
231
|
+
"%(name)s.sendMessage(%(stopData)s)\n"
|
|
166
232
|
)
|
|
167
233
|
buff.writeIndented(code % params)
|
|
168
|
-
#
|
|
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
|
-
#
|
|
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.
|
|
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
|
-
"#
|
|
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
|
-
|
|
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
|
-
|
|
285
|
+
Code to setup a device with this backend.
|
|
194
286
|
|
|
195
287
|
Parameters
|
|
196
288
|
----------
|
|
197
|
-
|
|
198
|
-
|
|
289
|
+
buff : io.StringIO
|
|
290
|
+
Text buffer to write code to.
|
|
199
291
|
"""
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
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
|
-
|
|
303
|
+
# register backend with Component
|
|
304
|
+
SerialOutComponent.registerBackend(SerialDeviceBackend)
|