/*************** * N_Back * ***************/ import { core, data, sound, util, visual, hardware } from './lib/psychojs-2023.2.2.js'; const { PsychoJS } = core; const { TrialHandler, MultiStairHandler } = data; const { Scheduler } = util; //some handy aliases as in the psychopy scripts; const { abs, sin, cos, PI: pi, sqrt } = Math; const { round } = util; // store info about the experiment session: let expName = 'n_back'; // from the Builder filename that created this script let expInfo = { 'participant': '', 'n_back': ["2", "3", "4", "5"], 'session': '001', }; // Start code blocks for 'Before Experiment' // init psychoJS: const psychoJS = new PsychoJS({ debug: true }); // open window: psychoJS.openWindow({ fullscr: true, color: new util.Color([(- 1.0), (- 1.0), (- 1.0)]), units: 'height', waitBlanking: true, backgroundImage: '', backgroundFit: 'none', }); // schedule the experiment: psychoJS.schedule(psychoJS.gui.DlgFromDict({ dictionary: expInfo, title: expName })); const flowScheduler = new Scheduler(psychoJS); const dialogCancelScheduler = new Scheduler(psychoJS); psychoJS.scheduleCondition(function() { return (psychoJS.gui.dialogComponent.button === 'OK'); }, flowScheduler, dialogCancelScheduler); // flowScheduler gets run if the participants presses OK flowScheduler.add(updateInfo); // add timeStamp flowScheduler.add(experimentInit); flowScheduler.add(instructionsRoutineBegin()); flowScheduler.add(instructionsRoutineEachFrame()); flowScheduler.add(instructionsRoutineEnd()); const trialsLoopScheduler = new Scheduler(psychoJS); flowScheduler.add(trialsLoopBegin(trialsLoopScheduler)); flowScheduler.add(trialsLoopScheduler); flowScheduler.add(trialsLoopEnd); flowScheduler.add(endRoutineBegin()); flowScheduler.add(endRoutineEachFrame()); flowScheduler.add(endRoutineEnd()); flowScheduler.add(quitPsychoJS, '', true); // quit if user presses Cancel in dialog box: dialogCancelScheduler.add(quitPsychoJS, '', false); psychoJS.start({ expName: expName, expInfo: expInfo, resources: [ // resources: {'name': 'nback_spreadsheets/2_back.xlsx', 'path': 'nback_spreadsheets/2_back.xlsx'}, {'name': 'nback_spreadsheets/3_back.xlsx', 'path': 'nback_spreadsheets/3_back.xlsx'}, {'name': 'nback_spreadsheets/4_back.xlsx', 'path': 'nback_spreadsheets/4_back.xlsx'}, {'name': 'nback_spreadsheets/5_back.xlsx', 'path': 'nback_spreadsheets/5_back.xlsx'}, ] }); psychoJS.experimentLogger.setLevel(core.Logger.ServerLevel.EXP); var currentLoop; var frameDur; async function updateInfo() { currentLoop = psychoJS.experiment; // right now there are no loops expInfo['date'] = util.MonotonicClock.getDateStr(); // add a simple timestamp expInfo['expName'] = expName; expInfo['psychopyVersion'] = '2023.2.2'; expInfo['OS'] = window.navigator.platform; // store frame rate of monitor if we can measure it successfully expInfo['frameRate'] = psychoJS.window.getActualFrameRate(); if (typeof expInfo['frameRate'] !== 'undefined') frameDur = 1.0 / Math.round(expInfo['frameRate']); else frameDur = 1.0 / 60.0; // couldn't get a reliable measure so guess // add info from the URL: util.addInfoFromUrl(expInfo); psychoJS.experiment.dataFileName = (("." + "/") + `data/${expInfo["participant"]}_${expName}_${expInfo["date"]}`); psychoJS.experiment.field_separator = '\t'; return Scheduler.Event.NEXT; } var instructionsClock; var instructionsTxt; var startClick; var fixationClock; var ISI_list; var fix; var trial_counter; var trialClock; var stimulus; var mouse; var correct_counter; var trial_counter_2; var endClock; var endMsg; var globalClock; var routineTimer; async function experimentInit() { // Initialize components for Routine "instructions" instructionsClock = new util.Clock(); instructionsTxt = new visual.TextStim({ win: psychoJS.window, name: 'instructionsTxt', text: '', font: 'Arial', units: undefined, pos: [0, 0], height: 0.05, wrapWidth: undefined, ori: 0, languageStyle: 'LTR', color: new util.Color('white'), opacity: 1, depth: 0.0 }); startClick = new core.Mouse({ win: psychoJS.window, }); startClick.mouseClock = new util.Clock(); // Initialize components for Routine "fixation" fixationClock = new util.Clock(); // Run 'Begin Experiment' code from ISI_code ISI_list = [0.5, 0.75, 1, 1.25, 1.5]; fix = new visual.TextStim({ win: psychoJS.window, name: 'fix', text: '+', font: 'Open Sans', units: undefined, pos: [0, 0], height: 0.05, wrapWidth: undefined, ori: 0.0, languageStyle: 'LTR', color: new util.Color('white'), opacity: undefined, depth: -1.0 }); trial_counter = new visual.TextStim({ win: psychoJS.window, name: 'trial_counter', text: '', font: 'Arial', units: undefined, pos: [0, (- 0.4)], height: 0.05, wrapWidth: undefined, ori: 0.0, languageStyle: 'LTR', color: new util.Color('white'), opacity: undefined, depth: -2.0 }); // Initialize components for Routine "trial" trialClock = new util.Clock(); stimulus = new visual.TextStim({ win: psychoJS.window, name: 'stimulus', text: '', font: 'Open Sans', units: undefined, pos: [0, 0], height: 0.05, wrapWidth: undefined, ori: 0.0, languageStyle: 'LTR', color: new util.Color('white'), opacity: undefined, depth: 0.0 }); mouse = new core.Mouse({ win: psychoJS.window, }); mouse.mouseClock = new util.Clock(); // Run 'Begin Experiment' code from saveCorrect correct_counter = 0; trial_counter_2 = new visual.TextStim({ win: psychoJS.window, name: 'trial_counter_2', text: '', font: 'Arial', units: undefined, pos: [0, (- 0.4)], height: 0.05, wrapWidth: undefined, ori: 0.0, languageStyle: 'LTR', color: new util.Color('white'), opacity: undefined, depth: -3.0 }); // Initialize components for Routine "end" endClock = new util.Clock(); endMsg = new visual.TextStim({ win: psychoJS.window, name: 'endMsg', text: '', font: 'Arial', units: undefined, pos: [0, 0], height: 0.05, wrapWidth: undefined, ori: 0, languageStyle: 'LTR', color: new util.Color('white'), opacity: 1, depth: 0.0 }); // Create some handy timers globalClock = new util.Clock(); // to track the time since experiment started routineTimer = new util.CountdownTimer(); // to track time remaining of each (non-slip) routine return Scheduler.Event.NEXT; } var t; var frameN; var continueRoutine; var gotValidClick; var instructionsComponents; function instructionsRoutineBegin(snapshot) { return async function () { TrialHandler.fromSnapshot(snapshot); // ensure that .thisN vals are up to date //--- Prepare to start Routine 'instructions' --- t = 0; instructionsClock.reset(); // clock frameN = -1; continueRoutine = true; // until we're told otherwise // update component parameters for each repeat psychoJS.experiment.addData('instructions.started', globalClock.getTime()); instructionsTxt.setText((("This is a demo of a basic n-back task. Click the mouse/tap the screen if the number you see is the same as " + expInfo["n_back"]) + " prior to it. Click anywhere to begin.")); // setup some python lists for storing info about the startClick gotValidClick = false; // until a click is received // keep track of which components have finished instructionsComponents = []; instructionsComponents.push(instructionsTxt); instructionsComponents.push(startClick); for (const thisComponent of instructionsComponents) if ('status' in thisComponent) thisComponent.status = PsychoJS.Status.NOT_STARTED; return Scheduler.Event.NEXT; } } var prevButtonState; var _mouseButtons; function instructionsRoutineEachFrame() { return async function () { //--- Loop for each frame of Routine 'instructions' --- // get current time t = instructionsClock.getTime(); frameN = frameN + 1;// number of completed frames (so 0 is the first frame) // update/draw components on each frame // *instructionsTxt* updates if (t >= 0.0 && instructionsTxt.status === PsychoJS.Status.NOT_STARTED) { // keep track of start time/frame for later instructionsTxt.tStart = t; // (not accounting for frame time here) instructionsTxt.frameNStart = frameN; // exact frame index instructionsTxt.setAutoDraw(true); } // *startClick* updates if (t >= 0.0 && startClick.status === PsychoJS.Status.NOT_STARTED) { // keep track of start time/frame for later startClick.tStart = t; // (not accounting for frame time here) startClick.frameNStart = frameN; // exact frame index startClick.status = PsychoJS.Status.STARTED; startClick.mouseClock.reset(); prevButtonState = startClick.getPressed(); // if button is down already this ISN'T a new click } if (startClick.status === PsychoJS.Status.STARTED) { // only update if started and not finished! _mouseButtons = startClick.getPressed(); if (!_mouseButtons.every( (e,i,) => (e == prevButtonState[i]) )) { // button state changed? prevButtonState = _mouseButtons; if (_mouseButtons.reduce( (e, acc) => (e+acc) ) > 0) { // state changed to a new click // end routine on response continueRoutine = false; } } } // check for quit (typically the Esc key) if (psychoJS.experiment.experimentEnded || psychoJS.eventManager.getKeys({keyList:['escape']}).length > 0) { return quitPsychoJS('The [Escape] key was pressed. Goodbye!', false); } // check if the Routine should terminate if (!continueRoutine) { // a component has requested a forced-end of Routine return Scheduler.Event.NEXT; } continueRoutine = false; // reverts to True if at least one component still running for (const thisComponent of instructionsComponents) if ('status' in thisComponent && thisComponent.status !== PsychoJS.Status.FINISHED) { continueRoutine = true; break; } // refresh the screen if continuing if (continueRoutine) { return Scheduler.Event.FLIP_REPEAT; } else { return Scheduler.Event.NEXT; } }; } var _mouseXYs; function instructionsRoutineEnd(snapshot) { return async function () { //--- Ending Routine 'instructions' --- for (const thisComponent of instructionsComponents) { if (typeof thisComponent.setAutoDraw === 'function') { thisComponent.setAutoDraw(false); } } psychoJS.experiment.addData('instructions.stopped', globalClock.getTime()); // store data for psychoJS.experiment (ExperimentHandler) _mouseXYs = startClick.getPos(); _mouseButtons = startClick.getPressed(); psychoJS.experiment.addData('startClick.x', _mouseXYs[0]); psychoJS.experiment.addData('startClick.y', _mouseXYs[1]); psychoJS.experiment.addData('startClick.leftButton', _mouseButtons[0]); psychoJS.experiment.addData('startClick.midButton', _mouseButtons[1]); psychoJS.experiment.addData('startClick.rightButton', _mouseButtons[2]); // the Routine "instructions" was not non-slip safe, so reset the non-slip timer routineTimer.reset(); // Routines running outside a loop should always advance the datafile row if (currentLoop === psychoJS.experiment) { psychoJS.experiment.nextEntry(snapshot); } return Scheduler.Event.NEXT; } } var trials; function trialsLoopBegin(trialsLoopScheduler, snapshot) { return async function() { TrialHandler.fromSnapshot(snapshot); // update internal variables (.thisN etc) of the loop // set up handler to look after randomisation of conditions etc trials = new TrialHandler({ psychoJS: psychoJS, nReps: 1, method: TrialHandler.Method.SEQUENTIAL, extraInfo: expInfo, originPath: undefined, trialList: (("nback_spreadsheets/" + expInfo["n_back"]) + "_back.xlsx"), seed: undefined, name: 'trials' }); psychoJS.experiment.addLoop(trials); // add the loop to the experiment currentLoop = trials; // we're now the current loop // Schedule all the trials in the trialList: for (const thisTrial of trials) { snapshot = trials.getSnapshot(); trialsLoopScheduler.add(importConditions(snapshot)); trialsLoopScheduler.add(fixationRoutineBegin(snapshot)); trialsLoopScheduler.add(fixationRoutineEachFrame()); trialsLoopScheduler.add(fixationRoutineEnd(snapshot)); trialsLoopScheduler.add(trialRoutineBegin(snapshot)); trialsLoopScheduler.add(trialRoutineEachFrame()); trialsLoopScheduler.add(trialRoutineEnd(snapshot)); trialsLoopScheduler.add(trialsLoopEndIteration(trialsLoopScheduler, snapshot)); } return Scheduler.Event.NEXT; } } async function trialsLoopEnd() { // terminate loop psychoJS.experiment.removeLoop(trials); // update the current loop from the ExperimentHandler if (psychoJS.experiment._unfinishedLoops.length>0) currentLoop = psychoJS.experiment._unfinishedLoops.at(-1); else currentLoop = psychoJS.experiment; // so we use addData from the experiment return Scheduler.Event.NEXT; } function trialsLoopEndIteration(scheduler, snapshot) { // ------Prepare for next entry------ return async function () { if (typeof snapshot !== 'undefined') { // ------Check if user ended loop early------ if (snapshot.finished) { // Check for and save orphaned data if (psychoJS.experiment.isEntryEmpty()) { psychoJS.experiment.nextEntry(snapshot); } scheduler.stop(); } else { psychoJS.experiment.nextEntry(snapshot); } return Scheduler.Event.NEXT; } }; } var thisISI; var fixationComponents; function fixationRoutineBegin(snapshot) { return async function () { TrialHandler.fromSnapshot(snapshot); // ensure that .thisN vals are up to date //--- Prepare to start Routine 'fixation' --- t = 0; fixationClock.reset(); // clock frameN = -1; continueRoutine = true; // until we're told otherwise // update component parameters for each repeat psychoJS.experiment.addData('fixation.started', globalClock.getTime()); // Run 'Begin Routine' code from ISI_code util.shuffle(ISI_list); thisISI = ISI_list[0]; psychoJS.experiment.addData("thisISI", thisISI); trial_counter.setText((((trials.thisN + 1).toString() + "/") + trials.nTotal.toString())); // keep track of which components have finished fixationComponents = []; fixationComponents.push(fix); fixationComponents.push(trial_counter); for (const thisComponent of fixationComponents) if ('status' in thisComponent) thisComponent.status = PsychoJS.Status.NOT_STARTED; return Scheduler.Event.NEXT; } } var frameRemains; function fixationRoutineEachFrame() { return async function () { //--- Loop for each frame of Routine 'fixation' --- // get current time t = fixationClock.getTime(); frameN = frameN + 1;// number of completed frames (so 0 is the first frame) // update/draw components on each frame // *fix* updates if (t >= 0.0 && fix.status === PsychoJS.Status.NOT_STARTED) { // keep track of start time/frame for later fix.tStart = t; // (not accounting for frame time here) fix.frameNStart = frameN; // exact frame index fix.setAutoDraw(true); } frameRemains = 0.0 + thisISI - psychoJS.window.monitorFramePeriod * 0.75; // most of one frame period left if (fix.status === PsychoJS.Status.STARTED && t >= frameRemains) { fix.setAutoDraw(false); } // *trial_counter* updates if (t >= 0.0 && trial_counter.status === PsychoJS.Status.NOT_STARTED) { // keep track of start time/frame for later trial_counter.tStart = t; // (not accounting for frame time here) trial_counter.frameNStart = frameN; // exact frame index trial_counter.setAutoDraw(true); } frameRemains = 0.0 + thisISI - psychoJS.window.monitorFramePeriod * 0.75; // most of one frame period left if (trial_counter.status === PsychoJS.Status.STARTED && t >= frameRemains) { trial_counter.setAutoDraw(false); } // check for quit (typically the Esc key) if (psychoJS.experiment.experimentEnded || psychoJS.eventManager.getKeys({keyList:['escape']}).length > 0) { return quitPsychoJS('The [Escape] key was pressed. Goodbye!', false); } // check if the Routine should terminate if (!continueRoutine) { // a component has requested a forced-end of Routine return Scheduler.Event.NEXT; } continueRoutine = false; // reverts to True if at least one component still running for (const thisComponent of fixationComponents) if ('status' in thisComponent && thisComponent.status !== PsychoJS.Status.FINISHED) { continueRoutine = true; break; } // refresh the screen if continuing if (continueRoutine) { return Scheduler.Event.FLIP_REPEAT; } else { return Scheduler.Event.NEXT; } }; } function fixationRoutineEnd(snapshot) { return async function () { //--- Ending Routine 'fixation' --- for (const thisComponent of fixationComponents) { if (typeof thisComponent.setAutoDraw === 'function') { thisComponent.setAutoDraw(false); } } psychoJS.experiment.addData('fixation.stopped', globalClock.getTime()); // the Routine "fixation" was not non-slip safe, so reset the non-slip timer routineTimer.reset(); // Routines running outside a loop should always advance the datafile row if (currentLoop === psychoJS.experiment) { psychoJS.experiment.nextEntry(snapshot); } return Scheduler.Event.NEXT; } } var correct; var trialComponents; function trialRoutineBegin(snapshot) { return async function () { TrialHandler.fromSnapshot(snapshot); // ensure that .thisN vals are up to date //--- Prepare to start Routine 'trial' --- t = 0; trialClock.reset(); // clock frameN = -1; continueRoutine = true; // until we're told otherwise routineTimer.add(1.000000); // update component parameters for each repeat psychoJS.experiment.addData('trial.started', globalClock.getTime()); stimulus.setText(numbers); // setup some python lists for storing info about the mouse // current position of the mouse: mouse.x = []; mouse.y = []; mouse.leftButton = []; mouse.midButton = []; mouse.rightButton = []; mouse.time = []; gotValidClick = false; // until a click is received // Run 'Begin Routine' code from saveCorrect correct = 0; trial_counter_2.setText((((trials.thisN + 1).toString() + "/") + trials.nTotal.toString())); // keep track of which components have finished trialComponents = []; trialComponents.push(stimulus); trialComponents.push(mouse); trialComponents.push(trial_counter_2); for (const thisComponent of trialComponents) if ('status' in thisComponent) thisComponent.status = PsychoJS.Status.NOT_STARTED; return Scheduler.Event.NEXT; } } function trialRoutineEachFrame() { return async function () { //--- Loop for each frame of Routine 'trial' --- // get current time t = trialClock.getTime(); frameN = frameN + 1;// number of completed frames (so 0 is the first frame) // update/draw components on each frame // *stimulus* updates if (t >= 0.0 && stimulus.status === PsychoJS.Status.NOT_STARTED) { // keep track of start time/frame for later stimulus.tStart = t; // (not accounting for frame time here) stimulus.frameNStart = frameN; // exact frame index stimulus.setAutoDraw(true); } frameRemains = 0.0 + 1 - psychoJS.window.monitorFramePeriod * 0.75; // most of one frame period left if (stimulus.status === PsychoJS.Status.STARTED && t >= frameRemains) { stimulus.setAutoDraw(false); } // *mouse* updates if (t >= 0.0 && mouse.status === PsychoJS.Status.NOT_STARTED) { // keep track of start time/frame for later mouse.tStart = t; // (not accounting for frame time here) mouse.frameNStart = frameN; // exact frame index mouse.status = PsychoJS.Status.STARTED; mouse.mouseClock.reset(); prevButtonState = mouse.getPressed(); // if button is down already this ISN'T a new click } frameRemains = 0.0 + 1 - psychoJS.window.monitorFramePeriod * 0.75; // most of one frame period left if (mouse.status === PsychoJS.Status.STARTED && t >= frameRemains) { mouse.status = PsychoJS.Status.FINISHED; } if (mouse.status === PsychoJS.Status.STARTED) { // only update if started and not finished! _mouseButtons = mouse.getPressed(); if (!_mouseButtons.every( (e,i,) => (e == prevButtonState[i]) )) { // button state changed? prevButtonState = _mouseButtons; if (_mouseButtons.reduce( (e, acc) => (e+acc) ) > 0) { // state changed to a new click _mouseXYs = mouse.getPos(); mouse.x.push(_mouseXYs[0]); mouse.y.push(_mouseXYs[1]); mouse.leftButton.push(_mouseButtons[0]); mouse.midButton.push(_mouseButtons[1]); mouse.rightButton.push(_mouseButtons[2]); mouse.time.push(mouse.mouseClock.getTime()); } } } // *trial_counter_2* updates if (t >= 0.0 && trial_counter_2.status === PsychoJS.Status.NOT_STARTED) { // keep track of start time/frame for later trial_counter_2.tStart = t; // (not accounting for frame time here) trial_counter_2.frameNStart = frameN; // exact frame index trial_counter_2.setAutoDraw(true); } frameRemains = 0.0 + 1 - psychoJS.window.monitorFramePeriod * 0.75; // most of one frame period left if (trial_counter_2.status === PsychoJS.Status.STARTED && t >= frameRemains) { trial_counter_2.setAutoDraw(false); } // check for quit (typically the Esc key) if (psychoJS.experiment.experimentEnded || psychoJS.eventManager.getKeys({keyList:['escape']}).length > 0) { return quitPsychoJS('The [Escape] key was pressed. Goodbye!', false); } // check if the Routine should terminate if (!continueRoutine) { // a component has requested a forced-end of Routine return Scheduler.Event.NEXT; } continueRoutine = false; // reverts to True if at least one component still running for (const thisComponent of trialComponents) if ('status' in thisComponent && thisComponent.status !== PsychoJS.Status.FINISHED) { continueRoutine = true; break; } // refresh the screen if continuing if (continueRoutine && routineTimer.getTime() > 0) { return Scheduler.Event.FLIP_REPEAT; } else { return Scheduler.Event.NEXT; } }; } function trialRoutineEnd(snapshot) { return async function () { //--- Ending Routine 'trial' --- for (const thisComponent of trialComponents) { if (typeof thisComponent.setAutoDraw === 'function') { thisComponent.setAutoDraw(false); } } psychoJS.experiment.addData('trial.stopped', globalClock.getTime()); // store data for psychoJS.experiment (ExperimentHandler) psychoJS.experiment.addData('mouse.x', mouse.x); psychoJS.experiment.addData('mouse.y', mouse.y); psychoJS.experiment.addData('mouse.leftButton', mouse.leftButton); psychoJS.experiment.addData('mouse.midButton', mouse.midButton); psychoJS.experiment.addData('mouse.rightButton', mouse.rightButton); psychoJS.experiment.addData('mouse.time', mouse.time); // Run 'End Routine' code from saveCorrect console.log(mouse.time, respond); if ((((mouse.time.length > 0) && (respond === 1)) || ((mouse.time.length === 0) && (respond === 0)))) { correct = 1; } else { correct = 0; } correct_counter += correct; console.log(correct); psychoJS.experiment.addData("correct", correct); // Routines running outside a loop should always advance the datafile row if (currentLoop === psychoJS.experiment) { psychoJS.experiment.nextEntry(snapshot); } return Scheduler.Event.NEXT; } } var endComponents; function endRoutineBegin(snapshot) { return async function () { TrialHandler.fromSnapshot(snapshot); // ensure that .thisN vals are up to date //--- Prepare to start Routine 'end' --- t = 0; endClock.reset(); // clock frameN = -1; continueRoutine = true; // until we're told otherwise // update component parameters for each repeat psychoJS.experiment.addData('end.started', globalClock.getTime()); endMsg.setText((((("You scored " + correct_counter.toString()) + "/") + trials.nTotal.toString()) + " correct!")); // keep track of which components have finished endComponents = []; endComponents.push(endMsg); for (const thisComponent of endComponents) if ('status' in thisComponent) thisComponent.status = PsychoJS.Status.NOT_STARTED; return Scheduler.Event.NEXT; } } function endRoutineEachFrame() { return async function () { //--- Loop for each frame of Routine 'end' --- // get current time t = endClock.getTime(); frameN = frameN + 1;// number of completed frames (so 0 is the first frame) // update/draw components on each frame // *endMsg* updates if (t >= 0.0 && endMsg.status === PsychoJS.Status.NOT_STARTED) { // keep track of start time/frame for later endMsg.tStart = t; // (not accounting for frame time here) endMsg.frameNStart = frameN; // exact frame index endMsg.setAutoDraw(true); } // check for quit (typically the Esc key) if (psychoJS.experiment.experimentEnded || psychoJS.eventManager.getKeys({keyList:['escape']}).length > 0) { return quitPsychoJS('The [Escape] key was pressed. Goodbye!', false); } // check if the Routine should terminate if (!continueRoutine) { // a component has requested a forced-end of Routine return Scheduler.Event.NEXT; } continueRoutine = false; // reverts to True if at least one component still running for (const thisComponent of endComponents) if ('status' in thisComponent && thisComponent.status !== PsychoJS.Status.FINISHED) { continueRoutine = true; break; } // refresh the screen if continuing if (continueRoutine) { return Scheduler.Event.FLIP_REPEAT; } else { return Scheduler.Event.NEXT; } }; } function endRoutineEnd(snapshot) { return async function () { //--- Ending Routine 'end' --- for (const thisComponent of endComponents) { if (typeof thisComponent.setAutoDraw === 'function') { thisComponent.setAutoDraw(false); } } psychoJS.experiment.addData('end.stopped', globalClock.getTime()); // the Routine "end" was not non-slip safe, so reset the non-slip timer routineTimer.reset(); // Routines running outside a loop should always advance the datafile row if (currentLoop === psychoJS.experiment) { psychoJS.experiment.nextEntry(snapshot); } return Scheduler.Event.NEXT; } } function importConditions(currentLoop) { return async function () { psychoJS.importAttributes(currentLoop.getCurrentTrial()); return Scheduler.Event.NEXT; }; } async function quitPsychoJS(message, isCompleted) { // Check for and save orphaned data if (psychoJS.experiment.isEntryEmpty()) { psychoJS.experiment.nextEntry(); } psychoJS.window.close(); psychoJS.quit({message: message, isCompleted: isCompleted}); return Scheduler.Event.QUIT; }