psi.controller package
Subpackages
- psi.controller.calibration package
- Submodules
- psi.controller.calibration.acquire module
- psi.controller.calibration.api module
- psi.controller.calibration.calibrate module
- psi.controller.calibration.chirp module
- psi.controller.calibration.click module
- psi.controller.calibration.plugin module
- psi.controller.calibration.tone module
- Module contents
- psi.controller.engines package
- Subpackages
- Submodules
- psi.controller.engines.callback module
BaseCallbackMixinChannelSliceCallbackMixinChannelSliceCallbackMixin.register_ai_callback()ChannelSliceCallbackMixin.register_ao_callback()ChannelSliceCallbackMixin.register_ci_callback()ChannelSliceCallbackMixin.register_di_callback()ChannelSliceCallbackMixin.register_done_callback()ChannelSliceCallbackMixin.unregister_ai_callback()ChannelSliceCallbackMixin.unregister_ao_callback()ChannelSliceCallbackMixin.unregister_di_callback()ChannelSliceCallbackMixin.unregister_done_callback()
- psi.controller.engines.nidaq module
- psi.controller.engines.null module
- psi.controller.engines.thread module
- Module contents
Submodules
psi.controller.api module
psi.controller.channel module
psi.controller.controller_commands module
Command handlers for the controller manifest.
These functions implement the behavior behind the workbench commands
declared in psi.controller.manifest. They are kept in a plain Python
module so they can be imported and tested without the Enaml import
machinery.
psi.controller.dispatcher module
Single-owner dispatcher for the experiment control plane.
All experiment-control decisions (action matching/invocation, the action context, delayed events) execute on one dedicated thread. Data-plane threads (hardware acquisition callbacks, input pipelines) and the GUI thread request control work through this dispatcher instead of mutating shared state directly. See docs/threading.md for the full contract.
Design notes
submit_syncpreserves the historical synchronous semantics ofinvoke_actions: the caller blocks until the work completes, receives its return value, and sees its exceptions. When called from the dispatcher thread (an action triggering another action), the work runs inline so recursion cannot deadlock.submitis fire-and-forget for callers that must never block (e.g., high-rate acquisition callbacks). Exceptions are logged.call_laterschedules a named callable. The timer thread only enqueues: user code always runs on the dispatcher thread.On Windows, the dispatcher thread is COM-initialized (STA) for its entire lifetime, since some COM-based audio APIs raise/hang if a thread touches them without this (see https://github.com/PortAudio/portaudio/issues/250).
submit_syncaccepts an optionalpumpcallback for GUI-thread callers. Some Windows audio backends require certain PortAudio calls (observed with ASIO devices opened viapsi.controller.engines. soundcard) to run on the thread that owns the application’s Windows message loop, not on this dispatcher thread. Those calls are marshaled to the GUI thread viaenaml.application.deferred_callfrom within dispatched control-plane work – but the GUI thread can only service that if it keeps pumping its event loop while it waits here, instead of blocking outright. SeeControllerPlugin.invoke_actions.
- class psi.controller.dispatcher.ControlDispatcher(name='control-dispatcher')[source]
Bases:
object- call_later(name, delay, fn, *args, cancel_existing=True)[source]
Schedule fn to run on the dispatcher thread after delay seconds.
A subsequent call with the same name cancels the pending one (unless cancel_existing is False).
- cancel(name)[source]
Cancel the named delayed call. No-op if it does not exist or has already fired.
- submit(fn, *args, **kwargs)[source]
Run fn on the dispatcher thread without waiting (fire-and-forget). Exceptions are logged, not raised.
- submit_sync(fn, *args, pump=None, **kwargs)[source]
Run fn on the dispatcher thread and block until it completes.
Returns fn’s return value; exceptions propagate to the caller. If already on the dispatcher thread, runs inline (allowing actions to recursively invoke actions).
- Parameters:
pump (callable, optional) – If given, called repeatedly instead of blocking indefinitely while waiting for the result. Needed when the calling thread must keep servicing its own event loop for the dispatched work to complete (e.g., the GUI thread pumping Qt events so a deferred_call issued from the dispatcher thread mid-flight can run) – otherwise the two threads wait on each other forever.
psi.controller.engine module
psi.controller.input module
psi.controller.io_manager module
psi.controller.output module
psi.controller.output_commands module
Command handlers for output manifests.
These functions implement the behavior behind the workbench commands
declared in psi.controller.output_manifest. They are kept in a plain
Python module so they can be imported and tested without the Enaml import
machinery.
- psi.controller.output_commands.prepare_output(event, output)[source]
Set up the factory in preparation for producing the signal. This allows the factory to cache some potentially expensive computations in advance rather than just before we actually want the signal played.
psi.controller.plugin module
psi.controller.token_context module
Machinery for wiring stimulus token blocks into the context system.
A token (see psi.token) is a declarative tree of blocks (e.g., a tone
nested inside an envelope), where each block contributes parameters (e.g.,
frequency, rise time). When a token is assigned to an output, each block
parameter is registered with the context plugin under a globally unique name
({output}_{block}_{parameter}). At stimulus-generation time, the values
for those context items are mapped back to each block’s own parameter names
and used to instantiate the block’s waveform factory.
The mapping between global context names and block parameter names is stored
on the output itself (see BaseOutput._block_context_map), keyed by block.
Historically this was a module-level global keyed by (output, block) that
was never cleaned up.
- psi.controller.token_context.get_parameters(output, block)[source]
Return the (global) context item names for all parameters in the block hierarchy. Requires load_items to have been called first.
- psi.controller.token_context.initialize_factory(output, block, context)[source]
Instantiate the waveform factory for the block hierarchy.
- Parameters:
output (BaseOutput) – Output the token is assigned to.
block (Block) – Root block of the token.
context (dict) – Mapping of global context item names to values. May also provide fs and calibration; if absent, they are read from the output.
- psi.controller.token_context.load_items(output, block)[source]
Create the context items for all parameters in the block hierarchy.
Each parameter is copied and renamed to
{output}_{block}_{parameter}so it can be registered with the context plugin without colliding with other outputs using the same token. The mapping from the new (global) name back to the block’s own parameter name is recorded on the output.- Parameters:
output (BaseOutput) – Output the token is assigned to.
block (Block or None) – Root block of the token.
- Returns:
parameters – Renamed copies of all parameters in the block hierarchy, ready to be contributed to the context plugin.
- Return type:
list of Parameter
psi.controller.util module
- psi.controller.util.acquire(engine, waveform, ao_channel_name, ai_channel_names, gain=0, vrms=1, repetitions=2, min_snr=None, max_thd=None, thd_harmonics=3, trim=0.01, iti=0.01, debug=False)[source]
Given a single output, measure response in multiple input channels.
- Parameters:
TODO
- Returns:
result – TODO
- Return type:
array