Jeremy

Forum Replies Created

Viewing 15 posts - 826 through 840 (of 1,522 total)
  • Author
    Posts
  • Jeremy
    Keymaster

    I re-read your message and realized that you might want to print multiple sentences, but only one per line

    Here’s a suggestion, it will split your string at every ., ? or ! followed by a space and print each sentence on a new, centered line:

    dashed = (name,sentences) => [
        newText(name,"").size("100vw","auto").center().css("text-align","center").print()
        ,
        ...sentences.split(/(?<=[\.!\?])[\s\t]/).map( (s,n) => [
            newText(name+"-s"+n,"")
                .css({display:'inline-block','line-height':'2em','white-space':'nowrap'})
                .print(getText(name))
            ,
            ...s.split(/[\s\t]+/).map( (w,i) =>
                    newText(name+'-s'+n+"-"+i, w.replace(/([^.,?:;!\s])/g,"<span class='DashedSentence-ospan'><span class='DashedSentence-ispan'>$1</span></span>"))
                        .css("margin","0em 0.25em")
                        .print(getText(name+"-s"+n))
            )
        ] ).flat()
        ,
        newKey(name+'-start', " ").log().wait() // first keypress, to reveal first word
        ,
        ...sentences.split(/(?<=[\.!\?])[\s\t]/).map( (s,n) => [
            ...s.split(/[\s\t]+/).map((w,i)=>[
                getText(name+'-s'+n+'-'+i).text(w) // reveal chunk
                ,
                newKey('s'+n+'-'+i+"-"+w," ").log().wait() // wait for keypress
                ,
                getText(name+'-s'+n+'-'+i).text(w.replace(/([^.,?:;!\s])/g,"<span class='DashedSentence-ospan'><span class='DashedSentence-ispan'>$1</span></span>")) // hide chunk
            ] ).flat()
        ] ).flat()
    ]

    Jeremy

    Jeremy
    Keymaster

    Hi,

    Try this:

    newController("DashedSentence",{s:"This is a rather long sentence but hopefully this will be long enough that it should insert linebreaks into narrow containers"})
        .css("white-space","nowrap")
        .center()
        .print()
        .wait()

    Note that since you don’t want any linebreak, your sentence might overflow horizontally from the page area if it is too long

    Jeremy

    in reply to: Video recording in InitiateRecorder? #7006
    Jeremy
    Keymaster

    Hi,

    The MediaRecorder element will capture video unless it is explicitly restricted to audio: https://doc.pcibex.net/mediarecorder/

    If there is a newMediaRecorder command in your script that does not explicitly specify "audio" (ie neither newMediaRecorder("audio") nor newMediaRecorder("myrecorder", "audio")) then PennController will determine that you need to capture video and accordingly require access to the participant’s webcam

    Jeremy

    in reply to: Issues with DashedSentence on new farm #7005
    Jeremy
    Keymaster

    Hi Zach,

    You are calling wait on the Canvas element: there is no wait command on Canvas elements. You want to call it on the Controller element instead:

    newImage("sb1", "training_sb_inplace.png")
        .size(sb_width, sb_height)
    ,
    newController("ds1", "DashedSentence", {s: row.SentenceText, mode: "speeded acceptability", blankText: "+", display: "in place"})
    ,
    newCanvas("sprcanv", canv_width, canv_height)
        .center()
        .add( sb_x_left, 0, getImage("sb1"))
        .add(ds_x_left, ds_y , getController("ds1"))
        .print()
    ,
    getController("ds1")
        .wait()
    ,
    getCanvas("sprcanv")
        .remove()
    

    (I called remove on the Canvas element, but since you meant to call wait on the Controller element, I’m not sure which element you wanted to remove)

    Jeremy

    in reply to: Concatenating Strings #6998
    Jeremy
    Keymaster

    Hi Doug,

    PennController elements, and Text elements in particular, are not strings, so you cannot just concatenate them to a string with +. More info here: https://doc.pcibex.net/how-to-guides/using-javascript/

    Here’s how to achieve what you need:

    newVar("link")
      .set(getVar("pID"))
      .set(v=>"<p><a href='https://pitt.co1.qualtrics.com/jfe/form/SV_9suLiT1t7sABeDg?id="+v+"' target='_blank' rel='noopener noreferrer'>Click here to complete a brief survey on Qualtrics.</a></p>")
    ,
    newText("")
      .text(getVar("link"))
      .center()
      .print()

    Jeremy

    in reply to: Refreshing canvas #6997
    Jeremy
    Keymaster

    Hi Rafael,

    The reasoning is the same here: elements are parts of trials, so when their trial ends, they cease to exist. The only exception is the Var element, which can be made global so you can access and manipulate its value across trials

    If you need to keep track of how much time has passed since the beginning of the experiment, you can set a javascript variable to Date.now() and check it on every trial, for example inside a Header. Here’s an example that will print a 2min timer in the top-left corner of the page, and clear the screen and print a “Timed out!” message when there’s not time left:

    ALLOTEDTIME = 2*60*1000
    startTime = Date.now()
    toMinSec = v=>Math.floor((ALLOTEDTIME-v)/60000)+":"+Math.floor(((ALLOTEDTIME-v)/1000)%60)
    
    Header(
        newText("timer", "").print("left at 2em", "top at 2em")
        ,
        newVar("time elapsed")
            .set(v=>Date.now()-startTime)
            .test.is(v=>v>ALLOTEDTIME).success( end() )
        ,
        newTimer("timeout", 500)
            .callback(
                getVar("time elapsed")
                    .set(v=>Date.now()-startTime)
                    .test.is(v=>v>ALLOTEDTIME)
                    .success(
                        clear()
                        ,
                        newText("Time out!").print()
                        ,
                        newButton().wait()   
                    )
                    .failure( 
                        getVar("time elapsed").set(toMinSec)
                        ,
                        getText("timer").text(getVar("time elapsed"))
                        ,
                        getTimer("timeout").start() 
                    )
            )
            .start()
    )

    Jeremy

    in reply to: Disabling video controls #6995
    Jeremy
    Keymaster

    Hi,

    The element being MediaRecorder and not Video, you need to use .PennController-mediarecorder video as your selector

    Jeremy

    in reply to: Different sequencing for different participants #6988
    Jeremy
    Keymaster

    The counter is unfortunately not a solution: if you do not increment it as soon as a participant starts your study then you run the risk of running too many early participants in the same condition (for example, with rapid signups, it could be that the counter only increases after 40 participants have started). Even if you manage to increment the counter instantly, you end up with a number of participants who did not complete your study (maybe they decide to stop, or they encounter a fatal error, etc.)

    As you can imagine, this is not specific to how Ibex handles the counter. You could imagine more sophisticated algorithms (eg. initially increment with each click on the link, and switch to conditional assignment once you start receiving submissions) but in the end you will always have multiple participants taking your study in parallel at the same time, and you will never be certain that a given participant will complete your study

    This is why I suggested you handle this manually. The other reason why you don’t want to use the internal counter is that it is already used to subset your table to rows that share the same value in the Group/List column, so you would have a confounding factor here. Moreover, what you suggest (switching to the other sequence once the counter reaches 30) practically means that not all participants start at the same time, ie. one group of participants starts before the other one. If you’re ok with that, I don’t really see a problem with running 30 participants first, and another 30 participants after that

    If your recruiting solution gives you that option, you could generate two links that differ in the value of a specific parameter (eg. “seqOrder”: https://farm.pcibex.net/p/mySlug/?seqOrder=0 vs https://farm.pcibex.net/p/mySlug/?seqOrder=1) with your participants only allowed to click one of the two. Then you can do:

    if (GetURLParameter("seqOrder")==0)
      Sequence("intro","consent","subexp1practice",rshuffle("subexp1items","subexp1fillers"),"break","subexp2practice",rshuffle("subexp2items","subexp2fillers"),"send","ending")
    else
      Sequence("intro","consent","subexp2practice",rshuffle("subexp2items","subexp2fillers"),"break","subexp1practice",rshuffle("subexp1items","subexp1fillers"),"send","ending")

    Jeremy

    in reply to: Refreshing canvas #6985
    Jeremy
    Keymaster

    Hi Rafael,

    The last command in your trial is wait on the Button element, so whenever the button is clicked, the trial ends, and all elements are removed from the page, so you’ll just no longer be able to see any changes to the Canvas elements after the click. You will either need to keep track of things across trials using a global Var element and testing it at the beginning of the trial to run different add or print commands accordingly, or change your approach of your task and incorporate it within a single trial

    A couple tips: if you’re not getting the Text elements, you don’t need to explicitly give them names (right now you’re creating two distinct elements both named “player 1” and three distinct elements all named “player 2”). Timer elements have no visual content, no it doesn’t make much sense to add them to a Canvas element. You could simply attach your callback commands directly onto the newTimer command (also, you can pass three comma-separated commands to callback, no need to call three callbacks in a row)

    Jeremy

    in reply to: Error: unprocessable entity #6984
    Jeremy
    Keymaster

    It looks like the problem has to do with the project’s name being too long. Ironically that very problem prevents you from renaming the cloned copy of the project, because it requires creating a new project with a long name before renaming it…

    I’ll fix this and update the farm soon, but in the meantime you can try renaming the original project with a shorter name, just make sure that you use fewer than 45 characters (the overall limit is 50 characters, and cloned projects automatically add “_copy” at the end, which uses 5 characters)

    Jeremy

    in reply to: Error: unprocessable entity #6981
    Jeremy
    Keymaster

    Hi Xinchi,

    I am sorry you are experiencing issues cloning and saving your project. May I ask what the slug of your project is (the 6 characters at the end of the demonstration link) and whether you still encounter this problem with this and/or other projects?

    Jeremy

    in reply to: Different sequencing for different participants #6980
    Jeremy
    Keymaster

    Hi,

    If you need perfectly balanced groups (30-30) you’ll have to assign participants manually. If I were you, I would run a first batch of 30 participants with Sequence("intro","consent","subexp1practice",rshuffle("subexp1items","subexp1fillers"),"break","subexp2practice",randomize("subexp2items","subexp2fillers"),"send","ending"), then run a second batch of 30 participants with Sequence("intro","consent","subexp2practice",randomize("subexp2items","subexp2fillers"),"break","subexp1practice",randomize("subexp1items","subexp1fillers"),"send","ending")

    If you want it to be random, you could simply do this, but then you won’t be certain you’ll end up with exactly 30 participants in each group:

    if (Math.random() >= 0.5)
      Sequence("intro","consent","subexp1practice",rshuffle("subexp1items","subexp1fillers"),"break","subexp2practice",randomize("subexp2items","subexp2fillers"),"send","ending")
    else
      Sequence("intro","consent","subexp2practice",randomize("subexp2items","subexp2fillers"),"break","subexp1practice",randomize("subexp1items","subexp1fillers"),"send","ending")

    NB: shouldn’t it be rshuffle or randomize everywhere? Right now you have rshuffle for exp1 in the first Sequence command only

    Jeremy

    in reply to: UploadRecordings #6977
    Jeremy
    Keymaster

    Hi,

    sepWith is illustrated on the documentation page for UploadRecordings: https://doc.pcibex.net/global-commands/uploadrecordings/

    It’s an Ibex function that inserts trials after each trial from a set, which proves handy for regularly inserting asynchronous uploadrecordings trials.

    Would you mind sharing your project’s demonstration link with me, either here or at support@pcibex.net?

    Jeremy

    in reply to: UploadRecordings #6975
    Jeremy
    Keymaster

    Hi,

    UploadRecordings creates a trial, you cannot include it inside another trial (only SendResults has this exceptional behavior, which might not have been the best design decision on my end, since that seems to introduce confusion)

    I’m working on fixing the behavior of the automatically inserted last UploadRecordings trial. When you say that “this always happens” even when you manually include an UploadRecordings trial earlier in your Sequence, do you mean that you still see the default one at the end of the experiment, and that that one shows you an error message (but not the one you insert earlier)?

    Have you tried something along those lines?

    Sequence("intro", sepWith("asyncUpload",randomize("trials")), "syncUpload", SendResults(), "thanks")
    
    UploadRecordings("syncUpload")
    UploadRecordings("asyncUpload", "noblock")
    
    newTrial("thanks",
      newText("Thank you for participating in this study.").print()
      ,
      newButton().wait() // wait on this screen forever
    )

    Jeremy

    in reply to: DashedSentence in a PennController trial #6970
    Jeremy
    Keymaster

    Hi Matthias,

    Since what you need is not natively supported by Ibex’s DashedSentence controller, and is not immediately implementable in PennController from a simple string, you must resort to at least some javascript code. The snippet from my previous message defines a function that takes a string and automatically generates a series of PennController commands that reproduce certain behaviors of the native-Ibex DashedSentence controller

    PennController lets you inject some native-Ibex controller inside your trial’s script, but you cannot use (bits of) PennController code to edit a native-Ibex controller. This means that you cannot simply integrate the function above (which, again, simply outputs a simple series of PennController commands) into the DashedSentence controller

    I decided to exclusively rely on PennController for several reasons. First, this is a PennController support space 😉 This means that people here know at least some PennController code, but they do not necessarily know the quite advanced javascript making up the DashedSentence controller’s code. Second, PennController gives you more control over the elements in your trial (technically, you could access each chunk separately, since each is a Text element). Third, I think it’s much easier to understand. This is the output of dashed("myDashed", "Hello world") (for the above definition of dashed):

    newText("myDashed","").css({display:'flex','flex-direction':'row','flex-wrap':'wrap','line-height':'2em','max-width':'100%'}).print()
    ,
    newText("myDashed-0", "<span class='DashedSentence-ospan'><span class='DashedSentence-ispan'>Hello</span></span>"))
        .css("margin","0em 0.2em")
        .print(getText("myDashed"))
    ,
    newText("myDashed-1", "<span class='DashedSentence-ospan'><span class='DashedSentence-ispan'>world</span></span>"))
        .css("margin","0em 0.2em")
        .print(getText("myDashed"))
    ,
    newKey("0-Hello"," ").log().wait(),
    getText("myDashed-0").text("Hello")
    ,
    newKey("0-Hello"," ").log().wait(),
    getText("myDashed-1").text("world")

    This output is reasonably simple PennController code. The tricky part is seeing how exactly the dashed function maps the string onto that series of commands. This part does most of the job ...sentence.split(/[\s\t<>]+/): it splits the string at every space/tab/</> character, and generates commands for each chunk. The code is further obscured by the need to handle <br>s, which include linebreaks (rendered as content-less Text elements which occupy 100% of the available page’s width, visually resulting in linebreaks)

    Replacing the space/tab separator character with * so as to split chunks of words rather than individual words is pretty straightforward, all that’s needed is to replace the regular expression /[\s\t<>]+/ with /[*<>]+/. Masking the previous word again when revealing the next word requires some slight rearrangement, so that the function outputs “reveal word; wait for keypress; hide word” for each chunk instead of just “wait for keypress; reveal word.” as it does now.

    So here’s what you get (I also made it use hyphens instead of underscores after I went back to your previous messages):

    dashed = (name,sentence) => [
        newText(name,"").css({display:'flex','flex-direction':'row','flex-wrap':'wrap','line-height':'2em','max-width':'100%'}).print()
        ,
        ...sentence.split(/[*<>]+/).map( (w,i) => (w=="br"?
                newText("").css("width","100vw").print(getText(name))
                :
                newText(name+'-'+i, w.replace(/([^.,?:;!\s])/g,'-'))
                    .css({margin:"0em 0.2em",'font-family':'monospace',"font-size":"large"})
                    .print(getText(name))
        ))
        ,
        newKey(name+'-start', " ").log().wait() // first keypress, to reveal first chunk
        ,
        ...sentence.split(/[*<>]+/).map((w,i)=>(w!="br"?[
            getText(name+'-'+i).text(w) // reveal chunk
            ,
            newKey(i+"-"+w," ").log().wait() // wait for keypress
            ,
            getText(name+'-'+i).text(w.replace(/([^.,?:;!\s])/g,'-')) // hide chunk
        ]:null))
    ]
    
    newTrial(
        dashed("myDashed",  "This is a test.*This is the second,*longer part of the test!*"+
                            "<br>And now*this is a third part,*just to test whether*it will automatically*insert a linebreak")
        ,
        getText("myDashed").remove()
        ,
        newButton("Finish").print().wait()
    )

    Here’s a live example: https://farm.pcibex.net/r/frrjaU/

    Let me know if you have any questions

    Jeremy

Viewing 15 posts - 826 through 840 (of 1,522 total)