Callbacks are the way that you control what happens on different portions of the trial and what gets drawn to the screen. They are simply functions that get called at specific times in the experiment.
It doesn't matter exactly what you call them, but it does matter exactly what order you register them in.
There are two required callbacks, and the rest are optional. If for some reason you don't need one of the required callbacks, you can just leave it empty, but you must still define it.
Callbacks are also discussed in the overview.
You must register your callbacks with the initTask function, in the following order:
[task myscreen] = initTask(task,myscreen,@startSegmentCallback,@screenUpdateCallback,@getResponseCallback,@startTrialCallback,@endTrialCallback,@startBlockCallback);
You do not need to specify all the callbacks, only startSegmentCallback and screenUpdateCallback. To omit any of the callbacks, either don't pass it in to initTask or set the appropriate argument to []. Make sure that you return task and myscreen.
For example, you might have
[task myscreen] = initTask(task,myscreen,@startSegmentCallback,@screenUpdateCallback,[],@startTrialCallback,[],@startBlockCallback);
or
[task myscreen] = initTask(task,myscreen,@startSegmentCallback,@screenUpdateCallback,@getResponseCallback);
function [task myscreen] = screenUpdateCallback(task, myscreen) % do your draw functions in here.
Note that you will normally declare a global variable named stimulus that contains any textures or information about the stimulus and use that in here. Remember that screenUpdateCallback gets called every frame update. For a refresh rate of 60 Hz that means it definitely has to run within 1/60 th of a second, or else the program will start to drop frames and become slow. You should therefore make this function as simple as possible. For example, if you are using textures, call mglCreateTexture in your myInitStimulus function and only use the precomputed texture here in an mglBltTexture function.
Another option that you can consider is that for many types of stimulus you don't have to update the screen every frame refresh. For something like moving dots or a drifting gabor you will need to update the frame every screen refresh, but if you just want to show a static gabor for a full segment, you can use the flushMode=1 feature that is described below in startSegmentCallback.
The other mandatory callback is the one that is called at the beginning of each segment:
function [task myscreen] = startSegmentCallback(task, myscreen)
The variable task.thistrial will have fields set to what the parameters are for that trial. For instance if you have dir as one of your parameters, then you will have the field task.thistrial.dir set to one of the directions (chosen by updateTask).
If you are only drawing to the screen at the start of every segment, then you can use the flushMode=1 feature. Say for example you want to clear the screen and draw your texture to the screen and that is all that will happen in the segment then you can do something like:
mglClearScreen; mglBltTexture(stimulus.tex,[4 0]); myscreen.flushMode = 1;
Note that in this case you do not do any drawing in the screenUpdateCallback (this function will be empty). You only do drawing in the startSegmentCallback. This assumes that the only time the screen changes is when you start a new segment of your trial.
You can (optionally) define a callback for when the subject hits a response key:
function [task myscreen] = getResponseCallback(task,myscreen)
If you don't have subject responses in your experiment, you can just put this one line in with nothing after it.
There is a field called
task.thistrial.whichButton
This will get filled with which button was pressed (a number from 1-9). Note that if two keys are pressed down at the same time, it will only return the first in the list (e.g. if 1 and 2 are simultaneously pressed, it will return 1).
If you want to get all the keys that are pressed, you can look at
task.thistrial.buttonState
This will be an array where each element will have 0 or 1 depending on whether the key was pressed or not.
Note that the getResponseCallback will only be called if in the task structure you have set the appropriate segment of the getResponse variable. For example, if you have a two segment trial, and you want to get subject responses in the second segment of the trial you would do:
task.getResponse = [0 1];
You may also set a getResponse segment to 2. What this does is similar to setting myscreen.flushMode = 1. It prevents mglFlush from being called to update the screen while you are waiting for a keyboard press. This will get much more accurate keyboard timing, but will not allow the screen to update while you are waiting (i.e. you have to have a static display–no moving dots or flickering gratings or anything). Note that this functionality is not usually used anymore since we have accurate timestamps on the keyboard events in the latest version of mgl.
task.getResponse = [0 2];
If you want to get other keys, rather than the defined keys 1-9, for example if you want the keypad numbers, you can override which keys will be checked with:
myscreen.keyboard.nums = [84 85]; myscreen = initScreen(myscreen);
This is called at the beginning of your program. Note that to get the keycodes that correspond to a key, you can either use:
mglCharToKeycode({'a' 'b' 'c'})
or, for keys that you can't write like the keypad numbers or the esc key, run the program:
mglTestKeys
and type the keys you want and it will print out the correct keycode.
The getResponseCallback will get called every time the subject presses a button, so if the subject presses two buttons one after the other during the response period, getResponseCallback will be called twice. If you want to ignore the 2nd button press you can do:
if task.thistrial.gotResponse == 0 %your response code here end
task.thistrial.gotResponse will be set to 1 the second time the subject presses a key.
You can (optionally) define a callback that gets called at the beginning of each trial
function [task myscreen] = startTrialCallback(task,myscreen);
You can (optionally) define a callback that gets called at the end of each trial
function [task myscreen] = endTrialCallback(task,myscreen);
You can (optionally) define a callback that gets called at the beginning of a block
[task myscreen] = startBlockCallback(task,myscreen)