arabic_demo

Created Diff never expires
20 removals
Lines
Total
Removed
Words
Total
Removed
To continue using this feature, upgrade to
Diffchecker logo
Diffchecker Pro
843 lines
18 additions
Lines
Total
Added
Words
Total
Added
To continue using this feature, upgrade to
Diffchecker logo
Diffchecker Pro
840 lines
/*****************
/*****************
* 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