Audio, Text unfolding and Clicks

Audio

PennController makes playing audio simple: just create an audio element in the cache using newAudio and play it using the command .play.

the scripts on this page correspond to the content of the picture-selection PennController(...)

newAudio("2fishRoundTank.mp3")
    .play()
,
newText("The fish swim in a tank which is perfectly round")
    .print()
,
newImage("two", "2fishRoundTank.png")
    .settings.size(200,200)
,
newImage("one", "1fishSquareTank.png")
    .settings.size(200,200)
,
newCanvas(450,200)
    .settings.add(   0 , 0 , getImage("two") )
    .settings.add( 250 , 0 , getImage("one") )
    .print()
,
newKey("FJ")
    .settings.log()
    .wait()

You might want more explicit control over what happens to audio play back relative to potential key presses before the audio is over. Let us consider two approaches: (a) you could decide to stop playback when a key is pressed, or (b) you could decide to wait until playback is over before moving on to the next trial. Both of these require us to do something with the audio element after a key is pressed, in other words you will want to add commands after the key element’s .wait command, which will refer to the audio element you created at the beginning of the trial.

Stopping play back at key press

In order to refer back to your audio element, you first have to name it. Then you can add a new last block of commands relating to it at the end of your trial by adding a comma and referring back to your audio element using getAudio. Thus, after the key press (satisfying the condition imposed by .wait on the key element), the script evaluates getAudio("description") and will interpret the next commands as relating to the audio element that you named description (see line 1). If audio playback should stop when a key is pressed before it’s over, just add .stop as the command on this audio element. After registering the key press, this will stop the audio and then move on in the script to the end of the trial. (If the audio was already finished at the time of the key press, the script will continue in its execution as well.)

newAudio("description", "2fishRoundTank.mp3")
    .play()
,
newText("The fish swim in a tank which is perfectly round")
    .print()
,
newImage("two", "2fishRoundTank.png")
    .settings.size(200,200)
,
newImage("one", "1fishSquareTank.png")
    .settings.size(200,200)
,
newCanvas(450,200)
    .settings.add(   0 , 0 , getImage("two") )
    .settings.add( 250 , 0 , getImage("one") )
    .print()
,
newKey("FJ")
    .settings.log()
    .wait()
,
getAudio("description")
    .stop()

Waiting until audio is over

Alternatively, if you want to wait until playback is over to end the trial, you would use a .wait command on the audio:

newAudio("description", "2fishRoundTank.mp3")
    .play()
,
newText("The fish swim in a tank which is perfectly round")
    .print()
,
newImage("two", "2fishRoundTank.png")
    .settings.size(200,200)
,
newImage("one", "1fishSquareTank.png")
    .settings.size(200,200)
,
newCanvas(450,200)
    .settings.add(   0 , 0 , getImage("two") )
    .settings.add( 250 , 0 , getImage("one") )
    .print()
,
newKey("FJ")
    .settings.log()
    .wait()
,
getAudio("description")
    .wait("first")

The command .wait("first") on the audio element will pause the execution, and therefore prevent the trial from ending, until the audio is done playing.

The "first" part in the parentheses of wait helps to ensure proper interaction with key presses both during and after the audio is over. Without it, execution would only resume when the script detects an end-of-playback event. But if the key press occurs after the audio was over, there will be no more such end-of-playback events, and the condition imposed by wait would never be met, pausing the trial indefinitely. The presence of "first" tells the .wait command to release execution whenever an end-of-playback event has occurred, either after or before the key press: this way, there will be no pause if the end-of-playback event happened before execution of line 23 and the trial will therefore end immediately after the key press, but if instead the key was pressed before the audio was done playing, execution is paused until the end-of-playback event.

Text unfolding

You already know how to instantly print a text element onto the page: just use .print. If you want it to unfold over time, simply use .unfold instead. To align unfolding (roughly) with the auditory playback, we can just time the unfolding relative to the audio file length. The duration of our audio file is approximately 2.6s, so we will use a duration of 2600ms for our .unfold command:

newAudio("description", "2fishRoundTank.mp3")
    .play()
,
newText("The fish swim in a tank which is perfectly round")
    .unfold(2600)
,
newImage("two", "2fishRoundTank.png")
    .settings.size(200,200)
,
newImage("one", "1fishSquareTank.png")
    .settings.size(200,200)
,
newCanvas(450,200)
    .settings.add(   0 , 0 , getImage("two") )
    .settings.add( 250 , 0 , getImage("one" )
    .print()
,
newKey("FJ")
    .settings.log()
    .wait()
,
getAudio("description")
    .wait("first")

Clicks

There is one last change we want to bring to our trial structure: in addition to pressing a key, we want to make it possible to click on the pictures to make a choice. This can seem trivial, but we currently pause the execution until selection happens using .wait on our key element. How can we instead pause the execution until (a) F or J is pressed, or (b) a picture is clicked?

Since all PennController commands are element-based, ideally we would want to use .wait on an element that represents a choice between two options: option (i) a press on the F key or a click on the picture on the left; option (ii) a press on the J key or a click on the picture on the right.

The selector element does just that: it groups elements together and makes them exclusively selectable through clicks or key presses:

newAudio("description", "2fishRoundTank.mp3")
    .play()
,
newText("The fish swim in a tank which is perfectly round")
    .unfold(2600)
,
newImage("two", "2fishRoundTank.png")
    .settings.size(200,200)
,
newImage("one", "1fishSquareTank.png")
    .settings.size(200,200)
,
newCanvas(450,200)
    .settings.add(   0 , 0 , getImage("two") )
    .settings.add( 250 , 0 , getImage("one") )
    .print()
,
// newKey("FJ")
newSelector()
    .settings.add( getImage("two") , getImage("one") )
    .settings.keys(          "F"    ,          "J"   )
    .settings.log()
    .wait()
,
getAudio("description")
   .wait("first")

We replaced the key element with a selector element, to which we added the two images using .settings.add( getImage("two") , getImage("one") ) which we respectively associate with the F and J keys using .settings.keys("F","J") (spaces only for sake of transparency for the reader; PCIbex doesn’t care about them).

We kept the .settings.log command since we’re still interested in the selection event (which picture was selected, and when selection happened). We also kept the .wait command which now applies to the selector element and achieves our goal: it pauses execution until one of the elements added to the selector (that is, one of the two images) is selected, either through a click or through a key press.

Note that as things stand, it’s possible for participants to click multiple times, allowing them to change which picture is selected until the trial is over (here, when the audio play back ends, assuming at least one selection has been made). If you don’t want to allow this, insert a .settings.once() command just before .wait(), and no further clicks or key presses will alter the participant’s initial choice.


Next: Trial templates & tables