Lab Streaming Layer (LSL)
Put event markers into the same LabRecorder file as your eye tracker or EEG — on one clock, no trigger box.
Run your study in the browser and record it alongside your eye tracker, EEG, fNIRS or any other LSL stream, the way PsychoPy and Psychtoolbox labs do. Every marker your experiment sends lands in LabRecorder’s XDF file on the same clock as your device data. Your experiment code does not change.
LSL or a TTL trigger box?
- LSL — codes and text labels into LabRecorder alongside every other stream. Needs a small bridge program on the lab computer; use Chrome or Edge.
- TTL — a number 1–255 on a recorder’s trigger input. Needs a USB trigger box and Chrome or Edge. See the Lab triggers (TTL) guide.
- Both at once — for example TTL to the EEG amplifier and LSL for the eye-tracking recording. The same markers go to both.
How the pieces fit
Three programs run on the lab computer during a session: your device’s LSL app (sends the eye or brain signal), the VibeMyExpt bridge (sends your experiment’s markers), and LabRecorder (saves both into one .xdf file). The study itself runs in Chrome on that same computer. A web page cannot talk to LSL by itself — that is the only reason the bridge exists.
One-time setup on the lab computer
- 1
Install uv (runs the bridge)
Windows: press Start, type PowerShell and open it. Paste
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"and press Enter. When it finishes, close PowerShell — the next window you open will know the new command. Mac: open Terminal and runcurl -LsSf https://astral.sh/uv/install.sh | sh.uv is a small, free tool from Astral. It downloads the bridge for you the first time and keeps it up to date — nothing else to install.
- 2
Install LabRecorder
Download the Windows zip from github.com/labstreaminglayer/App-LabRecorder/releases (under Assets, the file ending in
Win_amd64.zip). Right-click → Extract All, put the folder somewhere easy such asC:\LabRecorder, and double-clickLabRecorder.exe. If Windows says “Windows protected your PC”, click More info → Run anyway.If it complains about a missing MSVCP140.dll, install Microsoft’s free Visual C++ Redistributable (x64) and try again.
- 3
Install your device’s LSL app
Your eye tracker or amplifier maker provides it (often called an “LSL connector”, “LSL relay” or “LSL app”). Check it appears in LabRecorder after you click Update.
- 4
Turn on LSL for your study
Project → Settings → Lab triggers → tick “Send event markers to Lab Streaming Layer” → Save settings. Resume is switched off automatically, because a resumed session would repeat markers in your recording.
- 5
Add markers to your experiment
In the AI editor, say what should be marked in plain language, e.g. “send marker 10 when the picture appears and 20 when they press a key, and mark the start and end of each block”. Or paste the prompt below. Publish the project when you are happy.
Every marker number must be different — two conditions sharing a number cannot be told apart afterwards.
Add lab markers to an experiment
Paste into the AI editor. Works for LSL, TTL or both — the delivery is set in project settings, not in code.
Add event markers to this experiment for a lab recording (EEG / eye tracking via Lab Streaming Layer or a TTL trigger box).
- Use window.__TRIGGER only. It is always defined; do not feature-detect it. Do not write LSL, WebSocket or serial code — the platform delivers the markers.
- Put the codes in CONFIG.triggers as DISTINCT integers 1-255, e.g. { fixation: 1, stim_onset: 10, response: 20, feedback: 30 }.
- Send stimulus-onset markers in the trial's on_load: this.trigger_t = __TRIGGER.send(CONFIG.triggers.stim_onset). Send response markers in on_finish. Never inside setTimeout, never awaited.
- Add trigger_code and trigger_t to each trial's data.
- Mark block boundaries with __TRIGGER.mark('block_1_start') / __TRIGGER.mark('block_1_end').
- Keep the existing initJsPsych data protocol (vibemyexpt:trial_data / vibemyexpt:experiment_complete) unchanged.Every session
- 1
Start your device
Turn on the eye tracker or amplifier and start its LSL app, as usual.
- 2
Start the bridge
Open PowerShell (Mac: Terminal), type
uvx vibemyexpt-lsland press Enter. Leave this window open until the session is over — closing it stops the markers.It should say “listening on ws://127.0.0.1:17654”. The first time takes a few seconds while it downloads.
- 3
Start recording in LabRecorder
Click Update, tick “VibeMyExpt Markers” and your device’s streams, choose a folder under Study Root, and click Start. Start before the participant gives consent and keep recording for the whole session.
- 4
Open the study in Chrome
On the same computer, open the study link (
/take/…), not the Preview button — Preview skips the hardware check. If Chrome asks to allow access to devices on your local network, click Allow. - 5
Pass the check screen
It should show “LSL bridge connected” and “LabRecorder subscribed”. Click “Send test markers”, tick the confirmation box and hand over to the participant.
- 6
Finish up
When the participant is done, click Stop in LabRecorder — your .xdf file is in the Study Root folder. Close the bridge window (or press Ctrl+C in it).
What you should see when it works
- Bridge window: “Study page connected” when the check screen opens, “LabRecorder subscribed” when you press Start in LabRecorder.
- Check screen: a green “LabRecorder subscribed” and “Pushed to LSL 3 / 3” after the test markers.
- LabRecorder: “VibeMyExpt Markers” in the stream list, and the recording file growing while it runs.
What ends up in your data
In the XDF and the export
- XDF: a “VibeMyExpt Markers” stream.
send(12)arrives as "12",mark('block_start')as "block_start", andsession_start:/session_end:markers bracket each participant. -
events.tsv: every marker with its trial context and anlsl_timestampcolumn — the exact stamp that marker carries in the XDF. -
sessions.csv:lsl_markers_acked, and whether LabRecorder was subscribed when the pre-flight ran.
Join events.tsv to the XDF (Python)
For whoever analyses the data: attaches each marker’s trial information to the markers in the recording, using the shared lsl_timestamp.
import pandas as pd
import pyxdf
# Raw stamps (no clock correction) are exactly what events.tsv recorded in lsl_timestamp.
streams, _ = pyxdf.load_xdf("sub-01.xdf", synchronize_clocks=False, dejitter_timestamps=False)
mk = next(s for s in streams if s["info"]["name"][0] == "VibeMyExpt Markers")
xdf = pd.DataFrame({"lsl_timestamp": mk["time_stamps"], "xdf_value": [v[0] for v in mk["time_series"]]})
ev = pd.read_csv("events.tsv", sep="\t")
ev["lsl_timestamp"] = pd.to_numeric(ev["lsl_timestamp"], errors="coerce")
ev = ev.dropna(subset=["lsl_timestamp"]).sort_values("lsl_timestamp")
aligned = pd.merge_asof(ev, xdf.sort_values("lsl_timestamp"), on="lsl_timestamp",
direction="nearest", tolerance=1e-6)