Jeremy

Forum Replies Created

Viewing 15 posts - 241 through 255 (of 1,522 total)
  • Author
    Posts
  • in reply to: Template trouble #9758
    Jeremy
    Keymaster

    Hi Nico,

    The experiment at your link shows no error and works well; I see that the code contains two Template generating one trial each (the two CSV tables list 8 groups from A to H each, and a single trial per group) and the sequence does indeed contain the two trials

    Were you able to fix the issue in the meantime?

    Jeremy

    in reply to: Spacing "scale" radio buttons apart #9753
    Jeremy
    Keymaster

    Hi Kate,

    The name of the file in Resources is RadioButtonsSentence.png but you reference RadioButtonSentence.png in your table

    The Text element is in fact printed on the page after you click A, B or C, because of the wait command on the Scale element coming before print on the Text element. The trial finishes immediately, because there is no further command to run, so you don’t even have time to effectively see the text on the page

    Jeremy

    in reply to: Spacing "scale" radio buttons apart #9751
    Jeremy
    Keymaster

    Hi,

    Use CSS to control the Scale element’s aesthetics

    Another solution would be to inject some HTML in the labels:

    newScale("score",   
        "<span style='margin: 2em;'>A</span>",
        "<span style='margin: 2em;'>B</span>",
        "<span style='margin: 2em;'>C</span>"
    )
        .css({"font-size": "20px", "font-weight": "normal"})
        .radio()
        .labelsPosition("top")
        .print()
        .wait()

    Jeremy

    in reply to: Trial judgement and present unequal fillers #9749
    Jeremy
    Keymaster

    Hi,

    The project at https://farm.pcibex.net/r/IqekEL/ does not define the function pick. Also, you forgot ... before repetitions in Sequence

    Once I fix both issues, the experiment runs with a mix of target and filler trials, as intended

    Jeremy

    in reply to: Logging key press for each trial within template? #9748
    Jeremy
    Keymaster

    Hi,

    The identifier and video file are already logged as extra columns in the code that you posted. As far as I can tell, there is a single response time in that code, which corresponds to how much time elapsed since the beginning of the timer and when the participant presses space. So you could add a Var element to log the response time as an extra column like this:

    Template( "eCIDEx.csv" , row => 
        newTrial("Experiment1",
            newText(row.identifier)
                .css("font-size", "2.5em")
                .css("text-align", "center")
                .settings.center()
                .print()
                .log()
            ,
            newVideo(row.videofile)
                .size("60vw", "auto")
                .settings.center()
                .print()
                .play()
                .disable(0.01)
                .log()
            ,
            newVar("RT").global().set( () => Date.now() )
            ,
            newTimer("7.2s", 7200).start()
            ,
            newKey(" ").log("last").callback( getTimer("7.2s").stop() )
            ,
            getTimer("7.2s").wait()
            ,
            getVar("RT").set( v => Date.now() - v )
            ,
            getVideo(row.videofile).remove()
            ,
            newImage("Fix", "Focus_Point.jpg")
                .center()
                .print("middle at 50%", "middle at 50%")
            ,
            newTimer("Timer1", 1000).start().wait()
        )
        .log("id", getVar("subjID"))
        .log("identifier", row.identifier)
        .log("videofile", row.videofile)
        .log("ResponseTime", getVar("RT"))
        .log("qualID", getVar("qualID"))
    )

    Then you can systematically look at a single line in the results file (eg. the Text element’s log line) and get all the information you want in it

    Jeremy

    in reply to: putting a frame around element of a scale object #9747
    Jeremy
    Keymaster

    Hi,

    The code above dates from 2019, there have been new releases of PennController since then, and Scale elements are no longer rendered as HTML table elements

    Judging from the project you shared, it seems like you have come up with a solution, as you have this in global_z.css:

    .PennController-Scale .option label {
        margin: 0em 1em; 
        font-size: 18px;
    }

    You could add a frame like this:

    .PennController-Scale .option label {
        margin: 0em 1em; 
        font-size: 18px;
        border:1px solid gray;
    }

    Jeremy

    in reply to: Audio replay only once #9746
    Jeremy
    Keymaster

    Hi Nianpo,

    Just add a second .wait:

    newAudio("audio", variable.audio).center().print().wait(),
    newTimer("callback",1).callback( getAudio("audio").wait().wait().remove() ).start()

    Jeremy

    Jeremy
    Keymaster

    Hi Dimitra,

    Assuming you are willing to merge together the four blocks of experimental trials (otherwise the items sharing the same picture would never appear in a row to start with, since you seem to use each picture only once per block) you can make it work as in this project (I have updated some commands to match the modern PennController syntax — you also seemed to use the same code for all your trials, practice and experimental, so I use a single Template command)

    Jeremy

    in reply to: About recording key response reaction time #9744
    Jeremy
    Keymaster

    Hi Karen,

    You don’t need the callback here, since you already have a wait that ensures that the following code will always be executed after the key has been pressed

    You also seem to confuse key.log and var.log with newTrial().log

    newTrial(
        // ...
        newVar("RT1").global().set( () => Date.now() )
        ,
        newKey("flan_RespKey", "FJ")
            .log()
            .wait()
        ,
        getVar("RT1").set( v => Date.now() - v )
        // ...
    )
    .log("flanRT",getVar("RT1"))

    Jeremy

    in reply to: Unfold text before and after TextInput #9743
    Jeremy
    Keymaster

    Hi,

    There are multiple things at play here, but a crucial one is that print and unfold will add content to a new line each time, so you won’t be able to get everything on a single line using those commands

    To really have the input box in the same stream as the surrounding text, ideally it needs to be contained in a simple span HTML element surrounded with textNode elements

    Because you won’t be able to use the PennController command unfold, you will need to come up with a custom function that does the job of dynamically revealing the content of a span element. This is one such function, which you can place at the top of your script for example:

    function unfold(element,duration) {
    
      class TextNode {
        constructor(original){
          this.original = original;
          this.replacer = document.createElement("SPAN");
          this.replacer.style.visibility='hidden';
          this.replacer.textContent = this.original.textContent;
          this.original.replaceWith(this.replacer);
        }
        unfold(n) {
          if (n>=this.original.length) return this.replacer.replaceWith(this.original);
          [...this.replacer.childNodes].forEach(n=>n.remove());
          this.replacer.style.visibility = 'visible';
          const vis = document.createTextNode(this.original.textContent.substring(0,n));
          const hid = document.createElement("SPAN");
          hid.style.visibility = 'hidden';
          hid.innerText = this.original.textContent.substring(n);
          this.replacer.append(vis);
          this.replacer.append(hid);
        }
      }
    
      let toUnfold = (function buildArray(el){
        const ar = [];
        if (el.childNodes.length>0) {
          for (let i=0; i<el.childNodes.length; i++)
            ar.push(...buildArray(el.childNodes[i],duration,'hold'));
        }
        else if (el.nodeName=="#text")
          ar.push(new TextNode(el));
        else if (el.style.visibility!="hidden") {
          el.style.visibility='hidden';
          ar.push(el);
        }
        return ar;
      })(element);
    
      let totalChar = 0;
      toUnfold = toUnfold.map(e=>{
        const n = e instanceof TextNode ? e.original.length : 1;
        totalChar += n;
        return {element: e, len: n, total: totalChar};
      });
      const perChar = duration / totalChar;
      
      let startTime;
      return new Promise(r=> {
        const updateUnfold = timestamp => {
          if (startTime===undefined) startTime = timestamp;
          const elapsed = timestamp-startTime, progress = elapsed/perChar;
          if (elapsed >= duration) {
            toUnfold.forEach( e=>e.element instanceof TextNode ? e.element.unfold(e.len+1) : e.element.style.visibility='visible' );
            r();
          }
          else {
            let total = 0;
            toUnfold.find(e=>{
              const lastToReveal = progress < e.total;
              if (e.element instanceof TextNode) e.element.unfold(lastToReveal ? progress-total : progress);
              else e.element.style.visibility = 'visible';
              total += e.len;
              return lastToReveal;
            });
            window.requestAnimationFrame(updateUnfold);
          }
        };
        window.requestAnimationFrame(updateUnfold);
      });
    
    }

    Then integrating it inside a trial is pretty easy:

    newTextInput("filledInBlank")
        .size("6em", "1.5em")
        .lines(1)
        .css({
            "outline": "none",
            "resize": "none",
            "border": "0",
            "padding": "0",
            "margin": "0",
            "margin-left": "1ex",
            "margin-right": "1ex",
            "vertical-align": "-.33em",
            "background-color": "white",
            "border-bottom": "2px solid black",
            "display": "inline"
            })
        .cssContainer({"display": "inline", "font-size": "16px"})
        .print()
    ,
    newFunction( async ()=> {
        const answer = document.querySelector("textarea.PennController-filledInBlank");
        const container = document.createElement("SPAN");
        answer.replaceWith(container);
        const before = document.createTextNode( row.before_blank );
        const after = document.createTextNode( row.after_blank );
        container.append(before);
        container.append(answer);
        container.append(after);
        await unfold(container, parseInt(row.time_before)+parseInt(row.time_after) );
    }).call()
    ,
    newButton("moveOn", "Continue")
        .right()
        .print()
        .wait()

    Jeremy

    in reply to: Priming experiment with pseudorandomization #9727
    Jeremy
    Keymaster

    Hi,

    Looking at your question and your tables, I think that you are talking about presenting several stimuli within a *single* trial, and that your subsequences prime-target correspond to single trials. As far as I understand, a single trial in your experiment has the following structure:

    1. Show a prime word
    2. Show a target word
    3. Show a post-target word (filler trials only)

    My suggestion is you add a column after target in your table for the third word, and simply leave it empty for the target trials. Then, in the newTrial command inside Template, you include the third stimulus only for the filler items. The basic idea would be something like this:

    Table:

    group,prime,target,post,pairtype,condition,correct
    A,prime1A,target1A,,target,noun.noun,f
    A,prime2A,target2A,,target,noun.noun,f
    A,prime3A,target3A,,target,noun.noun,f
    A,prime4A,target4A,,target,noun.noun,f
    A,prime5A,target5A,,target,noun.noun,f
    A,filler1A,filler1A,filler1A,filler,filler,f
    A,filler2A,filler2A,filler2A,filler,filler,f
    A,filler3A,filler3A,filler3A,filler,filler,f
    A,filler4A,filler4A,filler4A,filler,filler,f
    A,filler5A,filler5A,filler5A,filler,filler,f
    

    Script:

    Template( "mytable.csv" , row =>
      newTrial( row.condition ,
        newText( "prime", row.prime ).print(),
        newTimer(1000).start().wait(),
        getText( "prime" ).remove()
        ,
        newText( "target", row.target ).print(),
        newTimer(1000).start().wait(),
        getText( "target" ).remove()
        ,
        ...( row.condition=="filler" ? [
              newText( "post", row.post ).print(),
              newTimer(1000).start().wait(),
              getText( "post" ).remove()
        ] : [] )
        // ...
      )
      .log("pairtype",row.pairtype)
      // ...
    )

    Jeremy

    in reply to: Text Not Displaying Correctly #9725
    Jeremy
    Keymaster

    Hi Nasim,

    Not sure why the @import rule doesn’t work, but I don’t think using url("https://fonts.googleapis.com/css2?family=Raleway:wght@500&display=swap") as the src attribute in @font-face is valid anyway

    However, if I visit that URL and copy-paste the raw CSS rules back in global_z.css in place of the @font-face rule you currently have, I do see the Google font when I open the experiment

    Jeremy

    in reply to: Experiment Recovery #9724
    Jeremy
    Keymaster

    Hi,

    Indeed, there was an issue with the registration/activation process and two accounts were created with the same username and email address, and the two accounts owned different experiments. I have kept only the first user, and assigned all the experiments to that account. You should see all your projects listed once you log out and in again

    Jeremy

    in reply to: Eliminating empty values from csv file #9718
    Jeremy
    Keymaster

    Hello,

    Yes, you can use the javascript ternary conditional operator:

    ...( row.audio13.length>0 ? [
      newAudio("audio13", row.audio13)
      ,
      newButton("(Re)play audio13")
          .callback(
              getAudio("audio13")
              .play()
          )
          .print()
    ] : [] )
    

    Jeremy

    in reply to: Audio file naming #9717
    Jeremy
    Keymaster

    Hi,

    This is because PennController elements are created immediately, but PennController commands are delayed: getVar() does not return a string, it returns an object that some PennController commands will use to retrieve its value

    What you can do is generate a unique ID for each participant and use it both in the Audio elements’ names and in a log command on the newTrial()s so you can cross-reference things from the results file, for instance:

    uniqueID = [1,2,3,4].map(v=>Math.floor((1+Math.random())*0x10000).toString(16).substring(1)).join('-');
    
    Template( "exp5.csv" , row => 
      newTrial( "exp5Task" ,
        // ...
        newMediaRecorder(uniqueID+"_"+getVar("ID"),"audio").record()
        // ...
      )
      .log("uniqueID", uniqueID)
    )

    Jeremy

Viewing 15 posts - 241 through 255 (of 1,522 total)