Forum Replies Created
-
AuthorPosts
-
Jeremy
KeymasterHi,
This is a problem that has to do with how I coded the Controller element. Add this to your script, and replace
.print().wait().remove()
with simply.run()
on your Controller element:PennController._AddStandardCommands( function(PennEngine) { this.actions = { run: async function(resolve){ if (this.type != "Controller" || this.controller != "DashedSentence") return resolve(); this.done = false; await new Promise(r=>PennEngine.elements.standardCommands.actions.print.call(this,r)); const oldCallback = this.finishedCallback; await new Promise(r=>this.finishedCallback=function(){ oldCallback.call(this); this.jQueryContainer.detach(); r(); }); resolve(); } } })
You also want to change your trial’s code slightly:
newTrial("practice-trial", newButton("launch-practice-trial", "Redo") .callback( ...PracticeTrial() , getButton("proceed").print() , getButton("launch-practice-trial").print() ) .center() .click() , newButton("proceed", "Proceed") .center() .wait() )
Here’s a demo link: https://farm.pcibex.net/r/AjyfGD/
Let me know if you have any questions
Jeremy
March 9, 2021 at 7:22 pm in reply to: Ensure the user clicks on a given set of words before continuing #6705Jeremy
KeymasterHi,
Here’s a live demo: https://farm.pcibex.net/r/EMYHvp/
And here’s the code (you can edit it directly on the farm too):
newTargetButton = (name,text) => newButton(name, (name||text)) .callback( getButton(name) .disable() .css("color","red") , getVar("targetsLeft") .set(v=>v-1) .test.is(0) .success( getButton("Next").click() ) , getText("counter") .text( getVar("targetsLeft") ) ) .selector("buttons") newFilerButton = (name,text) => newButton(name, (name||text)) .callback( getButton(name).disable() ) .selector("buttons") newTrial( newVar("targetsLeft", 3) , newText("counter", '3') .before( newText("# targets left: ") ) .print() , newCanvas("container", 800,100) , newSelector("buttons").disableClicks() , newTargetButton("target1").print( 0,0,getCanvas("container")), newTargetButton("target2").print(200, 0,getCanvas("container")), newTargetButton("target3").print(400, 0,getCanvas("container")) , newFilerButton("filler1").print(600, 0,getCanvas("container")), newFilerButton("filler2").print( 0,40,getCanvas("container")), newFilerButton("filler3").print(200,40,getCanvas("container")), newFilerButton("filler4").print(400,40,getCanvas("container")), newFilerButton("filler5").print(600,40,getCanvas("container")), newFilerButton("filler6").print( 0,80,getCanvas("container")), newFilerButton("filler7").print(200,80,getCanvas("container")) , getCanvas("container").print() , getSelector("buttons").shuffle() , newButton("Next").wait() )
Let me know if you have any questions
Jeremy
Jeremy
KeymasterDear Aliona,
Each submission in the results file starts with these lines:
# Results on Friday February 26 2021 14:31:11 UTC # USER AGENT: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:85.0) Gecko/20100101 Firefox/85.0
As you can see, in this case, the submission came from a participant (me) who used Firefox. This is not 100% reliable, but there is no method that is 100% reliable when it comes to determining the browser’s identity
Jeremy
Jeremy
KeymasterThank you—so, the first event that gets logged in your trial (after its start) is indeed the keypress of the space bar that reveals the second word (ie. the keypress that “validates” the first word): if you subtract all the reading times from the bolded timestamp, you’ll get the timestamp corresponding to when the first word was revealed. Because the very first event that occurs in your trial is printing the all-blank sentence, you can subtract the trial’s start timestamp (which approximates when the all-blank sentence was printed to the page) from the first-word timestamp you just calculated, and you’ll get how long it took to your participant to reveal the first word by pressing the space bar.
The last event in your trial is a keypress on F or J, which you log: if you subtract the bolded timestamp (which corresponds to the completion of the Controller element) from the keypress’s timestamp, you’ll get how it took for your participant to give an answer to the question
Let me know if you have questions
Jeremy
Jeremy
KeymasterHi,
Regarding your first question, it is a known bug: pressing the spacebar again during the same trial after the DashedSentence controller has completed will duplicate the logged lines. I am working on fixing this for the next release of PennController. In the meantime, the easiest way to avoid this is to move to the next trial immediately after the DashedSentence controller, but I realize this is not always possible.
Regarding you second question: the bolded number is the timestamp corresponding to when the DashedSentence controller was completed. If you subtract all the reading times from it, you’ll get the timestamp corresponding to the when the first word was displayed (not to when the all-blank sentence itself was displayed). The delay between the display of the the all-blank sentence and when the first word was revealed might account for the mismatch you report, but I would need to see your code to tell for sure (you can use the demonstration link from your project’s Share action for that)
Jeremy
Jeremy
KeymasterHi,
Remove these three lines from the definition of
read.pcibex
:if (index < length(cols)){ cols <- c() }
Jeremy
Jeremy
KeymasterHi Rick,
As far as I can tell, you don’t need a Var element to do what you’re after:
subjID = String.fromCharCode(65+Math.floor(Math.random() * 26)) + Math.floor((Math.random() * 10000) + 1) Sequence ("experiment","bye") Header( // ... ) .log("ParticipantID", subjID) // ... experiment newTrial("bye", defaultText .center() .print() , newText("<p>Thank you for doing this study!!!</p>") , newText("Your participant id number is:") , newText(subjID) , newButton("void") .wait() ) .setOption("countsForProgressBar" , false)
What you report is a known bug, I’ll work on fixing it for the next release (or the one after that)
Jeremy
March 2, 2021 at 12:25 am in reply to: Automatically start recording at end of trial and have a submit button #6681Jeremy
KeymasterI’m not sure why, but I realize that the
once
command there is superfluous, so maybe try removing it to see if that fixes the problem. Also try adding a 200ms Timer element (don’t forget tostart
andwait
for it) before the closing parenthesis of thenewTrial
so as to give time to your trial to process the stopping of the MediaRecorder elementMarch 1, 2021 at 1:30 pm in reply to: Automatically start recording at end of trial and have a submit button #6679Jeremy
KeymasterHi,
The code you posted will not show any button, because there is no print command in the script after clear has been executed. Why don’t you remove the last wait command (on the MediaRecorder element) and create a “Save recording” button?
newMediaRecorder("recorder", "audio") .log() .once() .record() // .wait() , newButton("Save recording") .callback(getMediaRecorder("recorder").stop()) .print() .wait()
Jeremy
Jeremy
KeymasterHi,
Feel free to share the demonstration link of your experiment with me, here or at support@pcibex.net, so I can take a look
If you say that it was working on the old farm (that is, even using Chrome/Opera/Edge) then maybe what changed is that your project on the new farm uses a newer version of the PennController library. Maybe try downgrading to PennController 1.8 (remember to pause data collection when editing your project): https://github.com/PennController/penncontroller/tree/master/releases/1.8
(download the js file and upload it to the Modules folder of your project)Jeremy
Jeremy
KeymasterGlad it ended up working! I don’t know what might have caused an issue with PennController 1.8, but it’s good to know that PennController 1.9 seems to have fixed it (whatever “it” is)
Jeremy
Jeremy
KeymasterHi,
Your code works well for me. (Note that I replaced s: with q: in the Question controller, but that wouldn’t make the experiment crash anyway)
If you are using a Sequence command, did you make sure to include the trials labeled "trials" in it?
Jeremy
Jeremy
KeymasterHi Danil
2.
I know the recordings of the trial before the last trial and the last trial are uploaded because I can see the lines
1613560743,856aafdd008ea714bef357058d6b9c1c,PennController,1,0,sendAsync,NULL,PennController,UploadRecordings,_Trial_,Start,1613560695092,NULL 1613560743,856aafdd008ea714bef357058d6b9c1c,PennController,1,0,sendAsync,NULL,PennController,UploadRecordings,_Trial_,End,1613560695095,NULL
These lines simply report that the UploadRecordings trial was run, they do not report that any file was uploaded. The lines that report successful uploads are the ones you mention otherwise, which include the filename of the zip file that was uploaded, like this one:
1613560743,856aafdd008ea714bef357058d6b9c1c,PennController,73,0,experiment,NULL,PennController,UploadRecordings,Filename,2b02bf46-7ba9-6d0a-4f64-ebf0ad7c8394.zip,1613560695207,filler2,APN,,карамфил,От градината бяха откъснати засадените от девойката,карамфил,A,STE-044_mono.wav,filler2_APN_карамфил__A,async
It does not seem to be a problem of coding since the final uploading (which is asynchronous (the line used in the main file is indeed UploadRecordings(“sendAsync”, “noblock”)) and the next trial begins before the last upload has completed, which in turn explains why each .zip file points to the preceding trial) seems to happen after the execution of the final trial (as the second “sendAsync”) suggests.
Precisely, this is why I suggested you make (at least the last of) your UploadRecordings trial synchronous, so that the final screen is only reached after upload has completed
Contrary to your previous suggestion I cannot input this information with my materials during the experiment, since I do not have a direct control of my participants.
Are you referring to this message? I’m not sure how it is relevant to the current point, which is about checking whether the (algorithmatically uniquely-named) zip files have been uploaded
Contrary again to your previous suggestion, I wouldn’t want to reset the value of this counter file, since this file contains important information about the number of participants.
Are you referring to my suggestion to use the SetCounter command to increase the value of the counter early in the experiment? Using that command would not reset the counter, it would simply change the default behavior of automatically increasing the counter at the end of experiment, and would increase it wherever you run the SetCounter trial instead (for example, at the very beginning of the experiment, if you make that SetCoutner trial the first one to run). If you want to test your experiment in a specific group without editing anything in your project, use the withsquare method described in the tutorial.
Regarding adding participant-identifying info to the filenames of your audio recordings (ie not the uploaded zip files themselves, but the files they contain): for the reasons you describe, two or more participants could run the experiment with the same counter value, but generating another unique id, in addition to the MD5 hash that’s reported at the beginning of every line, and adding it at the end of every line is not terribly difficult. Just add this to your script:
var id = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c=>{ const r = Math.random() * 16 | 0,v = c == 'x' ? r : (r & 0x3 | 0x8); return v.toString(16)]; }) Header( // void ) .log("uniqueID", id)
And then add +id to your MediaRecorder elements’ names, for example
newMediaRecorder("test1_recording"+id, "audio")
/getMediaRecorder("test1_recording"+id)
3. The first thing you should do, assuming you are hosting your experiment on a secure domain (which is the case on the PCIbex Farm) is replace your line
AddHost("http://sabotin.ung.si/~dk0035/")
with
AddHost("https://sabotin.ung.si/~dk0035/")
In any case, your experiment seems to use many files, each between 250-800KB: I wouldn’t be surprised if the servers where you host your files just stopped serving some files after too many requests in a short time window. Which makes me reiterate my recommendation to consolidate them into one or a few zip files. You can easily generate unique filenames by prefixing the name of their current containing folder, eg: Fillers2_STE-001_mono.wav
Regarding some of your participants taking your experiment again: this is something you should avoid, so I think you should rather spend time and efforts reducing the likelihood that they would have to take your experiment again. I don’t know how you will be recruiting your participants and what resources you have access to, but my policy with online experiments has been to pay each participant only once, so they had no incentive to take the experiment again after completing it once, and to pay them even if they couldn’t finish the experiment as long as they can prove that they tried (usually by describing the content of the experiment and/or sending a screenshot). In my opinion, the counter-based conditional system that you describe seems overly complicated, and ultimately not necessary once the initial problem has been addressed.
Feel free to send me a link to your experiment here or by email at support@pcibex.net
Jeremy
Jeremy
KeymasterGood morning,
There sure are alternative/additional ways, but subtracting the two timestamps is probably the most accurate measure you can get: each timestamp reports when the event happened, to the millisecond (modulo browser lag). In your case, you know that the participant pressed a key almost exactly one second (1072ms) after the main Canvas element was displayed. Subtracting the timestamps is a simple operation in R (line 7).
If you really need to report the RT at the end of every row, you can use a global Var element that you set to Date.now() when printing the Canvas and whose value you subtract to Date.now() after selection happened, and you can log it on your newTrial:
Template("fullstim.csv", row => ["fullstim", "DashedSentence", {s: row.Sentence}, "PennController", newTrial("experiment", defaultImage .size(500,350) , newImage("male", row.MaleImageFile) , newImage("female", row.FemaleImageFile) , newCanvas(1000,500) .add( -50 , 0 , newCanvas("left" , 500, 500) ) .add( 475 , 0 , newCanvas("right", 500, 500) ) .print() .log() , newVar("RT").global().set( v=>Date.now() ) , newCanvas(50,50) .add(180,-100, newText("(A)")) .add(700,-100, newText("(B)")) .print() , getCanvas("left").add( 0 , 0 , getImage(row.LeftPicture)) , getCanvas("right").add( 0, 0 , getImage(row.RightPicture)) , newSelector() .disableClicks() .add( getImage(row.LeftPicture) , getImage(row.RightPicture) ) .keys( "A" , "B" ) .log() .wait() .test.selected( getImage(row.Target) ) .success( newText("Correct! Remember, <i> faire </i> plus the infinitive shows that the object performs the action. Otherwise, the subject performs the action.").css("font-size","1.5em").print() ) .failure( newText("Incorrect! Remember, <i> faire </i> plus the infinitive shows that the object performs the action. Otherwise, the subject performs the action.").css("font-size", "1.5em").print() ) , getVar("RT").set( v=>Date.now()-v ) , newButton("Next").print().wait() ) .log('Item', row.Sentence) .log('Group', row.Group) .log('RT', getVar("RT")) ])
Jeremy
Jeremy
Keymaster -
AuthorPosts