arabic_demo

Created Diff never expires
/*****************
/*****************
* Crowding Test *
* Crowding Test *
*****************/
*****************/


const debug = false;
const debug = false;


import { core, data, util, visual } from "./psychojs/out/psychojs-2021.3.0.js";
import { core, data, util, visual } from "./psychojs/out/psychojs-2021.3.0.js";
const { PsychoJS } = core;
const { PsychoJS } = core;
const { TrialHandler, MultiStairHandler } = data;
const { TrialHandler, MultiStairHandler } = data;
const { Scheduler } = util;
const { Scheduler } = util;


////
////
import * as jsQUEST from "./lib/jsQUEST.module.js";
import * as jsQUEST from "./lib/jsQUEST.module.js";


////
////
/* ------------------------------- Components ------------------------------- */
/* ------------------------------- Components ------------------------------- */


import { hideCursor, showCursor, shuffle } from "./components/utils.js";
import { hideCursor, showCursor, shuffle } from "./components/utils.js";


import {
import {
addBeepButton,
addBeepButton,
instructionsText,
instructionsText,
removeBeepButton,
removeBeepButton,
} from "./components/instructions.js";
} from "./components/instructions.js";


import { calculateBlockWithTrialIndex } from "./components/trialCounter.js";
import { calculateBlockWithTrialIndex } from "./components/trialCounter.js";
import {
import {
getCorrectSynth,
getCorrectSynth,
getWrongSynth,
getWrongSynth,
getPurrSynth,
getPurrSynth,
} from "./components/sound.js";
} from "./components/sound.js";
import {
import {
removeClickableAlphabet,
removeClickableAlphabet,
setupClickableAlphabet,
setupClickableAlphabet,
} from "./components/showAlphabet.js";
} from "./components/showAlphabet.js";


/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */


window.jsQUEST = jsQUEST;
window.jsQUEST = jsQUEST;


var conditionTrials;
var conditionTrials;
var levelLeft, levelRight;
var levelLeft, levelRight;
let correctAns;
let correctAns;


// For development purposes, toggle RC off for testing speed
// For development purposes, toggle RC off for testing speed
const useRC = !debug;
const useRC = !debug;
const rc = RemoteCalibrator;
const rc = RemoteCalibrator;
rc.init();
rc.init();


// store info about the experiment session:
// store info about the experiment session:
let expName = "Threshold"; // from the Builder filename that created this script
let expName = "Threshold"; // from the Builder filename that created this script
let expInfo = { participant: debug ? rc.id.value : "", session: "001" };
let expInfo = { participant: debug ? rc.id.value : "", session: "001" };


const fontsRequired = new Set();
const fontsRequired = {};


////
////
// blockCount is just a file telling the program how many blocks in total
// blockCount is just a file telling the program how many blocks in total
Papa.parse("conditions/blockCount.csv", {
Papa.parse("conditions/blockCount.csv", {
download: true,
download: true,
complete: function (results) {
complete: function (results) {
const blockCount = results.data.length - 2; // TODO Make this calculation robust
const blockCount = results.data.length - 2; // TODO Make this calculation robust
loadBlockFiles(blockCount, () => {
loadBlockFiles(blockCount, () => {
if (useRC) {
if (useRC) {
rc.panel(
rc.panel(
[
[
{
{
name: "screenSize",
name: "screenSize",
},
},
{
{
name: "trackDistance",
name: "trackDistance",
options: {
options: {
nearPoint: false,
nearPoint: false,
showVideo: false,
showVideo: false,
},
},
},
},
],
],
"body",
"body",
{},
{},
() => {
() => {
rc.removePanel();
rc.removePanel();
// ! Start actual experiment
// ! Start actual experiment
experiment(blockCount);
experiment(blockCount);
}
}
);
);
} else {
} else {
// NO RC
// NO RC
experiment(blockCount);
experiment(blockCount);
}
}
});
});
},
},
});
});


const blockFiles = {};
const blockFiles = {};


const loadBlockFiles = (count, callback) => {
const loadBlockFiles = (count, callback) => {
if (count === 0) {
if (count === 0) {
callback();
callback();
return;
return;
}
}
Papa.parse(`conditions/block_${count}.csv`, {
Papa.parse(`conditions/block_${count}.csv`, {
download: true,
download: true,
header: true,
header: true,
skipEmptyLines: true,
skipEmptyLines: true,
dynamicTyping: true,
dynamicTyping: true,
complete: function (results) {
complete: function (results) {
blockFiles[count] = results.data;
blockFiles[count] = results.data;
if (debug) console.log("Block " + count + ": ", results.data);
if (debug) console.log("Block " + count + ": ", results.data);


Object.values(results.data).forEach((row) => {
Object.values(results.data).forEach((row) => {
let fontFamily = row["targetFont"];
let fontFamily = row["targetFont"];
let fontTestString = "12px " + fontFamily;
let fontTestString = "12px " + fontFamily;
let fontPath = "fonts/" + fontFamily + ".woff";
let fontPath = "fonts/" + fontFamily + ".woff2";
if (debug) console.log("fontTestString: ", fontTestString);
if (debug) console.log("fontTestString: ", fontTestString);


let response = fetch(fontPath).then((response) => {
let response = fetch(fontPath).then((response) => {
if (response.ok) {
if (response.ok) {
fontsRequired.add(row["targetFont"]);
// let f = new FontFace(fontFamily, `url(${response.url})`);
// f.load()
// .then((loadedFontFace) => {
// document.fonts.add(loadedFontFace);
// })
// .catch((err) => {
// console.error(err);
// });
fontsRequired[fontFamily] = fontPath;
} else {
} else {
console.log(
console.log(
"Does the browser consider this font supported?",
"Does the browser consider this font supported?",
document.fonts.check(fontTestString)
document.fonts.check(fontTestString)
);
);
console.log(
console.log(
"Uh oh, unable to find the font file for: " +
"Uh oh, unable to find the font file for: " +
fontFamily +
fontFamily +
"\n" +
"\n" +
"If this font is already supported by the browser then it should display correctly. " +
"If this font is already supported by the browser then it should display correctly. " +
"\n" +
"\n" +
"If not, however, a different fallback font will be chosen by the browser, and your stimulus will not be displayed as intended. " +
"If not, however, a different fallback font will be chosen by the browser, and your stimulus will not be displayed as intended. " +
"\n" +
"\n" +
"Please verify for yourself that " +
"Please verify for yourself that " +
fontFamily +
fontFamily +
" is being correctly represented in your experiment."
" is being correctly represented in your experiment."
);
);
}
}
});
});
});
});


loadBlockFiles(count - 1, callback);
loadBlockFiles(count - 1, callback);
},
},
});
});
};
};


var totalTrialConfig = {
var totalTrialConfig = {
initialVal: 1,
initialVal: 1,
fontSize: 20,
fontSize: 20,
x: window.innerWidth / 2,
x: window.innerWidth / 2,
y: -window.innerHeight / 2,
y: -window.innerHeight / 2,
fontName: "Arial",
fontName: "Arial",
alignHoriz: "right",
alignHoriz: "right",
alignVert: "bottom",
alignVert: "bottom",
};
};
var totalTrial, // TextSim object
var totalTrial, // TextSim object
totalTrialIndex = totalTrialConfig.initialVal, // numerical value of totalTrialIndex
totalTrialIndex = totalTrialConfig.initialVal, // numerical value of totalTrialIndex
totalTrialCount = 0;
totalTrialCount = 0;


var totalBlockConfig = {
var totalBlockConfig = {
initialVal: 0,
initialVal: 0,
};
};
var totalBlockIndex = totalBlockConfig.initialVal,
var totalBlockIndex = totalBlockConfig.initialVal,
totalBlockTrialList = [],
totalBlockTrialList = [],
totalBlockCount = 0;
totalBlockCount = 0;


const experiment = (blockCount) => {
const experiment = (blockCount) => {
////
////
// Resources
// Resources
const _resources = [];
const _resources = [];
for (let i = 1; i <= blockCount; i++) {
for (let i = 1; i <= blockCount; i++) {
_resources.push({
_resources.push({
name: `conditions/block_${i}.csv`,
name: `conditions/block_${i}.csv`,
path: `conditions/block_${i}.csv`,
path: `conditions/block_${i}.csv`,
});
});
}
}
if (debug) console.log("fontsRequired: ", fontsRequired);
if (debug) console.log("fontsRequired: ", fontsRequired);


fontsRequired.forEach((fontFamily) => {
for (let i in fontsRequired) {
_resources.push({ name: fontFamily, path: fontPath });
if (debug) console.log(i, fontsRequired[i]);
});
_resources.push({ name: i, path: fontsRequired[i] });
}


// Start code blocks for 'Before Experiment'
// Start code blocks for 'Before Experiment'
// init psychoJS:
// init psychoJS:
const psychoJS = new PsychoJS({
const psychoJS = new PsychoJS({
debug: debug,
debug: debug,
});
});


/* ---------------------------------- Sound --------------------------------- */
/* ---------------------------------- Sound --------------------------------- */
const correctSynth = getCorrectSynth(psychoJS);
const correctSynth = getCorrectSynth(psychoJS);
const wrongSynth = getWrongSynth(psychoJS);
const wrongSynth = getWrongSynth(psychoJS);
const purrSynth = getPurrSynth(psychoJS);
const purrSynth = getPurrSynth(psychoJS);


// open window:
// open window:
psychoJS.openWindow({
psychoJS.openWindow({
fullscr: !debug,
fullscr: !debug,
color: new util.Color([0.9, 0.9, 0.9]),
color: new util.Color([0.9, 0.9, 0.9]),
units: "height", // TODO change to pix
units: "height", // TODO change to pix
waitBlanking: true,
waitBlanking: true,
});
});


// schedule the experiment:
// schedule the experiment:
psychoJS.schedule(
psychoJS.schedule(
psychoJS.gui.DlgFromDict({
psychoJS.gui.DlgFromDict({
dictionary: expInfo,
dictionary: expInfo,
title: expName,
title: expName,
})
})
);
);


const flowScheduler = new Scheduler(psychoJS);
const flowScheduler = new Scheduler(psychoJS);
const dialogCancelScheduler = new Scheduler(psychoJS);
const dialogCancelScheduler = new Scheduler(psychoJS);
psychoJS.scheduleCondition(
psychoJS.scheduleCondition(
function () {
function () {
return psychoJS.gui.dialogComponent.button === "OK";
return psychoJS.gui.dialogComponent.button === "OK";
},
},
flowScheduler,
flowScheduler,
dialogCancelScheduler
dialogCancelScheduler
);
);


// flowScheduler gets run if the participants presses OK
// flowScheduler gets run if the participants presses OK
flowScheduler.add(updateInfo); // add timeStamp
flowScheduler.add(updateInfo); // add timeStamp
flowScheduler.add(experimentInit);
flowScheduler.add(experimentInit);
flowScheduler.add(fileRoutineBegin());
flowScheduler.add(fileRoutineBegin());
flowScheduler.add(fileRoutineEachFrame());
flowScheduler.add(fileRoutineEachFrame());
flowScheduler.add(fileRoutineEnd());
flowScheduler.add(fileRoutineEnd());
// flowScheduler.add(initInstructionRoutineBegin());
// flowScheduler.add(initInstructionRoutineBegin());
// flowScheduler.add(initInstructionRoutineEachFrame());
// flowScheduler.add(initInstructionRoutineEachFrame());
// flowScheduler.add(initInstructionRoutineEnd());
// flowScheduler.add(initInstructionRoutineEnd());
const blocksLoopScheduler = new Scheduler(psychoJS);
const blocksLoopScheduler = new Scheduler(psychoJS);
flowScheduler.add(blocksLoopBegin(blocksLoopScheduler));
flowScheduler.add(blocksLoopBegin(blocksLoopScheduler));
flowScheduler.add(blocksLoopScheduler);
flowScheduler.add(blocksLoopScheduler);
flowScheduler.add(blocksLoopEnd);
flowScheduler.add(blocksLoopEnd);
flowScheduler.add(quitPsychoJS, "", true);
flowScheduler.add(quitPsychoJS, "", true);


// quit if user presses Cancel in dialog box:
// quit if user presses Cancel in dialog box:
dialogCancelScheduler.add(quitPsychoJS, "", false);
dialogCancelScheduler.add(quitPsychoJS, "", false);


if (useRC) {
if (useRC) {
expInfo["participant"] = rc.id.value;
expInfo["participant"] = rc.id.value;
}
}


if (debug) console.log("_resources: ", _resources);
if (debug) console.log("_resources: ", _resources);
psychoJS.start({
psychoJS.start({
expName: expName,
expName: expName,
expInfo: expInfo,
expInfo: expInfo,
resources: [
resources: [
{ name: "conditions/blockCount.csv", path: "conditions/blockCount.csv" },
{ name: "conditions/blockCount.csv", path: "conditions/blockCount.csv" },
..._resources,
..._resources,
],
],
});
});


psychoJS.experimentLogger.setLevel(core.Logger.ServerLevel.EXP);
psychoJS.experimentLogger.setLevel(core.Logger.ServerLevel.EXP);


var frameDur;
var frameDur;
async function updateInfo() {
async function updateInfo() {
expInfo["date"] = util.MonotonicClock.getDateStr(); // add a simple timestamp
expInfo["date"] = util.MonotonicClock.getDateStr(); // add a simple timestamp
expInfo["expName"] = expName;
expInfo["expName"] = expName;
expInfo["psychopyVersion"] = "2021.3.1";
expInfo["psychopyVersion"] = "2021.3.1";
expInfo["OS"] = rc.systemFamily.value;
expInfo["OS"] = rc.systemFamily.value;


// store frame rate of monitor if we can measure it successfully
// store frame rate of monitor if we can measure it successfully
expInfo["frameRate"] = psychoJS.window.getActualFrameRate();
expInfo["frameRate"] = psychoJS.window.getActualFrameRate();
if (typeof expInfo["frameRate"] !== "undefined")
if (typeof expInfo["frameRate"] !== "undefined")
frameDur = 1.0 / Math.round(expInfo["frameRate"]);
frameDur = 1.0 / Math.round(expInfo["frameRate"]);
else frameDur = 1.0 / 60.0; // couldn't get a reliable measure so guess
else frameDur = 1.0 / 60.0; // couldn't get a reliable measure so guess


// add info from the URL:
// add info from the URL:
util.addInfoFromUrl(expInfo);
util.addInfoFromUrl(expInfo);


return Scheduler.Event.NEXT;
return Scheduler.Event.NEXT;
}
}


var fileClock;
var fileClock;
var filterClock;
var filterClock;
var instructionsClock;
var instructionsClock;


var thisLoopNumber; // ! BLOCK COUNTER
var thisLoopNumber; // ! BLOCK COUNTER
var thisConditionsFile;
var thisConditionsFile;
var trialClock;
var trialClock;
// var targetBoundingPoly; // Target Bounding Box
// var targetBoundingPoly; // Target Bounding Box
var instructions;
var instructions;
var key_resp;
var key_resp;
var fixation; ////
var fixation; ////
var flanker1;
var flanker1;
var target;
var target;
var flanker2;
var flanker2;
var showAlphabet;
var showAlphabet;


var globalClock;
var globalClock;
var routineTimer;
var routineTimer;
async function experimentInit() {
async function experimentInit() {
// Initialize components for Routine "file"
// Initialize components for Routine "file"
fileClock = new util.Clock();
fileClock = new util.Clock();
// Initialize components for Routine "filter"
// Initialize components for Routine "filter"
filterClock = new util.Clock();
filterClock = new util.Clock();
instructionsClock = new util.Clock();
instructionsClock = new util.Clock();


thisLoopNumber = 0;
thisLoopNumber = 0;
thisConditionsFile = "./conditions/block_1.csv";
thisConditionsFile = "./conditions/block_1.csv";


// Initialize components for Routine "trial"
// Initialize components for Routine "trial"
trialClock = new util.Clock();
trialClock = new util.Clock();


// Target Bounding Box
// Target Bounding Box
// targetBoundingPoly = new visual.Rect ({
// targetBoundingPoly = new visual.Rect ({
// win: psychoJS.window, name: 'targetBoundingPoly', units : 'pix',
// win: psychoJS.window, name: 'targetBoundingPoly', units : 'pix',
// width: [1.0, 1.0][0], height: [1.0, 1.0][1],
// width: [1.0, 1.0][0], height: [1.0, 1.0][1],
// ori: 0.0, pos: [0, 0],
// ori: 0.0, pos: [0, 0],
// lineWidth: 1.0, lineColor: new util.Color('pink'),
// lineWidth: 1.0, lineColor: new util.Color('pink'),
// // fillColor: new util.Color('pink'),
// // fillColor: new util.Color('pink'),
// fillColor: undefined,
// fillColor: undefined,
// opacity: undefined, depth: -10, interpolate: true,
// opacity: undefined, depth: -10, interpolate: true,
// });
// });


key_resp = new core.Keyboard({
key_resp = new core.Keyboard({
psychoJS: psychoJS,
psychoJS: psychoJS,
clock: new util.Clock(),
clock: new util.Clock(),
waitForStart: true,
waitForStart: true,
});
});


fixation = new visual.TextStim({
fixation = new visual.TextStim({
win: psychoJS.window,
win: psychoJS.window,
name: "fixation",
name: "fixation",
text: "+",
text: "+",
font: "Open Sans",
font: "Open Sans",
units: "pix",
units: "pix",
pos: [0, 0],
pos: [0, 0],
height: 1.0,
height: 1.0,
wrapWidth: undefined,
wrapWidth: undefined,
ori: 0.0,
ori: 0.0,
color: new util.Color("black"),
color: new util.Color("black"),
opacity: undefined,
opacity: undefined,
depth: -6.0,
depth: -6.0,
});
});


flanker1 = new visual.TextStim({
flanker1 = new visual.TextStim({
win: psychoJS.window,
win: psychoJS.window,
name: "flanker1",
name: "flanker1",
text: "",
text: "",
font: "Arial",
font: "Arial",
units: "pix",
units: "pix",
pos: [0, 0],
pos: [0, 0],
height: 1.0,
height: 1.0,
wrapWidth: undefined,
wrapWidth: undefined,
ori: 0.0,
ori: 0.0,
color: new util.Color("black"),
color: new util.Color("black"),
opacity: 1.0,
opacity: 1.0,
depth: -7.0,
depth: -7.0,
});
});


target = new visual.TextStim({
target = new visual.TextStim({
win: psychoJS.window,
win: psychoJS.window,
name: "target",
name: "target",
text: "",
text: "",
font: "Arial",
font: "Arial",
units: "pix",
units: "pix",
pos: [0, 0],
pos: [0, 0],
height: 1.0,
height: 1.0,
wrapWidth: undefined,
wrapWidth: undefined,
ori: 0.0,
ori: 0.0,
color: new util.Color("black"),
color: new util.Color("black"),
opacity: 1.0,
opacity: 1.0,
depth: -8.0,
depth: -8.0,
});
});


flanker2 = new visual.TextStim({
flanker2 = new visual.TextStim({
win: psychoJS.window,
win: psychoJS.window,
name: "flanker2",
name: "flanker2",
text: "",
text: "",
font: "Arial",
font: "Arial",
units: "pix",
units: "pix",
pos: [0, 0],
pos: [0, 0],
height: 1.0,
height: 1.0,
wrapWidth: undefined,
wrapWidth: undefined,
ori: 0.0,
ori: 0.0,
color: new util.Color("black"),
color: new util.Color("black"),
opacity: 1.0,
opacity: 1.0,
depth: -9.0,
depth: -9.0,
});
});


showAlphabet = new visual.TextStim({
showAlphabet = new visual.TextStim({
win: psychoJS.window,
win: psychoJS.window,
name: "showAlphabet",
name: "showAlphabet",
text: "",
text: "",
font: "Arial",
font: "Arial",
units: "pix",
units: "pix",
pos: [0, 0],
pos: [0, 0],
height: 1.0,
height: 1.0,
wrapWidth: window.innerWidth,
wrapWidth: window.innerWidth,
ori: 0.0,
ori: 0.0,
color: new util.Color("black"),
color: new util.Color("black"),
opacity: 1.0,
opacity: 1.0,
depth: -5.0,
depth: -5.0,
});
});


totalTrial = new visual.TextStim({
totalTrial = new visual.TextStim({
win: psychoJS.window,
win: psychoJS.window,
name: "totalTrial",
name: "totalTrial",
text: "",
text: "",
font: totalTrialConfig.fontName,
font: totalTrialConfig.fontName,
units: "pix",
units: "pix",
pos: [totalTrialConfig.x, totalTrialConfig.y],
pos: [totalTrialConfig.x, totalTrialConfig.y],
alignHoriz: totalTrialConfig.alignHoriz,
alignHoriz: totalTrialConfig.alignHoriz,
alignVert: totalTrialConfig.alignVert,
alignVert: totalTrialConfig.alignVert,
height: 1.0,
height: 1.0,
wrapWidth: undefined,
wrapWidth: undefined,
ori: 0.0,
ori: 0.0,
color: new util.Color("black"),
color: new util.Color("black"),
opacity: 1.0,
opacity: 1.0,
depth: -20.0,
depth: -20.0,
});
});


instructions = new visual.TextStim({
instructions = new visual.TextStim({
win: psychoJS.window,
win: psychoJS.window,
name: "instructions",
name: "instructions",
text: "",
text: "",
font: "Arial",
font: "Arial",
units: "pix",
units: "pix",
pos: [-window.innerWidth * 0.4, window.innerHeight * 0.4],
pos: [-window.innerWidth * 0.4, window.innerHeight * 0.4],
height: 32.0,
height: 32.0,
wrapWidth: window.innerWidth * 0.8,
wrapWidth: window.innerWidth * 0.8,
ori: 0.0,
ori: 0.0,
color: new util.Color("black"),
color: new util.Color("black"),
opacity: 1.0,
opacity: 1.0,
depth: -12.0,
depth: -12.0,
alignHoriz: "left",
alignHoriz: "left",
alignVert: "top",
alignVert: "top",
});
});


// Create some handy timers
// Create some handy timers
globalClock = new util.Clock(); // to track the time since experiment started
globalClock = new util.Clock(); // to track the time since experiment started
routineTimer = new util.CountdownTimer(); // to track time remaining of each (non-slip) routine
routineTimer = new util.CountdownTimer(); // to track time remaining of each (non-slip) routine


return Scheduler.Event.NEXT;
return Scheduler.Event.NEXT;
}
}


var t;
var t;
var frameN;
var frameN;
var continueRoutine;
var continueRoutine;
var fileComponents;
var fileComponents;


var clickedContinue;
var clickedContinue;


// TODO Read from config
// TODO Read from config
var responseType = 2;
var responseType = 2;


function fileRoutineBegin(snapshot) {
function fileRoutineBegin(snapshot) {
return async function () {
return async function () {
TrialHandler.fromSnapshot(snapshot); // ensure that .thisN vals are up to date
TrialHandler.fromSnapshot(snapshot); // ensure that .thisN vals are up to date


//------Prepare to start Routine 'file'-------
//------Prepare to start Routine 'file'-------
t = 0;
t = 0;
fileClock.reset(); // clock
fileClock.reset(); // clock
frameN = -1;
frameN = -1;
continueRoutine = true; // until we're told otherwise
continueRoutine = true; // until we're told otherwise
// update component parameters for each repeat
// update component parameters for each repeat
// keep track of which components have finished
// keep track of which components have finished
fileComponents = [];
fileComponents = [];


for (const thisComponent of fileComponents)
for (const thisComponent of fileComponents)
if ("status" in thisComponent)
if ("status" in thisComponent)
thisComponent.status = PsychoJS.Status.NOT_STARTED;
thisComponent.status = PsychoJS.Status.NOT_STARTED;
return Scheduler.Event.NEXT;
return Scheduler.Event.NEXT;
};
};
}
}


function fileRoutineEachFrame() {
function fileRoutineEachFrame() {
return async function () {
return async function () {
//------Loop for each frame of Routine 'file'-------
//------Loop for each frame of Routine 'file'-------
// get current time
// get current time
t = fileClock.getTime();
t = fileClock.getTime();
frameN = frameN + 1; // number of completed frames (so 0 is the first frame)
frameN = frameN + 1; // number of completed frames (so 0 is the first frame)
// update/draw components on each frame
// update/draw components on each frame
// check for quit (typically the Esc key)
// check for quit (typically the Esc key)
if (
if (
psychoJS.experiment.experimentEnded ||
psychoJS.experiment.experimentEnded ||
psychoJS.eventManager.getKeys({ keyList: ["escape"] }).length > 0
psychoJS.eventManager.getKeys({ keyList: ["escape"] }).length > 0
) {
) {
return quitPsychoJS("The [Escape] key was pressed. Goodbye!", false);
return quitPsychoJS("The [Escape] key was pressed. Goodbye!", false);
}
}


// check if the Routine should terminate
// check if the Routine should terminate
if (!continueRoutine) {
if (!continueRoutine) {
// a component has requested a forced-end of Routine
// a component has requested a forced-end of Routine
return Scheduler.Event.NEXT;
return Scheduler.Event.NEXT;
}
}


continueRoutine = false; // reverts to True if at least one component still running
continueRoutine = false; // reverts to True if at least one component still running
for (const thisComponent of fileComponents)
for (const thisComponent of fileComponents)
if (
if (
"status" in thisComponent &&
"status" in thisComponent &&
thisComponent.status !== PsychoJS.Status.FINISHED
thisComponent.status !== PsychoJS.Status.FINISHED
) {
) {
continueRoutine = true;
continueRoutine = true;
break;
break;
}
}


// refresh the screen if continuing
// refresh the screen if continuing
if (continueRoutine) {
if (continueRoutine) {
return Scheduler.Event.FLIP_REPEAT;
return Scheduler.Event.FLIP_REPEAT;
} else {
} else {
return Scheduler.Event.NEXT;
return Scheduler.Event.NEXT;
}
}
};
};
}
}


function fileRoutineEnd() {
function fileRoutineEnd() {
return async function () {
return async function () {
//------Ending Routine 'file'-------
//------Ending Routine 'file'-------
for (const thisComponent of fileComponents) {
for (const thisComponent of fileComponents) {
if (typeof thisComponent.setAutoDraw === "function") {
if (typeof thisComponent.setAutoDraw === "function") {
thisComponent.setAutoDraw(false);
thisComponent.setAutoDraw(false);
}
}
}
}
// the Routine "file" was not non-slip safe, so reset the non-slip timer
// the Routine "file" was not non-slip safe, so reset the non-slip timer
routineTimer.reset();
routineTimer.reset();


return Scheduler.Event.NEXT;
return Scheduler.Event.NEXT;
};
};
}
}


var _beepButton;
var _beepButton;


function _instructionSetup(text) {
function _instructionSetup(text) {
t = 0;
t = 0;
instructionsClock.reset(); // clock
instructionsClock.reset(); // clock
frameN = -1;
frameN = -1;
continueRoutine = true;
continueRoutine = true;
instructions.setWrapWidth(window.innerWidth * 0.8);
instructions.setWrapWidth(window.innerWidth * 0.8);
instructions.setPos([-window.innerWidth * 0.4, window.innerHeight * 0.4]);
instructions.setPos([-window.innerWidth * 0.4, window.innerHeight * 0.4]);
instructions.setText(text);
instructions.setText(text);
instructions.setAutoDraw(true);
instructions.setAutoDraw(true);
}
}


function _clickContinue(e) {
function _clickContinue(e) {
if (e.target.id !== "threshold-beep-button") clickedContinue = true;
if (e.target.id !== "threshold-beep-button") clickedContinue = true;
}
}


async function _instructionRoutineEachFrame() {
async function _instructionRoutineEachFrame() {
t = instructionsClock.getTime();
t = instructionsClock.getTime();
frameN = frameN + 1;
frameN = frameN + 1;


if (
if (
psychoJS.experiment.experimentEnded ||
psychoJS.experiment.experimentEnded ||
psychoJS.eventManager.getKeys({ keyList: ["escape"] }).length > 0
psychoJS.eventManager.getKeys({ keyList: ["escape"] }).length > 0
) {
) {
document.removeEventListener("click", _clickContinue);
document.removeEventListener("click", _clickContinue);
document.removeEventListener("touchend", _clickContinue);
document.removeEventListener("touchend", _clickContinue);
removeBeepButton(_beepButton);
removeBeepButton(_beepButton);


return quitPsychoJS("The [Escape] key was pressed. Goodbye!", false);
return quitPsychoJS("The [Escape] key was pressed. Goodbye!", false);
}
}


if (!continueRoutine) {
if (!continueRoutine) {
return Scheduler.Event.NEXT;
return Scheduler.Event.NEXT;
}
}


continueRoutine = true;
continueRoutine = true;
if (psychoJS.eventManager.getKeys({ keyList: ["return"] }).length > 0) {
if (psychoJS.eventManager.getKeys({ keyList: ["return"] }).length > 0) {
continueRoutine = false;
continueRoutine = false;
}
}


if (continueRoutine && !clickedContinue) {
if (continueRoutine && !clickedContinue) {
return Scheduler.Event.FLIP_REPEAT;
return Scheduler.Event.FLIP_REPEAT;
} else {
} else {
return Scheduler.Event.NEXT;
return Scheduler.Event.NEXT;
}
}
}
}


async function _instructionRoutineEnd() {
async function _instructionRoutineEnd() {
instructions.setAutoDraw(false);
instructions.setAutoDraw(false);
routineTimer.reset();
routineTimer.reset();


document.removeEventListener("click", _clickContinue);
document.removeEventListener("click", _clickContinue);
document.removeEventListener("touchend", _clickContinue);
document.removeEventListener("touchend", _clickContinue);


return Scheduler.Event.NEXT;
return Scheduler.Event.NEXT;
}
}


var blocks;
var blocks;
var currentLoop;
var currentLoop;
function blocksLoopBegin(blocksLoopScheduler, snapshot) {
function blocksLoopBegin(blocksLoopScheduler, snapshot) {
return async function () {
return async function () {
TrialHandler.fromSnapshot(snapshot); // update internal variables (.thisN etc) of the loop
TrialHandler.fromSnapshot(snapshot); // update internal variables (.thisN etc) of the loop


// set up handler to look after randomisation of conditions etc
// set up handler to look after randomisation of conditions etc
blocks = new TrialHandler({
blocks = new TrialHandler({
psychoJS: psychoJS,
psychoJS: psychoJS,
nReps: 1,
nReps: 1,
method: TrialHandler.Method.SEQUENTIAL,
method: TrialHandler.Method.SEQUENTIAL,
extraInfo: expInfo,
extraInfo: expInfo,
originPath: undefined,
originPath: undefined,
trialList: "conditions/blockCount.csv",
trialList: "conditions/blockCount.csv",
seed: undefined,
seed: undefined,
name: "blocks",
name: "blocks",
});
});
psychoJS.experiment.addLoop(blocks); // add the loop to the experiment
psychoJS.experiment.addLoop(blocks); // add the loop to the experiment
currentLoop = blocks; // we're now the current loop
currentLoop = blocks; // we're now the current loop


// Schedule all the trials in the trialList:
// Schedule all the trials in the trialList:
for (const thisBlock of blocks) {
for (const thisBlock of blocks) {
const snapshot = blocks.getSnapshot();
const snapshot = blocks.getSnapshot();
blocksLoopScheduler.add(importConditions(snapshot));
blocksLoopScheduler.add(importConditions(snapshot));
blocksLoopScheduler.add(filterRoutineBegin(snapshot));
blocksLoopScheduler.add(filterRoutineBegin(snapshot));
blocksLoopScheduler.add(filterRoutineEachFrame());
blocksLoopScheduler.add(filterRoutineEachFrame());
blocksLoopScheduler.add(filterRoutineEnd());
blocksLoopScheduler.add(filterRoutineEnd());
blocksLoopScheduler.add(initInstructionRoutineBegin(snapshot));
blocksLoopScheduler.add(initInstructionRoutineBegin(snapshot));
blocksLoopScheduler.add(initInstructionRoutineEachFrame());
blocksLoopScheduler.add(initInstructionRoutineEachFrame());
blocksLoopScheduler.add(initInstructionRoutineEnd());
blocksLoopScheduler.add(initInstructionRoutineEnd());
const trialsLoopScheduler = new Scheduler(psychoJS);
const trialsLoopScheduler = new Scheduler(psychoJS);
blocksLoopScheduler.add(trialsLoopBegin(trialsLoopScheduler, snapshot));
blocksLoopScheduler.add(trialsLoopBegin(trialsLoopScheduler, snapshot));
blocksLoopScheduler.add(trialsLoopScheduler);
blocksLoopScheduler.add(trialsLoopScheduler);
blocksLoopScheduler.add(trialsLoopEnd);
blocksLoopScheduler.add(trialsLoopEnd);
blocksLoopScheduler.add(
blocksLoopScheduler.add(
endLoopIteration(blocksLoopScheduler, snapshot)
endLoopIteration(blocksLoopScheduler, snapshot)
);
);
}
}


return Scheduler.Event.NEXT;
return Scheduler.Event.NEXT;
};
};
}
}


var trialsConditions;
var trialsConditions;
var trials;
var trials;
function trialsLoopBegin(trialsLoopScheduler, snapshot) {
function trialsLoopBegin(trialsLoopScheduler, snapshot) {
return async function () {
return async function () {
// setup a MultiStairTrialHandler
// setup a MultiStairTrialHandler
trialsConditions = TrialHandler.importConditions(
trialsConditions = TrialHandler.importConditions(
psychoJS.serverManager,
psychoJS.serverManager,
thisConditionsFile
thisConditionsFile
);
);
trials = new data.MultiStairHandler({
trials = new data.MultiStairHandler({
stairType: MultiStairHandler.StaircaseType.QUEST,
stairType: MultiStairHandler.StaircaseType.QUEST,
psychoJS: psychoJS,
psychoJS: psychoJS,
name: "trials",
name: "trials",
varName: "trialsVal",
varName: "trialsVal",
nTrials: conditionTrials,
nTrials: conditionTrials,
conditions: trialsConditions,
conditions: trialsConditions,
method: TrialHandler.Method.FULLRANDOM,
method: TrialHandler.Method.FULLRANDOM,
});
});
psychoJS.experiment.addLoop(trials); // add the loop to the experiment
psychoJS.experiment.addLoop(trials); // add the loop to the experiment
currentLoop = trials; // we're now the current loop
currentLoop = trials; // we're now the current loop
// Schedule all the trials in the trialList:
// Schedule all the trials in the trialList:
for (const thisQuestLoop of trials) {
for (const thisQuestLoop of trials) {
const snapshot = trials.getSnapshot();
const snapshot = trials.getSnapshot();
trialsLoopScheduler.add(importConditions(snapshot));
trialsLoopScheduler.add(importConditions(snapshot));
trialsLoopScheduler.add(trialInstructionRoutineBegin(snapshot));
trialsLoopScheduler.add(trialInstructionRoutineBegin(snapshot));
trialsLoopScheduler.add(trialInstructionRoutineEachFrame());
trialsLoopScheduler.add(trialInstructionRoutineEachFrame());
trialsLoopScheduler.add(trialInstructionRoutineEnd());
trialsLoopScheduler.add(trialInstructionRoutineEnd());
trialsLoopScheduler.add(trialRoutineBegin(snapshot));
trialsLoopScheduler.add(trialRoutineBegin(snapshot));
trialsLoopScheduler.add(trialRoutineEachFrame());
trialsLoopScheduler.add(trialRoutineEachFrame());
trialsLoopScheduler.add(trialRoutineEnd());
trialsLoopScheduler.add(trialRoutineEnd());
trialsLoopScheduler.add(
trialsLoopScheduler.add(
endLoopIteration(trialsLoopScheduler, snapshot)
endLoopIteration(trialsLoopScheduler, snapshot)
);
);
}
}


return Scheduler.Event.NEXT;
return Scheduler.Event.NEXT;
};
};
}
}


async function trialsLoopEnd() {
async function trialsLoopEnd() {
psychoJS.experiment.addData(
psychoJS.experiment.addData(
"staircaseName",
"staircaseName",
currentLoop._currentStaircase._name
currentLoop._currentStaircase._name
);
);
psychoJS.experiment.addData(
psychoJS.experiment.addData(
"questMeanAtEndOfTrialsLoop",
"questMeanAtEndOfTrialsLoop",
currentLoop._currentStaircase.mean()
currentLoop._currentStaircase.mean()
);
);
psychoJS.experiment.addData(
psychoJS.experiment.addData(
"questSDAtEndOfTrialsLoop",
"questSDAtEndOfTrialsLoop",
currentLoop._currentStaircase.sd()
currentLoop._currentStaircase.sd()
);
);
psychoJS.experiment.addData(
psychoJS.experiment.addData(
"questQuantileOfQuantileOrderAtEndOfTrialsLoop",
"questQuantileOfQuantileOrderAtEndOfTrialsLoop",
currentLoop._currentStaircase.quantile(
currentLoop._currentStaircase.quantile(
currentLoop._currentStaircase._jsQuest.quantileOrder
currentLoop._currentStaircase._jsQuest.quantileOrder
)
)
);
);
// terminate loop
// terminate loop
psychoJS.experiment.removeLoop(trials);
psychoJS.experiment.removeLoop(trials);
return Scheduler.Event.NEXT;
return Scheduler.Event.NEXT;
}
}


async function blocksLoopEnd() {
async function blocksLoopEnd() {
psychoJS.experiment.removeLoop(blocks);
psychoJS.experiment.removeLoop(blocks);


return Scheduler.Event.NEXT;
return Scheduler.Event.NEXT;
}
}


var filterComponents;
var filterComponents;
function filterRoutineBegin(snapshot) {
function filterRoutineBegin(snapshot) {
return async function () {
return async function () {
TrialHandler.fromSnapshot(snapshot); // ensure that .thisN vals are up to date
TrialHandler.fromSnapshot(snapshot); // ensure that .thisN vals are up to date


//------Prepare to start Routine 'filter'-------
//------Prepare to start Routine 'filter'-------
t = 0;
t = 0;
filterClock.reset(); // clock
filterClock.reset(); // clock
frameN = -1;
frameN = -1;
continueRoutine = true; // until we're told otherwise
continueRoutine = true; // until we're told otherwise
// update component parameters for each repeat
// update component parameters for each repeat
thisLoopNumber += 1;
thisLoopNumber += 1;
thisConditionsFile = `conditions/block_${thisLoopNumber}.csv`;
thisConditionsFile = `conditions/block_${thisLoopNumber}.csv`;


const possibleTrials = [];
const possibleTrials = [];
const thisBlockFileData = blockFiles[thisLoopNumber];
const thisBlockFileData = blockFiles[thisLoopNumber];
if (debug) console.log("thisBlockFileData: ", thisBlockFileData);
if (debug) console.log("thisBlockFileData: ", thisBlockFileData);


for (let rowKey in thisBlockFileData) {
for (let rowKey in thisBlockFileData) {
let rowIndex = parseInt(rowKey);
let rowIndex = parseInt(rowKey);
if (Object.keys(thisBlockFileData[rowIndex]).length > 1) {
if (Object.keys(thisBlockFileData[rowIndex]).length > 1) {
if (debug)
if (debug)
console.log(
console.log(
"condition trials this row of block: ",
"condition trials this row of block: ",
parseInt(thisBlockFileData[rowIndex]["conditionTrials"])
parseInt(thisBlockFileData[rowIndex]["conditionTrials"])
);
);
possibleTrials.push(
possibleTrials.push(
parseInt(thisBlockFileData[rowIndex]["conditionTrials"])
parseInt(thisBlockFileData[rowIndex]["conditionTrials"])
);
);
}
}
}
}
if (debug) console.log("possibleTrials: ", possibleTrials);
if (debug) console.log("possibleTrials: ", possibleTrials);
totalTrialCount = possibleTrials.reduce((a, b) => a + b, 0); // sum of possible trials
totalTrialCount = possibleTrials.reduce((a, b) => a + b, 0); // sum of possible trials
totalBlockCount = Object.keys(blockFiles).length;
totalBlockCount = Object.keys(blockFiles).length;
totalBlockTrialList = [...possibleTrials];
totalBlockTrialList = [...possibleTrials];
// console.log('totalBlockTrialList', totalBlockTrialList)
// console.log('totalBlockTrialList', totalBlockTrialList)
// totalBlockCount = blockFiles.length;
// totalBlockCount = blockFiles.length;


// TODO Remove this constraint to allow different # of trials for each condition
// TODO Remove this constraint to allow different # of trials for each condition
if (!possibleTrials.every((a) => a === possibleTrials[0]))
if (!possibleTrials.every((a) => a === possibleTrials[0]))
throw "Number of trials for each condition within one block has to be equal. (Will be updated soon.)";
throw "Number of trials for each condition within one block has to be equal. (Will be updated soon.)";


conditionTrials = possibleTrials[0];
conditionTrials = possibleTrials[0];


// keep track of which components have finished
// keep track of which components have finished
filterComponents = [];
filterComponents = [];


for (const thisComponent of filterComponents)
for (const thisComponent of filterComponents)
if ("status" in thisComponent)
if ("status" in thisComponent)
thisComponent.status = PsychoJS.Status.NOT_STARTED;
thisComponent.status = PsychoJS.Status.NOT_STARTED;
return Scheduler.Event.NEXT;
return Scheduler.Event.NEXT;
};
};
}
}


function filterRoutineEachFrame() {
function filterRoutineEachFrame() {
return async function () {
return async function () {
//------Loop for each frame of Routine 'filter'-------
//------Loop for each frame of Routine 'filter'-------
// get current time
// get current time
t = filterClock.getTime();
t = filterClock.getTime();
frameN = frameN + 1; // number of completed frames (so 0 is the first frame)
frameN = frameN + 1; // number of completed frames (so 0 is the first frame)
// update/draw components on each frame
// update/draw components on each frame
// check for quit (typically the Esc key)
// check for quit (typically the Esc key)
if (
if (
psychoJS.experiment.experimentEnded ||
psychoJS.experiment.experimentEnded ||
psychoJS.eventManager.getKeys({ keyList: ["escape"] }).length > 0
psychoJS.eventManager.getKeys({ keyList: ["escape"] }).length > 0
) {
) {
return quitPsychoJS("The [Escape] key was pressed. Goodbye!", false);
return quitPsychoJS("The [Escape] key was pressed. Goodbye!", false);
}
}


// check if the Routine should terminate
// check if the Routine should terminate
if (!continueRoutine) {
if (!continueRoutine) {
// a component has requested a forced-end of Routine
// a component has requested a forced-end of Routine
return Scheduler.Event.NEXT;
return Scheduler.Event.NEXT;
}
}


continueRoutine = false; // reverts to True if at least one component still running
continueRoutine = false; // reverts to True if at least one component still running
for (const thisComponent of filterComponents)
for (const thisComponent of filterComponents)
if (
if (
"status" in thisComponent &&
"status" in thisComponent &&
thisComponent.status !== PsychoJS.Status.FINISHED
thisComponent.status !== PsychoJS.Status.FINISHED
) {
) {
continueRoutine = true;
continueRoutine = true;
break;
break;
}
}


// refresh the screen if continuing
// refresh the screen if continuing
if (continueRoutine) {
if (continueRoutine) {
return Scheduler.Event.FLIP_REPEAT;
return Scheduler.Event.FLIP_REPEAT;
} else {
} else {
return Scheduler.Event.NEXT;
return Scheduler.Event.NEXT;
}
}
};
};
}
}


function filterRoutineEnd() {
function filterRoutineEnd() {
return async function () {
return async function () {
//------Ending Routine 'filter'-------
//------Ending Routine 'filter'-------
for (const thisComponent of filterComponents) {
for (const thisComponent of filterComponents) {
if (typeof thisComponent.setAutoDraw === "function") {
if (typeof thisComponent.setAutoDraw === "function") {
thisComponent.setAutoDraw(false);
thisComponent.setAutoDraw(false);
}
}
}
}
// the Routine "filter" was not non-slip safe, so reset the non-slip timer
// the Routine "filter" was not non-slip safe, so reset the non-slip timer
routineTimer.reset();
routineTimer.reset();


return Scheduler.Event.NEXT;
return Scheduler.Event.NEXT;
};
};
}
}


function initInstructionRoutineBegin(snapshot) {
function initInstructionRoutineBegin(snapshot) {
return async function () {
return async function () {
TrialHandler.fromSnapshot(snapshot);
TrialHandler.fromSnapshot(snapshot);
_instructionSetup(
_instructionSetup(
instructionsText.initial(expInfo.participant) +
instructionsText.initial(expInfo.participant) +
instructionsText.initialByThresholdParameter["spacing"](
instructionsText.initialByThresholdParameter["spacing"](
responseType,
responseType,
totalTrialCount
totalTrialCount
) +
) +
instructionsText.initialEnd(responseType)
instructionsText.initialEnd(responseType)
);
);


clickedContinue = false;
clickedContinue = false;
document.addEventListener("click", _clickContinue);
document.addEventListener("click", _clickContinue);
document.addEventListener("touchend", _clickContinue);
document.addEventListener("touchend", _clickContinue);


_beepButton = addBeepButton(correctSynth);
_beepButton = addBeepButton(correctSynth);


psychoJS.eventManager.clearKeys();
psychoJS.eventManager.clearKeys();


return Scheduler.Event.NEXT;
return Scheduler.Event.NEXT;
};
};
}
}


function initInstructionRoutineEachFrame() {
function initInstructionRoutineEachFrame() {
return _instructionRoutineEachFrame;
return _instructionRoutineEachFrame;
}
}


function initInstructionRoutineEnd() {
function initInstructionRoutineEnd() {
return async function () {
return async function () {
instructions.setAutoDraw(false);
instructions.setAutoDraw(false);
routineTimer.reset();
routineTimer.reset();


document.removeEventListener("click", _clickContinue);
document.removeEventListener("click", _clickC
document.removeEventListener("touchend", _clickContinue);

removeBeepButton(_beepButton);

return Scheduler.Event.NEXT;
};
}

function blockInstructionRoutineBegin(snapshot) {
return async function () {
TrialHandler.fromSnapshot(snapshot);
_instructionSetup(instructionsText.block(snapshot.b