Timers & Randomization

Adding Timers & Delays

When you tried out the experiment, you probably noticed that the trials came in rapid sequence without any pauses between them, with picture selection (or end of audio) marking the end of the trial, followed by an immediate transition to the next trial.
It would probably be a good idea to add some time both at the beginning and at the end of a trial, to create a brief pause between trials. To do this, simply use newTimer:

your script should also still contain the welcome trial above this

PennController.Template( 
  variable => PennController(
    newTimer(500)
        .start()
        .wait()
    ,
    newAudio("description", variable.AudioFile)
        .play()
    ,
    newText(variable.Description)
        .unfold(2600)
    ,
    newImage("two", variable.PluralImageFile)
        .settings.size(200,200)
    ,
    newImage("one", variable.SingularImageFile)
        .settings.size(200,200)
    ,
    newCanvas(450,200)
        .settings.add(   0 , 0 , getImage("two") )
        .settings.add( 250 , 0 , getImage("one") )
        .print()
    ,
    newSelector()
        .settings.add( getImage("two") , getImage("one") )
        .settings.keys(          "F"    ,          "J"   )
        .settings.log()
        .wait()
    ,
    getAudio("description")
       .wait("first")
    ,
    newTimer(500)
        .start()
        .wait()
  )
  .log( "ID"     , getVar("ID")    )
  .log( "Item"   , variable.Item   )
  .log( "Ending" , variable.Ending )
  .log( "Group"  , variable.Group  )
)

(Note that using a template makes things much easier when we want to update details of our trial structure for a whole sequence of trials!)

Following the general logic of PennController, creating a timer element in the cache does not on its own start the timer, in much the same way that creating a text or an image element does not print it. If you don’t want to freeze your experiment, make sure that a timer has been started prior to a .wait command relating to it being executed.

Randomization

There are at least two things you might want to randomize in your experiment.

The images’ positions

Having a static layout for your stimuli, with fixed positions for images of a certain type, is usually not a good idea, unless it was part of your design for some reason.
In our case, it could be that participants are in general faster to choose any image that appears on the left (maybe English readers tend to process visual information from left to right, leading to reaction time advantage for images on the left) and since our current version always prints the two images on the left, this creates a potential confound that could wrongly lead us to conclude that people are faster to notice the absence of -s than the presence of -s.

To remedy this, we can randomly shuffle the positions of all the elements contained in a selector using the command .shuffle:

PennController.Template( 
  variable => PennController(
    newTimer(500)
        .start()
        .wait()
    ,
    newAudio("description", variable.AudioFile)
        .play()
    ,
    newText(variable.Description)
        .unfold(2600)
    ,
    newImage("two", variable.PluralImageFile)
        .settings.size(200,200)
    ,
    newImage("one", variable.SingularImageFile)
        .settings.size(200,200)
    ,
    newCanvas(450,200)
        .settings.add(   0 , 0 , getImage("two") )
        .settings.add( 250 , 0 , getImage("one") )
        .print()
    ,
    newSelector()
        .settings.add( getImage("two") , getImage("one") )
        .shuffle()
        .settings.keys(          "F"    ,          "J"   )
        .settings.log()
        .wait()
    ,
    getAudio("description")
       .wait("first")
    ,
    newTimer(500)
        .start()
        .wait()
  )
  .log( "ID"     , getVar("ID")    )
  .log( "Item"   , variable.Item   )
  .log( "Ending" , variable.Ending )
  .log( "Group"  , variable.Group  )
)

It is important here that .shuffle gets executed after the images are printed (through the canvas’ .print command above) and added to the selector (otherwise, there is nothing to shuffle) but also before the command .settings.keys is executed: this way, shuffling happens before key assignment, and whichever image ends up to the left and right will be respectively associated with the F and J keys. If .settings.keys were executed before .shuffle then the association would take place before the shuffling. (Such a scenario would maybe make sense if you wanted to respectively associate the two and one images with the numeric keys 2 and 1 regardless of where each image ends up on the screen.)

The sequence of trials

Manipulating the order in which the trials are presented requires labeling them first. Note that labels are not unique IDs: two trials can share the same label, which we will make use of.

For all the trials you define with PennController(...), you can specify a label using a string as the first argument: PennController( "label" , ... ).

(Note that some steps in the trial sequence require a slightly different format. For example, the command PennController.SendResults has no pair of parentheses appended to PennController: instead, you can define a label within the parentheses of SendResults: PennController.SendResults( "label" ).)

To gain explicit control over the order of the overall trial sequence, you can specify your own order using the command PennController.Sequence:

PennController.ResetPrefix(null)

PennController.Sequence( "welcome" , randomize("experiment") , "send" , "final" )

PennController( "welcome" ,
    defaultText
        .print()
    ,
    newText("<p>Welcome!</p>")
    ,
    newText("<p>In this experiment, you will have to report which of two pictures matches a description.</p>")
    ,
    newText("<p>Press the <strong>F</strong> key for the picture on the left, or the <strong>J</strong> key for the picture on the right.</p>")
    ,
    newText("<p>Please enter your ID and then click the button below to start the experiment.</p>")
    ,
    newTextInput("ID")
        .print()
    ,
    newButton("Start")
        .print()
        .wait()
    ,
    newVar("ID")
        .settings.global()
        .set( getTextInput("ID") )
)
.log( "ID" , getVar("ID") )


PennController.Template( 
  variable => PennController( "experiment" ,
    newTimer(500)
        .start()
        .wait()
    ,
    newAudio("description", variable.AudioFile)
        .play()
    ,
    newText(variable.Description)
        .unfold(2600)
    ,
    newImage("two", variable.PluralImageFile)
        .settings.size(200,200)
    ,
    newImage("one", variable.SingularImageFile)
        .settings.size(200,200)
    ,
    newCanvas(450,200)
        .settings.add(   0 , 0 , getImage("two") )
        .settings.add( 250 , 0 , getImage("one") )
        .print()
    ,
    newSelector()
        .settings.add( getImage("two") , getImage("one") )
        .shuffle()
        .settings.keys(          "F"    ,          "J"   )
        .settings.log()
        .wait()
    ,
    getAudio("description")
       .wait("first")
    ,
    newTimer(500)
        .start()
        .wait()
  )
  .log( "ID"     , getVar("ID")    )
  .log( "Item"   , variable.Item   )
  .log( "Ending" , variable.Ending )
  .log( "Group"  , variable.Group  )
)


PennController.SendResults( "send" )


PennController( "final" ,
    newText("<p>Thank you for your participation!</p>")
        .print()
    ,
    newText("<p><a href='https://www.put.your/platform/confirmation/link.here'>Click here to validate your participation.</a></p>")
        .print()
    ,
    newButton("void")
        .wait()
)

Our command PennController.Sequence( "welcome" , randomize("experiment") , "send" , "final" ) does not change the order much here, since it still corresponds to the order of trial elements in the script. However, the additional specification of randomize("experiment") makes sure that all the trials labeled experiment (all the table-generated trials, see line 32) will be run in a random order.

The list of commands like randomize that you can use within Sequence (which come from the basic Ibex setup, and are not special to PennController) are listed in the Ibex manual.


Next: Data analysis in R