Jeremy

Forum Replies Created

Viewing 15 posts - 451 through 465 (of 1,522 total)
  • Author
    Posts
  • in reply to: Implementing test recording #8104
    Jeremy
    Keymaster

    Hi,

    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

    in reply to: Implementing test recording #8101
    Jeremy
    Keymaster

    Hi,

    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 coding

    Re. 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, use callback on the “Play” button itself, because that’s what makes the script go on without waiting for playback to finish

    Re. 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 the newButton line to executing the .test.clicked line, and so the test will always fail

    Putting 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

    in reply to: MediaRecorder's Ajax post failed #8100
    Jeremy
    Keymaster

    Hi 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 be https://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

    in reply to: Repeat trials until correct #8096
    Jeremy
    Keymaster

    Hi,

    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

    in reply to: Problem reading results file #8095
    Jeremy
    Keymaster

    Hi Noelia,

    Have you tried simply passing quote="" to the two occurrences of read.csv in the body of read.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

    in reply to: Question conntroller answering with number keys #8091
    Jeremy
    Keymaster

    Hi,

    Either increase min-width, or add horizontal margins to the li elements, eg margin: 0em 1em;

    Jeremy

    in reply to: Counterbalancing issues #8088
    Jeremy
    Keymaster

    Hi 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 here

    Here 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

    in reply to: Javascript in HTML Files #8082
    Jeremy
    Keymaster

    Hi Jack,

    When the content of your HTML file is injected in the experiment, its first div element is not the first div in the document, unlike when you open the file separately in your browser

    You 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

    in reply to: Problem uploading experiment audios #8077
    Jeremy
    Keymaster

    Hi 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 again

    Jeremy

    in reply to: Question conntroller answering with number keys #8074
    Jeremy
    Keymaster

    Hi Danil,

    The li elements don’t get the scale-box class when you do not set presentAsScale to true in the Question controller (and in my experience, setting it to true prevents keypresses on 1/2/3)

    However, they get the normal-answer class, so you could either add the rule font-size: 18pt; to li.normal-answer { in Question.css, or you could add it to .PennController-myController.Question-Question li { in global_z.css

    Jeremy

    in reply to: Report of eye-tracker stop working during experiment #8072
    Jeremy
    Keymaster

    Hi 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

    in reply to: Internal Server Error (Probably error 500) #8071
    Jeremy
    Keymaster

    Hello,

    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 the www folder, not in chunk_includes—csv files should still live in chunk_includes)

    We will let you know in advance by sending an announcement email when we proceed to maintenance operations

    Thank you for you understanding,
    Jeremy

    in reply to: Randomization mixes the sentences in the csv file #8070
    Jeremy
    Keymaster

    Hi 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

    in reply to: Question conntroller answering with number keys #8064
    Jeremy
    Keymaster

    I 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 the li elements disables mouse clicks

    Jeremy

    in reply to: Validation of participant's ID (password) #8062
    Jeremy
    Keymaster

    Hi,

    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 for bye earth. I have added comments but of course keeping them when actually running the experiment would defeat the whole purpose of hashing the input

    You 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 the password_hashes array

    Jeremy

Viewing 15 posts - 451 through 465 (of 1,522 total)