Experiment example (Psychtoolbox): Two-tone passive auditory localizer
Description
A passive auditory experiment for the KIT MEG system. The participant fixates a static red dot on a grey screen and listens to two pure tones (default 500 Hz and 1000 Hz) through the foam insert earphones. There is no task and no response. It gives a clean auditory evoked response (M50/M100) for each tone and is suitable for participants with epilepsy: the screen never changes, every tone has soft on/off ramps, and the level is capped.
Code:
experiments/psychtoolbox/auditory_two_tones
(start with main.m).
The two tones are played equally often in a constrained random order (never more than three identical tones in a row). Onset-to-onset intervals (SOA) are jittered uniformly between a minimum and a maximum, but the run length is still exact: the SOAs are evenly spaced between the two limits and shuffled.
A 2 s silent baseline follows the run-start trigger; each tone has an onset
trigger (by tone) and an offset trigger; run_end follows the last SOA.
Sound and trigger timing
Audio route
Sound is played with PsychPortAudio on the stimulus PC’s default output
device (low-latency mode, 48 kHz, stereo, same tone in both ears), the same
route as the lab’s other KIT auditory tasks (Visual_Imagery, oddball,
auditory_vs_visual), to the participant’s foam insert earphones.
Locking the sound to the trigger
The older auditory scripts start the sound and then raise the trigger on the next screen flip, so the sound-to-trigger lag is unknown. Here the sound is scheduled for the exact video frame that carries its trigger:
About 0.1 s before a tone, a normal (non-trigger) frame is flipped. Its real flip time anchors the schedule, so an error in the measured refresh interval cannot accumulate over the run.
PsychPortAudio('Start', pa, 1, onsetFrameTime)schedules the first sample of the tone for the predicted time of the trigger frame, a whole number of frames after the anchor.The trigger frame is flipped at that time (KIT 224 for the low tone, 225 for the high tone) and cleared on the next frame.
The offset trigger (KIT 226) is placed on the frame nearest the end of the sound.
After the tone, PsychPortAudio’s own report of when the sound started is read and logged. Production stops if it differs from the trigger frame by more than 2 ms or if the audio device reports an underrun.
In simulation, the sound was scheduled within 0.03 ms of its trigger frame for 120 tones even with a 2 µs error in the measured refresh interval.
One default tone against the 120 Hz frame grid. With a 0.1 s tone the offset
frame coincides with the end of the sound; for durations that are not whole
frames, the residual is logged as trigger_delay.
Each event’s trigger_delay (pulse time minus sound time, using the
PsychPortAudio-reported start) is written to events.tsv. When onsets are
filled from the recording, onset = pulse - trigger_delay is the sound
itself, not the frame.
Important
The delay from the sound card to the eardrum (driver, amplifier, cables and tubing) is not included. Measure it once on the rig (for example with a microphone at the earphone, or a copy of the audio on a MEG analog channel) and add it when epoching, as is done for the projector lag in Display and trigger timing.
Trigger protocol
Single-channel DataPixx pixel mode, same RGB codes as trigger_template.m.
Event |
KIT channel |
MNE channel |
Pixel RGB |
When |
|---|---|---|---|---|
|
224 |
|
|
Frame on which the low tone starts |
|
225 |
|
|
Frame on which the high tone starts |
|
226 |
|
|
Frame nearest the end of either tone |
|
228 |
|
|
Start of the silent baseline |
|
229 |
|
|
End of the last SOA (completed runs only) |
A completed run has 2 x nTrials + 2 pulses (242 by default), each lasting
one 120 Hz frame and each with exactly one row in events.tsv. The screen
background is grey, so the trigger pixel is drawn black on every other frame.
Pixel mode is enabled only after SPACE and disabled before the end screen.
Timing robustness on the stimulus PC
30 trigger-free warm-up frames (0.25 s,
cfg.warmupFrames) follow SPACE before the run-start pulse. The first flips after the start screen can return a bad timestamp on the stimulus PC (PTB “beamposition timestamping computed an impossible stimulus onset”); the warm-up keeps that away from every trigger frame.Every timing check outside tolerance is logged in the
*_okcolumns and printed as a warning. Timing never stops a run.A pulse whose flip timestamps give less than half a frame (impossible for a 1-frame pulse) is a timestamp error, not a trigger error: it is counted in
RunInfo.n_timestamp_anomaliesinbeh.jsonand never stops the run.
Safety limits
The defaults and hard limits (cfg.safety in
two_tones_config.m)
are enforced in the launcher and again at the start of every run:
Parameter |
Default / limit |
Why |
|---|---|---|
Level |
-2 dBFS (0.79 of full scale, as the lab’s other KIT tone tasks) / -60 to -1 dBFS |
Prevents a typo from producing a much louder sound than calibrated. |
On/off ramp |
10 ms / at least 5 ms, at most 25 % of the tone |
No clicks or startling onsets. |
Frequencies |
500 and 1000 Hz / 250-4000 Hz, high above low |
Comfortable range for insert earphones. |
Tone duration |
0.1 s / 0.05-1.0 s |
|
SOA (jittered) |
1.2-1.6 s / 0.8-5.0 s, silence between tones at least 0.5 s |
Gentle rate; avoids entrainment. |
Run length |
at most 15 min |
Split longer sessions into runs. |
Warning
dBFS is a digital level, not loudness at the ear. Calibrate the chain once with a sound level meter or ear simulator so the default is comfortable (about 60-70 dB SPL), then keep the Windows volume and amplifier fixed. Before each session, use Play test tones in the launcher with the participant. It plays the tones through the same PsychPortAudio device and mode as the run. In low-latency mode Windows can bypass the system mixer, so other players may sound quieter than the run.
The visual display is completely static (grey background, red dot, text before and after), with Psychtoolbox splash screens disabled and a 2 s fade to black when the window closes. Set the projector desktop background to black.
Running the experiment
cd experiments/psychtoolbox/auditory_two_tones
main % launcher (production preset selected)
main('validate') % headless self-test, no Psychtoolbox needed
The launcher recomputes the run length whenever a value changes (press Enter or Tab), together with the number of tones, rate, silence and level. Start stays disabled while anything is invalid or outside the limits.
Preset |
DataPixx |
Display and checks |
Output folder |
|---|---|---|---|
Production |
ON |
Full screen, 120 Hz required, timing and audio sync logged, never fatal |
|
Full rehearsal |
OFF |
Full screen, same audio checks |
|
Debug |
OFF |
Windowed, 50 px visible trigger patch, faults logged only |
|
run length = 2 s baseline + nTrials x (SOA min + SOA max) / 2
Tones |
Run length (SOA 1.2-1.6 s) |
Note |
|---|---|---|
120 |
2 min 50 s |
Default (60 per tone) |
200 |
4 min 42 s |
|
400 |
9 min 22 s |
|
600 |
14 min 02 s |
Close to the 15 min limit |
Scripted launch (safety limits still apply):
spec = struct('subject', 'P01', 'session', '01', 'mode', 'debug');
spec.params = struct('nTrials', 10); % plain doubles
main(spec)
Operator keys: SPACE starts, ESC or Q stops (a tone that is already playing finishes its ramp; partial data are saved).
The console prints a progress line after each trial (Trial 17/120 done, 103
left, about 3 min 26 s to go). A run stopped early keeps the standard file
names with RunStatus aborted in beh.json; events.tsv has
exactly one row per trigger actually sent, so its onsets can be filled from the
MEG recording and the completed trials analysed.
Before the session
One-time: calibrate the sound level and measure the sound-card-to-ear delay.
Run
main('validate')after any change to the code or configuration.Check channels 224, 225, 226, 228 and 229 with test_all_meg_channels_triggers.m if the setup or wiring changed.
Use Play test tones with the participant in place.
Start KIT acquisition, then press SPACE.
Saved data (BIDS)
data/
dataset_description.json, README
sub-P01/ses-01/meg/sub-P01_ses-01_task-twotones_run-01_events.tsv / .json
sub-P01/ses-01/beh/sub-P01_ses-01_task-twotones_run-01_beh.tsv / .json
stimuli/task-twotones/tone-low_freq-500Hz_dur-100ms_level-m02dBFS_h-<hash>.wav
sourcedata/sub-P01/ses-01/beh/..._desc-snapshot_beh.mat, ..._desc-matlabconsole_log.txt
events.tsv has one row per trigger pulse in time order. Its columns are
onset, duration, trial_type, value, sample, channel,
tone, frequency_hz, stim_file, onset_from_run_start,
trigger_delay, presentation_index, level_dbfs,
audio_start_minus_trigger and hardware_emitted.
As in the other lab experiments,
onsetandsamplearen/auntil they are filled from the recording.valueequals the KIT channel, and onset and offset rows of the same tone sharepresentation_index.events.jsondescribes every column. It also holds:"TriggerMode": "single_channel", the channel table and the expected pulse count per channel;the trigger definitions and the onset rule;
the audio route and sound parameters;
the measured refresh rate;
RunStatusandRunComplete.
beh.tsv has one row per planned tone. It logs the frame and audio times
(planned, anchored, flipped, PsychPortAudio start and stop), the
audio-minus-trigger difference, underruns, SOA and pulse checks, and missed
flips. The WAV files are the exact sounds played. The file name contains a
hash of the samples, so different settings never share a file.
Preprocessing
Fill onset/sample from the recording (after MNE-BIDS conversion, which
writes its own events file to the same path):
python experiments/psychtoolbox/auditory_two_tones/fill_events_onsets.py \
--events data/sub-P01/ses-01/meg/sub-P01_ses-01_task-twotones_run-01_events.tsv \
--raw <recording>.con \
--out <BIDS root>/sub-P01/ses-01/meg/sub-P01_ses-01_task-twotones_run-01_events.tsv
The script uses the lab’s pulse detector on MISC 001-MISC 008 and
matches every logged pulse one-to-one, in order. It then sets
onset = pulse - trigger_delay. If any pulse is missing or extra, it writes
nothing. Epoch on the tone_low/tone_high rows (values 224/225) and add
the measured sound-card-to-ear delay.
Validation
main('validate') checks:
the trigger map;
the presets and limits, and that the default run length is 2 min 50 s;
that 15 unsafe or invalid settings are refused;
the generated tones: frequency, peak level, ramps, zero first sample, the same signal in both ears;
120 random schedules: balance, run constraint, exact jittered length;
every output file for a completed and an aborted run.
During development the run loop was exercised against a simulated 120 Hz Psychtoolbox, DataPixx and PsychPortAudio, checking:
every pulse lasted one frame with the correct code;
every tone was scheduled exactly on its trigger frame, with the right sound;
the pulse sequence matched
events.tsv, including after an abort;the onset-filling script recovered exact onsets from a synthetic recording, including the offset residual for a 0.105 s tone.
Confirm on the rig with a recorded rehearsal before the first participant.