Ok, your license key is now confirmed.
What you would do is the inverse order in your stimulus for-loop:
% Start one-time (1) playback asap (0), wait until predicted audio onset (1):
tOnset = PsychPortAudio('Start', pahandle, 1, 0, 1);
% Write TTL pulse, wait for confirmed transmit completion, so 'when' is meaningful:
[n, tTrigger(i)] = IOPort('Write', TB, uint8(MyRandomSequence(i)), 1);
% Returned n should be == 1 for successful trigger write.
% Estimate unwanted delay between sound onset and trigger onset 'latency':
latency = tTrigger(i) - tOnset;
% ...
% Wait for end of playback. A xruns > 0 suggests playback malfunction.
[~, ~, xruns] = PsychPortAudio('Stop', pahandle, 1);
This way the ‘Start’ command will compute the estimated time when sound leaves the audio output connector of your sound card, ie. the best estimate of true sound onset.
It will then pause your script until that time when sound truly starts. Immediately after that, IOPort sends the 1 marker-byte write command to the serial port and itself waits until the operating system confirms write completion and gives you a timestamp ‘tTrigger’ of that as well. If ‘n’ is not == 1 something went wrong during trigger write. ‘latency’ gives you an estimate of how much the TTL trigger was delayed wrt. sound onset.
The ‘Stop’ command waits until estimated stop/offset of sound, and reports ‘xruns’, which should be 0, or some audio playback glitch has been detected. After that you’re ready for the next trial loop iteration.
Making sound ‘Start’ wait for predicted onset is important, because there is always a software + hardware delay between when you ‘Start’ and when sound leaves the speakers, e.g., on MS-Windows with an onboard soundchip, this can be on the order of 10 - 20 msecs. Similar with the wait for IOPort, to account for system + hardware delays.
Now the quality of the audio time prediction depends on OS + audio driver + hardware, so you should always verify for you setup independently if possible, e.g., recording the sound wave and the TTL trigger with your EEG amplifier at least once to make sure that timing with your sound card is fine. A well working system, e.g., Linux or macOS, can achieve sub-milliseconds precision, ditto in the past for pro soundcards on MS-Windows. I don’t have current reference values at hand for MS-Windows.
There can be a write → emit delay for the TTL trigger as well, but i’d expect it to be sub-millisecond for typical real serial ports or no more than a millisecond for USB->Serial converters, at least as tested in the past with USB-serial converters from FTDI.
So all in all this method should get your TTL pulse to match within about 1-2 milliseconds on a well working system. It’s also advisable to use the Priority(MaxPriority(...));
to switch your script to realtime scheduling.
This is all good for sending isolated tones with triggers, because each Start->Trigger->Stop sequence will give good timing, but add a bunch of msecs delay between sounds, due to the hardware start delay before each sound. If you wanted to play a whole sequence of sounds back-to-back without any delay between them, you could define a sound schedule as demonstrated in BasicSoundScheduleDemo.m and only emit a trigger marking the start of the sequence, just as described above for a single sound.
If you needed a trigger per sound, and no gaps between sounds, you’d have to define a sound schedule, and then emit triggers at the time a sound is expected during scheduled playback, e.g., the code above for the first sound and trigger (i == 1), but then emit all other triggers at the expected time offsets since start of playback of the schedule, a la:
% Start one-time (1) playback asap (0), wait until predicted audio onset (1):
tOnset(1) = PsychPortAudio('Start', pahandle, 1, 0, 1);
% Write TTL pulse, wait for confirmed transmit completion, so 'when' is meaningful:
[n, tTrigger(1)] = IOPort('Write', TB, uint8(MyRandomSequence(1)), 1);
for i=2:length(MyRandomSequence)
tOnset(i) = tOnset(i-1) + 0.400; % Next sound starts 400 msecs after previous one.
WaitSecs('UntilTime', tOnset(i));
[n, tTrigger(i)] = IOPort('Write', TB, uint8(MyRandomSequence(i)), 1);
end
Given that sound playback of a schedule, once started, is highly deterministic, e.g., an error accumulation of usually less than 50 microseconds audio clock drift per second of sound playback on typical sound hardware, probably more like < 15 usecs, this triggering at predicted times should be quite accurate, with minimal possible jitter of triggers wrt. sounds for a given operating system + driver + hardware combination.
Hope it helps.
-mario
[Time spent: Over 60 minutes, paid support membership of 30 minutes more than used up.]