Forum Replies Created
-
AuthorPosts
-
JeremyKeymasterHi,
The error you report in your second message seems inconsistent with your receiving any mp3 files at all in your uploads folder. If the Ajax post fails, then it never reaches your server, so however you got that error to show up, it’s probably not why you got blank mp3 files.
What browser are you using? Safari (especially the iOS version, but some desktop versions too) is known to have bad to no support of audio capture.
When you ask how you can combine the regular ibex trials with the PennController trials, what do you mean exactly? Judging from your code, it looks like it’s already what you are doing.
NB: you should use .log() before .record() to make it easier to pair the files your receive on your server with the lines in your results file.
NB bis: I edited the code in your first message to replace the URL to your php script—attacks are unlikely but it’s always safer not to expose a public way of uploading content to your servers
Jeremy
JeremyKeymasterSome errors are not fatal: as long as you know what triggers them (eg. the intentional absence of a Group column in the filler table) but you are still able to run the experiment and collect the data you need, it’s ok to ignore them (your participants won’t see them once you’re ready for data collection and use DebugOff()). Just make sure you don’t overlook other errors when skipping them over.
Jeremy
JeremyKeymasterHello,
You are passing three arguments to the trial log command, when it should take only two: the first argument is the name of the column to be added, the second argument is the value of that column on each trial row in the results file.
If you simply get the participant’s id by looking up the URL (as opposed to, say, asking them to type it in a field) then you don’t need to use a Var element, just use:
.log( "ParticipantID" , GetURLParameter("id") )Jeremy
JeremyKeymasterHi,
The solution should indeed be the same—as you saw on the other topic, the table used for the filler items does not have a group column and yet it just works. You just want to make sure you use two Template commands to use the two different tables, as illustrated on the other topic.
Jeremy
JeremyKeymasterHi Angelica,
You can always use regular javascript alongside PennController commands. So you can do this:
customTrial = label => variable => newTrial( label , defaultText .center() , // Display target word newText("listen_for", "Listen for:") .css({"width": "300px"}) .italic() , newText("target_word", variable.target) .css({"font-size": "200%", "width": "300px"}) , newText("next", "Press the spacebar to continue.") .css({"width": "300px"}) .italic() , newCanvas(300, 300) .add(0, 100, getText("listen_for")) .add(0, 150, getText("target_word")) .add(0, 250, getText("next")) .print() , newKey(" ") .wait() , clear() , // Play audio stimulus and record reaction time newImage("fixation_cross", "fixation_cross.png") .size(300, 300) , newCanvas(300, 300) .add(0, 0, getImage("fixation_cross")) .print() , newKey("reaction_time", " ") .log("all") , newAudio("stimulus", variable.stimulus) .play() .wait() , clear() , // Comprehension question (variable.question?[newText("question", variable.question) .cssContainer({"width": "600px", "font-size": "150%", "height": "50px"}) , newText("answer_1", variable.answer_1) .cssContainer({"width": "300px"}) , newText("answer_2", variable.answer_2) .cssContainer({"width": "300px"}) , newCanvas(600, 300) .add(0, 150, getText("question")) .add(0, 200, getText("answer_1")) .add(300, 200, getText("answer_2")) .print() , newKey("FJ") .wait() .log() ]:null) ) .log("group", variable.group) .log("item", variable.item) .log("condition", variable.cond2) .log("target", variable.target) .log("correct", variable.correct)Then you can use it like this:
// experimental item trial Template("test_items", customTrial("items") ) // Filler item trial Template("test_fillers", customTrial("fillers") )Jeremy
JeremyKeymasterGreat!
If anyone else is having the same issue, one solution is to inject some HTML in your sentence, for example: Please identify the last <span style='color: red; font-weight: bold;'>sound<span>.
Jeremy
JeremyKeymasterHello,
Yes, you can use .test.text to check the content of a TextInput element, and doing so in the wait command will only validate the click if the test is successful. In your case, you can use regular expressions to check for appropriate numbers, just replace your very last .wait() line with this:
.wait( getTextInput("age").test.text( /^(\d|1[0-8])$/ ) .failure( newText("Age should be a number between 0 and 18").print() ) .and( getTextInput("weight").test.text( /^\d+$/ ) .failure( newText("Weight should be a numeric value").print() ) ).and( getTextInput("height").test.text( /^\d+$/ ) .failure( newText("Height should be a numeric value").print() ) ) )Jeremy
JeremyKeymasterHi Mieke,
You could test __counter_value_from_server__ in your Template command and assign labels accordingly: Template( row => newTrial( __counter_value_from_server__%2?"secondblock":"firstblock" , /* ... */ ) )
Note: __counter_value_from_server__ is only defined after your script has been evaluated, so you cannot use it outside Template (whose execution is delayed to fetch the tables)
Jeremy
JeremyKeymasterHi Mieke,
Yes, you can definitely accomplish what you want with PennController, but I think you’d need to take a slightly different approach. Let me give you an example, starting with a table of this format (of course you can use a CSV file directly instead of using AddTable):
AddTable("myTable", `Item,Sentence,Group 0,tree lost its leaves,possessive 0,tree fell on itself,reflexive 1,pond had its water drained,possessive 1,pond replenished itself,reflexive 2,cloud kept its shape,possessive 2,cloud stood by itself,reflexive 3,rock had its top smoothed,possessive 3,rock spun on itself,reflexive 4,hill hid a cave at its top,possessive 4,hill hid on itself,reflexive`)Here I have 5 trials, numbered from 0 to 4, which appear in one of two conditions (possessive vs reflexive) every other time the experiment is run, because of the Group column. Nothing crucial relies on this, it’s just for the sake of illustration.
Now I’ll define some variables in the script to configure the design you describe:
firstBlockLength = 2 secondBlockLength = 3 primes = "EU" firstBlockQuantifiers = "EU" secondBlockQuantifiers = "EUN"
I’m saying: there will be 2 trials in the first block and 3 trials in the second (you would replace those numbers with 30 and 24 instead). I’m also saying: each prime (regardless of block) will be either ‘E’ (for existential) or ‘U’ (for universal), and each in-sentence quantifier in the first block will be either ‘E’ (to be replaced with the determiner “A”) or ‘U’ (to be replaced with “Every”) and each in-sentence quantifier in the second block will be ‘E,’ ‘U’ or ‘N’ (to be replaced with “No”).
Note: the numbers in my example are not ideal, since the second block has 3 trials over which you cannot properly distribute 2 prime types—just make sure the number of trials in each block is a multiple of the number of prime types and the numbers of quantifiers in both trials; 24 and 30 both being multiples of both 2 and 3, you should be fine
Now here’s some javascript magic to automatically associate your trials with the desired conditions:
// Generates a random, even array of LENGTH characters picked from a string randomArray = (length,list) => list.split('').map(v=>v.repeat(parseInt(length/list.length))) .flat(1).sort(v=>Math.random()>=0.5) listOfPrimesFirst = randomArray(firstBlockLength,primes) listOfPrimesSecond = randomArray(secondBlockLength,primes) listOfQuantifiersFirst = randomArray(firstBlockLength,firstBlockQuantifiers) listOfQuantifiersSecond = randomArray(secondBlockLength,secondBlockQuantifiers) translator = {E: "One", U: "Every", N: "No"} // Randomly order the indices first indices = [...new Array(5)].map((v,i)=>i).sort(v=>Math.random()>=0.5) // Now assign properties to trials trials = [] listOfQuantifiersFirst.map( (v,i) => trials[indices[i]] = { block: "first", prime: translator[listOfPrimesFirst[i]], quantifier: translator[v] } ) listOfQuantifiersSecond.map( (v,i) => trials[indices[firstBlockLength+i]] = { block: "second", prime: translator[listOfPrimesSecond[i]], quantifier: translator[v] } )Once this is in place, you can use Template and GetTable().filter to generate trials:
Template( // Only use the rows where Item appears in the indices of the first block GetTable("myTable").filter( row=>indices.slice(0,firstBlockLength).indexOf(Number(row.Item))>=0 ) , row => newTrial( 'firstblock' , newText("space","Please press Space").print(), newKey(" ").wait(), getText("space").remove() , newText( "prime" , trials[row.Item].prime ).log().print(), newTimer(20).start().log().wait(), // 20ms prime getText("prime").remove() , newController("DashedSentence", {s: trials[row.Item].quantifier+' '+row.Sentence}) .print().log().wait().remove() ) .log( "Group" , row.Group ) .log( "Block" , trials[row.Item].block ) .log( "Sentence" , row.Sentence ) .log( "Prime" , trials[row.Item].prime ) .log( "Quantifier" , trials[row.Item].quantifier ) ) Template( // Only use the rows where Item appears in the indices of the second block GetTable("myTable").filter( row=>indices.slice(firstBlockLength,).indexOf(Number(row.Item))>=0 ) , row => newTrial( 'secondblock' , newButton("Please click here").print().wait().remove() , newText( "prime" , trials[row.Item].prime ).log().print(), newTimer(10).start().log().wait(), // 10ms prime (more-or-less, hardware constraints) getText("prime").remove() , newController("DashedSentence", {s: trials[row.Item].quantifier+' '+row.Sentence}) .print().log().wait().remove() ) .log( "Group" , row.Group ) .log( "Block" , trials[row.Item].block ) .log( "Sentence" , row.Sentence ) .log( "Prime" , trials[row.Item].prime ) .log( "Quantifier" , trials[row.Item].quantifier ) )Let me know if you have questions about adapting this to your case
Jeremy
JeremyKeymasterHello,
You could do something like this:
Template( "myTable.csv" , row => newTrial( "experiment" , newController("DashedSentence", {s: row.Sentence}).print().log().wait() .remove() , (row.Question?[ newText("Question", row.Question).print() , newKey("FJ") .wait() .log() ]:null) , [etc]Jeremy
JeremyKeymasterHi Sam,
EDIT: actually you don’t have to use the Function element as I suggest below, but you’ll still need a little bit of javascript when setting and testing your Var element:
Template( "fulldesign.csv" , variable => newTrial( "task" , newVar("fullstring", "") , newText("Please type your guess").print() , newKey("guess", "") .callback( getVar("fullstring").set( v=>v+getKey("guess").value ) ) , newKey("Enter").wait() , getVar("fullstring").test.is( v=>v.match(new RegExp(variable.answer+"$","i")) ) .success( newText("Correct!").print() ) .failure( newText("Incorrect").print() ) , newButton("next").print().wait() ) )End of edit
I think you’ll have to inject some javascript in there, using the Function element:
Template( "fulldesign.csv" , variable => newTrial( "task" , newText("Please type your guess").print() , newFunction(function(){ this.fullstring = ""; let oldOnkeydown = document.onkeydown; document.onkeydown = e=>{ if (oldOnkeydown instanceof Function) oldOnkeydown.call(document, e); this.fullstring += e.key; }; }).call() , newKey("Enter").wait() , newFunction(function(){ return this.fullstring.match(new RegExp(variable.answer+"$","i"))!=null; }).test.is( true ) .success( newText("Correct!").print() ) .failure( newText("Incorrect").print() ) , newButton("next").print().wait() ) )Two comments on your code:
- The setVar command (as all PennController commands) is executed immediately when the script encounters it, so unless it is in a callback command, it will only be executed once. In your case, you call it immediately after creating the Key element, so your Var element will remain empty.
- The after command adds an element to the right of another element when printed on the page, but it does not change the value of either element
Jeremy
-
This reply was modified 6 years ago by
Jeremy. Reason: Added a Function-less script
JeremyKeymasterHello Gemma,
This is really weird, it’s an error that comes from an old version of the PHP script from this page, I remember fixing it (on August 15, 2019 according to the logs). Did you make sure your PHP script is up to date?
Jeremy
JeremyKeymasterThat’s because the counter value from the URL is interpreted as a string but the switch statement tests for numbers. Use Number(GetURLParameter("withsquare")) to make sure the counter value from the URL is interpreted as a number.
Jeremy
JeremyKeymasterNo difference, I was just trying to make the forum’s syntax-highlighter happy
JeremyKeymasterHello!
1. Everything looks OK as far as I can tell, but I think you should be able to test SONA’s credit granting if you can create a dummy account. At least this is what I did in the past, and I setup my SONA projects to only accept my dummy account as a participant (maybe I also password-protected them?) and I was able to do a proper testrun
2. Do you mean that the credit token will be different for different SONA experiments? I forget. If so, you could check the value of withsquare from the URL at the top of your script and set a variable accordingly that you use to generate your link:
credittoken = ""; switch (GetURLParameter("withsquare")) { case 0: credittoken = "hellothere"; break; case 1: credittoken = "byenow"; break; case 2: credittoken = "salutsalut"; break; case 3: credittoken = "bonbahaplus"; break; } // ... newText('Click here to confirm my submission on SONA.')Jeremy
-
AuthorPosts