reorganization and parameterization

This commit is contained in:
Josh de Leeuw 2021-11-22 11:48:19 -05:00
parent aa660ad597
commit 827df6c95d
2 changed files with 57 additions and 33 deletions

View File

@ -17,7 +17,10 @@
let ar = { let ar = {
type: jsPsychHtmlAudioResponse, type: jsPsychHtmlAudioResponse,
stimulus: '<p>Speak!</p>' stimulus: '<p>Speak!</p>',
on_finish: (data) => {
console.log(data);
}
} }
jsPsych.run([init_mic, ar]); jsPsych.run([init_mic, ar]);

View File

@ -9,7 +9,6 @@ const info = <const>{
pretty_name: "Stimulus", pretty_name: "Stimulus",
default: undefined, default: undefined,
}, },
/** How long to show the stimulus. */ /** How long to show the stimulus. */
stimulus_duration: { stimulus_duration: {
type: ParameterType.INT, type: ParameterType.INT,
@ -20,7 +19,11 @@ const info = <const>{
trial_duration: { trial_duration: {
type: ParameterType.INT, type: ParameterType.INT,
pretty_name: "Trial duration", pretty_name: "Trial duration",
default: null, default: 2000,
},
button_label: {
type: ParameterType.STRING,
default: "Continue",
}, },
}, },
}; };
@ -37,88 +40,104 @@ class HtmlAudioResponsePlugin implements JsPsychPlugin<Info> {
static info = info; static info = info;
private stimulus_start_time; private stimulus_start_time;
private recorder_start_time; private recorder_start_time;
private recorder: MediaRecorder;
private response;
private load_resolver;
constructor(private jsPsych: JsPsych) {} constructor(private jsPsych: JsPsych) {}
trial(display_element: HTMLElement, trial: TrialType<Info>) { trial(display_element: HTMLElement, trial: TrialType<Info>) {
const ro = new ResizeObserver((entries) => { this.recorder = this.jsPsych.pluginAPI.getMicrophoneRecorder();
console.log("ro event");
this.stimulus_start_time = performance.now();
});
ro.observe(display_element); this.setupRecordingEvents(display_element, trial);
// this.showDisplay(display_element, trial); this.startRecording();
// this.addButtonEvent(display_element, trial);
this.startRecording(display_element, trial); // setup timer for hiding the stimulus
// hide image if timing is set
if (trial.stimulus_duration !== null) { if (trial.stimulus_duration !== null) {
this.jsPsych.pluginAPI.setTimeout(() => { this.jsPsych.pluginAPI.setTimeout(() => {
display_element.querySelector<HTMLElement>( this.hideStimulus(display_element);
"#jspsych-html-audio-response-stimulus"
).style.visibility = "hidden";
}, trial.stimulus_duration); }, trial.stimulus_duration);
} }
// end trial if time limit is set // setup timer for ending the trial
if (trial.trial_duration !== null) { if (trial.trial_duration !== null) {
this.jsPsych.pluginAPI.setTimeout(() => { this.jsPsych.pluginAPI.setTimeout(() => {
this.endTrial(display_element, trial); this.stopRecording().then(() => {
this.endTrial(display_element, trial);
});
}, trial.trial_duration); }, trial.trial_duration);
} }
} }
private showDisplay(display_element, trial) { private showDisplay(display_element, trial) {
const ro = new ResizeObserver((entries) => {
this.stimulus_start_time = performance.now();
console.log("ro event");
ro.disconnect();
});
ro.observe(display_element);
let html = `<div id="jspsych-html-audio-response-stimulus">${trial.stimulus}</div>`; let html = `<div id="jspsych-html-audio-response-stimulus">${trial.stimulus}</div>`;
html += `<p><button class="jspsych-btn" id="finish-trial">Done</button></p>`; html += `<p><button class="jspsych-btn" id="finish-trial">${trial.button_label}</button></p>`;
display_element.innerHTML = html; display_element.innerHTML = html;
} }
private hideStimulus(display_element: HTMLElement) {
display_element.querySelector<HTMLElement>(
"#jspsych-html-audio-response-stimulus"
).style.visibility = "hidden";
}
private addButtonEvent(display_element, trial) { private addButtonEvent(display_element, trial) {
display_element.querySelector("#finish-trial").addEventListener("click", () => { display_element.querySelector("#finish-trial").addEventListener("click", () => {
const end_time = performance.now(); const end_time = performance.now();
const rt = Math.round(end_time - this.stimulus_start_time); const rt = Math.round(end_time - this.stimulus_start_time);
this.endTrial(display_element, trial, rt); this.stopRecording().then(() => {
this.endTrial(display_element, trial, rt);
});
}); });
} }
private startRecording(display_element, trial) { private setupRecordingEvents(display_element, trial) {
const recorder: MediaRecorder = this.jsPsych.pluginAPI.getMicrophoneRecorder();
const recorded_data_chunks = []; const recorded_data_chunks = [];
recorder.addEventListener("dataavailable", (e) => { this.recorder.addEventListener("dataavailable", (e) => {
if (e.data.size > 0) { if (e.data.size > 0) {
recorded_data_chunks.push(e.data); recorded_data_chunks.push(e.data);
} }
console.log("dataavail event");
}); });
recorder.addEventListener("stop", () => { this.recorder.addEventListener("stop", () => {
const url = new Blob(recorded_data_chunks, { type: "audio/webm" }); const url = new Blob(recorded_data_chunks, { type: "audio/webm" });
const reader = new FileReader(); const reader = new FileReader();
reader.addEventListener("load", () => { reader.addEventListener("load", () => {
const base64 = (reader.result as string).split(",")[1]; const base64 = (reader.result as string).split(",")[1];
console.log(base64); this.response = base64;
this.load_resolver();
}); });
reader.readAsDataURL(url); reader.readAsDataURL(url);
console.log(this.stimulus_start_time, this.recorder_start_time);
}); });
recorder.addEventListener("start", (e) => { this.recorder.addEventListener("start", (e) => {
this.recorder_start_time = e.timeStamp; this.recorder_start_time = e.timeStamp;
this.showDisplay(display_element, trial); this.showDisplay(display_element, trial);
this.addButtonEvent(display_element, trial); this.addButtonEvent(display_element, trial);
}); });
}
this.jsPsych.pluginAPI.setTimeout(() => { private startRecording() {
recorder.stop(); this.recorder.start();
}, 2000); }
recorder.start();
private stopRecording() {
this.recorder.stop();
return new Promise((resolve) => {
this.load_resolver = resolve;
});
} }
private endTrial(display_element, trial, rt: number = null) { private endTrial(display_element, trial, rt: number = null) {
@ -129,6 +148,8 @@ class HtmlAudioResponsePlugin implements JsPsychPlugin<Info> {
var trial_data = { var trial_data = {
rt: rt, rt: rt,
stimulus: trial.stimulus, stimulus: trial.stimulus,
response: this.response,
estimated_stimulus_onset: Math.round(this.stimulus_start_time - this.recorder_start_time),
}; };
// clear the display // clear the display