Waveforms and Signals
Waveform generation in psiexperiment is handled by the Token plugin (psi.token). It provides a high-performance system for generating signals ranging from simple tones and clicks to complex noise masks and chirps.
The Token System
A Token is a declarative block that describes a signal. Tokens are composed of Blocks that can be chained together to create complex waveforms. For example, you can create a “Tone Pip” by chaining a Tone block with a CosineEnvelope block.
Tokens are defined in your manifest by contributing to the psi.token.tokens extension point:
from psi.token.api import Token, Tone, CosineEnvelope
Extension:
id = manifest.id + '.tokens'
point = 'psi.token.tokens'
Token:
name = 'tone_pip'
# Parameters can be tied to context items
Tone:
freq = C.frequency
level = C.level
CosineEnvelope:
duration = C.duration
rise_time = 0.005
Continuous vs. Epoch Signals
Psiexperiment distinguishes between two primary types of signal presentation:
Continuous Signals
These are signals that have an effectively infinite duration, such as a background noise masker. * Infinite Loop: The engine continuously pulls samples from these generators. * Gapless: Unlike many other systems, psiexperiment does not repeat short segments of noise. It generates unique samples for the entire duration of the experiment.
Epoch Signals
These are finite stimuli, such as a 5ms click or a 50ms tone pip.
* On-Demand: These signals are triggered by experimental events (e.g., trial_start).
* Queued: Multiple epoch stimuli can be queued for presentation at precise times relative to each other.
Hardware Engines and Buffers
The hardware engine is responsible for the actual delivery of samples to the digital-to-analog converter (DAC).
Polling: The engine continuously “polls” all active outputs (continuous and epoch-based) for new samples.
Buffering: To prevent audio glitches if the computer is momentarily busy, the engine maintains a large output buffer (typically 1-5 seconds of audio).
Zero-Fill: If no stimulus is active (e.g., during an inter-trial interval), the engine automatically fills the buffer with zeros.
Controlling Waveforms
While signals are often triggered automatically via actions, they can also be controlled programmatically using Commands.
# Get the workbench core plugin
core = workbench.get_plugin('enaml.workbench.core')
# Tell an output named 'target' to prepare its next stimulus
core.invoke_command('target.prepare')
# Tell the output to start playing at a specific timestamp (in seconds)
core.invoke_command('target.start', {'ts': 10.5})
By using this command-driven approach, you can synchronize stimulus presentation with other experimental events across different plugins.