Experiment example: The oddball paradigm

The oddball paradigm is an experimental design used within psychology research. The oddball paradigm relies on the brain’s sensitivity to rare deviant stimuli presented pseudo-randomly in a series of repeated standard stimuli. The oddball paradigm has a wide selection of stimulus types, including stimuli such as sound duration, frequency, intensity, phonetic features, complex music, or speech sequences. The reaction of the participant to this “oddball” stimulus is recorded.

In the classic Oddball paradigm, two types of stimuli affecting the same sensory channel are presented randomly within an experiment, with a significant difference in the probability of occurrence. The more frequently occurring stimulus is called the standard stimulus, which serves as the background of the entire experiment; the less frequent and sporadic stimulus is known as the deviant stimulus. Since the physical properties of the two stimuli are very similar, the deviant stimulus appears as a deviation from the frequently occurring standard stimulus, hence the names “standard stimulus” and “deviant stimulus.” In the classic Oddball paradigm, the deviant stimulus typically has an occurrence probability of about 20%, while the standard stimulus has a probability of about 80%.

The experiment is adapted from sijiazhao code, the original one is found here.

The code below is the main script, to make it work you will need to download the full directory containing the sound files as well from Download oddball full directory

% Readapted for NYUAD MEG Lab by Hadi Zaatiti
% Original copyright:
% Copyright (c) 2019, Sijia Zhao.  All rights reserved.

% Note: Search '% CHANGE BASED ON YOUR SETUP!' to find lines you must customise
% for your own setup.

clear;close all;clc
rng('shuffle');
dbstop if error;

Screen('Preference', 'SkipSyncTests', 1);
expsbj = input(' NAME of PARTICIPANT? [eg. S10] = ','s');
thisblock = input(' BLOCK INDEX? = ');


%% Hardware parameters

el = 0; % 1 = Eyelink on; 0 = Eyelink off;

vpix_use = 1;    %Vpixx send triggers or not


%% Experiment parameters

%ntrial_c = [60, 10, 10]; %number of trials per conditions: respectively 500Hz, noise white and 200Hz
ntrial_c = [6, 1, 1];
ntrial = sum(ntrial_c);

ISI = 3:0.1:3.5; % [second] Inter-soundonset-interval. The distance between the onset of the current trial and the next sound
Fs = 44100; % sampling rate for sound play
stimDur = 0.5; % [second] sound duration. this must be pre-set and identical across all sounds

path_in = './SoundFiles/';

condlist = {'tone_500Hz','noise_white','ht_200Hz'};

black_rgb = [0 0 0];

%% Setup Vpixx

% Define trigger pixels for all usable MEG channels
% trig.ch224 = [4  0  0]; %224 meg channel
% trig.ch225 = [16  0  0];  %225 meg channel
% trig.ch226 = [64 0 0]; % 226 meg channel
% trig.ch227 = [0  1 0]; % 227 meg channel
% trig.ch228 = [0  4 0]; % 228 meg channel
% trig.ch229 = [0 16 0]; % 229 meg channel
% trig.ch230 = [0 64 0]; % 230 meg channel
% trig.ch231 = [0 0  1]; % 231 meg channel


trigRect = [0 0 1 1];

trigch224 = [4  0  0];  % 224 MEG CHANNEL: Resting state trigger
trigch225 = [16  0  0]; % 224 MEG CHANNEL:500 Hz audio trigger
trigch226 = [64 0 0];   % 225 MEG CHANNEL: White noise audio trigger
trigch227 = [0  1 0];   % 226 MEG CHANNEL: 200 Hz audio trigger
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

ExpCond.condlist = condlist;

signal_sounds = {}; % read and store all three sounds
for i = 1:numel(condlist)
    filename = [path_in condlist{i} '.wav'];
    [signal_sounds{i},~] = audioread(filename);
end

ExpCond.numTrial = ntrial;
ExpCond.ISI = ISI;
ExpCond.stimDur = stimDur;

% Common eyetracking set up
ExpCond.distSc_Sbj = 65; % Distance from subject to monitor [cm] % CHANGE BASED ON YOUR SETUP!
ExpCond.ScWidth = 53.4; % Screen width [cm] % CHANGE BASED ON YOUR SETUP!
ExpCond.smpfreq = 1000; % Sampling rate of Eyelink [Hz] % CHANGE BASED ON YOUR SETUP!
ExpCond.linewidth = 7; % in pixels

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Set folders and filenames
ExpDataDrct = ['./Data/']; %this vector will be updated later (add date)
outfile = strcat(expsbj,'_');

% Eye-tracking data's filename (temporary)
switch el
    case 1
        Eyelinkuse = 'on';
    case 0
        Eyelinkuse = 'off';
end
ExpDrct =  './';
tmpname = '100'; %temporary name for eyetracking data

ExpDataDrct = [ExpDataDrct,'/'];
mkdir(ExpDataDrct);


str_exp = string(ExpDataDrct);
diary_string = strcat(ExpDataDrct,outfile,'_audiolog.asc');
diary(diary_string);

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Compute the stimuli indexs for each block
shuffleidx = [];
for k = 1:numel(condlist)
    shuffleidx = [shuffleidx, k*ones(1,ntrial_c(k))];
end
triallist = shuffleidx(randperm(ntrial));

%% Add a constraint: make sure two deviants (2 or 3) are not next to each other
% ## This part needs to be optimised.
minDeviantDistance = 1;
i_d = find(triallist~=1);
flag_consc = find(diff(i_d)<=minDeviantDistance);
while ~isempty(flag_consc)
    triallist = shuffleidx(randperm(ntrial));
    i_d = find(triallist~=1);
    flag_consc = find(diff(i_d) <= minDeviantDistance);
    disp('Reshuffle...');
end
% ## This part needs to be optimised.

ExpCond.triallist = triallist;

%% Initialize PsychPortAudio & Create Buffer

%Uncomment to initial version
% InitializePsychSound(1);
% nsoundcard = 9; % Soundcard location % CHANGE BASED ON YOUR SETUP!
% padevice = PsychPortAudio('Open',nsoundcard,[],0,Fs); 
% PsychPortAudio('RunMode',padevice,1);
%End Uncomment to inital version


% Begin psychdemos
% Always init to 2 channels, for the sake of simplicity:
nrchannels = 2;

% Perform basic initialization of the sound driver:
InitializePsychSound;

suggestedLatencySecs = [];
if IsARM
    % ARM processor, probably the RaspberryPi SoC. This can not quite handle the
    % low latency settings of a Intel PC, so be more lenient:
    suggestedLatencySecs = 0.025;
    fprintf('Choosing a high suggestedLatencySecs setting of 25 msecs to account for lower performing ARM SoC.\n');
end

% Open the audio 'device' with default mode [] (== Only playback),
% and a required latencyclass of 1 == standard low-latency mode, as well as
% default playback frequency and 'nrchannels' sound output channels.
% This returns a handle 'pahandle' to the audio device:
device = [];
padevice = PsychPortAudio('Open', device, [], 1, [], nrchannels, [], suggestedLatencySecs);


% Get what freq'uency we are actually using for playback:
s = PsychPortAudio('GetStatus', padevice);



%End PsychDemos




%   Read wavname, extract stimuli information, and put audio to buffer
%   (prepare the sound)
BufferHandles = zeros(1,ntrial);
for k = 1:ntrial
    tmpwav = signal_sounds{triallist(k)};
    BufferHandles(k) = PsychPortAudio('CreateBuffer', padevice, repmat(tmpwav,1,2)');
end
clear tmpwav;

load([ExpDrct, 'Tone1000.mat']);
BufferHandles(ntrial+1) = PsychPortAudio('CreateBuffer', padevice, repmat(mywav,2,1));

% Archive the output latency for RT calculation
PsychPortAudio('FillBuffer',padevice,BufferHandles(ntrial+1));
PsychPortAudio('Start',padevice, 1, 0, 1);
diary off;

%% load audiolog (output latency)
filename = [ExpDataDrct,outfile,'_audiolog.asc'];
identifier = '%s %s %s %s %s %s %s %s %s %s %s %s';
fid = fopen(filename);
tmp = textscan(fid, identifier);
fclose(fid);
for k=1:length(tmp{11})
    if strcmp(tmp{11}{k},'latency')==1
        if strcmpi(tmp{10}{k},'output')==1
            Oindex = k;
            break
        end
    end
end


ExpCond.audlag = str2double(tmp{12}{Oindex}); % unit:[msec]
clear Oindex tmp

diary(diary_string);

%% Eyelink Setting
dummymode = 0;
KbName('UnifyKeyNames');
Screen('Preference', 'VisualDebuglevel', 2);
screens=Screen('Screens');

% screenNumber = 1;
screenNumber = max(screens);
[window,ExpCond.rect]=Screen('OpenWindow',screenNumber);
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% EyeLink Calibration %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
if strcmp(Eyelinkuse,'on')==1
    if ~dummymode, HideCursor; end
    commandwindow;
    fprintf('EyelinkToolbox Example\n\n\t');
    eyl=EyelinkInitDefaults(window);
    ListenChar(2);
    if ~EyelinkInit(dummymode, 1)
        fprintf('Eyelink Init aborted.\n');
        cleanup;  % cleanup function
        return;
    end
    [v,vs]=Eyelink('GetTrackerVersion');
    fprintf('Running experiment on a ''%s'' tracker.\n', vs );
    Eyelink('Command', 'link_sample_data = LEFT,RIGHT,GAZE,AREA');
    %     Eyelink('Openfile',[EyelinkName,'.edf']);
    Eyelink('Openfile',[tmpname,'.edf']);
    EyelinkDoTrackerSetup(eyl);
    EyelinkDoDriftCorrection(eyl);
    Eyelink('StartRecording');
    WaitSecs(0.1);
    Eyelink('Message', 'SYNCTIME');
end
%%%%%%%%%%%%%%%%%%%%%% EyeLink Calibration End %%%%%%%%%%%%%%%%%%%%%%%%%%

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Set up Screens

white = WhiteIndex(window);
black = BlackIndex(window);
gray = (white+black)/2;
inc = white-gray;
bgColor = [gray gray gray]*3/2;
red = [black white white];
fixSize = [0 0 25 25];
fixColor = 10;
FBcolor = [180 180 180];

VA1deg.cm = 2*pi*ExpCond.distSc_Sbj/360;  % visual angle 1 deg [unit:cm]
VA05deg.cm = 2*pi*ExpCond.distSc_Sbj/360/2;  % visual angle 0.5 deg [unit:cm]
px_in_cm = ExpCond.ScWidth/ExpCond.rect(3); % one pixel on the specified screen [unit:cm]
VA1deg.px = floor(VA1deg.cm/px_in_cm); % visual angle 1 deg [unit:pixel]
VA05deg.px = floor(VA05deg.cm/px_in_cm); % visual angle 0.5 deg [unit:pixel]

% positions of the fixation point
centerpx = [ExpCond.rect(3)/2 ExpCond.rect(4)/2];       % position of the center H,V (in pixel)
fxpointH = [centerpx(1) centerpx(2) centerpx(1) centerpx(2)]+[-1 0 1 0]*floor(VA1deg.px/2);
fxpointV = [centerpx(1) centerpx(2) centerpx(1) centerpx(2)]+[0 -1 0 1]*floor(VA1deg.px/2);

textSize = 16;
text='Press SPACE KEY to start the experiment';
Screen('FillRect', window, bgColor);
Screen(window,'TextFont','Arial');
Screen(window,'TextSize',textSize);
x=(ExpCond.rect(3)-textSize*18)/2;
y=(ExpCond.rect(4)+textSize*0.75)/2;
Screen(window,'DrawText',text,x,y,[black black black]);
Screen('FillRect', window, black_rgb, trigRect);
Screen('Flip', window);



%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
done = 0;
while 1
    [ keyIsDown, ~, keyCode ] = KbCheck;
    if keyIsDown && done==0
        if keyCode(KbName('Space'))
            Screen('FillRect', window, bgColor);
            Screen('DrawLine', window, [black black black], fxpointH(1), fxpointH(2), fxpointH(3), fxpointH(4), 4);
            Screen('DrawLine', window, [black black black], fxpointV(1), fxpointV(2), fxpointV(3), fxpointV(4), 4);
            Screen('FillRect', window, black_rgb, trigRect);
            Screen('Flip', window);
            disp('START!');
            WaitSecs(1.5)
            done=1;
            break
        end
    end
end
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Experiment Part

Screen('FillRect', window, bgColor);
Screen('DrawLine', window, [black black black], fxpointH(1), fxpointH(2), fxpointH(3), fxpointH(4), 4);
Screen('DrawLine', window, [black black black], fxpointV(1), fxpointV(2), fxpointV(3), fxpointV(4), 4);
Screen('FillRect', window, black_rgb, trigRect);
Screen('Flip', window);

tmptime = GetSecs;

breakp = 0;

%% Resting state pupil diameter
if strcmp(Eyelinkuse,'on')==1
    
    feedback4fixation = 0;
    
    Eyelink('StartRecording'); % start recording (to the file)
    error = Eyelink('checkrecording'); % Check recording status, stop display if error
    
    % check for endsaccade events
    fixcenter = 0;
    while fixcenter==0
        if Eyelink('isconnected') == eyl.dummyconnected % in dummy mode use mousecoordinates
            [x,y] = GetMouse(window);
            evt.type = eyl.ENDSACC;
            evt.genx = x;
            evt.geny = y;
            evtype = eyl.ENDSACC;
        else % check for events
            evtype = Eyelink('getnextdatatype');
        end
        
        if evtype == eyl.ENDSACC % if the subject finished a saccade check if it fell on an object
            if Eyelink('isconnected') == eyl.connected % if we're really measuring eye-movements
                evt = Eyelink('getfloatdata', evtype); % get data
            end
            
            % check if saccade landed on fixation cross
            if 1 == IsInRect(evt.genx,evt.geny, [centerpx(1)-100,centerpx(2)-100,centerpx(1)+100,centerpx(2)+100])
                
                fixcenter = 1;
                if feedback4fixation
                    
                    Screen('FillRect', window, bgColor);
                    Screen('DrawLine', window, [black black black], fxpointH(1), fxpointH(2), fxpointH(3), fxpointH(4), 4);
                    Screen('DrawLine', window, [black black black], fxpointV(1), fxpointV(2), fxpointV(3), fxpointV(4), 4);
                    Screen('FillRect', window, black_rgb, trigRect);
                    Screen('Flip', window);
                end
                
            else % if not fixating, toggle red fixation !
                
                if feedback4fixation
                    Screen('FillRect', window, bgColor);
                    Screen('DrawLine', window, [black white white], fxpointH(1), fxpointH(2), fxpointH(3), fxpointH(4), 4);
                    Screen('DrawLine', window, [black white white], fxpointV(1), fxpointV(2), fxpointV(3), fxpointV(4), 4);
                    Screen('FillRect', window, black_rgb, trigRect);
                    Screen('Flip', window);
                end
                
            end
            WaitSecs(.1);
        end % saccade?
    end
    
    Screen('FillRect', window, bgColor);
    Screen('DrawLine', window, [black black black], fxpointH(1), fxpointH(2), fxpointH(3), fxpointH(4), 4);
    Screen('DrawLine', window, [black black black], fxpointV(1), fxpointV(2), fxpointV(3), fxpointV(4), 4);
    Screen('FillRect', window, black_rgb, trigRect);
    Screen('Flip', window);
    
    WaitSecs(2);
end % el

%% Wait for resting state
disp(['Block:',thisblock,' Resting state starts (15s)']);
disp('***');
if strcmp(Eyelinkuse,'on') == 1
    Eyelink('Message', ['Resting: Resting_state_for_15s']);
end
WaitSecs(15);


%% Check if you want to terminate the experiment
[ keyIsDown, ~, keyCode ] = KbCheck;
if keyCode(KbName('Escape'))
    breakp = 1;
    %break;
end

%% Main experiment starts!


if vpix_use == 1
    %VIEW PIXX SETUP
    
    
    Datapixx('Open');
    Screen('FillRect', window, black_rgb, trigRect);
    Datapixx('EnablePixelMode');  % to use topleft pixel to code trigger information, see https://vpixx.com/vocal/pixelmode/
    Datapixx('RegWr');

end


for k = 1:ntrial
       
    disp(['Block:',thisblock,' Trial:',num2str(k),' Condition: ', condlist{triallist(k)}]);
    disp('***');
    if strcmp(Eyelinkuse,'on')==1
        Eyelink('Message', ['Trial:',num2str(k),' ',condlist{triallist(k)}]);
    end
    
    %% Play sound
    PsychPortAudio('FillBuffer',padevice,BufferHandles(k));
    PsychPortAudio('Start', padevice, 1, 0, 1); %Start audio playback, return onset timestamp.
    

    if vpix_use == 1
        
        % Switch-case structure
        switch triallist(k)
            case 1
                Screen('FillRect', window, trigch224, trigRect);
            case 2
                Screen('FillRect', window, trigch225, trigRect);
            case 3
                Screen('FillRect', window, trigch226, trigRect);
            otherwise
                disp('Invalid audio condition');
        end

        Screen('Flip', window);
        Screen('FillRect', window, black_rgb, trigRect);
        Screen('Flip', window);
    end

    %% Wait for ISI
    WaitSecs(ISI(randperm(length(ISI),1))); %wait for ISI
    
    %% Check if you want to terminate the experiment
    [ keyIsDown, ~, keyCode ] = KbCheck;
    if keyCode(KbName('Escape'))
        breakp = 1;
        break
    end
end





totaltime = GetSecs - tmptime;

ListenChar(0);
for k=1:ntrial
    PsychPortAudio('DeleteBuffer',BufferHandles(k));
end
PsychPortAudio('Close');

if breakp==0
    text='FINISHED!';
elseif breakp==1
    text='ABORTED!';
end
Screen('FillRect', window, bgColor);
Screen(window,'TextFont','Arial');
Screen(window,'TextSize',textSize);
x=(ExpCond.rect(3)-textSize*8)/2;
y=(ExpCond.rect(4)+textSize*0.75)/2;
Screen(window,'DrawText',text,x,y,[black black black]);
Screen('FillRect', window, black_rgb, trigRect);
Screen('Flip', window);
WaitSecs(1.5);

if strcmp(Eyelinkuse,'on')==1
    
    EyelinkName=[ExpDataDrct outfile];
    
    Eyelink('Stoprecording');
    Eyelink('ReceiveFile',tmpname); % copy the file from eyetracker PC to Stim PC
    Eyelink('CloseFile');
    Eyelink('Shutdown');
    if breakp==0
        command = ['edf2asc ',tmpname,'.edf -ns'];
        status = dos(command);
        command = ['rename ',tmpname,'.asc ',tmpname,'_event.asc '];
        status = dos(command);
        command = ['edf2asc ',tmpname,'.edf -ne'];
        status = dos(command);
        command = ['rename ',tmpname,'.asc ',tmpname,'_sample.asc '];
        status = dos(command);
        movefile([tmpname '.edf'],[EyelinkName '.edf']);
        movefile([tmpname '_sample.asc'],[EyelinkName '_sample.asc']);
        movefile([tmpname '_event.asc'],[EyelinkName '_event.asc']);
    end
end


% Ensure all parts are strings
ExpDataDrct = string(ExpDataDrct);

% Create the filename by concatenating the parts
filename = strcat(ExpDataDrct, 'result_', outfile, '.mat');

%filename = [ExpDataDrct,'result_',outfile,'.mat']; %changed by Sijia
if breakp==0 %&& ifpractice==0 %&& Sblock~=0
    
    save(filename,'ExpCond');
    disp('----- EXPERIMENT FINISHIED -----')
    disp(['- TOTAL TIME: ',num2str(totaltime)])
elseif breakp==1
    disp('----- EXPERIMENT ABORTED -----')
    disp(['- TOTAL TIME: ',num2str(totaltime)])
end
diary off;


textend='Stop the MEG recording and press SPACE KEY to END the experiment';
Screen('FillRect', window, bgColor);
Screen(window,'TextFont','Arial');
Screen(window,'TextSize',textSize);
x=(ExpCond.rect(3)-textSize*18)/2;
y=(ExpCond.rect(4)+textSize*0.75)/2;
Screen(window,'DrawText',textend,x,y,[black black black]);
Screen('FillRect', window, black_rgb, trigRect);
Screen('Flip', window);

while 1
    [ keyIsDown, ~, keyCode ] = KbCheck;
    if keyIsDown
        if keyCode(KbName('Space'))
            break
        end
    end
end

Screen('CloseAll');

if vpix_use == 1
    %VIEW PIXX SETUP
    Datapixx('DisablePixelMode');  % to use topleft pixel to code trigger information, see https://vpixx.com/vocal/pixelmode/
    Datapixx('SetDoutValues', 0);
    Datapixx('Close');
    disp('Datapixx Vpixx Closed')
end