@jspsych/plugin-audio-button-response 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.browser.js +57 -17
- package/dist/index.browser.js.map +1 -1
- package/dist/index.browser.min.js +1 -1
- package/dist/index.browser.min.js.map +1 -1
- package/dist/index.cjs +57 -17
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +5 -0
- package/dist/index.js +57 -17
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
- package/src/index.spec.ts +53 -2
- package/src/index.ts +76 -17
package/dist/index.browser.js
CHANGED
|
@@ -88,7 +88,6 @@ var jsPsychAudioButtonResponse = (function (jspsych) {
|
|
|
88
88
|
let trial_complete;
|
|
89
89
|
// setup stimulus
|
|
90
90
|
var context = this.jsPsych.pluginAPI.audioContext();
|
|
91
|
-
var audio;
|
|
92
91
|
// store response
|
|
93
92
|
var response = {
|
|
94
93
|
rt: null,
|
|
@@ -99,30 +98,30 @@ var jsPsychAudioButtonResponse = (function (jspsych) {
|
|
|
99
98
|
// load audio file
|
|
100
99
|
this.jsPsych.pluginAPI
|
|
101
100
|
.getAudioBuffer(trial.stimulus)
|
|
102
|
-
.then(
|
|
101
|
+
.then((buffer) => {
|
|
103
102
|
if (context !== null) {
|
|
104
|
-
audio = context.createBufferSource();
|
|
105
|
-
audio.buffer = buffer;
|
|
106
|
-
audio.connect(context.destination);
|
|
103
|
+
this.audio = context.createBufferSource();
|
|
104
|
+
this.audio.buffer = buffer;
|
|
105
|
+
this.audio.connect(context.destination);
|
|
107
106
|
}
|
|
108
107
|
else {
|
|
109
|
-
audio = buffer;
|
|
110
|
-
audio.currentTime = 0;
|
|
108
|
+
this.audio = buffer;
|
|
109
|
+
this.audio.currentTime = 0;
|
|
111
110
|
}
|
|
112
111
|
setupTrial();
|
|
113
112
|
})
|
|
114
|
-
.catch(
|
|
113
|
+
.catch((err) => {
|
|
115
114
|
console.error(`Failed to load audio file "${trial.stimulus}". Try checking the file path. We recommend using the preload plugin to load audio files.`);
|
|
116
115
|
console.error(err);
|
|
117
116
|
});
|
|
118
117
|
const setupTrial = () => {
|
|
119
118
|
// set up end event if trial needs it
|
|
120
119
|
if (trial.trial_ends_after_audio) {
|
|
121
|
-
audio.addEventListener("ended", end_trial);
|
|
120
|
+
this.audio.addEventListener("ended", end_trial);
|
|
122
121
|
}
|
|
123
122
|
// enable buttons after audio ends if necessary
|
|
124
123
|
if (!trial.response_allowed_while_playing && !trial.trial_ends_after_audio) {
|
|
125
|
-
audio.addEventListener("ended", enable_buttons);
|
|
124
|
+
this.audio.addEventListener("ended", enable_buttons);
|
|
126
125
|
}
|
|
127
126
|
//display buttons
|
|
128
127
|
var buttons = [];
|
|
@@ -172,14 +171,14 @@ var jsPsychAudioButtonResponse = (function (jspsych) {
|
|
|
172
171
|
// start audio
|
|
173
172
|
if (context !== null) {
|
|
174
173
|
startTime = context.currentTime;
|
|
175
|
-
audio.start(startTime);
|
|
174
|
+
this.audio.start(startTime);
|
|
176
175
|
}
|
|
177
176
|
else {
|
|
178
|
-
audio.play();
|
|
177
|
+
this.audio.play();
|
|
179
178
|
}
|
|
180
179
|
// end trial if time limit is set
|
|
181
180
|
if (trial.trial_duration !== null) {
|
|
182
|
-
this.jsPsych.pluginAPI.setTimeout(
|
|
181
|
+
this.jsPsych.pluginAPI.setTimeout(() => {
|
|
183
182
|
end_trial();
|
|
184
183
|
}, trial.trial_duration);
|
|
185
184
|
}
|
|
@@ -209,13 +208,13 @@ var jsPsychAudioButtonResponse = (function (jspsych) {
|
|
|
209
208
|
// stop the audio file if it is playing
|
|
210
209
|
// remove end event listeners if they exist
|
|
211
210
|
if (context !== null) {
|
|
212
|
-
audio.stop();
|
|
211
|
+
this.audio.stop();
|
|
213
212
|
}
|
|
214
213
|
else {
|
|
215
|
-
audio.pause();
|
|
214
|
+
this.audio.pause();
|
|
216
215
|
}
|
|
217
|
-
audio.removeEventListener("ended", end_trial);
|
|
218
|
-
audio.removeEventListener("ended", enable_buttons);
|
|
216
|
+
this.audio.removeEventListener("ended", end_trial);
|
|
217
|
+
this.audio.removeEventListener("ended", enable_buttons);
|
|
219
218
|
// gather the data to store for the trial
|
|
220
219
|
var trial_data = {
|
|
221
220
|
rt: response.rt,
|
|
@@ -256,6 +255,47 @@ var jsPsychAudioButtonResponse = (function (jspsych) {
|
|
|
256
255
|
trial_complete = resolve;
|
|
257
256
|
});
|
|
258
257
|
}
|
|
258
|
+
simulate(trial, simulation_mode, simulation_options, load_callback) {
|
|
259
|
+
if (simulation_mode == "data-only") {
|
|
260
|
+
load_callback();
|
|
261
|
+
this.simulate_data_only(trial, simulation_options);
|
|
262
|
+
}
|
|
263
|
+
if (simulation_mode == "visual") {
|
|
264
|
+
this.simulate_visual(trial, simulation_options, load_callback);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
create_simulation_data(trial, simulation_options) {
|
|
268
|
+
const default_data = {
|
|
269
|
+
stimulus: trial.stimulus,
|
|
270
|
+
rt: this.jsPsych.randomization.sampleExGaussian(500, 50, 1 / 150, true),
|
|
271
|
+
response: this.jsPsych.randomization.randomInt(0, trial.choices.length - 1),
|
|
272
|
+
};
|
|
273
|
+
const data = this.jsPsych.pluginAPI.mergeSimulationData(default_data, simulation_options);
|
|
274
|
+
this.jsPsych.pluginAPI.ensureSimulationDataConsistency(trial, data);
|
|
275
|
+
return data;
|
|
276
|
+
}
|
|
277
|
+
simulate_data_only(trial, simulation_options) {
|
|
278
|
+
const data = this.create_simulation_data(trial, simulation_options);
|
|
279
|
+
this.jsPsych.finishTrial(data);
|
|
280
|
+
}
|
|
281
|
+
simulate_visual(trial, simulation_options, load_callback) {
|
|
282
|
+
const data = this.create_simulation_data(trial, simulation_options);
|
|
283
|
+
const display_element = this.jsPsych.getDisplayElement();
|
|
284
|
+
const respond = () => {
|
|
285
|
+
if (data.rt !== null) {
|
|
286
|
+
this.jsPsych.pluginAPI.clickTarget(display_element.querySelector(`div[data-choice="${data.response}"] button`), data.rt);
|
|
287
|
+
}
|
|
288
|
+
};
|
|
289
|
+
this.trial(display_element, trial, () => {
|
|
290
|
+
load_callback();
|
|
291
|
+
if (!trial.response_allowed_while_playing) {
|
|
292
|
+
this.audio.addEventListener("ended", respond);
|
|
293
|
+
}
|
|
294
|
+
else {
|
|
295
|
+
respond();
|
|
296
|
+
}
|
|
297
|
+
});
|
|
298
|
+
}
|
|
259
299
|
}
|
|
260
300
|
AudioButtonResponsePlugin.info = info;
|
|
261
301
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.browser.js","sources":["../src/index.ts"],"sourcesContent":["import { JsPsych, JsPsychPlugin, ParameterType, TrialType } from \"jspsych\";\n\nconst info = <const>{\n name: \"audio-button-response\",\n parameters: {\n /** The audio to be played. */\n stimulus: {\n type: ParameterType.AUDIO,\n pretty_name: \"Stimulus\",\n default: undefined,\n },\n /** Array containing the label(s) for the button(s). */\n choices: {\n type: ParameterType.STRING,\n pretty_name: \"Choices\",\n default: undefined,\n array: true,\n },\n /** The HTML for creating button. Can create own style. Use the \"%choice%\" string to indicate where the label from the choices parameter should be inserted. */\n button_html: {\n type: ParameterType.HTML_STRING,\n pretty_name: \"Button HTML\",\n default: '<button class=\"jspsych-btn\">%choice%</button>',\n array: true,\n },\n /** Any content here will be displayed below the stimulus. */\n prompt: {\n type: ParameterType.HTML_STRING,\n pretty_name: \"Prompt\",\n default: null,\n },\n /** The maximum duration to wait for a response. */\n trial_duration: {\n type: ParameterType.INT,\n pretty_name: \"Trial duration\",\n default: null,\n },\n /** Vertical margin of button. */\n margin_vertical: {\n type: ParameterType.STRING,\n pretty_name: \"Margin vertical\",\n default: \"0px\",\n },\n /** Horizontal margin of button. */\n margin_horizontal: {\n type: ParameterType.STRING,\n pretty_name: \"Margin horizontal\",\n default: \"8px\",\n },\n /** If true, the trial will end when user makes a response. */\n response_ends_trial: {\n type: ParameterType.BOOL,\n pretty_name: \"Response ends trial\",\n default: true,\n },\n /** If true, then the trial will end as soon as the audio file finishes playing. */\n trial_ends_after_audio: {\n type: ParameterType.BOOL,\n pretty_name: \"Trial ends after audio\",\n default: false,\n },\n /**\n * If true, then responses are allowed while the audio is playing.\n * If false, then the audio must finish playing before a response is accepted.\n */\n response_allowed_while_playing: {\n type: ParameterType.BOOL,\n pretty_name: \"Response allowed while playing\",\n default: true,\n },\n },\n};\n\ntype Info = typeof info;\n\n/**\n * **audio-button-response**\n *\n * jsPsych plugin for playing an audio file and getting a button response\n *\n * @author Kristin Diep\n * @see {@link https://www.jspsych.org/plugins/jspsych-audio-button-response/ audio-button-response plugin documentation on jspsych.org}\n */\nclass AudioButtonResponsePlugin implements JsPsychPlugin<Info> {\n static info = info;\n\n constructor(private jsPsych: JsPsych) {}\n\n trial(display_element: HTMLElement, trial: TrialType<Info>, on_load: () => void) {\n // hold the .resolve() function from the Promise that ends the trial\n let trial_complete;\n\n // setup stimulus\n var context = this.jsPsych.pluginAPI.audioContext();\n var audio;\n\n // store response\n var response = {\n rt: null,\n button: null,\n };\n\n // record webaudio context start time\n var startTime;\n\n // load audio file\n this.jsPsych.pluginAPI\n .getAudioBuffer(trial.stimulus)\n .then(function (buffer) {\n if (context !== null) {\n audio = context.createBufferSource();\n audio.buffer = buffer;\n audio.connect(context.destination);\n } else {\n audio = buffer;\n audio.currentTime = 0;\n }\n setupTrial();\n })\n .catch(function (err) {\n console.error(\n `Failed to load audio file \"${trial.stimulus}\". Try checking the file path. We recommend using the preload plugin to load audio files.`\n );\n console.error(err);\n });\n\n const setupTrial = () => {\n // set up end event if trial needs it\n if (trial.trial_ends_after_audio) {\n audio.addEventListener(\"ended\", end_trial);\n }\n\n // enable buttons after audio ends if necessary\n if (!trial.response_allowed_while_playing && !trial.trial_ends_after_audio) {\n audio.addEventListener(\"ended\", enable_buttons);\n }\n\n //display buttons\n var buttons = [];\n if (Array.isArray(trial.button_html)) {\n if (trial.button_html.length == trial.choices.length) {\n buttons = trial.button_html;\n } else {\n console.error(\n \"Error in audio-button-response plugin. The length of the button_html array does not equal the length of the choices array\"\n );\n }\n } else {\n for (var i = 0; i < trial.choices.length; i++) {\n buttons.push(trial.button_html);\n }\n }\n\n var html = '<div id=\"jspsych-audio-button-response-btngroup\">';\n for (var i = 0; i < trial.choices.length; i++) {\n var str = buttons[i].replace(/%choice%/g, trial.choices[i]);\n html +=\n '<div class=\"jspsych-audio-button-response-button\" style=\"cursor: pointer; display: inline-block; margin:' +\n trial.margin_vertical +\n \" \" +\n trial.margin_horizontal +\n '\" id=\"jspsych-audio-button-response-button-' +\n i +\n '\" data-choice=\"' +\n i +\n '\">' +\n str +\n \"</div>\";\n }\n html += \"</div>\";\n\n //show prompt if there is one\n if (trial.prompt !== null) {\n html += trial.prompt;\n }\n\n display_element.innerHTML = html;\n\n if (trial.response_allowed_while_playing) {\n enable_buttons();\n } else {\n disable_buttons();\n }\n\n // start time\n startTime = performance.now();\n\n // start audio\n if (context !== null) {\n startTime = context.currentTime;\n audio.start(startTime);\n } else {\n audio.play();\n }\n\n // end trial if time limit is set\n if (trial.trial_duration !== null) {\n this.jsPsych.pluginAPI.setTimeout(function () {\n end_trial();\n }, trial.trial_duration);\n }\n\n on_load();\n };\n\n // function to handle responses by the subject\n function after_response(choice) {\n // measure rt\n var endTime = performance.now();\n var rt = Math.round(endTime - startTime);\n if (context !== null) {\n endTime = context.currentTime;\n rt = Math.round((endTime - startTime) * 1000);\n }\n response.button = parseInt(choice);\n response.rt = rt;\n\n // disable all the buttons after a response\n disable_buttons();\n\n if (trial.response_ends_trial) {\n end_trial();\n }\n }\n\n // function to end trial when it is time\n const end_trial = () => {\n // kill any remaining setTimeout handlers\n this.jsPsych.pluginAPI.clearAllTimeouts();\n\n // stop the audio file if it is playing\n // remove end event listeners if they exist\n if (context !== null) {\n audio.stop();\n } else {\n audio.pause();\n }\n\n audio.removeEventListener(\"ended\", end_trial);\n audio.removeEventListener(\"ended\", enable_buttons);\n\n // gather the data to store for the trial\n var trial_data = {\n rt: response.rt,\n stimulus: trial.stimulus,\n response: response.button,\n };\n\n // clear the display\n display_element.innerHTML = \"\";\n\n // move on to the next trial\n this.jsPsych.finishTrial(trial_data);\n\n trial_complete();\n };\n\n function button_response(e) {\n var choice = e.currentTarget.getAttribute(\"data-choice\"); // don't use dataset for jsdom compatibility\n after_response(choice);\n }\n\n function disable_buttons() {\n var btns = document.querySelectorAll(\".jspsych-audio-button-response-button\");\n for (var i = 0; i < btns.length; i++) {\n var btn_el = btns[i].querySelector(\"button\");\n if (btn_el) {\n btn_el.disabled = true;\n }\n btns[i].removeEventListener(\"click\", button_response);\n }\n }\n\n function enable_buttons() {\n var btns = document.querySelectorAll(\".jspsych-audio-button-response-button\");\n for (var i = 0; i < btns.length; i++) {\n var btn_el = btns[i].querySelector(\"button\");\n if (btn_el) {\n btn_el.disabled = false;\n }\n btns[i].addEventListener(\"click\", button_response);\n }\n }\n\n return new Promise((resolve) => {\n trial_complete = resolve;\n });\n }\n}\n\nexport default AudioButtonResponsePlugin;\n"],"names":["ParameterType"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.browser.js","sources":["../src/index.ts"],"sourcesContent":["import { JsPsych, JsPsychPlugin, ParameterType, TrialType } from \"jspsych\";\n\nconst info = <const>{\n name: \"audio-button-response\",\n parameters: {\n /** The audio to be played. */\n stimulus: {\n type: ParameterType.AUDIO,\n pretty_name: \"Stimulus\",\n default: undefined,\n },\n /** Array containing the label(s) for the button(s). */\n choices: {\n type: ParameterType.STRING,\n pretty_name: \"Choices\",\n default: undefined,\n array: true,\n },\n /** The HTML for creating button. Can create own style. Use the \"%choice%\" string to indicate where the label from the choices parameter should be inserted. */\n button_html: {\n type: ParameterType.HTML_STRING,\n pretty_name: \"Button HTML\",\n default: '<button class=\"jspsych-btn\">%choice%</button>',\n array: true,\n },\n /** Any content here will be displayed below the stimulus. */\n prompt: {\n type: ParameterType.HTML_STRING,\n pretty_name: \"Prompt\",\n default: null,\n },\n /** The maximum duration to wait for a response. */\n trial_duration: {\n type: ParameterType.INT,\n pretty_name: \"Trial duration\",\n default: null,\n },\n /** Vertical margin of button. */\n margin_vertical: {\n type: ParameterType.STRING,\n pretty_name: \"Margin vertical\",\n default: \"0px\",\n },\n /** Horizontal margin of button. */\n margin_horizontal: {\n type: ParameterType.STRING,\n pretty_name: \"Margin horizontal\",\n default: \"8px\",\n },\n /** If true, the trial will end when user makes a response. */\n response_ends_trial: {\n type: ParameterType.BOOL,\n pretty_name: \"Response ends trial\",\n default: true,\n },\n /** If true, then the trial will end as soon as the audio file finishes playing. */\n trial_ends_after_audio: {\n type: ParameterType.BOOL,\n pretty_name: \"Trial ends after audio\",\n default: false,\n },\n /**\n * If true, then responses are allowed while the audio is playing.\n * If false, then the audio must finish playing before a response is accepted.\n */\n response_allowed_while_playing: {\n type: ParameterType.BOOL,\n pretty_name: \"Response allowed while playing\",\n default: true,\n },\n },\n};\n\ntype Info = typeof info;\n\n/**\n * **audio-button-response**\n *\n * jsPsych plugin for playing an audio file and getting a button response\n *\n * @author Kristin Diep\n * @see {@link https://www.jspsych.org/plugins/jspsych-audio-button-response/ audio-button-response plugin documentation on jspsych.org}\n */\nclass AudioButtonResponsePlugin implements JsPsychPlugin<Info> {\n static info = info;\n private audio;\n\n constructor(private jsPsych: JsPsych) {}\n\n trial(display_element: HTMLElement, trial: TrialType<Info>, on_load: () => void) {\n // hold the .resolve() function from the Promise that ends the trial\n let trial_complete;\n\n // setup stimulus\n var context = this.jsPsych.pluginAPI.audioContext();\n\n // store response\n var response = {\n rt: null,\n button: null,\n };\n\n // record webaudio context start time\n var startTime;\n\n // load audio file\n this.jsPsych.pluginAPI\n .getAudioBuffer(trial.stimulus)\n .then((buffer) => {\n if (context !== null) {\n this.audio = context.createBufferSource();\n this.audio.buffer = buffer;\n this.audio.connect(context.destination);\n } else {\n this.audio = buffer;\n this.audio.currentTime = 0;\n }\n setupTrial();\n })\n .catch((err) => {\n console.error(\n `Failed to load audio file \"${trial.stimulus}\". Try checking the file path. We recommend using the preload plugin to load audio files.`\n );\n console.error(err);\n });\n\n const setupTrial = () => {\n // set up end event if trial needs it\n if (trial.trial_ends_after_audio) {\n this.audio.addEventListener(\"ended\", end_trial);\n }\n\n // enable buttons after audio ends if necessary\n if (!trial.response_allowed_while_playing && !trial.trial_ends_after_audio) {\n this.audio.addEventListener(\"ended\", enable_buttons);\n }\n\n //display buttons\n var buttons = [];\n if (Array.isArray(trial.button_html)) {\n if (trial.button_html.length == trial.choices.length) {\n buttons = trial.button_html;\n } else {\n console.error(\n \"Error in audio-button-response plugin. The length of the button_html array does not equal the length of the choices array\"\n );\n }\n } else {\n for (var i = 0; i < trial.choices.length; i++) {\n buttons.push(trial.button_html);\n }\n }\n\n var html = '<div id=\"jspsych-audio-button-response-btngroup\">';\n for (var i = 0; i < trial.choices.length; i++) {\n var str = buttons[i].replace(/%choice%/g, trial.choices[i]);\n html +=\n '<div class=\"jspsych-audio-button-response-button\" style=\"cursor: pointer; display: inline-block; margin:' +\n trial.margin_vertical +\n \" \" +\n trial.margin_horizontal +\n '\" id=\"jspsych-audio-button-response-button-' +\n i +\n '\" data-choice=\"' +\n i +\n '\">' +\n str +\n \"</div>\";\n }\n html += \"</div>\";\n\n //show prompt if there is one\n if (trial.prompt !== null) {\n html += trial.prompt;\n }\n\n display_element.innerHTML = html;\n\n if (trial.response_allowed_while_playing) {\n enable_buttons();\n } else {\n disable_buttons();\n }\n\n // start time\n startTime = performance.now();\n\n // start audio\n if (context !== null) {\n startTime = context.currentTime;\n this.audio.start(startTime);\n } else {\n this.audio.play();\n }\n\n // end trial if time limit is set\n if (trial.trial_duration !== null) {\n this.jsPsych.pluginAPI.setTimeout(() => {\n end_trial();\n }, trial.trial_duration);\n }\n\n on_load();\n };\n\n // function to handle responses by the subject\n function after_response(choice) {\n // measure rt\n var endTime = performance.now();\n var rt = Math.round(endTime - startTime);\n if (context !== null) {\n endTime = context.currentTime;\n rt = Math.round((endTime - startTime) * 1000);\n }\n response.button = parseInt(choice);\n response.rt = rt;\n\n // disable all the buttons after a response\n disable_buttons();\n\n if (trial.response_ends_trial) {\n end_trial();\n }\n }\n\n // function to end trial when it is time\n const end_trial = () => {\n // kill any remaining setTimeout handlers\n this.jsPsych.pluginAPI.clearAllTimeouts();\n\n // stop the audio file if it is playing\n // remove end event listeners if they exist\n if (context !== null) {\n this.audio.stop();\n } else {\n this.audio.pause();\n }\n\n this.audio.removeEventListener(\"ended\", end_trial);\n this.audio.removeEventListener(\"ended\", enable_buttons);\n\n // gather the data to store for the trial\n var trial_data = {\n rt: response.rt,\n stimulus: trial.stimulus,\n response: response.button,\n };\n\n // clear the display\n display_element.innerHTML = \"\";\n\n // move on to the next trial\n this.jsPsych.finishTrial(trial_data);\n\n trial_complete();\n };\n\n function button_response(e) {\n var choice = e.currentTarget.getAttribute(\"data-choice\"); // don't use dataset for jsdom compatibility\n after_response(choice);\n }\n\n function disable_buttons() {\n var btns = document.querySelectorAll(\".jspsych-audio-button-response-button\");\n for (var i = 0; i < btns.length; i++) {\n var btn_el = btns[i].querySelector(\"button\");\n if (btn_el) {\n btn_el.disabled = true;\n }\n btns[i].removeEventListener(\"click\", button_response);\n }\n }\n\n function enable_buttons() {\n var btns = document.querySelectorAll(\".jspsych-audio-button-response-button\");\n for (var i = 0; i < btns.length; i++) {\n var btn_el = btns[i].querySelector(\"button\");\n if (btn_el) {\n btn_el.disabled = false;\n }\n btns[i].addEventListener(\"click\", button_response);\n }\n }\n\n return new Promise((resolve) => {\n trial_complete = resolve;\n });\n }\n\n simulate(\n trial: TrialType<Info>,\n simulation_mode,\n simulation_options: any,\n load_callback: () => void\n ) {\n if (simulation_mode == \"data-only\") {\n load_callback();\n this.simulate_data_only(trial, simulation_options);\n }\n if (simulation_mode == \"visual\") {\n this.simulate_visual(trial, simulation_options, load_callback);\n }\n }\n\n private create_simulation_data(trial: TrialType<Info>, simulation_options) {\n const default_data = {\n stimulus: trial.stimulus,\n rt: this.jsPsych.randomization.sampleExGaussian(500, 50, 1 / 150, true),\n response: this.jsPsych.randomization.randomInt(0, trial.choices.length - 1),\n };\n\n const data = this.jsPsych.pluginAPI.mergeSimulationData(default_data, simulation_options);\n\n this.jsPsych.pluginAPI.ensureSimulationDataConsistency(trial, data);\n\n return data;\n }\n\n private simulate_data_only(trial: TrialType<Info>, simulation_options) {\n const data = this.create_simulation_data(trial, simulation_options);\n\n this.jsPsych.finishTrial(data);\n }\n\n private simulate_visual(trial: TrialType<Info>, simulation_options, load_callback: () => void) {\n const data = this.create_simulation_data(trial, simulation_options);\n\n const display_element = this.jsPsych.getDisplayElement();\n\n const respond = () => {\n if (data.rt !== null) {\n this.jsPsych.pluginAPI.clickTarget(\n display_element.querySelector(`div[data-choice=\"${data.response}\"] button`),\n data.rt\n );\n }\n };\n\n this.trial(display_element, trial, () => {\n load_callback();\n if (!trial.response_allowed_while_playing) {\n this.audio.addEventListener(\"ended\", respond);\n } else {\n respond();\n }\n });\n }\n}\n\nexport default AudioButtonResponsePlugin;\n"],"names":["ParameterType"],"mappings":";;;EAEA,MAAM,IAAI,GAAU;MAClB,IAAI,EAAE,uBAAuB;MAC7B,UAAU,EAAE;;UAEV,QAAQ,EAAE;cACR,IAAI,EAAEA,qBAAa,CAAC,KAAK;cACzB,WAAW,EAAE,UAAU;cACvB,OAAO,EAAE,SAAS;WACnB;;UAED,OAAO,EAAE;cACP,IAAI,EAAEA,qBAAa,CAAC,MAAM;cAC1B,WAAW,EAAE,SAAS;cACtB,OAAO,EAAE,SAAS;cAClB,KAAK,EAAE,IAAI;WACZ;;UAED,WAAW,EAAE;cACX,IAAI,EAAEA,qBAAa,CAAC,WAAW;cAC/B,WAAW,EAAE,aAAa;cAC1B,OAAO,EAAE,+CAA+C;cACxD,KAAK,EAAE,IAAI;WACZ;;UAED,MAAM,EAAE;cACN,IAAI,EAAEA,qBAAa,CAAC,WAAW;cAC/B,WAAW,EAAE,QAAQ;cACrB,OAAO,EAAE,IAAI;WACd;;UAED,cAAc,EAAE;cACd,IAAI,EAAEA,qBAAa,CAAC,GAAG;cACvB,WAAW,EAAE,gBAAgB;cAC7B,OAAO,EAAE,IAAI;WACd;;UAED,eAAe,EAAE;cACf,IAAI,EAAEA,qBAAa,CAAC,MAAM;cAC1B,WAAW,EAAE,iBAAiB;cAC9B,OAAO,EAAE,KAAK;WACf;;UAED,iBAAiB,EAAE;cACjB,IAAI,EAAEA,qBAAa,CAAC,MAAM;cAC1B,WAAW,EAAE,mBAAmB;cAChC,OAAO,EAAE,KAAK;WACf;;UAED,mBAAmB,EAAE;cACnB,IAAI,EAAEA,qBAAa,CAAC,IAAI;cACxB,WAAW,EAAE,qBAAqB;cAClC,OAAO,EAAE,IAAI;WACd;;UAED,sBAAsB,EAAE;cACtB,IAAI,EAAEA,qBAAa,CAAC,IAAI;cACxB,WAAW,EAAE,wBAAwB;cACrC,OAAO,EAAE,KAAK;WACf;;;;;UAKD,8BAA8B,EAAE;cAC9B,IAAI,EAAEA,qBAAa,CAAC,IAAI;cACxB,WAAW,EAAE,gCAAgC;cAC7C,OAAO,EAAE,IAAI;WACd;OACF;GACF,CAAC;EAIF;;;;;;;;EAQA,MAAM,yBAAyB;MAI7B,YAAoB,OAAgB;UAAhB,YAAO,GAAP,OAAO,CAAS;OAAI;MAExC,KAAK,CAAC,eAA4B,EAAE,KAAsB,EAAE,OAAmB;;UAE7E,IAAI,cAAc,CAAC;;UAGnB,IAAI,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,YAAY,EAAE,CAAC;;UAGpD,IAAI,QAAQ,GAAG;cACb,EAAE,EAAE,IAAI;cACR,MAAM,EAAE,IAAI;WACb,CAAC;;UAGF,IAAI,SAAS,CAAC;;UAGd,IAAI,CAAC,OAAO,CAAC,SAAS;eACnB,cAAc,CAAC,KAAK,CAAC,QAAQ,CAAC;eAC9B,IAAI,CAAC,CAAC,MAAM;cACX,IAAI,OAAO,KAAK,IAAI,EAAE;kBACpB,IAAI,CAAC,KAAK,GAAG,OAAO,CAAC,kBAAkB,EAAE,CAAC;kBAC1C,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC;kBAC3B,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;eACzC;mBAAM;kBACL,IAAI,CAAC,KAAK,GAAG,MAAM,CAAC;kBACpB,IAAI,CAAC,KAAK,CAAC,WAAW,GAAG,CAAC,CAAC;eAC5B;cACD,UAAU,EAAE,CAAC;WACd,CAAC;eACD,KAAK,CAAC,CAAC,GAAG;cACT,OAAO,CAAC,KAAK,CACX,8BAA8B,KAAK,CAAC,QAAQ,2FAA2F,CACxI,CAAC;cACF,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;WACpB,CAAC,CAAC;UAEL,MAAM,UAAU,GAAG;;cAEjB,IAAI,KAAK,CAAC,sBAAsB,EAAE;kBAChC,IAAI,CAAC,KAAK,CAAC,gBAAgB,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;eACjD;;cAGD,IAAI,CAAC,KAAK,CAAC,8BAA8B,IAAI,CAAC,KAAK,CAAC,sBAAsB,EAAE;kBAC1E,IAAI,CAAC,KAAK,CAAC,gBAAgB,CAAC,OAAO,EAAE,cAAc,CAAC,CAAC;eACtD;;cAGD,IAAI,OAAO,GAAG,EAAE,CAAC;cACjB,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,WAAW,CAAC,EAAE;kBACpC,IAAI,KAAK,CAAC,WAAW,CAAC,MAAM,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE;sBACpD,OAAO,GAAG,KAAK,CAAC,WAAW,CAAC;mBAC7B;uBAAM;sBACL,OAAO,CAAC,KAAK,CACX,2HAA2H,CAC5H,CAAC;mBACH;eACF;mBAAM;kBACL,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE;sBAC7C,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;mBACjC;eACF;cAED,IAAI,IAAI,GAAG,mDAAmD,CAAC;cAC/D,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE;kBAC7C,IAAI,GAAG,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,WAAW,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;kBAC5D,IAAI;sBACF,0GAA0G;0BAC1G,KAAK,CAAC,eAAe;0BACrB,GAAG;0BACH,KAAK,CAAC,iBAAiB;0BACvB,6CAA6C;0BAC7C,CAAC;0BACD,iBAAiB;0BACjB,CAAC;0BACD,IAAI;0BACJ,GAAG;0BACH,QAAQ,CAAC;eACZ;cACD,IAAI,IAAI,QAAQ,CAAC;;cAGjB,IAAI,KAAK,CAAC,MAAM,KAAK,IAAI,EAAE;kBACzB,IAAI,IAAI,KAAK,CAAC,MAAM,CAAC;eACtB;cAED,eAAe,CAAC,SAAS,GAAG,IAAI,CAAC;cAEjC,IAAI,KAAK,CAAC,8BAA8B,EAAE;kBACxC,cAAc,EAAE,CAAC;eAClB;mBAAM;kBACL,eAAe,EAAE,CAAC;eACnB;;cAGD,SAAS,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC;;cAG9B,IAAI,OAAO,KAAK,IAAI,EAAE;kBACpB,SAAS,GAAG,OAAO,CAAC,WAAW,CAAC;kBAChC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;eAC7B;mBAAM;kBACL,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;eACnB;;cAGD,IAAI,KAAK,CAAC,cAAc,KAAK,IAAI,EAAE;kBACjC,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,UAAU,CAAC;sBAChC,SAAS,EAAE,CAAC;mBACb,EAAE,KAAK,CAAC,cAAc,CAAC,CAAC;eAC1B;cAED,OAAO,EAAE,CAAC;WACX,CAAC;;UAGF,SAAS,cAAc,CAAC,MAAM;;cAE5B,IAAI,OAAO,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC;cAChC,IAAI,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,SAAS,CAAC,CAAC;cACzC,IAAI,OAAO,KAAK,IAAI,EAAE;kBACpB,OAAO,GAAG,OAAO,CAAC,WAAW,CAAC;kBAC9B,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,OAAO,GAAG,SAAS,IAAI,IAAI,CAAC,CAAC;eAC/C;cACD,QAAQ,CAAC,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC;cACnC,QAAQ,CAAC,EAAE,GAAG,EAAE,CAAC;;cAGjB,eAAe,EAAE,CAAC;cAElB,IAAI,KAAK,CAAC,mBAAmB,EAAE;kBAC7B,SAAS,EAAE,CAAC;eACb;WACF;;UAGD,MAAM,SAAS,GAAG;;cAEhB,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,gBAAgB,EAAE,CAAC;;;cAI1C,IAAI,OAAO,KAAK,IAAI,EAAE;kBACpB,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;eACnB;mBAAM;kBACL,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;eACpB;cAED,IAAI,CAAC,KAAK,CAAC,mBAAmB,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;cACnD,IAAI,CAAC,KAAK,CAAC,mBAAmB,CAAC,OAAO,EAAE,cAAc,CAAC,CAAC;;cAGxD,IAAI,UAAU,GAAG;kBACf,EAAE,EAAE,QAAQ,CAAC,EAAE;kBACf,QAAQ,EAAE,KAAK,CAAC,QAAQ;kBACxB,QAAQ,EAAE,QAAQ,CAAC,MAAM;eAC1B,CAAC;;cAGF,eAAe,CAAC,SAAS,GAAG,EAAE,CAAC;;cAG/B,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,UAAU,CAAC,CAAC;cAErC,cAAc,EAAE,CAAC;WAClB,CAAC;UAEF,SAAS,eAAe,CAAC,CAAC;cACxB,IAAI,MAAM,GAAG,CAAC,CAAC,aAAa,CAAC,YAAY,CAAC,aAAa,CAAC,CAAC;cACzD,cAAc,CAAC,MAAM,CAAC,CAAC;WACxB;UAED,SAAS,eAAe;cACtB,IAAI,IAAI,GAAG,QAAQ,CAAC,gBAAgB,CAAC,uCAAuC,CAAC,CAAC;cAC9E,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE;kBACpC,IAAI,MAAM,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;kBAC7C,IAAI,MAAM,EAAE;sBACV,MAAM,CAAC,QAAQ,GAAG,IAAI,CAAC;mBACxB;kBACD,IAAI,CAAC,CAAC,CAAC,CAAC,mBAAmB,CAAC,OAAO,EAAE,eAAe,CAAC,CAAC;eACvD;WACF;UAED,SAAS,cAAc;cACrB,IAAI,IAAI,GAAG,QAAQ,CAAC,gBAAgB,CAAC,uCAAuC,CAAC,CAAC;cAC9E,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE;kBACpC,IAAI,MAAM,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;kBAC7C,IAAI,MAAM,EAAE;sBACV,MAAM,CAAC,QAAQ,GAAG,KAAK,CAAC;mBACzB;kBACD,IAAI,CAAC,CAAC,CAAC,CAAC,gBAAgB,CAAC,OAAO,EAAE,eAAe,CAAC,CAAC;eACpD;WACF;UAED,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO;cACzB,cAAc,GAAG,OAAO,CAAC;WAC1B,CAAC,CAAC;OACJ;MAED,QAAQ,CACN,KAAsB,EACtB,eAAe,EACf,kBAAuB,EACvB,aAAyB;UAEzB,IAAI,eAAe,IAAI,WAAW,EAAE;cAClC,aAAa,EAAE,CAAC;cAChB,IAAI,CAAC,kBAAkB,CAAC,KAAK,EAAE,kBAAkB,CAAC,CAAC;WACpD;UACD,IAAI,eAAe,IAAI,QAAQ,EAAE;cAC/B,IAAI,CAAC,eAAe,CAAC,KAAK,EAAE,kBAAkB,EAAE,aAAa,CAAC,CAAC;WAChE;OACF;MAEO,sBAAsB,CAAC,KAAsB,EAAE,kBAAkB;UACvE,MAAM,YAAY,GAAG;cACnB,QAAQ,EAAE,KAAK,CAAC,QAAQ;cACxB,EAAE,EAAE,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,gBAAgB,CAAC,GAAG,EAAE,EAAE,EAAE,CAAC,GAAG,GAAG,EAAE,IAAI,CAAC;cACvE,QAAQ,EAAE,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,SAAS,CAAC,CAAC,EAAE,KAAK,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC;WAC5E,CAAC;UAEF,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,mBAAmB,CAAC,YAAY,EAAE,kBAAkB,CAAC,CAAC;UAE1F,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,+BAA+B,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;UAEpE,OAAO,IAAI,CAAC;OACb;MAEO,kBAAkB,CAAC,KAAsB,EAAE,kBAAkB;UACnE,MAAM,IAAI,GAAG,IAAI,CAAC,sBAAsB,CAAC,KAAK,EAAE,kBAAkB,CAAC,CAAC;UAEpE,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;OAChC;MAEO,eAAe,CAAC,KAAsB,EAAE,kBAAkB,EAAE,aAAyB;UAC3F,MAAM,IAAI,GAAG,IAAI,CAAC,sBAAsB,CAAC,KAAK,EAAE,kBAAkB,CAAC,CAAC;UAEpE,MAAM,eAAe,GAAG,IAAI,CAAC,OAAO,CAAC,iBAAiB,EAAE,CAAC;UAEzD,MAAM,OAAO,GAAG;cACd,IAAI,IAAI,CAAC,EAAE,KAAK,IAAI,EAAE;kBACpB,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,WAAW,CAChC,eAAe,CAAC,aAAa,CAAC,oBAAoB,IAAI,CAAC,QAAQ,WAAW,CAAC,EAC3E,IAAI,CAAC,EAAE,CACR,CAAC;eACH;WACF,CAAC;UAEF,IAAI,CAAC,KAAK,CAAC,eAAe,EAAE,KAAK,EAAE;cACjC,aAAa,EAAE,CAAC;cAChB,IAAI,CAAC,KAAK,CAAC,8BAA8B,EAAE;kBACzC,IAAI,CAAC,KAAK,CAAC,gBAAgB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;eAC/C;mBAAM;kBACL,OAAO,EAAE,CAAC;eACX;WACF,CAAC,CAAC;OACJ;;EAtQM,8BAAI,GAAG,IAAI;;;;;;;;"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
var jsPsychAudioButtonResponse=function(
|
|
1
|
+
var jsPsychAudioButtonResponse=function(t){"use strict";const e={name:"audio-button-response",parameters:{stimulus:{type:t.ParameterType.AUDIO,pretty_name:"Stimulus",default:void 0},choices:{type:t.ParameterType.STRING,pretty_name:"Choices",default:void 0,array:!0},button_html:{type:t.ParameterType.HTML_STRING,pretty_name:"Button HTML",default:'<button class="jspsych-btn">%choice%</button>',array:!0},prompt:{type:t.ParameterType.HTML_STRING,pretty_name:"Prompt",default:null},trial_duration:{type:t.ParameterType.INT,pretty_name:"Trial duration",default:null},margin_vertical:{type:t.ParameterType.STRING,pretty_name:"Margin vertical",default:"0px"},margin_horizontal:{type:t.ParameterType.STRING,pretty_name:"Margin horizontal",default:"8px"},response_ends_trial:{type:t.ParameterType.BOOL,pretty_name:"Response ends trial",default:!0},trial_ends_after_audio:{type:t.ParameterType.BOOL,pretty_name:"Trial ends after audio",default:!1},response_allowed_while_playing:{type:t.ParameterType.BOOL,pretty_name:"Response allowed while playing",default:!0}}};class a{constructor(t){this.jsPsych=t}trial(t,e,a){let i;var n,r=this.jsPsych.pluginAPI.audioContext(),s={rt:null,button:null};this.jsPsych.pluginAPI.getAudioBuffer(e.stimulus).then((t=>{null!==r?(this.audio=r.createBufferSource(),this.audio.buffer=t,this.audio.connect(r.destination)):(this.audio=t,this.audio.currentTime=0),o()})).catch((t=>{console.error(`Failed to load audio file "${e.stimulus}". Try checking the file path. We recommend using the preload plugin to load audio files.`),console.error(t)}));const o=()=>{e.trial_ends_after_audio&&this.audio.addEventListener("ended",l),e.response_allowed_while_playing||e.trial_ends_after_audio||this.audio.addEventListener("ended",c);var i=[];if(Array.isArray(e.button_html))e.button_html.length==e.choices.length?i=e.button_html:console.error("Error in audio-button-response plugin. The length of the button_html array does not equal the length of the choices array");else for(var s=0;s<e.choices.length;s++)i.push(e.button_html);var o='<div id="jspsych-audio-button-response-btngroup">';for(s=0;s<e.choices.length;s++){var u=i[s].replace(/%choice%/g,e.choices[s]);o+='<div class="jspsych-audio-button-response-button" style="cursor: pointer; display: inline-block; margin:'+e.margin_vertical+" "+e.margin_horizontal+'" id="jspsych-audio-button-response-button-'+s+'" data-choice="'+s+'">'+u+"</div>"}o+="</div>",null!==e.prompt&&(o+=e.prompt),t.innerHTML=o,e.response_allowed_while_playing?c():d(),n=performance.now(),null!==r?(n=r.currentTime,this.audio.start(n)):this.audio.play(),null!==e.trial_duration&&this.jsPsych.pluginAPI.setTimeout((()=>{l()}),e.trial_duration),a()};const l=()=>{this.jsPsych.pluginAPI.clearAllTimeouts(),null!==r?this.audio.stop():this.audio.pause(),this.audio.removeEventListener("ended",l),this.audio.removeEventListener("ended",c);var a={rt:s.rt,stimulus:e.stimulus,response:s.button};t.innerHTML="",this.jsPsych.finishTrial(a),i()};function u(t){!function(t){var a=performance.now(),i=Math.round(a-n);null!==r&&(a=r.currentTime,i=Math.round(1e3*(a-n))),s.button=parseInt(t),s.rt=i,d(),e.response_ends_trial&&l()}(t.currentTarget.getAttribute("data-choice"))}function d(){for(var t=document.querySelectorAll(".jspsych-audio-button-response-button"),e=0;e<t.length;e++){var a=t[e].querySelector("button");a&&(a.disabled=!0),t[e].removeEventListener("click",u)}}function c(){for(var t=document.querySelectorAll(".jspsych-audio-button-response-button"),e=0;e<t.length;e++){var a=t[e].querySelector("button");a&&(a.disabled=!1),t[e].addEventListener("click",u)}}return new Promise((t=>{i=t}))}simulate(t,e,a,i){"data-only"==e&&(i(),this.simulate_data_only(t,a)),"visual"==e&&this.simulate_visual(t,a,i)}create_simulation_data(t,e){const a={stimulus:t.stimulus,rt:this.jsPsych.randomization.sampleExGaussian(500,50,1/150,!0),response:this.jsPsych.randomization.randomInt(0,t.choices.length-1)},i=this.jsPsych.pluginAPI.mergeSimulationData(a,e);return this.jsPsych.pluginAPI.ensureSimulationDataConsistency(t,i),i}simulate_data_only(t,e){const a=this.create_simulation_data(t,e);this.jsPsych.finishTrial(a)}simulate_visual(t,e,a){const i=this.create_simulation_data(t,e),n=this.jsPsych.getDisplayElement(),r=()=>{null!==i.rt&&this.jsPsych.pluginAPI.clickTarget(n.querySelector(`div[data-choice="${i.response}"] button`),i.rt)};this.trial(n,t,(()=>{a(),t.response_allowed_while_playing?r():this.audio.addEventListener("ended",r)}))}}return a.info=e,a}(jsPsychModule);
|
|
2
2
|
//# sourceMappingURL=index.browser.min.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.browser.min.js","sources":["../src/index.ts"],"sourcesContent":["import { JsPsych, JsPsychPlugin, ParameterType, TrialType } from \"jspsych\";\n\nconst info = <const>{\n name: \"audio-button-response\",\n parameters: {\n /** The audio to be played. */\n stimulus: {\n type: ParameterType.AUDIO,\n pretty_name: \"Stimulus\",\n default: undefined,\n },\n /** Array containing the label(s) for the button(s). */\n choices: {\n type: ParameterType.STRING,\n pretty_name: \"Choices\",\n default: undefined,\n array: true,\n },\n /** The HTML for creating button. Can create own style. Use the \"%choice%\" string to indicate where the label from the choices parameter should be inserted. */\n button_html: {\n type: ParameterType.HTML_STRING,\n pretty_name: \"Button HTML\",\n default: '<button class=\"jspsych-btn\">%choice%</button>',\n array: true,\n },\n /** Any content here will be displayed below the stimulus. */\n prompt: {\n type: ParameterType.HTML_STRING,\n pretty_name: \"Prompt\",\n default: null,\n },\n /** The maximum duration to wait for a response. */\n trial_duration: {\n type: ParameterType.INT,\n pretty_name: \"Trial duration\",\n default: null,\n },\n /** Vertical margin of button. */\n margin_vertical: {\n type: ParameterType.STRING,\n pretty_name: \"Margin vertical\",\n default: \"0px\",\n },\n /** Horizontal margin of button. */\n margin_horizontal: {\n type: ParameterType.STRING,\n pretty_name: \"Margin horizontal\",\n default: \"8px\",\n },\n /** If true, the trial will end when user makes a response. */\n response_ends_trial: {\n type: ParameterType.BOOL,\n pretty_name: \"Response ends trial\",\n default: true,\n },\n /** If true, then the trial will end as soon as the audio file finishes playing. */\n trial_ends_after_audio: {\n type: ParameterType.BOOL,\n pretty_name: \"Trial ends after audio\",\n default: false,\n },\n /**\n * If true, then responses are allowed while the audio is playing.\n * If false, then the audio must finish playing before a response is accepted.\n */\n response_allowed_while_playing: {\n type: ParameterType.BOOL,\n pretty_name: \"Response allowed while playing\",\n default: true,\n },\n },\n};\n\ntype Info = typeof info;\n\n/**\n * **audio-button-response**\n *\n * jsPsych plugin for playing an audio file and getting a button response\n *\n * @author Kristin Diep\n * @see {@link https://www.jspsych.org/plugins/jspsych-audio-button-response/ audio-button-response plugin documentation on jspsych.org}\n */\nclass AudioButtonResponsePlugin implements JsPsychPlugin<Info> {\n static info = info;\n\n constructor(private jsPsych: JsPsych) {}\n\n trial(display_element: HTMLElement, trial: TrialType<Info>, on_load: () => void) {\n // hold the .resolve() function from the Promise that ends the trial\n let trial_complete;\n\n // setup stimulus\n var context = this.jsPsych.pluginAPI.audioContext();\n var audio;\n\n // store response\n var response = {\n rt: null,\n button: null,\n };\n\n // record webaudio context start time\n var startTime;\n\n // load audio file\n this.jsPsych.pluginAPI\n .getAudioBuffer(trial.stimulus)\n .then(function (buffer) {\n if (context !== null) {\n audio = context.createBufferSource();\n audio.buffer = buffer;\n audio.connect(context.destination);\n } else {\n audio = buffer;\n audio.currentTime = 0;\n }\n setupTrial();\n })\n .catch(function (err) {\n console.error(\n `Failed to load audio file \"${trial.stimulus}\". Try checking the file path. We recommend using the preload plugin to load audio files.`\n );\n console.error(err);\n });\n\n const setupTrial = () => {\n // set up end event if trial needs it\n if (trial.trial_ends_after_audio) {\n audio.addEventListener(\"ended\", end_trial);\n }\n\n // enable buttons after audio ends if necessary\n if (!trial.response_allowed_while_playing && !trial.trial_ends_after_audio) {\n audio.addEventListener(\"ended\", enable_buttons);\n }\n\n //display buttons\n var buttons = [];\n if (Array.isArray(trial.button_html)) {\n if (trial.button_html.length == trial.choices.length) {\n buttons = trial.button_html;\n } else {\n console.error(\n \"Error in audio-button-response plugin. The length of the button_html array does not equal the length of the choices array\"\n );\n }\n } else {\n for (var i = 0; i < trial.choices.length; i++) {\n buttons.push(trial.button_html);\n }\n }\n\n var html = '<div id=\"jspsych-audio-button-response-btngroup\">';\n for (var i = 0; i < trial.choices.length; i++) {\n var str = buttons[i].replace(/%choice%/g, trial.choices[i]);\n html +=\n '<div class=\"jspsych-audio-button-response-button\" style=\"cursor: pointer; display: inline-block; margin:' +\n trial.margin_vertical +\n \" \" +\n trial.margin_horizontal +\n '\" id=\"jspsych-audio-button-response-button-' +\n i +\n '\" data-choice=\"' +\n i +\n '\">' +\n str +\n \"</div>\";\n }\n html += \"</div>\";\n\n //show prompt if there is one\n if (trial.prompt !== null) {\n html += trial.prompt;\n }\n\n display_element.innerHTML = html;\n\n if (trial.response_allowed_while_playing) {\n enable_buttons();\n } else {\n disable_buttons();\n }\n\n // start time\n startTime = performance.now();\n\n // start audio\n if (context !== null) {\n startTime = context.currentTime;\n audio.start(startTime);\n } else {\n audio.play();\n }\n\n // end trial if time limit is set\n if (trial.trial_duration !== null) {\n this.jsPsych.pluginAPI.setTimeout(function () {\n end_trial();\n }, trial.trial_duration);\n }\n\n on_load();\n };\n\n // function to handle responses by the subject\n function after_response(choice) {\n // measure rt\n var endTime = performance.now();\n var rt = Math.round(endTime - startTime);\n if (context !== null) {\n endTime = context.currentTime;\n rt = Math.round((endTime - startTime) * 1000);\n }\n response.button = parseInt(choice);\n response.rt = rt;\n\n // disable all the buttons after a response\n disable_buttons();\n\n if (trial.response_ends_trial) {\n end_trial();\n }\n }\n\n // function to end trial when it is time\n const end_trial = () => {\n // kill any remaining setTimeout handlers\n this.jsPsych.pluginAPI.clearAllTimeouts();\n\n // stop the audio file if it is playing\n // remove end event listeners if they exist\n if (context !== null) {\n audio.stop();\n } else {\n audio.pause();\n }\n\n audio.removeEventListener(\"ended\", end_trial);\n audio.removeEventListener(\"ended\", enable_buttons);\n\n // gather the data to store for the trial\n var trial_data = {\n rt: response.rt,\n stimulus: trial.stimulus,\n response: response.button,\n };\n\n // clear the display\n display_element.innerHTML = \"\";\n\n // move on to the next trial\n this.jsPsych.finishTrial(trial_data);\n\n trial_complete();\n };\n\n function button_response(e) {\n var choice = e.currentTarget.getAttribute(\"data-choice\"); // don't use dataset for jsdom compatibility\n after_response(choice);\n }\n\n function disable_buttons() {\n var btns = document.querySelectorAll(\".jspsych-audio-button-response-button\");\n for (var i = 0; i < btns.length; i++) {\n var btn_el = btns[i].querySelector(\"button\");\n if (btn_el) {\n btn_el.disabled = true;\n }\n btns[i].removeEventListener(\"click\", button_response);\n }\n }\n\n function enable_buttons() {\n var btns = document.querySelectorAll(\".jspsych-audio-button-response-button\");\n for (var i = 0; i < btns.length; i++) {\n var btn_el = btns[i].querySelector(\"button\");\n if (btn_el) {\n btn_el.disabled = false;\n }\n btns[i].addEventListener(\"click\", button_response);\n }\n }\n\n return new Promise((resolve) => {\n trial_complete = resolve;\n });\n }\n}\n\nexport default AudioButtonResponsePlugin;\n"],"names":["info","name","parameters","stimulus","type","ParameterType","AUDIO","pretty_name","default","undefined","choices","STRING","array","button_html","HTML_STRING","prompt","trial_duration","INT","margin_vertical","margin_horizontal","response_ends_trial","BOOL","trial_ends_after_audio","response_allowed_while_playing","AudioButtonResponsePlugin","constructor","jsPsych","this","trial","display_element","on_load","trial_complete","audio","startTime","context","pluginAPI","audioContext","response","rt","button","getAudioBuffer","then","buffer","createBufferSource","connect","destination","currentTime","setupTrial","catch","err","console","error","addEventListener","end_trial","enable_buttons","buttons","Array","isArray","length","i","push","html","str","replace","innerHTML","disable_buttons","performance","now","start","play","setTimeout","clearAllTimeouts","stop","pause","removeEventListener","trial_data","finishTrial","button_response","e","choice","endTime","Math","round","parseInt","after_response","currentTarget","getAttribute","btns","document","querySelectorAll","btn_el","querySelector","disabled","Promise","resolve"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.browser.min.js","sources":["../src/index.ts"],"sourcesContent":["import { JsPsych, JsPsychPlugin, ParameterType, TrialType } from \"jspsych\";\n\nconst info = <const>{\n name: \"audio-button-response\",\n parameters: {\n /** The audio to be played. */\n stimulus: {\n type: ParameterType.AUDIO,\n pretty_name: \"Stimulus\",\n default: undefined,\n },\n /** Array containing the label(s) for the button(s). */\n choices: {\n type: ParameterType.STRING,\n pretty_name: \"Choices\",\n default: undefined,\n array: true,\n },\n /** The HTML for creating button. Can create own style. Use the \"%choice%\" string to indicate where the label from the choices parameter should be inserted. */\n button_html: {\n type: ParameterType.HTML_STRING,\n pretty_name: \"Button HTML\",\n default: '<button class=\"jspsych-btn\">%choice%</button>',\n array: true,\n },\n /** Any content here will be displayed below the stimulus. */\n prompt: {\n type: ParameterType.HTML_STRING,\n pretty_name: \"Prompt\",\n default: null,\n },\n /** The maximum duration to wait for a response. */\n trial_duration: {\n type: ParameterType.INT,\n pretty_name: \"Trial duration\",\n default: null,\n },\n /** Vertical margin of button. */\n margin_vertical: {\n type: ParameterType.STRING,\n pretty_name: \"Margin vertical\",\n default: \"0px\",\n },\n /** Horizontal margin of button. */\n margin_horizontal: {\n type: ParameterType.STRING,\n pretty_name: \"Margin horizontal\",\n default: \"8px\",\n },\n /** If true, the trial will end when user makes a response. */\n response_ends_trial: {\n type: ParameterType.BOOL,\n pretty_name: \"Response ends trial\",\n default: true,\n },\n /** If true, then the trial will end as soon as the audio file finishes playing. */\n trial_ends_after_audio: {\n type: ParameterType.BOOL,\n pretty_name: \"Trial ends after audio\",\n default: false,\n },\n /**\n * If true, then responses are allowed while the audio is playing.\n * If false, then the audio must finish playing before a response is accepted.\n */\n response_allowed_while_playing: {\n type: ParameterType.BOOL,\n pretty_name: \"Response allowed while playing\",\n default: true,\n },\n },\n};\n\ntype Info = typeof info;\n\n/**\n * **audio-button-response**\n *\n * jsPsych plugin for playing an audio file and getting a button response\n *\n * @author Kristin Diep\n * @see {@link https://www.jspsych.org/plugins/jspsych-audio-button-response/ audio-button-response plugin documentation on jspsych.org}\n */\nclass AudioButtonResponsePlugin implements JsPsychPlugin<Info> {\n static info = info;\n private audio;\n\n constructor(private jsPsych: JsPsych) {}\n\n trial(display_element: HTMLElement, trial: TrialType<Info>, on_load: () => void) {\n // hold the .resolve() function from the Promise that ends the trial\n let trial_complete;\n\n // setup stimulus\n var context = this.jsPsych.pluginAPI.audioContext();\n\n // store response\n var response = {\n rt: null,\n button: null,\n };\n\n // record webaudio context start time\n var startTime;\n\n // load audio file\n this.jsPsych.pluginAPI\n .getAudioBuffer(trial.stimulus)\n .then((buffer) => {\n if (context !== null) {\n this.audio = context.createBufferSource();\n this.audio.buffer = buffer;\n this.audio.connect(context.destination);\n } else {\n this.audio = buffer;\n this.audio.currentTime = 0;\n }\n setupTrial();\n })\n .catch((err) => {\n console.error(\n `Failed to load audio file \"${trial.stimulus}\". Try checking the file path. We recommend using the preload plugin to load audio files.`\n );\n console.error(err);\n });\n\n const setupTrial = () => {\n // set up end event if trial needs it\n if (trial.trial_ends_after_audio) {\n this.audio.addEventListener(\"ended\", end_trial);\n }\n\n // enable buttons after audio ends if necessary\n if (!trial.response_allowed_while_playing && !trial.trial_ends_after_audio) {\n this.audio.addEventListener(\"ended\", enable_buttons);\n }\n\n //display buttons\n var buttons = [];\n if (Array.isArray(trial.button_html)) {\n if (trial.button_html.length == trial.choices.length) {\n buttons = trial.button_html;\n } else {\n console.error(\n \"Error in audio-button-response plugin. The length of the button_html array does not equal the length of the choices array\"\n );\n }\n } else {\n for (var i = 0; i < trial.choices.length; i++) {\n buttons.push(trial.button_html);\n }\n }\n\n var html = '<div id=\"jspsych-audio-button-response-btngroup\">';\n for (var i = 0; i < trial.choices.length; i++) {\n var str = buttons[i].replace(/%choice%/g, trial.choices[i]);\n html +=\n '<div class=\"jspsych-audio-button-response-button\" style=\"cursor: pointer; display: inline-block; margin:' +\n trial.margin_vertical +\n \" \" +\n trial.margin_horizontal +\n '\" id=\"jspsych-audio-button-response-button-' +\n i +\n '\" data-choice=\"' +\n i +\n '\">' +\n str +\n \"</div>\";\n }\n html += \"</div>\";\n\n //show prompt if there is one\n if (trial.prompt !== null) {\n html += trial.prompt;\n }\n\n display_element.innerHTML = html;\n\n if (trial.response_allowed_while_playing) {\n enable_buttons();\n } else {\n disable_buttons();\n }\n\n // start time\n startTime = performance.now();\n\n // start audio\n if (context !== null) {\n startTime = context.currentTime;\n this.audio.start(startTime);\n } else {\n this.audio.play();\n }\n\n // end trial if time limit is set\n if (trial.trial_duration !== null) {\n this.jsPsych.pluginAPI.setTimeout(() => {\n end_trial();\n }, trial.trial_duration);\n }\n\n on_load();\n };\n\n // function to handle responses by the subject\n function after_response(choice) {\n // measure rt\n var endTime = performance.now();\n var rt = Math.round(endTime - startTime);\n if (context !== null) {\n endTime = context.currentTime;\n rt = Math.round((endTime - startTime) * 1000);\n }\n response.button = parseInt(choice);\n response.rt = rt;\n\n // disable all the buttons after a response\n disable_buttons();\n\n if (trial.response_ends_trial) {\n end_trial();\n }\n }\n\n // function to end trial when it is time\n const end_trial = () => {\n // kill any remaining setTimeout handlers\n this.jsPsych.pluginAPI.clearAllTimeouts();\n\n // stop the audio file if it is playing\n // remove end event listeners if they exist\n if (context !== null) {\n this.audio.stop();\n } else {\n this.audio.pause();\n }\n\n this.audio.removeEventListener(\"ended\", end_trial);\n this.audio.removeEventListener(\"ended\", enable_buttons);\n\n // gather the data to store for the trial\n var trial_data = {\n rt: response.rt,\n stimulus: trial.stimulus,\n response: response.button,\n };\n\n // clear the display\n display_element.innerHTML = \"\";\n\n // move on to the next trial\n this.jsPsych.finishTrial(trial_data);\n\n trial_complete();\n };\n\n function button_response(e) {\n var choice = e.currentTarget.getAttribute(\"data-choice\"); // don't use dataset for jsdom compatibility\n after_response(choice);\n }\n\n function disable_buttons() {\n var btns = document.querySelectorAll(\".jspsych-audio-button-response-button\");\n for (var i = 0; i < btns.length; i++) {\n var btn_el = btns[i].querySelector(\"button\");\n if (btn_el) {\n btn_el.disabled = true;\n }\n btns[i].removeEventListener(\"click\", button_response);\n }\n }\n\n function enable_buttons() {\n var btns = document.querySelectorAll(\".jspsych-audio-button-response-button\");\n for (var i = 0; i < btns.length; i++) {\n var btn_el = btns[i].querySelector(\"button\");\n if (btn_el) {\n btn_el.disabled = false;\n }\n btns[i].addEventListener(\"click\", button_response);\n }\n }\n\n return new Promise((resolve) => {\n trial_complete = resolve;\n });\n }\n\n simulate(\n trial: TrialType<Info>,\n simulation_mode,\n simulation_options: any,\n load_callback: () => void\n ) {\n if (simulation_mode == \"data-only\") {\n load_callback();\n this.simulate_data_only(trial, simulation_options);\n }\n if (simulation_mode == \"visual\") {\n this.simulate_visual(trial, simulation_options, load_callback);\n }\n }\n\n private create_simulation_data(trial: TrialType<Info>, simulation_options) {\n const default_data = {\n stimulus: trial.stimulus,\n rt: this.jsPsych.randomization.sampleExGaussian(500, 50, 1 / 150, true),\n response: this.jsPsych.randomization.randomInt(0, trial.choices.length - 1),\n };\n\n const data = this.jsPsych.pluginAPI.mergeSimulationData(default_data, simulation_options);\n\n this.jsPsych.pluginAPI.ensureSimulationDataConsistency(trial, data);\n\n return data;\n }\n\n private simulate_data_only(trial: TrialType<Info>, simulation_options) {\n const data = this.create_simulation_data(trial, simulation_options);\n\n this.jsPsych.finishTrial(data);\n }\n\n private simulate_visual(trial: TrialType<Info>, simulation_options, load_callback: () => void) {\n const data = this.create_simulation_data(trial, simulation_options);\n\n const display_element = this.jsPsych.getDisplayElement();\n\n const respond = () => {\n if (data.rt !== null) {\n this.jsPsych.pluginAPI.clickTarget(\n display_element.querySelector(`div[data-choice=\"${data.response}\"] button`),\n data.rt\n );\n }\n };\n\n this.trial(display_element, trial, () => {\n load_callback();\n if (!trial.response_allowed_while_playing) {\n this.audio.addEventListener(\"ended\", respond);\n } else {\n respond();\n }\n });\n }\n}\n\nexport default AudioButtonResponsePlugin;\n"],"names":["info","name","parameters","stimulus","type","ParameterType","AUDIO","pretty_name","default","undefined","choices","STRING","array","button_html","HTML_STRING","prompt","trial_duration","INT","margin_vertical","margin_horizontal","response_ends_trial","BOOL","trial_ends_after_audio","response_allowed_while_playing","AudioButtonResponsePlugin","constructor","jsPsych","this","trial","display_element","on_load","trial_complete","startTime","context","pluginAPI","audioContext","response","rt","button","getAudioBuffer","then","buffer","audio","createBufferSource","connect","destination","currentTime","setupTrial","catch","err","console","error","addEventListener","end_trial","enable_buttons","buttons","Array","isArray","length","i","push","html","str","replace","innerHTML","disable_buttons","performance","now","start","play","setTimeout","clearAllTimeouts","stop","pause","removeEventListener","trial_data","finishTrial","button_response","e","choice","endTime","Math","round","parseInt","after_response","currentTarget","getAttribute","btns","document","querySelectorAll","btn_el","querySelector","disabled","Promise","resolve","simulate","simulation_mode","simulation_options","load_callback","simulate_data_only","simulate_visual","create_simulation_data","default_data","randomization","sampleExGaussian","randomInt","data","mergeSimulationData","ensureSimulationDataConsistency","getDisplayElement","respond","clickTarget"],"mappings":"wDAEA,MAAMA,EAAc,CAClBC,KAAM,wBACNC,WAAY,CAEVC,SAAU,CACRC,KAAMC,gBAAcC,MACpBC,YAAa,WACbC,aAASC,GAGXC,QAAS,CACPN,KAAMC,gBAAcM,OACpBJ,YAAa,UACbC,aAASC,EACTG,OAAO,GAGTC,YAAa,CACXT,KAAMC,gBAAcS,YACpBP,YAAa,cACbC,QAAS,gDACTI,OAAO,GAGTG,OAAQ,CACNX,KAAMC,gBAAcS,YACpBP,YAAa,SACbC,QAAS,MAGXQ,eAAgB,CACdZ,KAAMC,gBAAcY,IACpBV,YAAa,iBACbC,QAAS,MAGXU,gBAAiB,CACfd,KAAMC,gBAAcM,OACpBJ,YAAa,kBACbC,QAAS,OAGXW,kBAAmB,CACjBf,KAAMC,gBAAcM,OACpBJ,YAAa,oBACbC,QAAS,OAGXY,oBAAqB,CACnBhB,KAAMC,gBAAcgB,KACpBd,YAAa,sBACbC,SAAS,GAGXc,uBAAwB,CACtBlB,KAAMC,gBAAcgB,KACpBd,YAAa,yBACbC,SAAS,GAMXe,+BAAgC,CAC9BnB,KAAMC,gBAAcgB,KACpBd,YAAa,iCACbC,SAAS,KAef,MAAMgB,EAIJC,YAAoBC,GAAAC,aAAAD,EAEpBE,MAAMC,EAA8BD,EAAwBE,GAE1D,IAAIC,EAGJ,IASIC,EATAC,EAAUN,KAAKD,QAAQQ,UAAUC,eAGjCC,EAAW,CACbC,GAAI,KACJC,OAAQ,MAOVX,KAAKD,QAAQQ,UACVK,eAAeX,EAAMzB,UACrBqC,MAAMC,IACW,OAAZR,GACFN,KAAKe,MAAQT,EAAQU,qBACrBhB,KAAKe,MAAMD,OAASA,EACpBd,KAAKe,MAAME,QAAQX,EAAQY,eAE3BlB,KAAKe,MAAQD,EACbd,KAAKe,MAAMI,YAAc,GAE3BC,OAEDC,OAAOC,IACNC,QAAQC,MACN,8BAA8BvB,EAAMzB,qGAEtC+C,QAAQC,MAAMF,MAGlB,MAAMF,EAAa,KAEbnB,EAAMN,wBACRK,KAAKe,MAAMU,iBAAiB,QAASC,GAIlCzB,EAAML,gCAAmCK,EAAMN,wBAClDK,KAAKe,MAAMU,iBAAiB,QAASE,GAIvC,IAAIC,EAAU,GACd,GAAIC,MAAMC,QAAQ7B,EAAMf,aAClBe,EAAMf,YAAY6C,QAAU9B,EAAMlB,QAAQgD,OAC5CH,EAAU3B,EAAMf,YAEhBqC,QAAQC,MACN,kIAIJ,IAAK,IAAIQ,EAAI,EAAGA,EAAI/B,EAAMlB,QAAQgD,OAAQC,IACxCJ,EAAQK,KAAKhC,EAAMf,aAIvB,IAAIgD,EAAO,oDACX,IAASF,EAAI,EAAGA,EAAI/B,EAAMlB,QAAQgD,OAAQC,IAAK,CAC7C,IAAIG,EAAMP,EAAQI,GAAGI,QAAQ,YAAanC,EAAMlB,QAAQiD,IACxDE,GACE,2GACAjC,EAAMV,gBACN,IACAU,EAAMT,kBACN,8CACAwC,EACA,kBACAA,EACA,KACAG,EACA,SAEJD,GAAQ,SAGa,OAAjBjC,EAAMb,SACR8C,GAAQjC,EAAMb,QAGhBc,EAAgBmC,UAAYH,EAExBjC,EAAML,+BACR+B,IAEAW,IAIFjC,EAAYkC,YAAYC,MAGR,OAAZlC,GACFD,EAAYC,EAAQa,YACpBnB,KAAKe,MAAM0B,MAAMpC,IAEjBL,KAAKe,MAAM2B,OAIgB,OAAzBzC,EAAMZ,gBACRW,KAAKD,QAAQQ,UAAUoC,YAAW,KAChCjB,MACCzB,EAAMZ,gBAGXc,KAwBF,MAAMuB,EAAY,KAEhB1B,KAAKD,QAAQQ,UAAUqC,mBAIP,OAAZtC,EACFN,KAAKe,MAAM8B,OAEX7C,KAAKe,MAAM+B,QAGb9C,KAAKe,MAAMgC,oBAAoB,QAASrB,GACxC1B,KAAKe,MAAMgC,oBAAoB,QAASpB,GAGxC,IAAIqB,EAAa,CACftC,GAAID,EAASC,GACblC,SAAUyB,EAAMzB,SAChBiC,SAAUA,EAASE,QAIrBT,EAAgBmC,UAAY,GAG5BrC,KAAKD,QAAQkD,YAAYD,GAEzB5C,KAGF,SAAS8C,EAAgBC,IAnDzB,SAAwBC,GAEtB,IAAIC,EAAUd,YAAYC,MACtB9B,EAAK4C,KAAKC,MAAMF,EAAUhD,GACd,OAAZC,IACF+C,EAAU/C,EAAQa,YAClBT,EAAK4C,KAAKC,MAA8B,KAAvBF,EAAUhD,KAE7BI,EAASE,OAAS6C,SAASJ,GAC3B3C,EAASC,GAAKA,EAGd4B,IAEIrC,EAAMR,qBACRiC,IAsCF+B,CADaN,EAAEO,cAAcC,aAAa,gBAI5C,SAASrB,IAEP,IADA,IAAIsB,EAAOC,SAASC,iBAAiB,yCAC5B9B,EAAI,EAAGA,EAAI4B,EAAK7B,OAAQC,IAAK,CACpC,IAAI+B,EAASH,EAAK5B,GAAGgC,cAAc,UAC/BD,IACFA,EAAOE,UAAW,GAEpBL,EAAK5B,GAAGe,oBAAoB,QAASG,IAIzC,SAASvB,IAEP,IADA,IAAIiC,EAAOC,SAASC,iBAAiB,yCAC5B9B,EAAI,EAAGA,EAAI4B,EAAK7B,OAAQC,IAAK,CACpC,IAAI+B,EAASH,EAAK5B,GAAGgC,cAAc,UAC/BD,IACFA,EAAOE,UAAW,GAEpBL,EAAK5B,GAAGP,iBAAiB,QAASyB,IAItC,OAAO,IAAIgB,SAASC,IAClB/D,EAAiB+D,KAIrBC,SACEnE,EACAoE,EACAC,EACAC,GAEuB,aAAnBF,IACFE,IACAvE,KAAKwE,mBAAmBvE,EAAOqE,IAEV,UAAnBD,GACFrE,KAAKyE,gBAAgBxE,EAAOqE,EAAoBC,GAI5CG,uBAAuBzE,EAAwBqE,GACrD,MAAMK,EAAe,CACnBnG,SAAUyB,EAAMzB,SAChBkC,GAAIV,KAAKD,QAAQ6E,cAAcC,iBAAiB,IAAK,GAAI,EAAI,KAAK,GAClEpE,SAAUT,KAAKD,QAAQ6E,cAAcE,UAAU,EAAG7E,EAAMlB,QAAQgD,OAAS,IAGrEgD,EAAO/E,KAAKD,QAAQQ,UAAUyE,oBAAoBL,EAAcL,GAItE,OAFAtE,KAAKD,QAAQQ,UAAU0E,gCAAgChF,EAAO8E,GAEvDA,EAGDP,mBAAmBvE,EAAwBqE,GACjD,MAAMS,EAAO/E,KAAK0E,uBAAuBzE,EAAOqE,GAEhDtE,KAAKD,QAAQkD,YAAY8B,GAGnBN,gBAAgBxE,EAAwBqE,EAAoBC,GAClE,MAAMQ,EAAO/E,KAAK0E,uBAAuBzE,EAAOqE,GAE1CpE,EAAkBF,KAAKD,QAAQmF,oBAE/BC,EAAU,KACE,OAAZJ,EAAKrE,IACPV,KAAKD,QAAQQ,UAAU6E,YACrBlF,EAAgB8D,cAAc,oBAAoBe,EAAKtE,qBACvDsE,EAAKrE,KAKXV,KAAKC,MAAMC,EAAiBD,GAAO,KACjCsE,IACKtE,EAAML,+BAGTuF,IAFAnF,KAAKe,MAAMU,iBAAiB,QAAS0D,cAjQpCtF,OAAOxB"}
|
package/dist/index.cjs
CHANGED
|
@@ -89,7 +89,6 @@ class AudioButtonResponsePlugin {
|
|
|
89
89
|
let trial_complete;
|
|
90
90
|
// setup stimulus
|
|
91
91
|
var context = this.jsPsych.pluginAPI.audioContext();
|
|
92
|
-
var audio;
|
|
93
92
|
// store response
|
|
94
93
|
var response = {
|
|
95
94
|
rt: null,
|
|
@@ -100,30 +99,30 @@ class AudioButtonResponsePlugin {
|
|
|
100
99
|
// load audio file
|
|
101
100
|
this.jsPsych.pluginAPI
|
|
102
101
|
.getAudioBuffer(trial.stimulus)
|
|
103
|
-
.then(
|
|
102
|
+
.then((buffer) => {
|
|
104
103
|
if (context !== null) {
|
|
105
|
-
audio = context.createBufferSource();
|
|
106
|
-
audio.buffer = buffer;
|
|
107
|
-
audio.connect(context.destination);
|
|
104
|
+
this.audio = context.createBufferSource();
|
|
105
|
+
this.audio.buffer = buffer;
|
|
106
|
+
this.audio.connect(context.destination);
|
|
108
107
|
}
|
|
109
108
|
else {
|
|
110
|
-
audio = buffer;
|
|
111
|
-
audio.currentTime = 0;
|
|
109
|
+
this.audio = buffer;
|
|
110
|
+
this.audio.currentTime = 0;
|
|
112
111
|
}
|
|
113
112
|
setupTrial();
|
|
114
113
|
})
|
|
115
|
-
.catch(
|
|
114
|
+
.catch((err) => {
|
|
116
115
|
console.error(`Failed to load audio file "${trial.stimulus}". Try checking the file path. We recommend using the preload plugin to load audio files.`);
|
|
117
116
|
console.error(err);
|
|
118
117
|
});
|
|
119
118
|
const setupTrial = () => {
|
|
120
119
|
// set up end event if trial needs it
|
|
121
120
|
if (trial.trial_ends_after_audio) {
|
|
122
|
-
audio.addEventListener("ended", end_trial);
|
|
121
|
+
this.audio.addEventListener("ended", end_trial);
|
|
123
122
|
}
|
|
124
123
|
// enable buttons after audio ends if necessary
|
|
125
124
|
if (!trial.response_allowed_while_playing && !trial.trial_ends_after_audio) {
|
|
126
|
-
audio.addEventListener("ended", enable_buttons);
|
|
125
|
+
this.audio.addEventListener("ended", enable_buttons);
|
|
127
126
|
}
|
|
128
127
|
//display buttons
|
|
129
128
|
var buttons = [];
|
|
@@ -173,14 +172,14 @@ class AudioButtonResponsePlugin {
|
|
|
173
172
|
// start audio
|
|
174
173
|
if (context !== null) {
|
|
175
174
|
startTime = context.currentTime;
|
|
176
|
-
audio.start(startTime);
|
|
175
|
+
this.audio.start(startTime);
|
|
177
176
|
}
|
|
178
177
|
else {
|
|
179
|
-
audio.play();
|
|
178
|
+
this.audio.play();
|
|
180
179
|
}
|
|
181
180
|
// end trial if time limit is set
|
|
182
181
|
if (trial.trial_duration !== null) {
|
|
183
|
-
this.jsPsych.pluginAPI.setTimeout(
|
|
182
|
+
this.jsPsych.pluginAPI.setTimeout(() => {
|
|
184
183
|
end_trial();
|
|
185
184
|
}, trial.trial_duration);
|
|
186
185
|
}
|
|
@@ -210,13 +209,13 @@ class AudioButtonResponsePlugin {
|
|
|
210
209
|
// stop the audio file if it is playing
|
|
211
210
|
// remove end event listeners if they exist
|
|
212
211
|
if (context !== null) {
|
|
213
|
-
audio.stop();
|
|
212
|
+
this.audio.stop();
|
|
214
213
|
}
|
|
215
214
|
else {
|
|
216
|
-
audio.pause();
|
|
215
|
+
this.audio.pause();
|
|
217
216
|
}
|
|
218
|
-
audio.removeEventListener("ended", end_trial);
|
|
219
|
-
audio.removeEventListener("ended", enable_buttons);
|
|
217
|
+
this.audio.removeEventListener("ended", end_trial);
|
|
218
|
+
this.audio.removeEventListener("ended", enable_buttons);
|
|
220
219
|
// gather the data to store for the trial
|
|
221
220
|
var trial_data = {
|
|
222
221
|
rt: response.rt,
|
|
@@ -257,6 +256,47 @@ class AudioButtonResponsePlugin {
|
|
|
257
256
|
trial_complete = resolve;
|
|
258
257
|
});
|
|
259
258
|
}
|
|
259
|
+
simulate(trial, simulation_mode, simulation_options, load_callback) {
|
|
260
|
+
if (simulation_mode == "data-only") {
|
|
261
|
+
load_callback();
|
|
262
|
+
this.simulate_data_only(trial, simulation_options);
|
|
263
|
+
}
|
|
264
|
+
if (simulation_mode == "visual") {
|
|
265
|
+
this.simulate_visual(trial, simulation_options, load_callback);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
create_simulation_data(trial, simulation_options) {
|
|
269
|
+
const default_data = {
|
|
270
|
+
stimulus: trial.stimulus,
|
|
271
|
+
rt: this.jsPsych.randomization.sampleExGaussian(500, 50, 1 / 150, true),
|
|
272
|
+
response: this.jsPsych.randomization.randomInt(0, trial.choices.length - 1),
|
|
273
|
+
};
|
|
274
|
+
const data = this.jsPsych.pluginAPI.mergeSimulationData(default_data, simulation_options);
|
|
275
|
+
this.jsPsych.pluginAPI.ensureSimulationDataConsistency(trial, data);
|
|
276
|
+
return data;
|
|
277
|
+
}
|
|
278
|
+
simulate_data_only(trial, simulation_options) {
|
|
279
|
+
const data = this.create_simulation_data(trial, simulation_options);
|
|
280
|
+
this.jsPsych.finishTrial(data);
|
|
281
|
+
}
|
|
282
|
+
simulate_visual(trial, simulation_options, load_callback) {
|
|
283
|
+
const data = this.create_simulation_data(trial, simulation_options);
|
|
284
|
+
const display_element = this.jsPsych.getDisplayElement();
|
|
285
|
+
const respond = () => {
|
|
286
|
+
if (data.rt !== null) {
|
|
287
|
+
this.jsPsych.pluginAPI.clickTarget(display_element.querySelector(`div[data-choice="${data.response}"] button`), data.rt);
|
|
288
|
+
}
|
|
289
|
+
};
|
|
290
|
+
this.trial(display_element, trial, () => {
|
|
291
|
+
load_callback();
|
|
292
|
+
if (!trial.response_allowed_while_playing) {
|
|
293
|
+
this.audio.addEventListener("ended", respond);
|
|
294
|
+
}
|
|
295
|
+
else {
|
|
296
|
+
respond();
|
|
297
|
+
}
|
|
298
|
+
});
|
|
299
|
+
}
|
|
260
300
|
}
|
|
261
301
|
AudioButtonResponsePlugin.info = info;
|
|
262
302
|
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","sources":["../src/index.ts"],"sourcesContent":["import { JsPsych, JsPsychPlugin, ParameterType, TrialType } from \"jspsych\";\n\nconst info = <const>{\n name: \"audio-button-response\",\n parameters: {\n /** The audio to be played. */\n stimulus: {\n type: ParameterType.AUDIO,\n pretty_name: \"Stimulus\",\n default: undefined,\n },\n /** Array containing the label(s) for the button(s). */\n choices: {\n type: ParameterType.STRING,\n pretty_name: \"Choices\",\n default: undefined,\n array: true,\n },\n /** The HTML for creating button. Can create own style. Use the \"%choice%\" string to indicate where the label from the choices parameter should be inserted. */\n button_html: {\n type: ParameterType.HTML_STRING,\n pretty_name: \"Button HTML\",\n default: '<button class=\"jspsych-btn\">%choice%</button>',\n array: true,\n },\n /** Any content here will be displayed below the stimulus. */\n prompt: {\n type: ParameterType.HTML_STRING,\n pretty_name: \"Prompt\",\n default: null,\n },\n /** The maximum duration to wait for a response. */\n trial_duration: {\n type: ParameterType.INT,\n pretty_name: \"Trial duration\",\n default: null,\n },\n /** Vertical margin of button. */\n margin_vertical: {\n type: ParameterType.STRING,\n pretty_name: \"Margin vertical\",\n default: \"0px\",\n },\n /** Horizontal margin of button. */\n margin_horizontal: {\n type: ParameterType.STRING,\n pretty_name: \"Margin horizontal\",\n default: \"8px\",\n },\n /** If true, the trial will end when user makes a response. */\n response_ends_trial: {\n type: ParameterType.BOOL,\n pretty_name: \"Response ends trial\",\n default: true,\n },\n /** If true, then the trial will end as soon as the audio file finishes playing. */\n trial_ends_after_audio: {\n type: ParameterType.BOOL,\n pretty_name: \"Trial ends after audio\",\n default: false,\n },\n /**\n * If true, then responses are allowed while the audio is playing.\n * If false, then the audio must finish playing before a response is accepted.\n */\n response_allowed_while_playing: {\n type: ParameterType.BOOL,\n pretty_name: \"Response allowed while playing\",\n default: true,\n },\n },\n};\n\ntype Info = typeof info;\n\n/**\n * **audio-button-response**\n *\n * jsPsych plugin for playing an audio file and getting a button response\n *\n * @author Kristin Diep\n * @see {@link https://www.jspsych.org/plugins/jspsych-audio-button-response/ audio-button-response plugin documentation on jspsych.org}\n */\nclass AudioButtonResponsePlugin implements JsPsychPlugin<Info> {\n static info = info;\n\n constructor(private jsPsych: JsPsych) {}\n\n trial(display_element: HTMLElement, trial: TrialType<Info>, on_load: () => void) {\n // hold the .resolve() function from the Promise that ends the trial\n let trial_complete;\n\n // setup stimulus\n var context = this.jsPsych.pluginAPI.audioContext();\n var audio;\n\n // store response\n var response = {\n rt: null,\n button: null,\n };\n\n // record webaudio context start time\n var startTime;\n\n // load audio file\n this.jsPsych.pluginAPI\n .getAudioBuffer(trial.stimulus)\n .then(function (buffer) {\n if (context !== null) {\n audio = context.createBufferSource();\n audio.buffer = buffer;\n audio.connect(context.destination);\n } else {\n audio = buffer;\n audio.currentTime = 0;\n }\n setupTrial();\n })\n .catch(function (err) {\n console.error(\n `Failed to load audio file \"${trial.stimulus}\". Try checking the file path. We recommend using the preload plugin to load audio files.`\n );\n console.error(err);\n });\n\n const setupTrial = () => {\n // set up end event if trial needs it\n if (trial.trial_ends_after_audio) {\n audio.addEventListener(\"ended\", end_trial);\n }\n\n // enable buttons after audio ends if necessary\n if (!trial.response_allowed_while_playing && !trial.trial_ends_after_audio) {\n audio.addEventListener(\"ended\", enable_buttons);\n }\n\n //display buttons\n var buttons = [];\n if (Array.isArray(trial.button_html)) {\n if (trial.button_html.length == trial.choices.length) {\n buttons = trial.button_html;\n } else {\n console.error(\n \"Error in audio-button-response plugin. The length of the button_html array does not equal the length of the choices array\"\n );\n }\n } else {\n for (var i = 0; i < trial.choices.length; i++) {\n buttons.push(trial.button_html);\n }\n }\n\n var html = '<div id=\"jspsych-audio-button-response-btngroup\">';\n for (var i = 0; i < trial.choices.length; i++) {\n var str = buttons[i].replace(/%choice%/g, trial.choices[i]);\n html +=\n '<div class=\"jspsych-audio-button-response-button\" style=\"cursor: pointer; display: inline-block; margin:' +\n trial.margin_vertical +\n \" \" +\n trial.margin_horizontal +\n '\" id=\"jspsych-audio-button-response-button-' +\n i +\n '\" data-choice=\"' +\n i +\n '\">' +\n str +\n \"</div>\";\n }\n html += \"</div>\";\n\n //show prompt if there is one\n if (trial.prompt !== null) {\n html += trial.prompt;\n }\n\n display_element.innerHTML = html;\n\n if (trial.response_allowed_while_playing) {\n enable_buttons();\n } else {\n disable_buttons();\n }\n\n // start time\n startTime = performance.now();\n\n // start audio\n if (context !== null) {\n startTime = context.currentTime;\n audio.start(startTime);\n } else {\n audio.play();\n }\n\n // end trial if time limit is set\n if (trial.trial_duration !== null) {\n this.jsPsych.pluginAPI.setTimeout(function () {\n end_trial();\n }, trial.trial_duration);\n }\n\n on_load();\n };\n\n // function to handle responses by the subject\n function after_response(choice) {\n // measure rt\n var endTime = performance.now();\n var rt = Math.round(endTime - startTime);\n if (context !== null) {\n endTime = context.currentTime;\n rt = Math.round((endTime - startTime) * 1000);\n }\n response.button = parseInt(choice);\n response.rt = rt;\n\n // disable all the buttons after a response\n disable_buttons();\n\n if (trial.response_ends_trial) {\n end_trial();\n }\n }\n\n // function to end trial when it is time\n const end_trial = () => {\n // kill any remaining setTimeout handlers\n this.jsPsych.pluginAPI.clearAllTimeouts();\n\n // stop the audio file if it is playing\n // remove end event listeners if they exist\n if (context !== null) {\n audio.stop();\n } else {\n audio.pause();\n }\n\n audio.removeEventListener(\"ended\", end_trial);\n audio.removeEventListener(\"ended\", enable_buttons);\n\n // gather the data to store for the trial\n var trial_data = {\n rt: response.rt,\n stimulus: trial.stimulus,\n response: response.button,\n };\n\n // clear the display\n display_element.innerHTML = \"\";\n\n // move on to the next trial\n this.jsPsych.finishTrial(trial_data);\n\n trial_complete();\n };\n\n function button_response(e) {\n var choice = e.currentTarget.getAttribute(\"data-choice\"); // don't use dataset for jsdom compatibility\n after_response(choice);\n }\n\n function disable_buttons() {\n var btns = document.querySelectorAll(\".jspsych-audio-button-response-button\");\n for (var i = 0; i < btns.length; i++) {\n var btn_el = btns[i].querySelector(\"button\");\n if (btn_el) {\n btn_el.disabled = true;\n }\n btns[i].removeEventListener(\"click\", button_response);\n }\n }\n\n function enable_buttons() {\n var btns = document.querySelectorAll(\".jspsych-audio-button-response-button\");\n for (var i = 0; i < btns.length; i++) {\n var btn_el = btns[i].querySelector(\"button\");\n if (btn_el) {\n btn_el.disabled = false;\n }\n btns[i].addEventListener(\"click\", button_response);\n }\n }\n\n return new Promise((resolve) => {\n trial_complete = resolve;\n });\n }\n}\n\nexport default AudioButtonResponsePlugin;\n"],"names":["ParameterType"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs","sources":["../src/index.ts"],"sourcesContent":["import { JsPsych, JsPsychPlugin, ParameterType, TrialType } from \"jspsych\";\n\nconst info = <const>{\n name: \"audio-button-response\",\n parameters: {\n /** The audio to be played. */\n stimulus: {\n type: ParameterType.AUDIO,\n pretty_name: \"Stimulus\",\n default: undefined,\n },\n /** Array containing the label(s) for the button(s). */\n choices: {\n type: ParameterType.STRING,\n pretty_name: \"Choices\",\n default: undefined,\n array: true,\n },\n /** The HTML for creating button. Can create own style. Use the \"%choice%\" string to indicate where the label from the choices parameter should be inserted. */\n button_html: {\n type: ParameterType.HTML_STRING,\n pretty_name: \"Button HTML\",\n default: '<button class=\"jspsych-btn\">%choice%</button>',\n array: true,\n },\n /** Any content here will be displayed below the stimulus. */\n prompt: {\n type: ParameterType.HTML_STRING,\n pretty_name: \"Prompt\",\n default: null,\n },\n /** The maximum duration to wait for a response. */\n trial_duration: {\n type: ParameterType.INT,\n pretty_name: \"Trial duration\",\n default: null,\n },\n /** Vertical margin of button. */\n margin_vertical: {\n type: ParameterType.STRING,\n pretty_name: \"Margin vertical\",\n default: \"0px\",\n },\n /** Horizontal margin of button. */\n margin_horizontal: {\n type: ParameterType.STRING,\n pretty_name: \"Margin horizontal\",\n default: \"8px\",\n },\n /** If true, the trial will end when user makes a response. */\n response_ends_trial: {\n type: ParameterType.BOOL,\n pretty_name: \"Response ends trial\",\n default: true,\n },\n /** If true, then the trial will end as soon as the audio file finishes playing. */\n trial_ends_after_audio: {\n type: ParameterType.BOOL,\n pretty_name: \"Trial ends after audio\",\n default: false,\n },\n /**\n * If true, then responses are allowed while the audio is playing.\n * If false, then the audio must finish playing before a response is accepted.\n */\n response_allowed_while_playing: {\n type: ParameterType.BOOL,\n pretty_name: \"Response allowed while playing\",\n default: true,\n },\n },\n};\n\ntype Info = typeof info;\n\n/**\n * **audio-button-response**\n *\n * jsPsych plugin for playing an audio file and getting a button response\n *\n * @author Kristin Diep\n * @see {@link https://www.jspsych.org/plugins/jspsych-audio-button-response/ audio-button-response plugin documentation on jspsych.org}\n */\nclass AudioButtonResponsePlugin implements JsPsychPlugin<Info> {\n static info = info;\n private audio;\n\n constructor(private jsPsych: JsPsych) {}\n\n trial(display_element: HTMLElement, trial: TrialType<Info>, on_load: () => void) {\n // hold the .resolve() function from the Promise that ends the trial\n let trial_complete;\n\n // setup stimulus\n var context = this.jsPsych.pluginAPI.audioContext();\n\n // store response\n var response = {\n rt: null,\n button: null,\n };\n\n // record webaudio context start time\n var startTime;\n\n // load audio file\n this.jsPsych.pluginAPI\n .getAudioBuffer(trial.stimulus)\n .then((buffer) => {\n if (context !== null) {\n this.audio = context.createBufferSource();\n this.audio.buffer = buffer;\n this.audio.connect(context.destination);\n } else {\n this.audio = buffer;\n this.audio.currentTime = 0;\n }\n setupTrial();\n })\n .catch((err) => {\n console.error(\n `Failed to load audio file \"${trial.stimulus}\". Try checking the file path. We recommend using the preload plugin to load audio files.`\n );\n console.error(err);\n });\n\n const setupTrial = () => {\n // set up end event if trial needs it\n if (trial.trial_ends_after_audio) {\n this.audio.addEventListener(\"ended\", end_trial);\n }\n\n // enable buttons after audio ends if necessary\n if (!trial.response_allowed_while_playing && !trial.trial_ends_after_audio) {\n this.audio.addEventListener(\"ended\", enable_buttons);\n }\n\n //display buttons\n var buttons = [];\n if (Array.isArray(trial.button_html)) {\n if (trial.button_html.length == trial.choices.length) {\n buttons = trial.button_html;\n } else {\n console.error(\n \"Error in audio-button-response plugin. The length of the button_html array does not equal the length of the choices array\"\n );\n }\n } else {\n for (var i = 0; i < trial.choices.length; i++) {\n buttons.push(trial.button_html);\n }\n }\n\n var html = '<div id=\"jspsych-audio-button-response-btngroup\">';\n for (var i = 0; i < trial.choices.length; i++) {\n var str = buttons[i].replace(/%choice%/g, trial.choices[i]);\n html +=\n '<div class=\"jspsych-audio-button-response-button\" style=\"cursor: pointer; display: inline-block; margin:' +\n trial.margin_vertical +\n \" \" +\n trial.margin_horizontal +\n '\" id=\"jspsych-audio-button-response-button-' +\n i +\n '\" data-choice=\"' +\n i +\n '\">' +\n str +\n \"</div>\";\n }\n html += \"</div>\";\n\n //show prompt if there is one\n if (trial.prompt !== null) {\n html += trial.prompt;\n }\n\n display_element.innerHTML = html;\n\n if (trial.response_allowed_while_playing) {\n enable_buttons();\n } else {\n disable_buttons();\n }\n\n // start time\n startTime = performance.now();\n\n // start audio\n if (context !== null) {\n startTime = context.currentTime;\n this.audio.start(startTime);\n } else {\n this.audio.play();\n }\n\n // end trial if time limit is set\n if (trial.trial_duration !== null) {\n this.jsPsych.pluginAPI.setTimeout(() => {\n end_trial();\n }, trial.trial_duration);\n }\n\n on_load();\n };\n\n // function to handle responses by the subject\n function after_response(choice) {\n // measure rt\n var endTime = performance.now();\n var rt = Math.round(endTime - startTime);\n if (context !== null) {\n endTime = context.currentTime;\n rt = Math.round((endTime - startTime) * 1000);\n }\n response.button = parseInt(choice);\n response.rt = rt;\n\n // disable all the buttons after a response\n disable_buttons();\n\n if (trial.response_ends_trial) {\n end_trial();\n }\n }\n\n // function to end trial when it is time\n const end_trial = () => {\n // kill any remaining setTimeout handlers\n this.jsPsych.pluginAPI.clearAllTimeouts();\n\n // stop the audio file if it is playing\n // remove end event listeners if they exist\n if (context !== null) {\n this.audio.stop();\n } else {\n this.audio.pause();\n }\n\n this.audio.removeEventListener(\"ended\", end_trial);\n this.audio.removeEventListener(\"ended\", enable_buttons);\n\n // gather the data to store for the trial\n var trial_data = {\n rt: response.rt,\n stimulus: trial.stimulus,\n response: response.button,\n };\n\n // clear the display\n display_element.innerHTML = \"\";\n\n // move on to the next trial\n this.jsPsych.finishTrial(trial_data);\n\n trial_complete();\n };\n\n function button_response(e) {\n var choice = e.currentTarget.getAttribute(\"data-choice\"); // don't use dataset for jsdom compatibility\n after_response(choice);\n }\n\n function disable_buttons() {\n var btns = document.querySelectorAll(\".jspsych-audio-button-response-button\");\n for (var i = 0; i < btns.length; i++) {\n var btn_el = btns[i].querySelector(\"button\");\n if (btn_el) {\n btn_el.disabled = true;\n }\n btns[i].removeEventListener(\"click\", button_response);\n }\n }\n\n function enable_buttons() {\n var btns = document.querySelectorAll(\".jspsych-audio-button-response-button\");\n for (var i = 0; i < btns.length; i++) {\n var btn_el = btns[i].querySelector(\"button\");\n if (btn_el) {\n btn_el.disabled = false;\n }\n btns[i].addEventListener(\"click\", button_response);\n }\n }\n\n return new Promise((resolve) => {\n trial_complete = resolve;\n });\n }\n\n simulate(\n trial: TrialType<Info>,\n simulation_mode,\n simulation_options: any,\n load_callback: () => void\n ) {\n if (simulation_mode == \"data-only\") {\n load_callback();\n this.simulate_data_only(trial, simulation_options);\n }\n if (simulation_mode == \"visual\") {\n this.simulate_visual(trial, simulation_options, load_callback);\n }\n }\n\n private create_simulation_data(trial: TrialType<Info>, simulation_options) {\n const default_data = {\n stimulus: trial.stimulus,\n rt: this.jsPsych.randomization.sampleExGaussian(500, 50, 1 / 150, true),\n response: this.jsPsych.randomization.randomInt(0, trial.choices.length - 1),\n };\n\n const data = this.jsPsych.pluginAPI.mergeSimulationData(default_data, simulation_options);\n\n this.jsPsych.pluginAPI.ensureSimulationDataConsistency(trial, data);\n\n return data;\n }\n\n private simulate_data_only(trial: TrialType<Info>, simulation_options) {\n const data = this.create_simulation_data(trial, simulation_options);\n\n this.jsPsych.finishTrial(data);\n }\n\n private simulate_visual(trial: TrialType<Info>, simulation_options, load_callback: () => void) {\n const data = this.create_simulation_data(trial, simulation_options);\n\n const display_element = this.jsPsych.getDisplayElement();\n\n const respond = () => {\n if (data.rt !== null) {\n this.jsPsych.pluginAPI.clickTarget(\n display_element.querySelector(`div[data-choice=\"${data.response}\"] button`),\n data.rt\n );\n }\n };\n\n this.trial(display_element, trial, () => {\n load_callback();\n if (!trial.response_allowed_while_playing) {\n this.audio.addEventListener(\"ended\", respond);\n } else {\n respond();\n }\n });\n }\n}\n\nexport default AudioButtonResponsePlugin;\n"],"names":["ParameterType"],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -150,7 +150,12 @@ declare class AudioButtonResponsePlugin implements JsPsychPlugin<Info> {
|
|
|
150
150
|
};
|
|
151
151
|
};
|
|
152
152
|
};
|
|
153
|
+
private audio;
|
|
153
154
|
constructor(jsPsych: JsPsych);
|
|
154
155
|
trial(display_element: HTMLElement, trial: TrialType<Info>, on_load: () => void): Promise<unknown>;
|
|
156
|
+
simulate(trial: TrialType<Info>, simulation_mode: any, simulation_options: any, load_callback: () => void): void;
|
|
157
|
+
private create_simulation_data;
|
|
158
|
+
private simulate_data_only;
|
|
159
|
+
private simulate_visual;
|
|
155
160
|
}
|
|
156
161
|
export default AudioButtonResponsePlugin;
|
package/dist/index.js
CHANGED
|
@@ -87,7 +87,6 @@ class AudioButtonResponsePlugin {
|
|
|
87
87
|
let trial_complete;
|
|
88
88
|
// setup stimulus
|
|
89
89
|
var context = this.jsPsych.pluginAPI.audioContext();
|
|
90
|
-
var audio;
|
|
91
90
|
// store response
|
|
92
91
|
var response = {
|
|
93
92
|
rt: null,
|
|
@@ -98,30 +97,30 @@ class AudioButtonResponsePlugin {
|
|
|
98
97
|
// load audio file
|
|
99
98
|
this.jsPsych.pluginAPI
|
|
100
99
|
.getAudioBuffer(trial.stimulus)
|
|
101
|
-
.then(
|
|
100
|
+
.then((buffer) => {
|
|
102
101
|
if (context !== null) {
|
|
103
|
-
audio = context.createBufferSource();
|
|
104
|
-
audio.buffer = buffer;
|
|
105
|
-
audio.connect(context.destination);
|
|
102
|
+
this.audio = context.createBufferSource();
|
|
103
|
+
this.audio.buffer = buffer;
|
|
104
|
+
this.audio.connect(context.destination);
|
|
106
105
|
}
|
|
107
106
|
else {
|
|
108
|
-
audio = buffer;
|
|
109
|
-
audio.currentTime = 0;
|
|
107
|
+
this.audio = buffer;
|
|
108
|
+
this.audio.currentTime = 0;
|
|
110
109
|
}
|
|
111
110
|
setupTrial();
|
|
112
111
|
})
|
|
113
|
-
.catch(
|
|
112
|
+
.catch((err) => {
|
|
114
113
|
console.error(`Failed to load audio file "${trial.stimulus}". Try checking the file path. We recommend using the preload plugin to load audio files.`);
|
|
115
114
|
console.error(err);
|
|
116
115
|
});
|
|
117
116
|
const setupTrial = () => {
|
|
118
117
|
// set up end event if trial needs it
|
|
119
118
|
if (trial.trial_ends_after_audio) {
|
|
120
|
-
audio.addEventListener("ended", end_trial);
|
|
119
|
+
this.audio.addEventListener("ended", end_trial);
|
|
121
120
|
}
|
|
122
121
|
// enable buttons after audio ends if necessary
|
|
123
122
|
if (!trial.response_allowed_while_playing && !trial.trial_ends_after_audio) {
|
|
124
|
-
audio.addEventListener("ended", enable_buttons);
|
|
123
|
+
this.audio.addEventListener("ended", enable_buttons);
|
|
125
124
|
}
|
|
126
125
|
//display buttons
|
|
127
126
|
var buttons = [];
|
|
@@ -171,14 +170,14 @@ class AudioButtonResponsePlugin {
|
|
|
171
170
|
// start audio
|
|
172
171
|
if (context !== null) {
|
|
173
172
|
startTime = context.currentTime;
|
|
174
|
-
audio.start(startTime);
|
|
173
|
+
this.audio.start(startTime);
|
|
175
174
|
}
|
|
176
175
|
else {
|
|
177
|
-
audio.play();
|
|
176
|
+
this.audio.play();
|
|
178
177
|
}
|
|
179
178
|
// end trial if time limit is set
|
|
180
179
|
if (trial.trial_duration !== null) {
|
|
181
|
-
this.jsPsych.pluginAPI.setTimeout(
|
|
180
|
+
this.jsPsych.pluginAPI.setTimeout(() => {
|
|
182
181
|
end_trial();
|
|
183
182
|
}, trial.trial_duration);
|
|
184
183
|
}
|
|
@@ -208,13 +207,13 @@ class AudioButtonResponsePlugin {
|
|
|
208
207
|
// stop the audio file if it is playing
|
|
209
208
|
// remove end event listeners if they exist
|
|
210
209
|
if (context !== null) {
|
|
211
|
-
audio.stop();
|
|
210
|
+
this.audio.stop();
|
|
212
211
|
}
|
|
213
212
|
else {
|
|
214
|
-
audio.pause();
|
|
213
|
+
this.audio.pause();
|
|
215
214
|
}
|
|
216
|
-
audio.removeEventListener("ended", end_trial);
|
|
217
|
-
audio.removeEventListener("ended", enable_buttons);
|
|
215
|
+
this.audio.removeEventListener("ended", end_trial);
|
|
216
|
+
this.audio.removeEventListener("ended", enable_buttons);
|
|
218
217
|
// gather the data to store for the trial
|
|
219
218
|
var trial_data = {
|
|
220
219
|
rt: response.rt,
|
|
@@ -255,6 +254,47 @@ class AudioButtonResponsePlugin {
|
|
|
255
254
|
trial_complete = resolve;
|
|
256
255
|
});
|
|
257
256
|
}
|
|
257
|
+
simulate(trial, simulation_mode, simulation_options, load_callback) {
|
|
258
|
+
if (simulation_mode == "data-only") {
|
|
259
|
+
load_callback();
|
|
260
|
+
this.simulate_data_only(trial, simulation_options);
|
|
261
|
+
}
|
|
262
|
+
if (simulation_mode == "visual") {
|
|
263
|
+
this.simulate_visual(trial, simulation_options, load_callback);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
create_simulation_data(trial, simulation_options) {
|
|
267
|
+
const default_data = {
|
|
268
|
+
stimulus: trial.stimulus,
|
|
269
|
+
rt: this.jsPsych.randomization.sampleExGaussian(500, 50, 1 / 150, true),
|
|
270
|
+
response: this.jsPsych.randomization.randomInt(0, trial.choices.length - 1),
|
|
271
|
+
};
|
|
272
|
+
const data = this.jsPsych.pluginAPI.mergeSimulationData(default_data, simulation_options);
|
|
273
|
+
this.jsPsych.pluginAPI.ensureSimulationDataConsistency(trial, data);
|
|
274
|
+
return data;
|
|
275
|
+
}
|
|
276
|
+
simulate_data_only(trial, simulation_options) {
|
|
277
|
+
const data = this.create_simulation_data(trial, simulation_options);
|
|
278
|
+
this.jsPsych.finishTrial(data);
|
|
279
|
+
}
|
|
280
|
+
simulate_visual(trial, simulation_options, load_callback) {
|
|
281
|
+
const data = this.create_simulation_data(trial, simulation_options);
|
|
282
|
+
const display_element = this.jsPsych.getDisplayElement();
|
|
283
|
+
const respond = () => {
|
|
284
|
+
if (data.rt !== null) {
|
|
285
|
+
this.jsPsych.pluginAPI.clickTarget(display_element.querySelector(`div[data-choice="${data.response}"] button`), data.rt);
|
|
286
|
+
}
|
|
287
|
+
};
|
|
288
|
+
this.trial(display_element, trial, () => {
|
|
289
|
+
load_callback();
|
|
290
|
+
if (!trial.response_allowed_while_playing) {
|
|
291
|
+
this.audio.addEventListener("ended", respond);
|
|
292
|
+
}
|
|
293
|
+
else {
|
|
294
|
+
respond();
|
|
295
|
+
}
|
|
296
|
+
});
|
|
297
|
+
}
|
|
258
298
|
}
|
|
259
299
|
AudioButtonResponsePlugin.info = info;
|
|
260
300
|
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../src/index.ts"],"sourcesContent":["import { JsPsych, JsPsychPlugin, ParameterType, TrialType } from \"jspsych\";\n\nconst info = <const>{\n name: \"audio-button-response\",\n parameters: {\n /** The audio to be played. */\n stimulus: {\n type: ParameterType.AUDIO,\n pretty_name: \"Stimulus\",\n default: undefined,\n },\n /** Array containing the label(s) for the button(s). */\n choices: {\n type: ParameterType.STRING,\n pretty_name: \"Choices\",\n default: undefined,\n array: true,\n },\n /** The HTML for creating button. Can create own style. Use the \"%choice%\" string to indicate where the label from the choices parameter should be inserted. */\n button_html: {\n type: ParameterType.HTML_STRING,\n pretty_name: \"Button HTML\",\n default: '<button class=\"jspsych-btn\">%choice%</button>',\n array: true,\n },\n /** Any content here will be displayed below the stimulus. */\n prompt: {\n type: ParameterType.HTML_STRING,\n pretty_name: \"Prompt\",\n default: null,\n },\n /** The maximum duration to wait for a response. */\n trial_duration: {\n type: ParameterType.INT,\n pretty_name: \"Trial duration\",\n default: null,\n },\n /** Vertical margin of button. */\n margin_vertical: {\n type: ParameterType.STRING,\n pretty_name: \"Margin vertical\",\n default: \"0px\",\n },\n /** Horizontal margin of button. */\n margin_horizontal: {\n type: ParameterType.STRING,\n pretty_name: \"Margin horizontal\",\n default: \"8px\",\n },\n /** If true, the trial will end when user makes a response. */\n response_ends_trial: {\n type: ParameterType.BOOL,\n pretty_name: \"Response ends trial\",\n default: true,\n },\n /** If true, then the trial will end as soon as the audio file finishes playing. */\n trial_ends_after_audio: {\n type: ParameterType.BOOL,\n pretty_name: \"Trial ends after audio\",\n default: false,\n },\n /**\n * If true, then responses are allowed while the audio is playing.\n * If false, then the audio must finish playing before a response is accepted.\n */\n response_allowed_while_playing: {\n type: ParameterType.BOOL,\n pretty_name: \"Response allowed while playing\",\n default: true,\n },\n },\n};\n\ntype Info = typeof info;\n\n/**\n * **audio-button-response**\n *\n * jsPsych plugin for playing an audio file and getting a button response\n *\n * @author Kristin Diep\n * @see {@link https://www.jspsych.org/plugins/jspsych-audio-button-response/ audio-button-response plugin documentation on jspsych.org}\n */\nclass AudioButtonResponsePlugin implements JsPsychPlugin<Info> {\n static info = info;\n\n constructor(private jsPsych: JsPsych) {}\n\n trial(display_element: HTMLElement, trial: TrialType<Info>, on_load: () => void) {\n // hold the .resolve() function from the Promise that ends the trial\n let trial_complete;\n\n // setup stimulus\n var context = this.jsPsych.pluginAPI.audioContext();\n var audio;\n\n // store response\n var response = {\n rt: null,\n button: null,\n };\n\n // record webaudio context start time\n var startTime;\n\n // load audio file\n this.jsPsych.pluginAPI\n .getAudioBuffer(trial.stimulus)\n .then(function (buffer) {\n if (context !== null) {\n audio = context.createBufferSource();\n audio.buffer = buffer;\n audio.connect(context.destination);\n } else {\n audio = buffer;\n audio.currentTime = 0;\n }\n setupTrial();\n })\n .catch(function (err) {\n console.error(\n `Failed to load audio file \"${trial.stimulus}\". Try checking the file path. We recommend using the preload plugin to load audio files.`\n );\n console.error(err);\n });\n\n const setupTrial = () => {\n // set up end event if trial needs it\n if (trial.trial_ends_after_audio) {\n audio.addEventListener(\"ended\", end_trial);\n }\n\n // enable buttons after audio ends if necessary\n if (!trial.response_allowed_while_playing && !trial.trial_ends_after_audio) {\n audio.addEventListener(\"ended\", enable_buttons);\n }\n\n //display buttons\n var buttons = [];\n if (Array.isArray(trial.button_html)) {\n if (trial.button_html.length == trial.choices.length) {\n buttons = trial.button_html;\n } else {\n console.error(\n \"Error in audio-button-response plugin. The length of the button_html array does not equal the length of the choices array\"\n );\n }\n } else {\n for (var i = 0; i < trial.choices.length; i++) {\n buttons.push(trial.button_html);\n }\n }\n\n var html = '<div id=\"jspsych-audio-button-response-btngroup\">';\n for (var i = 0; i < trial.choices.length; i++) {\n var str = buttons[i].replace(/%choice%/g, trial.choices[i]);\n html +=\n '<div class=\"jspsych-audio-button-response-button\" style=\"cursor: pointer; display: inline-block; margin:' +\n trial.margin_vertical +\n \" \" +\n trial.margin_horizontal +\n '\" id=\"jspsych-audio-button-response-button-' +\n i +\n '\" data-choice=\"' +\n i +\n '\">' +\n str +\n \"</div>\";\n }\n html += \"</div>\";\n\n //show prompt if there is one\n if (trial.prompt !== null) {\n html += trial.prompt;\n }\n\n display_element.innerHTML = html;\n\n if (trial.response_allowed_while_playing) {\n enable_buttons();\n } else {\n disable_buttons();\n }\n\n // start time\n startTime = performance.now();\n\n // start audio\n if (context !== null) {\n startTime = context.currentTime;\n audio.start(startTime);\n } else {\n audio.play();\n }\n\n // end trial if time limit is set\n if (trial.trial_duration !== null) {\n this.jsPsych.pluginAPI.setTimeout(function () {\n end_trial();\n }, trial.trial_duration);\n }\n\n on_load();\n };\n\n // function to handle responses by the subject\n function after_response(choice) {\n // measure rt\n var endTime = performance.now();\n var rt = Math.round(endTime - startTime);\n if (context !== null) {\n endTime = context.currentTime;\n rt = Math.round((endTime - startTime) * 1000);\n }\n response.button = parseInt(choice);\n response.rt = rt;\n\n // disable all the buttons after a response\n disable_buttons();\n\n if (trial.response_ends_trial) {\n end_trial();\n }\n }\n\n // function to end trial when it is time\n const end_trial = () => {\n // kill any remaining setTimeout handlers\n this.jsPsych.pluginAPI.clearAllTimeouts();\n\n // stop the audio file if it is playing\n // remove end event listeners if they exist\n if (context !== null) {\n audio.stop();\n } else {\n audio.pause();\n }\n\n audio.removeEventListener(\"ended\", end_trial);\n audio.removeEventListener(\"ended\", enable_buttons);\n\n // gather the data to store for the trial\n var trial_data = {\n rt: response.rt,\n stimulus: trial.stimulus,\n response: response.button,\n };\n\n // clear the display\n display_element.innerHTML = \"\";\n\n // move on to the next trial\n this.jsPsych.finishTrial(trial_data);\n\n trial_complete();\n };\n\n function button_response(e) {\n var choice = e.currentTarget.getAttribute(\"data-choice\"); // don't use dataset for jsdom compatibility\n after_response(choice);\n }\n\n function disable_buttons() {\n var btns = document.querySelectorAll(\".jspsych-audio-button-response-button\");\n for (var i = 0; i < btns.length; i++) {\n var btn_el = btns[i].querySelector(\"button\");\n if (btn_el) {\n btn_el.disabled = true;\n }\n btns[i].removeEventListener(\"click\", button_response);\n }\n }\n\n function enable_buttons() {\n var btns = document.querySelectorAll(\".jspsych-audio-button-response-button\");\n for (var i = 0; i < btns.length; i++) {\n var btn_el = btns[i].querySelector(\"button\");\n if (btn_el) {\n btn_el.disabled = false;\n }\n btns[i].addEventListener(\"click\", button_response);\n }\n }\n\n return new Promise((resolve) => {\n trial_complete = resolve;\n });\n }\n}\n\nexport default AudioButtonResponsePlugin;\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../src/index.ts"],"sourcesContent":["import { JsPsych, JsPsychPlugin, ParameterType, TrialType } from \"jspsych\";\n\nconst info = <const>{\n name: \"audio-button-response\",\n parameters: {\n /** The audio to be played. */\n stimulus: {\n type: ParameterType.AUDIO,\n pretty_name: \"Stimulus\",\n default: undefined,\n },\n /** Array containing the label(s) for the button(s). */\n choices: {\n type: ParameterType.STRING,\n pretty_name: \"Choices\",\n default: undefined,\n array: true,\n },\n /** The HTML for creating button. Can create own style. Use the \"%choice%\" string to indicate where the label from the choices parameter should be inserted. */\n button_html: {\n type: ParameterType.HTML_STRING,\n pretty_name: \"Button HTML\",\n default: '<button class=\"jspsych-btn\">%choice%</button>',\n array: true,\n },\n /** Any content here will be displayed below the stimulus. */\n prompt: {\n type: ParameterType.HTML_STRING,\n pretty_name: \"Prompt\",\n default: null,\n },\n /** The maximum duration to wait for a response. */\n trial_duration: {\n type: ParameterType.INT,\n pretty_name: \"Trial duration\",\n default: null,\n },\n /** Vertical margin of button. */\n margin_vertical: {\n type: ParameterType.STRING,\n pretty_name: \"Margin vertical\",\n default: \"0px\",\n },\n /** Horizontal margin of button. */\n margin_horizontal: {\n type: ParameterType.STRING,\n pretty_name: \"Margin horizontal\",\n default: \"8px\",\n },\n /** If true, the trial will end when user makes a response. */\n response_ends_trial: {\n type: ParameterType.BOOL,\n pretty_name: \"Response ends trial\",\n default: true,\n },\n /** If true, then the trial will end as soon as the audio file finishes playing. */\n trial_ends_after_audio: {\n type: ParameterType.BOOL,\n pretty_name: \"Trial ends after audio\",\n default: false,\n },\n /**\n * If true, then responses are allowed while the audio is playing.\n * If false, then the audio must finish playing before a response is accepted.\n */\n response_allowed_while_playing: {\n type: ParameterType.BOOL,\n pretty_name: \"Response allowed while playing\",\n default: true,\n },\n },\n};\n\ntype Info = typeof info;\n\n/**\n * **audio-button-response**\n *\n * jsPsych plugin for playing an audio file and getting a button response\n *\n * @author Kristin Diep\n * @see {@link https://www.jspsych.org/plugins/jspsych-audio-button-response/ audio-button-response plugin documentation on jspsych.org}\n */\nclass AudioButtonResponsePlugin implements JsPsychPlugin<Info> {\n static info = info;\n private audio;\n\n constructor(private jsPsych: JsPsych) {}\n\n trial(display_element: HTMLElement, trial: TrialType<Info>, on_load: () => void) {\n // hold the .resolve() function from the Promise that ends the trial\n let trial_complete;\n\n // setup stimulus\n var context = this.jsPsych.pluginAPI.audioContext();\n\n // store response\n var response = {\n rt: null,\n button: null,\n };\n\n // record webaudio context start time\n var startTime;\n\n // load audio file\n this.jsPsych.pluginAPI\n .getAudioBuffer(trial.stimulus)\n .then((buffer) => {\n if (context !== null) {\n this.audio = context.createBufferSource();\n this.audio.buffer = buffer;\n this.audio.connect(context.destination);\n } else {\n this.audio = buffer;\n this.audio.currentTime = 0;\n }\n setupTrial();\n })\n .catch((err) => {\n console.error(\n `Failed to load audio file \"${trial.stimulus}\". Try checking the file path. We recommend using the preload plugin to load audio files.`\n );\n console.error(err);\n });\n\n const setupTrial = () => {\n // set up end event if trial needs it\n if (trial.trial_ends_after_audio) {\n this.audio.addEventListener(\"ended\", end_trial);\n }\n\n // enable buttons after audio ends if necessary\n if (!trial.response_allowed_while_playing && !trial.trial_ends_after_audio) {\n this.audio.addEventListener(\"ended\", enable_buttons);\n }\n\n //display buttons\n var buttons = [];\n if (Array.isArray(trial.button_html)) {\n if (trial.button_html.length == trial.choices.length) {\n buttons = trial.button_html;\n } else {\n console.error(\n \"Error in audio-button-response plugin. The length of the button_html array does not equal the length of the choices array\"\n );\n }\n } else {\n for (var i = 0; i < trial.choices.length; i++) {\n buttons.push(trial.button_html);\n }\n }\n\n var html = '<div id=\"jspsych-audio-button-response-btngroup\">';\n for (var i = 0; i < trial.choices.length; i++) {\n var str = buttons[i].replace(/%choice%/g, trial.choices[i]);\n html +=\n '<div class=\"jspsych-audio-button-response-button\" style=\"cursor: pointer; display: inline-block; margin:' +\n trial.margin_vertical +\n \" \" +\n trial.margin_horizontal +\n '\" id=\"jspsych-audio-button-response-button-' +\n i +\n '\" data-choice=\"' +\n i +\n '\">' +\n str +\n \"</div>\";\n }\n html += \"</div>\";\n\n //show prompt if there is one\n if (trial.prompt !== null) {\n html += trial.prompt;\n }\n\n display_element.innerHTML = html;\n\n if (trial.response_allowed_while_playing) {\n enable_buttons();\n } else {\n disable_buttons();\n }\n\n // start time\n startTime = performance.now();\n\n // start audio\n if (context !== null) {\n startTime = context.currentTime;\n this.audio.start(startTime);\n } else {\n this.audio.play();\n }\n\n // end trial if time limit is set\n if (trial.trial_duration !== null) {\n this.jsPsych.pluginAPI.setTimeout(() => {\n end_trial();\n }, trial.trial_duration);\n }\n\n on_load();\n };\n\n // function to handle responses by the subject\n function after_response(choice) {\n // measure rt\n var endTime = performance.now();\n var rt = Math.round(endTime - startTime);\n if (context !== null) {\n endTime = context.currentTime;\n rt = Math.round((endTime - startTime) * 1000);\n }\n response.button = parseInt(choice);\n response.rt = rt;\n\n // disable all the buttons after a response\n disable_buttons();\n\n if (trial.response_ends_trial) {\n end_trial();\n }\n }\n\n // function to end trial when it is time\n const end_trial = () => {\n // kill any remaining setTimeout handlers\n this.jsPsych.pluginAPI.clearAllTimeouts();\n\n // stop the audio file if it is playing\n // remove end event listeners if they exist\n if (context !== null) {\n this.audio.stop();\n } else {\n this.audio.pause();\n }\n\n this.audio.removeEventListener(\"ended\", end_trial);\n this.audio.removeEventListener(\"ended\", enable_buttons);\n\n // gather the data to store for the trial\n var trial_data = {\n rt: response.rt,\n stimulus: trial.stimulus,\n response: response.button,\n };\n\n // clear the display\n display_element.innerHTML = \"\";\n\n // move on to the next trial\n this.jsPsych.finishTrial(trial_data);\n\n trial_complete();\n };\n\n function button_response(e) {\n var choice = e.currentTarget.getAttribute(\"data-choice\"); // don't use dataset for jsdom compatibility\n after_response(choice);\n }\n\n function disable_buttons() {\n var btns = document.querySelectorAll(\".jspsych-audio-button-response-button\");\n for (var i = 0; i < btns.length; i++) {\n var btn_el = btns[i].querySelector(\"button\");\n if (btn_el) {\n btn_el.disabled = true;\n }\n btns[i].removeEventListener(\"click\", button_response);\n }\n }\n\n function enable_buttons() {\n var btns = document.querySelectorAll(\".jspsych-audio-button-response-button\");\n for (var i = 0; i < btns.length; i++) {\n var btn_el = btns[i].querySelector(\"button\");\n if (btn_el) {\n btn_el.disabled = false;\n }\n btns[i].addEventListener(\"click\", button_response);\n }\n }\n\n return new Promise((resolve) => {\n trial_complete = resolve;\n });\n }\n\n simulate(\n trial: TrialType<Info>,\n simulation_mode,\n simulation_options: any,\n load_callback: () => void\n ) {\n if (simulation_mode == \"data-only\") {\n load_callback();\n this.simulate_data_only(trial, simulation_options);\n }\n if (simulation_mode == \"visual\") {\n this.simulate_visual(trial, simulation_options, load_callback);\n }\n }\n\n private create_simulation_data(trial: TrialType<Info>, simulation_options) {\n const default_data = {\n stimulus: trial.stimulus,\n rt: this.jsPsych.randomization.sampleExGaussian(500, 50, 1 / 150, true),\n response: this.jsPsych.randomization.randomInt(0, trial.choices.length - 1),\n };\n\n const data = this.jsPsych.pluginAPI.mergeSimulationData(default_data, simulation_options);\n\n this.jsPsych.pluginAPI.ensureSimulationDataConsistency(trial, data);\n\n return data;\n }\n\n private simulate_data_only(trial: TrialType<Info>, simulation_options) {\n const data = this.create_simulation_data(trial, simulation_options);\n\n this.jsPsych.finishTrial(data);\n }\n\n private simulate_visual(trial: TrialType<Info>, simulation_options, load_callback: () => void) {\n const data = this.create_simulation_data(trial, simulation_options);\n\n const display_element = this.jsPsych.getDisplayElement();\n\n const respond = () => {\n if (data.rt !== null) {\n this.jsPsych.pluginAPI.clickTarget(\n display_element.querySelector(`div[data-choice=\"${data.response}\"] button`),\n data.rt\n );\n }\n };\n\n this.trial(display_element, trial, () => {\n load_callback();\n if (!trial.response_allowed_while_playing) {\n this.audio.addEventListener(\"ended\", respond);\n } else {\n respond();\n }\n });\n }\n}\n\nexport default AudioButtonResponsePlugin;\n"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jspsych/plugin-audio-button-response",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "jsPsych plugin for playing an audio file and getting a button response",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.cjs",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
],
|
|
17
17
|
"source": "src/index.ts",
|
|
18
18
|
"scripts": {
|
|
19
|
-
"test": "jest
|
|
19
|
+
"test": "jest",
|
|
20
20
|
"test:watch": "npm test -- --watch",
|
|
21
21
|
"tsc": "tsc",
|
|
22
22
|
"build": "rollup --config",
|
|
@@ -34,10 +34,10 @@
|
|
|
34
34
|
},
|
|
35
35
|
"homepage": "https://www.jspsych.org/latest/plugins/audio-button-response",
|
|
36
36
|
"peerDependencies": {
|
|
37
|
-
"jspsych": ">=7.
|
|
37
|
+
"jspsych": ">=7.1.0"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
|
-
"@jspsych/config": "^1.
|
|
41
|
-
"@jspsych/test-utils": "^1.
|
|
40
|
+
"@jspsych/config": "^1.1.0",
|
|
41
|
+
"@jspsych/test-utils": "^1.1.0"
|
|
42
42
|
}
|
|
43
43
|
}
|
package/src/index.spec.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { clickTarget, startTimeline } from "@jspsych/test-utils";
|
|
1
|
+
import { clickTarget, simulateTimeline, startTimeline } from "@jspsych/test-utils";
|
|
2
2
|
import { initJsPsych } from "jspsych";
|
|
3
3
|
|
|
4
4
|
import audioButtonResponse from ".";
|
|
@@ -15,7 +15,7 @@ describe.skip("audio-button-response", () => {
|
|
|
15
15
|
prompt: "foo",
|
|
16
16
|
choices: ["choice1"],
|
|
17
17
|
on_load: () => {
|
|
18
|
-
expect(getHTML()).toContain("
|
|
18
|
+
expect(getHTML()).toContain("foo");
|
|
19
19
|
|
|
20
20
|
clickTarget(displayElement.querySelector("button"));
|
|
21
21
|
},
|
|
@@ -33,3 +33,54 @@ describe.skip("audio-button-response", () => {
|
|
|
33
33
|
await finished;
|
|
34
34
|
});
|
|
35
35
|
});
|
|
36
|
+
|
|
37
|
+
describe("audio-button-response simulation", () => {
|
|
38
|
+
test("data mode works", async () => {
|
|
39
|
+
const timeline = [
|
|
40
|
+
{
|
|
41
|
+
type: audioButtonResponse,
|
|
42
|
+
stimulus: "foo.mp3",
|
|
43
|
+
choices: ["click"],
|
|
44
|
+
},
|
|
45
|
+
];
|
|
46
|
+
|
|
47
|
+
const { expectFinished, getData } = await simulateTimeline(timeline);
|
|
48
|
+
|
|
49
|
+
await expectFinished();
|
|
50
|
+
|
|
51
|
+
expect(getData().values()[0].rt).toBeGreaterThan(0);
|
|
52
|
+
expect(getData().values()[0].response).toBe(0);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
// can't run this until we mock Audio elements.
|
|
56
|
+
test.skip("visual mode works", async () => {
|
|
57
|
+
const jsPsych = initJsPsych({ use_webaudio: false });
|
|
58
|
+
|
|
59
|
+
const timeline = [
|
|
60
|
+
{
|
|
61
|
+
type: audioButtonResponse,
|
|
62
|
+
stimulus: "foo.mp3",
|
|
63
|
+
prompt: "foo",
|
|
64
|
+
choices: ["click"],
|
|
65
|
+
},
|
|
66
|
+
];
|
|
67
|
+
|
|
68
|
+
const { expectFinished, expectRunning, getHTML, getData } = await simulateTimeline(
|
|
69
|
+
timeline,
|
|
70
|
+
"visual",
|
|
71
|
+
{},
|
|
72
|
+
jsPsych
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
await expectRunning();
|
|
76
|
+
|
|
77
|
+
expect(getHTML()).toContain("foo");
|
|
78
|
+
|
|
79
|
+
jest.runAllTimers();
|
|
80
|
+
|
|
81
|
+
await expectFinished();
|
|
82
|
+
|
|
83
|
+
expect(getData().values()[0].rt).toBeGreaterThan(0);
|
|
84
|
+
expect(getData().values()[0].response).toBe(0);
|
|
85
|
+
});
|
|
86
|
+
});
|
package/src/index.ts
CHANGED
|
@@ -83,6 +83,7 @@ type Info = typeof info;
|
|
|
83
83
|
*/
|
|
84
84
|
class AudioButtonResponsePlugin implements JsPsychPlugin<Info> {
|
|
85
85
|
static info = info;
|
|
86
|
+
private audio;
|
|
86
87
|
|
|
87
88
|
constructor(private jsPsych: JsPsych) {}
|
|
88
89
|
|
|
@@ -92,7 +93,6 @@ class AudioButtonResponsePlugin implements JsPsychPlugin<Info> {
|
|
|
92
93
|
|
|
93
94
|
// setup stimulus
|
|
94
95
|
var context = this.jsPsych.pluginAPI.audioContext();
|
|
95
|
-
var audio;
|
|
96
96
|
|
|
97
97
|
// store response
|
|
98
98
|
var response = {
|
|
@@ -106,18 +106,18 @@ class AudioButtonResponsePlugin implements JsPsychPlugin<Info> {
|
|
|
106
106
|
// load audio file
|
|
107
107
|
this.jsPsych.pluginAPI
|
|
108
108
|
.getAudioBuffer(trial.stimulus)
|
|
109
|
-
.then(
|
|
109
|
+
.then((buffer) => {
|
|
110
110
|
if (context !== null) {
|
|
111
|
-
audio = context.createBufferSource();
|
|
112
|
-
audio.buffer = buffer;
|
|
113
|
-
audio.connect(context.destination);
|
|
111
|
+
this.audio = context.createBufferSource();
|
|
112
|
+
this.audio.buffer = buffer;
|
|
113
|
+
this.audio.connect(context.destination);
|
|
114
114
|
} else {
|
|
115
|
-
audio = buffer;
|
|
116
|
-
audio.currentTime = 0;
|
|
115
|
+
this.audio = buffer;
|
|
116
|
+
this.audio.currentTime = 0;
|
|
117
117
|
}
|
|
118
118
|
setupTrial();
|
|
119
119
|
})
|
|
120
|
-
.catch(
|
|
120
|
+
.catch((err) => {
|
|
121
121
|
console.error(
|
|
122
122
|
`Failed to load audio file "${trial.stimulus}". Try checking the file path. We recommend using the preload plugin to load audio files.`
|
|
123
123
|
);
|
|
@@ -127,12 +127,12 @@ class AudioButtonResponsePlugin implements JsPsychPlugin<Info> {
|
|
|
127
127
|
const setupTrial = () => {
|
|
128
128
|
// set up end event if trial needs it
|
|
129
129
|
if (trial.trial_ends_after_audio) {
|
|
130
|
-
audio.addEventListener("ended", end_trial);
|
|
130
|
+
this.audio.addEventListener("ended", end_trial);
|
|
131
131
|
}
|
|
132
132
|
|
|
133
133
|
// enable buttons after audio ends if necessary
|
|
134
134
|
if (!trial.response_allowed_while_playing && !trial.trial_ends_after_audio) {
|
|
135
|
-
audio.addEventListener("ended", enable_buttons);
|
|
135
|
+
this.audio.addEventListener("ended", enable_buttons);
|
|
136
136
|
}
|
|
137
137
|
|
|
138
138
|
//display buttons
|
|
@@ -188,14 +188,14 @@ class AudioButtonResponsePlugin implements JsPsychPlugin<Info> {
|
|
|
188
188
|
// start audio
|
|
189
189
|
if (context !== null) {
|
|
190
190
|
startTime = context.currentTime;
|
|
191
|
-
audio.start(startTime);
|
|
191
|
+
this.audio.start(startTime);
|
|
192
192
|
} else {
|
|
193
|
-
audio.play();
|
|
193
|
+
this.audio.play();
|
|
194
194
|
}
|
|
195
195
|
|
|
196
196
|
// end trial if time limit is set
|
|
197
197
|
if (trial.trial_duration !== null) {
|
|
198
|
-
this.jsPsych.pluginAPI.setTimeout(
|
|
198
|
+
this.jsPsych.pluginAPI.setTimeout(() => {
|
|
199
199
|
end_trial();
|
|
200
200
|
}, trial.trial_duration);
|
|
201
201
|
}
|
|
@@ -231,13 +231,13 @@ class AudioButtonResponsePlugin implements JsPsychPlugin<Info> {
|
|
|
231
231
|
// stop the audio file if it is playing
|
|
232
232
|
// remove end event listeners if they exist
|
|
233
233
|
if (context !== null) {
|
|
234
|
-
audio.stop();
|
|
234
|
+
this.audio.stop();
|
|
235
235
|
} else {
|
|
236
|
-
audio.pause();
|
|
236
|
+
this.audio.pause();
|
|
237
237
|
}
|
|
238
238
|
|
|
239
|
-
audio.removeEventListener("ended", end_trial);
|
|
240
|
-
audio.removeEventListener("ended", enable_buttons);
|
|
239
|
+
this.audio.removeEventListener("ended", end_trial);
|
|
240
|
+
this.audio.removeEventListener("ended", enable_buttons);
|
|
241
241
|
|
|
242
242
|
// gather the data to store for the trial
|
|
243
243
|
var trial_data = {
|
|
@@ -286,6 +286,65 @@ class AudioButtonResponsePlugin implements JsPsychPlugin<Info> {
|
|
|
286
286
|
trial_complete = resolve;
|
|
287
287
|
});
|
|
288
288
|
}
|
|
289
|
+
|
|
290
|
+
simulate(
|
|
291
|
+
trial: TrialType<Info>,
|
|
292
|
+
simulation_mode,
|
|
293
|
+
simulation_options: any,
|
|
294
|
+
load_callback: () => void
|
|
295
|
+
) {
|
|
296
|
+
if (simulation_mode == "data-only") {
|
|
297
|
+
load_callback();
|
|
298
|
+
this.simulate_data_only(trial, simulation_options);
|
|
299
|
+
}
|
|
300
|
+
if (simulation_mode == "visual") {
|
|
301
|
+
this.simulate_visual(trial, simulation_options, load_callback);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
private create_simulation_data(trial: TrialType<Info>, simulation_options) {
|
|
306
|
+
const default_data = {
|
|
307
|
+
stimulus: trial.stimulus,
|
|
308
|
+
rt: this.jsPsych.randomization.sampleExGaussian(500, 50, 1 / 150, true),
|
|
309
|
+
response: this.jsPsych.randomization.randomInt(0, trial.choices.length - 1),
|
|
310
|
+
};
|
|
311
|
+
|
|
312
|
+
const data = this.jsPsych.pluginAPI.mergeSimulationData(default_data, simulation_options);
|
|
313
|
+
|
|
314
|
+
this.jsPsych.pluginAPI.ensureSimulationDataConsistency(trial, data);
|
|
315
|
+
|
|
316
|
+
return data;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
private simulate_data_only(trial: TrialType<Info>, simulation_options) {
|
|
320
|
+
const data = this.create_simulation_data(trial, simulation_options);
|
|
321
|
+
|
|
322
|
+
this.jsPsych.finishTrial(data);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
private simulate_visual(trial: TrialType<Info>, simulation_options, load_callback: () => void) {
|
|
326
|
+
const data = this.create_simulation_data(trial, simulation_options);
|
|
327
|
+
|
|
328
|
+
const display_element = this.jsPsych.getDisplayElement();
|
|
329
|
+
|
|
330
|
+
const respond = () => {
|
|
331
|
+
if (data.rt !== null) {
|
|
332
|
+
this.jsPsych.pluginAPI.clickTarget(
|
|
333
|
+
display_element.querySelector(`div[data-choice="${data.response}"] button`),
|
|
334
|
+
data.rt
|
|
335
|
+
);
|
|
336
|
+
}
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
this.trial(display_element, trial, () => {
|
|
340
|
+
load_callback();
|
|
341
|
+
if (!trial.response_allowed_while_playing) {
|
|
342
|
+
this.audio.addEventListener("ended", respond);
|
|
343
|
+
} else {
|
|
344
|
+
respond();
|
|
345
|
+
}
|
|
346
|
+
});
|
|
347
|
+
}
|
|
289
348
|
}
|
|
290
349
|
|
|
291
350
|
export default AudioButtonResponsePlugin;
|