Define distinct trials – for loops, addTrial
Set trial repetition and order – setTrialList
Define objects not specific to trial (if needed) – addToExperiment
Plan in a spreadsheet – sheet2vars
Trial templates – addTemplate, getTemplate
View in table – viewExperiment
Test using auto response – runExperiment -a
Quit/Resume – Ctrl + Esc, saveExperiment, loadExperiment
Other tools – showElements, recordElements, etc.
Randomize order of combinations of values (exhaustive)
Randomize order of combinations of values (non-exhaustive)
Counterbalance combinations of values
Randomize values independently
Randomize/Counterbalance across subjects
Trial groups (blocks, training trials, etc.)
One-off trials (intro, breaks, etc.)
In the coding method of making an experiment we use a MATLAB script. This gives all the flexibility of MATLAB, which may be needed for complex experiments. That said, it's not really code in the usual sense, more like a list of statements setting parameters. That makes it just as good for simple experiments. Also note it's still possible to work semi-visually in the coding method using the optional tool sheet2vars. For all these reasons it's the bedrock and we describe it first...
The best way to see how the coding method works is by examples. Below is the basic example from the homepage with a few lines added. For many more realistic examples see <><PsychBench folder><>/docs/demos.
newExperiment
fileNames = [<cdsm>"dog.jpg" "cat.jpg" "bird.jpg"<cdsm>];
<cdkm>for<cdkm> fileName = fileNames
pic = pictureObject;
pic.fileName = fileName;
pic.height = 10;
pic.start.t = 0;
pic.end.response = true;
pic.report = <cdsm>"fileName"<cdsm>;
recorder = keyPressObject;
recorder.start.t = 0;
recorder.report = [<cdsm>"responseTime" "responseLatency"<cdsm>];
addTrial(picture, recorder);
<cdkm>end<cdkm>
nn = randomOrder(rep(1:3, 2));
setTrialList(nn);
experiment = experimentObject;
experiment.backColor = [0.5 0.5 0.5];
addToExperiment(experiment)
[results, resultsMat] = runExperiment;
Following are the general steps, which you can see in the above example. These steps use a handful of commands specific to the coding method, which are in <><PsychBench folder><>/coding. As usual you can type help <><function name><> in MATLAB for usage of each.
Call newExperiment at the top of every experiment script. This tells PsychBench to set up for building an experiment in memory, including clearing any experiment already in memory.
The main part of making an experiment is making its trial definitions. For each trial: Make and set properties for all the element objects to run in it. Optionally you can also make a trial object if you want to set properties at that level (e.g. preTrialInterval). To make objects use <><type><>Object functions (e.g. pictureObject). Put them in any variables you want—you can also group objects in arrays as long as they have the same object type. Set properties that you want to change from default using standard dot syntax. (See Objects and properties.) When you have all the objects for a trial, input them to the function addTrial. This adds the trial definition to the set of trial definitions the script is building in memory.
The usual approach is to ignore trial repetition and order to run in when defining trials. Instead just define each distinct trial once, meaning each distinct combination of property values across objects. And you can define trials in any order, which means you can use for loops through conditions to automate it, calling addTrial at the end of each iteration. You can also use nested for loops for combinations of conditions—see Randomization below.
After you have defined your distinct trials, you can add repetition and order to run in. The usual way is to set a trial list, which is just a list of trial definitions for the experiment to run through. By default PsychBench automatically numbers trial definitions 1, 2, 3, … in the order your script calls addTrial, and the trial list is just a vector with numbers drawn from those. You can repeat numbers in the vector. You can order numbers however you want, including randomizing order in parts or in whole. You can use an expression to set the vector—little functions rep and randomOrder in <><PsychBench folder><>/tools are useful here. Then call the function setTrialList once and input the vector.
setTrialList is optional. If you don’t use it, the experiment just runs each trial definition once in number/name order. (Or for unusual cases you can set repetition and order directly in trial definitions—see below.)
Custom trial definition numbering/names: Optionally you can use custom numbering or names for any trial definition using an input to addTrial. Custom numbering can be useful if you want to organize trials into groups, like blocks or training trials. Names are useful for one-off "trials" like intros, breaks, etc. See below on trial groups and one-off trials for more information. See addTrial and setTrialList help text for usage in custom numbering/names.
[Set trial repetition and order directly in trial definitions: For unusual cases you can set trial numbers to run as during the experiment directly in trial definitions using an extra input to addTrial. In that case you don't set a trial list. Usually this is harder but the option is available.]
If you need any objects not specific to trial—e.g. an experiment object, screen or other device objects, staircase objects, etc.—make them just like element objects, except only once and outside any for loops. Then call the function addToExperiment once and input them. Otherwise ignore this step.
Run the script to build the experiment in memory. Generally re-run the script for each run/subject in order to redo any randomization in the script.
Use the command runExperiment to run the experiment that is in memory. runExperiment outputs results in variables, and optionally saves them to .mat and .csv files.
There are options you can add in as needed:
sheet2vars is an optional tool that can be useful for making more complex experiments. You can plan values across trial definitions in spreadsheet tables (properties/variables in columns, trial definitions in rows), use sheet2vars in the experiment script to read them into variables, then draw from those variables in your for loops making trial definitions. This allows for a "semi-visual" approach without losing the flexibility of the coding method. sheet2vars is similar to MATLAB readVars but aims to have much more helpful handling of different data types. Type help sheet2vars for usage.
See <><PsychBench folder><>/docs/demos/experiments/sheet2varsDemo.m.
addTemplate and getTemplate are optional tools that can help when writing more complex experiment scripts. If you need to write multiple blocks of code that set similar objects (for example, write code for test trials and then write code for training trials that are the same or similar), you can write code once that makes the objects and sets whatever property values are common across those blocks for them, save them as a "template" in memory using addTemplate, then at each block get copies of them using getTemplate and just add to or modify the copies instead of rewriting the whole thing.
You can also use getTemplate to get copies of built-in templates, e.g. visual feedback, a "press any key to continue" setup, etc. See getTemplate function help.
See <><PsychBench folder><>/docs/demos/experiments/gonogoDemo.m.
Before running an experiment for the first time, it can be helpful to check it in table form in MATLAB. After running the script to build the experiment in memory, use viewExperiment at the MATLAB command line to see the trials that will run in table form. You can also call viewExperiment -d to sort them by trial definition.
Another useful testing feature is running an experiment in auto response mode to simulate a subject. To do this call runExperiment -a. You can change auto response options in properties autoResponse, autoResponseLatency which all response handler elements have.
You can press Ctrl + Esc to quit running an experiment part way through.
If you quit an experiment part way through and then call runExperiment again, it asks if you want to resume the experiment at the trial you quit. Results output and time measurements will be as if the experiment was never quit. You can only resume an experiment if it’s still in memory, i.e. up until the next loadExperiment, newExperiment, clearExperiment, clear all, or quitting MATLAB. However, most commonly you’ll want to resume in a later session. To do this, use saveExperiment to save the state of the experiment to a .mat file, then loadExperiment to load it back into memory later.
A few other tools can be used at the MATLAB command line for various tasks. For example, showElements shows/runs element objects without needing to make an experiment, recordElements captures element objects to image or movie files, etc. These are all in the root PsychBench folder—type help PsychBench for an overview.
Often the hardest part of writing an experiment script is implementing the randomization and balancing of conditions across trials. We look at some basic cases below with concrete code patterns. Note you can merge any number of these in the same experiment—this is where the flexibility of MATLAB comes into play.
Remember the general rule: Each trial definition is a distinct trial, i.e. a distinct combination of property values across objects. The trial list then sets repetition and order to run those distinct trials in during the experiment.
Little functions in <><PsychBench folder><>/tools are essential here, e.g. randomNum, randomChoose, randomRoll, randomBalance, randomOrder, etc. Type help tools for an overview.
The simplest case is sampling values from a discrete set (factor) a certain number of times each. Generally the way to do this is to use a for loop to make one trial definition for each distinct value, then in the trial list apply repetition (if any) and randomization to the order the distinct trials will run in (tools rep, randomOrder):
vv = [1 3 5 7 9];
<cdkm>for<cdkm> v = vv
text = textObject;
text.fontSize = v;
...
addTrial(text, ...
<cdkm>end<cdkm>
nn = randomOrder(rep(1:5, 2));
setTrialList(nn);
As above except here each trial definition is a combination of values from multiple sets (factors). Use nested for loops to generate one trial definition for each distinct combination of values, then apply repetition (if any) and randomization to order in the trial list (tools rep, randomOrder).
vv = [1 3 5 7 9];
ww = [<cdsm>"A" "B" "C"<cdsm>];
<cdkm>for<cdkm> v = vv
<cdkm>for<cdkm> w = ww
text = textObject;
text.fontSize = v;
text.text = w;
...
addTrial(text, ...
<cdkm>end<cdkm>
<cdkm>end<cdkm>
nn = randomOrder(rep(1:15, 2));
setTrialList(nn);
Here we test combinations of values from multiple sets (factors), but not every combination. So, we need to additionally randomize which values appear together. In the most basic case we have sets of equal sizes and we just want to shuffle the correspondence between them. We can do this by making a row/column array for each set of values but now with one value per trial definition we want to make, then applying randomization to the order in one of those arrays (tool randomOrder). Then to make trial definitions, run just one for loop and at each iteration draw the value from each array for that trial definition. Finally we still apply randomization to order in the trial list for overall order of presentation. More complex cases are extensions of this logic.
vv = [2 2 2 4 4 4 6 6 6 8 8 8];
ww = [<cdsm>"a" "a" "a" "a" "b" "b" "b" "b" "c" "c" "c" "c"<cdsm>];
ww = randomOrder(ww);
<cdkm>for<cdkm> n = 1:12
v = vv(n);
w = ww(n);
...
text = textObject;
text.fontSize = v;
text.text = w;
...
addTrial(text, ...
<cdkm>end<cdkm>
nn = randomOrder(rep(1:12, 2));
setTrialList(nn);
Like above except when you randomize which values go together it is with the constraint that each distinct value in a set goes an equal number of times with each distinct value in another set (and vice versa). To do this, use the tool randomBalance instead of randomOrder. randomBalance can do a lot—e.g. balance across multiple sets simultaneously and/or jointly, work with various data types, etc.—see its help text for usage.
vv = [2 2 2 4 4 4 6 6 6 8 8 8];
ww = [<cdsm>"a" "a" "a" "a" "b" "b" "b" "b" "c" "c" "c" "c"<cdsm>];
ww = randomBalance(vv, ww);
<cdkm>for<cdkm> n = 1:12
v = vv(n);
w = ww(n);
...
text = textObject;
text.fontSize = v;
text.text = w;
...
addTrial(text, ...
<cdkm>end<cdkm>
nn = randomOrder(rep(1:12, 2));
setTrialList(nn);
Another basic case is independent random sampling, i.e. just re-rolling the dice in each trial (usually from a continuous distribution, e.g. any real number in a given range). The thing to note here is that each trial during the experiment potentially has a different random value, so each trial is a distinct trial and needs its own trial definition. Randomization is applied when setting the value in each trial definition—tools like randomNum and randomNum_normal are useful here. Since randomization is in the trial definitions themselves, there is no need to randomize order in the trial list (unless you're combining with other cases above). Note repeating trial definitions in the trial list would just repeat running the same random values, so generally you don't want to do that.
<cdkm>for<cdkm> n = 1:30
text = textObject;
text.fontSize = randomNum(1, 10);
...
addTrial(text, ...
<cdkm>end<cdkm>
nn = 1:30;
setTrialList(nn);
Here different subjects (runs of the experiment) receive different values taken from a larger set, typically so you can counterbalance values across subjects. To do this, create arrays of values at the across-subjects level as needed, use the usual tools (randomOrder, randomBalance, etc.) to randomize them, and save the arrays to a .mat file. Then at each run of the experiment script load the file, draw the values for that run, and apply them in the script.
Making groups of trials to run in the experiment—such as blocks, or a set of training trials to start the experiment with—is nothing special. Just specify those trials to run together where you want in the trial list (setTrialList). However, often it's convenient to give those trial definitions a custom range of adjacent numbers so you can use simple vector syntax like <cd>101:120<cd> in the trial list. To do this, use an extra input to addTrial to specify a base number, e.g. 100 to tell PsychBench to number those trial definitions 101, 102, 103, ... as addTrial is called (default if you don't use the extra input = 0, which gives 1, 2, 3, ...). See addTrial help text for usage.
The pattern below makes test trial definitions numbered 1–50 and training trials definitions numbered 101–110. The experiment runs the training trials followed by the test trials, each group in random order. For full examples see demo experiments like <><PsychBench folder><>/docs/demos/experiments/gonogoDemo.m.
<cdkm>for<cdkm> n = 1:50
...
addTrial(...);
<cdkm>end<cdkm>
<cdkm>for<cdkm> n = 1:10
...
addTrial(..., 100);
<cdkm>end<cdkm>
nn = [randomOrder(101:110) randomOrder(1:50)];
setTrialList(nn)
Special events like an intro and breaks are usually best implemented as one-off trials. Like above, just specify them to run where you want in the trial list (setTrialList). Often it's convenient to give one-off trials custom names as opposed to numbers, just for clarity. To do this, use the extra input to addTrial but specify a name in a string. Then for the trial list you can mix vectors and strings (<cds>"<cds> or <cds>'<cds>) in a cell array. Note you can repeat names in the trial list just like numbers.
Here we extend the example from above by adding an intro before the training trials and a break after. For full examples see demo experiments like <><PsychBench folder><>/docs/demos/experiments/gonogoDemo.m.
<cdkm>for<cdkm> n = 1:50
...
addTrial(...);
<cdkm>end<cdkm>
<cdkm>for<cdkm> n = 1:10
...
addTrial(..., 100);
<cdkm>end<cdkm>
...
addTrial(..., <cdsm>"intro"<cdsm>);
...
addTrial(..., <cdsm>"break"<cdsm>);
nn = {<cdsm>"intro"<cdsm> randomOrder(101:110) <cdsm>"break"<cdsm> randomOrder(1:50)};
setTrialList(nn)