Audio element

As of June 2019, some browsers implement a new policy preventing multimedia content from automatically playing before the user has interacted with the page. As a consequence, do not assume playback will automatically start if this is the first thing that should happen when your participants land on the page of the experiment. Most often though, you will have a screen asking your participants to click a button or press a key before you try to play any audio, which qualifies as an interaction enabling automatic playback later on.

Audio elements let you add audio streams to the page:

Creation:

newAudio("myAudio", "http://myserver/audio.mp3")

Creates an <audio> tag in the cache.

By default, PennController is set to preload all resources, which means the trial will not start until the audio stream is stored in the browser’s cache.

You can omit the host URL if:
– you have already added it to the list of host URLs with the command PennController.AddHost.
– or, the audio file is contained in a Zip file (see the Using Zip files page)

newAudio("myAudio", "audio.mp3")

You can take a look at the w3schools HTML5 Audio page to learn more about audio support (as of March 2019, only WAV files appear to be supported across the board in IE, Firefox, Chrome, Safari and Opera).

Actions

audio.pause

getAudio(id).pause() (since beta 0.3)

Pauses the audio file.

Example:

newAudio("sentence", "test.mp3")
    .play()
,
newTimer("preview", 750)
    .start()
    .wait()
,
getAudio("sentence")
    .pause()
    .print()
    .wait()

Starts playing the file test.mp3, pauses it after 750ms, and shows controls on the screen making it possible to resume playback.

audio.play

getAudio(id).play()

or getAudio(id).play("loop") (since PennController 1.6)

or getAudio(id).play("once") (since PennController 1.6)

Starts playing the audio file.

If you pass "loop" as a parameter, the audio will loop forever until the next stop command, or until play is called again, this time using the parameter "once". Note that if you use play("loop"), then stop playback using stop and use play() again later (that is, without passing "once" as a parameter) the audio will loop again.

Example:

newAudio("test", "test.mp3")
    .play()
    .wait()

Starts playing the file test.mp3 (nothing is added onto the screen).

audio.print

getAudio(id).print()

Adds the <audio> element to the screen, containing buttons to control playback and volume.

Example:

newAudio("test", "test.mp3")
    .print()
    .wait()

Adds an interface with buttons to start/stop playing the audio file test.mp3 onto the screen.

audio.remove

getAudio(id).remove()

Removes the <audio> element from the screen, which contained buttons to control playback and volume.

Example:

newAudio("beep", "test.mp3")
    .print()
    .wait()
,
getAudio("beep")
    .remove()

Adds an interface to start/stop playing the audio file test.mp3 onto the screen, and removes it from the screen after the audio has been fully played.

audio.stop

getAudio(id).stop() (since beta 0.3)

Stops the playback of the audio and goes back to the start of it, making it impossible to resume from the current position later.

Example:

newAudio("sentence", "test.mp3")
    .play()
,
newTimer("preview", 750)
    .start()
    .wait()
,
getAudio("sentence")
    .stop()

Starts playing the file test.mp3 and stops it after 750ms. Calling play later on would not resume playback from there, but start all over from the beginning.

audio.wait

getAudio(id).wait()

or getAudio(id).wait("first")

or getAudio(id).wait(test)

Waits until the audio resource is done playing before evaluating and executing the next commands.

If you call wait("first"), then if the audio has already been played at least once by the time this command is evaluated, the next commands are evaluated and executed right away. If it was never played before, the next commands are only evaluated and executed after the audio has been played.

If you pass a test on an element as an argument, it only evaluates and executes the next commands when the audio finishes playing while the test is successful. If the test is not successful, it will be checked again next time the audio finishes playing.

Example:

newAudio("sentence", "test.mp3")
    .play()
,
newButton("validation", "Validate")
    .print()
    .wait()
,
getAudio("sentence")
    .wait("first")
,
newText("thanks", "Thank you for listening")
    .print()

Starts playing the file test.mp3 and adds a button onto the screen. If the button is clicked before the audio is done playing for the first time, it waits until the end before printing the text Thank you for listening.

standard.refresh

getElement(id).refresh() (since PennController 1.1)

Calls the command print again on the element, while leaving it where it was printed last.

This command is primarily useful if some aesthetic command does not take effect unless print is called afterward, but you do not want to call print because calling it when you need the aesthetic command to take effect would move the element undesirably.

NOTE: PennController 1.1 handles this command incorrectly and it bugs systematically (draft)

Example:

newImage( "smiley" , "ya.png" )
,
newCanvas( "myCanvas" , 100 , 100 )
    .settings.css( "border" , "solid 1px black" )
    .settings.center()
    .settings.add( "center at 50%" , "center at 50%" , getImage("smiley") )
    .print()
,
newButton( "resize" , "Resize the image" )
    .print()
    .wait()
,
getImage( "smiley" )
    .settings.size( 64, 64 )
,
getCanvas("myCanvas")
    .refresh()

Prints the smiley image at the center of the canvas and adds a button below the canvas. After the button is clicked, the smiley is resized to 64×64 and without refresh the smiley would no longer be centrally aligned on canvas. Calling print on the canvas would recenter the image correctly, but it would move the canvas below the button. The solution is therefore to call refresh (or to call the settings command add again on the canvas).

standard.setVar

getElement(id).setVar( varName ) (since beta 0.3)

Stores the current value of the element in the Var element named varName (if it exists).

What the current value corresponds to is specific to each element type:

  • For Audio elements, it corresponds to the timestamp of the end of the last playback so far (0 if never played back or playback never eneded).
  • For Button elements, it corresponds to the timestamp of the last click so far (0 if never clicked).
  • For Canvas elements, it corresponds to the number of elements it currently contains.
  • For Function elements, it corresponds to the value returned by the function.
  • For Html elements, it corresponds to whether the element is completely filled.
  • For Image elements, it corresponds to whether the image is currently being displayed.
  • For Key elements, it corresponds to the last key that was pressed so far (empty string if no key has been pressed so far).
  • For Scale elements, it corresponds to the value of the last option that was selected (NaN if no option has been selected so far).
  • For Selector elements, it corresponds to the last element that was selected (null if no element has been selected so far).
  • For Text elements, it corresponds to the current text.
  • For TextInput elements, it corresponds to the text that is currently in the input box.
  • For Timer elements, it corresponds to whether the timer has elapsed.
  • For Tooltip elements, it corresponds to whether the tooltip has been validated.
  • For Var elements, it corresponds to the element’s value.
  • For Video elements, it corresponds to the timestamp of the end of the last playback so far (0 if never played back or playback never eneded).
  • For VoiceRecorder elements, it corresponds to the last recording (undefined if no recording so far).
  • For Youtube elements, it corresponds to whether the video has been played.

Example:

newVar("name")
,
newTextInput("nameInput", "What is your name?")
    .settings.once()
    .print()
    .wait()
    .setVar("name")
,
newText("helloname")
    .settings.before( newText("hello", "Hello ") )
    .settings.text( getVar("name") )
    .print()

Creates a Var element named name and adds a text box in which to enter a name. When the return/enter key is pressed while editing the input box, it disables the box and stores its value in the Var element named name. Then it prints a text reading Hello name, where name corresponds to the value of the Var element.

Settings

audio.settings.disable

getAudio(id).settings.disable()

or getAudio(id).settings.disable(opacity) (since PennController 1.6)

Will disable the controls interface. Note that some browsers might be render disabled interfaces as a plain gray rectangle with no controls visible whatsoever.

If you pass no parameter, a 50% opaque layer will be printed on top of the interface. You can pass a value from 0.01 (fully transparent***) to 1 (fully opaque) to control the aspect of the layer.

*** the value 0 is mistreated as no-parameter by PennController 1.6, hence the need to use a non-null value close to 0.

Example:

newAudio("test.mp3")
  .print()
  .settings.disable()
  .play()
  .wait()

Prints a disabled interface for the audio test.mp3 and starts playing it.

audio.settings.log

getAudio(id).settings.log()

or getAudio(id).settings.log("play")

or getAudio(id).settings.log("pause")

or getAudio(id).settings.log("end")

or getAudio(id).settings.log("seek")

Tells to add a line in the results file each time an event happens. If you do not specify which event you want to log, all of them will add a line to the results file.

“play” adds a line including a timestamp and an offset, as the playing is sometimes detected only after a delay. You can compute the actual timestamp for when some sound started to be emitted by subtracting the offset from the timestamp.

“pause” and “end” add lines of the same format for the respective events. “seek” adds a line when the progress bar is clicked (visible if the controls are displayed, see the action print).

Example:

newText("instructions", "Please listen to the audio below")
    .print()
,
newAudio("sentence", "test_sentence.ogg")
    .settings.once()
    .settings.log()
    .print()
    .wait()
,
newButton("validation", "Validate")
    .print()
    .wait()

Adds some instruction text to the screen and control buttons below the text. After the audio gets played, the control buttons are grayed out, and a Validate button appears below them.

The results file will contain as many lines for when the audio was played, paused, when it ended playing and when its position was changed by clicking the progress bar.

audio.settings.once

getAudio(id).settings.once()

Disables the buttons to play/pause the audio right after its first playing (the audio can still be played using the action play).

Example:

newText("instructions", "Please listen to the audio below")
    .print()
,
newAudio("sentence", "test.mp3")
    .settings.once()
    .print()
    .wait()
,
newButton("validation", "Validate")
    .print()
    .wait()

Adds some instruction text to the screen and control buttons below the text. After the audio gets played, the control buttons are grayed out, and a Validate button appears below them.

standard.settings.after

getElement(id).settings.after( getElement(id) )

Adds some content to the right of the element.

Example:

newImage("bad", "no.png")
,
newImage("good", "ya.png")
,
newText("left label", "Bad")
    .settings.before( getImage("bad") )
,
newText("right label", "Good")
    .settings.after( getImage("good") )
,
newScale("judgment", 5)
    .settings.before( getText("left label") )
    .settings.after( getText("right label") )
    .print()
    .wait()

Creates two image and two text elements and prints a 5-point radio-button scale, with the text Bad preceded by the image no.png on its left, and the text Good followed by the image ya.png on its right.

standard.settings.before

or getElement(id).settings.before( getElement(id) )

Adds some content to the left of the element.

Example:

newImage("bad", "no.png")
,
newImage("good", "ya.png")
,
newText("left label", "Bad")
    .settings.before( getImage("bad") )
,
newText("right label", "Good")
    .settings.after( getImage("good") )
,
newScale("judgment", 5)
    .settings.before( getText("left label") )
    .settings.after( getText("right label") )
    .print()
    .wait()

Creates two image and two text elements and prints a 5-point radio-button scale, with the text Bad preceded by the image no.png on its left, and the text Good followed by the image ya.png on its right.

standard.settings.center

getElement(id).settings.center()

Makes the element appear centered on the horizontal axis.

Example:

newText("helloworld", "Hello world")
    .settings.center()
    .print()

Prints Hello world onto the screen, horizontally centered.

standard.settings.css

getElement(id).settings.css("styleName", "style")

or getElement(id).settings.css({"style name 1": "style 1", "style name 2": "style 2"})

Applies the CSS style(s) to the element.

Example:

newText("frame", "framed")
    .settings.css("border", "solid 1px black")
,
newText("sentence", "The last word of this sentence is ")
    .settings.after( getText("frame") )
    .print()

Prints a text reading The last word of this sentence is framed, with the last word (framed) framed in a box with 1px black borders.

standard.settings.cssContainer

getElement(id).settings.cssContainer("styleName", "style")

or getElement(id).settings.cssContainer({"style name 1": "style 1", "style name 2": "style 2"})

Applies the CSS style(s) to the container’s element.

This will affect both the element itself and any element wrapping it as added via .settings.before or .settings.after.

This command often more closely accomplishes what you want to achieve than the .settings.css command.

Example:

newText("frame", " world")
    .settings.before( newText("Hello ") )
    .settings.css("border", "solid 1px black")
    .settings.cssContainer("border", "solid 1px red")
    .print()

Prints the text world preceded with the text Hello and adds a black frame around world and a red frame around the whole Hello world text.

standard.settings.enable

getElement(id).settings.enable()

Enables any interactive feature of the element that was previously disabled.

Example:

newAudio("sentence", "test.mp3")
    .settings.once()
    .print()
,
newKey("secret key", "R")
    .wait()
,
getAudio("sentence")
    .settings.enable()

Prints buttons to play/pause the audio file test_sentence.ogg, and disables those buttons when the file has played through (see audio.settings.once).

standard.settings.hidden

getElement(id).settings.hidden() (since beta 0.3)

Makes the element invisible. Note that when printed, a hidden element still occupies space on the page, but its content is not visible.

Example:

newText("instruction", "Guess what fruit is in the image below")
    .print()
,
newImage("fruit", "pear.png")
    .settings.hidden()
    .print()
,
newButton("reveal", "Reveal fruit")
    .print()
    .wait()
,
getImage("fruit")
    .settings.visible()

Adds some text to the page, a blank space below it and a button below the blank space which, when clicked, reveals an image of a pear.

standard.settings.left

getElement(id).settings.left()

Makes the element appear horizontally aligned to the left (default).

Note: the left means the left of the container of the element, not necessarily the left of the screen.

Example:

newText("helloworld", "Hello world")
    .settings.right()
    .print()
,
newButton("left", "Align text to the left")
    .print()
    .wait()
,
getText("helloworld")
    .settings.left()

Prints Hello world onto the screen, horizontally aligned to the left.

standard.settings.right

getElement(id).settings.right()

Makes the element appear horizontally aligned to the right.

Note: the right means the right of the container of the element, not necessarily the right of the screen.

Example:

newText("sentence", "This is a longer sentence")
    .print()
,
newText("helloworld", "Hello world")
    .settings.right()
    .print()

Prints Hello world onto the screen, horizontally aligned to the right.

standard.settings.selector

getElement(id).settings.selector( selectorName )

or getElement(id).settings.selector( getSelector(selectorName) ) (since beta 0.3)

Since beta 0.3, Selector adds a .settings.selector command to all elements as another method for adding them to a selector.

Example:

newSelector("shapes")
,
newImage("square", "square.png")
    .settings.selector("shapes")
,
newImage("triangle", "triangle.png")
    .settings.selector("shapes")
,
newCanvas("shapes canvas", 825, 400)
    .settings.add(  0, 0, getImage("square") )
    .settings.add(425, 0, getImage("triangle") )
    .print()
,
getSelector("shapes")
    .wait()

Adds two images side by side and waits for a click on either one of them.

standard.settings.size

getElement(id).settings.size(width, height)

Resizes the element to a width of width pixels and a height of height pixels.

Example:

newImage("smiley", "pear.png")
    .settings.size(40, 40)
    .print()

Adds a 40x40px image pear.png onto the screen.

standard.settings.visible

getElement(id).settings.visible() (since beta 0.3)

Makes the element visible (again). This is useful if you previously hid the element with .settings.hidden.

Example:

newText("instruction", "Guess what fruit is in the image below")
    .print()
,
newImage("fruit", "pear.png")
    .settings.hidden()
    .print()
,
newButton("reveal", "Reveal fruit")
    .print()
    .wait()
,
getImage("fruit")
    .settings.visible()

Adds some text to the page, a blank space below it and a button below the blank space which, when clicked, reveals an image of a pear.

Tests

audio.test.hasPlayed

getAudio(id).test.hasPlayed() (since beta 0.3)

Tests whether the audio stream has played at least once before in the trial.

audio.test.playing

getAudio(id).test.playing() (since beta 0.3)

Tests whether the audio stream is playing at the moment when the test command gets evaluated.

standard.test.printed

getElement(id).test.printed() (since beta 0.3)

Tests whether the element was printed onto the page (and has not been removed since then).

Example:

newText("instructions", "Click on Top/Bottom to NOT print its word")
    .print()
,
newButton("top", "Top")
    .settings.callback( getButton("top").remove() ) 
    .print()
,
newButton("bottom", "Bottom")
    .settings.callback( getButton("bottom").remove() )
    .print()
,
newButton("print", "Print the buttons' words")
    .print()
    .wait()
    .remove()
,
getButton("top")
    .test.printed()
    .success( newText("top word", "hello").print() )
,
getButton("bottom")
    .test.printed()
    .success( newText("bottom word", "world").print() )

Prints a Top and a Bottom button onto the page, which disappear when clicked. After a click on the print button, the word hello will appear if the top button is still displayed, and the word world will appear if the bottom button is still displayed.