@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.
@@ -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(function (buffer) {
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(function (err) {
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(function () {
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":"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"}
@@ -1,2 +1,2 @@
1
- var jsPsychAudioButtonResponse=function(e){"use strict";const t={name:"audio-button-response",parameters:{stimulus:{type:e.ParameterType.AUDIO,pretty_name:"Stimulus",default:void 0},choices:{type:e.ParameterType.STRING,pretty_name:"Choices",default:void 0,array:!0},button_html:{type:e.ParameterType.HTML_STRING,pretty_name:"Button HTML",default:'<button class="jspsych-btn">%choice%</button>',array:!0},prompt:{type:e.ParameterType.HTML_STRING,pretty_name:"Prompt",default:null},trial_duration:{type:e.ParameterType.INT,pretty_name:"Trial duration",default:null},margin_vertical:{type:e.ParameterType.STRING,pretty_name:"Margin vertical",default:"0px"},margin_horizontal:{type:e.ParameterType.STRING,pretty_name:"Margin horizontal",default:"8px"},response_ends_trial:{type:e.ParameterType.BOOL,pretty_name:"Response ends trial",default:!0},trial_ends_after_audio:{type:e.ParameterType.BOOL,pretty_name:"Trial ends after audio",default:!1},response_allowed_while_playing:{type:e.ParameterType.BOOL,pretty_name:"Response allowed while playing",default:!0}}};class r{constructor(e){this.jsPsych=e}trial(e,t,r){let n;var o,a,i=this.jsPsych.pluginAPI.audioContext(),s={rt:null,button:null};this.jsPsych.pluginAPI.getAudioBuffer(t.stimulus).then((function(e){null!==i?((o=i.createBufferSource()).buffer=e,o.connect(i.destination)):(o=e).currentTime=0,l()})).catch((function(e){console.error(`Failed to load audio file "${t.stimulus}". Try checking the file path. We recommend using the preload plugin to load audio files.`),console.error(e)}));const l=()=>{t.trial_ends_after_audio&&o.addEventListener("ended",u),t.response_allowed_while_playing||t.trial_ends_after_audio||o.addEventListener("ended",d);var n=[];if(Array.isArray(t.button_html))t.button_html.length==t.choices.length?n=t.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<t.choices.length;s++)n.push(t.button_html);var l='<div id="jspsych-audio-button-response-btngroup">';for(s=0;s<t.choices.length;s++){var c=n[s].replace(/%choice%/g,t.choices[s]);l+='<div class="jspsych-audio-button-response-button" style="cursor: pointer; display: inline-block; margin:'+t.margin_vertical+" "+t.margin_horizontal+'" id="jspsych-audio-button-response-button-'+s+'" data-choice="'+s+'">'+c+"</div>"}l+="</div>",null!==t.prompt&&(l+=t.prompt),e.innerHTML=l,t.response_allowed_while_playing?d():p(),a=performance.now(),null!==i?(a=i.currentTime,o.start(a)):o.play(),null!==t.trial_duration&&this.jsPsych.pluginAPI.setTimeout((function(){u()}),t.trial_duration),r()};const u=()=>{this.jsPsych.pluginAPI.clearAllTimeouts(),null!==i?o.stop():o.pause(),o.removeEventListener("ended",u),o.removeEventListener("ended",d);var r={rt:s.rt,stimulus:t.stimulus,response:s.button};e.innerHTML="",this.jsPsych.finishTrial(r),n()};function c(e){!function(e){var r=performance.now(),n=Math.round(r-a);null!==i&&(r=i.currentTime,n=Math.round(1e3*(r-a))),s.button=parseInt(e),s.rt=n,p(),t.response_ends_trial&&u()}(e.currentTarget.getAttribute("data-choice"))}function p(){for(var e=document.querySelectorAll(".jspsych-audio-button-response-button"),t=0;t<e.length;t++){var r=e[t].querySelector("button");r&&(r.disabled=!0),e[t].removeEventListener("click",c)}}function d(){for(var e=document.querySelectorAll(".jspsych-audio-button-response-button"),t=0;t<e.length;t++){var r=e[t].querySelector("button");r&&(r.disabled=!1),e[t].addEventListener("click",c)}}return new Promise((e=>{n=e}))}}return r.info=t,r}(jsPsychModule);
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":"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"}
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(function (buffer) {
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(function (err) {
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(function () {
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
 
@@ -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":";;;;AAEA,MAAM,IAAI,GAAU;IAClB,IAAI,EAAE,uBAAuB;IAC7B,UAAU,EAAE;;QAEV,QAAQ,EAAE;YACR,IAAI,EAAEA,qBAAa,CAAC,KAAK;YACzB,WAAW,EAAE,UAAU;YACvB,OAAO,EAAE,SAAS;SACnB;;QAED,OAAO,EAAE;YACP,IAAI,EAAEA,qBAAa,CAAC,MAAM;YAC1B,WAAW,EAAE,SAAS;YACtB,OAAO,EAAE,SAAS;YAClB,KAAK,EAAE,IAAI;SACZ;;QAED,WAAW,EAAE;YACX,IAAI,EAAEA,qBAAa,CAAC,WAAW;YAC/B,WAAW,EAAE,aAAa;YAC1B,OAAO,EAAE,+CAA+C;YACxD,KAAK,EAAE,IAAI;SACZ;;QAED,MAAM,EAAE;YACN,IAAI,EAAEA,qBAAa,CAAC,WAAW;YAC/B,WAAW,EAAE,QAAQ;YACrB,OAAO,EAAE,IAAI;SACd;;QAED,cAAc,EAAE;YACd,IAAI,EAAEA,qBAAa,CAAC,GAAG;YACvB,WAAW,EAAE,gBAAgB;YAC7B,OAAO,EAAE,IAAI;SACd;;QAED,eAAe,EAAE;YACf,IAAI,EAAEA,qBAAa,CAAC,MAAM;YAC1B,WAAW,EAAE,iBAAiB;YAC9B,OAAO,EAAE,KAAK;SACf;;QAED,iBAAiB,EAAE;YACjB,IAAI,EAAEA,qBAAa,CAAC,MAAM;YAC1B,WAAW,EAAE,mBAAmB;YAChC,OAAO,EAAE,KAAK;SACf;;QAED,mBAAmB,EAAE;YACnB,IAAI,EAAEA,qBAAa,CAAC,IAAI;YACxB,WAAW,EAAE,qBAAqB;YAClC,OAAO,EAAE,IAAI;SACd;;QAED,sBAAsB,EAAE;YACtB,IAAI,EAAEA,qBAAa,CAAC,IAAI;YACxB,WAAW,EAAE,wBAAwB;YACrC,OAAO,EAAE,KAAK;SACf;;;;;QAKD,8BAA8B,EAAE;YAC9B,IAAI,EAAEA,qBAAa,CAAC,IAAI;YACxB,WAAW,EAAE,gCAAgC;YAC7C,OAAO,EAAE,IAAI;SACd;KACF;CACF,CAAC;AAIF;;;;;;;;AAQA,MAAM,yBAAyB;IAI7B,YAAoB,OAAgB;QAAhB,YAAO,GAAP,OAAO,CAAS;KAAI;IAExC,KAAK,CAAC,eAA4B,EAAE,KAAsB,EAAE,OAAmB;;QAE7E,IAAI,cAAc,CAAC;;QAGnB,IAAI,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,YAAY,EAAE,CAAC;;QAGpD,IAAI,QAAQ,GAAG;YACb,EAAE,EAAE,IAAI;YACR,MAAM,EAAE,IAAI;SACb,CAAC;;QAGF,IAAI,SAAS,CAAC;;QAGd,IAAI,CAAC,OAAO,CAAC,SAAS;aACnB,cAAc,CAAC,KAAK,CAAC,QAAQ,CAAC;aAC9B,IAAI,CAAC,CAAC,MAAM;YACX,IAAI,OAAO,KAAK,IAAI,EAAE;gBACpB,IAAI,CAAC,KAAK,GAAG,OAAO,CAAC,kBAAkB,EAAE,CAAC;gBAC1C,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC;gBAC3B,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;aACzC;iBAAM;gBACL,IAAI,CAAC,KAAK,GAAG,MAAM,CAAC;gBACpB,IAAI,CAAC,KAAK,CAAC,WAAW,GAAG,CAAC,CAAC;aAC5B;YACD,UAAU,EAAE,CAAC;SACd,CAAC;aACD,KAAK,CAAC,CAAC,GAAG;YACT,OAAO,CAAC,KAAK,CACX,8BAA8B,KAAK,CAAC,QAAQ,2FAA2F,CACxI,CAAC;YACF,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;SACpB,CAAC,CAAC;QAEL,MAAM,UAAU,GAAG;;YAEjB,IAAI,KAAK,CAAC,sBAAsB,EAAE;gBAChC,IAAI,CAAC,KAAK,CAAC,gBAAgB,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;aACjD;;YAGD,IAAI,CAAC,KAAK,CAAC,8BAA8B,IAAI,CAAC,KAAK,CAAC,sBAAsB,EAAE;gBAC1E,IAAI,CAAC,KAAK,CAAC,gBAAgB,CAAC,OAAO,EAAE,cAAc,CAAC,CAAC;aACtD;;YAGD,IAAI,OAAO,GAAG,EAAE,CAAC;YACjB,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,WAAW,CAAC,EAAE;gBACpC,IAAI,KAAK,CAAC,WAAW,CAAC,MAAM,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE;oBACpD,OAAO,GAAG,KAAK,CAAC,WAAW,CAAC;iBAC7B;qBAAM;oBACL,OAAO,CAAC,KAAK,CACX,2HAA2H,CAC5H,CAAC;iBACH;aACF;iBAAM;gBACL,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE;oBAC7C,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;iBACjC;aACF;YAED,IAAI,IAAI,GAAG,mDAAmD,CAAC;YAC/D,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE;gBAC7C,IAAI,GAAG,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,WAAW,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;gBAC5D,IAAI;oBACF,0GAA0G;wBAC1G,KAAK,CAAC,eAAe;wBACrB,GAAG;wBACH,KAAK,CAAC,iBAAiB;wBACvB,6CAA6C;wBAC7C,CAAC;wBACD,iBAAiB;wBACjB,CAAC;wBACD,IAAI;wBACJ,GAAG;wBACH,QAAQ,CAAC;aACZ;YACD,IAAI,IAAI,QAAQ,CAAC;;YAGjB,IAAI,KAAK,CAAC,MAAM,KAAK,IAAI,EAAE;gBACzB,IAAI,IAAI,KAAK,CAAC,MAAM,CAAC;aACtB;YAED,eAAe,CAAC,SAAS,GAAG,IAAI,CAAC;YAEjC,IAAI,KAAK,CAAC,8BAA8B,EAAE;gBACxC,cAAc,EAAE,CAAC;aAClB;iBAAM;gBACL,eAAe,EAAE,CAAC;aACnB;;YAGD,SAAS,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC;;YAG9B,IAAI,OAAO,KAAK,IAAI,EAAE;gBACpB,SAAS,GAAG,OAAO,CAAC,WAAW,CAAC;gBAChC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;aAC7B;iBAAM;gBACL,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;aACnB;;YAGD,IAAI,KAAK,CAAC,cAAc,KAAK,IAAI,EAAE;gBACjC,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,UAAU,CAAC;oBAChC,SAAS,EAAE,CAAC;iBACb,EAAE,KAAK,CAAC,cAAc,CAAC,CAAC;aAC1B;YAED,OAAO,EAAE,CAAC;SACX,CAAC;;QAGF,SAAS,cAAc,CAAC,MAAM;;YAE5B,IAAI,OAAO,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC;YAChC,IAAI,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,SAAS,CAAC,CAAC;YACzC,IAAI,OAAO,KAAK,IAAI,EAAE;gBACpB,OAAO,GAAG,OAAO,CAAC,WAAW,CAAC;gBAC9B,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,OAAO,GAAG,SAAS,IAAI,IAAI,CAAC,CAAC;aAC/C;YACD,QAAQ,CAAC,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC;YACnC,QAAQ,CAAC,EAAE,GAAG,EAAE,CAAC;;YAGjB,eAAe,EAAE,CAAC;YAElB,IAAI,KAAK,CAAC,mBAAmB,EAAE;gBAC7B,SAAS,EAAE,CAAC;aACb;SACF;;QAGD,MAAM,SAAS,GAAG;;YAEhB,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,gBAAgB,EAAE,CAAC;;;YAI1C,IAAI,OAAO,KAAK,IAAI,EAAE;gBACpB,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;aACnB;iBAAM;gBACL,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;aACpB;YAED,IAAI,CAAC,KAAK,CAAC,mBAAmB,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;YACnD,IAAI,CAAC,KAAK,CAAC,mBAAmB,CAAC,OAAO,EAAE,cAAc,CAAC,CAAC;;YAGxD,IAAI,UAAU,GAAG;gBACf,EAAE,EAAE,QAAQ,CAAC,EAAE;gBACf,QAAQ,EAAE,KAAK,CAAC,QAAQ;gBACxB,QAAQ,EAAE,QAAQ,CAAC,MAAM;aAC1B,CAAC;;YAGF,eAAe,CAAC,SAAS,GAAG,EAAE,CAAC;;YAG/B,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,UAAU,CAAC,CAAC;YAErC,cAAc,EAAE,CAAC;SAClB,CAAC;QAEF,SAAS,eAAe,CAAC,CAAC;YACxB,IAAI,MAAM,GAAG,CAAC,CAAC,aAAa,CAAC,YAAY,CAAC,aAAa,CAAC,CAAC;YACzD,cAAc,CAAC,MAAM,CAAC,CAAC;SACxB;QAED,SAAS,eAAe;YACtB,IAAI,IAAI,GAAG,QAAQ,CAAC,gBAAgB,CAAC,uCAAuC,CAAC,CAAC;YAC9E,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE;gBACpC,IAAI,MAAM,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;gBAC7C,IAAI,MAAM,EAAE;oBACV,MAAM,CAAC,QAAQ,GAAG,IAAI,CAAC;iBACxB;gBACD,IAAI,CAAC,CAAC,CAAC,CAAC,mBAAmB,CAAC,OAAO,EAAE,eAAe,CAAC,CAAC;aACvD;SACF;QAED,SAAS,cAAc;YACrB,IAAI,IAAI,GAAG,QAAQ,CAAC,gBAAgB,CAAC,uCAAuC,CAAC,CAAC;YAC9E,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE;gBACpC,IAAI,MAAM,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;gBAC7C,IAAI,MAAM,EAAE;oBACV,MAAM,CAAC,QAAQ,GAAG,KAAK,CAAC;iBACzB;gBACD,IAAI,CAAC,CAAC,CAAC,CAAC,gBAAgB,CAAC,OAAO,EAAE,eAAe,CAAC,CAAC;aACpD;SACF;QAED,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO;YACzB,cAAc,GAAG,OAAO,CAAC;SAC1B,CAAC,CAAC;KACJ;IAED,QAAQ,CACN,KAAsB,EACtB,eAAe,EACf,kBAAuB,EACvB,aAAyB;QAEzB,IAAI,eAAe,IAAI,WAAW,EAAE;YAClC,aAAa,EAAE,CAAC;YAChB,IAAI,CAAC,kBAAkB,CAAC,KAAK,EAAE,kBAAkB,CAAC,CAAC;SACpD;QACD,IAAI,eAAe,IAAI,QAAQ,EAAE;YAC/B,IAAI,CAAC,eAAe,CAAC,KAAK,EAAE,kBAAkB,EAAE,aAAa,CAAC,CAAC;SAChE;KACF;IAEO,sBAAsB,CAAC,KAAsB,EAAE,kBAAkB;QACvE,MAAM,YAAY,GAAG;YACnB,QAAQ,EAAE,KAAK,CAAC,QAAQ;YACxB,EAAE,EAAE,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,gBAAgB,CAAC,GAAG,EAAE,EAAE,EAAE,CAAC,GAAG,GAAG,EAAE,IAAI,CAAC;YACvE,QAAQ,EAAE,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,SAAS,CAAC,CAAC,EAAE,KAAK,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC;SAC5E,CAAC;QAEF,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,mBAAmB,CAAC,YAAY,EAAE,kBAAkB,CAAC,CAAC;QAE1F,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,+BAA+B,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;QAEpE,OAAO,IAAI,CAAC;KACb;IAEO,kBAAkB,CAAC,KAAsB,EAAE,kBAAkB;QACnE,MAAM,IAAI,GAAG,IAAI,CAAC,sBAAsB,CAAC,KAAK,EAAE,kBAAkB,CAAC,CAAC;QAEpE,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;KAChC;IAEO,eAAe,CAAC,KAAsB,EAAE,kBAAkB,EAAE,aAAyB;QAC3F,MAAM,IAAI,GAAG,IAAI,CAAC,sBAAsB,CAAC,KAAK,EAAE,kBAAkB,CAAC,CAAC;QAEpE,MAAM,eAAe,GAAG,IAAI,CAAC,OAAO,CAAC,iBAAiB,EAAE,CAAC;QAEzD,MAAM,OAAO,GAAG;YACd,IAAI,IAAI,CAAC,EAAE,KAAK,IAAI,EAAE;gBACpB,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,WAAW,CAChC,eAAe,CAAC,aAAa,CAAC,oBAAoB,IAAI,CAAC,QAAQ,WAAW,CAAC,EAC3E,IAAI,CAAC,EAAE,CACR,CAAC;aACH;SACF,CAAC;QAEF,IAAI,CAAC,KAAK,CAAC,eAAe,EAAE,KAAK,EAAE;YACjC,aAAa,EAAE,CAAC;YAChB,IAAI,CAAC,KAAK,CAAC,8BAA8B,EAAE;gBACzC,IAAI,CAAC,KAAK,CAAC,gBAAgB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;aAC/C;iBAAM;gBACL,OAAO,EAAE,CAAC;aACX;SACF,CAAC,CAAC;KACJ;;AAtQM,8BAAI,GAAG,IAAI;;;;"}
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(function (buffer) {
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(function (err) {
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(function () {
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.0.0",
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 --passWithNoTests",
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.0.0"
37
+ "jspsych": ">=7.1.0"
38
38
  },
39
39
  "devDependencies": {
40
- "@jspsych/config": "^1.0.0",
41
- "@jspsych/test-utils": "^1.0.0"
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("ffgfgoo");
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(function (buffer) {
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(function (err) {
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(function () {
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;