@jspsych/plugin-audio-keyboard-response 2.1.1 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.browser.js +36 -8
- package/dist/index.browser.js.map +1 -1
- package/dist/index.browser.min.js +2 -2
- package/dist/index.browser.min.js.map +1 -1
- package/dist/index.cjs +35 -7
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +30 -0
- package/dist/index.js +35 -7
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/index.spec.ts +169 -1
- package/src/index.ts +40 -4
package/dist/index.browser.js
CHANGED
|
@@ -49,7 +49,7 @@ var jsPsychAudioKeyboardResponse = (function (jspsych) {
|
|
|
49
49
|
|
|
50
50
|
var autoBind$1 = /*@__PURE__*/getDefaultExportFromCjs(autoBind);
|
|
51
51
|
|
|
52
|
-
var version = "2.
|
|
52
|
+
var version = "2.2.0";
|
|
53
53
|
|
|
54
54
|
const info = {
|
|
55
55
|
name: "audio-keyboard-response",
|
|
@@ -110,6 +110,17 @@ var jsPsychAudioKeyboardResponse = (function (jspsych) {
|
|
|
110
110
|
response_allowed_while_playing: {
|
|
111
111
|
type: jspsych.ParameterType.BOOL,
|
|
112
112
|
default: true
|
|
113
|
+
},
|
|
114
|
+
/** If true, the response is not registered until the participant releases the key. The response
|
|
115
|
+
* time (`rt`) still reflects when the key was pressed, and the additional data field
|
|
116
|
+
* `rt_key_duration` records how long the key was held down. Note that when this is true, the
|
|
117
|
+
* trial cannot end until the key is released: with `response_ends_trial: true` the trial ends at
|
|
118
|
+
* the key release, and if the trial ends for another reason (e.g., `trial_duration`) while the
|
|
119
|
+
* key is still held, no response is recorded for the trial.
|
|
120
|
+
*/
|
|
121
|
+
wait_for_key_release: {
|
|
122
|
+
type: jspsych.ParameterType.BOOL,
|
|
123
|
+
default: false
|
|
113
124
|
}
|
|
114
125
|
},
|
|
115
126
|
data: {
|
|
@@ -127,6 +138,10 @@ var jsPsychAudioKeyboardResponse = (function (jspsych) {
|
|
|
127
138
|
/** Path to the audio file that played during the trial. */
|
|
128
139
|
stimulus: {
|
|
129
140
|
type: jspsych.ParameterType.STRING
|
|
141
|
+
},
|
|
142
|
+
/** The duration in milliseconds that the response key was held down, measured from key press to key release. Only recorded when `wait_for_key_release` is true; null otherwise or when no response was made. */
|
|
143
|
+
rt_key_duration: {
|
|
144
|
+
type: jspsych.ParameterType.INT
|
|
130
145
|
}
|
|
131
146
|
},
|
|
132
147
|
// prettier-ignore
|
|
@@ -138,7 +153,11 @@ var jsPsychAudioKeyboardResponse = (function (jspsych) {
|
|
|
138
153
|
class AudioKeyboardResponsePlugin {
|
|
139
154
|
constructor(jsPsych) {
|
|
140
155
|
this.jsPsych = jsPsych;
|
|
141
|
-
this.response = {
|
|
156
|
+
this.response = {
|
|
157
|
+
rt: null,
|
|
158
|
+
key: null,
|
|
159
|
+
rt_key_duration: null
|
|
160
|
+
};
|
|
142
161
|
autoBind$1(this);
|
|
143
162
|
}
|
|
144
163
|
static {
|
|
@@ -180,13 +199,18 @@ var jsPsychAudioKeyboardResponse = (function (jspsych) {
|
|
|
180
199
|
var trial_data = {
|
|
181
200
|
rt: this.response.rt,
|
|
182
201
|
response: this.response.key,
|
|
183
|
-
stimulus: this.params.stimulus
|
|
202
|
+
stimulus: this.params.stimulus,
|
|
203
|
+
rt_key_duration: this.response.rt_key_duration
|
|
184
204
|
};
|
|
185
205
|
this.display.innerHTML = "";
|
|
186
206
|
this.finish(trial_data);
|
|
187
207
|
}
|
|
188
208
|
after_response(info2) {
|
|
189
|
-
this.response =
|
|
209
|
+
this.response = {
|
|
210
|
+
rt: info2.rt,
|
|
211
|
+
key: info2.key,
|
|
212
|
+
rt_key_duration: info2.rt_key_duration ?? null
|
|
213
|
+
};
|
|
190
214
|
if (this.params.response_ends_trial) {
|
|
191
215
|
this.end_trial();
|
|
192
216
|
}
|
|
@@ -200,7 +224,8 @@ var jsPsychAudioKeyboardResponse = (function (jspsych) {
|
|
|
200
224
|
persist: false,
|
|
201
225
|
allow_held_key: false,
|
|
202
226
|
audio_context: this.jsPsych.pluginAPI.audioContext(),
|
|
203
|
-
audio_context_start_time: this.startTime
|
|
227
|
+
audio_context_start_time: this.startTime,
|
|
228
|
+
wait_for_key_release: this.params.wait_for_key_release
|
|
204
229
|
});
|
|
205
230
|
} else {
|
|
206
231
|
this.jsPsych.pluginAPI.getKeyboardResponse({
|
|
@@ -208,7 +233,8 @@ var jsPsychAudioKeyboardResponse = (function (jspsych) {
|
|
|
208
233
|
valid_responses: this.params.choices,
|
|
209
234
|
rt_method: "performance",
|
|
210
235
|
persist: false,
|
|
211
|
-
allow_held_key: false
|
|
236
|
+
allow_held_key: false,
|
|
237
|
+
wait_for_key_release: this.params.wait_for_key_release
|
|
212
238
|
});
|
|
213
239
|
}
|
|
214
240
|
}
|
|
@@ -247,8 +273,10 @@ var jsPsychAudioKeyboardResponse = (function (jspsych) {
|
|
|
247
273
|
const default_data = {
|
|
248
274
|
stimulus: trial.stimulus,
|
|
249
275
|
rt: this.jsPsych.randomization.sampleExGaussian(500, 50, 1 / 150, true),
|
|
250
|
-
response: this.jsPsych.pluginAPI.getValidKey(trial.choices)
|
|
276
|
+
response: this.jsPsych.pluginAPI.getValidKey(trial.choices),
|
|
277
|
+
rt_key_duration: null
|
|
251
278
|
};
|
|
279
|
+
default_data.rt_key_duration = default_data.rt === null || !trial.wait_for_key_release ? null : this.jsPsych.randomization.sampleExGaussian(150, 30, 1 / 100, true);
|
|
252
280
|
const data = this.jsPsych.pluginAPI.mergeSimulationData(default_data, simulation_options);
|
|
253
281
|
this.jsPsych.pluginAPI.ensureSimulationDataConsistency(trial, data);
|
|
254
282
|
return data;
|
|
@@ -258,4 +286,4 @@ var jsPsychAudioKeyboardResponse = (function (jspsych) {
|
|
|
258
286
|
return AudioKeyboardResponsePlugin;
|
|
259
287
|
|
|
260
288
|
})(jsPsychModule);
|
|
261
|
-
//# sourceMappingURL=https://unpkg.com/@jspsych/plugin-audio-keyboard-response@2.
|
|
289
|
+
//# sourceMappingURL=https://unpkg.com/@jspsych/plugin-audio-keyboard-response@2.2.0/dist/index.browser.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.browser.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.1\",\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 // 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 // stop the audio file if it is playing\n this.audio.stop();\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":";;;;;;;CAEA;CACA,MAAM,gBAAgB,GAAG,MAAM,IAAI;CACnC,CAAC,MAAM,UAAU,GAAG,IAAI,GAAG,EAAE,CAAA;;CAE7B,CAAC,GAAG;CACJ,EAAE,KAAK,MAAM,GAAG,IAAI,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE;CAC7C,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAA;CAChC,GAAA;CACA,EAAE,QAAQ,CAAC,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,MAAM,CAAC,KAAK,MAAM,KAAK,MAAM,CAAC,SAAS,EAAA;;CAElF,CAAC,OAAO,UAAU,CAAA;CAClB,CAAC,CAAA;;KAED,QAAc,GAAG,CAAC,IAAI,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,GAAG,EAAE,KAAK;CACpD,CAAC,MAAM,MAAM,GAAG,GAAG,IAAI;CACvB,EAAE,MAAM,KAAK,GAAG,OAAO,IAAI,OAAO,OAAO,KAAK,QAAQ,GAAG,GAAG,KAAK,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;;CAE5F,EAAE,IAAI,OAAO,EAAE;CACf,GAAG,OAAO,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;CAC7B,GAAA;;CAEA,EAAE,IAAI,OAAO,EAAE;CACf,GAAG,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;CAC9B,GAAA;;CAEA,EAAE,OAAO,IAAI,CAAA;CACb,EAAE,CAAA;;CAEF,CAAC,KAAK,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,IAAI,gBAAgB,CAAC,IAAI,CAAC,WAAW,CAAC,SAAS,CAAC,EAAE;CAC3E,EAAE,IAAI,GAAG,KAAK,aAAa,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE;CAC7C,GAAG,SAAA;CACH,GAAA;;CAEA,EAAE,MAAM,UAAU,GAAG,OAAO,CAAC,wBAAwB,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;CAClE,EAAE,IAAI,UAAU,IAAI,OAAO,UAAU,CAAC,KAAK,KAAK,UAAU,EAAE;CAC5D,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;CACnC,GAAA;CACA,EAAA;;CAEA,CAAC,OAAO,IAAI,CAAA;CACZ,CAAC,CAAA;;;;CCxCC,IAAW,OAAA,GAAA,OAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GCmFA,SAAA,EAAA;CAAA;;IAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;","x_google_ignoreList":[0]}
|
|
1
|
+
{"version":3,"file":"index.browser.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.2.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.3.4\",\n \"@jspsych/test-utils\": \"^1.3.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 /** If true, the response is not registered until the participant releases the key. The response\n * time (`rt`) still reflects when the key was pressed, and the additional data field\n * `rt_key_duration` records how long the key was held down. Note that when this is true, the\n * trial cannot end until the key is released: with `response_ends_trial: true` the trial ends at\n * the key release, and if the trial ends for another reason (e.g., `trial_duration`) while the\n * key is still held, no response is recorded for the trial.\n */\n wait_for_key_release: {\n type: ParameterType.BOOL,\n default: false,\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 /** The duration in milliseconds that the response key was held down, measured from key press to key release. Only recorded when `wait_for_key_release` is true; null otherwise or when no response was made. */\n rt_key_duration: {\n type: ParameterType.INT,\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_key_duration: number } = {\n rt: null,\n key: null,\n rt_key_duration: null,\n };\n private startTime: number;\n private finish: ({}: {\n rt: number;\n response: string;\n stimulus: string;\n rt_key_duration: number;\n }) => 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 // 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 // stop the audio file if it is playing\n this.audio.stop();\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 rt_key_duration: this.response.rt_key_duration,\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; rt_key_duration?: number }) {\n this.response = {\n rt: info.rt,\n key: info.key,\n rt_key_duration: info.rt_key_duration ?? null,\n };\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 wait_for_key_release: this.params.wait_for_key_release,\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 wait_for_key_release: this.params.wait_for_key_release,\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 rt_key_duration: null,\n };\n default_data.rt_key_duration =\n default_data.rt === null || !trial.wait_for_key_release\n ? null\n : this.jsPsych.randomization.sampleExGaussian(150, 30, 1 / 100, true);\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":";;;;;;;CAEA;CACA,MAAM,gBAAgB,GAAG,MAAM,IAAI;CACnC,CAAC,MAAM,UAAU,GAAG,IAAI,GAAG,EAAE;;CAE7B,CAAC,GAAG;CACJ,EAAE,KAAK,MAAM,GAAG,IAAI,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE;CAC7C,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CAChC,EAAA;CACA,CAAA,CAAE,QAAQ,CAAC,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,MAAM,CAAC,KAAK,MAAM,KAAK,MAAM,CAAC,SAAS;;CAElF,CAAC,OAAO,UAAU;CAClB,CAAC;;KAED,QAAc,GAAG,CAAC,IAAI,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,GAAG,EAAE,KAAK;CACpD,CAAC,MAAM,MAAM,GAAG,GAAG,IAAI;CACvB,EAAE,MAAM,KAAK,GAAG,OAAO,IAAI,OAAO,OAAO,KAAK,QAAQ,GAAG,GAAG,KAAK,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC;;CAE5F,EAAE,IAAI,OAAO,EAAE;CACf,GAAG,OAAO,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC;CAC7B,EAAA;;CAEA,EAAE,IAAI,OAAO,EAAE;CACf,GAAG,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC;CAC9B,EAAA;;CAEA,EAAE,OAAO,IAAI;CACb,CAAA,CAAE;;CAEF,CAAC,KAAK,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,IAAI,gBAAgB,CAAC,IAAI,CAAC,WAAW,CAAC,SAAS,CAAC,EAAE;CAC3E,EAAE,IAAI,GAAG,KAAK,aAAa,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE;CAC7C,GAAG;CACH,EAAA;;CAEA,EAAE,MAAM,UAAU,GAAG,OAAO,CAAC,wBAAwB,CAAC,MAAM,EAAE,GAAG,CAAC;CAClE,EAAE,IAAI,UAAU,IAAI,OAAO,UAAU,CAAC,KAAK,KAAK,UAAU,EAAE;CAC5D,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;CACnC,EAAA;CACA,CAAA;;CAEA,CAAC,OAAO,IAAI;CACZ,CAAC;;;;CCxCC,IAAA,OAAA,GAAW,OAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GCkGA,SAAA,EAAA;CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;","x_google_ignoreList":[0]}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
var jsPsychAudioKeyboardResponse=function(
|
|
2
|
-
//# sourceMappingURL=https://unpkg.com/@jspsych/plugin-audio-keyboard-response@2.
|
|
1
|
+
var jsPsychAudioKeyboardResponse=(function(n){"use strict";function h(s){return s&&s.__esModule&&Object.prototype.hasOwnProperty.call(s,"default")?s.default:s}const y=s=>{const e=new Set;do for(const t of Reflect.ownKeys(s))e.add([s,t]);while((s=Reflect.getPrototypeOf(s))&&s!==Object.prototype);return e};var c=(s,{include:e,exclude:t}={})=>{const i=r=>{const a=o=>typeof o=="string"?r===o:o.test(r);return e?e.some(a):t?!t.some(a):!0};for(const[r,a]of y(s.constructor.prototype)){if(a==="constructor"||!i(a))continue;const o=Reflect.getOwnPropertyDescriptor(r,a);o&&typeof o.value=="function"&&(s[a]=s[a].bind(s))}return s},m=h(c),f="2.2.0",u=(s,e,t)=>new Promise((i,r)=>{var a=l=>{try{d(t.next(l))}catch(p){r(p)}},o=l=>{try{d(t.throw(l))}catch(p){r(p)}},d=l=>l.done?i(l.value):Promise.resolve(l.value).then(a,o);d((t=t.apply(s,e)).next())});const P={name:"audio-keyboard-response",version:f,parameters:{stimulus:{type:n.ParameterType.AUDIO,default:void 0},choices:{type:n.ParameterType.KEYS,default:"ALL_KEYS"},prompt:{type:n.ParameterType.HTML_STRING,pretty_name:"Prompt",default:null},trial_duration:{type:n.ParameterType.INT,default:null},response_ends_trial:{type:n.ParameterType.BOOL,default:!0},trial_ends_after_audio:{type:n.ParameterType.BOOL,pretty_name:"Trial ends after audio",default:!1},response_allowed_while_playing:{type:n.ParameterType.BOOL,default:!0},wait_for_key_release:{type:n.ParameterType.BOOL,default:!1}},data:{response:{type:n.ParameterType.STRING},rt:{type:n.ParameterType.INT},stimulus:{type:n.ParameterType.STRING},rt_key_duration:{type:n.ParameterType.INT}},citations:{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 ",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}, } '}};class _{constructor(e){this.jsPsych=e,this.response={rt:null,key:null,rt_key_duration:null},m(this)}trial(e,t,i){return new Promise(r=>u(this,null,function*(){var a;this.finish=r,this.params=t,this.display=e,this.audio=yield this.jsPsych.pluginAPI.getAudioPlayer(t.stimulus),t.trial_ends_after_audio&&this.audio.addEventListener("ended",this.end_trial),t.prompt!==null&&(e.innerHTML=t.prompt),this.startTime=(a=this.jsPsych.pluginAPI.audioContext())==null?void 0:a.currentTime,t.response_allowed_while_playing?this.setup_keyboard_listener():t.trial_ends_after_audio||this.audio.addEventListener("ended",this.setup_keyboard_listener),t.trial_duration!==null&&this.jsPsych.pluginAPI.setTimeout(()=>{this.end_trial()},t.trial_duration),i(),this.audio.play()}))}end_trial(){this.jsPsych.pluginAPI.clearAllTimeouts(),this.audio.removeEventListener("ended",this.end_trial),this.audio.removeEventListener("ended",this.setup_keyboard_listener),this.audio.stop(),this.jsPsych.pluginAPI.cancelAllKeyboardResponses();var e={rt:this.response.rt,response:this.response.key,stimulus:this.params.stimulus,rt_key_duration:this.response.rt_key_duration};this.display.innerHTML="",this.finish(e)}after_response(e){var t;this.response={rt:e.rt,key:e.key,rt_key_duration:(t=e.rt_key_duration)!=null?t:null},this.params.response_ends_trial&&this.end_trial()}setup_keyboard_listener(){this.jsPsych.pluginAPI.useWebaudio?this.jsPsych.pluginAPI.getKeyboardResponse({callback_function:this.after_response,valid_responses:this.params.choices,rt_method:"audio",persist:!1,allow_held_key:!1,audio_context:this.jsPsych.pluginAPI.audioContext(),audio_context_start_time:this.startTime,wait_for_key_release:this.params.wait_for_key_release}):this.jsPsych.pluginAPI.getKeyboardResponse({callback_function:this.after_response,valid_responses:this.params.choices,rt_method:"performance",persist:!1,allow_held_key:!1,wait_for_key_release:this.params.wait_for_key_release})}simulate(e,t,i,r){return u(this,null,function*(){if(t=="data-only")return r(),this.simulate_data_only(e,i);if(t=="visual")return this.simulate_visual(e,i,r)})}simulate_data_only(e,t){return this.create_simulation_data(e,t)}simulate_visual(e,t,i){return u(this,null,function*(){const r=this.create_simulation_data(e,t),a=this.jsPsych.getDisplayElement(),o=()=>{r.rt!==null&&this.jsPsych.pluginAPI.pressKey(r.response,r.rt)};return yield this.trial(a,e,()=>{i(),e.response_allowed_while_playing?o():this.audio.addEventListener("ended",o)})})}create_simulation_data(e,t){const i={stimulus:e.stimulus,rt:this.jsPsych.randomization.sampleExGaussian(500,50,.006666666666666667,!0),response:this.jsPsych.pluginAPI.getValidKey(e.choices),rt_key_duration:null};i.rt_key_duration=i.rt===null||!e.wait_for_key_release?null:this.jsPsych.randomization.sampleExGaussian(150,30,.01,!0);const r=this.jsPsych.pluginAPI.mergeSimulationData(i,t);return this.jsPsych.pluginAPI.ensureSimulationDataConsistency(e,r),r}}return _.info=P,_})(jsPsychModule);
|
|
2
|
+
//# sourceMappingURL=https://unpkg.com/@jspsych/plugin-audio-keyboard-response@2.2.0/dist/index.browser.min.js.map
|
|
@@ -1 +1 @@
|
|
|
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.1\",\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 // 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 // stop the audio file if it is playing\n this.audio.stop();\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]}
|
|
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.2.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.3.4\",\n \"@jspsych/test-utils\": \"^1.3.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 /** If true, the response is not registered until the participant releases the key. The response\n * time (`rt`) still reflects when the key was pressed, and the additional data field\n * `rt_key_duration` records how long the key was held down. Note that when this is true, the\n * trial cannot end until the key is released: with `response_ends_trial: true` the trial ends at\n * the key release, and if the trial ends for another reason (e.g., `trial_duration`) while the\n * key is still held, no response is recorded for the trial.\n */\n wait_for_key_release: {\n type: ParameterType.BOOL,\n default: false,\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 /** The duration in milliseconds that the response key was held down, measured from key press to key release. Only recorded when `wait_for_key_release` is true; null otherwise or when no response was made. */\n rt_key_duration: {\n type: ParameterType.INT,\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_key_duration: number } = {\n rt: null,\n key: null,\n rt_key_duration: null,\n };\n private startTime: number;\n private finish: ({}: {\n rt: number;\n response: string;\n stimulus: string;\n rt_key_duration: number;\n }) => 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 // 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 // stop the audio file if it is playing\n this.audio.stop();\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 rt_key_duration: this.response.rt_key_duration,\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; rt_key_duration?: number }) {\n this.response = {\n rt: info.rt,\n key: info.key,\n rt_key_duration: info.rt_key_duration ?? null,\n };\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 wait_for_key_release: this.params.wait_for_key_release,\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 wait_for_key_release: this.params.wait_for_key_release,\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 rt_key_duration: null,\n };\n default_data.rt_key_duration =\n default_data.rt === null || !trial.wait_for_key_release\n ? null\n : this.jsPsych.randomization.sampleExGaussian(150, 30, 1 / 100, true);\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","d","t","e","s","n","r","l","_a"],"mappings":"+JAGA,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,QCFb,EAAA,CAAAC,EAAAC,EAAAC,IAAA,IAAA,QAAA,CAAA,EAAAC,IAAA,CAAA,IAAAC,EAAAC,GAAA,CAAA,GAAA,CAAAC,EAAAJ,EAAA,KAAAG,CAAA,CAAA,CAAA,OAAA,EAAA,CAAAF,EAAA,CAAA,CAAA,CAAA,EAAA,EAAAE,GAAA,CAAA,GAAA,CAAAC,EAAAJ,EAAA,MAAAG,CAAA,CAAA,CAAA,OAAA,EAAA,CAAAF,EAAA,CAAA,CAAA,CAAA,EAAAG,EAAAD,GAAAA,EAAA,KAAA,EAAAA,EAAA,KAAA,EAAA,QAAA,QAAAA,EAAA,KAAA,EAAA,KAAAD,EAAA,CAAA,EAAAE,GAAAJ,EAAAA,EAAA,MAAAF,EAAAC,CAAA,GAAA,KAAA,CAAA,CAAA,CAAA,8uBAoGa,UAAA,iuBAAe,kKApG5B,IAAAM,ygCAAA,IAAAA","x_google_ignoreList":[0]}
|
package/dist/index.cjs
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
var autoBind = require('auto-bind');
|
|
4
4
|
var jspsych = require('jspsych');
|
|
5
5
|
|
|
6
|
-
var version = "2.
|
|
6
|
+
var version = "2.2.0";
|
|
7
7
|
|
|
8
8
|
const info = {
|
|
9
9
|
name: "audio-keyboard-response",
|
|
@@ -64,6 +64,17 @@ const info = {
|
|
|
64
64
|
response_allowed_while_playing: {
|
|
65
65
|
type: jspsych.ParameterType.BOOL,
|
|
66
66
|
default: true
|
|
67
|
+
},
|
|
68
|
+
/** If true, the response is not registered until the participant releases the key. The response
|
|
69
|
+
* time (`rt`) still reflects when the key was pressed, and the additional data field
|
|
70
|
+
* `rt_key_duration` records how long the key was held down. Note that when this is true, the
|
|
71
|
+
* trial cannot end until the key is released: with `response_ends_trial: true` the trial ends at
|
|
72
|
+
* the key release, and if the trial ends for another reason (e.g., `trial_duration`) while the
|
|
73
|
+
* key is still held, no response is recorded for the trial.
|
|
74
|
+
*/
|
|
75
|
+
wait_for_key_release: {
|
|
76
|
+
type: jspsych.ParameterType.BOOL,
|
|
77
|
+
default: false
|
|
67
78
|
}
|
|
68
79
|
},
|
|
69
80
|
data: {
|
|
@@ -81,6 +92,10 @@ const info = {
|
|
|
81
92
|
/** Path to the audio file that played during the trial. */
|
|
82
93
|
stimulus: {
|
|
83
94
|
type: jspsych.ParameterType.STRING
|
|
95
|
+
},
|
|
96
|
+
/** The duration in milliseconds that the response key was held down, measured from key press to key release. Only recorded when `wait_for_key_release` is true; null otherwise or when no response was made. */
|
|
97
|
+
rt_key_duration: {
|
|
98
|
+
type: jspsych.ParameterType.INT
|
|
84
99
|
}
|
|
85
100
|
},
|
|
86
101
|
// prettier-ignore
|
|
@@ -92,7 +107,11 @@ const info = {
|
|
|
92
107
|
class AudioKeyboardResponsePlugin {
|
|
93
108
|
constructor(jsPsych) {
|
|
94
109
|
this.jsPsych = jsPsych;
|
|
95
|
-
this.response = {
|
|
110
|
+
this.response = {
|
|
111
|
+
rt: null,
|
|
112
|
+
key: null,
|
|
113
|
+
rt_key_duration: null
|
|
114
|
+
};
|
|
96
115
|
autoBind(this);
|
|
97
116
|
}
|
|
98
117
|
static {
|
|
@@ -134,13 +153,18 @@ class AudioKeyboardResponsePlugin {
|
|
|
134
153
|
var trial_data = {
|
|
135
154
|
rt: this.response.rt,
|
|
136
155
|
response: this.response.key,
|
|
137
|
-
stimulus: this.params.stimulus
|
|
156
|
+
stimulus: this.params.stimulus,
|
|
157
|
+
rt_key_duration: this.response.rt_key_duration
|
|
138
158
|
};
|
|
139
159
|
this.display.innerHTML = "";
|
|
140
160
|
this.finish(trial_data);
|
|
141
161
|
}
|
|
142
162
|
after_response(info2) {
|
|
143
|
-
this.response =
|
|
163
|
+
this.response = {
|
|
164
|
+
rt: info2.rt,
|
|
165
|
+
key: info2.key,
|
|
166
|
+
rt_key_duration: info2.rt_key_duration ?? null
|
|
167
|
+
};
|
|
144
168
|
if (this.params.response_ends_trial) {
|
|
145
169
|
this.end_trial();
|
|
146
170
|
}
|
|
@@ -154,7 +178,8 @@ class AudioKeyboardResponsePlugin {
|
|
|
154
178
|
persist: false,
|
|
155
179
|
allow_held_key: false,
|
|
156
180
|
audio_context: this.jsPsych.pluginAPI.audioContext(),
|
|
157
|
-
audio_context_start_time: this.startTime
|
|
181
|
+
audio_context_start_time: this.startTime,
|
|
182
|
+
wait_for_key_release: this.params.wait_for_key_release
|
|
158
183
|
});
|
|
159
184
|
} else {
|
|
160
185
|
this.jsPsych.pluginAPI.getKeyboardResponse({
|
|
@@ -162,7 +187,8 @@ class AudioKeyboardResponsePlugin {
|
|
|
162
187
|
valid_responses: this.params.choices,
|
|
163
188
|
rt_method: "performance",
|
|
164
189
|
persist: false,
|
|
165
|
-
allow_held_key: false
|
|
190
|
+
allow_held_key: false,
|
|
191
|
+
wait_for_key_release: this.params.wait_for_key_release
|
|
166
192
|
});
|
|
167
193
|
}
|
|
168
194
|
}
|
|
@@ -201,8 +227,10 @@ class AudioKeyboardResponsePlugin {
|
|
|
201
227
|
const default_data = {
|
|
202
228
|
stimulus: trial.stimulus,
|
|
203
229
|
rt: this.jsPsych.randomization.sampleExGaussian(500, 50, 1 / 150, true),
|
|
204
|
-
response: this.jsPsych.pluginAPI.getValidKey(trial.choices)
|
|
230
|
+
response: this.jsPsych.pluginAPI.getValidKey(trial.choices),
|
|
231
|
+
rt_key_duration: null
|
|
205
232
|
};
|
|
233
|
+
default_data.rt_key_duration = default_data.rt === null || !trial.wait_for_key_release ? null : this.jsPsych.randomization.sampleExGaussian(150, 30, 1 / 100, true);
|
|
206
234
|
const data = this.jsPsych.pluginAPI.mergeSimulationData(default_data, simulation_options);
|
|
207
235
|
this.jsPsych.pluginAPI.ensureSimulationDataConsistency(trial, data);
|
|
208
236
|
return data;
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","sources":["../package.json","../src/index.ts"],"sourcesContent":["{\n \"name\": \"@jspsych/plugin-audio-keyboard-response\",\n \"version\": \"2.1.1\",\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 // 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 // stop the audio file if it is playing\n this.audio.stop();\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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
|
1
|
+
{"version":3,"file":"index.cjs","sources":["../package.json","../src/index.ts"],"sourcesContent":["{\n \"name\": \"@jspsych/plugin-audio-keyboard-response\",\n \"version\": \"2.2.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.3.4\",\n \"@jspsych/test-utils\": \"^1.3.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 /** If true, the response is not registered until the participant releases the key. The response\n * time (`rt`) still reflects when the key was pressed, and the additional data field\n * `rt_key_duration` records how long the key was held down. Note that when this is true, the\n * trial cannot end until the key is released: with `response_ends_trial: true` the trial ends at\n * the key release, and if the trial ends for another reason (e.g., `trial_duration`) while the\n * key is still held, no response is recorded for the trial.\n */\n wait_for_key_release: {\n type: ParameterType.BOOL,\n default: false,\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 /** The duration in milliseconds that the response key was held down, measured from key press to key release. Only recorded when `wait_for_key_release` is true; null otherwise or when no response was made. */\n rt_key_duration: {\n type: ParameterType.INT,\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_key_duration: number } = {\n rt: null,\n key: null,\n rt_key_duration: null,\n };\n private startTime: number;\n private finish: ({}: {\n rt: number;\n response: string;\n stimulus: string;\n rt_key_duration: number;\n }) => 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 // 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 // stop the audio file if it is playing\n this.audio.stop();\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 rt_key_duration: this.response.rt_key_duration,\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; rt_key_duration?: number }) {\n this.response = {\n rt: info.rt,\n key: info.key,\n rt_key_duration: info.rt_key_duration ?? null,\n };\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 wait_for_key_release: this.params.wait_for_key_release,\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 wait_for_key_release: this.params.wait_for_key_release,\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 rt_key_duration: null,\n };\n default_data.rt_key_duration =\n default_data.rt === null || !trial.wait_for_key_release\n ? null\n : this.jsPsych.randomization.sampleExGaussian(150, 30, 1 / 100, true);\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,IAAA,OAAA,GAAW,OAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ECkGA,SAAA,EAAA;AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
package/dist/index.d.ts
CHANGED
|
@@ -60,6 +60,17 @@ declare const info: {
|
|
|
60
60
|
readonly type: ParameterType.BOOL;
|
|
61
61
|
readonly default: true;
|
|
62
62
|
};
|
|
63
|
+
/** If true, the response is not registered until the participant releases the key. The response
|
|
64
|
+
* time (`rt`) still reflects when the key was pressed, and the additional data field
|
|
65
|
+
* `rt_key_duration` records how long the key was held down. Note that when this is true, the
|
|
66
|
+
* trial cannot end until the key is released: with `response_ends_trial: true` the trial ends at
|
|
67
|
+
* the key release, and if the trial ends for another reason (e.g., `trial_duration`) while the
|
|
68
|
+
* key is still held, no response is recorded for the trial.
|
|
69
|
+
*/
|
|
70
|
+
readonly wait_for_key_release: {
|
|
71
|
+
readonly type: ParameterType.BOOL;
|
|
72
|
+
readonly default: false;
|
|
73
|
+
};
|
|
63
74
|
};
|
|
64
75
|
readonly data: {
|
|
65
76
|
/** Indicates which key the participant pressed. If no key was pressed before the trial ended, then the value will be `null`. */
|
|
@@ -77,6 +88,10 @@ declare const info: {
|
|
|
77
88
|
readonly stimulus: {
|
|
78
89
|
readonly type: ParameterType.STRING;
|
|
79
90
|
};
|
|
91
|
+
/** The duration in milliseconds that the response key was held down, measured from key press to key release. Only recorded when `wait_for_key_release` is true; null otherwise or when no response was made. */
|
|
92
|
+
readonly rt_key_duration: {
|
|
93
|
+
readonly type: ParameterType.INT;
|
|
94
|
+
};
|
|
80
95
|
};
|
|
81
96
|
readonly citations: "__CITATIONS__";
|
|
82
97
|
};
|
|
@@ -160,6 +175,17 @@ declare class AudioKeyboardResponsePlugin implements JsPsychPlugin<Info> {
|
|
|
160
175
|
readonly type: ParameterType.BOOL;
|
|
161
176
|
readonly default: true;
|
|
162
177
|
};
|
|
178
|
+
/** If true, the response is not registered until the participant releases the key. The response
|
|
179
|
+
* time (`rt`) still reflects when the key was pressed, and the additional data field
|
|
180
|
+
* `rt_key_duration` records how long the key was held down. Note that when this is true, the
|
|
181
|
+
* trial cannot end until the key is released: with `response_ends_trial: true` the trial ends at
|
|
182
|
+
* the key release, and if the trial ends for another reason (e.g., `trial_duration`) while the
|
|
183
|
+
* key is still held, no response is recorded for the trial.
|
|
184
|
+
*/
|
|
185
|
+
readonly wait_for_key_release: {
|
|
186
|
+
readonly type: ParameterType.BOOL;
|
|
187
|
+
readonly default: false;
|
|
188
|
+
};
|
|
163
189
|
};
|
|
164
190
|
readonly data: {
|
|
165
191
|
/** Indicates which key the participant pressed. If no key was pressed before the trial ended, then the value will be `null`. */
|
|
@@ -177,6 +203,10 @@ declare class AudioKeyboardResponsePlugin implements JsPsychPlugin<Info> {
|
|
|
177
203
|
readonly stimulus: {
|
|
178
204
|
readonly type: ParameterType.STRING;
|
|
179
205
|
};
|
|
206
|
+
/** The duration in milliseconds that the response key was held down, measured from key press to key release. Only recorded when `wait_for_key_release` is true; null otherwise or when no response was made. */
|
|
207
|
+
readonly rt_key_duration: {
|
|
208
|
+
readonly type: ParameterType.INT;
|
|
209
|
+
};
|
|
180
210
|
};
|
|
181
211
|
readonly citations: "__CITATIONS__";
|
|
182
212
|
};
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import autoBind from 'auto-bind';
|
|
2
2
|
import { ParameterType } from 'jspsych';
|
|
3
3
|
|
|
4
|
-
var version = "2.
|
|
4
|
+
var version = "2.2.0";
|
|
5
5
|
|
|
6
6
|
const info = {
|
|
7
7
|
name: "audio-keyboard-response",
|
|
@@ -62,6 +62,17 @@ const info = {
|
|
|
62
62
|
response_allowed_while_playing: {
|
|
63
63
|
type: ParameterType.BOOL,
|
|
64
64
|
default: true
|
|
65
|
+
},
|
|
66
|
+
/** If true, the response is not registered until the participant releases the key. The response
|
|
67
|
+
* time (`rt`) still reflects when the key was pressed, and the additional data field
|
|
68
|
+
* `rt_key_duration` records how long the key was held down. Note that when this is true, the
|
|
69
|
+
* trial cannot end until the key is released: with `response_ends_trial: true` the trial ends at
|
|
70
|
+
* the key release, and if the trial ends for another reason (e.g., `trial_duration`) while the
|
|
71
|
+
* key is still held, no response is recorded for the trial.
|
|
72
|
+
*/
|
|
73
|
+
wait_for_key_release: {
|
|
74
|
+
type: ParameterType.BOOL,
|
|
75
|
+
default: false
|
|
65
76
|
}
|
|
66
77
|
},
|
|
67
78
|
data: {
|
|
@@ -79,6 +90,10 @@ const info = {
|
|
|
79
90
|
/** Path to the audio file that played during the trial. */
|
|
80
91
|
stimulus: {
|
|
81
92
|
type: ParameterType.STRING
|
|
93
|
+
},
|
|
94
|
+
/** The duration in milliseconds that the response key was held down, measured from key press to key release. Only recorded when `wait_for_key_release` is true; null otherwise or when no response was made. */
|
|
95
|
+
rt_key_duration: {
|
|
96
|
+
type: ParameterType.INT
|
|
82
97
|
}
|
|
83
98
|
},
|
|
84
99
|
// prettier-ignore
|
|
@@ -90,7 +105,11 @@ const info = {
|
|
|
90
105
|
class AudioKeyboardResponsePlugin {
|
|
91
106
|
constructor(jsPsych) {
|
|
92
107
|
this.jsPsych = jsPsych;
|
|
93
|
-
this.response = {
|
|
108
|
+
this.response = {
|
|
109
|
+
rt: null,
|
|
110
|
+
key: null,
|
|
111
|
+
rt_key_duration: null
|
|
112
|
+
};
|
|
94
113
|
autoBind(this);
|
|
95
114
|
}
|
|
96
115
|
static {
|
|
@@ -132,13 +151,18 @@ class AudioKeyboardResponsePlugin {
|
|
|
132
151
|
var trial_data = {
|
|
133
152
|
rt: this.response.rt,
|
|
134
153
|
response: this.response.key,
|
|
135
|
-
stimulus: this.params.stimulus
|
|
154
|
+
stimulus: this.params.stimulus,
|
|
155
|
+
rt_key_duration: this.response.rt_key_duration
|
|
136
156
|
};
|
|
137
157
|
this.display.innerHTML = "";
|
|
138
158
|
this.finish(trial_data);
|
|
139
159
|
}
|
|
140
160
|
after_response(info2) {
|
|
141
|
-
this.response =
|
|
161
|
+
this.response = {
|
|
162
|
+
rt: info2.rt,
|
|
163
|
+
key: info2.key,
|
|
164
|
+
rt_key_duration: info2.rt_key_duration ?? null
|
|
165
|
+
};
|
|
142
166
|
if (this.params.response_ends_trial) {
|
|
143
167
|
this.end_trial();
|
|
144
168
|
}
|
|
@@ -152,7 +176,8 @@ class AudioKeyboardResponsePlugin {
|
|
|
152
176
|
persist: false,
|
|
153
177
|
allow_held_key: false,
|
|
154
178
|
audio_context: this.jsPsych.pluginAPI.audioContext(),
|
|
155
|
-
audio_context_start_time: this.startTime
|
|
179
|
+
audio_context_start_time: this.startTime,
|
|
180
|
+
wait_for_key_release: this.params.wait_for_key_release
|
|
156
181
|
});
|
|
157
182
|
} else {
|
|
158
183
|
this.jsPsych.pluginAPI.getKeyboardResponse({
|
|
@@ -160,7 +185,8 @@ class AudioKeyboardResponsePlugin {
|
|
|
160
185
|
valid_responses: this.params.choices,
|
|
161
186
|
rt_method: "performance",
|
|
162
187
|
persist: false,
|
|
163
|
-
allow_held_key: false
|
|
188
|
+
allow_held_key: false,
|
|
189
|
+
wait_for_key_release: this.params.wait_for_key_release
|
|
164
190
|
});
|
|
165
191
|
}
|
|
166
192
|
}
|
|
@@ -199,8 +225,10 @@ class AudioKeyboardResponsePlugin {
|
|
|
199
225
|
const default_data = {
|
|
200
226
|
stimulus: trial.stimulus,
|
|
201
227
|
rt: this.jsPsych.randomization.sampleExGaussian(500, 50, 1 / 150, true),
|
|
202
|
-
response: this.jsPsych.pluginAPI.getValidKey(trial.choices)
|
|
228
|
+
response: this.jsPsych.pluginAPI.getValidKey(trial.choices),
|
|
229
|
+
rt_key_duration: null
|
|
203
230
|
};
|
|
231
|
+
default_data.rt_key_duration = default_data.rt === null || !trial.wait_for_key_release ? null : this.jsPsych.randomization.sampleExGaussian(150, 30, 1 / 100, true);
|
|
204
232
|
const data = this.jsPsych.pluginAPI.mergeSimulationData(default_data, simulation_options);
|
|
205
233
|
this.jsPsych.pluginAPI.ensureSimulationDataConsistency(trial, data);
|
|
206
234
|
return data;
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../package.json","../src/index.ts"],"sourcesContent":["{\n \"name\": \"@jspsych/plugin-audio-keyboard-response\",\n \"version\": \"2.1.1\",\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 // 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 // stop the audio file if it is playing\n this.audio.stop();\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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../package.json","../src/index.ts"],"sourcesContent":["{\n \"name\": \"@jspsych/plugin-audio-keyboard-response\",\n \"version\": \"2.2.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.3.4\",\n \"@jspsych/test-utils\": \"^1.3.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 /** If true, the response is not registered until the participant releases the key. The response\n * time (`rt`) still reflects when the key was pressed, and the additional data field\n * `rt_key_duration` records how long the key was held down. Note that when this is true, the\n * trial cannot end until the key is released: with `response_ends_trial: true` the trial ends at\n * the key release, and if the trial ends for another reason (e.g., `trial_duration`) while the\n * key is still held, no response is recorded for the trial.\n */\n wait_for_key_release: {\n type: ParameterType.BOOL,\n default: false,\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 /** The duration in milliseconds that the response key was held down, measured from key press to key release. Only recorded when `wait_for_key_release` is true; null otherwise or when no response was made. */\n rt_key_duration: {\n type: ParameterType.INT,\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_key_duration: number } = {\n rt: null,\n key: null,\n rt_key_duration: null,\n };\n private startTime: number;\n private finish: ({}: {\n rt: number;\n response: string;\n stimulus: string;\n rt_key_duration: number;\n }) => 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 // 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 // stop the audio file if it is playing\n this.audio.stop();\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 rt_key_duration: this.response.rt_key_duration,\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; rt_key_duration?: number }) {\n this.response = {\n rt: info.rt,\n key: info.key,\n rt_key_duration: info.rt_key_duration ?? null,\n };\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 wait_for_key_release: this.params.wait_for_key_release,\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 wait_for_key_release: this.params.wait_for_key_release,\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 rt_key_duration: null,\n };\n default_data.rt_key_duration =\n default_data.rt === null || !trial.wait_for_key_release\n ? null\n : this.jsPsych.randomization.sampleExGaussian(150, 30, 1 / 100, true);\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,IAAA,OAAA,GAAW,OAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ECkGA,SAAA,EAAA;AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jspsych/plugin-audio-keyboard-response",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.2.0",
|
|
4
4
|
"description": "jsPsych plugin for playing an audio file and getting a keyboard response",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.cjs",
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"jspsych": ">=7.1.0"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
|
-
"@jspsych/config": "^3.
|
|
41
|
-
"@jspsych/test-utils": "^1.
|
|
40
|
+
"@jspsych/config": "^3.3.4",
|
|
41
|
+
"@jspsych/test-utils": "^1.3.0"
|
|
42
42
|
}
|
|
43
43
|
}
|
package/src/index.spec.ts
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
jest.mock("../../jspsych/src/modules/plugin-api/AudioPlayer");
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
flushPromises,
|
|
5
|
+
keyDown,
|
|
6
|
+
keyUp,
|
|
7
|
+
pressKey,
|
|
8
|
+
simulateTimeline,
|
|
9
|
+
startTimeline,
|
|
10
|
+
} from "@jspsych/test-utils";
|
|
4
11
|
import { initJsPsych } from "jspsych";
|
|
5
12
|
|
|
6
13
|
//@ts-expect-error mock
|
|
@@ -126,6 +133,167 @@ describe("audio-keyboard-response", () => {
|
|
|
126
133
|
});
|
|
127
134
|
});
|
|
128
135
|
|
|
136
|
+
describe("audio-keyboard-response wait_for_key_release", () => {
|
|
137
|
+
it("does not end the trial until the key is released and records the hold duration", async () => {
|
|
138
|
+
const jsPsych = initJsPsych({ use_webaudio: false });
|
|
139
|
+
|
|
140
|
+
const { getData, expectRunning, expectFinished } = await startTimeline(
|
|
141
|
+
[
|
|
142
|
+
{
|
|
143
|
+
type: audioKeyboardResponse,
|
|
144
|
+
stimulus: "foo.mp3",
|
|
145
|
+
choices: ["a"],
|
|
146
|
+
wait_for_key_release: true,
|
|
147
|
+
},
|
|
148
|
+
],
|
|
149
|
+
jsPsych
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
jest.advanceTimersByTime(100);
|
|
153
|
+
await keyDown("a");
|
|
154
|
+
|
|
155
|
+
await expectRunning();
|
|
156
|
+
|
|
157
|
+
jest.advanceTimersByTime(250);
|
|
158
|
+
await keyUp("a");
|
|
159
|
+
|
|
160
|
+
await expectFinished();
|
|
161
|
+
|
|
162
|
+
expect(getData().values()[0].rt).toBe(100);
|
|
163
|
+
expect(getData().values()[0].rt_key_duration).toBe(250);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it("records the hold duration when the trial ends by duration", async () => {
|
|
167
|
+
const jsPsych = initJsPsych({ use_webaudio: false });
|
|
168
|
+
|
|
169
|
+
const { getData, expectFinished } = await startTimeline(
|
|
170
|
+
[
|
|
171
|
+
{
|
|
172
|
+
type: audioKeyboardResponse,
|
|
173
|
+
stimulus: "foo.mp3",
|
|
174
|
+
choices: ["a"],
|
|
175
|
+
wait_for_key_release: true,
|
|
176
|
+
response_ends_trial: false,
|
|
177
|
+
trial_duration: 1000,
|
|
178
|
+
},
|
|
179
|
+
],
|
|
180
|
+
jsPsych
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
await keyDown("a");
|
|
184
|
+
jest.advanceTimersByTime(250);
|
|
185
|
+
await keyUp("a");
|
|
186
|
+
|
|
187
|
+
jest.advanceTimersByTime(750);
|
|
188
|
+
await expectFinished();
|
|
189
|
+
|
|
190
|
+
expect(getData().values()[0].rt_key_duration).toBe(250);
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it("records a null response when the trial ends while the key is still held", async () => {
|
|
194
|
+
const jsPsych = initJsPsych({ use_webaudio: false });
|
|
195
|
+
|
|
196
|
+
const { getData, expectFinished } = await startTimeline(
|
|
197
|
+
[
|
|
198
|
+
{
|
|
199
|
+
type: audioKeyboardResponse,
|
|
200
|
+
stimulus: "foo.mp3",
|
|
201
|
+
choices: ["a"],
|
|
202
|
+
wait_for_key_release: true,
|
|
203
|
+
response_ends_trial: false,
|
|
204
|
+
trial_duration: 1000,
|
|
205
|
+
},
|
|
206
|
+
],
|
|
207
|
+
jsPsych
|
|
208
|
+
);
|
|
209
|
+
|
|
210
|
+
await keyDown("a");
|
|
211
|
+
jest.advanceTimersByTime(1000);
|
|
212
|
+
await expectFinished();
|
|
213
|
+
|
|
214
|
+
expect(getData().values()[0].response).toBe(null);
|
|
215
|
+
expect(getData().values()[0].rt).toBe(null);
|
|
216
|
+
expect(getData().values()[0].rt_key_duration).toBe(null);
|
|
217
|
+
|
|
218
|
+
await keyUp("a");
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
it("rt_key_duration is null on default trials (wait_for_key_release false)", async () => {
|
|
222
|
+
const jsPsych = initJsPsych({ use_webaudio: false });
|
|
223
|
+
|
|
224
|
+
const { getData, expectFinished } = await startTimeline(
|
|
225
|
+
[
|
|
226
|
+
{
|
|
227
|
+
type: audioKeyboardResponse,
|
|
228
|
+
stimulus: "foo.mp3",
|
|
229
|
+
choices: ["a"],
|
|
230
|
+
},
|
|
231
|
+
],
|
|
232
|
+
jsPsych
|
|
233
|
+
);
|
|
234
|
+
|
|
235
|
+
await keyDown("a");
|
|
236
|
+
await expectFinished();
|
|
237
|
+
|
|
238
|
+
expect(getData().values()[0].rt_key_duration).toBe(null);
|
|
239
|
+
|
|
240
|
+
await keyUp("a");
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
it("a key held past trial_duration does not contaminate the next trial", async () => {
|
|
244
|
+
const jsPsych = initJsPsych({ use_webaudio: false });
|
|
245
|
+
|
|
246
|
+
const { getData, expectRunning, expectFinished } = await startTimeline(
|
|
247
|
+
[
|
|
248
|
+
{
|
|
249
|
+
type: audioKeyboardResponse,
|
|
250
|
+
stimulus: "foo.mp3",
|
|
251
|
+
choices: ["a"],
|
|
252
|
+
wait_for_key_release: true,
|
|
253
|
+
trial_duration: 1000,
|
|
254
|
+
},
|
|
255
|
+
{
|
|
256
|
+
type: audioKeyboardResponse,
|
|
257
|
+
stimulus: "foo.mp3",
|
|
258
|
+
choices: ["a"],
|
|
259
|
+
wait_for_key_release: true,
|
|
260
|
+
},
|
|
261
|
+
],
|
|
262
|
+
jsPsych
|
|
263
|
+
);
|
|
264
|
+
|
|
265
|
+
// trial 1: press and hold the key, then let the trial time out
|
|
266
|
+
await keyDown("a");
|
|
267
|
+
jest.advanceTimersByTime(1000);
|
|
268
|
+
await flushPromises();
|
|
269
|
+
|
|
270
|
+
// trial 1 ended by duration with no response because the key was never released
|
|
271
|
+
expect(getData().values()[0].response).toBe(null);
|
|
272
|
+
expect(getData().values()[0].rt).toBe(null);
|
|
273
|
+
expect(getData().values()[0].rt_key_duration).toBe(null);
|
|
274
|
+
|
|
275
|
+
// trial 2 is now running with the key still held; a key-repeat must not register
|
|
276
|
+
await keyDown("a");
|
|
277
|
+
await expectRunning();
|
|
278
|
+
|
|
279
|
+
// releasing the held-over key must not register a response in trial 2
|
|
280
|
+
await keyUp("a");
|
|
281
|
+
await expectRunning();
|
|
282
|
+
|
|
283
|
+
// a fresh press/release cycle in trial 2 works normally
|
|
284
|
+
jest.advanceTimersByTime(200);
|
|
285
|
+
await keyDown("a");
|
|
286
|
+
jest.advanceTimersByTime(125);
|
|
287
|
+
await keyUp("a");
|
|
288
|
+
|
|
289
|
+
await expectFinished();
|
|
290
|
+
|
|
291
|
+
expect(getData().values()[1].response).toBe("a");
|
|
292
|
+
expect(getData().values()[1].rt).toBe(200);
|
|
293
|
+
expect(getData().values()[1].rt_key_duration).toBe(125);
|
|
294
|
+
});
|
|
295
|
+
});
|
|
296
|
+
|
|
129
297
|
describe("audio-keyboard-response simulation", () => {
|
|
130
298
|
test("data mode works", async () => {
|
|
131
299
|
const timeline = [
|
package/src/index.ts
CHANGED
|
@@ -64,6 +64,17 @@ const info = <const>{
|
|
|
64
64
|
type: ParameterType.BOOL,
|
|
65
65
|
default: true,
|
|
66
66
|
},
|
|
67
|
+
/** If true, the response is not registered until the participant releases the key. The response
|
|
68
|
+
* time (`rt`) still reflects when the key was pressed, and the additional data field
|
|
69
|
+
* `rt_key_duration` records how long the key was held down. Note that when this is true, the
|
|
70
|
+
* trial cannot end until the key is released: with `response_ends_trial: true` the trial ends at
|
|
71
|
+
* the key release, and if the trial ends for another reason (e.g., `trial_duration`) while the
|
|
72
|
+
* key is still held, no response is recorded for the trial.
|
|
73
|
+
*/
|
|
74
|
+
wait_for_key_release: {
|
|
75
|
+
type: ParameterType.BOOL,
|
|
76
|
+
default: false,
|
|
77
|
+
},
|
|
67
78
|
},
|
|
68
79
|
data: {
|
|
69
80
|
/** Indicates which key the participant pressed. If no key was pressed before the trial ended, then the value will be `null`. */
|
|
@@ -81,6 +92,10 @@ const info = <const>{
|
|
|
81
92
|
stimulus: {
|
|
82
93
|
type: ParameterType.STRING,
|
|
83
94
|
},
|
|
95
|
+
/** The duration in milliseconds that the response key was held down, measured from key press to key release. Only recorded when `wait_for_key_release` is true; null otherwise or when no response was made. */
|
|
96
|
+
rt_key_duration: {
|
|
97
|
+
type: ParameterType.INT,
|
|
98
|
+
},
|
|
84
99
|
},
|
|
85
100
|
// prettier-ignore
|
|
86
101
|
citations: '__CITATIONS__',
|
|
@@ -110,9 +125,18 @@ class AudioKeyboardResponsePlugin implements JsPsychPlugin<Info> {
|
|
|
110
125
|
private audio: AudioPlayerInterface;
|
|
111
126
|
private params: TrialType<Info>;
|
|
112
127
|
private display: HTMLElement;
|
|
113
|
-
private response: { rt: number; key: string } = {
|
|
128
|
+
private response: { rt: number; key: string; rt_key_duration: number } = {
|
|
129
|
+
rt: null,
|
|
130
|
+
key: null,
|
|
131
|
+
rt_key_duration: null,
|
|
132
|
+
};
|
|
114
133
|
private startTime: number;
|
|
115
|
-
private finish: ({}: {
|
|
134
|
+
private finish: ({}: {
|
|
135
|
+
rt: number;
|
|
136
|
+
response: string;
|
|
137
|
+
stimulus: string;
|
|
138
|
+
rt_key_duration: number;
|
|
139
|
+
}) => void;
|
|
116
140
|
|
|
117
141
|
constructor(private jsPsych: JsPsych) {
|
|
118
142
|
autoBind(this);
|
|
@@ -181,6 +205,7 @@ class AudioKeyboardResponsePlugin implements JsPsychPlugin<Info> {
|
|
|
181
205
|
rt: this.response.rt,
|
|
182
206
|
response: this.response.key,
|
|
183
207
|
stimulus: this.params.stimulus,
|
|
208
|
+
rt_key_duration: this.response.rt_key_duration,
|
|
184
209
|
};
|
|
185
210
|
|
|
186
211
|
// clear the display
|
|
@@ -190,8 +215,12 @@ class AudioKeyboardResponsePlugin implements JsPsychPlugin<Info> {
|
|
|
190
215
|
this.finish(trial_data);
|
|
191
216
|
}
|
|
192
217
|
|
|
193
|
-
private after_response(info: { key: string; rt: number }) {
|
|
194
|
-
this.response =
|
|
218
|
+
private after_response(info: { key: string; rt: number; rt_key_duration?: number }) {
|
|
219
|
+
this.response = {
|
|
220
|
+
rt: info.rt,
|
|
221
|
+
key: info.key,
|
|
222
|
+
rt_key_duration: info.rt_key_duration ?? null,
|
|
223
|
+
};
|
|
195
224
|
if (this.params.response_ends_trial) {
|
|
196
225
|
this.end_trial();
|
|
197
226
|
}
|
|
@@ -208,6 +237,7 @@ class AudioKeyboardResponsePlugin implements JsPsychPlugin<Info> {
|
|
|
208
237
|
allow_held_key: false,
|
|
209
238
|
audio_context: this.jsPsych.pluginAPI.audioContext(),
|
|
210
239
|
audio_context_start_time: this.startTime,
|
|
240
|
+
wait_for_key_release: this.params.wait_for_key_release,
|
|
211
241
|
});
|
|
212
242
|
} else {
|
|
213
243
|
this.jsPsych.pluginAPI.getKeyboardResponse({
|
|
@@ -216,6 +246,7 @@ class AudioKeyboardResponsePlugin implements JsPsychPlugin<Info> {
|
|
|
216
246
|
rt_method: "performance",
|
|
217
247
|
persist: false,
|
|
218
248
|
allow_held_key: false,
|
|
249
|
+
wait_for_key_release: this.params.wait_for_key_release,
|
|
219
250
|
});
|
|
220
251
|
}
|
|
221
252
|
}
|
|
@@ -273,7 +304,12 @@ class AudioKeyboardResponsePlugin implements JsPsychPlugin<Info> {
|
|
|
273
304
|
stimulus: trial.stimulus,
|
|
274
305
|
rt: this.jsPsych.randomization.sampleExGaussian(500, 50, 1 / 150, true),
|
|
275
306
|
response: this.jsPsych.pluginAPI.getValidKey(trial.choices),
|
|
307
|
+
rt_key_duration: null,
|
|
276
308
|
};
|
|
309
|
+
default_data.rt_key_duration =
|
|
310
|
+
default_data.rt === null || !trial.wait_for_key_release
|
|
311
|
+
? null
|
|
312
|
+
: this.jsPsych.randomization.sampleExGaussian(150, 30, 1 / 100, true);
|
|
277
313
|
|
|
278
314
|
const data = this.jsPsych.pluginAPI.mergeSimulationData(default_data, simulation_options);
|
|
279
315
|
|