Forum Replies Created
-
AuthorPosts
-
Jeremy
KeymasterHi,
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
print
ed the Text element with your question immediately afterwait
ing 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 you1651092196900-1651092191988=4912
ms, ie. about 5sJeremy
June 23, 2023 at 9:29 am in reply to: Question about assigning conditions, groups + randomization #10705Jeremy
KeymasterHi 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 returnundefined
. As a result, every single trial generated by it is labeled"experiment_undefined"
and therefore thewhile
loop does find three successive trials innew_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 workJeremy
Jeremy
KeymasterHi 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 yourwait
command only comes later). As a matter of fact, there is notest
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 thedefaultScale
commands fromHeader
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
June 23, 2023 at 7:20 am in reply to: Question about assigning conditions, groups + randomization #10702Jeremy
KeymasterHi,
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 laterTemplate
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
Jeremy
KeymasterHi 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
Jeremy
KeymasterHi,
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
Jeremy
KeymasterHello 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
Jeremy
KeymasterDear 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
Jeremy
KeymasterHi,
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
Jeremy
KeymasterHi,
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 scaleAlso 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
Jeremy
KeymasterHi,
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=
characterJeremy
Jeremy
KeymasterHello,
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
June 9, 2023 at 4:37 am in reply to: Trouble with Timer – not stopping and not letting me press a key #10674Jeremy
KeymasterHello 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 thecallback
line, and on to the next lines until it reaches the.wait()
line on the Timer element, and stays there until the timer endsSo 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’swait
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 trialIf I understand you correctly, you simply want to take out the
.wait()
line on the Key elementJeremy
Jeremy
KeymasterHello,
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 likelet fileName = PennEngine.utils.guidGenerator()+'-'+PennEngine.controllers.running.id+'.zip';
to overwrite the MediaRecorder elementJeremy
Jeremy
KeymasterHi,
You need to close your
and
s so you can attachfailure
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
-
AuthorPosts