psi.controller package

Subpackages

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.controller_commands.accumulate_actions(plugin)[source]
psi.controller.controller_commands.accumulate_io_tree(plugin)[source]
psi.controller.controller_commands.get_hw_ai_choices(workbench)[source]
psi.controller.controller_commands.get_hw_ao_choices(workbench)[source]
psi.controller.controller_commands.invoke_actions(event)[source]
psi.controller.controller_commands.log_actions(event)[source]
psi.controller.controller_commands.log_commands(event)[source]
psi.controller.controller_commands.log_events(event)[source]
psi.controller.controller_commands.log_io(event)[source]
psi.controller.controller_commands.start_experiment(event)[source]
psi.controller.controller_commands.stop_experiment(event)[source]

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_sync preserves the historical synchronous semantics of invoke_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.

  • submit is fire-and-forget for callers that must never block (e.g., high-rate acquisition callbacks). Exceptions are logged.

  • call_later schedules 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_sync accepts an optional pump callback for GUI-thread callers. Some Windows audio backends require certain PortAudio calls (observed with ASIO devices opened via psi.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 via enaml.application.deferred_call from 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. See ControllerPlugin.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.

cancel_all()[source]
is_dispatcher_thread()[source]
stop()[source]
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.clear_output(event, output)[source]
psi.controller.output_commands.clear_synchronized(synchronized, event)[source]
psi.controller.output_commands.decrement_key(event, output)[source]
psi.controller.output_commands.get_pause_text(contribution, is_paused)[source]
psi.controller.output_commands.get_tokens(workbench, ttype)[source]
psi.controller.output_commands.output_pause(event, output)[source]
psi.controller.output_commands.output_resume(event, output)[source]
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.output_commands.prepare_output_queue(event, output)[source]
psi.controller.output_commands.prepare_synchronized(synchronized, event)[source]
psi.controller.output_commands.set_token(event, output, output_type)[source]
psi.controller.output_commands.start_output(event, output)[source]

clear_delay : The time at which to stop the output if it’s currently active.

psi.controller.output_commands.start_synchronized(synchronized, event)[source]
psi.controller.output_commands.subscribe_to_queue(event, output)[source]
psi.controller.output_commands.toggle(event, output)[source]
psi.controller.output_commands.toggle_off(event, output)[source]
psi.controller.output_commands.toggle_on(event, output)[source]

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.generate_waveform(output, context)[source]
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

Module contents