11from psychopy import core , visual , logging , sound
22from psychopy .hardware .keyboard import Keyboard
3- from typing import Callable , Optional , List , Dict , Any , Union
3+ from typing import Callable , Optional , List , Dict , Any , Sequence , TypeAlias , Union
4+ import importlib
45import random
56from .qa .context import get_context
67from .io .events import TriggerEvent
8+ from psychopy .sound ._base import _SoundBase
9+
10+
11+ def _resolve_audio_stim_types () -> tuple [type , ...]:
12+ """Resolve concrete PsychoPy sound classes used at runtime."""
13+ types : list [type ] = [_SoundBase ]
14+ candidates = (
15+ ("psychopy.sound.backend_ptb" , "SoundPTB" ),
16+ ("psychopy.sound.backend_sounddevice" , "SoundDeviceSound" ),
17+ ("psychopy.sound.backend_pyo" , "SoundPyo" ),
18+ ("psychopy.sound.backend_pygame" , "SoundPygame" ),
19+ )
20+ for module_name , class_name in candidates :
21+ try :
22+ module = importlib .import_module (module_name )
23+ cls = getattr (module , class_name , None )
24+ if isinstance (cls , type ) and cls not in types :
25+ types .append (cls )
26+ except Exception :
27+ continue
28+ return tuple (types )
29+
30+
31+ AUDIO_STIM_TYPES = _resolve_audio_stim_types ()
32+ SUPPORTED_STIM_TYPES = (visual .BaseVisualStim ,) + AUDIO_STIM_TYPES
33+ SupportedStim : TypeAlias = Union [visual .BaseVisualStim , _SoundBase ]
734
835class StimUnit :
936 """
@@ -78,7 +105,7 @@ def _qa_scale_duration(self, nominal_s: float) -> tuple[float, int, bool]:
78105 used = max (self .frame_time , n_frames * self .frame_time )
79106 return used , n_frames , True
80107
81- def add_stim (self , * stims : Union [visual . BaseVisualStim , sound . Sound , List [ Union [ visual . BaseVisualStim , sound . Sound ] ]]) -> "StimUnit" :
108+ def add_stim (self , * stims : Union [SupportedStim , Sequence [ SupportedStim ]]) -> "StimUnit" :
82109 """
83110 Add one or more visual or sound stimuli to the trial.
84111
@@ -101,8 +128,14 @@ def add_stim(self, *stims: Union[visual.BaseVisualStim, sound.Sound, List[Union[
101128 stims = stims [0 ]
102129
103130 for stim in stims :
104- if not isinstance (stim , (visual .BaseVisualStim , sound .Sound )):
105- raise TypeError (f"add_stim expects visual or sound stimuli, got { type (stim )} " )
131+ if not isinstance (stim , SUPPORTED_STIM_TYPES ):
132+ supported = ", " .join (sorted ({t .__name__ for t in SUPPORTED_STIM_TYPES }))
133+ msg = (
134+ "add_stim got unsupported object type "
135+ f"{ type (stim ).__name__ } . Supported types include: { supported } "
136+ )
137+ logging .warning (f"[StimUnit] { msg } " )
138+ raise TypeError (msg )
106139 self .stimuli .append (stim )
107140
108141 return self
0 commit comments