Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ref: change all func taking in jspsych to const syntax / trials to co… #529

Merged
merged 5 commits into from
Aug 14, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 3 additions & 4 deletions src/experiment/honeycomb.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,18 +31,17 @@ export const honeycombOptions = {
* Take a look at how the code here compares to the jsPsych documentation!
* See the jsPsych documentation for more: https://www.jspsych.org/7.3/tutorials/rt-task/
*
* @param {Object} jsPsych The jsPsych instance being used to run the task
* @returns {Object} A jsPsych timeline object
*/
export function buildHoneycombTimeline() {
export const buildHoneycombTimeline = () => {
// Build the trials that make up the start procedure
const startProcedure = buildStartProcedure();

// Build the trials that make up the task procedure
const honeycombProcedure = buildHoneycombProcedure();

// Builds the trial needed to debrief the participant on their performance
const debriefTrial = buildDebriefTrial();
const debriefTrial = buildDebriefTrial;

// Builds the trials that make up the end procedure
const endProcedure = buildEndProcedure();
Expand All @@ -56,4 +55,4 @@ export function buildHoneycombTimeline() {
endProcedure,
];
return timeline;
}
};
7 changes: 3 additions & 4 deletions src/experiment/procedures/endProcedure.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,19 @@ import { exitFullscreenTrial } from "../trials/fullscreen";
* 1) Trial used to complete the user's camera recording is displayed
* 2) The experiment exits fullscreen
*
* @param {Object} jsPsych The jsPsych instance being used to run the task
* @returns {Object} A jsPsych (nested) timeline object
*/
export function buildEndProcedure() {
export const buildEndProcedure = () => {
const procedure = [];

// Conditionally add the camera breakdown trials
if (ENV.USE_CAMERA) {
procedure.push(buildCameraEndTrial());
procedure.push(buildCameraEndTrial);
}

// Add the other trials needed to end the experiment
procedure.push(exitFullscreenTrial, conclusionTrial);

// Return the block as a nested timeline
return { timeline: procedure };
}
};
7 changes: 3 additions & 4 deletions src/experiment/procedures/honeycombProcedure.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,11 @@ import { buildFixationTrial } from "../trials/fixation";
*
* Note that the block is conditionally rendered and repeated based on the task settings
*
* @param {Object} jsPsych The jsPsych instance being used to run the task
* @returns {Object} A jsPsych (nested) timeline object
*/
export function buildHoneycombProcedure() {
export const buildHoneycombProcedure = () => {
const honeycombSettings = SETTINGS.honeycomb;
const fixationTrial = buildFixationTrial();
const fixationTrial = buildFixationTrial;
/**
* Displays a colored circle and waits for participant to response with a keyboard press
*
Expand Down Expand Up @@ -68,4 +67,4 @@ export function buildHoneycombProcedure() {
timeline: [fixationTrial, taskTrial],
};
return honeycombBlock;
}
};
7 changes: 3 additions & 4 deletions src/experiment/procedures/startProcedure.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,9 @@ import { introductionTrial } from "../trials/introduction";
* 4) Trials used to set up a photodiode and trigger box are displayed (if applicable)
* 5) Trials used to set up the user's camera are displayed (if applicable)
*
* @param {Object} jsPsych The jsPsych instance being used to run the task
* @returns {Object} A jsPsych (nested) timeline object
*/
export function buildStartProcedure() {
export const buildStartProcedure = () => {
const procedure = [nameTrial, enterFullscreenTrial, introductionTrial];

// Conditionally add the photodiode setup trials
Expand All @@ -29,9 +28,9 @@ export function buildStartProcedure() {

// Conditionally add the camera setup trials
if (ENV.USE_CAMERA) {
procedure.push(buildCameraStartTrial());
procedure.push(buildCameraStartTrial);
}

// Return the block as a nested timeline
return { timeline: procedure };
}
};
168 changes: 81 additions & 87 deletions src/experiment/trials/camera.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,111 +9,105 @@ const WEBCAM_ID = "webcam";

/**
* A trial that begins recording the participant using their computer's default camera
* @param {Object} jsPsych The jsPsych instance being used to run the task
*
* @returns {Object} A jsPsych trial object
*/
// TODO @brown-ccv #301: Use jsPsych extension, deprecate this function
YUUU23 marked this conversation as resolved.
Show resolved Hide resolved
// TODO @brown-ccv #343: We should be able to make this work on both electron and browser?
// TODO @brown-ccv #301: Rolling save to the deployment (webm is a subset of mkv)
export function buildCameraStartTrial() {
return {
timeline: [
{
// Prompts user permission for camera device
type: initializeCamera,
include_audio: true,
mime_type: "video/webm",
export const buildCameraStartTrial = {
timeline: [
{
// Prompts user permission for camera device
type: initializeCamera,
include_audio: true,
mime_type: "video/webm",
},
{
// Helps participant center themselves inside the camera
type: htmlButtonResponse,
stimulus: function () {
const videoMarkup = tag("video", "", {
id: WEBCAM_ID,
width: 640,
height: 480,
autoplay: true,
});
const cameraStartMarkup = p(LANGUAGE.trials.camera.start);
const trialMarkup = div(cameraStartMarkup + videoMarkup, {
class: "align-items-center-col",
});
return div(trialMarkup);
},
{
// Helps participant center themselves inside the camera
type: htmlButtonResponse,
stimulus: function () {
const videoMarkup = tag("video", "", {
id: WEBCAM_ID,
width: 640,
height: 480,
autoplay: true,
});
const cameraStartMarkup = p(LANGUAGE.trials.camera.start);
const trialMarkup = div(cameraStartMarkup + videoMarkup, {
class: "align-items-center-col",
});
return div(trialMarkup);
},
choices: [LANGUAGE.prompts.continue.button],
response_ends_trial: true,
on_start: function () {
// Initialize and store the camera feed
if (!ENV.USE_ELECTRON) {
throw new Error("video recording is only available when running inside Electron");
}
choices: [LANGUAGE.prompts.continue.button],
response_ends_trial: true,
on_start: function () {
// Initialize and store the camera feed
if (!ENV.USE_ELECTRON) {
throw new Error("video recording is only available when running inside Electron");
}

const cameraRecorder = window.jsPsych.pluginAPI.getCameraRecorder();
if (!cameraRecorder) {
console.error("Camera is not initialized, no data will be recorded.");
return;
}
const cameraChunks = [];
const cameraRecorder = window.jsPsych.pluginAPI.getCameraRecorder();
if (!cameraRecorder) {
console.error("Camera is not initialized, no data will be recorded.");
return;
}
const cameraChunks = [];

// Push data whenever available
cameraRecorder.addEventListener("dataavailable", (event) => {
if (event.data.size > 0) cameraChunks.push(event.data);
});
// Push data whenever available
cameraRecorder.addEventListener("dataavailable", (event) => {
if (event.data.size > 0) cameraChunks.push(event.data);
});

// Saves the raw data feed from the participants camera (executed on cameraRecorder.stop()).
cameraRecorder.addEventListener("stop", () => {
const blob = new Blob(cameraChunks, { type: cameraRecorder.mimeType });
// Saves the raw data feed from the participants camera (executed on cameraRecorder.stop()).
cameraRecorder.addEventListener("stop", () => {
const blob = new Blob(cameraChunks, { type: cameraRecorder.mimeType });

// Pass video data to Electron as a base64 encoded string
const reader = new FileReader();
reader.readAsDataURL(blob);
reader.onloadend = () => {
window.electronAPI.saveVideo(reader.result);
};
});
},
on_load: function () {
// Assign camera feed to the <video> element
const camera = document.getElementById(WEBCAM_ID);
// Pass video data to Electron as a base64 encoded string
const reader = new FileReader();
reader.readAsDataURL(blob);
reader.onloadend = () => {
window.electronAPI.saveVideo(reader.result);
};
});
},
on_load: function () {
// Assign camera feed to the <video> element
const camera = document.getElementById(WEBCAM_ID);

camera.srcObject = window.jsPsych.pluginAPI.getCameraRecorder().stream;
},
on_finish: function () {
// Begin video recording
window.jsPsych.pluginAPI.getCameraRecorder().start();
},
camera.srcObject = window.jsPsych.pluginAPI.getCameraRecorder().stream;
},
on_finish: function () {
// Begin video recording
window.jsPsych.pluginAPI.getCameraRecorder().start();
},
],
};
}
},
],
};

/**
* A trial that finishes recording the participant using their computer's default camera
*
* @param {Number} duration How long to show the trial for
eldu marked this conversation as resolved.
Show resolved Hide resolved
* @returns {Object} A jsPsych trial object
*/
export function buildCameraEndTrial() {
const recordingEndMarkup = h1(LANGUAGE.trials.camera.end);

return {
type: htmlKeyboardResponse,
stimulus: div(recordingEndMarkup),
trial_duration: 5000,
on_start: function () {
// Complete the camera recording
const recordingEndMarkup = h1(LANGUAGE.trials.camera.end);
export const buildCameraEndTrial = {
eldu marked this conversation as resolved.
Show resolved Hide resolved
type: htmlKeyboardResponse,
stimulus: div(recordingEndMarkup),
trial_duration: 5000,
on_start: function () {
// Complete the camera recording

if (!ENV.USE_ELECTRON) {
throw new Error("video recording is only available when running inside Electron");
}
if (!ENV.USE_ELECTRON) {
throw new Error("video recording is only available when running inside Electron");
}

const cameraRecorder = window.jsPsych.pluginAPI.getCameraRecorder();
if (!cameraRecorder) {
console.error("Camera is not initialized, no data will be recorded.");
return;
}
const cameraRecorder = window.jsPsych.pluginAPI.getCameraRecorder();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I commented on the other out window.jsPsych. What's the order of these PRs?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please let me know if this is not what you mean by order, but I think we should merge this branch into jsPsych-global and then merge jsPsych-global into feat-v4

if (!cameraRecorder) {
console.error("Camera is not initialized, no data will be recorded.");
return;
}

cameraRecorder.stop();
},
};
}
cameraRecorder.stop();
},
};
70 changes: 34 additions & 36 deletions src/experiment/trials/fixation.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,43 +5,41 @@ import { eventCodes } from "../../config/trigger";
import { pdSpotEncode, photodiodeGhostBox } from "../../lib/markup/photodiode";
import { div } from "../../lib/markup/tags";

const fixationSettings = SETTINGS.fixation;
const fixationCode = eventCodes.fixation;

/**
* Builds a trial with a fixation dot and optional photodiode box.
* @param {Object} jsPsych The global jsPsych object used to build the trial
*
* @returns {Object} A jsPsych trial object
*/
export function buildFixationTrial() {
const fixationSettings = SETTINGS.fixation;
const fixationCode = eventCodes.fixation;

return {
type: htmlKeyboardResponse,
choices: "NO_KEYS",
// Display the fixation dot
stimulus: div("", { id: "fixation-dot" }),
prompt: function () {
// Conditionally display the photodiodeGhostBox
if (ENV.USE_PHOTODIODE) return photodiodeGhostBox;
else return null;
},
trial_duration: function () {
if (fixationSettings.randomize_duration) {
// Select a random duration from the durations array to show the fixation dot for
return window.jsPsych.randomization.sampleWithoutReplacement(
fixationSettings.durations,
1
)[0];
} else {
// Show the fixation dot for default duration seconds
return fixationSettings.default_duration;
}
},
data: {
code: fixationCode, // Add event code to the recorded data
},
on_load: function () {
// Conditionally flash the photodiode when the trial first loads
if (ENV.USE_PHOTODIODE) pdSpotEncode(fixationCode);
},
};
}
export const buildFixationTrial = {
type: htmlKeyboardResponse,
choices: "NO_KEYS",
// Display the fixation dot
stimulus: div("", { id: "fixation-dot" }),
prompt: function () {
// Conditionally display the photodiodeGhostBox
if (ENV.USE_PHOTODIODE) return photodiodeGhostBox;
else return null;
},
trial_duration: function () {
if (fixationSettings.randomize_duration) {
// Select a random duration from the durations array to show the fixation dot for
return window.jsPsych.randomization.sampleWithoutReplacement(
fixationSettings.durations,
1
)[0];
} else {
// Show the fixation dot for default duration seconds
return fixationSettings.default_duration;
}
},
data: {
code: fixationCode, // Add event code to the recorded data
},
on_load: function () {
// Conditionally flash the photodiode when the trial first loads
if (ENV.USE_PHOTODIODE) pdSpotEncode(fixationCode);
},
};
Loading
Loading