Forum Replies Created
-
AuthorPosts
-
Jeremy
KeymasterWell, there are the differences I just described, having to do with cumulative delays when setting a final Timer element using the VAR method, vs using an overall 4000ms Timer element which introduces no such delay
Jeremy
Jeremy
KeymasterThe event times reported in the results file correspond to when the relevant events occurred. For the Key element, it stands for when the relevant key was pressed, and for the Canvas element, it stands for when the
print
command was executed. So the difference you calculate corresponds to how many milliseconds passed between when theprint
command of the Canvas element was executed (which should be almost exactly when its content started being visible on the page, barring performance issues) and when the keypress was detected (which should be almost instantaneous, again barring performance issues). That difference includes the time it took for all the commands between the printing of the Canvas element and the keypress to be executed (basically,justnewKey
andlog
, so maybe 1ms) but that shouldn’t matter because all you care about is how long it actually took for the participant to press a key after seeing the Canvas element, regardless of whether the script executed commands during that timeJeremy
Jeremy
KeymasterNo, performance issues are orthogonal to the method you use: if a participant’s browser is having a hard time running the script of the pages they are visiting, the whole execution of the experiment will be affected
The delays, as I said, come from the fact that every single command takes a non-null time to be executed, which means that even two consecutive commands cannot be considered to happen at the same time, so that a sequence
newTimer(100).start().wait(),newTimer(200).start().wait()
cannot be taken to last exactly 300ms with 100% certainty. The advantage of creating an overall Timer element of 4000ms is that you don’t depend on a sequence of commands to calculate its duration, and are therefore not at risk of accumulating such delays in doing soJeremy
Jeremy
KeymasterMy initial suggestion is to set a duration of 4000ms, so the duration between the
start
command of the Timer element and its end (which would coincide with the completion of itswait
command) would be as close to 4000ms as you could get, barring any performance or technical issue with the participant’s browser/deviceJeremy
Jeremy
KeymasterYou would need to use the method that you use with the Var element named localRT to dynamically calculate the duration in another Var element, and set the duration of a final Timer element using that Var element. Be aware, though, that each command takes a non-null time to be executed, so the value of a Var element set the way localRT is set is not guaranteed to be exact to the millisecond:
newVar("localRT").set(v=>Date.now()), newKey("keypress1","SK").wait(), getVar("localRT").set(v=>Date.now()-v),
Both
newKey
andwait
(andset
too, to some extent) take some time to be executed, which means thatDate.now()-v
might represent a time difference that’s slightly greater (by up to a couple milliseconds) than the actual time that passed between the creation of the Key element and a keypress. Also, all the commands that come after thewait
command of the previous Timer element (timer_D3) and beforenewVar
take time to be executed too, so those few milliseconds are not taken into account either if you create the Var element a few commands afterwait
ing for the end of the previous Timer elementMoreover, your actual trials won’t have the
wait
command on the Key element, but rather on the Timer element named timer-RT, so that’s the one after which you would need to set a Var element like localRT. So simplifying the structure a little bit, you would have something like this:timerd1duration = 400+Math.round(1200*Math.random()) , newTimer("timer-D1",timerd1duration).start().wait() , getText("D1").remove() , newText("cue", "*").color("green") , newTimer("timer_cue_D2",100).start().wait() , getText("cue").remove() , newText("D3", "+").color("pink") , newTimer("timer_D3",400).start().wait() , newVar("newTimerDuration").set( v=>Date.now() ) , getText("D3").remove() , newTimer("timer-RT",1700).start() , newImage("imagens", row.imagem).size(500, 200) , newText("cruz_central", "+").color("black") , newCanvas("center", 150,150).add( "center at 50%" , "center at 50%" , getImage("imagens")).print() , newKey("keypress1","SK").log().callback( getTimer("timer-RT").stop() ) , getTimer("timer-RT").wait() , getVar("newTimerDuration").set( v=> 3500 - timerd1duration - (Date.now()-v) ) , newTimer("separacao").set( getVar("newTimerDuration") ).start() , getKey("keypress1").disable(),getText("cruz_central"),getCanvas("center").remove() , newText("+").color("yellow") , getTimer("separacao").wait()
Finally, let me point that any reviewer who would argue that the design is not replicated using the method I suggested earlier either would not understand it, or (unless I myself misunderstood the design as described in the excerpt you quoted) would be completely dishonest in their assessment (keeping in mind that there is a myriad of other technical points of divergence when implementing the same design using different codes and engines)
Jeremy
Jeremy
KeymasterThe solution I give does *not* create a final Timer element with a variable duration. Instead, the solution I suggest (which, in my opinion, is simpler and cleaner, at least when implemented in PCIbex) consists in starting a Timer element at the beginning of the trial of a fixed duration, ie. the intended overall duration of the trial, and wait for it to elapse at the end of the trial before proceeding to the next one
The excerpt you quote includes three timers with a variable duration: D1 (400ms–1600ms) RT (0ms–1700ms) and WAIT (3500ms minus D1 minus RT). Then there are two timers with a fixed duration: D2 (100ms) and D3 (400ms) which makes for a total of 500ms. Because of how WAIT is calculated, adding the duration of the five timers totals to 4000ms
I misunderstood your previous messages as suggesting that you meant to have a total trial duration of max(D1)+max(RT)+D2+D3, which is why I proposed creating a Timer element of 3800ms. But if you want to replicate the design from that quote, set the very first Timer element (the one you need to
wait
for at the end) to4000
insteadJeremy
Jeremy
KeymasterPlace the commands in the
success
and thefeedback
commands, eg. forsuccess
from the code in your message:.success( newVar("positiveFeedback").set( getVar("localRT") ).set( v=> "Correto! You spent "+v+"ms on this trial" ) , newText("success") .text( getVar("positiveFeedback") ) .cssContainer({"font-size":"30px", "margin-top":"255px","font-family":"Comic Sans MS", "color":"green"}) , getVar("accurate").set(v=>[...v,true]) )
Your script already achieves including a trial (labeled “feedback”) that prints the average time spent on the practice trials
Jeremy
Jeremy
KeymasterHi Sabrina,
It looks like there was a problem with imagelist.CSV in the first project: when I click it to edit it, it says “Error opening file chunk_includes/imagelist.CSV: Internal Server Error”
I’m not sure what caused the file corruption, but the problem should be fixed if you delete the file and create/upload it again
Jeremy
Jeremy
KeymasterHi,
I was able to reproduce the problem, and it doesn’t look like it’s coming from a filename mismatch: in my case, the file A10-3.mp3 failed to play, and it looks like my browser never even sent a request to fetch it. PennController keeps only up to 4 pending requests in parallel, to prevent flooding distant servers with requests. It could be that sometimes, 4 audio files fail to fully preload, so PennController never gets to the remaining resources. In my experience, this is more likely to happen with Firefox (and when requests lots of resources) because of the way it loads media streams. I have updated the way that preloading is detected in PennController 2.1beta, so you could try overwriting the copy of PennController.js from your project’s Modules folder with it, and hopefully that should address the issue
Jeremy
Jeremy
KeymasterHi Larissa,
If you want the totality of your trial to be 3800ms (because timer-RT = 1700ms, max timer-D1 = 1600ms, timer_cue_D2 = 100ms and timer_D3 = 400ms) start the timer at the beginning of the trial, and wait for it at the end:
Template("tabela-target.csv" , row => newTrial("trial_2_center_cue_UP", defaultText .center() .cssContainer({"position": "absolute", "top": "50%", "left": "50%", "transform": "translate(-50%, +50%)"}) .print() , newTimer("wait-separacao",3800).start() , newText("D1", "<br><b>+</b>") .cssContainer({"font-size":"100px", "color":"blue"}) .center() .print() , newTimer("timer-D1",400+Math.round(1200*Math.random())) // if you want between 400ms and 1600ms, you need 400+[0--1200] .start() .wait() , getText("D1").remove() , newText("cue", "<br> <b>*</b>") .cssContainer({"font-size":"100px", "color":"green"}) .center() .print() , newTimer("timer_cue_D2",100) .start() .wait() , getText("cue","D2").remove() , newText("D3", "<br> <b>+</b>") .cssContainer({"font-size":"100px", "color":"pink"}) .center() .print() , newTimer("timer_D3",400) .start() .wait() , getText("D3").remove() , newTimer("timer-RT",1700).start() , newImage("imagens", row.imagem).size(500, 200) , newText("cruz_central", "<br> <b>+</b>") .cssContainer({"font-size":"100px", "color":"black"}) .center() .print() , //para cima// newCanvas("center", 150,150) .add( "center at 50%" , "center at 50%" , getImage("imagens")) .cssContainer({"position": "absolute", "margin-top": "85px"}) .center() .log() .print() , newKey("keypress1","SK") .log() .callback( getTimer("timer-RT").stop() ) , getTimer("timer-RT").wait() , getKey("keypress1").disable(), getText("cruz_central"), getCanvas("center").remove() , newText("separacao", "<br> <b>+</b>") .cssContainer({"font-size":"100px", "color":"yellow"}) .center() .print() , getTimer("wait-separacao").wait() ) .log("imagens", row.imagem) .log("item", row.versao) );
Also, re. the feedback on each trial, just use the Var named localRT to set a Text element in the
success
/failure
commands. For example, insuccess
:newVar("positiveFeedbackText").set( getVar("localRT") ).set( v => "Correct! Your speed was: "+v+"ms!" ) , newText("positiveFeedback").text( getVar("positiveFeedbackText") ).print()
Jeremy
Jeremy
KeymasterHi,
The results should be accessible again (for now). The server is having a hard time handling results submissions and requests to access results these past days, so I restart the server periodically to ease the burden on it. Unfortunately the issue is likely to happen again until I revise the code of the farm (which I am actively working on)
Jeremy
Jeremy
KeymasterThe logic for average RT per trial is the same as average accuracy: add
newVar("localRT").set(v=>Date.now())
abovenewKey("keypress1","SK")
and below thewait
command addgetVar("localRT").set(v=>Date.now()-v) , newVar("practiceRTs",[]).global().set(v=>[...v,getVar("localRT").value])
Then your feedback trial would look like this:
newTrial("feedback", newVar("timeText").set( getVar("practiceRTs").global() ).set(v=> "You spent "+(v.reduce((n,m)=>n+m)/v.length)/1000+"s per trial on average" ), newText( "feedbackTime" ).text( getVar("timeText") ).print() , newVar("accuracyText").set( getVar("accurate").global() ).set(v=> "You got "+v.filter(a=>a==true).length+" correct answers out of "+v.length ), newText( "feedbackAccuracy" ).text( getVar("accuracyText") ).print() , newButton("Continue").print().wait() )
Jeremy
Jeremy
KeymasterThanks, I’ve been unable to replicate the issue so far, so I can’t be sure that PennController 2.1beta would fix it. However, the way PennController 2.0 downloads and decompresses zip archives occasionally yields a corrupt output, so if the crash is related to that, chances are that 2.1beta would take care of it
Jeremy
Jeremy
Keymasterwould it be possible to show them how much time they’ve spent on the practice trial as well as their accuracy? If so, how?
EDIT: I just saw your new message, which asks about average response time per practice trial rather than time spent on the whole practice block. I’ll address that later
You could add this at the end of your keyboard_warning trial, after
getText("keyboard-text").remove()
:newVar("startPractice").global().set(()=>Date.now())
Then before the test on your Key element in the code of your practice trials, add
newVar("accurate", []).global()
and inside thesuccess
andfailure
commands, addgetVar("accurate").set(v=>[...v,true])
/getVar("accurate").set(v=>[...v,false])
(respectively)You can add a trial before instructions_E that would do something like:
newTrial("feedback", newVar("timeText").set( getVar("startPractice").global() ).set(v=> "You spent "+Math.round((Date.now()-v)/600)/100+"min practicing" ), newText( "feedbackTime" ).text( getVar("timeText") ).print() , newVar("accuracyText").set( getVar("accurate").global() ).set(v=> "You got "+v.filter(a=>a==true).length+" correct answers out of "+v.length ), newText( "feedbackAccuracy" ).text( getVar("accuracyText") ).print() , newButton("Continue").print().wait() )
Jeremy
Jeremy
KeymasterMy bad, it’s a typo from a previous version of my code, it should be
timer-D1
—I have edited my message accordinglyJeremy
-
AuthorPosts