Jeremy

Forum Replies Created

Viewing 15 posts - 76 through 90 (of 1,522 total)
  • Author
    Posts
  • in reply to: Question about reaction times #10709
    Jeremy
    Keymaster

    Hi,

    The timestamp that PennController reports for Controller elements corresponds to when it ended; in this case, it corresponds to when the last word disappeared. Because you printed the Text element with your question immediately after waiting for the Controller element, the timestamp reported for the latter is a good approximation of the former. S subtracting that timestamp from the timestamp of the keypress gives you 1651092196900-1651092191988=4912ms, ie. about 5s

    Jeremy

    Jeremy
    Keymaster

    Hi Kate,

    This is because your “pair” column has a trailing space character at the end, which makes all the references to the pair column in the code return undefined. As a result, every single trial generated by it is labeled "experiment_undefined" and therefore the while loop does find three successive trials in new_targets that share a label forever no matter how many times they are shuffled (because they are all labeled "experiment_undefined") and so the script gets stuck there. Just delete the trailing space and it should work

    Jeremy

    in reply to: Branching Structure for filtering participants #10703
    Jeremy
    Keymaster

    Hi AY,

    There is no .test.selected command on the Controller element (and even if there were, you would be testing the selection before it even happened because your wait command only comes later). As a matter of fact, there is no test command on the Controller element whatsoever, because it’s a native-Ibex element. My suggestion is you use the Scale element, as in my example (note that you want to remove the defaultScale commands from Header if you use the code below):

    newText( row.sentence ).center().print()
    ,
    newScale("choice", 5)
      .button()
      .keys()
      .before( newText("Grammatical") )
      .after( newText("Ungrammatical") )
      .log()
      .center()
      .print()
      .wait()
      .test.selected( row.correct_answer ).failure(getVar("error_count").set(v=>v+1))
    

    You can play with the scale’s aesthetics by using CSS rules. See this post for a minimal example

    Jeremy

    Jeremy
    Keymaster

    Hi,

    There are many ways to go about this, but here is a suggestion. You can first use Template to read the CSV table into a javascript dictionary, then manipulate that dictionary in a later Template command to create three items per row, but only in the NEG or POS condition for each pair. Here’s on way of doing it:

    // Note: Template gives us access to the content of the CSV file, but it executes the function it is passed
    // only after this whole script file has been executed, this is why we need to place our code inside Template
    
    const targets = {} // store the rows from the CSV file into a javascript dictionary
    Template("target_test.csv", row => {
        // the dictionary lists all the same-pair items under the same key
        if (targets[row.pair]===undefined) targets[row.pair] = {POS:[],NEG:[]};
        targets[row.pair][row.target_polarity].push(row);
        return {}; // we're not actually generating trials here: return an empty object
    })
    
    AddTable("dummy", "a,b\rc,d") // Dummy one-row table to execute some code from Template once after the code above
    Template("dummy", () => {
        const targetKeys = Object.keys(targets);
        fisherYates(targetKeys);   // shuffle the references to the pairs
        let new_targets = []; // this will contain half the items (only POS or only NEG for each pair)
        for (let i=0; i<targetKeys.length; i++) // keep the POS rows for the first half, the NEG rows for the second half
            new_targets.push( ...targets[targetKeys[i]][ i<targetKeys.length/2 ? "POS" : "NEG"] );
        // Create three items per row that we kept
        new_targets = new_targets.map(t=>
            [ [t['construction 1'],t['c1 question']],[t['construction 2'],t['c2 question']],[t['construction 3'],t['c3 question']] ].map(t2=>
                ["experiment_"+t.pair,"PennController",newTrial(
                    newText([t.context_adj,t.target_adj,t.target_polarity,t2[0],t2[1]].join("<br>")).print()
                    ,
                    newButton("Next").print().wait()
                )]
            ) // this map returns an array of 3 trials
        ).flat(); // flatten the array to have all the trials at the root, instead of having a series of arrays of 3 trials
        // Shuffle new_targets as long as we can find three items in a row that come from the same pair
        while (new_targets.find( (v,i)=>i<(new_targets.length-2) && v[0].split('_')[1]==new_targets[i+1][0].split('_')[1] && v[0].split('_')[1]==new_targets[i+2][0].split('_')[1] ))
            fisherYates(new_targets);
        window.items = new_targets; // now add the trials to the experiment's items
        return {}; // we added the items manually above: return an empty object from Template
    })

    Jeremy

    in reply to: Branching Structure for filtering participants #10697
    Jeremy
    Keymaster

    Hi AY,

    Here’s a basic illustration of what you described:

    // This will be included at the beginning of every trial
    Header(
        newVar("error_count",0) // set the initial value to 0
            .global()           // make sure the Var element is global
            .test.is(v=>v<3)    // the value should be below 3
            .failure(           // if not, then block the script here
                newText("Sorry, too many mistakes. The end.").center().print()
                ,
                newButton().wait() // will wait forever
            )
        ,
        defaultScale.button().print().wait()
    )
    
    // 8 dummy trials
    newTrial( newScale("dummy", "Yes", "No").test.selected("Yes").failure(getVar("error_count").set(v=>v+1)) )
    newTrial( newScale("dummy", "Yes", "No").test.selected("Yes").failure(getVar("error_count").set(v=>v+1)) )
    newTrial( newScale("dummy", "Yes", "No").test.selected("Yes").failure(getVar("error_count").set(v=>v+1)) )
    newTrial( newScale("dummy", "Yes", "No").test.selected("Yes").failure(getVar("error_count").set(v=>v+1)) )
    newTrial( newScale("dummy", "Yes", "No").test.selected("Yes").failure(getVar("error_count").set(v=>v+1)) )
    newTrial( newScale("dummy", "Yes", "No").test.selected("Yes").failure(getVar("error_count").set(v=>v+1)) )
    newTrial( newScale("dummy", "Yes", "No").test.selected("Yes").failure(getVar("error_count").set(v=>v+1)) )
    newTrial( newScale("dummy", "Yes", "No").test.selected("Yes").failure(getVar("error_count").set(v=>v+1)) )
    

    Jeremy

    in reply to: randomized set of scales on different pages #10696
    Jeremy
    Keymaster

    Hi,

    Assuming you are not using Template, you can list the question+scale pairs in an array and shuffle it, then successively fetch the elements from the (shuffled) array in three different trials:

    // array of functions that return a question+scale pair
    SCALES = [
        ()=>[
            newText("question1", "Question 1").center().print(),
            newScale("scale1", 7).before(newText("low X1")).after(newText("high X1")).log().center().print()
        ],
        ()=>[
            newText("question2", "Question 2").center().print(),
            newScale("scale2", 7).before(newText("low X2")).after(newText("high X2")).log().center().print()
        ],
        ()=>[
            newText("question3", "Question 3").center().print(),
            newScale("scale3", 7).before(newText("low X3")).after(newText("high X3")).log().center().print()
        ],
        ()=>[
            newText("question4", "Question 4").center().print(),
            newScale("scale4", 7).before(newText("low X4")).after(newText("high X4")).log().center().print()
        ],
        ()=>[
            newText("question5", "Question 5").center().print(),
            newScale("scale1", 7).before(newText("low X5")).after(newText("high X5")).log().center().print()
        ],
        ()=>[
            newText("question6", "Question 6").center().print(),
            newScale("scale6", 7).before(newText("low X6")).after(newText("high X6")).log().center().print()
        ],
        // etc.
    ]
    
    // shuffle the array
    fisherYates(SCALES)
    
    newTrial( "page1",
        ...SCALES[0](), // insert the commands returned by the function in the first entry of the array
        ...SCALES[1](), // insert the commands returned by the function in the second entry of the array
        ...SCALES[2](), // etc.
        newButton("Next").center().print().wait()
    )
    newTrial( "page2",
        ...SCALES[3](),
        ...SCALES[4](),
        ...SCALES[5](),
        newButton("Next").center().print().wait()
    )
    // etc.
    

    Jeremy

    in reply to: Multiple use of the same object #10693
    Jeremy
    Keymaster

    Hello Christin,

    There is a method to set the text of a Text element: .text

    Here is an illustration of what I think you are trying to achieve:

    TEXTS = [
        null, // index 0, unused
        (m,l)=>`Der ${m} hat eine Leistung von ${l} PS`, // index 1
        (m,l)=>` er hat eine Leistung von ${l} PS.`, // index 2
        (m,l)=>` test ${l} test2 ${m}.`, // index 3
        // etc. 
    ]
    
    Tempalte( variable =>
     newTrial(
        newButton("PS") // create this button now (don't apply the defaultButton below to this button)
        ,
        // I just use these two buttons for testing purposes
        newButton("Next text").callback( getVar("nr").set(v=>v+1) ),
        newButton("Switch side").callback( getVar("side").set(d=>d=="left"?"right":"left") )
        ,
        newVar("side", "left"), // which side to print the text
        newVar("nr", 1),        // which text to print (will look up TEXTS using the value)
        newVar("canvasnr",1)    // which canvas to print on
        ,
        defaultCanvas.css( "border" , "solid 1px black" ).print() // add a border a print all the canvases created below
        ,
        defaultButton.callback( getVar("canvasnr").set(v=>v+1) ) // increment canvasnr upon click on the buttons created below
        ,
        newCanvas("canvas1", 780, 20) // this button will make canvas2 visible
            .add("right at 100%", "middle at 50%", newButton("+1").callback(getCanvas("canvas2").visible(),self.remove()) )
            .add(0, "middle at 50%", newText("left1",""))
            .add("right at 90%", "middle at 50%", newText("right1",""))
        ,
        defaultCanvas.hidden() // hide all the canvases created below
        ,
        newCanvas("canvas2", 780, 20) // this button will make canvas3 visible
            .add("right at 100%", "middle at 50%", newButton("+2").callback(getCanvas("canvas3").visible(),self.remove()) )
            .add(0, "middle at 50%", newText("left2",""))
            .add("right at 90%", "middle at 50%", newText("right2",""))
        ,
        newCanvas("canvas3", 780, 20)
            .add("right at 100%", "middle at 50%", newButton("+3").callback(getCanvas("canvas4").visible(),self.remove()) )
            .add(0, "middle at 50%", newText("left3",""))
            .add("right at 90%", "middle at 50%", newText("right3",""))
        ,
        newCanvas("canvas4", 780, 20)
            // etc.
        ,
        getButton("PS")
            .callback( 
                // Create a Var element whose text content is fetched from TEXTS, based on the index in nr
                newVar("txt").set(getVar("nr")).set(n=>TEXTS[n](variable.model,variable.LeistungPS))
                ,
                getVar("side").test.is("left").success( 
                    // left
                    getVar("canvasnr")
                        .test.is(1).success( getText("left1").text(getVar("txt")) )
                        .test.is(2).success( getText("left2").text(getVar("txt")) )
                        .test.is(3).success( getText("left3").text(getVar("txt")) )
                        // etc.
                ).failure(
                    // right
                    getVar("canvasnr")
                        .test.is(1).success( getText("right1").text(getVar("txt")) )
                        .test.is(2).success( getText("right2").text(getVar("txt")) )
                        .test.is(3).success( getText("right3").text(getVar("txt")) )
                        // etc.
                )
            )
            .center()
            .print()
        ,
        // etc.
        getButton("Next text").center().print(),getButton("Switch side").center().print()
        ,
        newKey(" ").wait()
      )
    )
    

    Jeremy

    in reply to: Multiple use of the same object #10686
    Jeremy
    Keymaster

    Dear Christin,

    If you want some text to be displayed at multiple locations on the page at the same time, then you need as many Text elements. If you know in advance that you’ll have up to 10 elements, you can create all 10 elements in advance:

    newCanvas("canvas1", 200,200),newCanvas("canvas2", 200,200),newCanvas("canvas3", 200,200),newCanvas("canvas4", 200,200),newCanvas("canvas5", 200,200),
    newCanvas("canvas6", 200,200),newCanvas("canvas7", 200,200),newCanvas("canvas8", 200,200),newCanvas("canvas9", 200,200),newCanvas("canvas10", 200,200)
    ,
    newText("Beschleunigung1"),newText("Beschleunigung2"),newText("Beschleunigung3"),newText("Beschleunigung4"),newText("Beschleunigung5"),
    newText("Beschleunigung6"),newText("Beschleunigung7"),newText("Beschleunigung8"),newText("Beschleunigung9"),newText("Beschleunigung10")
    ,
    newVar("nr",1)
    ,
    newButton("speed")
      .callback( getVar("nr")
        .test.is(1).success( getText("Beschleunigung1").print("left at 55%","middle at 50%",getCanvas("canvas1")) )
        .test.is(2).success( getText("Beschleunigung2").print("left at 55%","middle at 50%",getCanvas("canvas2")) )
        .test.is(3).success( getText("Beschleunigung3").print("left at 55%","middle at 50%",getCanvas("canvas3")) )
        .test.is(4).success( getText("Beschleunigung4").print("left at 55%","middle at 50%",getCanvas("canvas4")) )
        .test.is(5).success( getText("Beschleunigung5").print("left at 55%","middle at 50%",getCanvas("canvas5")) )
        .test.is(6).success( getText("Beschleunigung6").print("left at 55%","middle at 50%",getCanvas("canvas6")) )
        .test.is(7).success( getText("Beschleunigung7").print("left at 55%","middle at 50%",getCanvas("canvas7")) )
        .test.is(8).success( getText("Beschleunigung8").print("left at 55%","middle at 50%",getCanvas("canvas8")) )
        .test.is(9).success( getText("Beschleunigung9").print("left at 55%","middle at 50%",getCanvas("canvas9")) )
        .test.is(10).success( getText("Beschleunigung10").print("left at 55%","middle at 50%",getCanvas("canvas10")) )
        .set(v=>v+1)
      )
      .print()
    

    Jeremy

    in reply to: run command while MediaRecorder is recording #10685
    Jeremy
    Keymaster

    Hi,

    The most straightforward solution that will give you the most control is probably to code the recording interface yourself. Here’s an example:

    newTrial(
        // create the elements but don't print/start them yet
        newButton("Done"),
        newMediaRecorder("audio").log()
        ,
        newButton("Stop recording")
            .callback(
                // Whenever stop is clicked we remove it, stop+print the recorder, and (re-)print record and done
                getButton("Stop recording").remove(),
                getMediaRecorder("audio").stop().print(),
                getButton("Record").print(),
                getButton("Done").print()
            )
        ,
        newButton("Record")
            .callback(
                // Whenever record is clicked we remove it, remove done, remove+start the recorder and print stop
                getButton("Record").remove(),
                getButton("Done").remove(),
                getMediaRecorder("audio").remove().record(),
                getButton("Stop recording").print()
            )
            .print() // start the trial by printing record
        ,
        // the script will stay on this line until done is clicked
        getButton("Done").wait()
    )

    Since you handle recording manually, you’ll want to hide the default “Record” button printed along with the MediaRecorder element. Just add this to Aesthetics/PennController.css:

    .MediaRecorder-record {
        display: none;
    }

    Jeremy

    in reply to: Results log not complete #10684
    Jeremy
    Keymaster

    Hi,

    Actually you are passing "final" to your Scale elements’ log commands, when it should be "last" (reference). Ironically, passing "final" instead of "last" causes a bug that prevents logging any (and so, a fortiori, the last) choice when several choices happened on the same scale

    Also note that you still have the ill-formed test on the Scale element(s) I mentioned above which allows participants to click the button and proceed without having made a selection on all the scales, in which case you would also not see lines in the results file for unselected scales

    Jeremy

    in reply to: Group Columns – distribution #10678
    Jeremy
    Keymaster

    Hi,

    You can definitely just add server.py?withsquare=0 to the URL you gave and the experiment will run normally using the first group; it won’t impact the sending of the results (if the results won’t get sent, then there’s probably an issue with your experiment’s sequence). However, you shouldn’t append .html to the number that comes after the = character

    Jeremy

    in reply to: Checking if scale item with canvas label is selected #10675
    Jeremy
    Keymaster

    Hello,

    When you set an option’s label using a reference to a PennController element, you can test the selected value by using that element’s name. As a consequence, you’ll want to make sure that when multiple labels refer to PennController elements, the elements have different names (which they do, in your case). So you can do this:

    newScale('pltest', "0", "1", "2")
        .vertical()
        .radio()
        .label(0, getCanvas('pl_0_can'))
        .label(1, getCanvas('pl_1_can'))
        .label(2, getCanvas('pl_2_can'))
        .labelsPosition("right")
        .settings.center()
        .print()
        .wait(getScale('pltest').test.selected("pl_0_can"))

    Jeremy

    Jeremy
    Keymaster

    Hello Lorrainy,

    Your script works as expected: when it reaches the .wait() line on the Key element, it stays there until the A or L key is pressed. Once either key is pressed, it moves on to the next line, which is the callback line, and on to the next lines until it reaches the .wait() line on the Timer element, and stays there until the timer ends

    So only after an initial key press on A or L will further key presses on A or L stop the timer. If you never press A or L, the script will remain on the Key element’s wait line forever. If you press A or L once, it will stay on the Timer element’s wait line until the 6 seconds have passed. If you press A or L a second time within 6s, then the timer will end early and so will the trial

    If I understand you correctly, you simply want to take out the .wait() line on the Key element

    Jeremy

    in reply to: Storing trial order information in audio file name #10672
    Jeremy
    Keymaster

    Hello,

    Unfortunately, since elements are created before the running order is determined, naming the audio elements after the order in which the trials are run would be challenging. You could always try uploading an edited version of PennController_media.js to your project, in which you replace let fileName = PennEngine.utils.guidGenerator()+'.zip'; with something like let fileName = PennEngine.utils.guidGenerator()+'-'+PennEngine.controllers.running.id+'.zip'; to overwrite the MediaRecorder element

    Jeremy

    in reply to: Results log not complete #10671
    Jeremy
    Keymaster

    Hi,

    You need to close your ands so you can attach failure on their closing parenthesis:

    newFunction('dummy', ()=>true).test.is(true)
      .and( getScale("selbstbewusstsein").test.selected()
        .failure( newText('errorscales', "<br>Bitte auf jeder Skala einen Punkt auswählen.").color("red") .center().print() )
      ).and( getScale("sympathie").test.selected()
        .failure( newText('errorscales', "<br>Bitte auf jeder Skala einen Punkt auswählen.").color("red") .center().print() )
      ).and( getScale("erfolg").test.selected()
        .failure( newText('errorscales', "<br>Bitte auf jeder Skala einen Punkt auswählen.").color("red") .center().print() )
      ).and( getScale("entspanntheit").test.selected()
        .failure( newText('errorscales', "<br>Bitte auf jeder Skala einen Punkt auswählen.").color("red") .center().print() )
      ).and( getScale("intelligenz").test.selected()
        .failure( newText('errorscales', "<br>Bitte auf jeder Skala einen Punkt auswählen.").color("red") .center().print() )
      ).and( getScale("vertrautheit").test.selected()
        .failure( newText('errorscales', "<br>Bitte auf jeder Skala einen Punkt auswählen.").color("red") .center().print() )
      ).and( getScale("kompetenz").test.selected()
        .failure( newText('errorscales', "<br>Bitte auf jeder Skala einen Punkt auswählen.").color("red") .center().print() )
      ).and( getScale("humor").test.selected()
        .failure( newText('errorscales', "<br>Bitte auf jeder Skala einen Punkt auswählen.").color("red") .center().print() )
      ).and( getScale("ehrgeiz").test.selected()
        .failure( newText('errorscales', "<br>Bitte auf jeder Skala einen Punkt auswählen.").color("red") .center().print() )
      ).and( getScale("freundlichkeit").test.selected()
        .failure( newText('errorscales', "<br>Bitte auf jeder Skala einen Punkt auswählen.").color("red") .center().print() )
      ).and( getTextInput("herkunft","alter","wohnort","situation").test.text(/^.+/) // testing if at least one digit was written in the input box
        .failure( newText("textfelderror","<br>Bitte jede Frage zur Person im jeweiligen Textfeld beantworten.").color("red").center().print() )
      )

    Jeremy

Viewing 15 posts - 76 through 90 (of 1,522 total)