@jspsych/plugin-audio-keyboard-response 1.1.3 → 2.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.
@@ -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-keyboard-response\",\n parameters: {\n /** The audio file to be played. */\n stimulus: {\n type: ParameterType.AUDIO,\n pretty_name: \"Stimulus\",\n default: undefined,\n },\n /** Array containing the key(s) the subject is allowed to press to respond to the stimulus. */\n choices: {\n type: ParameterType.KEYS,\n pretty_name: \"Choices\",\n default: \"ALL_KEYS\",\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 /** 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 /** If true, then responses are allowed while the audio is playing. If false, then the audio must finish playing before a response is accepted. */\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-keyboard-response**\n *\n * jsPsych plugin for playing an audio file and getting a keyboard response\n *\n * @author Josh de Leeuw\n * @see {@link https://www.jspsych.org/plugins/jspsych-audio-keyboard-response/ audio-keyboard-response plugin documentation on jspsych.org}\n */\nclass AudioKeyboardResponsePlugin 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 key: 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 // show prompt if there is one\n if (trial.prompt !== null) {\n display_element.innerHTML = trial.prompt;\n }\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 // start keyboard listener when trial starts or sound ends\n if (trial.response_allowed_while_playing) {\n setup_keyboard_listener();\n } else if (!trial.trial_ends_after_audio) {\n this.audio.addEventListener(\"ended\", setup_keyboard_listener);\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 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\", setup_keyboard_listener);\n\n // kill keyboard listeners\n this.jsPsych.pluginAPI.cancelAllKeyboardResponses();\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.key,\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 to handle responses by the subject\n function after_response(info) {\n // only record the first response\n if (response.key == null) {\n response = info;\n }\n\n if (trial.response_ends_trial) {\n end_trial();\n }\n }\n\n const setup_keyboard_listener = () => {\n // start the response listener\n if (context !== null) {\n this.jsPsych.pluginAPI.getKeyboardResponse({\n callback_function: after_response,\n valid_responses: trial.choices,\n rt_method: \"audio\",\n persist: false,\n allow_held_key: false,\n audio_context: context,\n audio_context_start_time: startTime,\n });\n } else {\n this.jsPsych.pluginAPI.getKeyboardResponse({\n callback_function: after_response,\n valid_responses: trial.choices,\n rt_method: \"performance\",\n persist: false,\n allow_held_key: false,\n });\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 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.pressKey(data.response, data.rt);\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 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.pluginAPI.getValidKey(trial.choices),\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\nexport default AudioKeyboardResponsePlugin;\n"],"names":["info","name","parameters","stimulus","type","ParameterType","AUDIO","pretty_name","default","undefined","choices","KEYS","prompt","HTML_STRING","trial_duration","INT","response_ends_trial","BOOL","trial_ends_after_audio","response_allowed_while_playing","AudioKeyboardResponsePlugin","jsPsych","_classCallCheck","this","key","value","display_element","trial","on_load","trial_complete","startTime","_this","context","pluginAPI","audioContext","response","rt","getAudioBuffer","then","buffer","audio","createBufferSource","connect","destination","currentTime","setupTrial","err","console","error","concat","addEventListener","end_trial","innerHTML","start","play","setup_keyboard_listener","setTimeout","clearAllTimeouts","stop","pause","removeEventListener","cancelAllKeyboardResponses","trial_data","finishTrial","after_response","getKeyboardResponse","callback_function","valid_responses","rt_method","persist","allow_held_key","audio_context","audio_context_start_time","Promise","resolve","simulation_mode","simulation_options","load_callback","simulate_data_only","simulate_visual","data","create_simulation_data","_this2","getDisplayElement","respond","pressKey","default_data","randomization","sampleExGaussian","getValidKey","mergeSimulationData","ensureSimulationDataConsistency"],"mappings":"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"}
1
+ {"version":3,"file":"index.browser.min.js","sources":["../../../node_modules/auto-bind/index.js","../package.json","../src/index.ts"],"sourcesContent":["'use strict';\n\n// Gets all non-builtin properties up the prototype chain\nconst getAllProperties = object => {\n\tconst properties = new Set();\n\n\tdo {\n\t\tfor (const key of Reflect.ownKeys(object)) {\n\t\t\tproperties.add([object, key]);\n\t\t}\n\t} while ((object = Reflect.getPrototypeOf(object)) && object !== Object.prototype);\n\n\treturn properties;\n};\n\nmodule.exports = (self, {include, exclude} = {}) => {\n\tconst filter = key => {\n\t\tconst match = pattern => typeof pattern === 'string' ? key === pattern : pattern.test(key);\n\n\t\tif (include) {\n\t\t\treturn include.some(match);\n\t\t}\n\n\t\tif (exclude) {\n\t\t\treturn !exclude.some(match);\n\t\t}\n\n\t\treturn true;\n\t};\n\n\tfor (const [object, key] of getAllProperties(self.constructor.prototype)) {\n\t\tif (key === 'constructor' || !filter(key)) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst descriptor = Reflect.getOwnPropertyDescriptor(object, key);\n\t\tif (descriptor && typeof descriptor.value === 'function') {\n\t\t\tself[key] = self[key].bind(self);\n\t\t}\n\t}\n\n\treturn self;\n};\n","{\n \"name\": \"@jspsych/plugin-audio-keyboard-response\",\n \"version\": \"2.1.0\",\n \"description\": \"jsPsych plugin for playing an audio file and getting a keyboard response\",\n \"type\": \"module\",\n \"main\": \"dist/index.cjs\",\n \"exports\": {\n \"import\": \"./dist/index.js\",\n \"require\": \"./dist/index.cjs\"\n },\n \"typings\": \"dist/index.d.ts\",\n \"unpkg\": \"dist/index.browser.min.js\",\n \"files\": [\n \"src\",\n \"dist\"\n ],\n \"source\": \"src/index.ts\",\n \"scripts\": {\n \"test\": \"jest\",\n \"test:watch\": \"npm test -- --watch\",\n \"tsc\": \"tsc\",\n \"build\": \"rollup --config\",\n \"build:watch\": \"npm run build -- --watch\"\n },\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/jspsych/jsPsych.git\",\n \"directory\": \"packages/plugin-audio-keyboard-response\"\n },\n \"author\": \"Josh de Leeuw\",\n \"license\": \"MIT\",\n \"bugs\": {\n \"url\": \"https://github.com/jspsych/jsPsych/issues\"\n },\n \"homepage\": \"https://www.jspsych.org/latest/plugins/audio-keyboard-response\",\n \"peerDependencies\": {\n \"jspsych\": \">=7.1.0\"\n },\n \"devDependencies\": {\n \"@jspsych/config\": \"^3.2.0\",\n \"@jspsych/test-utils\": \"^1.2.0\"\n }\n}\n","import autoBind from \"auto-bind\";\nimport { JsPsych, JsPsychPlugin, ParameterType, TrialType } from \"jspsych\";\n\nimport { AudioPlayerInterface } from \"../../jspsych/src/modules/plugin-api/AudioPlayer\";\nimport { version } from \"../package.json\";\n\nconst info = <const>{\n name: \"audio-keyboard-response\",\n version: version,\n parameters: {\n /** The audio file to be played. */\n stimulus: {\n type: ParameterType.AUDIO,\n default: undefined,\n },\n /** This array contains the key(s) that the participant is allowed to press in order to respond to the stimulus.\n * Keys should be specified as characters (e.g., `'a'`, `'q'`, `' '`, `'Enter'`, `'ArrowDown'`) -\n * see [this page](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values)\n * and [this page (event.key column)](https://www.freecodecamp.org/news/javascript-keycode-list-keypress-event-key-codes/)\n * for more examples. Any key presses that are not listed in the array will be ignored. The default value of `\"ALL_KEYS\"`\n * means that all keys will be accepted as valid responses. Specifying `\"NO_KEYS\"` will mean that no responses are allowed.\n */\n choices: {\n type: ParameterType.KEYS,\n default: \"ALL_KEYS\",\n },\n /** This string can contain HTML markup. Any content here will be displayed below the stimulus. The intention is that\n * it can be used to provide a reminder about the action the participant is supposed to take (e.g., which key to press).\n */\n prompt: {\n type: ParameterType.HTML_STRING,\n pretty_name: \"Prompt\",\n default: null,\n },\n /** How long to wait for the participant to make a response before ending the trial in milliseconds. If the\n * participant fails to make a response before this timer is reached, the participant's response will be\n * recorded as null for the trial and the trial will end. If the value of this parameter is null, then the\n * trial will wait for a response indefinitely.\n */\n trial_duration: {\n type: ParameterType.INT,\n default: null,\n },\n /** If true, then the trial will end whenever the participant makes a response (assuming they make their\n * response before the cutoff specified by the `trial_duration` parameter). If false, then the trial will\n * continue until the value for `trial_duration` is reached. You can use set this parameter to `false` to\n * force the participant to listen to the stimulus for a fixed amount of time, even if they respond before the time is complete\n */\n response_ends_trial: {\n type: ParameterType.BOOL,\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 /** If true, then responses are allowed while the audio is playing. If false, then the audio must finish\n * playing before a keyboard response is accepted. Once the audio has played all the way through, a valid\n * keyboard response is allowed (including while the audio is being re-played via on-screen playback controls).\n */\n response_allowed_while_playing: {\n type: ParameterType.BOOL,\n default: true,\n },\n },\n data: {\n /** Indicates which key the participant pressed. If no key was pressed before the trial ended, then the value will be `null`. */\n response: {\n type: ParameterType.STRING,\n },\n /** The response time in milliseconds for the participant to make a response. The time is measured from when the stimulus\n * first began playing until the participant made a key response. If no key was pressed before the trial ended, then the\n * value will be `null`.\n */\n rt: {\n type: ParameterType.INT,\n },\n /** Path to the audio file that played during the trial. */\n stimulus: {\n type: ParameterType.STRING,\n },\n },\n // prettier-ignore\n citations: '__CITATIONS__',\n};\n\ntype Info = typeof info;\n\n/**\n * This plugin plays audio files and records responses generated with the keyboard.\n *\n * If the browser supports it, audio files are played using the WebAudio API. This allows for reasonably precise timing of the\n * playback. The timing of responses generated is measured against the WebAudio specific clock, improving the measurement of\n * response times. If the browser does not support the WebAudio API, then the audio file is played with HTML5 audio.\n *\n * Audio files can be automatically preloaded by jsPsych using the [`preload` plugin](preload.md). However, if you are using\n * timeline variables or another dynamic method to specify the audio stimulus, then you will need to [manually preload](../overview/media-preloading.md#manual-preloading) the audio.\n *\n * The trial can end when the participant responds, when the audio file has finished playing, or if the participant has\n * failed to respond within a fixed length of time. You can also prevent a keyboard response from being recorded before\n * the audio has finished playing.\n *\n * @author Josh de Leeuw\n * @see {@link https://www.jspsych.org/latest/plugins/audio-keyboard-response/ audio-keyboard-response plugin documentation on jspsych.org}\n */\nclass AudioKeyboardResponsePlugin implements JsPsychPlugin<Info> {\n static info = info;\n private audio: AudioPlayerInterface;\n private params: TrialType<Info>;\n private display: HTMLElement;\n private response: { rt: number; key: string } = { rt: null, key: null };\n private startTime: number;\n private finish: ({}: { rt: number; response: string; stimulus: string }) => void;\n\n constructor(private jsPsych: JsPsych) {\n autoBind(this);\n }\n\n trial(display_element: HTMLElement, trial: TrialType<Info>, on_load: () => void) {\n return new Promise(async (resolve) => {\n this.finish = resolve;\n this.params = trial;\n this.display = display_element;\n // load audio file\n this.audio = await this.jsPsych.pluginAPI.getAudioPlayer(trial.stimulus);\n\n // set up end event if trial needs it\n if (trial.trial_ends_after_audio) {\n this.audio.addEventListener(\"ended\", this.end_trial);\n }\n\n // show prompt if there is one\n if (trial.prompt !== null) {\n display_element.innerHTML = trial.prompt;\n }\n\n // start playing audio here to record time\n // use this for offsetting RT measurement in\n // setup_keyboard_listener\n this.startTime = this.jsPsych.pluginAPI.audioContext()?.currentTime;\n\n // start keyboard listener when trial starts or sound ends\n if (trial.response_allowed_while_playing) {\n this.setup_keyboard_listener();\n } else if (!trial.trial_ends_after_audio) {\n this.audio.addEventListener(\"ended\", this.setup_keyboard_listener);\n }\n\n // end trial if time limit is set\n if (trial.trial_duration !== null) {\n this.jsPsych.pluginAPI.setTimeout(() => {\n this.end_trial();\n }, trial.trial_duration);\n }\n\n // call trial on_load method because we are done with all loading setup\n on_load();\n\n this.audio.play();\n });\n }\n\n private end_trial() {\n // kill any remaining setTimeout handlers\n this.jsPsych.pluginAPI.clearAllTimeouts();\n\n // stop the audio file if it is playing\n this.audio.stop();\n\n // remove end event listeners if they exist\n this.audio.removeEventListener(\"ended\", this.end_trial);\n this.audio.removeEventListener(\"ended\", this.setup_keyboard_listener);\n\n // kill keyboard listeners\n this.jsPsych.pluginAPI.cancelAllKeyboardResponses();\n\n // gather the data to store for the trial\n var trial_data = {\n rt: this.response.rt,\n response: this.response.key,\n stimulus: this.params.stimulus,\n };\n\n // clear the display\n this.display.innerHTML = \"\";\n\n // move on to the next trial\n this.finish(trial_data);\n }\n\n private after_response(info: { key: string; rt: number }) {\n this.response = info;\n if (this.params.response_ends_trial) {\n this.end_trial();\n }\n }\n\n private setup_keyboard_listener() {\n // start the response listener\n if (this.jsPsych.pluginAPI.useWebaudio) {\n this.jsPsych.pluginAPI.getKeyboardResponse({\n callback_function: this.after_response,\n valid_responses: this.params.choices,\n rt_method: \"audio\",\n persist: false,\n allow_held_key: false,\n audio_context: this.jsPsych.pluginAPI.audioContext(),\n audio_context_start_time: this.startTime,\n });\n } else {\n this.jsPsych.pluginAPI.getKeyboardResponse({\n callback_function: this.after_response,\n valid_responses: this.params.choices,\n rt_method: \"performance\",\n persist: false,\n allow_held_key: false,\n });\n }\n }\n\n async 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 return this.simulate_data_only(trial, simulation_options);\n }\n if (simulation_mode == \"visual\") {\n return this.simulate_visual(trial, simulation_options, load_callback);\n }\n }\n\n private simulate_data_only(trial: TrialType<Info>, simulation_options) {\n const data = this.create_simulation_data(trial, simulation_options);\n\n return data;\n }\n\n private async simulate_visual(\n trial: TrialType<Info>,\n simulation_options,\n load_callback: () => void\n ) {\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.pressKey(data.response, data.rt);\n }\n };\n\n const result = await 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 return result;\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.pluginAPI.getValidKey(trial.choices),\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\nexport default AudioKeyboardResponsePlugin;\n"],"names":["getAllProperties","object","properties","key","autoBind","self","include","exclude","filter","match","pattern","descriptor","version","_a"],"mappings":"8JAGA,MAAMA,EAAmBC,GAAU,CAClC,MAAMC,EAAa,IAAI,IAEvB,EACC,WAAWC,KAAO,QAAQ,QAAQF,CAAM,EACvCC,EAAW,IAAI,CAACD,EAAQE,CAAG,CAAC,SAEpBF,EAAS,QAAQ,eAAeA,CAAM,IAAMA,IAAW,OAAO,WAExE,OAAOC,CACR,MAEAE,EAAiB,CAACC,EAAM,CAAC,QAAAC,EAAS,QAAAC,CAAO,EAAI,CAAA,IAAO,CACnD,MAAMC,EAASL,GAAO,CACrB,MAAMM,EAAQC,GAAW,OAAOA,GAAY,SAAWP,IAAQO,EAAUA,EAAQ,KAAKP,CAAG,EAEzF,OAAIG,EACIA,EAAQ,KAAKG,CAAK,EAGtBF,EACI,CAACA,EAAQ,KAAKE,CAAK,EAGpB,EACT,EAEC,SAAW,CAACR,EAAQE,CAAG,IAAKH,EAAiBK,EAAK,YAAY,SAAS,EAAG,CACzE,GAAIF,IAAQ,eAAiB,CAACK,EAAOL,CAAG,EACvC,SAGD,MAAMQ,EAAa,QAAQ,yBAAyBV,EAAQE,CAAG,EAC3DQ,GAAc,OAAOA,EAAW,OAAU,aAC7CN,EAAKF,CAAG,EAAIE,EAAKF,CAAG,EAAE,KAAKE,CAAI,EAElC,CAEC,OAAOA,CACR,SCxCEO,EAAW,s1BCmFA,UAAA,iuBAAe,4HArF5B,KAAA,WAAA,CAAA,IAAAC","x_google_ignoreList":[0]}
package/dist/index.cjs CHANGED
@@ -1,237 +1,213 @@
1
1
  'use strict';
2
2
 
3
+ var autoBind = require('auto-bind');
3
4
  var jspsych = require('jspsych');
4
5
 
6
+ var version = "2.1.0";
7
+
5
8
  const info = {
6
- name: "audio-keyboard-response",
7
- parameters: {
8
- /** The audio file to be played. */
9
- stimulus: {
10
- type: jspsych.ParameterType.AUDIO,
11
- pretty_name: "Stimulus",
12
- default: undefined,
13
- },
14
- /** Array containing the key(s) the subject is allowed to press to respond to the stimulus. */
15
- choices: {
16
- type: jspsych.ParameterType.KEYS,
17
- pretty_name: "Choices",
18
- default: "ALL_KEYS",
19
- },
20
- /** Any content here will be displayed below the stimulus. */
21
- prompt: {
22
- type: jspsych.ParameterType.HTML_STRING,
23
- pretty_name: "Prompt",
24
- default: null,
25
- },
26
- /** The maximum duration to wait for a response. */
27
- trial_duration: {
28
- type: jspsych.ParameterType.INT,
29
- pretty_name: "Trial duration",
30
- default: null,
31
- },
32
- /** If true, the trial will end when user makes a response. */
33
- response_ends_trial: {
34
- type: jspsych.ParameterType.BOOL,
35
- pretty_name: "Response ends trial",
36
- default: true,
37
- },
38
- /** If true, then the trial will end as soon as the audio file finishes playing. */
39
- trial_ends_after_audio: {
40
- type: jspsych.ParameterType.BOOL,
41
- pretty_name: "Trial ends after audio",
42
- default: false,
43
- },
44
- /** If true, then responses are allowed while the audio is playing. If false, then the audio must finish playing before a response is accepted. */
45
- response_allowed_while_playing: {
46
- type: jspsych.ParameterType.BOOL,
47
- pretty_name: "Response allowed while playing",
48
- default: true,
49
- },
9
+ name: "audio-keyboard-response",
10
+ version,
11
+ parameters: {
12
+ /** The audio file to be played. */
13
+ stimulus: {
14
+ type: jspsych.ParameterType.AUDIO,
15
+ default: void 0
50
16
  },
51
- };
52
- /**
53
- * **audio-keyboard-response**
54
- *
55
- * jsPsych plugin for playing an audio file and getting a keyboard response
56
- *
57
- * @author Josh de Leeuw
58
- * @see {@link https://www.jspsych.org/plugins/jspsych-audio-keyboard-response/ audio-keyboard-response plugin documentation on jspsych.org}
59
- */
60
- class AudioKeyboardResponsePlugin {
61
- constructor(jsPsych) {
62
- this.jsPsych = jsPsych;
17
+ /** This array contains the key(s) that the participant is allowed to press in order to respond to the stimulus.
18
+ * Keys should be specified as characters (e.g., `'a'`, `'q'`, `' '`, `'Enter'`, `'ArrowDown'`) -
19
+ * see [this page](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values)
20
+ * and [this page (event.key column)](https://www.freecodecamp.org/news/javascript-keycode-list-keypress-event-key-codes/)
21
+ * for more examples. Any key presses that are not listed in the array will be ignored. The default value of `"ALL_KEYS"`
22
+ * means that all keys will be accepted as valid responses. Specifying `"NO_KEYS"` will mean that no responses are allowed.
23
+ */
24
+ choices: {
25
+ type: jspsych.ParameterType.KEYS,
26
+ default: "ALL_KEYS"
27
+ },
28
+ /** This string can contain HTML markup. Any content here will be displayed below the stimulus. The intention is that
29
+ * it can be used to provide a reminder about the action the participant is supposed to take (e.g., which key to press).
30
+ */
31
+ prompt: {
32
+ type: jspsych.ParameterType.HTML_STRING,
33
+ pretty_name: "Prompt",
34
+ default: null
35
+ },
36
+ /** How long to wait for the participant to make a response before ending the trial in milliseconds. If the
37
+ * participant fails to make a response before this timer is reached, the participant's response will be
38
+ * recorded as null for the trial and the trial will end. If the value of this parameter is null, then the
39
+ * trial will wait for a response indefinitely.
40
+ */
41
+ trial_duration: {
42
+ type: jspsych.ParameterType.INT,
43
+ default: null
44
+ },
45
+ /** If true, then the trial will end whenever the participant makes a response (assuming they make their
46
+ * response before the cutoff specified by the `trial_duration` parameter). If false, then the trial will
47
+ * continue until the value for `trial_duration` is reached. You can use set this parameter to `false` to
48
+ * force the participant to listen to the stimulus for a fixed amount of time, even if they respond before the time is complete
49
+ */
50
+ response_ends_trial: {
51
+ type: jspsych.ParameterType.BOOL,
52
+ default: true
53
+ },
54
+ /** If true, then the trial will end as soon as the audio file finishes playing. */
55
+ trial_ends_after_audio: {
56
+ type: jspsych.ParameterType.BOOL,
57
+ pretty_name: "Trial ends after audio",
58
+ default: false
59
+ },
60
+ /** If true, then responses are allowed while the audio is playing. If false, then the audio must finish
61
+ * playing before a keyboard response is accepted. Once the audio has played all the way through, a valid
62
+ * keyboard response is allowed (including while the audio is being re-played via on-screen playback controls).
63
+ */
64
+ response_allowed_while_playing: {
65
+ type: jspsych.ParameterType.BOOL,
66
+ default: true
63
67
  }
64
- trial(display_element, trial, on_load) {
65
- // hold the .resolve() function from the Promise that ends the trial
66
- let trial_complete;
67
- // setup stimulus
68
- var context = this.jsPsych.pluginAPI.audioContext();
69
- // store response
70
- var response = {
71
- rt: null,
72
- key: null,
73
- };
74
- // record webaudio context start time
75
- var startTime;
76
- // load audio file
77
- this.jsPsych.pluginAPI
78
- .getAudioBuffer(trial.stimulus)
79
- .then((buffer) => {
80
- if (context !== null) {
81
- this.audio = context.createBufferSource();
82
- this.audio.buffer = buffer;
83
- this.audio.connect(context.destination);
84
- }
85
- else {
86
- this.audio = buffer;
87
- this.audio.currentTime = 0;
88
- }
89
- setupTrial();
90
- })
91
- .catch((err) => {
92
- console.error(`Failed to load audio file "${trial.stimulus}". Try checking the file path. We recommend using the preload plugin to load audio files.`);
93
- console.error(err);
94
- });
95
- const setupTrial = () => {
96
- // set up end event if trial needs it
97
- if (trial.trial_ends_after_audio) {
98
- this.audio.addEventListener("ended", end_trial);
99
- }
100
- // show prompt if there is one
101
- if (trial.prompt !== null) {
102
- display_element.innerHTML = trial.prompt;
103
- }
104
- // start audio
105
- if (context !== null) {
106
- startTime = context.currentTime;
107
- this.audio.start(startTime);
108
- }
109
- else {
110
- this.audio.play();
111
- }
112
- // start keyboard listener when trial starts or sound ends
113
- if (trial.response_allowed_while_playing) {
114
- setup_keyboard_listener();
115
- }
116
- else if (!trial.trial_ends_after_audio) {
117
- this.audio.addEventListener("ended", setup_keyboard_listener);
118
- }
119
- // end trial if time limit is set
120
- if (trial.trial_duration !== null) {
121
- this.jsPsych.pluginAPI.setTimeout(() => {
122
- end_trial();
123
- }, trial.trial_duration);
124
- }
125
- on_load();
126
- };
127
- // function to end trial when it is time
128
- const end_trial = () => {
129
- // kill any remaining setTimeout handlers
130
- this.jsPsych.pluginAPI.clearAllTimeouts();
131
- // stop the audio file if it is playing
132
- // remove end event listeners if they exist
133
- if (context !== null) {
134
- this.audio.stop();
135
- }
136
- else {
137
- this.audio.pause();
138
- }
139
- this.audio.removeEventListener("ended", end_trial);
140
- this.audio.removeEventListener("ended", setup_keyboard_listener);
141
- // kill keyboard listeners
142
- this.jsPsych.pluginAPI.cancelAllKeyboardResponses();
143
- // gather the data to store for the trial
144
- var trial_data = {
145
- rt: response.rt,
146
- stimulus: trial.stimulus,
147
- response: response.key,
148
- };
149
- // clear the display
150
- display_element.innerHTML = "";
151
- // move on to the next trial
152
- this.jsPsych.finishTrial(trial_data);
153
- trial_complete();
154
- };
155
- // function to handle responses by the subject
156
- function after_response(info) {
157
- // only record the first response
158
- if (response.key == null) {
159
- response = info;
160
- }
161
- if (trial.response_ends_trial) {
162
- end_trial();
163
- }
164
- }
165
- const setup_keyboard_listener = () => {
166
- // start the response listener
167
- if (context !== null) {
168
- this.jsPsych.pluginAPI.getKeyboardResponse({
169
- callback_function: after_response,
170
- valid_responses: trial.choices,
171
- rt_method: "audio",
172
- persist: false,
173
- allow_held_key: false,
174
- audio_context: context,
175
- audio_context_start_time: startTime,
176
- });
177
- }
178
- else {
179
- this.jsPsych.pluginAPI.getKeyboardResponse({
180
- callback_function: after_response,
181
- valid_responses: trial.choices,
182
- rt_method: "performance",
183
- persist: false,
184
- allow_held_key: false,
185
- });
186
- }
187
- };
188
- return new Promise((resolve) => {
189
- trial_complete = resolve;
190
- });
68
+ },
69
+ data: {
70
+ /** Indicates which key the participant pressed. If no key was pressed before the trial ended, then the value will be `null`. */
71
+ response: {
72
+ type: jspsych.ParameterType.STRING
73
+ },
74
+ /** The response time in milliseconds for the participant to make a response. The time is measured from when the stimulus
75
+ * first began playing until the participant made a key response. If no key was pressed before the trial ended, then the
76
+ * value will be `null`.
77
+ */
78
+ rt: {
79
+ type: jspsych.ParameterType.INT
80
+ },
81
+ /** Path to the audio file that played during the trial. */
82
+ stimulus: {
83
+ type: jspsych.ParameterType.STRING
191
84
  }
192
- simulate(trial, simulation_mode, simulation_options, load_callback) {
193
- if (simulation_mode == "data-only") {
194
- load_callback();
195
- this.simulate_data_only(trial, simulation_options);
196
- }
197
- if (simulation_mode == "visual") {
198
- this.simulate_visual(trial, simulation_options, load_callback);
199
- }
85
+ },
86
+ // prettier-ignore
87
+ citations: {
88
+ "apa": "de Leeuw, J. R., Gilbert, R. A., & Luchterhandt, B. (2023). jsPsych: Enabling an Open-Source Collaborative Ecosystem of Behavioral Experiments. Journal of Open Source Software, 8(85), 5351. https://doi.org/10.21105/joss.05351 ",
89
+ "bibtex": '@article{Leeuw2023jsPsych, author = {de Leeuw, Joshua R. and Gilbert, Rebecca A. and Luchterhandt, Bj{\\" o}rn}, journal = {Journal of Open Source Software}, doi = {10.21105/joss.05351}, issn = {2475-9066}, number = {85}, year = {2023}, month = {may 11}, pages = {5351}, publisher = {Open Journals}, title = {jsPsych: Enabling an {Open}-{Source} {Collaborative} {Ecosystem} of {Behavioral} {Experiments}}, url = {https://joss.theoj.org/papers/10.21105/joss.05351}, volume = {8}, } '
90
+ }
91
+ };
92
+ class AudioKeyboardResponsePlugin {
93
+ constructor(jsPsych) {
94
+ this.jsPsych = jsPsych;
95
+ this.response = { rt: null, key: null };
96
+ autoBind(this);
97
+ }
98
+ static {
99
+ this.info = info;
100
+ }
101
+ trial(display_element, trial, on_load) {
102
+ return new Promise(async (resolve) => {
103
+ this.finish = resolve;
104
+ this.params = trial;
105
+ this.display = display_element;
106
+ this.audio = await this.jsPsych.pluginAPI.getAudioPlayer(trial.stimulus);
107
+ if (trial.trial_ends_after_audio) {
108
+ this.audio.addEventListener("ended", this.end_trial);
109
+ }
110
+ if (trial.prompt !== null) {
111
+ display_element.innerHTML = trial.prompt;
112
+ }
113
+ this.startTime = this.jsPsych.pluginAPI.audioContext()?.currentTime;
114
+ if (trial.response_allowed_while_playing) {
115
+ this.setup_keyboard_listener();
116
+ } else if (!trial.trial_ends_after_audio) {
117
+ this.audio.addEventListener("ended", this.setup_keyboard_listener);
118
+ }
119
+ if (trial.trial_duration !== null) {
120
+ this.jsPsych.pluginAPI.setTimeout(() => {
121
+ this.end_trial();
122
+ }, trial.trial_duration);
123
+ }
124
+ on_load();
125
+ this.audio.play();
126
+ });
127
+ }
128
+ end_trial() {
129
+ this.jsPsych.pluginAPI.clearAllTimeouts();
130
+ this.audio.stop();
131
+ this.audio.removeEventListener("ended", this.end_trial);
132
+ this.audio.removeEventListener("ended", this.setup_keyboard_listener);
133
+ this.jsPsych.pluginAPI.cancelAllKeyboardResponses();
134
+ var trial_data = {
135
+ rt: this.response.rt,
136
+ response: this.response.key,
137
+ stimulus: this.params.stimulus
138
+ };
139
+ this.display.innerHTML = "";
140
+ this.finish(trial_data);
141
+ }
142
+ after_response(info2) {
143
+ this.response = info2;
144
+ if (this.params.response_ends_trial) {
145
+ this.end_trial();
200
146
  }
201
- simulate_data_only(trial, simulation_options) {
202
- const data = this.create_simulation_data(trial, simulation_options);
203
- this.jsPsych.finishTrial(data);
147
+ }
148
+ setup_keyboard_listener() {
149
+ if (this.jsPsych.pluginAPI.useWebaudio) {
150
+ this.jsPsych.pluginAPI.getKeyboardResponse({
151
+ callback_function: this.after_response,
152
+ valid_responses: this.params.choices,
153
+ rt_method: "audio",
154
+ persist: false,
155
+ allow_held_key: false,
156
+ audio_context: this.jsPsych.pluginAPI.audioContext(),
157
+ audio_context_start_time: this.startTime
158
+ });
159
+ } else {
160
+ this.jsPsych.pluginAPI.getKeyboardResponse({
161
+ callback_function: this.after_response,
162
+ valid_responses: this.params.choices,
163
+ rt_method: "performance",
164
+ persist: false,
165
+ allow_held_key: false
166
+ });
204
167
  }
205
- simulate_visual(trial, simulation_options, load_callback) {
206
- const data = this.create_simulation_data(trial, simulation_options);
207
- const display_element = this.jsPsych.getDisplayElement();
208
- const respond = () => {
209
- if (data.rt !== null) {
210
- this.jsPsych.pluginAPI.pressKey(data.response, data.rt);
211
- }
212
- };
213
- this.trial(display_element, trial, () => {
214
- load_callback();
215
- if (!trial.response_allowed_while_playing) {
216
- this.audio.addEventListener("ended", respond);
217
- }
218
- else {
219
- respond();
220
- }
221
- });
168
+ }
169
+ async simulate(trial, simulation_mode, simulation_options, load_callback) {
170
+ if (simulation_mode == "data-only") {
171
+ load_callback();
172
+ return this.simulate_data_only(trial, simulation_options);
222
173
  }
223
- create_simulation_data(trial, simulation_options) {
224
- const default_data = {
225
- stimulus: trial.stimulus,
226
- rt: this.jsPsych.randomization.sampleExGaussian(500, 50, 1 / 150, true),
227
- response: this.jsPsych.pluginAPI.getValidKey(trial.choices),
228
- };
229
- const data = this.jsPsych.pluginAPI.mergeSimulationData(default_data, simulation_options);
230
- this.jsPsych.pluginAPI.ensureSimulationDataConsistency(trial, data);
231
- return data;
174
+ if (simulation_mode == "visual") {
175
+ return this.simulate_visual(trial, simulation_options, load_callback);
232
176
  }
177
+ }
178
+ simulate_data_only(trial, simulation_options) {
179
+ const data = this.create_simulation_data(trial, simulation_options);
180
+ return data;
181
+ }
182
+ async simulate_visual(trial, simulation_options, load_callback) {
183
+ const data = this.create_simulation_data(trial, simulation_options);
184
+ const display_element = this.jsPsych.getDisplayElement();
185
+ const respond = () => {
186
+ if (data.rt !== null) {
187
+ this.jsPsych.pluginAPI.pressKey(data.response, data.rt);
188
+ }
189
+ };
190
+ const result = await this.trial(display_element, trial, () => {
191
+ load_callback();
192
+ if (!trial.response_allowed_while_playing) {
193
+ this.audio.addEventListener("ended", respond);
194
+ } else {
195
+ respond();
196
+ }
197
+ });
198
+ return result;
199
+ }
200
+ create_simulation_data(trial, simulation_options) {
201
+ const default_data = {
202
+ stimulus: trial.stimulus,
203
+ rt: this.jsPsych.randomization.sampleExGaussian(500, 50, 1 / 150, true),
204
+ response: this.jsPsych.pluginAPI.getValidKey(trial.choices)
205
+ };
206
+ const data = this.jsPsych.pluginAPI.mergeSimulationData(default_data, simulation_options);
207
+ this.jsPsych.pluginAPI.ensureSimulationDataConsistency(trial, data);
208
+ return data;
209
+ }
233
210
  }
234
- AudioKeyboardResponsePlugin.info = info;
235
211
 
236
212
  module.exports = AudioKeyboardResponsePlugin;
237
213
  //# sourceMappingURL=index.cjs.map
@@ -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-keyboard-response\",\n parameters: {\n /** The audio file to be played. */\n stimulus: {\n type: ParameterType.AUDIO,\n pretty_name: \"Stimulus\",\n default: undefined,\n },\n /** Array containing the key(s) the subject is allowed to press to respond to the stimulus. */\n choices: {\n type: ParameterType.KEYS,\n pretty_name: \"Choices\",\n default: \"ALL_KEYS\",\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 /** 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 /** If true, then responses are allowed while the audio is playing. If false, then the audio must finish playing before a response is accepted. */\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-keyboard-response**\n *\n * jsPsych plugin for playing an audio file and getting a keyboard response\n *\n * @author Josh de Leeuw\n * @see {@link https://www.jspsych.org/plugins/jspsych-audio-keyboard-response/ audio-keyboard-response plugin documentation on jspsych.org}\n */\nclass AudioKeyboardResponsePlugin 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 key: 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 // show prompt if there is one\n if (trial.prompt !== null) {\n display_element.innerHTML = trial.prompt;\n }\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 // start keyboard listener when trial starts or sound ends\n if (trial.response_allowed_while_playing) {\n setup_keyboard_listener();\n } else if (!trial.trial_ends_after_audio) {\n this.audio.addEventListener(\"ended\", setup_keyboard_listener);\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 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\", setup_keyboard_listener);\n\n // kill keyboard listeners\n this.jsPsych.pluginAPI.cancelAllKeyboardResponses();\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.key,\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 to handle responses by the subject\n function after_response(info) {\n // only record the first response\n if (response.key == null) {\n response = info;\n }\n\n if (trial.response_ends_trial) {\n end_trial();\n }\n }\n\n const setup_keyboard_listener = () => {\n // start the response listener\n if (context !== null) {\n this.jsPsych.pluginAPI.getKeyboardResponse({\n callback_function: after_response,\n valid_responses: trial.choices,\n rt_method: \"audio\",\n persist: false,\n allow_held_key: false,\n audio_context: context,\n audio_context_start_time: startTime,\n });\n } else {\n this.jsPsych.pluginAPI.getKeyboardResponse({\n callback_function: after_response,\n valid_responses: trial.choices,\n rt_method: \"performance\",\n persist: false,\n allow_held_key: false,\n });\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 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.pressKey(data.response, data.rt);\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 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.pluginAPI.getValidKey(trial.choices),\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\nexport default AudioKeyboardResponsePlugin;\n"],"names":["ParameterType"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","sources":["../package.json","../src/index.ts"],"sourcesContent":["{\n \"name\": \"@jspsych/plugin-audio-keyboard-response\",\n \"version\": \"2.1.0\",\n \"description\": \"jsPsych plugin for playing an audio file and getting a keyboard response\",\n \"type\": \"module\",\n \"main\": \"dist/index.cjs\",\n \"exports\": {\n \"import\": \"./dist/index.js\",\n \"require\": \"./dist/index.cjs\"\n },\n \"typings\": \"dist/index.d.ts\",\n \"unpkg\": \"dist/index.browser.min.js\",\n \"files\": [\n \"src\",\n \"dist\"\n ],\n \"source\": \"src/index.ts\",\n \"scripts\": {\n \"test\": \"jest\",\n \"test:watch\": \"npm test -- --watch\",\n \"tsc\": \"tsc\",\n \"build\": \"rollup --config\",\n \"build:watch\": \"npm run build -- --watch\"\n },\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/jspsych/jsPsych.git\",\n \"directory\": \"packages/plugin-audio-keyboard-response\"\n },\n \"author\": \"Josh de Leeuw\",\n \"license\": \"MIT\",\n \"bugs\": {\n \"url\": \"https://github.com/jspsych/jsPsych/issues\"\n },\n \"homepage\": \"https://www.jspsych.org/latest/plugins/audio-keyboard-response\",\n \"peerDependencies\": {\n \"jspsych\": \">=7.1.0\"\n },\n \"devDependencies\": {\n \"@jspsych/config\": \"^3.2.0\",\n \"@jspsych/test-utils\": \"^1.2.0\"\n }\n}\n","import autoBind from \"auto-bind\";\nimport { JsPsych, JsPsychPlugin, ParameterType, TrialType } from \"jspsych\";\n\nimport { AudioPlayerInterface } from \"../../jspsych/src/modules/plugin-api/AudioPlayer\";\nimport { version } from \"../package.json\";\n\nconst info = <const>{\n name: \"audio-keyboard-response\",\n version: version,\n parameters: {\n /** The audio file to be played. */\n stimulus: {\n type: ParameterType.AUDIO,\n default: undefined,\n },\n /** This array contains the key(s) that the participant is allowed to press in order to respond to the stimulus.\n * Keys should be specified as characters (e.g., `'a'`, `'q'`, `' '`, `'Enter'`, `'ArrowDown'`) -\n * see [this page](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values)\n * and [this page (event.key column)](https://www.freecodecamp.org/news/javascript-keycode-list-keypress-event-key-codes/)\n * for more examples. Any key presses that are not listed in the array will be ignored. The default value of `\"ALL_KEYS\"`\n * means that all keys will be accepted as valid responses. Specifying `\"NO_KEYS\"` will mean that no responses are allowed.\n */\n choices: {\n type: ParameterType.KEYS,\n default: \"ALL_KEYS\",\n },\n /** This string can contain HTML markup. Any content here will be displayed below the stimulus. The intention is that\n * it can be used to provide a reminder about the action the participant is supposed to take (e.g., which key to press).\n */\n prompt: {\n type: ParameterType.HTML_STRING,\n pretty_name: \"Prompt\",\n default: null,\n },\n /** How long to wait for the participant to make a response before ending the trial in milliseconds. If the\n * participant fails to make a response before this timer is reached, the participant's response will be\n * recorded as null for the trial and the trial will end. If the value of this parameter is null, then the\n * trial will wait for a response indefinitely.\n */\n trial_duration: {\n type: ParameterType.INT,\n default: null,\n },\n /** If true, then the trial will end whenever the participant makes a response (assuming they make their\n * response before the cutoff specified by the `trial_duration` parameter). If false, then the trial will\n * continue until the value for `trial_duration` is reached. You can use set this parameter to `false` to\n * force the participant to listen to the stimulus for a fixed amount of time, even if they respond before the time is complete\n */\n response_ends_trial: {\n type: ParameterType.BOOL,\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 /** If true, then responses are allowed while the audio is playing. If false, then the audio must finish\n * playing before a keyboard response is accepted. Once the audio has played all the way through, a valid\n * keyboard response is allowed (including while the audio is being re-played via on-screen playback controls).\n */\n response_allowed_while_playing: {\n type: ParameterType.BOOL,\n default: true,\n },\n },\n data: {\n /** Indicates which key the participant pressed. If no key was pressed before the trial ended, then the value will be `null`. */\n response: {\n type: ParameterType.STRING,\n },\n /** The response time in milliseconds for the participant to make a response. The time is measured from when the stimulus\n * first began playing until the participant made a key response. If no key was pressed before the trial ended, then the\n * value will be `null`.\n */\n rt: {\n type: ParameterType.INT,\n },\n /** Path to the audio file that played during the trial. */\n stimulus: {\n type: ParameterType.STRING,\n },\n },\n // prettier-ignore\n citations: '__CITATIONS__',\n};\n\ntype Info = typeof info;\n\n/**\n * This plugin plays audio files and records responses generated with the keyboard.\n *\n * If the browser supports it, audio files are played using the WebAudio API. This allows for reasonably precise timing of the\n * playback. The timing of responses generated is measured against the WebAudio specific clock, improving the measurement of\n * response times. If the browser does not support the WebAudio API, then the audio file is played with HTML5 audio.\n *\n * Audio files can be automatically preloaded by jsPsych using the [`preload` plugin](preload.md). However, if you are using\n * timeline variables or another dynamic method to specify the audio stimulus, then you will need to [manually preload](../overview/media-preloading.md#manual-preloading) the audio.\n *\n * The trial can end when the participant responds, when the audio file has finished playing, or if the participant has\n * failed to respond within a fixed length of time. You can also prevent a keyboard response from being recorded before\n * the audio has finished playing.\n *\n * @author Josh de Leeuw\n * @see {@link https://www.jspsych.org/latest/plugins/audio-keyboard-response/ audio-keyboard-response plugin documentation on jspsych.org}\n */\nclass AudioKeyboardResponsePlugin implements JsPsychPlugin<Info> {\n static info = info;\n private audio: AudioPlayerInterface;\n private params: TrialType<Info>;\n private display: HTMLElement;\n private response: { rt: number; key: string } = { rt: null, key: null };\n private startTime: number;\n private finish: ({}: { rt: number; response: string; stimulus: string }) => void;\n\n constructor(private jsPsych: JsPsych) {\n autoBind(this);\n }\n\n trial(display_element: HTMLElement, trial: TrialType<Info>, on_load: () => void) {\n return new Promise(async (resolve) => {\n this.finish = resolve;\n this.params = trial;\n this.display = display_element;\n // load audio file\n this.audio = await this.jsPsych.pluginAPI.getAudioPlayer(trial.stimulus);\n\n // set up end event if trial needs it\n if (trial.trial_ends_after_audio) {\n this.audio.addEventListener(\"ended\", this.end_trial);\n }\n\n // show prompt if there is one\n if (trial.prompt !== null) {\n display_element.innerHTML = trial.prompt;\n }\n\n // start playing audio here to record time\n // use this for offsetting RT measurement in\n // setup_keyboard_listener\n this.startTime = this.jsPsych.pluginAPI.audioContext()?.currentTime;\n\n // start keyboard listener when trial starts or sound ends\n if (trial.response_allowed_while_playing) {\n this.setup_keyboard_listener();\n } else if (!trial.trial_ends_after_audio) {\n this.audio.addEventListener(\"ended\", this.setup_keyboard_listener);\n }\n\n // end trial if time limit is set\n if (trial.trial_duration !== null) {\n this.jsPsych.pluginAPI.setTimeout(() => {\n this.end_trial();\n }, trial.trial_duration);\n }\n\n // call trial on_load method because we are done with all loading setup\n on_load();\n\n this.audio.play();\n });\n }\n\n private end_trial() {\n // kill any remaining setTimeout handlers\n this.jsPsych.pluginAPI.clearAllTimeouts();\n\n // stop the audio file if it is playing\n this.audio.stop();\n\n // remove end event listeners if they exist\n this.audio.removeEventListener(\"ended\", this.end_trial);\n this.audio.removeEventListener(\"ended\", this.setup_keyboard_listener);\n\n // kill keyboard listeners\n this.jsPsych.pluginAPI.cancelAllKeyboardResponses();\n\n // gather the data to store for the trial\n var trial_data = {\n rt: this.response.rt,\n response: this.response.key,\n stimulus: this.params.stimulus,\n };\n\n // clear the display\n this.display.innerHTML = \"\";\n\n // move on to the next trial\n this.finish(trial_data);\n }\n\n private after_response(info: { key: string; rt: number }) {\n this.response = info;\n if (this.params.response_ends_trial) {\n this.end_trial();\n }\n }\n\n private setup_keyboard_listener() {\n // start the response listener\n if (this.jsPsych.pluginAPI.useWebaudio) {\n this.jsPsych.pluginAPI.getKeyboardResponse({\n callback_function: this.after_response,\n valid_responses: this.params.choices,\n rt_method: \"audio\",\n persist: false,\n allow_held_key: false,\n audio_context: this.jsPsych.pluginAPI.audioContext(),\n audio_context_start_time: this.startTime,\n });\n } else {\n this.jsPsych.pluginAPI.getKeyboardResponse({\n callback_function: this.after_response,\n valid_responses: this.params.choices,\n rt_method: \"performance\",\n persist: false,\n allow_held_key: false,\n });\n }\n }\n\n async 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 return this.simulate_data_only(trial, simulation_options);\n }\n if (simulation_mode == \"visual\") {\n return this.simulate_visual(trial, simulation_options, load_callback);\n }\n }\n\n private simulate_data_only(trial: TrialType<Info>, simulation_options) {\n const data = this.create_simulation_data(trial, simulation_options);\n\n return data;\n }\n\n private async simulate_visual(\n trial: TrialType<Info>,\n simulation_options,\n load_callback: () => void\n ) {\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.pressKey(data.response, data.rt);\n }\n };\n\n const result = await 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 return result;\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.pluginAPI.getValidKey(trial.choices),\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\nexport default AudioKeyboardResponsePlugin;\n"],"names":[],"mappings":";;;;;AAEE,IAAW,OAAA,GAAA,OAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ECmFA,SAAA,EAAA;AAAA;;GAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}