Forum Replies Created
-
AuthorPosts
-
Jeremy
KeymasterHi,
I’m pretty sure you would only get recording per MediaRecorder, only the most recent one
Your questions make perfect sense to me! But, unfortunately, PennController’s reasoning is much more blunt, it doesn’t try to understand the reasoning behind the code
Jeremy
Jeremy
KeymasterHi,
Why don’t you use
.print()
on the MediaRecorder element and let participants use the interface to freely record and play back? It would save you a lot of codingRe. looping: you are already using
callback
on the Button element “Play” so that’s a command you could use (on another button) to implement a loop-like behavior too. Actually you don’t need to, and probably shouldn’t, usecallback
on the “Play” button itself, because that’s what makes the script go on without waiting for playback to finishRe.
test.clicked
: you place it immediately after creating the Button element, so of course the participant won’t have time to click the button in the 0-1ms it takes the script to move from executing thenewButton
line to executing the.test.clicked
line, and so the test will always failPutting this all together, here’s the simple way to implement your test:
newTrial( "audio_check" , defaultText.css({"text-align":"center", "margin-bottom":"1em"}).center().print() , newText("instr-1", "Before starting...") , newMediaRecorder("audiotest1", "audio") .log() .print() .wait() , newText("testagain", "If it seems to you that the audio is not clear and/or there is too much background noise...") , newText("next", "If the audio seems clear and of good quality, press CONTINUE") , newButton("Continue").center().print().wait() )
If you really want to implement a custom interface, you could do something like this:
newTrial( "audio_check" , defaultText.css({"text-align":"center", "margin-bottom":"1em"}).center().print() , defaultButton.center() , newText("instr-1", "Before starting...") , newButton("trigger", "Do a test recording") .callback( clear() , newMediaRecorder("audiotest1", "audio").log().record() , newButton("Stop").print().wait().remove() , getMediaRecorder("audiotest1").stop() , newButton("Play").print().wait().remove() , getMediaRecorder("audiotest1").play().wait("playback") , newText("If it seems to you that the audio is not clear and/or there is too much background noise...") , newText("If the audio seems clear and of good quality, press CONTINUE") , getButton("trigger").print() // Clicking this will execute the callback sequence again , getButton("Continue").print() // Print the button now: clicking it will end the trial (=validate the wait command) ) .print() , // This button will only be printed at the end of a test, // So the script won't have a chance to move past the wait command until then newButton("Continue").css("margin-top","1em").wait() )
Jeremy
Jeremy
KeymasterHi Shamim,
Did you make sure the domain where you run your experiment matches the one you reference in the line
header("Access-Control-Allow-Origin: https://my.domain.xyz");
? It should behttps://farm.pcibex.net
if you are running your experiment on the farm (like here)Feel free to share a link to your experiment with me, either here or at support@pcibex.net, so I can help you troubleshoot the issue
Jeremy
Jeremy
KeymasterHi,
You have different ways of doing it, but what I would do would be to test the different words in separate trials, which I would generate using
Template
(I would list the words in a CSV table). Then I would use a global Var element to keep track of performance and, at the end of the series of trials, I would go back to the beginning of the series if they did not get everything right. Here is what it would look like:Sequence("pre-verb", "verb", "post-verb") newTrial("pre-verb", newVar("verb-performance").global().set(true) ) //Learning the verbs Template("verbs.csv", row=> newTrial("verb", newText("question", "<p>What does '"+row.word+"' mean?</p>") .center() .print() , newText("guess1", row.guess1) .print() , newText("guess2", row.guess2) .print() , newSelector("choice") .add(getText("guess1") , getText("guess2")) .shuffle() .frame("solid 2px purple") .wait() .log() , newButton("check_response", "Continue") .center() .print() .wait( getSelector("choice") .test.selected( getText(row.correct) ) .failure( getVar("verb-performance").set(false) , newText("Sorry, this is not the correct answer. Please select again.").print() ) ) ) ) newTrial("post-verb", getVar("verb-performance").global().test.is(true).failure( jump("pre-verb") ) )
With verbs.csv:
word,guess1,guess2,correct dax,Lift,Turn,guess1 zep,Lift,Turn,guess2
Jeremy
Jeremy
KeymasterHi Noelia,
Have you tried simply passing
quote=""
to the two occurrences ofread.csv
in the body ofread.pcibex
?Feel free to send me the results file at support@pcibex.net if that is OK with your privacy policy (I would delete the file as soon as we solve the issue)
Jeremy
Jeremy
KeymasterHi,
Either increase
min-width
, or add horizontal margins to theli
elements, egmargin: 0em 1em;
Jeremy
Jeremy
KeymasterHi Mieke,
As reported in the Errors tab of the debugger, you are creating multiple Image elements with the same name, which causes problems when you refer back to them later (eg.
getImage("Topleft_image").visible()
). See explanations hereHere is a possible solution, let me know if it works as you expect:
FillerDisplays = ["A", "A", "A", "A", "A", "A", "B", "B", "B", "B", "B", "B", "C", "C", "C", "C", "C", "C", "D", "D", "D", "D", "D", "D"]; TargetDisplays = ["A", "A", "A", "B", "B", "B", "C", "C", "C", "D", "D", "D"]; fisherYates(FillerDisplays) fisherYates(TargetDisplays) DisplayMap = { A: {TopLeft: "CorAgent", TopRight: "FoilAgent", BottomRight: "CorTheme", BottomLeft: "FoilTheme"}, B: {TopLeft: "FoilAgent", TopRight: "CorAgent", BottomRight: "CorTheme", BottomLeft: "FoilTheme"}, C: {TopLeft: "CorAgent", TopRight: "FoilAgent", BottomRight: "FoilTheme", BottomLeft: "CorTheme"}, D: {TopLeft: "FoilAgent", TopRight: "CorAgent", BottomRight: "FoilTheme", BottomLeft: "CorTheme"} } Template("trials.csv", row => newTrial("Trials", defaultImage.hidden() , newVar("dropped", 0) , newFunction( ()=> $("body").css({width: '100vw',height: '100vh'}) ).call() , getVar("nTrials") .log() .test.is(18) .success( newHtml("HalfWay", "Halfway.html") .center() .cssContainer({"width":"720px"}) .print("center at 50%", "middle at 50%") , newButton("continue", "Continue to the next trial") .center() .print("center at 50%", "middle at 80%") .wait() , fullscreen() , clear() ) , newFunction( ()=> $("body").css('cursor','none') ).call() , newImage("Al", "Al.png") .size("15vw", "20vh") .print("center at 50%", "middle at 50%") , newImage("Speaker", "speaker_green.png") .size("7vw", "7vw") .print("center at 50%", "middle at 50%") , newAudio("TestSentence", row.TestSentence) , displayGroup = (row.StimulusType=="Target"?TargetDisplays.pop():FillerDisplays.pop()) , defaultImage.log().size("30vw","30vh") , newImage("Topleft_image", row[DisplayMap[displayGroup].TopLeft]).print("center at 25%", "middle at 25%") , newImage("Topright_image", row[DisplayMap[displayGroup].TopRight]).print("center at 75%", "middle at 25%") , newImage("Bottomright_image", row[DisplayMap[displayGroup].BottomRight]).print("center at 75%", "middle at 75%") , newImage("Bottomleft_image", row[DisplayMap[displayGroup].BottomLeft]).print("center at 25%", "middle at 75%") , newAudio("Topleft_audio", row[DisplayMap[displayGroup].TopLeft+"_sound"]) , newAudio("Topright_audio", row[DisplayMap[displayGroup].TopRight+"_sound"]) , newAudio("Bottomright_audio", row[DisplayMap[displayGroup].BottomRight+"_sound"]) , newAudio("Bottomleft_audio", row[DisplayMap[displayGroup].BottomLeft+"_sound"]) , newText("Display_"+displayGroup).print() , getImage("Al").visible() , newAudio(IntroSentence[Math.floor(Math.random() * 3)]).play().log().wait() , getImage("Topleft_image").visible() , getAudio("Topleft_audio").play().log().wait() , newTimer(300).start().wait() , getImage("Topright_image").visible() , getAudio("Topright_audio").play().log().wait() , newTimer(300).start().wait() , getImage("Bottomright_image").visible() , getAudio("Bottomright_audio").play().log().wait() , newTimer(300).start().wait() , getImage("Bottomleft_image").visible() , newAudio(And[Math.floor(Math.random() * 12)]).play().log().wait() , getAudio("Bottomleft_audio").play().log().wait() , newTimer(500).start().wait() , getImage("Al").hidden() , newTimer(200).start().wait() , getImage("Speaker").visible().log() , getAudio("TestSentence").play().log().wait() , newTimer(200).start().wait() , getImage("Speaker").hidden().log() , getImage("Al").visible().log() , newKey(" ").wait() , clear() , getVar("nTrials").set( v => v+1) // increase nTrials ) .log("Quantifier", row.Quantifier) .log("Version", row.Version) .log("Item", row.Item) .log("StimulusType", row.StimulusType) .log("Group", row.Group) .log("Sentence", row.Sentence) .log("Verb", row.Verb) .log("CorAgent", row.CorAgent) .log("FoilAgent",row.FoilAgent) .log("CorTheme",row.CorTheme) .log("FoilTheme",row.FoilTheme) .log("TestSentence",row.TestSentence) .log("CorAgent_sound",row.CorAgent_sound) .log("FoilAgent_sound",row.FoilAgent_sound) .log("CorTheme_sound",row.CorTheme_sound) .log("FoilTheme_sound",row.FoilTheme_sound) .log("DisplayGroup", displayGroup) )
Jeremy
Jeremy
KeymasterHi Jack,
When the content of your HTML file is injected in the experiment, its first
div
element is not the firstdiv
in the document, unlike when you open the file separately in your browserYou could instead mark the elements whose lines you want to number (in particular, the
div
element you target) by giving it a specific class (eg.numberTheLines
):<script type="text/javascript"> window.addEventListener("load", ()=>{ var toNumber = document.querySelectorAll('.numberTheLines'); console.log("toNumber", toNumber); toNumber.forEach( el => { var lines = el.innerText.split('\n'); console.log("lines", lines); el.innerText = ''; for (var l = 0; l < lines.length; l++) { var line = lines[l]; if (line.length == 0) { continue; } var span = document.createElement('span'); next = (l < lines.length - 1) ? lines[l+1] : ''; if (next.length > 0) { span.className = 'line'; } else { span.className = 'line end-of-paragraph'; } span.innerText = line; el.appendChild(span); el.appendChild(document.createTextNode('\n')); } }); }) </script>
Jeremy
Jeremy
KeymasterHi Erin,
Judging from the filenames of the files you were able to upload, I suspect that the problem with the ones that fail to upload has to do with the length of their filenames: the farm has a limit of 50 characters on filenames. Make sure all your files are 50 characters of less (including the four characters
.mp3
) and try uploading them againJeremy
Jeremy
KeymasterHi Danil,
The
li
elements don’t get thescale-box
class when you do not setpresentAsScale
totrue
in the Question controller (and in my experience, setting it totrue
prevents keypresses on 1/2/3)However, they get the
normal-answer
class, so you could either add the rulefont-size: 18pt;
toli.normal-answer {
in Question.css, or you could add it to.PennController-myController.Question-Question li {
in global_z.cssJeremy
Jeremy
KeymasterHi Tian,
What version of PennController are you using? Would you mind sharing a link to your experiment with me, either here or at support@pcibex.net? Thanks
Jeremy
Jeremy
KeymasterHello,
Thank you for bringing this to our attention, and apologies for the inconvenience. Our servers were targeted by a DDoS attack over the weekend, ie. we were flooded by multiple concurrent requests, which led the servers to crash. I blocked the IP addresses responsible for the attacks and proceeded to some cleanup, things should be back to normal now. No data was compromised by the attack (ie. your login credentials and existing results are safe) but if participants tried to take your experiment between April 15 13:00GMT and April 17 16:50GMT, chances are they either couldn’t open the page or couldn’t submit their responses
I understand that such downtime episodes compromise the process of collecting data, and even though we do our best to prevent them, some aspects of a big scale open service like the PCIbex Farm are harder to anticipate. Both PennController and IBEX are under open licenses and free to use. In particular, anyone can host experiments on their own webserver by following the instructions on the IBEX manual. Adding PennController is as simple as adding PennController.js to the
js_includes
folder of an experiment (note: if setting up your server, multimedia resources should be placed in thewww
folder, not inchunk_includes
—csv files should still live inchunk_includes
)We will let you know in advance by sending an announcement email when we proceed to maintenance operations
Thank you for you understanding,
JeremyJeremy
KeymasterHi Ecenur,
Nice finding! Then you can add this to the top of main.js to prevent Google Translate from translating the page:
const dontTranslate = ()=>(document.body!==null&&document.body.setAttribute("translate","no"))||window.requestAnimationFrame(dontTranslate); dontTranslate();
The errors you encountered are not related to the changes you made but to an attack on our servers over the weekend and to the crashes it led to. Things should be back to normal now, let me know if you still experience issues logging in and/or editing your projects
Jeremy
Jeremy
KeymasterI tested the code, and you can try it out too, and I was only able to make a selection using the keys 1, 2 and 3. The CSS rule
pointer-events: none;
on theli
elements disables mouse clicksJeremy
Jeremy
KeymasterHi,
First let me remind you that javascript is executed on the client’s side, so all the code is visible by the participant simply via clicking “View source” on the page. I would recommend using a one-way encryption method to list hashes:
const password_hashes = [ 'b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9', // hello world '4d371276d3db24d63534d530c302487492adcc1bf221387edb6836dbbf341524' // bye earth ] const digest = async message => Array.prototype.map .call( new Uint8Array( await crypto.subtle.digest("SHA-256", new TextEncoder().encode(message)) ), (x) => ("0" + x.toString(16)).slice(-2) ) .join(""); newTrial( "instruction", newText("<p>Welcome! Please fill your password into the box below.</p>").print() , newTextInput("inputID", "").center().css("margin", "1em").print() , newButton("Start my trials") .center() .print() .wait() , newFunction( "set", async function() { this.hash = await digest(document.querySelector(".PennController-inputID").value); }).call() , clear() , newFunction( "test", function () { return password_hashes.find(v=>v==this.hash); }) .testNot.is( undefined ) .failure( newText("ID not listed").print() , newButton("dummy").wait() ) , newVar("ID").global().set( getTextInput("inputID") ) )
Here I have pre-generated two hashes: one for
hello world
and one forbye earth
. I have added comments but of course keeping them when actually running the experiment would defeat the whole purpose of hashing the inputYou can generate the hashes to list yourself by running your experiment and, once you’re on the tab of your running experiment, open your browser’s web console and type:
digest("type the id here").then(console.log)
Replace
type the id here
with an ID, and then you’ll get the hash output in the console, which you can then report in thepassword_hashes
arrayJeremy
-
AuthorPosts