OS: Windows 10
PsychoPy version: 2023.2.3
Standard Standalone? (y/n): y
We are building a task in 2023.2.3 (we are bound to this version) that plays sounds in a predefined very fast succession with concurrent EEG monitoring. We are encountering issues with the timings of triggers whereby sometimes triggers are skipped and not recorded in the EEG.
The basic structure of the task is to send 3 pure tones on each trial (defined in PsychoPy, no external audio files are read), and send a TTL pulse to the trigger box at the onset of each tone. The tones are each 100 ms long, with 20 ms of silence in between them. When we tested the task, we found that the triggers are sometimes skipped (we measured 38 skipped triggers out of 2460), and are looking for suggestions on how to solve this issue.
The TTL triggers are sent out via the following code component:
# 1- Begin experiment tab:
import serial
s_port = serial.Serial() #serial port name
s_port.port = 'COM3' # chosen serial port
s_port.timeout = 1 # timeout 1 second (give 1 second to the board to initialize port)
s_port.open() #open serial port to start routine
ttl = {'status': NOT_STARTED}
# 2- Begin routine tab:
ttl['status'] = NOT_STARTED
# 3- Each frame tab:
if sound_1.status == STARTED and ttl['status'] == NOT_STARTED:
ttl['status'] = STARTED
win.callOnFlip(s_port.write, eeg_dict[get_sounds(trials.thisN)[0][word_index_sequence[trials.thisN]][0]]) #eeg_dict is a dictionary that maps frequencies to codes
#core.wait(0.01)
#win.callOnFlip(s_port.write, bytes([0])) #reset the triggerbox
elif sound_2.status==STARTED and ttl['status']==NOT_STARTED:
ttl['status'] = STARTED
win.callOnFlip(s_port.write, eeg_dict[get_sounds(trials.thisN)[0][word_index_sequence[trials.thisN]][1]])
#core.wait(0.01)
#win.callOnFlip(s_port.write, bytes([0])) #reset the triggerbox
elif sound_3.status==STARTED and ttl['status']==NOT_STARTED:
ttl['status'] = STARTED
win.callOnFlip(s_port.write, eeg_dict[get_sounds(trials.thisN)[0][word_index_sequence[trials.thisN]][2]])
#core.wait(0.01)
#win.callOnFlip(s_port.write, bytes([0])) #reset the triggerbox
elif sound_1.isPlaying==False and sound_2.isPlaying==False and sound_3.isPlaying==False and ttl['status']==STARTED:
ttl['status']=NOT_STARTED
# 4- End routine tab:
win.callOnFlip(s_port.write, bytes([0]))
# 5- End experiment tab:
s_port.close()
We found it unnecessary to set the trigger box to 0 after every send, hence why the code is commented out; but unsure if this is good practice. It did help us to miss fewer triggers, however, compared to when the reset was enabled.
Otherwise, this code just waits on the status of the sounds to change to send a trigger. We think the issue might be related to timing. To test this suspicion, we increased the interval between tones from 20 to 80 ms. This decreased the number of skipped triggers substantially, but not completely. However, we need the shorter interval.
Has anyone experienced similar issues when setting up a timing sensitive task together with TTL triggering?
We would appreciate any help!