Skip to content

Audio/Visual Task

This MATLAB function is a simple test of the audio-visual setup in Psychotoolbox(PTB) using the io64-module for parallel port triggering.

It uses information taken from Stimulus Delivery & Triggering

basicAudioVisualTask - code below.

"As the name implies, it’s very ‘basic’. The ’task’ is simply to count the number of times the red fixation cross changes colour to green (trigger=32). But this is just to keep the subject occupied, the main objective is to acquire simple ERF’s (Event-Related Fields's) to the 4 visual quadrant checkerboards (triggers 3-6) and the L/R auditory bips (triggers 1/2)"

The MEG Stim PC, CHBH-ST-MEG-W02, is running Windows 10 (64-bit), so 64-bit versions of the following files are used.
32-bit versions are available if required - contact MEG Support.

The following files are also required ...

basicAudioVisualTask.m

function basicAudioVisualTask()
% simple test of audio-visual setup on PTB
% using io64-module for parallel port triggers
% 
% Revision history:
% - created on 20 Jan 2018, cjb
%
% Copyright 2018 Christopher J. Bailey under the MIT License
%
% Permission is hereby granted, free of charge, to any person obtaining a
% copy of this software and associated documentation files (the
% "Software"), to deal in the Software without restriction, including
% without limitation the rights to use, copy, modify, merge, publish,
% distribute, sublicense, and/or sell copies of the Software, and to
% permit persons to whom the Software is furnished to do so, subject to
% the following conditions:
% 
% The above copyright notice and this permission notice shall be included
% in all copies or substantial portions of the Software.
% 
% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
% OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
% MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
% IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
% CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
% TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
% SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
%
% Parts of this code are copied from the Psychtoolbox examples,
% Copyright Psychtoolbox developers under the MIT License, see
% https://github.com/Psychtoolbox-3/Psychtoolbox-3/blob/master/Psychtoolbox/License.txt


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Find out which device to use; ASIO is a must, but there may be several
% For Creative-cards, got to the Control Panel-app, and find the
% 'Wave Device' under 'Device Information'
% To list all devices seen by PortAudio,
% >> all_audio_devs = PsychPortAudio('GetDevices');
% >> disp({all_audio_devs(:).DeviceName})

ASIO_DEV_NAME = 'Xonar DS ASIO(64)';
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% For developing, use a non-ASIO device if you like
% ASIO_DEV_NAME = 'DisplayPort';
skipSyncTest = 0;  % 1==don't test sync quality (fails without proper card)
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Aarhus-specific volume limit: set to 1 if you want 
% scale_max_volume_to = 0.563;
scale_max_volume_to = 0.25;  % CAREFUL YOU DON'T BLOW OUT AN EARDRUM!! TEST LEVEL ON YOURSELF FIRST!!
% scale_max_volume_to = 1.0;  % full blast out of the soundcard--DANGERZONE
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Paradigm parameters
minISI = 0.5;
maxISI = 0.8;
nStim = 120;  % stimuli / type
nStimFrames = 6;  % frames on; 6 == 50 ms @ 120 Hz

nTargetFrames = 12;  % 100 ms @ 120 Hz
nTargets = 10;  % targets in total
fixC = [140, 0, 0];
targetC = [0, 140, 0];

% trig_codes.audL = 128;
trig_codes.audL = 3;
trig_codes.audR = 5;
trig_codes.sefL = 16;
trig_codes.sefR = 32;
trig_codes.visUR = 1;
trig_codes.visLR = 2;
trig_codes.visLL = 4;
trig_codes.visUL = 8;
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% For developing/testing, including latency
Debug=0;
useAudio = 1;
latencyTest = 0;  % set to 1 for untapered 1kHz sinusoid (100 ms)
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

%% DO NOT MODIFY BELOW HERE

% Force GetSecs and WaitSecs into memory to avoid latency later on:
GetSecs;
WaitSecs(0.1);

if useAudio
    InitializePsychSound(1);  % 1 = reallyneedlowlatency

    if ~latencyTest
        leftWav = 'Tone_1kHz_100ms_hanning_left.wav';
        % leftWav = 'leftChan-1000Hz-50ms-48kHz.wav';
        rightWav = 'Tone_1kHz_100ms_hanning_right.wav';
        % rightWav = 'rightChan-1000Hz-50ms-48kHz.wav';

        % Read WAV files from filesystem:
        [yL, freqL] = psychwavread(leftWav);
        [yR, freqR] = psychwavread(rightWav);
        % See PTB sound demos for resampling to 48 kHz!

        assert(freqL == freqR, 'Left and right channel rates must be equal')
        freq = freqL;
        assert(size(yL, 2) == 2, 'All WAV files must be stereo (L is not)')
        assert(size(yR, 2) == 2, 'All WAV files must be stereo (R is not)')

        audL = yL' / (max(yL(:, 1)) / scale_max_volume_to);
        audR = yR' / (max(yR(:, 2)) / scale_max_volume_to);
    else
        % For latency testing:
        % Generate some beep sound 1000 Hz, 0.1 secs, 50% amplitude:
        freq = 48000;
        audL(1,:) = 0.5 * MakeBeep(1000, 0.1, freq);
        audL(2,:) = zeros(size(audL));
        audR(1,:) = audL(2,:);
        audR(2,:) = audL(1,:);
    end

    nrchannels = 2;  % fix to stereo

    % win_asio_devs = PsychPortAudio('GetDevices', 3);
    all_audio_devs = PsychPortAudio('GetDevices');
    DeviceIndex = -1;
    for ii = 1:length(all_audio_devs)
        if strcmp(all_audio_devs(ii).DeviceName, ASIO_DEV_NAME)
            DeviceIndex = all_audio_devs(ii).DeviceIndex;
            break
        end
    end
    assert(DeviceIndex  > 0, 'ASIO device %s not found', ASIO_DEV_NAME)

    % try
    % Try with the 'freq'uency we wanted:
    mode = 1;  % 1 == sound playback only;
    reqlatencyclass = 2; % 2 == take full control over audio device
    pahandle = PsychPortAudio('Open', DeviceIndex, mode,...
                              reqlatencyclass, freq, nrchannels);

    %%% FAIL if sound device isn't happy with frequency!
    % catch
    %    % Failed. Retry with default frequency as suggested by device:
    %    fprintf('\nCould not open device at wanted playback frequency of %i Hz. Will retry with device default frequency.\n', freq);
    %    fprintf('Sound may sound a bit out of tune, ...\n\n');

    %    psychlasterror('reset');
    %    pahandle = PsychPortAudio('Open', [], [], 0, [], nrchannels);
    % end

    % Perform one warmup trial, to get the sound hardware fully up and running,
    % performing whatever lazy initialization only happens at real first use.
    % This "useless" warmup will allow for lower latency for start of playback
    % during actual use of the audio driver in the real trials:
    % Fill buffer with silence:
    PsychPortAudio('FillBuffer', pahandle, zeros(2, 10000));
    PsychPortAudio('Start', pahandle, 1, 0, 1);
    PsychPortAudio('Stop', pahandle, 1);

end


%%% OPEN THE SCREEN
try

Screen('Preference','SkipsyncTests', skipSyncTest);
AssertOpenGL;
screenNumber = max(Screen('Screens'));

% Define black and white
white = WhiteIndex(screenNumber);
black = BlackIndex(screenNumber);
gray = white / 2;

ScrSize=get(0,'ScreenSize');
ScrSize=ScrSize(3:end);

if Debug
    ScrSize=floor(ScrSize.*0.5); %this is for debug; leaves a  tiny window open
    ScreenRect=[0 0 ScrSize];
else
    ScreenRect=[];
end

[window, winRect] = Screen('OpenWindow', screenNumber, black, ScreenRect);

[scrSizeX, scrSizeY] = Screen('WindowSize', window);% get size of open window
ifi = Screen('GetFlipInterval', window); % one refresh cycle. RR  1/ifi (60 Hz here)
fixX = floor(scrSizeX / 2);
fixY = floor(scrSizeY / 2);

% for getting the latency right
waitframes = 0;  % ceil((2 * suggestedLatencySecs) / ifi) + 1;

WaitSecs(1);

% start screen - not sure if/when this is executed
Screen('TextFont', window,'Arial');
Screen('TextSize',window,40);

Screen('Flip', window);

%% Make checkerboards in the 4 quadrants
[checkTexture, destRects] = makeCheckTexture(window, winRect);


%%% INITIALIZE TRIGGERS
% fake it if necessary
sendTrigger = intialiseParallelPort();

DrawFormattedText(window, ['Press key to prime triggers\nDo not record yet!'],'center', scrSizeY/2,[255 255 255],[],[],[],2)
drawFrameSync(window)
Screen('Flip', window);
KbStrokeWait;

for ii = 0:7
    sendTrigger(power(2, ii));
    WaitSecs(0.1);
end
sendTrigger(0);
Screen('Flip', window);

%%

    %****************************************************************************************************************************
    %%% START EXPERIMENT

    DrawFormattedText(window, ['Ready?\nRR = ' num2str(1/ifi) ' Hz'],'center',scrSizeY/2,[255 255 255],[],[],[],2)
    Screen('Flip', window); % show new screen
    HideCursor;
    KbStrokeWait;

    drawFixation(window, fixX, fixY, fixC);
    Screen('Flip', window);

    WaitSecs(2);

    if useAudio
        stimList(1) = struct('type', 1, 'stimulus', audL, 'location', 0, 'trigger', trig_codes.audL);
        stimList(2) = struct('type', 1, 'stimulus', audR, 'location', 0, 'trigger', trig_codes.audR);
    else
        stimList(1) = struct('type', 1, 'stimulus', 0, 'location', 0, 'trigger', trig_codes.audL);
        stimList(2) = struct('type', 1, 'stimulus', 0, 'location', 0, 'trigger', trig_codes.audR);
    end        

    vis_triglist = [trig_codes.visUR, trig_codes.visLR, trig_codes.visLL, trig_codes.visUL];
    for ii = 1:length(destRects)
        stimList(ii + 2) = struct('type', 0, 'stimulus', checkTexture,...
               'location', destRects{ii}, 'trigger', vis_triglist(ii));
%                'location', destRects{ii}, 'trigger', ii+2);
    end

    stimSequence = repmat(1:length(stimList), 1, nStim);

    % targets
    stimList(end + 1) = struct('type', -1, 'stimulus', -1, 'location', 0, 'trigger', 32);
    stimSequence = [stimSequence, length(stimList)*ones(1, nTargets)];
    stimSequence = stimSequence(randperm(length(stimSequence)));

    for iITI=1:length(stimSequence)

        curStim = stimList(stimSequence(iITI));

        % Screen('FillRect', window, [255 255 255] , ScreenRect);   % Fill the display with white

        % This flip clears the display to black and returns timestamp of black onset:
        % It also triggers start of audio recording by the DataPixx, if it is
        % used, so the DataPixx gets some lead-time before actual audio onset.
        drawFixation(window, fixX, fixY, fixC);
        [vbl1 visonset1]= Screen('Flip', window);

        holdScreen = 0;
        curC = fixC;
        tWhen = 0;  % relevant for sync'ing audio & visual only
        if (useAudio) && (curStim.type == 1)
            % Fill the audio playback buffer with the audio data 'wavedata':
            PsychPortAudio('FillBuffer', pahandle, curStim.stimulus);

            %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
            %%% These are relevant if the audio needs to be sync'ed with a
            %%% frame flip: have to pre-empt the flipping time, in which
            %%% case having waitframes > 0 might allow the sound card to
            %%% get its shit together. On an Audigy5/Rx this wasn't
            %%% relevant.

            %%% Compute tWhen onset time for wanted visual onset at >= tWhen:
            % tWhen = vbl1 + (waitframes - 0.5) * ifi;
            %%% Schedule start of audio at exactly the predicted visual stimulus
            %%% onset caused by the next flip command.
            % tPredictedVisualOnset = PredictVisualOnsetForTime(window, tWhen);
            % PsychPortAudio('Start', pahandle, 1, tPredictedVisualOnset, 0);
            %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

            %%% Send sound immediately, wait for it to actually start, then
            %%% fire off a trigger; latency should be < 2 ms & no jitter!
            % reps = 1, when = 0 (now), waitforstart = 1 (block until
            % really started)
            Screen('Flip', window);
            PsychPortAudio('Start', pahandle, 1, 0, 1);
            %%% SEND STIMULUS-SPECIFIC TRIGGERS
            sendTrigger(curStim.trigger);

        elseif (curStim.type < 1)
            if curStim.type == 0
                Screen('DrawTexture', window, curStim.stimulus, [], curStim.location);
                % draw frame sync spot
                drawFrameSync(window)
                holdScreen = nStimFrames;
            elseif curStim.type == -1
                curC = targetC;
                holdScreen = nTargetFrames;
            end
            % Ok, the next flip will do a black-white transition...
            drawFixation(window, fixX, fixY, curC);
            [vbl visual_onset t1] = Screen('Flip', window, tWhen);  

            %%% SEND STIMULUS-SPECIFIC TRIGGERS
            sendTrigger(curStim.trigger);
        end

        for hh = 1:holdScreen - 1
            drawFixation(window, fixX, fixY, curC);
            Screen('Flip', window);
        end
        drawFixation(window, fixX, fixY, fixC);
        Screen('Flip', window);

        % switch off TTL
        sendTrigger(0);

        if useAudio
            PsychPortAudio('Stop', pahandle, 1);
        end


        %%% ITI: count down and while waiting check for interrupting
        %key press.
        debrun=GetSecs;
        curISI = getISI(minISI, maxISI);
        while GetSecs-debrun < curISI
            [~, ~, keyCode] = KbCheck;
            if strcmp(KbName(keyCode),'q')==1
                %clean up and quit
                RestoreScreen('User hit quit')
                return  %this returns control to command window!
            end
        end
    end

    drawFixation(window, fixX, fixY, fixC);
    Screen('Flip', window);

    WaitSecs(2);

    DrawFormattedText(window, 'Run finished','center',scrSizeY/2,[255 255 255],[],[],[],2);
    Screen('Flip', window); %

    KbStrokeWait;
    RestoreScreen() % see below for what this function does

catch
    Screen('CloseAll');
    psychrethrow(psychlasterror);

end

end


%%
function RestoreScreen(varargin)
% restore normal display state (return to 120 Hz mode, exit PsychToolbox, etc.)
ShowCursor;
Screen('CloseAll');
PsychPortAudio('Close');

if nargin > 0
    fprintf(1, 'Exited with status: %s\n', varargin{1});
end

clear functions

end

%%
function [checkTexture, destRects] = makeCheckTexture(win, winRect)

    [width, height] = RectSize(winRect);
    % Get the centre coordinate of the window
    [xCenter, yCenter] = RectCenter(winRect);

    miniboard = eye(2,'uint8') .* 255;

    checkerboard = repmat(miniboard, ceil(0.5 .* 8))';
    checkerboard = imresize(checkerboard, 20,'box');

    checkTexture = Screen('MakeTexture', win, checkerboard);

    cbSize = size(checkerboard);
    cbSidelen = floor(cbSize(1) / 2) + 50;
    baseRect = [0 0 cbSize];
    centeredRect = CenterRectOnPointd(baseRect, xCenter, yCenter);

    destRects = cell(1, 4);
    for ii = 1:4
        if ii==1
            destRect = CenterRectOnPointd(baseRect, xCenter + cbSidelen, yCenter + cbSidelen);
        elseif ii == 2
            destRect = CenterRectOnPointd(baseRect, xCenter + cbSidelen, yCenter - cbSidelen);
        elseif ii == 3
            destRect = CenterRectOnPointd(baseRect, xCenter - cbSidelen, yCenter - cbSidelen);
        elseif ii == 4
            destRect = CenterRectOnPointd(baseRect, xCenter - cbSidelen, yCenter + cbSidelen);
        end
        destRects{ii} = destRect;
        %Screen('DrawTexture',win,checkTexture, [], destRect);
    end

end

function drawFixation(window, posX, posY, col)

    Screen('Drawtext',window, '+',posX,posY,col); %fixation cross
end

function drawFrameSync(window)

%     Screen('FillRect',window, [255 255 255], [1570, 830, 1900, 1060])
    Screen('FillRect',window, [255 255 255], [1870, 1030, 1900, 1060])
end

function curISI = getISI(minISI, maxISI)
    curISI = minISI + (maxISI - minISI)*rand;
end

With many thanks to Dr. Chris Bailey, Aarhus University