@jspsych/plugin-audio-button-response 2.0.1 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.browser.js +99 -92
- 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 +58 -45
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +10 -0
- package/dist/index.js +58 -45
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/index.ts +6 -0
package/dist/index.browser.js
CHANGED
|
@@ -49,129 +49,174 @@ var jsPsychAudioButtonResponse = (function (jspsych) {
|
|
|
49
49
|
|
|
50
50
|
var autoBind$1 = /*@__PURE__*/getDefaultExportFromCjs(autoBind);
|
|
51
51
|
|
|
52
|
-
var
|
|
53
|
-
name: "@jspsych/plugin-audio-button-response",
|
|
54
|
-
version: "2.0.1",
|
|
55
|
-
description: "jsPsych plugin for playing an audio file and getting a button response",
|
|
56
|
-
type: "module",
|
|
57
|
-
main: "dist/index.cjs",
|
|
58
|
-
exports: {
|
|
59
|
-
import: "./dist/index.js",
|
|
60
|
-
require: "./dist/index.cjs"
|
|
61
|
-
},
|
|
62
|
-
typings: "dist/index.d.ts",
|
|
63
|
-
unpkg: "dist/index.browser.min.js",
|
|
64
|
-
files: [
|
|
65
|
-
"src",
|
|
66
|
-
"dist"
|
|
67
|
-
],
|
|
68
|
-
source: "src/index.ts",
|
|
69
|
-
scripts: {
|
|
70
|
-
test: "jest",
|
|
71
|
-
"test:watch": "npm test -- --watch",
|
|
72
|
-
tsc: "tsc",
|
|
73
|
-
build: "rollup --config",
|
|
74
|
-
"build:watch": "npm run build -- --watch"
|
|
75
|
-
},
|
|
76
|
-
repository: {
|
|
77
|
-
type: "git",
|
|
78
|
-
url: "git+https://github.com/jspsych/jsPsych.git",
|
|
79
|
-
directory: "packages/plugin-audio-button-response"
|
|
80
|
-
},
|
|
81
|
-
author: "Kristin Diep",
|
|
82
|
-
license: "MIT",
|
|
83
|
-
bugs: {
|
|
84
|
-
url: "https://github.com/jspsych/jsPsych/issues"
|
|
85
|
-
},
|
|
86
|
-
homepage: "https://www.jspsych.org/latest/plugins/audio-button-response",
|
|
87
|
-
peerDependencies: {
|
|
88
|
-
jspsych: ">=7.1.0"
|
|
89
|
-
},
|
|
90
|
-
devDependencies: {
|
|
91
|
-
"@jspsych/config": "^3.0.0",
|
|
92
|
-
"@jspsych/test-utils": "^1.2.0"
|
|
93
|
-
}
|
|
94
|
-
};
|
|
52
|
+
var version = "2.1.0";
|
|
95
53
|
|
|
96
54
|
const info = {
|
|
97
55
|
name: "audio-button-response",
|
|
98
|
-
version
|
|
56
|
+
version,
|
|
99
57
|
parameters: {
|
|
58
|
+
/** Path to audio file to be played. */
|
|
100
59
|
stimulus: {
|
|
101
60
|
type: jspsych.ParameterType.AUDIO,
|
|
102
61
|
default: void 0
|
|
103
62
|
},
|
|
63
|
+
/** Labels for the buttons. Each different string in the array will generate a different button. */
|
|
104
64
|
choices: {
|
|
105
65
|
type: jspsych.ParameterType.STRING,
|
|
106
66
|
default: void 0,
|
|
107
67
|
array: true
|
|
108
68
|
},
|
|
69
|
+
/**
|
|
70
|
+
* A function that generates the HTML for each button in the `choices` array. The function gets the string
|
|
71
|
+
* and index of the item in the `choices` array and should return valid HTML. If you want to use different
|
|
72
|
+
* markup for each button, you can do that by using a conditional on either parameter. The default parameter
|
|
73
|
+
* returns a button element with the text label of the choice.
|
|
74
|
+
*/
|
|
109
75
|
button_html: {
|
|
110
76
|
type: jspsych.ParameterType.FUNCTION,
|
|
111
77
|
default: function(choice, choice_index) {
|
|
112
78
|
return `<button class="jspsych-btn">${choice}</button>`;
|
|
113
79
|
}
|
|
114
80
|
},
|
|
81
|
+
/** This string can contain HTML markup. Any content here will be displayed below the stimulus. The intention
|
|
82
|
+
* is that it can be used to provide a reminder about the action the participant is supposed to take
|
|
83
|
+
* (e.g., which key to press). */
|
|
115
84
|
prompt: {
|
|
116
85
|
type: jspsych.ParameterType.HTML_STRING,
|
|
117
86
|
default: null
|
|
118
87
|
},
|
|
88
|
+
/** How long to wait for the participant to make a response before ending the trial in milliseconds. If the
|
|
89
|
+
* participant fails to make a response before this timer is reached, the participant's response will be
|
|
90
|
+
* recorded as null for the trial and the trial will end. If the value of this parameter is null, the trial
|
|
91
|
+
* will wait for a response indefinitely */
|
|
119
92
|
trial_duration: {
|
|
120
93
|
type: jspsych.ParameterType.INT,
|
|
121
94
|
default: null
|
|
122
95
|
},
|
|
96
|
+
/** Setting to `'grid'` will make the container element have the CSS property `display: grid` and enable the
|
|
97
|
+
* use of `grid_rows` and `grid_columns`. Setting to `'flex'` will make the container element have the CSS
|
|
98
|
+
* property `display: flex`. You can customize how the buttons are laid out by adding inline CSS in the `button_html` parameter.
|
|
99
|
+
*/
|
|
123
100
|
button_layout: {
|
|
124
101
|
type: jspsych.ParameterType.STRING,
|
|
125
102
|
default: "grid"
|
|
126
103
|
},
|
|
104
|
+
/** The number of rows in the button grid. Only applicable when `button_layout` is set to `'grid'`. If null, the
|
|
105
|
+
* number of rows will be determined automatically based on the number of buttons and the number of columns.
|
|
106
|
+
*/
|
|
127
107
|
grid_rows: {
|
|
128
108
|
type: jspsych.ParameterType.INT,
|
|
129
109
|
default: 1
|
|
130
110
|
},
|
|
111
|
+
/** The number of columns in the button grid. Only applicable when `button_layout` is set to `'grid'`.
|
|
112
|
+
* If null, the number of columns will be determined automatically based on the number of buttons and the
|
|
113
|
+
* number of rows.
|
|
114
|
+
*/
|
|
131
115
|
grid_columns: {
|
|
132
116
|
type: jspsych.ParameterType.INT,
|
|
133
117
|
default: null
|
|
134
118
|
},
|
|
119
|
+
/** If true, then the trial will end whenever the participant makes a response (assuming they make their
|
|
120
|
+
* response before the cutoff specified by the `trial_duration` parameter). If false, then the trial will
|
|
121
|
+
* continue until the value for `trial_duration` is reached. You can set this parameter to `false` to force
|
|
122
|
+
* the participant to listen to the stimulus for a fixed amount of time, even if they respond before the time is complete. */
|
|
135
123
|
response_ends_trial: {
|
|
136
124
|
type: jspsych.ParameterType.BOOL,
|
|
137
125
|
default: true
|
|
138
126
|
},
|
|
127
|
+
/** If true, then the trial will end as soon as the audio file finishes playing. */
|
|
139
128
|
trial_ends_after_audio: {
|
|
140
129
|
type: jspsych.ParameterType.BOOL,
|
|
141
130
|
default: false
|
|
142
131
|
},
|
|
132
|
+
/**
|
|
133
|
+
* If true, then responses are allowed while the audio is playing. If false, then the audio must finish
|
|
134
|
+
* playing before the button choices are enabled and a response is accepted. Once the audio has played
|
|
135
|
+
* all the way through, the buttons are enabled and a response is allowed (including while the audio is
|
|
136
|
+
* being re-played via on-screen playback controls).
|
|
137
|
+
*/
|
|
143
138
|
response_allowed_while_playing: {
|
|
144
139
|
type: jspsych.ParameterType.BOOL,
|
|
145
140
|
default: true
|
|
146
141
|
},
|
|
142
|
+
/** How long the button will delay enabling in milliseconds. If `response_allowed_while_playing` is `true`,
|
|
143
|
+
* the timer will start immediately. If it is `false`, the timer will start at the end of the audio. */
|
|
147
144
|
enable_button_after: {
|
|
148
145
|
type: jspsych.ParameterType.INT,
|
|
149
146
|
default: 0
|
|
150
147
|
}
|
|
151
148
|
},
|
|
152
149
|
data: {
|
|
150
|
+
/** The path of the audio file that was played. */
|
|
151
|
+
stimulus: {
|
|
152
|
+
type: jspsych.ParameterType.STRING
|
|
153
|
+
},
|
|
154
|
+
/** The response time in milliseconds for the participant to make a response. The time is measured from
|
|
155
|
+
* when the stimulus first began playing until the participant's response.*/
|
|
153
156
|
rt: {
|
|
154
157
|
type: jspsych.ParameterType.INT
|
|
155
158
|
},
|
|
159
|
+
/** Indicates which button the participant pressed. The first button in the `choices` array is 0, the second is 1, and so on. */
|
|
156
160
|
response: {
|
|
157
161
|
type: jspsych.ParameterType.INT
|
|
158
162
|
}
|
|
163
|
+
},
|
|
164
|
+
// prettier-ignore
|
|
165
|
+
citations: {
|
|
166
|
+
"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 ",
|
|
167
|
+
"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}, } '
|
|
159
168
|
}
|
|
160
169
|
};
|
|
161
170
|
class AudioButtonResponsePlugin {
|
|
162
171
|
constructor(jsPsych) {
|
|
163
172
|
this.jsPsych = jsPsych;
|
|
173
|
+
this.buttonElements = [];
|
|
174
|
+
this.response = { rt: null, button: null };
|
|
175
|
+
this.disable_buttons = () => {
|
|
176
|
+
for (const button of this.buttonElements) {
|
|
177
|
+
button.setAttribute("disabled", "disabled");
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
this.enable_buttons_without_delay = () => {
|
|
181
|
+
for (const button of this.buttonElements) {
|
|
182
|
+
button.removeAttribute("disabled");
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
this.enable_buttons_with_delay = (delay) => {
|
|
186
|
+
this.jsPsych.pluginAPI.setTimeout(this.enable_buttons_without_delay, delay);
|
|
187
|
+
};
|
|
188
|
+
// function to handle responses by the subject
|
|
189
|
+
this.after_response = (choice) => {
|
|
190
|
+
var endTime = performance.now();
|
|
191
|
+
var rt = Math.round(endTime - this.startTime);
|
|
192
|
+
if (this.context !== null) {
|
|
193
|
+
endTime = this.context.currentTime;
|
|
194
|
+
rt = Math.round((endTime - this.startTime) * 1e3);
|
|
195
|
+
}
|
|
196
|
+
this.response.button = parseInt(choice);
|
|
197
|
+
this.response.rt = rt;
|
|
198
|
+
this.disable_buttons();
|
|
199
|
+
if (this.params.response_ends_trial) {
|
|
200
|
+
this.end_trial();
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
// method to end trial when it is time
|
|
204
|
+
this.end_trial = () => {
|
|
205
|
+
this.audio.stop();
|
|
206
|
+
this.audio.removeEventListener("ended", this.end_trial);
|
|
207
|
+
this.audio.removeEventListener("ended", this.enable_buttons);
|
|
208
|
+
var trial_data = {
|
|
209
|
+
rt: this.response.rt,
|
|
210
|
+
stimulus: this.params.stimulus,
|
|
211
|
+
response: this.response.button
|
|
212
|
+
};
|
|
213
|
+
this.trial_complete(trial_data);
|
|
214
|
+
};
|
|
164
215
|
autoBind$1(this);
|
|
165
216
|
}
|
|
166
|
-
static
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
buttonElements = [];
|
|
170
|
-
display;
|
|
171
|
-
response = { rt: null, button: null };
|
|
172
|
-
context;
|
|
173
|
-
startTime;
|
|
174
|
-
trial_complete;
|
|
217
|
+
static {
|
|
218
|
+
this.info = info;
|
|
219
|
+
}
|
|
175
220
|
async trial(display_element, trial, on_load) {
|
|
176
221
|
this.params = trial;
|
|
177
222
|
this.display = display_element;
|
|
@@ -235,19 +280,6 @@ var jsPsychAudioButtonResponse = (function (jspsych) {
|
|
|
235
280
|
this.trial_complete = resolve;
|
|
236
281
|
});
|
|
237
282
|
}
|
|
238
|
-
disable_buttons = () => {
|
|
239
|
-
for (const button of this.buttonElements) {
|
|
240
|
-
button.setAttribute("disabled", "disabled");
|
|
241
|
-
}
|
|
242
|
-
};
|
|
243
|
-
enable_buttons_without_delay = () => {
|
|
244
|
-
for (const button of this.buttonElements) {
|
|
245
|
-
button.removeAttribute("disabled");
|
|
246
|
-
}
|
|
247
|
-
};
|
|
248
|
-
enable_buttons_with_delay = (delay) => {
|
|
249
|
-
this.jsPsych.pluginAPI.setTimeout(this.enable_buttons_without_delay, delay);
|
|
250
|
-
};
|
|
251
283
|
enable_buttons() {
|
|
252
284
|
if (this.params.enable_button_after > 0) {
|
|
253
285
|
this.enable_buttons_with_delay(this.params.enable_button_after);
|
|
@@ -255,31 +287,6 @@ var jsPsychAudioButtonResponse = (function (jspsych) {
|
|
|
255
287
|
this.enable_buttons_without_delay();
|
|
256
288
|
}
|
|
257
289
|
}
|
|
258
|
-
after_response = (choice) => {
|
|
259
|
-
var endTime = performance.now();
|
|
260
|
-
var rt = Math.round(endTime - this.startTime);
|
|
261
|
-
if (this.context !== null) {
|
|
262
|
-
endTime = this.context.currentTime;
|
|
263
|
-
rt = Math.round((endTime - this.startTime) * 1e3);
|
|
264
|
-
}
|
|
265
|
-
this.response.button = parseInt(choice);
|
|
266
|
-
this.response.rt = rt;
|
|
267
|
-
this.disable_buttons();
|
|
268
|
-
if (this.params.response_ends_trial) {
|
|
269
|
-
this.end_trial();
|
|
270
|
-
}
|
|
271
|
-
};
|
|
272
|
-
end_trial = () => {
|
|
273
|
-
this.audio.stop();
|
|
274
|
-
this.audio.removeEventListener("ended", this.end_trial);
|
|
275
|
-
this.audio.removeEventListener("ended", this.enable_buttons);
|
|
276
|
-
var trial_data = {
|
|
277
|
-
rt: this.response.rt,
|
|
278
|
-
stimulus: this.params.stimulus,
|
|
279
|
-
response: this.response.button
|
|
280
|
-
};
|
|
281
|
-
this.trial_complete(trial_data);
|
|
282
|
-
};
|
|
283
290
|
async simulate(trial, simulation_mode, simulation_options, load_callback) {
|
|
284
291
|
if (simulation_mode == "data-only") {
|
|
285
292
|
load_callback();
|
|
@@ -330,4 +337,4 @@ var jsPsychAudioButtonResponse = (function (jspsych) {
|
|
|
330
337
|
return AudioButtonResponsePlugin;
|
|
331
338
|
|
|
332
339
|
})(jsPsychModule);
|
|
333
|
-
//# sourceMappingURL=https://unpkg.com/@jspsych/plugin-audio-button-response@2.0
|
|
340
|
+
//# sourceMappingURL=https://unpkg.com/@jspsych/plugin-audio-button-response@2.1.0/dist/index.browser.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.browser.js","sources":["../../../node_modules/auto-bind/index.js","../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","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-button-response\",\n version: version,\n parameters: {\n /** Path to audio file to be played. */\n stimulus: {\n type: ParameterType.AUDIO,\n default: undefined,\n },\n /** Labels for the buttons. Each different string in the array will generate a different button. */\n choices: {\n type: ParameterType.STRING,\n default: undefined,\n array: true,\n },\n /**\n * A function that generates the HTML for each button in the `choices` array. The function gets the string\n * and index of the item in the `choices` array and should return valid HTML. If you want to use different\n * markup for each button, you can do that by using a conditional on either parameter. The default parameter\n * returns a button element with the text label of the choice.\n */\n button_html: {\n type: ParameterType.FUNCTION,\n default: function (choice: string, choice_index: number) {\n return `<button class=\"jspsych-btn\">${choice}</button>`;\n },\n },\n /** This string can contain HTML markup. Any content here will be displayed below the stimulus. The intention\n * is that it can be used to provide a reminder about the action the participant is supposed to take\n * (e.g., which key to press). */\n prompt: {\n type: ParameterType.HTML_STRING,\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, the trial\n * will wait for a response indefinitely */\n trial_duration: {\n type: ParameterType.INT,\n default: null,\n },\n /** Setting to `'grid'` will make the container element have the CSS property `display: grid` and enable the\n * use of `grid_rows` and `grid_columns`. Setting to `'flex'` will make the container element have the CSS\n * property `display: flex`. You can customize how the buttons are laid out by adding inline CSS in the `button_html` parameter.\n */\n button_layout: {\n type: ParameterType.STRING,\n default: \"grid\",\n },\n /** The number of rows in the button grid. Only applicable when `button_layout` is set to `'grid'`. If null, the\n * number of rows will be determined automatically based on the number of buttons and the number of columns.\n */\n grid_rows: {\n type: ParameterType.INT,\n default: 1,\n },\n /** The number of columns in the button grid. Only applicable when `button_layout` is set to `'grid'`.\n * If null, the number of columns will be determined automatically based on the number of buttons and the\n * number of rows.\n */\n grid_columns: {\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 set this parameter to `false` to force\n * the participant to listen to the stimulus for a fixed amount of time, even if they respond before the time is complete. */\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 default: false,\n },\n /**\n * If true, then responses are allowed while the audio is playing. If false, then the audio must finish\n * playing before the button choices are enabled and a response is accepted. Once the audio has played\n * all the way through, the buttons are enabled and a response is allowed (including while the audio is\n * being re-played via on-screen playback controls).\n */\n response_allowed_while_playing: {\n type: ParameterType.BOOL,\n default: true,\n },\n /** How long the button will delay enabling in milliseconds. If `response_allowed_while_playing` is `true`,\n * the timer will start immediately. If it is `false`, the timer will start at the end of the audio. */\n enable_button_after: {\n type: ParameterType.INT,\n default: 0,\n },\n },\n data: {\n /** The response time in milliseconds for the participant to make a response. The time is measured from\n * when the stimulus first began playing until the participant's response.*/\n rt: {\n type: ParameterType.INT,\n },\n /** Indicates which button the participant pressed. The first button in the `choices` array is 0, the second is 1, and so on. */\n response: {\n type: ParameterType.INT,\n },\n },\n};\n\ntype Info = typeof info;\n\n/**\n * If the browser supports it, audio files are played using the WebAudio API. This allows for reasonably precise \n * timing of the playback. The timing of responses generated is measured against the WebAudio specific clock, \n * improving the measurement of response times. If the browser does not support the WebAudio API, then the audio file is \n * played with HTML5 audio. \n\n * Audio files can be automatically preloaded by jsPsych using the [`preload` plugin](preload.md). However, if \n * you are using timeline variables or another dynamic method to specify the audio stimulus, you will need \n * 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 \n * has failed to respond within a fixed length of time. You can also prevent a button response from being made before the \n * audio has finished playing.\n * \n * @author Kristin Diep\n * @see {@link https://www.jspsych.org/latest/plugins/audio-button-response/ audio-button-response plugin documentation on jspsych.org}\n */\nclass AudioButtonResponsePlugin implements JsPsychPlugin<Info> {\n static info = info;\n private audio: AudioPlayerInterface;\n private params: TrialType<Info>;\n private buttonElements: HTMLElement[] = [];\n private display: HTMLElement;\n private response: { rt: number; button: number } = { rt: null, button: null };\n private context: AudioContext;\n private startTime: number;\n private trial_complete: (trial_data: { rt: number; stimulus: string; response: number }) => void;\n\n constructor(private jsPsych: JsPsych) {\n autoBind(this);\n }\n\n async trial(display_element: HTMLElement, trial: TrialType<Info>, on_load: () => void) {\n this.params = trial;\n this.display = display_element;\n // setup stimulus\n this.context = this.jsPsych.pluginAPI.audioContext();\n\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 // enable buttons after audio ends if necessary\n if (!trial.response_allowed_while_playing && !trial.trial_ends_after_audio) {\n this.audio.addEventListener(\"ended\", this.enable_buttons);\n }\n\n // Display buttons\n const buttonGroupElement = document.createElement(\"div\");\n buttonGroupElement.id = \"jspsych-audio-button-response-btngroup\";\n if (trial.button_layout === \"grid\") {\n buttonGroupElement.classList.add(\"jspsych-btn-group-grid\");\n if (trial.grid_rows === null && trial.grid_columns === null) {\n throw new Error(\n \"You cannot set `grid_rows` to `null` without providing a value for `grid_columns`.\"\n );\n }\n const n_cols =\n trial.grid_columns === null\n ? Math.ceil(trial.choices.length / trial.grid_rows)\n : trial.grid_columns;\n const n_rows =\n trial.grid_rows === null\n ? Math.ceil(trial.choices.length / trial.grid_columns)\n : trial.grid_rows;\n buttonGroupElement.style.gridTemplateColumns = `repeat(${n_cols}, 1fr)`;\n buttonGroupElement.style.gridTemplateRows = `repeat(${n_rows}, 1fr)`;\n } else if (trial.button_layout === \"flex\") {\n buttonGroupElement.classList.add(\"jspsych-btn-group-flex\");\n }\n\n for (const [choiceIndex, choice] of trial.choices.entries()) {\n buttonGroupElement.insertAdjacentHTML(\"beforeend\", trial.button_html(choice, choiceIndex));\n const buttonElement = buttonGroupElement.lastChild as HTMLElement;\n buttonElement.dataset.choice = choiceIndex.toString();\n buttonElement.addEventListener(\"click\", () => {\n this.after_response(choiceIndex);\n });\n this.buttonElements.push(buttonElement);\n }\n\n display_element.appendChild(buttonGroupElement);\n\n // Show prompt if there is one\n if (trial.prompt !== null) {\n display_element.insertAdjacentHTML(\"beforeend\", trial.prompt);\n }\n\n if (trial.response_allowed_while_playing) {\n if (trial.enable_button_after > 0) {\n this.disable_buttons();\n this.enable_buttons();\n }\n } else {\n this.disable_buttons();\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 on_load();\n\n // start time\n this.startTime = performance.now();\n if (this.context !== null) {\n this.startTime = this.context.currentTime;\n }\n\n // start audio\n this.audio.play();\n\n return new Promise((resolve) => {\n // hold the .resolve() function from the Promise that ends the trial\n this.trial_complete = resolve;\n });\n }\n\n private disable_buttons = () => {\n for (const button of this.buttonElements) {\n button.setAttribute(\"disabled\", \"disabled\");\n }\n };\n\n private enable_buttons_without_delay = () => {\n for (const button of this.buttonElements) {\n button.removeAttribute(\"disabled\");\n }\n };\n\n private enable_buttons_with_delay = (delay: number) => {\n this.jsPsych.pluginAPI.setTimeout(this.enable_buttons_without_delay, delay);\n };\n\n private enable_buttons() {\n if (this.params.enable_button_after > 0) {\n this.enable_buttons_with_delay(this.params.enable_button_after);\n } else {\n this.enable_buttons_without_delay();\n }\n }\n\n // function to handle responses by the subject\n private after_response = (choice) => {\n // measure rt\n var endTime = performance.now();\n var rt = Math.round(endTime - this.startTime);\n if (this.context !== null) {\n endTime = this.context.currentTime;\n rt = Math.round((endTime - this.startTime) * 1000);\n }\n this.response.button = parseInt(choice);\n this.response.rt = rt;\n\n // disable all the buttons after a response\n this.disable_buttons();\n\n if (this.params.response_ends_trial) {\n this.end_trial();\n }\n };\n\n // method to end trial when it is time\n private end_trial = () => {\n // stop the audio file if it is playing\n this.audio.stop();\n\n // remove end event listeners if they exist\n this.audio.removeEventListener(\"ended\", this.end_trial);\n this.audio.removeEventListener(\"ended\", this.enable_buttons);\n\n // gather the data to store for the trial\n var trial_data = {\n rt: this.response.rt,\n stimulus: this.params.stimulus,\n response: this.response.button,\n };\n\n // move on to the next trial\n this.trial_complete(trial_data);\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 this.simulate_data_only(trial, simulation_options);\n }\n if (simulation_mode == \"visual\") {\n this.simulate_visual(trial, simulation_options, load_callback);\n }\n }\n\n private create_simulation_data(trial: TrialType<Info>, simulation_options) {\n const default_data = {\n stimulus: trial.stimulus,\n rt:\n this.jsPsych.randomization.sampleExGaussian(500, 50, 1 / 150, true) +\n trial.enable_button_after,\n response: this.jsPsych.randomization.randomInt(0, trial.choices.length - 1),\n };\n\n const data = this.jsPsych.pluginAPI.mergeSimulationData(default_data, simulation_options);\n\n this.jsPsych.pluginAPI.ensureSimulationDataConsistency(trial, data);\n\n return data;\n }\n\n private simulate_data_only(trial: TrialType<Info>, simulation_options) {\n const data = this.create_simulation_data(trial, simulation_options);\n\n this.jsPsych.finishTrial(data);\n }\n\n private simulate_visual(trial: TrialType<Info>, simulation_options, load_callback: () => void) {\n const data = this.create_simulation_data(trial, simulation_options);\n\n const display_element = this.jsPsych.getDisplayElement();\n\n const respond = () => {\n if (data.rt !== null) {\n this.jsPsych.pluginAPI.clickTarget(\n display_element.querySelector(\n `#jspsych-audio-button-response-btngroup [data-choice=\"${data.response}\"]`\n ),\n data.rt\n );\n }\n };\n\n this.trial(display_element, trial, () => {\n load_callback();\n if (!trial.response_allowed_while_playing) {\n this.audio.addEventListener(\"ended\", respond);\n } else {\n respond();\n }\n });\n }\n}\n\nexport default AudioButtonResponsePlugin;\n"],"names":["version","ParameterType","autoBind"],"mappings":"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"}
|
|
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-button-response\",\n \"version\": \"2.1.0\",\n \"description\": \"jsPsych plugin for playing an audio file and getting a button 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-button-response\"\n },\n \"author\": \"Kristin Diep\",\n \"license\": \"MIT\",\n \"bugs\": {\n \"url\": \"https://github.com/jspsych/jsPsych/issues\"\n },\n \"homepage\": \"https://www.jspsych.org/latest/plugins/audio-button-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-button-response\",\n version: version,\n parameters: {\n /** Path to audio file to be played. */\n stimulus: {\n type: ParameterType.AUDIO,\n default: undefined,\n },\n /** Labels for the buttons. Each different string in the array will generate a different button. */\n choices: {\n type: ParameterType.STRING,\n default: undefined,\n array: true,\n },\n /**\n * A function that generates the HTML for each button in the `choices` array. The function gets the string\n * and index of the item in the `choices` array and should return valid HTML. If you want to use different\n * markup for each button, you can do that by using a conditional on either parameter. The default parameter\n * returns a button element with the text label of the choice.\n */\n button_html: {\n type: ParameterType.FUNCTION,\n default: function (choice: string, choice_index: number) {\n return `<button class=\"jspsych-btn\">${choice}</button>`;\n },\n },\n /** This string can contain HTML markup. Any content here will be displayed below the stimulus. The intention\n * is that it can be used to provide a reminder about the action the participant is supposed to take\n * (e.g., which key to press). */\n prompt: {\n type: ParameterType.HTML_STRING,\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, the trial\n * will wait for a response indefinitely */\n trial_duration: {\n type: ParameterType.INT,\n default: null,\n },\n /** Setting to `'grid'` will make the container element have the CSS property `display: grid` and enable the\n * use of `grid_rows` and `grid_columns`. Setting to `'flex'` will make the container element have the CSS\n * property `display: flex`. You can customize how the buttons are laid out by adding inline CSS in the `button_html` parameter.\n */\n button_layout: {\n type: ParameterType.STRING,\n default: \"grid\",\n },\n /** The number of rows in the button grid. Only applicable when `button_layout` is set to `'grid'`. If null, the\n * number of rows will be determined automatically based on the number of buttons and the number of columns.\n */\n grid_rows: {\n type: ParameterType.INT,\n default: 1,\n },\n /** The number of columns in the button grid. Only applicable when `button_layout` is set to `'grid'`.\n * If null, the number of columns will be determined automatically based on the number of buttons and the\n * number of rows.\n */\n grid_columns: {\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 set this parameter to `false` to force\n * the participant to listen to the stimulus for a fixed amount of time, even if they respond before the time is complete. */\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 default: false,\n },\n /**\n * If true, then responses are allowed while the audio is playing. If false, then the audio must finish\n * playing before the button choices are enabled and a response is accepted. Once the audio has played\n * all the way through, the buttons are enabled and a response is allowed (including while the audio is\n * being re-played via on-screen playback controls).\n */\n response_allowed_while_playing: {\n type: ParameterType.BOOL,\n default: true,\n },\n /** How long the button will delay enabling in milliseconds. If `response_allowed_while_playing` is `true`,\n * the timer will start immediately. If it is `false`, the timer will start at the end of the audio. */\n enable_button_after: {\n type: ParameterType.INT,\n default: 0,\n },\n },\n data: {\n /** The path of the audio file that was played. */\n stimulus: {\n type: ParameterType.STRING,\n },\n /** The response time in milliseconds for the participant to make a response. The time is measured from\n * when the stimulus first began playing until the participant's response.*/\n rt: {\n type: ParameterType.INT,\n },\n /** Indicates which button the participant pressed. The first button in the `choices` array is 0, the second is 1, and so on. */\n response: {\n type: ParameterType.INT,\n },\n },\n // prettier-ignore\n citations: '__CITATIONS__',\n};\n\ntype Info = typeof info;\n\n/**\n * If the browser supports it, audio files are played using the WebAudio API. This allows for reasonably precise \n * timing of the playback. The timing of responses generated is measured against the WebAudio specific clock, \n * improving the measurement of response times. If the browser does not support the WebAudio API, then the audio file is \n * played with HTML5 audio. \n\n * Audio files can be automatically preloaded by jsPsych using the [`preload` plugin](preload.md). However, if \n * you are using timeline variables or another dynamic method to specify the audio stimulus, you will need \n * 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 \n * has failed to respond within a fixed length of time. You can also prevent a button response from being made before the \n * audio has finished playing.\n * \n * @author Kristin Diep\n * @see {@link https://www.jspsych.org/latest/plugins/audio-button-response/ audio-button-response plugin documentation on jspsych.org}\n */\nclass AudioButtonResponsePlugin implements JsPsychPlugin<Info> {\n static info = info;\n private audio: AudioPlayerInterface;\n private params: TrialType<Info>;\n private buttonElements: HTMLElement[] = [];\n private display: HTMLElement;\n private response: { rt: number; button: number } = { rt: null, button: null };\n private context: AudioContext;\n private startTime: number;\n private trial_complete: (trial_data: { rt: number; stimulus: string; response: number }) => void;\n\n constructor(private jsPsych: JsPsych) {\n autoBind(this);\n }\n\n async trial(display_element: HTMLElement, trial: TrialType<Info>, on_load: () => void) {\n this.params = trial;\n this.display = display_element;\n // setup stimulus\n this.context = this.jsPsych.pluginAPI.audioContext();\n\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 // enable buttons after audio ends if necessary\n if (!trial.response_allowed_while_playing && !trial.trial_ends_after_audio) {\n this.audio.addEventListener(\"ended\", this.enable_buttons);\n }\n\n // Display buttons\n const buttonGroupElement = document.createElement(\"div\");\n buttonGroupElement.id = \"jspsych-audio-button-response-btngroup\";\n if (trial.button_layout === \"grid\") {\n buttonGroupElement.classList.add(\"jspsych-btn-group-grid\");\n if (trial.grid_rows === null && trial.grid_columns === null) {\n throw new Error(\n \"You cannot set `grid_rows` to `null` without providing a value for `grid_columns`.\"\n );\n }\n const n_cols =\n trial.grid_columns === null\n ? Math.ceil(trial.choices.length / trial.grid_rows)\n : trial.grid_columns;\n const n_rows =\n trial.grid_rows === null\n ? Math.ceil(trial.choices.length / trial.grid_columns)\n : trial.grid_rows;\n buttonGroupElement.style.gridTemplateColumns = `repeat(${n_cols}, 1fr)`;\n buttonGroupElement.style.gridTemplateRows = `repeat(${n_rows}, 1fr)`;\n } else if (trial.button_layout === \"flex\") {\n buttonGroupElement.classList.add(\"jspsych-btn-group-flex\");\n }\n\n for (const [choiceIndex, choice] of trial.choices.entries()) {\n buttonGroupElement.insertAdjacentHTML(\"beforeend\", trial.button_html(choice, choiceIndex));\n const buttonElement = buttonGroupElement.lastChild as HTMLElement;\n buttonElement.dataset.choice = choiceIndex.toString();\n buttonElement.addEventListener(\"click\", () => {\n this.after_response(choiceIndex);\n });\n this.buttonElements.push(buttonElement);\n }\n\n display_element.appendChild(buttonGroupElement);\n\n // Show prompt if there is one\n if (trial.prompt !== null) {\n display_element.insertAdjacentHTML(\"beforeend\", trial.prompt);\n }\n\n if (trial.response_allowed_while_playing) {\n if (trial.enable_button_after > 0) {\n this.disable_buttons();\n this.enable_buttons();\n }\n } else {\n this.disable_buttons();\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 on_load();\n\n // start time\n this.startTime = performance.now();\n if (this.context !== null) {\n this.startTime = this.context.currentTime;\n }\n\n // start audio\n this.audio.play();\n\n return new Promise((resolve) => {\n // hold the .resolve() function from the Promise that ends the trial\n this.trial_complete = resolve;\n });\n }\n\n private disable_buttons = () => {\n for (const button of this.buttonElements) {\n button.setAttribute(\"disabled\", \"disabled\");\n }\n };\n\n private enable_buttons_without_delay = () => {\n for (const button of this.buttonElements) {\n button.removeAttribute(\"disabled\");\n }\n };\n\n private enable_buttons_with_delay = (delay: number) => {\n this.jsPsych.pluginAPI.setTimeout(this.enable_buttons_without_delay, delay);\n };\n\n private enable_buttons() {\n if (this.params.enable_button_after > 0) {\n this.enable_buttons_with_delay(this.params.enable_button_after);\n } else {\n this.enable_buttons_without_delay();\n }\n }\n\n // function to handle responses by the subject\n private after_response = (choice) => {\n // measure rt\n var endTime = performance.now();\n var rt = Math.round(endTime - this.startTime);\n if (this.context !== null) {\n endTime = this.context.currentTime;\n rt = Math.round((endTime - this.startTime) * 1000);\n }\n this.response.button = parseInt(choice);\n this.response.rt = rt;\n\n // disable all the buttons after a response\n this.disable_buttons();\n\n if (this.params.response_ends_trial) {\n this.end_trial();\n }\n };\n\n // method to end trial when it is time\n private end_trial = () => {\n // stop the audio file if it is playing\n this.audio.stop();\n\n // remove end event listeners if they exist\n this.audio.removeEventListener(\"ended\", this.end_trial);\n this.audio.removeEventListener(\"ended\", this.enable_buttons);\n\n // gather the data to store for the trial\n var trial_data = {\n rt: this.response.rt,\n stimulus: this.params.stimulus,\n response: this.response.button,\n };\n\n // move on to the next trial\n this.trial_complete(trial_data);\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 this.simulate_data_only(trial, simulation_options);\n }\n if (simulation_mode == \"visual\") {\n this.simulate_visual(trial, simulation_options, load_callback);\n }\n }\n\n private create_simulation_data(trial: TrialType<Info>, simulation_options) {\n const default_data = {\n stimulus: trial.stimulus,\n rt:\n this.jsPsych.randomization.sampleExGaussian(500, 50, 1 / 150, true) +\n trial.enable_button_after,\n response: this.jsPsych.randomization.randomInt(0, trial.choices.length - 1),\n };\n\n const data = this.jsPsych.pluginAPI.mergeSimulationData(default_data, simulation_options);\n\n this.jsPsych.pluginAPI.ensureSimulationDataConsistency(trial, data);\n\n return data;\n }\n\n private simulate_data_only(trial: TrialType<Info>, simulation_options) {\n const data = this.create_simulation_data(trial, simulation_options);\n\n this.jsPsych.finishTrial(data);\n }\n\n private simulate_visual(trial: TrialType<Info>, simulation_options, load_callback: () => void) {\n const data = this.create_simulation_data(trial, simulation_options);\n\n const display_element = this.jsPsych.getDisplayElement();\n\n const respond = () => {\n if (data.rt !== null) {\n this.jsPsych.pluginAPI.clickTarget(\n display_element.querySelector(\n `#jspsych-audio-button-response-btngroup [data-choice=\"${data.response}\"]`\n ),\n data.rt\n );\n }\n };\n\n this.trial(display_element, trial, () => {\n load_callback();\n if (!trial.response_allowed_while_playing) {\n this.audio.addEventListener(\"ended\", respond);\n } else {\n respond();\n }\n });\n }\n}\n\nexport default AudioButtonResponsePlugin;\n"],"names":[],"mappings":";;;;;;;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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GCmHA,SAAA,EAAA;CAAA;;IAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;","x_google_ignoreList":[0]}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
var jsPsychAudioButtonResponse=function(
|
|
2
|
-
//# sourceMappingURL=https://unpkg.com/@jspsych/plugin-audio-button-response@2.0
|
|
1
|
+
var jsPsychAudioButtonResponse=function(i){"use strict";function c(r){return r&&r.__esModule&&Object.prototype.hasOwnProperty.call(r,"default")?r.default:r}const _=r=>{const s=new Set;do for(const t of Reflect.ownKeys(r))s.add([r,t]);while((r=Reflect.getPrototypeOf(r))&&r!==Object.prototype);return s};var m=(r,{include:s,exclude:t}={})=>{const a=e=>{const n=o=>typeof o=="string"?e===o:o.test(e);return s?s.some(n):t?!t.some(n):!0};for(const[e,n]of _(r.constructor.prototype)){if(n==="constructor"||!a(n))continue;const o=Reflect.getOwnPropertyDescriptor(e,n);o&&typeof o.value=="function"&&(r[n]=r[n].bind(r))}return r},y=c(m),b="2.1.0",h=(r,s,t)=>new Promise((a,e)=>{var n=u=>{try{l(t.next(u))}catch(d){e(d)}},o=u=>{try{l(t.throw(u))}catch(d){e(d)}},l=u=>u.done?a(u.value):Promise.resolve(u.value).then(n,o);l((t=t.apply(r,s)).next())});const f={name:"audio-button-response",version:b,parameters:{stimulus:{type:i.ParameterType.AUDIO,default:void 0},choices:{type:i.ParameterType.STRING,default:void 0,array:!0},button_html:{type:i.ParameterType.FUNCTION,default:function(r,s){return`<button class="jspsych-btn">${r}</button>`}},prompt:{type:i.ParameterType.HTML_STRING,default:null},trial_duration:{type:i.ParameterType.INT,default:null},button_layout:{type:i.ParameterType.STRING,default:"grid"},grid_rows:{type:i.ParameterType.INT,default:1},grid_columns:{type:i.ParameterType.INT,default:null},response_ends_trial:{type:i.ParameterType.BOOL,default:!0},trial_ends_after_audio:{type:i.ParameterType.BOOL,default:!1},response_allowed_while_playing:{type:i.ParameterType.BOOL,default:!0},enable_button_after:{type:i.ParameterType.INT,default:0}},data:{stimulus:{type:i.ParameterType.STRING},rt:{type:i.ParameterType.INT},response:{type:i.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 p{constructor(s){this.jsPsych=s,this.buttonElements=[],this.response={rt:null,button:null},this.disable_buttons=()=>{for(const t of this.buttonElements)t.setAttribute("disabled","disabled")},this.enable_buttons_without_delay=()=>{for(const t of this.buttonElements)t.removeAttribute("disabled")},this.enable_buttons_with_delay=t=>{this.jsPsych.pluginAPI.setTimeout(this.enable_buttons_without_delay,t)},this.after_response=t=>{var a=performance.now(),e=Math.round(a-this.startTime);this.context!==null&&(a=this.context.currentTime,e=Math.round((a-this.startTime)*1e3)),this.response.button=parseInt(t),this.response.rt=e,this.disable_buttons(),this.params.response_ends_trial&&this.end_trial()},this.end_trial=()=>{this.audio.stop(),this.audio.removeEventListener("ended",this.end_trial),this.audio.removeEventListener("ended",this.enable_buttons);var t={rt:this.response.rt,stimulus:this.params.stimulus,response:this.response.button};this.trial_complete(t)},y(this)}trial(s,t,a){return h(this,null,function*(){this.params=t,this.display=s,this.context=this.jsPsych.pluginAPI.audioContext(),this.audio=yield this.jsPsych.pluginAPI.getAudioPlayer(t.stimulus),t.trial_ends_after_audio&&this.audio.addEventListener("ended",this.end_trial),!t.response_allowed_while_playing&&!t.trial_ends_after_audio&&this.audio.addEventListener("ended",this.enable_buttons);const e=document.createElement("div");if(e.id="jspsych-audio-button-response-btngroup",t.button_layout==="grid"){if(e.classList.add("jspsych-btn-group-grid"),t.grid_rows===null&&t.grid_columns===null)throw new Error("You cannot set `grid_rows` to `null` without providing a value for `grid_columns`.");const n=t.grid_columns===null?Math.ceil(t.choices.length/t.grid_rows):t.grid_columns,o=t.grid_rows===null?Math.ceil(t.choices.length/t.grid_columns):t.grid_rows;e.style.gridTemplateColumns=`repeat(${n}, 1fr)`,e.style.gridTemplateRows=`repeat(${o}, 1fr)`}else t.button_layout==="flex"&&e.classList.add("jspsych-btn-group-flex");for(const[n,o]of t.choices.entries()){e.insertAdjacentHTML("beforeend",t.button_html(o,n));const l=e.lastChild;l.dataset.choice=n.toString(),l.addEventListener("click",()=>{this.after_response(n)}),this.buttonElements.push(l)}return s.appendChild(e),t.prompt!==null&&s.insertAdjacentHTML("beforeend",t.prompt),t.response_allowed_while_playing?t.enable_button_after>0&&(this.disable_buttons(),this.enable_buttons()):this.disable_buttons(),t.trial_duration!==null&&this.jsPsych.pluginAPI.setTimeout(()=>{this.end_trial()},t.trial_duration),a(),this.startTime=performance.now(),this.context!==null&&(this.startTime=this.context.currentTime),this.audio.play(),new Promise(n=>{this.trial_complete=n})})}enable_buttons(){this.params.enable_button_after>0?this.enable_buttons_with_delay(this.params.enable_button_after):this.enable_buttons_without_delay()}simulate(s,t,a,e){return h(this,null,function*(){t=="data-only"&&(e(),this.simulate_data_only(s,a)),t=="visual"&&this.simulate_visual(s,a,e)})}create_simulation_data(s,t){const a={stimulus:s.stimulus,rt:this.jsPsych.randomization.sampleExGaussian(500,50,.006666666666666667,!0)+s.enable_button_after,response:this.jsPsych.randomization.randomInt(0,s.choices.length-1)},e=this.jsPsych.pluginAPI.mergeSimulationData(a,t);return this.jsPsych.pluginAPI.ensureSimulationDataConsistency(s,e),e}simulate_data_only(s,t){const a=this.create_simulation_data(s,t);this.jsPsych.finishTrial(a)}simulate_visual(s,t,a){const e=this.create_simulation_data(s,t),n=this.jsPsych.getDisplayElement(),o=()=>{e.rt!==null&&this.jsPsych.pluginAPI.clickTarget(n.querySelector(`#jspsych-audio-button-response-btngroup [data-choice="${e.response}"]`),e.rt)};this.trial(n,s,()=>{a(),s.response_allowed_while_playing?o():this.audio.addEventListener("ended",o)})}}return p.info=f,p}(jsPsychModule);
|
|
2
|
+
//# sourceMappingURL=https://unpkg.com/@jspsych/plugin-audio-button-response@2.1.0/dist/index.browser.min.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.browser.min.js","sources":["../../../node_modules/auto-bind/index.js","../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","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-button-response\",\n version: version,\n parameters: {\n /** Path to audio file to be played. */\n stimulus: {\n type: ParameterType.AUDIO,\n default: undefined,\n },\n /** Labels for the buttons. Each different string in the array will generate a different button. */\n choices: {\n type: ParameterType.STRING,\n default: undefined,\n array: true,\n },\n /**\n * A function that generates the HTML for each button in the `choices` array. The function gets the string\n * and index of the item in the `choices` array and should return valid HTML. If you want to use different\n * markup for each button, you can do that by using a conditional on either parameter. The default parameter\n * returns a button element with the text label of the choice.\n */\n button_html: {\n type: ParameterType.FUNCTION,\n default: function (choice: string, choice_index: number) {\n return `<button class=\"jspsych-btn\">${choice}</button>`;\n },\n },\n /** This string can contain HTML markup. Any content here will be displayed below the stimulus. The intention\n * is that it can be used to provide a reminder about the action the participant is supposed to take\n * (e.g., which key to press). */\n prompt: {\n type: ParameterType.HTML_STRING,\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, the trial\n * will wait for a response indefinitely */\n trial_duration: {\n type: ParameterType.INT,\n default: null,\n },\n /** Setting to `'grid'` will make the container element have the CSS property `display: grid` and enable the\n * use of `grid_rows` and `grid_columns`. Setting to `'flex'` will make the container element have the CSS\n * property `display: flex`. You can customize how the buttons are laid out by adding inline CSS in the `button_html` parameter.\n */\n button_layout: {\n type: ParameterType.STRING,\n default: \"grid\",\n },\n /** The number of rows in the button grid. Only applicable when `button_layout` is set to `'grid'`. If null, the\n * number of rows will be determined automatically based on the number of buttons and the number of columns.\n */\n grid_rows: {\n type: ParameterType.INT,\n default: 1,\n },\n /** The number of columns in the button grid. Only applicable when `button_layout` is set to `'grid'`.\n * If null, the number of columns will be determined automatically based on the number of buttons and the\n * number of rows.\n */\n grid_columns: {\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 set this parameter to `false` to force\n * the participant to listen to the stimulus for a fixed amount of time, even if they respond before the time is complete. */\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 default: false,\n },\n /**\n * If true, then responses are allowed while the audio is playing. If false, then the audio must finish\n * playing before the button choices are enabled and a response is accepted. Once the audio has played\n * all the way through, the buttons are enabled and a response is allowed (including while the audio is\n * being re-played via on-screen playback controls).\n */\n response_allowed_while_playing: {\n type: ParameterType.BOOL,\n default: true,\n },\n /** How long the button will delay enabling in milliseconds. If `response_allowed_while_playing` is `true`,\n * the timer will start immediately. If it is `false`, the timer will start at the end of the audio. */\n enable_button_after: {\n type: ParameterType.INT,\n default: 0,\n },\n },\n data: {\n /** The response time in milliseconds for the participant to make a response. The time is measured from\n * when the stimulus first began playing until the participant's response.*/\n rt: {\n type: ParameterType.INT,\n },\n /** Indicates which button the participant pressed. The first button in the `choices` array is 0, the second is 1, and so on. */\n response: {\n type: ParameterType.INT,\n },\n },\n};\n\ntype Info = typeof info;\n\n/**\n * If the browser supports it, audio files are played using the WebAudio API. This allows for reasonably precise \n * timing of the playback. The timing of responses generated is measured against the WebAudio specific clock, \n * improving the measurement of response times. If the browser does not support the WebAudio API, then the audio file is \n * played with HTML5 audio. \n\n * Audio files can be automatically preloaded by jsPsych using the [`preload` plugin](preload.md). However, if \n * you are using timeline variables or another dynamic method to specify the audio stimulus, you will need \n * 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 \n * has failed to respond within a fixed length of time. You can also prevent a button response from being made before the \n * audio has finished playing.\n * \n * @author Kristin Diep\n * @see {@link https://www.jspsych.org/latest/plugins/audio-button-response/ audio-button-response plugin documentation on jspsych.org}\n */\nclass AudioButtonResponsePlugin implements JsPsychPlugin<Info> {\n static info = info;\n private audio: AudioPlayerInterface;\n private params: TrialType<Info>;\n private buttonElements: HTMLElement[] = [];\n private display: HTMLElement;\n private response: { rt: number; button: number } = { rt: null, button: null };\n private context: AudioContext;\n private startTime: number;\n private trial_complete: (trial_data: { rt: number; stimulus: string; response: number }) => void;\n\n constructor(private jsPsych: JsPsych) {\n autoBind(this);\n }\n\n async trial(display_element: HTMLElement, trial: TrialType<Info>, on_load: () => void) {\n this.params = trial;\n this.display = display_element;\n // setup stimulus\n this.context = this.jsPsych.pluginAPI.audioContext();\n\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 // enable buttons after audio ends if necessary\n if (!trial.response_allowed_while_playing && !trial.trial_ends_after_audio) {\n this.audio.addEventListener(\"ended\", this.enable_buttons);\n }\n\n // Display buttons\n const buttonGroupElement = document.createElement(\"div\");\n buttonGroupElement.id = \"jspsych-audio-button-response-btngroup\";\n if (trial.button_layout === \"grid\") {\n buttonGroupElement.classList.add(\"jspsych-btn-group-grid\");\n if (trial.grid_rows === null && trial.grid_columns === null) {\n throw new Error(\n \"You cannot set `grid_rows` to `null` without providing a value for `grid_columns`.\"\n );\n }\n const n_cols =\n trial.grid_columns === null\n ? Math.ceil(trial.choices.length / trial.grid_rows)\n : trial.grid_columns;\n const n_rows =\n trial.grid_rows === null\n ? Math.ceil(trial.choices.length / trial.grid_columns)\n : trial.grid_rows;\n buttonGroupElement.style.gridTemplateColumns = `repeat(${n_cols}, 1fr)`;\n buttonGroupElement.style.gridTemplateRows = `repeat(${n_rows}, 1fr)`;\n } else if (trial.button_layout === \"flex\") {\n buttonGroupElement.classList.add(\"jspsych-btn-group-flex\");\n }\n\n for (const [choiceIndex, choice] of trial.choices.entries()) {\n buttonGroupElement.insertAdjacentHTML(\"beforeend\", trial.button_html(choice, choiceIndex));\n const buttonElement = buttonGroupElement.lastChild as HTMLElement;\n buttonElement.dataset.choice = choiceIndex.toString();\n buttonElement.addEventListener(\"click\", () => {\n this.after_response(choiceIndex);\n });\n this.buttonElements.push(buttonElement);\n }\n\n display_element.appendChild(buttonGroupElement);\n\n // Show prompt if there is one\n if (trial.prompt !== null) {\n display_element.insertAdjacentHTML(\"beforeend\", trial.prompt);\n }\n\n if (trial.response_allowed_while_playing) {\n if (trial.enable_button_after > 0) {\n this.disable_buttons();\n this.enable_buttons();\n }\n } else {\n this.disable_buttons();\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 on_load();\n\n // start time\n this.startTime = performance.now();\n if (this.context !== null) {\n this.startTime = this.context.currentTime;\n }\n\n // start audio\n this.audio.play();\n\n return new Promise((resolve) => {\n // hold the .resolve() function from the Promise that ends the trial\n this.trial_complete = resolve;\n });\n }\n\n private disable_buttons = () => {\n for (const button of this.buttonElements) {\n button.setAttribute(\"disabled\", \"disabled\");\n }\n };\n\n private enable_buttons_without_delay = () => {\n for (const button of this.buttonElements) {\n button.removeAttribute(\"disabled\");\n }\n };\n\n private enable_buttons_with_delay = (delay: number) => {\n this.jsPsych.pluginAPI.setTimeout(this.enable_buttons_without_delay, delay);\n };\n\n private enable_buttons() {\n if (this.params.enable_button_after > 0) {\n this.enable_buttons_with_delay(this.params.enable_button_after);\n } else {\n this.enable_buttons_without_delay();\n }\n }\n\n // function to handle responses by the subject\n private after_response = (choice) => {\n // measure rt\n var endTime = performance.now();\n var rt = Math.round(endTime - this.startTime);\n if (this.context !== null) {\n endTime = this.context.currentTime;\n rt = Math.round((endTime - this.startTime) * 1000);\n }\n this.response.button = parseInt(choice);\n this.response.rt = rt;\n\n // disable all the buttons after a response\n this.disable_buttons();\n\n if (this.params.response_ends_trial) {\n this.end_trial();\n }\n };\n\n // method to end trial when it is time\n private end_trial = () => {\n // stop the audio file if it is playing\n this.audio.stop();\n\n // remove end event listeners if they exist\n this.audio.removeEventListener(\"ended\", this.end_trial);\n this.audio.removeEventListener(\"ended\", this.enable_buttons);\n\n // gather the data to store for the trial\n var trial_data = {\n rt: this.response.rt,\n stimulus: this.params.stimulus,\n response: this.response.button,\n };\n\n // move on to the next trial\n this.trial_complete(trial_data);\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 this.simulate_data_only(trial, simulation_options);\n }\n if (simulation_mode == \"visual\") {\n this.simulate_visual(trial, simulation_options, load_callback);\n }\n }\n\n private create_simulation_data(trial: TrialType<Info>, simulation_options) {\n const default_data = {\n stimulus: trial.stimulus,\n rt:\n this.jsPsych.randomization.sampleExGaussian(500, 50, 1 / 150, true) +\n trial.enable_button_after,\n response: this.jsPsych.randomization.randomInt(0, trial.choices.length - 1),\n };\n\n const data = this.jsPsych.pluginAPI.mergeSimulationData(default_data, simulation_options);\n\n this.jsPsych.pluginAPI.ensureSimulationDataConsistency(trial, data);\n\n return data;\n }\n\n private simulate_data_only(trial: TrialType<Info>, simulation_options) {\n const data = this.create_simulation_data(trial, simulation_options);\n\n this.jsPsych.finishTrial(data);\n }\n\n private simulate_visual(trial: TrialType<Info>, simulation_options, load_callback: () => void) {\n const data = this.create_simulation_data(trial, simulation_options);\n\n const display_element = this.jsPsych.getDisplayElement();\n\n const respond = () => {\n if (data.rt !== null) {\n this.jsPsych.pluginAPI.clickTarget(\n display_element.querySelector(\n `#jspsych-audio-button-response-btngroup [data-choice=\"${data.response}\"]`\n ),\n data.rt\n );\n }\n };\n\n this.trial(display_element, trial, () => {\n load_callback();\n if (!trial.response_allowed_while_playing) {\n this.audio.addEventListener(\"ended\", respond);\n } else {\n respond();\n }\n });\n }\n}\n\nexport default AudioButtonResponsePlugin;\n"],"names":["getAllProperties","object","properties","key","autoBind","self","include","exclude","filter","match","pattern","descriptor","l","e","n","s","o","r","a","info","version","ParameterType","choice","choice_index","AudioButtonResponsePlugin","jsPsych","button","delay","endTime","rt","trial_data","display_element","trial","on_load","__async","buttonGroupElement","n_cols","n_rows","choiceIndex","buttonElement","resolve","simulation_mode","simulation_options","load_callback","default_data","data","respond"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.browser.min.js","sources":["../../../node_modules/auto-bind/index.js","../package.json","../src/index.ts"],"sourcesContent":["'use strict';\n\n// Gets all non-builtin properties up the prototype chain\nconst getAllProperties = object => {\n\tconst properties = new Set();\n\n\tdo {\n\t\tfor (const key of Reflect.ownKeys(object)) {\n\t\t\tproperties.add([object, key]);\n\t\t}\n\t} while ((object = Reflect.getPrototypeOf(object)) && object !== Object.prototype);\n\n\treturn properties;\n};\n\nmodule.exports = (self, {include, exclude} = {}) => {\n\tconst filter = key => {\n\t\tconst match = pattern => typeof pattern === 'string' ? key === pattern : pattern.test(key);\n\n\t\tif (include) {\n\t\t\treturn include.some(match);\n\t\t}\n\n\t\tif (exclude) {\n\t\t\treturn !exclude.some(match);\n\t\t}\n\n\t\treturn true;\n\t};\n\n\tfor (const [object, key] of getAllProperties(self.constructor.prototype)) {\n\t\tif (key === 'constructor' || !filter(key)) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst descriptor = Reflect.getOwnPropertyDescriptor(object, key);\n\t\tif (descriptor && typeof descriptor.value === 'function') {\n\t\t\tself[key] = self[key].bind(self);\n\t\t}\n\t}\n\n\treturn self;\n};\n","{\n \"name\": \"@jspsych/plugin-audio-button-response\",\n \"version\": \"2.1.0\",\n \"description\": \"jsPsych plugin for playing an audio file and getting a button 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-button-response\"\n },\n \"author\": \"Kristin Diep\",\n \"license\": \"MIT\",\n \"bugs\": {\n \"url\": \"https://github.com/jspsych/jsPsych/issues\"\n },\n \"homepage\": \"https://www.jspsych.org/latest/plugins/audio-button-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-button-response\",\n version: version,\n parameters: {\n /** Path to audio file to be played. */\n stimulus: {\n type: ParameterType.AUDIO,\n default: undefined,\n },\n /** Labels for the buttons. Each different string in the array will generate a different button. */\n choices: {\n type: ParameterType.STRING,\n default: undefined,\n array: true,\n },\n /**\n * A function that generates the HTML for each button in the `choices` array. The function gets the string\n * and index of the item in the `choices` array and should return valid HTML. If you want to use different\n * markup for each button, you can do that by using a conditional on either parameter. The default parameter\n * returns a button element with the text label of the choice.\n */\n button_html: {\n type: ParameterType.FUNCTION,\n default: function (choice: string, choice_index: number) {\n return `<button class=\"jspsych-btn\">${choice}</button>`;\n },\n },\n /** This string can contain HTML markup. Any content here will be displayed below the stimulus. The intention\n * is that it can be used to provide a reminder about the action the participant is supposed to take\n * (e.g., which key to press). */\n prompt: {\n type: ParameterType.HTML_STRING,\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, the trial\n * will wait for a response indefinitely */\n trial_duration: {\n type: ParameterType.INT,\n default: null,\n },\n /** Setting to `'grid'` will make the container element have the CSS property `display: grid` and enable the\n * use of `grid_rows` and `grid_columns`. Setting to `'flex'` will make the container element have the CSS\n * property `display: flex`. You can customize how the buttons are laid out by adding inline CSS in the `button_html` parameter.\n */\n button_layout: {\n type: ParameterType.STRING,\n default: \"grid\",\n },\n /** The number of rows in the button grid. Only applicable when `button_layout` is set to `'grid'`. If null, the\n * number of rows will be determined automatically based on the number of buttons and the number of columns.\n */\n grid_rows: {\n type: ParameterType.INT,\n default: 1,\n },\n /** The number of columns in the button grid. Only applicable when `button_layout` is set to `'grid'`.\n * If null, the number of columns will be determined automatically based on the number of buttons and the\n * number of rows.\n */\n grid_columns: {\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 set this parameter to `false` to force\n * the participant to listen to the stimulus for a fixed amount of time, even if they respond before the time is complete. */\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 default: false,\n },\n /**\n * If true, then responses are allowed while the audio is playing. If false, then the audio must finish\n * playing before the button choices are enabled and a response is accepted. Once the audio has played\n * all the way through, the buttons are enabled and a response is allowed (including while the audio is\n * being re-played via on-screen playback controls).\n */\n response_allowed_while_playing: {\n type: ParameterType.BOOL,\n default: true,\n },\n /** How long the button will delay enabling in milliseconds. If `response_allowed_while_playing` is `true`,\n * the timer will start immediately. If it is `false`, the timer will start at the end of the audio. */\n enable_button_after: {\n type: ParameterType.INT,\n default: 0,\n },\n },\n data: {\n /** The path of the audio file that was played. */\n stimulus: {\n type: ParameterType.STRING,\n },\n /** The response time in milliseconds for the participant to make a response. The time is measured from\n * when the stimulus first began playing until the participant's response.*/\n rt: {\n type: ParameterType.INT,\n },\n /** Indicates which button the participant pressed. The first button in the `choices` array is 0, the second is 1, and so on. */\n response: {\n type: ParameterType.INT,\n },\n },\n // prettier-ignore\n citations: '__CITATIONS__',\n};\n\ntype Info = typeof info;\n\n/**\n * If the browser supports it, audio files are played using the WebAudio API. This allows for reasonably precise \n * timing of the playback. The timing of responses generated is measured against the WebAudio specific clock, \n * improving the measurement of response times. If the browser does not support the WebAudio API, then the audio file is \n * played with HTML5 audio. \n\n * Audio files can be automatically preloaded by jsPsych using the [`preload` plugin](preload.md). However, if \n * you are using timeline variables or another dynamic method to specify the audio stimulus, you will need \n * 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 \n * has failed to respond within a fixed length of time. You can also prevent a button response from being made before the \n * audio has finished playing.\n * \n * @author Kristin Diep\n * @see {@link https://www.jspsych.org/latest/plugins/audio-button-response/ audio-button-response plugin documentation on jspsych.org}\n */\nclass AudioButtonResponsePlugin implements JsPsychPlugin<Info> {\n static info = info;\n private audio: AudioPlayerInterface;\n private params: TrialType<Info>;\n private buttonElements: HTMLElement[] = [];\n private display: HTMLElement;\n private response: { rt: number; button: number } = { rt: null, button: null };\n private context: AudioContext;\n private startTime: number;\n private trial_complete: (trial_data: { rt: number; stimulus: string; response: number }) => void;\n\n constructor(private jsPsych: JsPsych) {\n autoBind(this);\n }\n\n async trial(display_element: HTMLElement, trial: TrialType<Info>, on_load: () => void) {\n this.params = trial;\n this.display = display_element;\n // setup stimulus\n this.context = this.jsPsych.pluginAPI.audioContext();\n\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 // enable buttons after audio ends if necessary\n if (!trial.response_allowed_while_playing && !trial.trial_ends_after_audio) {\n this.audio.addEventListener(\"ended\", this.enable_buttons);\n }\n\n // Display buttons\n const buttonGroupElement = document.createElement(\"div\");\n buttonGroupElement.id = \"jspsych-audio-button-response-btngroup\";\n if (trial.button_layout === \"grid\") {\n buttonGroupElement.classList.add(\"jspsych-btn-group-grid\");\n if (trial.grid_rows === null && trial.grid_columns === null) {\n throw new Error(\n \"You cannot set `grid_rows` to `null` without providing a value for `grid_columns`.\"\n );\n }\n const n_cols =\n trial.grid_columns === null\n ? Math.ceil(trial.choices.length / trial.grid_rows)\n : trial.grid_columns;\n const n_rows =\n trial.grid_rows === null\n ? Math.ceil(trial.choices.length / trial.grid_columns)\n : trial.grid_rows;\n buttonGroupElement.style.gridTemplateColumns = `repeat(${n_cols}, 1fr)`;\n buttonGroupElement.style.gridTemplateRows = `repeat(${n_rows}, 1fr)`;\n } else if (trial.button_layout === \"flex\") {\n buttonGroupElement.classList.add(\"jspsych-btn-group-flex\");\n }\n\n for (const [choiceIndex, choice] of trial.choices.entries()) {\n buttonGroupElement.insertAdjacentHTML(\"beforeend\", trial.button_html(choice, choiceIndex));\n const buttonElement = buttonGroupElement.lastChild as HTMLElement;\n buttonElement.dataset.choice = choiceIndex.toString();\n buttonElement.addEventListener(\"click\", () => {\n this.after_response(choiceIndex);\n });\n this.buttonElements.push(buttonElement);\n }\n\n display_element.appendChild(buttonGroupElement);\n\n // Show prompt if there is one\n if (trial.prompt !== null) {\n display_element.insertAdjacentHTML(\"beforeend\", trial.prompt);\n }\n\n if (trial.response_allowed_while_playing) {\n if (trial.enable_button_after > 0) {\n this.disable_buttons();\n this.enable_buttons();\n }\n } else {\n this.disable_buttons();\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 on_load();\n\n // start time\n this.startTime = performance.now();\n if (this.context !== null) {\n this.startTime = this.context.currentTime;\n }\n\n // start audio\n this.audio.play();\n\n return new Promise((resolve) => {\n // hold the .resolve() function from the Promise that ends the trial\n this.trial_complete = resolve;\n });\n }\n\n private disable_buttons = () => {\n for (const button of this.buttonElements) {\n button.setAttribute(\"disabled\", \"disabled\");\n }\n };\n\n private enable_buttons_without_delay = () => {\n for (const button of this.buttonElements) {\n button.removeAttribute(\"disabled\");\n }\n };\n\n private enable_buttons_with_delay = (delay: number) => {\n this.jsPsych.pluginAPI.setTimeout(this.enable_buttons_without_delay, delay);\n };\n\n private enable_buttons() {\n if (this.params.enable_button_after > 0) {\n this.enable_buttons_with_delay(this.params.enable_button_after);\n } else {\n this.enable_buttons_without_delay();\n }\n }\n\n // function to handle responses by the subject\n private after_response = (choice) => {\n // measure rt\n var endTime = performance.now();\n var rt = Math.round(endTime - this.startTime);\n if (this.context !== null) {\n endTime = this.context.currentTime;\n rt = Math.round((endTime - this.startTime) * 1000);\n }\n this.response.button = parseInt(choice);\n this.response.rt = rt;\n\n // disable all the buttons after a response\n this.disable_buttons();\n\n if (this.params.response_ends_trial) {\n this.end_trial();\n }\n };\n\n // method to end trial when it is time\n private end_trial = () => {\n // stop the audio file if it is playing\n this.audio.stop();\n\n // remove end event listeners if they exist\n this.audio.removeEventListener(\"ended\", this.end_trial);\n this.audio.removeEventListener(\"ended\", this.enable_buttons);\n\n // gather the data to store for the trial\n var trial_data = {\n rt: this.response.rt,\n stimulus: this.params.stimulus,\n response: this.response.button,\n };\n\n // move on to the next trial\n this.trial_complete(trial_data);\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 this.simulate_data_only(trial, simulation_options);\n }\n if (simulation_mode == \"visual\") {\n this.simulate_visual(trial, simulation_options, load_callback);\n }\n }\n\n private create_simulation_data(trial: TrialType<Info>, simulation_options) {\n const default_data = {\n stimulus: trial.stimulus,\n rt:\n this.jsPsych.randomization.sampleExGaussian(500, 50, 1 / 150, true) +\n trial.enable_button_after,\n response: this.jsPsych.randomization.randomInt(0, trial.choices.length - 1),\n };\n\n const data = this.jsPsych.pluginAPI.mergeSimulationData(default_data, simulation_options);\n\n this.jsPsych.pluginAPI.ensureSimulationDataConsistency(trial, data);\n\n return data;\n }\n\n private simulate_data_only(trial: TrialType<Info>, simulation_options) {\n const data = this.create_simulation_data(trial, simulation_options);\n\n this.jsPsych.finishTrial(data);\n }\n\n private simulate_visual(trial: TrialType<Info>, simulation_options, load_callback: () => void) {\n const data = this.create_simulation_data(trial, simulation_options);\n\n const display_element = this.jsPsych.getDisplayElement();\n\n const respond = () => {\n if (data.rt !== null) {\n this.jsPsych.pluginAPI.clickTarget(\n display_element.querySelector(\n `#jspsych-audio-button-response-btngroup [data-choice=\"${data.response}\"]`\n ),\n data.rt\n );\n }\n };\n\n this.trial(display_element, trial, () => {\n load_callback();\n if (!trial.response_allowed_while_playing) {\n this.audio.addEventListener(\"ended\", respond);\n } else {\n respond();\n }\n });\n }\n}\n\nexport default AudioButtonResponsePlugin;\n"],"names":["getAllProperties","object","properties","key","autoBind","self","include","exclude","filter","match","pattern","descriptor","version","p","l","e","n","s","o","r","a"],"mappings":"4JAGA,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,QCFbC,EAAA,CAAAC,EAAAC,EAAA,IAAA,IAAA,QAAA,CAAAC,EAAAC,IAAA,CAAA,IAAAC,EAAA,GAAA,CAAA,GAAA,CAAAC,EAAA,EAAA,KAAA,CAAA,CAAA,CAAA,OAAA,EAAA,CAAAF,EAAA,CAAA,CAAA,CAAA,EAAAG,EAAA,GAAA,CAAA,GAAA,CAAAD,EAAA,EAAA,MAAA,CAAA,CAAA,CAAA,OAAA,EAAA,CAAAF,EAAA,CAAA,CAAA,CAAA,EAAAE,EAAA,GAAA,EAAA,KAAAH,EAAA,EAAA,KAAA,EAAA,QAAA,QAAA,EAAA,KAAA,EAAA,KAAAE,EAAAE,CAAA,EAAAD,GAAA,EAAA,EAAA,MAAAL,EAAAC,CAAA,GAAA,KAAA,CAAA,CAAA,CAAA,45BAqHa,UAAA,iuBAAe","x_google_ignoreList":[0]}
|
package/dist/index.cjs
CHANGED
|
@@ -3,113 +3,122 @@
|
|
|
3
3
|
var autoBind = require('auto-bind');
|
|
4
4
|
var jspsych = require('jspsych');
|
|
5
5
|
|
|
6
|
-
var
|
|
7
|
-
name: "@jspsych/plugin-audio-button-response",
|
|
8
|
-
version: "2.0.1",
|
|
9
|
-
description: "jsPsych plugin for playing an audio file and getting a button response",
|
|
10
|
-
type: "module",
|
|
11
|
-
main: "dist/index.cjs",
|
|
12
|
-
exports: {
|
|
13
|
-
import: "./dist/index.js",
|
|
14
|
-
require: "./dist/index.cjs"
|
|
15
|
-
},
|
|
16
|
-
typings: "dist/index.d.ts",
|
|
17
|
-
unpkg: "dist/index.browser.min.js",
|
|
18
|
-
files: [
|
|
19
|
-
"src",
|
|
20
|
-
"dist"
|
|
21
|
-
],
|
|
22
|
-
source: "src/index.ts",
|
|
23
|
-
scripts: {
|
|
24
|
-
test: "jest",
|
|
25
|
-
"test:watch": "npm test -- --watch",
|
|
26
|
-
tsc: "tsc",
|
|
27
|
-
build: "rollup --config",
|
|
28
|
-
"build:watch": "npm run build -- --watch"
|
|
29
|
-
},
|
|
30
|
-
repository: {
|
|
31
|
-
type: "git",
|
|
32
|
-
url: "git+https://github.com/jspsych/jsPsych.git",
|
|
33
|
-
directory: "packages/plugin-audio-button-response"
|
|
34
|
-
},
|
|
35
|
-
author: "Kristin Diep",
|
|
36
|
-
license: "MIT",
|
|
37
|
-
bugs: {
|
|
38
|
-
url: "https://github.com/jspsych/jsPsych/issues"
|
|
39
|
-
},
|
|
40
|
-
homepage: "https://www.jspsych.org/latest/plugins/audio-button-response",
|
|
41
|
-
peerDependencies: {
|
|
42
|
-
jspsych: ">=7.1.0"
|
|
43
|
-
},
|
|
44
|
-
devDependencies: {
|
|
45
|
-
"@jspsych/config": "^3.0.0",
|
|
46
|
-
"@jspsych/test-utils": "^1.2.0"
|
|
47
|
-
}
|
|
48
|
-
};
|
|
6
|
+
var version = "2.1.0";
|
|
49
7
|
|
|
50
8
|
const info = {
|
|
51
9
|
name: "audio-button-response",
|
|
52
|
-
version
|
|
10
|
+
version,
|
|
53
11
|
parameters: {
|
|
12
|
+
/** Path to audio file to be played. */
|
|
54
13
|
stimulus: {
|
|
55
14
|
type: jspsych.ParameterType.AUDIO,
|
|
56
15
|
default: void 0
|
|
57
16
|
},
|
|
17
|
+
/** Labels for the buttons. Each different string in the array will generate a different button. */
|
|
58
18
|
choices: {
|
|
59
19
|
type: jspsych.ParameterType.STRING,
|
|
60
20
|
default: void 0,
|
|
61
21
|
array: true
|
|
62
22
|
},
|
|
23
|
+
/**
|
|
24
|
+
* A function that generates the HTML for each button in the `choices` array. The function gets the string
|
|
25
|
+
* and index of the item in the `choices` array and should return valid HTML. If you want to use different
|
|
26
|
+
* markup for each button, you can do that by using a conditional on either parameter. The default parameter
|
|
27
|
+
* returns a button element with the text label of the choice.
|
|
28
|
+
*/
|
|
63
29
|
button_html: {
|
|
64
30
|
type: jspsych.ParameterType.FUNCTION,
|
|
65
31
|
default: function(choice, choice_index) {
|
|
66
32
|
return `<button class="jspsych-btn">${choice}</button>`;
|
|
67
33
|
}
|
|
68
34
|
},
|
|
35
|
+
/** This string can contain HTML markup. Any content here will be displayed below the stimulus. The intention
|
|
36
|
+
* is that it can be used to provide a reminder about the action the participant is supposed to take
|
|
37
|
+
* (e.g., which key to press). */
|
|
69
38
|
prompt: {
|
|
70
39
|
type: jspsych.ParameterType.HTML_STRING,
|
|
71
40
|
default: null
|
|
72
41
|
},
|
|
42
|
+
/** How long to wait for the participant to make a response before ending the trial in milliseconds. If the
|
|
43
|
+
* participant fails to make a response before this timer is reached, the participant's response will be
|
|
44
|
+
* recorded as null for the trial and the trial will end. If the value of this parameter is null, the trial
|
|
45
|
+
* will wait for a response indefinitely */
|
|
73
46
|
trial_duration: {
|
|
74
47
|
type: jspsych.ParameterType.INT,
|
|
75
48
|
default: null
|
|
76
49
|
},
|
|
50
|
+
/** Setting to `'grid'` will make the container element have the CSS property `display: grid` and enable the
|
|
51
|
+
* use of `grid_rows` and `grid_columns`. Setting to `'flex'` will make the container element have the CSS
|
|
52
|
+
* property `display: flex`. You can customize how the buttons are laid out by adding inline CSS in the `button_html` parameter.
|
|
53
|
+
*/
|
|
77
54
|
button_layout: {
|
|
78
55
|
type: jspsych.ParameterType.STRING,
|
|
79
56
|
default: "grid"
|
|
80
57
|
},
|
|
58
|
+
/** The number of rows in the button grid. Only applicable when `button_layout` is set to `'grid'`. If null, the
|
|
59
|
+
* number of rows will be determined automatically based on the number of buttons and the number of columns.
|
|
60
|
+
*/
|
|
81
61
|
grid_rows: {
|
|
82
62
|
type: jspsych.ParameterType.INT,
|
|
83
63
|
default: 1
|
|
84
64
|
},
|
|
65
|
+
/** The number of columns in the button grid. Only applicable when `button_layout` is set to `'grid'`.
|
|
66
|
+
* If null, the number of columns will be determined automatically based on the number of buttons and the
|
|
67
|
+
* number of rows.
|
|
68
|
+
*/
|
|
85
69
|
grid_columns: {
|
|
86
70
|
type: jspsych.ParameterType.INT,
|
|
87
71
|
default: null
|
|
88
72
|
},
|
|
73
|
+
/** If true, then the trial will end whenever the participant makes a response (assuming they make their
|
|
74
|
+
* response before the cutoff specified by the `trial_duration` parameter). If false, then the trial will
|
|
75
|
+
* continue until the value for `trial_duration` is reached. You can set this parameter to `false` to force
|
|
76
|
+
* the participant to listen to the stimulus for a fixed amount of time, even if they respond before the time is complete. */
|
|
89
77
|
response_ends_trial: {
|
|
90
78
|
type: jspsych.ParameterType.BOOL,
|
|
91
79
|
default: true
|
|
92
80
|
},
|
|
81
|
+
/** If true, then the trial will end as soon as the audio file finishes playing. */
|
|
93
82
|
trial_ends_after_audio: {
|
|
94
83
|
type: jspsych.ParameterType.BOOL,
|
|
95
84
|
default: false
|
|
96
85
|
},
|
|
86
|
+
/**
|
|
87
|
+
* If true, then responses are allowed while the audio is playing. If false, then the audio must finish
|
|
88
|
+
* playing before the button choices are enabled and a response is accepted. Once the audio has played
|
|
89
|
+
* all the way through, the buttons are enabled and a response is allowed (including while the audio is
|
|
90
|
+
* being re-played via on-screen playback controls).
|
|
91
|
+
*/
|
|
97
92
|
response_allowed_while_playing: {
|
|
98
93
|
type: jspsych.ParameterType.BOOL,
|
|
99
94
|
default: true
|
|
100
95
|
},
|
|
96
|
+
/** How long the button will delay enabling in milliseconds. If `response_allowed_while_playing` is `true`,
|
|
97
|
+
* the timer will start immediately. If it is `false`, the timer will start at the end of the audio. */
|
|
101
98
|
enable_button_after: {
|
|
102
99
|
type: jspsych.ParameterType.INT,
|
|
103
100
|
default: 0
|
|
104
101
|
}
|
|
105
102
|
},
|
|
106
103
|
data: {
|
|
104
|
+
/** The path of the audio file that was played. */
|
|
105
|
+
stimulus: {
|
|
106
|
+
type: jspsych.ParameterType.STRING
|
|
107
|
+
},
|
|
108
|
+
/** The response time in milliseconds for the participant to make a response. The time is measured from
|
|
109
|
+
* when the stimulus first began playing until the participant's response.*/
|
|
107
110
|
rt: {
|
|
108
111
|
type: jspsych.ParameterType.INT
|
|
109
112
|
},
|
|
113
|
+
/** Indicates which button the participant pressed. The first button in the `choices` array is 0, the second is 1, and so on. */
|
|
110
114
|
response: {
|
|
111
115
|
type: jspsych.ParameterType.INT
|
|
112
116
|
}
|
|
117
|
+
},
|
|
118
|
+
// prettier-ignore
|
|
119
|
+
citations: {
|
|
120
|
+
"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 ",
|
|
121
|
+
"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}, } '
|
|
113
122
|
}
|
|
114
123
|
};
|
|
115
124
|
class AudioButtonResponsePlugin {
|
|
@@ -130,6 +139,7 @@ class AudioButtonResponsePlugin {
|
|
|
130
139
|
this.enable_buttons_with_delay = (delay) => {
|
|
131
140
|
this.jsPsych.pluginAPI.setTimeout(this.enable_buttons_without_delay, delay);
|
|
132
141
|
};
|
|
142
|
+
// function to handle responses by the subject
|
|
133
143
|
this.after_response = (choice) => {
|
|
134
144
|
var endTime = performance.now();
|
|
135
145
|
var rt = Math.round(endTime - this.startTime);
|
|
@@ -144,6 +154,7 @@ class AudioButtonResponsePlugin {
|
|
|
144
154
|
this.end_trial();
|
|
145
155
|
}
|
|
146
156
|
};
|
|
157
|
+
// method to end trial when it is time
|
|
147
158
|
this.end_trial = () => {
|
|
148
159
|
this.audio.stop();
|
|
149
160
|
this.audio.removeEventListener("ended", this.end_trial);
|
|
@@ -157,6 +168,9 @@ class AudioButtonResponsePlugin {
|
|
|
157
168
|
};
|
|
158
169
|
autoBind(this);
|
|
159
170
|
}
|
|
171
|
+
static {
|
|
172
|
+
this.info = info;
|
|
173
|
+
}
|
|
160
174
|
async trial(display_element, trial, on_load) {
|
|
161
175
|
this.params = trial;
|
|
162
176
|
this.display = display_element;
|
|
@@ -273,7 +287,6 @@ class AudioButtonResponsePlugin {
|
|
|
273
287
|
});
|
|
274
288
|
}
|
|
275
289
|
}
|
|
276
|
-
AudioButtonResponsePlugin.info = info;
|
|
277
290
|
|
|
278
291
|
module.exports = AudioButtonResponsePlugin;
|
|
279
292
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","sources":["../src/index.ts"],"sourcesContent":["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-button-response\",\n version: version,\n parameters: {\n /** Path to audio file to be played. */\n stimulus: {\n type: ParameterType.AUDIO,\n default: undefined,\n },\n /** Labels for the buttons. Each different string in the array will generate a different button. */\n choices: {\n type: ParameterType.STRING,\n default: undefined,\n array: true,\n },\n /**\n * A function that generates the HTML for each button in the `choices` array. The function gets the string\n * and index of the item in the `choices` array and should return valid HTML. If you want to use different\n * markup for each button, you can do that by using a conditional on either parameter. The default parameter\n * returns a button element with the text label of the choice.\n */\n button_html: {\n type: ParameterType.FUNCTION,\n default: function (choice: string, choice_index: number) {\n return `<button class=\"jspsych-btn\">${choice}</button>`;\n },\n },\n /** This string can contain HTML markup. Any content here will be displayed below the stimulus. The intention\n * is that it can be used to provide a reminder about the action the participant is supposed to take\n * (e.g., which key to press). */\n prompt: {\n type: ParameterType.HTML_STRING,\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, the trial\n * will wait for a response indefinitely */\n trial_duration: {\n type: ParameterType.INT,\n default: null,\n },\n /** Setting to `'grid'` will make the container element have the CSS property `display: grid` and enable the\n * use of `grid_rows` and `grid_columns`. Setting to `'flex'` will make the container element have the CSS\n * property `display: flex`. You can customize how the buttons are laid out by adding inline CSS in the `button_html` parameter.\n */\n button_layout: {\n type: ParameterType.STRING,\n default: \"grid\",\n },\n /** The number of rows in the button grid. Only applicable when `button_layout` is set to `'grid'`. If null, the\n * number of rows will be determined automatically based on the number of buttons and the number of columns.\n */\n grid_rows: {\n type: ParameterType.INT,\n default: 1,\n },\n /** The number of columns in the button grid. Only applicable when `button_layout` is set to `'grid'`.\n * If null, the number of columns will be determined automatically based on the number of buttons and the\n * number of rows.\n */\n grid_columns: {\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 set this parameter to `false` to force\n * the participant to listen to the stimulus for a fixed amount of time, even if they respond before the time is complete. */\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 default: false,\n },\n /**\n * If true, then responses are allowed while the audio is playing. If false, then the audio must finish\n * playing before the button choices are enabled and a response is accepted. Once the audio has played\n * all the way through, the buttons are enabled and a response is allowed (including while the audio is\n * being re-played via on-screen playback controls).\n */\n response_allowed_while_playing: {\n type: ParameterType.BOOL,\n default: true,\n },\n /** How long the button will delay enabling in milliseconds. If `response_allowed_while_playing` is `true`,\n * the timer will start immediately. If it is `false`, the timer will start at the end of the audio. */\n enable_button_after: {\n type: ParameterType.INT,\n default: 0,\n },\n },\n data: {\n /** The response time in milliseconds for the participant to make a response. The time is measured from\n * when the stimulus first began playing until the participant's response.*/\n rt: {\n type: ParameterType.INT,\n },\n /** Indicates which button the participant pressed. The first button in the `choices` array is 0, the second is 1, and so on. */\n response: {\n type: ParameterType.INT,\n },\n },\n};\n\ntype Info = typeof info;\n\n/**\n * If the browser supports it, audio files are played using the WebAudio API. This allows for reasonably precise \n * timing of the playback. The timing of responses generated is measured against the WebAudio specific clock, \n * improving the measurement of response times. If the browser does not support the WebAudio API, then the audio file is \n * played with HTML5 audio. \n\n * Audio files can be automatically preloaded by jsPsych using the [`preload` plugin](preload.md). However, if \n * you are using timeline variables or another dynamic method to specify the audio stimulus, you will need \n * 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 \n * has failed to respond within a fixed length of time. You can also prevent a button response from being made before the \n * audio has finished playing.\n * \n * @author Kristin Diep\n * @see {@link https://www.jspsych.org/latest/plugins/audio-button-response/ audio-button-response plugin documentation on jspsych.org}\n */\nclass AudioButtonResponsePlugin implements JsPsychPlugin<Info> {\n static info = info;\n private audio: AudioPlayerInterface;\n private params: TrialType<Info>;\n private buttonElements: HTMLElement[] = [];\n private display: HTMLElement;\n private response: { rt: number; button: number } = { rt: null, button: null };\n private context: AudioContext;\n private startTime: number;\n private trial_complete: (trial_data: { rt: number; stimulus: string; response: number }) => void;\n\n constructor(private jsPsych: JsPsych) {\n autoBind(this);\n }\n\n async trial(display_element: HTMLElement, trial: TrialType<Info>, on_load: () => void) {\n this.params = trial;\n this.display = display_element;\n // setup stimulus\n this.context = this.jsPsych.pluginAPI.audioContext();\n\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 // enable buttons after audio ends if necessary\n if (!trial.response_allowed_while_playing && !trial.trial_ends_after_audio) {\n this.audio.addEventListener(\"ended\", this.enable_buttons);\n }\n\n // Display buttons\n const buttonGroupElement = document.createElement(\"div\");\n buttonGroupElement.id = \"jspsych-audio-button-response-btngroup\";\n if (trial.button_layout === \"grid\") {\n buttonGroupElement.classList.add(\"jspsych-btn-group-grid\");\n if (trial.grid_rows === null && trial.grid_columns === null) {\n throw new Error(\n \"You cannot set `grid_rows` to `null` without providing a value for `grid_columns`.\"\n );\n }\n const n_cols =\n trial.grid_columns === null\n ? Math.ceil(trial.choices.length / trial.grid_rows)\n : trial.grid_columns;\n const n_rows =\n trial.grid_rows === null\n ? Math.ceil(trial.choices.length / trial.grid_columns)\n : trial.grid_rows;\n buttonGroupElement.style.gridTemplateColumns = `repeat(${n_cols}, 1fr)`;\n buttonGroupElement.style.gridTemplateRows = `repeat(${n_rows}, 1fr)`;\n } else if (trial.button_layout === \"flex\") {\n buttonGroupElement.classList.add(\"jspsych-btn-group-flex\");\n }\n\n for (const [choiceIndex, choice] of trial.choices.entries()) {\n buttonGroupElement.insertAdjacentHTML(\"beforeend\", trial.button_html(choice, choiceIndex));\n const buttonElement = buttonGroupElement.lastChild as HTMLElement;\n buttonElement.dataset.choice = choiceIndex.toString();\n buttonElement.addEventListener(\"click\", () => {\n this.after_response(choiceIndex);\n });\n this.buttonElements.push(buttonElement);\n }\n\n display_element.appendChild(buttonGroupElement);\n\n // Show prompt if there is one\n if (trial.prompt !== null) {\n display_element.insertAdjacentHTML(\"beforeend\", trial.prompt);\n }\n\n if (trial.response_allowed_while_playing) {\n if (trial.enable_button_after > 0) {\n this.disable_buttons();\n this.enable_buttons();\n }\n } else {\n this.disable_buttons();\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 on_load();\n\n // start time\n this.startTime = performance.now();\n if (this.context !== null) {\n this.startTime = this.context.currentTime;\n }\n\n // start audio\n this.audio.play();\n\n return new Promise((resolve) => {\n // hold the .resolve() function from the Promise that ends the trial\n this.trial_complete = resolve;\n });\n }\n\n private disable_buttons = () => {\n for (const button of this.buttonElements) {\n button.setAttribute(\"disabled\", \"disabled\");\n }\n };\n\n private enable_buttons_without_delay = () => {\n for (const button of this.buttonElements) {\n button.removeAttribute(\"disabled\");\n }\n };\n\n private enable_buttons_with_delay = (delay: number) => {\n this.jsPsych.pluginAPI.setTimeout(this.enable_buttons_without_delay, delay);\n };\n\n private enable_buttons() {\n if (this.params.enable_button_after > 0) {\n this.enable_buttons_with_delay(this.params.enable_button_after);\n } else {\n this.enable_buttons_without_delay();\n }\n }\n\n // function to handle responses by the subject\n private after_response = (choice) => {\n // measure rt\n var endTime = performance.now();\n var rt = Math.round(endTime - this.startTime);\n if (this.context !== null) {\n endTime = this.context.currentTime;\n rt = Math.round((endTime - this.startTime) * 1000);\n }\n this.response.button = parseInt(choice);\n this.response.rt = rt;\n\n // disable all the buttons after a response\n this.disable_buttons();\n\n if (this.params.response_ends_trial) {\n this.end_trial();\n }\n };\n\n // method to end trial when it is time\n private end_trial = () => {\n // stop the audio file if it is playing\n this.audio.stop();\n\n // remove end event listeners if they exist\n this.audio.removeEventListener(\"ended\", this.end_trial);\n this.audio.removeEventListener(\"ended\", this.enable_buttons);\n\n // gather the data to store for the trial\n var trial_data = {\n rt: this.response.rt,\n stimulus: this.params.stimulus,\n response: this.response.button,\n };\n\n // move on to the next trial\n this.trial_complete(trial_data);\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 this.simulate_data_only(trial, simulation_options);\n }\n if (simulation_mode == \"visual\") {\n this.simulate_visual(trial, simulation_options, load_callback);\n }\n }\n\n private create_simulation_data(trial: TrialType<Info>, simulation_options) {\n const default_data = {\n stimulus: trial.stimulus,\n rt:\n this.jsPsych.randomization.sampleExGaussian(500, 50, 1 / 150, true) +\n trial.enable_button_after,\n response: this.jsPsych.randomization.randomInt(0, trial.choices.length - 1),\n };\n\n const data = this.jsPsych.pluginAPI.mergeSimulationData(default_data, simulation_options);\n\n this.jsPsych.pluginAPI.ensureSimulationDataConsistency(trial, data);\n\n return data;\n }\n\n private simulate_data_only(trial: TrialType<Info>, simulation_options) {\n const data = this.create_simulation_data(trial, simulation_options);\n\n this.jsPsych.finishTrial(data);\n }\n\n private simulate_visual(trial: TrialType<Info>, simulation_options, load_callback: () => void) {\n const data = this.create_simulation_data(trial, simulation_options);\n\n const display_element = this.jsPsych.getDisplayElement();\n\n const respond = () => {\n if (data.rt !== null) {\n this.jsPsych.pluginAPI.clickTarget(\n display_element.querySelector(\n `#jspsych-audio-button-response-btngroup [data-choice=\"${data.response}\"]`\n ),\n data.rt\n );\n }\n };\n\n this.trial(display_element, trial, () => {\n load_callback();\n if (!trial.response_allowed_while_playing) {\n this.audio.addEventListener(\"ended\", respond);\n } else {\n respond();\n }\n });\n }\n}\n\nexport default AudioButtonResponsePlugin;\n"],"names":["version","ParameterType"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs","sources":["../package.json","../src/index.ts"],"sourcesContent":["{\n \"name\": \"@jspsych/plugin-audio-button-response\",\n \"version\": \"2.1.0\",\n \"description\": \"jsPsych plugin for playing an audio file and getting a button 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-button-response\"\n },\n \"author\": \"Kristin Diep\",\n \"license\": \"MIT\",\n \"bugs\": {\n \"url\": \"https://github.com/jspsych/jsPsych/issues\"\n },\n \"homepage\": \"https://www.jspsych.org/latest/plugins/audio-button-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-button-response\",\n version: version,\n parameters: {\n /** Path to audio file to be played. */\n stimulus: {\n type: ParameterType.AUDIO,\n default: undefined,\n },\n /** Labels for the buttons. Each different string in the array will generate a different button. */\n choices: {\n type: ParameterType.STRING,\n default: undefined,\n array: true,\n },\n /**\n * A function that generates the HTML for each button in the `choices` array. The function gets the string\n * and index of the item in the `choices` array and should return valid HTML. If you want to use different\n * markup for each button, you can do that by using a conditional on either parameter. The default parameter\n * returns a button element with the text label of the choice.\n */\n button_html: {\n type: ParameterType.FUNCTION,\n default: function (choice: string, choice_index: number) {\n return `<button class=\"jspsych-btn\">${choice}</button>`;\n },\n },\n /** This string can contain HTML markup. Any content here will be displayed below the stimulus. The intention\n * is that it can be used to provide a reminder about the action the participant is supposed to take\n * (e.g., which key to press). */\n prompt: {\n type: ParameterType.HTML_STRING,\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, the trial\n * will wait for a response indefinitely */\n trial_duration: {\n type: ParameterType.INT,\n default: null,\n },\n /** Setting to `'grid'` will make the container element have the CSS property `display: grid` and enable the\n * use of `grid_rows` and `grid_columns`. Setting to `'flex'` will make the container element have the CSS\n * property `display: flex`. You can customize how the buttons are laid out by adding inline CSS in the `button_html` parameter.\n */\n button_layout: {\n type: ParameterType.STRING,\n default: \"grid\",\n },\n /** The number of rows in the button grid. Only applicable when `button_layout` is set to `'grid'`. If null, the\n * number of rows will be determined automatically based on the number of buttons and the number of columns.\n */\n grid_rows: {\n type: ParameterType.INT,\n default: 1,\n },\n /** The number of columns in the button grid. Only applicable when `button_layout` is set to `'grid'`.\n * If null, the number of columns will be determined automatically based on the number of buttons and the\n * number of rows.\n */\n grid_columns: {\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 set this parameter to `false` to force\n * the participant to listen to the stimulus for a fixed amount of time, even if they respond before the time is complete. */\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 default: false,\n },\n /**\n * If true, then responses are allowed while the audio is playing. If false, then the audio must finish\n * playing before the button choices are enabled and a response is accepted. Once the audio has played\n * all the way through, the buttons are enabled and a response is allowed (including while the audio is\n * being re-played via on-screen playback controls).\n */\n response_allowed_while_playing: {\n type: ParameterType.BOOL,\n default: true,\n },\n /** How long the button will delay enabling in milliseconds. If `response_allowed_while_playing` is `true`,\n * the timer will start immediately. If it is `false`, the timer will start at the end of the audio. */\n enable_button_after: {\n type: ParameterType.INT,\n default: 0,\n },\n },\n data: {\n /** The path of the audio file that was played. */\n stimulus: {\n type: ParameterType.STRING,\n },\n /** The response time in milliseconds for the participant to make a response. The time is measured from\n * when the stimulus first began playing until the participant's response.*/\n rt: {\n type: ParameterType.INT,\n },\n /** Indicates which button the participant pressed. The first button in the `choices` array is 0, the second is 1, and so on. */\n response: {\n type: ParameterType.INT,\n },\n },\n // prettier-ignore\n citations: '__CITATIONS__',\n};\n\ntype Info = typeof info;\n\n/**\n * If the browser supports it, audio files are played using the WebAudio API. This allows for reasonably precise \n * timing of the playback. The timing of responses generated is measured against the WebAudio specific clock, \n * improving the measurement of response times. If the browser does not support the WebAudio API, then the audio file is \n * played with HTML5 audio. \n\n * Audio files can be automatically preloaded by jsPsych using the [`preload` plugin](preload.md). However, if \n * you are using timeline variables or another dynamic method to specify the audio stimulus, you will need \n * 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 \n * has failed to respond within a fixed length of time. You can also prevent a button response from being made before the \n * audio has finished playing.\n * \n * @author Kristin Diep\n * @see {@link https://www.jspsych.org/latest/plugins/audio-button-response/ audio-button-response plugin documentation on jspsych.org}\n */\nclass AudioButtonResponsePlugin implements JsPsychPlugin<Info> {\n static info = info;\n private audio: AudioPlayerInterface;\n private params: TrialType<Info>;\n private buttonElements: HTMLElement[] = [];\n private display: HTMLElement;\n private response: { rt: number; button: number } = { rt: null, button: null };\n private context: AudioContext;\n private startTime: number;\n private trial_complete: (trial_data: { rt: number; stimulus: string; response: number }) => void;\n\n constructor(private jsPsych: JsPsych) {\n autoBind(this);\n }\n\n async trial(display_element: HTMLElement, trial: TrialType<Info>, on_load: () => void) {\n this.params = trial;\n this.display = display_element;\n // setup stimulus\n this.context = this.jsPsych.pluginAPI.audioContext();\n\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 // enable buttons after audio ends if necessary\n if (!trial.response_allowed_while_playing && !trial.trial_ends_after_audio) {\n this.audio.addEventListener(\"ended\", this.enable_buttons);\n }\n\n // Display buttons\n const buttonGroupElement = document.createElement(\"div\");\n buttonGroupElement.id = \"jspsych-audio-button-response-btngroup\";\n if (trial.button_layout === \"grid\") {\n buttonGroupElement.classList.add(\"jspsych-btn-group-grid\");\n if (trial.grid_rows === null && trial.grid_columns === null) {\n throw new Error(\n \"You cannot set `grid_rows` to `null` without providing a value for `grid_columns`.\"\n );\n }\n const n_cols =\n trial.grid_columns === null\n ? Math.ceil(trial.choices.length / trial.grid_rows)\n : trial.grid_columns;\n const n_rows =\n trial.grid_rows === null\n ? Math.ceil(trial.choices.length / trial.grid_columns)\n : trial.grid_rows;\n buttonGroupElement.style.gridTemplateColumns = `repeat(${n_cols}, 1fr)`;\n buttonGroupElement.style.gridTemplateRows = `repeat(${n_rows}, 1fr)`;\n } else if (trial.button_layout === \"flex\") {\n buttonGroupElement.classList.add(\"jspsych-btn-group-flex\");\n }\n\n for (const [choiceIndex, choice] of trial.choices.entries()) {\n buttonGroupElement.insertAdjacentHTML(\"beforeend\", trial.button_html(choice, choiceIndex));\n const buttonElement = buttonGroupElement.lastChild as HTMLElement;\n buttonElement.dataset.choice = choiceIndex.toString();\n buttonElement.addEventListener(\"click\", () => {\n this.after_response(choiceIndex);\n });\n this.buttonElements.push(buttonElement);\n }\n\n display_element.appendChild(buttonGroupElement);\n\n // Show prompt if there is one\n if (trial.prompt !== null) {\n display_element.insertAdjacentHTML(\"beforeend\", trial.prompt);\n }\n\n if (trial.response_allowed_while_playing) {\n if (trial.enable_button_after > 0) {\n this.disable_buttons();\n this.enable_buttons();\n }\n } else {\n this.disable_buttons();\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 on_load();\n\n // start time\n this.startTime = performance.now();\n if (this.context !== null) {\n this.startTime = this.context.currentTime;\n }\n\n // start audio\n this.audio.play();\n\n return new Promise((resolve) => {\n // hold the .resolve() function from the Promise that ends the trial\n this.trial_complete = resolve;\n });\n }\n\n private disable_buttons = () => {\n for (const button of this.buttonElements) {\n button.setAttribute(\"disabled\", \"disabled\");\n }\n };\n\n private enable_buttons_without_delay = () => {\n for (const button of this.buttonElements) {\n button.removeAttribute(\"disabled\");\n }\n };\n\n private enable_buttons_with_delay = (delay: number) => {\n this.jsPsych.pluginAPI.setTimeout(this.enable_buttons_without_delay, delay);\n };\n\n private enable_buttons() {\n if (this.params.enable_button_after > 0) {\n this.enable_buttons_with_delay(this.params.enable_button_after);\n } else {\n this.enable_buttons_without_delay();\n }\n }\n\n // function to handle responses by the subject\n private after_response = (choice) => {\n // measure rt\n var endTime = performance.now();\n var rt = Math.round(endTime - this.startTime);\n if (this.context !== null) {\n endTime = this.context.currentTime;\n rt = Math.round((endTime - this.startTime) * 1000);\n }\n this.response.button = parseInt(choice);\n this.response.rt = rt;\n\n // disable all the buttons after a response\n this.disable_buttons();\n\n if (this.params.response_ends_trial) {\n this.end_trial();\n }\n };\n\n // method to end trial when it is time\n private end_trial = () => {\n // stop the audio file if it is playing\n this.audio.stop();\n\n // remove end event listeners if they exist\n this.audio.removeEventListener(\"ended\", this.end_trial);\n this.audio.removeEventListener(\"ended\", this.enable_buttons);\n\n // gather the data to store for the trial\n var trial_data = {\n rt: this.response.rt,\n stimulus: this.params.stimulus,\n response: this.response.button,\n };\n\n // move on to the next trial\n this.trial_complete(trial_data);\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 this.simulate_data_only(trial, simulation_options);\n }\n if (simulation_mode == \"visual\") {\n this.simulate_visual(trial, simulation_options, load_callback);\n }\n }\n\n private create_simulation_data(trial: TrialType<Info>, simulation_options) {\n const default_data = {\n stimulus: trial.stimulus,\n rt:\n this.jsPsych.randomization.sampleExGaussian(500, 50, 1 / 150, true) +\n trial.enable_button_after,\n response: this.jsPsych.randomization.randomInt(0, trial.choices.length - 1),\n };\n\n const data = this.jsPsych.pluginAPI.mergeSimulationData(default_data, simulation_options);\n\n this.jsPsych.pluginAPI.ensureSimulationDataConsistency(trial, data);\n\n return data;\n }\n\n private simulate_data_only(trial: TrialType<Info>, simulation_options) {\n const data = this.create_simulation_data(trial, simulation_options);\n\n this.jsPsych.finishTrial(data);\n }\n\n private simulate_visual(trial: TrialType<Info>, simulation_options, load_callback: () => void) {\n const data = this.create_simulation_data(trial, simulation_options);\n\n const display_element = this.jsPsych.getDisplayElement();\n\n const respond = () => {\n if (data.rt !== null) {\n this.jsPsych.pluginAPI.clickTarget(\n display_element.querySelector(\n `#jspsych-audio-button-response-btngroup [data-choice=\"${data.response}\"]`\n ),\n data.rt\n );\n }\n };\n\n this.trial(display_element, trial, () => {\n load_callback();\n if (!trial.response_allowed_while_playing) {\n this.audio.addEventListener(\"ended\", respond);\n } else {\n respond();\n }\n });\n }\n}\n\nexport default AudioButtonResponsePlugin;\n"],"names":[],"mappings":";;;;;AAEE,IAAW,OAAA,GAAA,OAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ECmHA,SAAA,EAAA;AAAA;;GAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
package/dist/index.d.ts
CHANGED
|
@@ -94,6 +94,10 @@ declare const info: {
|
|
|
94
94
|
};
|
|
95
95
|
};
|
|
96
96
|
readonly data: {
|
|
97
|
+
/** The path of the audio file that was played. */
|
|
98
|
+
readonly stimulus: {
|
|
99
|
+
readonly type: ParameterType.STRING;
|
|
100
|
+
};
|
|
97
101
|
/** The response time in milliseconds for the participant to make a response. The time is measured from
|
|
98
102
|
* when the stimulus first began playing until the participant's response.*/
|
|
99
103
|
readonly rt: {
|
|
@@ -104,6 +108,7 @@ declare const info: {
|
|
|
104
108
|
readonly type: ParameterType.INT;
|
|
105
109
|
};
|
|
106
110
|
};
|
|
111
|
+
readonly citations: "__CITATIONS__";
|
|
107
112
|
};
|
|
108
113
|
type Info = typeof info;
|
|
109
114
|
/**
|
|
@@ -219,6 +224,10 @@ declare class AudioButtonResponsePlugin implements JsPsychPlugin<Info> {
|
|
|
219
224
|
};
|
|
220
225
|
};
|
|
221
226
|
readonly data: {
|
|
227
|
+
/** The path of the audio file that was played. */
|
|
228
|
+
readonly stimulus: {
|
|
229
|
+
readonly type: ParameterType.STRING;
|
|
230
|
+
};
|
|
222
231
|
/** The response time in milliseconds for the participant to make a response. The time is measured from
|
|
223
232
|
* when the stimulus first began playing until the participant's response.*/
|
|
224
233
|
readonly rt: {
|
|
@@ -229,6 +238,7 @@ declare class AudioButtonResponsePlugin implements JsPsychPlugin<Info> {
|
|
|
229
238
|
readonly type: ParameterType.INT;
|
|
230
239
|
};
|
|
231
240
|
};
|
|
241
|
+
readonly citations: "__CITATIONS__";
|
|
232
242
|
};
|
|
233
243
|
private audio;
|
|
234
244
|
private params;
|
package/dist/index.js
CHANGED
|
@@ -1,113 +1,122 @@
|
|
|
1
1
|
import autoBind from 'auto-bind';
|
|
2
2
|
import { ParameterType } from 'jspsych';
|
|
3
3
|
|
|
4
|
-
var
|
|
5
|
-
name: "@jspsych/plugin-audio-button-response",
|
|
6
|
-
version: "2.0.1",
|
|
7
|
-
description: "jsPsych plugin for playing an audio file and getting a button response",
|
|
8
|
-
type: "module",
|
|
9
|
-
main: "dist/index.cjs",
|
|
10
|
-
exports: {
|
|
11
|
-
import: "./dist/index.js",
|
|
12
|
-
require: "./dist/index.cjs"
|
|
13
|
-
},
|
|
14
|
-
typings: "dist/index.d.ts",
|
|
15
|
-
unpkg: "dist/index.browser.min.js",
|
|
16
|
-
files: [
|
|
17
|
-
"src",
|
|
18
|
-
"dist"
|
|
19
|
-
],
|
|
20
|
-
source: "src/index.ts",
|
|
21
|
-
scripts: {
|
|
22
|
-
test: "jest",
|
|
23
|
-
"test:watch": "npm test -- --watch",
|
|
24
|
-
tsc: "tsc",
|
|
25
|
-
build: "rollup --config",
|
|
26
|
-
"build:watch": "npm run build -- --watch"
|
|
27
|
-
},
|
|
28
|
-
repository: {
|
|
29
|
-
type: "git",
|
|
30
|
-
url: "git+https://github.com/jspsych/jsPsych.git",
|
|
31
|
-
directory: "packages/plugin-audio-button-response"
|
|
32
|
-
},
|
|
33
|
-
author: "Kristin Diep",
|
|
34
|
-
license: "MIT",
|
|
35
|
-
bugs: {
|
|
36
|
-
url: "https://github.com/jspsych/jsPsych/issues"
|
|
37
|
-
},
|
|
38
|
-
homepage: "https://www.jspsych.org/latest/plugins/audio-button-response",
|
|
39
|
-
peerDependencies: {
|
|
40
|
-
jspsych: ">=7.1.0"
|
|
41
|
-
},
|
|
42
|
-
devDependencies: {
|
|
43
|
-
"@jspsych/config": "^3.0.0",
|
|
44
|
-
"@jspsych/test-utils": "^1.2.0"
|
|
45
|
-
}
|
|
46
|
-
};
|
|
4
|
+
var version = "2.1.0";
|
|
47
5
|
|
|
48
6
|
const info = {
|
|
49
7
|
name: "audio-button-response",
|
|
50
|
-
version
|
|
8
|
+
version,
|
|
51
9
|
parameters: {
|
|
10
|
+
/** Path to audio file to be played. */
|
|
52
11
|
stimulus: {
|
|
53
12
|
type: ParameterType.AUDIO,
|
|
54
13
|
default: void 0
|
|
55
14
|
},
|
|
15
|
+
/** Labels for the buttons. Each different string in the array will generate a different button. */
|
|
56
16
|
choices: {
|
|
57
17
|
type: ParameterType.STRING,
|
|
58
18
|
default: void 0,
|
|
59
19
|
array: true
|
|
60
20
|
},
|
|
21
|
+
/**
|
|
22
|
+
* A function that generates the HTML for each button in the `choices` array. The function gets the string
|
|
23
|
+
* and index of the item in the `choices` array and should return valid HTML. If you want to use different
|
|
24
|
+
* markup for each button, you can do that by using a conditional on either parameter. The default parameter
|
|
25
|
+
* returns a button element with the text label of the choice.
|
|
26
|
+
*/
|
|
61
27
|
button_html: {
|
|
62
28
|
type: ParameterType.FUNCTION,
|
|
63
29
|
default: function(choice, choice_index) {
|
|
64
30
|
return `<button class="jspsych-btn">${choice}</button>`;
|
|
65
31
|
}
|
|
66
32
|
},
|
|
33
|
+
/** This string can contain HTML markup. Any content here will be displayed below the stimulus. The intention
|
|
34
|
+
* is that it can be used to provide a reminder about the action the participant is supposed to take
|
|
35
|
+
* (e.g., which key to press). */
|
|
67
36
|
prompt: {
|
|
68
37
|
type: ParameterType.HTML_STRING,
|
|
69
38
|
default: null
|
|
70
39
|
},
|
|
40
|
+
/** How long to wait for the participant to make a response before ending the trial in milliseconds. If the
|
|
41
|
+
* participant fails to make a response before this timer is reached, the participant's response will be
|
|
42
|
+
* recorded as null for the trial and the trial will end. If the value of this parameter is null, the trial
|
|
43
|
+
* will wait for a response indefinitely */
|
|
71
44
|
trial_duration: {
|
|
72
45
|
type: ParameterType.INT,
|
|
73
46
|
default: null
|
|
74
47
|
},
|
|
48
|
+
/** Setting to `'grid'` will make the container element have the CSS property `display: grid` and enable the
|
|
49
|
+
* use of `grid_rows` and `grid_columns`. Setting to `'flex'` will make the container element have the CSS
|
|
50
|
+
* property `display: flex`. You can customize how the buttons are laid out by adding inline CSS in the `button_html` parameter.
|
|
51
|
+
*/
|
|
75
52
|
button_layout: {
|
|
76
53
|
type: ParameterType.STRING,
|
|
77
54
|
default: "grid"
|
|
78
55
|
},
|
|
56
|
+
/** The number of rows in the button grid. Only applicable when `button_layout` is set to `'grid'`. If null, the
|
|
57
|
+
* number of rows will be determined automatically based on the number of buttons and the number of columns.
|
|
58
|
+
*/
|
|
79
59
|
grid_rows: {
|
|
80
60
|
type: ParameterType.INT,
|
|
81
61
|
default: 1
|
|
82
62
|
},
|
|
63
|
+
/** The number of columns in the button grid. Only applicable when `button_layout` is set to `'grid'`.
|
|
64
|
+
* If null, the number of columns will be determined automatically based on the number of buttons and the
|
|
65
|
+
* number of rows.
|
|
66
|
+
*/
|
|
83
67
|
grid_columns: {
|
|
84
68
|
type: ParameterType.INT,
|
|
85
69
|
default: null
|
|
86
70
|
},
|
|
71
|
+
/** If true, then the trial will end whenever the participant makes a response (assuming they make their
|
|
72
|
+
* response before the cutoff specified by the `trial_duration` parameter). If false, then the trial will
|
|
73
|
+
* continue until the value for `trial_duration` is reached. You can set this parameter to `false` to force
|
|
74
|
+
* the participant to listen to the stimulus for a fixed amount of time, even if they respond before the time is complete. */
|
|
87
75
|
response_ends_trial: {
|
|
88
76
|
type: ParameterType.BOOL,
|
|
89
77
|
default: true
|
|
90
78
|
},
|
|
79
|
+
/** If true, then the trial will end as soon as the audio file finishes playing. */
|
|
91
80
|
trial_ends_after_audio: {
|
|
92
81
|
type: ParameterType.BOOL,
|
|
93
82
|
default: false
|
|
94
83
|
},
|
|
84
|
+
/**
|
|
85
|
+
* If true, then responses are allowed while the audio is playing. If false, then the audio must finish
|
|
86
|
+
* playing before the button choices are enabled and a response is accepted. Once the audio has played
|
|
87
|
+
* all the way through, the buttons are enabled and a response is allowed (including while the audio is
|
|
88
|
+
* being re-played via on-screen playback controls).
|
|
89
|
+
*/
|
|
95
90
|
response_allowed_while_playing: {
|
|
96
91
|
type: ParameterType.BOOL,
|
|
97
92
|
default: true
|
|
98
93
|
},
|
|
94
|
+
/** How long the button will delay enabling in milliseconds. If `response_allowed_while_playing` is `true`,
|
|
95
|
+
* the timer will start immediately. If it is `false`, the timer will start at the end of the audio. */
|
|
99
96
|
enable_button_after: {
|
|
100
97
|
type: ParameterType.INT,
|
|
101
98
|
default: 0
|
|
102
99
|
}
|
|
103
100
|
},
|
|
104
101
|
data: {
|
|
102
|
+
/** The path of the audio file that was played. */
|
|
103
|
+
stimulus: {
|
|
104
|
+
type: ParameterType.STRING
|
|
105
|
+
},
|
|
106
|
+
/** The response time in milliseconds for the participant to make a response. The time is measured from
|
|
107
|
+
* when the stimulus first began playing until the participant's response.*/
|
|
105
108
|
rt: {
|
|
106
109
|
type: ParameterType.INT
|
|
107
110
|
},
|
|
111
|
+
/** Indicates which button the participant pressed. The first button in the `choices` array is 0, the second is 1, and so on. */
|
|
108
112
|
response: {
|
|
109
113
|
type: ParameterType.INT
|
|
110
114
|
}
|
|
115
|
+
},
|
|
116
|
+
// prettier-ignore
|
|
117
|
+
citations: {
|
|
118
|
+
"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 ",
|
|
119
|
+
"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}, } '
|
|
111
120
|
}
|
|
112
121
|
};
|
|
113
122
|
class AudioButtonResponsePlugin {
|
|
@@ -128,6 +137,7 @@ class AudioButtonResponsePlugin {
|
|
|
128
137
|
this.enable_buttons_with_delay = (delay) => {
|
|
129
138
|
this.jsPsych.pluginAPI.setTimeout(this.enable_buttons_without_delay, delay);
|
|
130
139
|
};
|
|
140
|
+
// function to handle responses by the subject
|
|
131
141
|
this.after_response = (choice) => {
|
|
132
142
|
var endTime = performance.now();
|
|
133
143
|
var rt = Math.round(endTime - this.startTime);
|
|
@@ -142,6 +152,7 @@ class AudioButtonResponsePlugin {
|
|
|
142
152
|
this.end_trial();
|
|
143
153
|
}
|
|
144
154
|
};
|
|
155
|
+
// method to end trial when it is time
|
|
145
156
|
this.end_trial = () => {
|
|
146
157
|
this.audio.stop();
|
|
147
158
|
this.audio.removeEventListener("ended", this.end_trial);
|
|
@@ -155,6 +166,9 @@ class AudioButtonResponsePlugin {
|
|
|
155
166
|
};
|
|
156
167
|
autoBind(this);
|
|
157
168
|
}
|
|
169
|
+
static {
|
|
170
|
+
this.info = info;
|
|
171
|
+
}
|
|
158
172
|
async trial(display_element, trial, on_load) {
|
|
159
173
|
this.params = trial;
|
|
160
174
|
this.display = display_element;
|
|
@@ -271,7 +285,6 @@ class AudioButtonResponsePlugin {
|
|
|
271
285
|
});
|
|
272
286
|
}
|
|
273
287
|
}
|
|
274
|
-
AudioButtonResponsePlugin.info = info;
|
|
275
288
|
|
|
276
289
|
export { AudioButtonResponsePlugin as default };
|
|
277
290
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../src/index.ts"],"sourcesContent":["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-button-response\",\n version: version,\n parameters: {\n /** Path to audio file to be played. */\n stimulus: {\n type: ParameterType.AUDIO,\n default: undefined,\n },\n /** Labels for the buttons. Each different string in the array will generate a different button. */\n choices: {\n type: ParameterType.STRING,\n default: undefined,\n array: true,\n },\n /**\n * A function that generates the HTML for each button in the `choices` array. The function gets the string\n * and index of the item in the `choices` array and should return valid HTML. If you want to use different\n * markup for each button, you can do that by using a conditional on either parameter. The default parameter\n * returns a button element with the text label of the choice.\n */\n button_html: {\n type: ParameterType.FUNCTION,\n default: function (choice: string, choice_index: number) {\n return `<button class=\"jspsych-btn\">${choice}</button>`;\n },\n },\n /** This string can contain HTML markup. Any content here will be displayed below the stimulus. The intention\n * is that it can be used to provide a reminder about the action the participant is supposed to take\n * (e.g., which key to press). */\n prompt: {\n type: ParameterType.HTML_STRING,\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, the trial\n * will wait for a response indefinitely */\n trial_duration: {\n type: ParameterType.INT,\n default: null,\n },\n /** Setting to `'grid'` will make the container element have the CSS property `display: grid` and enable the\n * use of `grid_rows` and `grid_columns`. Setting to `'flex'` will make the container element have the CSS\n * property `display: flex`. You can customize how the buttons are laid out by adding inline CSS in the `button_html` parameter.\n */\n button_layout: {\n type: ParameterType.STRING,\n default: \"grid\",\n },\n /** The number of rows in the button grid. Only applicable when `button_layout` is set to `'grid'`. If null, the\n * number of rows will be determined automatically based on the number of buttons and the number of columns.\n */\n grid_rows: {\n type: ParameterType.INT,\n default: 1,\n },\n /** The number of columns in the button grid. Only applicable when `button_layout` is set to `'grid'`.\n * If null, the number of columns will be determined automatically based on the number of buttons and the\n * number of rows.\n */\n grid_columns: {\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 set this parameter to `false` to force\n * the participant to listen to the stimulus for a fixed amount of time, even if they respond before the time is complete. */\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 default: false,\n },\n /**\n * If true, then responses are allowed while the audio is playing. If false, then the audio must finish\n * playing before the button choices are enabled and a response is accepted. Once the audio has played\n * all the way through, the buttons are enabled and a response is allowed (including while the audio is\n * being re-played via on-screen playback controls).\n */\n response_allowed_while_playing: {\n type: ParameterType.BOOL,\n default: true,\n },\n /** How long the button will delay enabling in milliseconds. If `response_allowed_while_playing` is `true`,\n * the timer will start immediately. If it is `false`, the timer will start at the end of the audio. */\n enable_button_after: {\n type: ParameterType.INT,\n default: 0,\n },\n },\n data: {\n /** The response time in milliseconds for the participant to make a response. The time is measured from\n * when the stimulus first began playing until the participant's response.*/\n rt: {\n type: ParameterType.INT,\n },\n /** Indicates which button the participant pressed. The first button in the `choices` array is 0, the second is 1, and so on. */\n response: {\n type: ParameterType.INT,\n },\n },\n};\n\ntype Info = typeof info;\n\n/**\n * If the browser supports it, audio files are played using the WebAudio API. This allows for reasonably precise \n * timing of the playback. The timing of responses generated is measured against the WebAudio specific clock, \n * improving the measurement of response times. If the browser does not support the WebAudio API, then the audio file is \n * played with HTML5 audio. \n\n * Audio files can be automatically preloaded by jsPsych using the [`preload` plugin](preload.md). However, if \n * you are using timeline variables or another dynamic method to specify the audio stimulus, you will need \n * 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 \n * has failed to respond within a fixed length of time. You can also prevent a button response from being made before the \n * audio has finished playing.\n * \n * @author Kristin Diep\n * @see {@link https://www.jspsych.org/latest/plugins/audio-button-response/ audio-button-response plugin documentation on jspsych.org}\n */\nclass AudioButtonResponsePlugin implements JsPsychPlugin<Info> {\n static info = info;\n private audio: AudioPlayerInterface;\n private params: TrialType<Info>;\n private buttonElements: HTMLElement[] = [];\n private display: HTMLElement;\n private response: { rt: number; button: number } = { rt: null, button: null };\n private context: AudioContext;\n private startTime: number;\n private trial_complete: (trial_data: { rt: number; stimulus: string; response: number }) => void;\n\n constructor(private jsPsych: JsPsych) {\n autoBind(this);\n }\n\n async trial(display_element: HTMLElement, trial: TrialType<Info>, on_load: () => void) {\n this.params = trial;\n this.display = display_element;\n // setup stimulus\n this.context = this.jsPsych.pluginAPI.audioContext();\n\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 // enable buttons after audio ends if necessary\n if (!trial.response_allowed_while_playing && !trial.trial_ends_after_audio) {\n this.audio.addEventListener(\"ended\", this.enable_buttons);\n }\n\n // Display buttons\n const buttonGroupElement = document.createElement(\"div\");\n buttonGroupElement.id = \"jspsych-audio-button-response-btngroup\";\n if (trial.button_layout === \"grid\") {\n buttonGroupElement.classList.add(\"jspsych-btn-group-grid\");\n if (trial.grid_rows === null && trial.grid_columns === null) {\n throw new Error(\n \"You cannot set `grid_rows` to `null` without providing a value for `grid_columns`.\"\n );\n }\n const n_cols =\n trial.grid_columns === null\n ? Math.ceil(trial.choices.length / trial.grid_rows)\n : trial.grid_columns;\n const n_rows =\n trial.grid_rows === null\n ? Math.ceil(trial.choices.length / trial.grid_columns)\n : trial.grid_rows;\n buttonGroupElement.style.gridTemplateColumns = `repeat(${n_cols}, 1fr)`;\n buttonGroupElement.style.gridTemplateRows = `repeat(${n_rows}, 1fr)`;\n } else if (trial.button_layout === \"flex\") {\n buttonGroupElement.classList.add(\"jspsych-btn-group-flex\");\n }\n\n for (const [choiceIndex, choice] of trial.choices.entries()) {\n buttonGroupElement.insertAdjacentHTML(\"beforeend\", trial.button_html(choice, choiceIndex));\n const buttonElement = buttonGroupElement.lastChild as HTMLElement;\n buttonElement.dataset.choice = choiceIndex.toString();\n buttonElement.addEventListener(\"click\", () => {\n this.after_response(choiceIndex);\n });\n this.buttonElements.push(buttonElement);\n }\n\n display_element.appendChild(buttonGroupElement);\n\n // Show prompt if there is one\n if (trial.prompt !== null) {\n display_element.insertAdjacentHTML(\"beforeend\", trial.prompt);\n }\n\n if (trial.response_allowed_while_playing) {\n if (trial.enable_button_after > 0) {\n this.disable_buttons();\n this.enable_buttons();\n }\n } else {\n this.disable_buttons();\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 on_load();\n\n // start time\n this.startTime = performance.now();\n if (this.context !== null) {\n this.startTime = this.context.currentTime;\n }\n\n // start audio\n this.audio.play();\n\n return new Promise((resolve) => {\n // hold the .resolve() function from the Promise that ends the trial\n this.trial_complete = resolve;\n });\n }\n\n private disable_buttons = () => {\n for (const button of this.buttonElements) {\n button.setAttribute(\"disabled\", \"disabled\");\n }\n };\n\n private enable_buttons_without_delay = () => {\n for (const button of this.buttonElements) {\n button.removeAttribute(\"disabled\");\n }\n };\n\n private enable_buttons_with_delay = (delay: number) => {\n this.jsPsych.pluginAPI.setTimeout(this.enable_buttons_without_delay, delay);\n };\n\n private enable_buttons() {\n if (this.params.enable_button_after > 0) {\n this.enable_buttons_with_delay(this.params.enable_button_after);\n } else {\n this.enable_buttons_without_delay();\n }\n }\n\n // function to handle responses by the subject\n private after_response = (choice) => {\n // measure rt\n var endTime = performance.now();\n var rt = Math.round(endTime - this.startTime);\n if (this.context !== null) {\n endTime = this.context.currentTime;\n rt = Math.round((endTime - this.startTime) * 1000);\n }\n this.response.button = parseInt(choice);\n this.response.rt = rt;\n\n // disable all the buttons after a response\n this.disable_buttons();\n\n if (this.params.response_ends_trial) {\n this.end_trial();\n }\n };\n\n // method to end trial when it is time\n private end_trial = () => {\n // stop the audio file if it is playing\n this.audio.stop();\n\n // remove end event listeners if they exist\n this.audio.removeEventListener(\"ended\", this.end_trial);\n this.audio.removeEventListener(\"ended\", this.enable_buttons);\n\n // gather the data to store for the trial\n var trial_data = {\n rt: this.response.rt,\n stimulus: this.params.stimulus,\n response: this.response.button,\n };\n\n // move on to the next trial\n this.trial_complete(trial_data);\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 this.simulate_data_only(trial, simulation_options);\n }\n if (simulation_mode == \"visual\") {\n this.simulate_visual(trial, simulation_options, load_callback);\n }\n }\n\n private create_simulation_data(trial: TrialType<Info>, simulation_options) {\n const default_data = {\n stimulus: trial.stimulus,\n rt:\n this.jsPsych.randomization.sampleExGaussian(500, 50, 1 / 150, true) +\n trial.enable_button_after,\n response: this.jsPsych.randomization.randomInt(0, trial.choices.length - 1),\n };\n\n const data = this.jsPsych.pluginAPI.mergeSimulationData(default_data, simulation_options);\n\n this.jsPsych.pluginAPI.ensureSimulationDataConsistency(trial, data);\n\n return data;\n }\n\n private simulate_data_only(trial: TrialType<Info>, simulation_options) {\n const data = this.create_simulation_data(trial, simulation_options);\n\n this.jsPsych.finishTrial(data);\n }\n\n private simulate_visual(trial: TrialType<Info>, simulation_options, load_callback: () => void) {\n const data = this.create_simulation_data(trial, simulation_options);\n\n const display_element = this.jsPsych.getDisplayElement();\n\n const respond = () => {\n if (data.rt !== null) {\n this.jsPsych.pluginAPI.clickTarget(\n display_element.querySelector(\n `#jspsych-audio-button-response-btngroup [data-choice=\"${data.response}\"]`\n ),\n data.rt\n );\n }\n };\n\n this.trial(display_element, trial, () => {\n load_callback();\n if (!trial.response_allowed_while_playing) {\n this.audio.addEventListener(\"ended\", respond);\n } else {\n respond();\n }\n });\n }\n}\n\nexport default AudioButtonResponsePlugin;\n"],"names":["version"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../package.json","../src/index.ts"],"sourcesContent":["{\n \"name\": \"@jspsych/plugin-audio-button-response\",\n \"version\": \"2.1.0\",\n \"description\": \"jsPsych plugin for playing an audio file and getting a button 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-button-response\"\n },\n \"author\": \"Kristin Diep\",\n \"license\": \"MIT\",\n \"bugs\": {\n \"url\": \"https://github.com/jspsych/jsPsych/issues\"\n },\n \"homepage\": \"https://www.jspsych.org/latest/plugins/audio-button-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-button-response\",\n version: version,\n parameters: {\n /** Path to audio file to be played. */\n stimulus: {\n type: ParameterType.AUDIO,\n default: undefined,\n },\n /** Labels for the buttons. Each different string in the array will generate a different button. */\n choices: {\n type: ParameterType.STRING,\n default: undefined,\n array: true,\n },\n /**\n * A function that generates the HTML for each button in the `choices` array. The function gets the string\n * and index of the item in the `choices` array and should return valid HTML. If you want to use different\n * markup for each button, you can do that by using a conditional on either parameter. The default parameter\n * returns a button element with the text label of the choice.\n */\n button_html: {\n type: ParameterType.FUNCTION,\n default: function (choice: string, choice_index: number) {\n return `<button class=\"jspsych-btn\">${choice}</button>`;\n },\n },\n /** This string can contain HTML markup. Any content here will be displayed below the stimulus. The intention\n * is that it can be used to provide a reminder about the action the participant is supposed to take\n * (e.g., which key to press). */\n prompt: {\n type: ParameterType.HTML_STRING,\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, the trial\n * will wait for a response indefinitely */\n trial_duration: {\n type: ParameterType.INT,\n default: null,\n },\n /** Setting to `'grid'` will make the container element have the CSS property `display: grid` and enable the\n * use of `grid_rows` and `grid_columns`. Setting to `'flex'` will make the container element have the CSS\n * property `display: flex`. You can customize how the buttons are laid out by adding inline CSS in the `button_html` parameter.\n */\n button_layout: {\n type: ParameterType.STRING,\n default: \"grid\",\n },\n /** The number of rows in the button grid. Only applicable when `button_layout` is set to `'grid'`. If null, the\n * number of rows will be determined automatically based on the number of buttons and the number of columns.\n */\n grid_rows: {\n type: ParameterType.INT,\n default: 1,\n },\n /** The number of columns in the button grid. Only applicable when `button_layout` is set to `'grid'`.\n * If null, the number of columns will be determined automatically based on the number of buttons and the\n * number of rows.\n */\n grid_columns: {\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 set this parameter to `false` to force\n * the participant to listen to the stimulus for a fixed amount of time, even if they respond before the time is complete. */\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 default: false,\n },\n /**\n * If true, then responses are allowed while the audio is playing. If false, then the audio must finish\n * playing before the button choices are enabled and a response is accepted. Once the audio has played\n * all the way through, the buttons are enabled and a response is allowed (including while the audio is\n * being re-played via on-screen playback controls).\n */\n response_allowed_while_playing: {\n type: ParameterType.BOOL,\n default: true,\n },\n /** How long the button will delay enabling in milliseconds. If `response_allowed_while_playing` is `true`,\n * the timer will start immediately. If it is `false`, the timer will start at the end of the audio. */\n enable_button_after: {\n type: ParameterType.INT,\n default: 0,\n },\n },\n data: {\n /** The path of the audio file that was played. */\n stimulus: {\n type: ParameterType.STRING,\n },\n /** The response time in milliseconds for the participant to make a response. The time is measured from\n * when the stimulus first began playing until the participant's response.*/\n rt: {\n type: ParameterType.INT,\n },\n /** Indicates which button the participant pressed. The first button in the `choices` array is 0, the second is 1, and so on. */\n response: {\n type: ParameterType.INT,\n },\n },\n // prettier-ignore\n citations: '__CITATIONS__',\n};\n\ntype Info = typeof info;\n\n/**\n * If the browser supports it, audio files are played using the WebAudio API. This allows for reasonably precise \n * timing of the playback. The timing of responses generated is measured against the WebAudio specific clock, \n * improving the measurement of response times. If the browser does not support the WebAudio API, then the audio file is \n * played with HTML5 audio. \n\n * Audio files can be automatically preloaded by jsPsych using the [`preload` plugin](preload.md). However, if \n * you are using timeline variables or another dynamic method to specify the audio stimulus, you will need \n * 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 \n * has failed to respond within a fixed length of time. You can also prevent a button response from being made before the \n * audio has finished playing.\n * \n * @author Kristin Diep\n * @see {@link https://www.jspsych.org/latest/plugins/audio-button-response/ audio-button-response plugin documentation on jspsych.org}\n */\nclass AudioButtonResponsePlugin implements JsPsychPlugin<Info> {\n static info = info;\n private audio: AudioPlayerInterface;\n private params: TrialType<Info>;\n private buttonElements: HTMLElement[] = [];\n private display: HTMLElement;\n private response: { rt: number; button: number } = { rt: null, button: null };\n private context: AudioContext;\n private startTime: number;\n private trial_complete: (trial_data: { rt: number; stimulus: string; response: number }) => void;\n\n constructor(private jsPsych: JsPsych) {\n autoBind(this);\n }\n\n async trial(display_element: HTMLElement, trial: TrialType<Info>, on_load: () => void) {\n this.params = trial;\n this.display = display_element;\n // setup stimulus\n this.context = this.jsPsych.pluginAPI.audioContext();\n\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 // enable buttons after audio ends if necessary\n if (!trial.response_allowed_while_playing && !trial.trial_ends_after_audio) {\n this.audio.addEventListener(\"ended\", this.enable_buttons);\n }\n\n // Display buttons\n const buttonGroupElement = document.createElement(\"div\");\n buttonGroupElement.id = \"jspsych-audio-button-response-btngroup\";\n if (trial.button_layout === \"grid\") {\n buttonGroupElement.classList.add(\"jspsych-btn-group-grid\");\n if (trial.grid_rows === null && trial.grid_columns === null) {\n throw new Error(\n \"You cannot set `grid_rows` to `null` without providing a value for `grid_columns`.\"\n );\n }\n const n_cols =\n trial.grid_columns === null\n ? Math.ceil(trial.choices.length / trial.grid_rows)\n : trial.grid_columns;\n const n_rows =\n trial.grid_rows === null\n ? Math.ceil(trial.choices.length / trial.grid_columns)\n : trial.grid_rows;\n buttonGroupElement.style.gridTemplateColumns = `repeat(${n_cols}, 1fr)`;\n buttonGroupElement.style.gridTemplateRows = `repeat(${n_rows}, 1fr)`;\n } else if (trial.button_layout === \"flex\") {\n buttonGroupElement.classList.add(\"jspsych-btn-group-flex\");\n }\n\n for (const [choiceIndex, choice] of trial.choices.entries()) {\n buttonGroupElement.insertAdjacentHTML(\"beforeend\", trial.button_html(choice, choiceIndex));\n const buttonElement = buttonGroupElement.lastChild as HTMLElement;\n buttonElement.dataset.choice = choiceIndex.toString();\n buttonElement.addEventListener(\"click\", () => {\n this.after_response(choiceIndex);\n });\n this.buttonElements.push(buttonElement);\n }\n\n display_element.appendChild(buttonGroupElement);\n\n // Show prompt if there is one\n if (trial.prompt !== null) {\n display_element.insertAdjacentHTML(\"beforeend\", trial.prompt);\n }\n\n if (trial.response_allowed_while_playing) {\n if (trial.enable_button_after > 0) {\n this.disable_buttons();\n this.enable_buttons();\n }\n } else {\n this.disable_buttons();\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 on_load();\n\n // start time\n this.startTime = performance.now();\n if (this.context !== null) {\n this.startTime = this.context.currentTime;\n }\n\n // start audio\n this.audio.play();\n\n return new Promise((resolve) => {\n // hold the .resolve() function from the Promise that ends the trial\n this.trial_complete = resolve;\n });\n }\n\n private disable_buttons = () => {\n for (const button of this.buttonElements) {\n button.setAttribute(\"disabled\", \"disabled\");\n }\n };\n\n private enable_buttons_without_delay = () => {\n for (const button of this.buttonElements) {\n button.removeAttribute(\"disabled\");\n }\n };\n\n private enable_buttons_with_delay = (delay: number) => {\n this.jsPsych.pluginAPI.setTimeout(this.enable_buttons_without_delay, delay);\n };\n\n private enable_buttons() {\n if (this.params.enable_button_after > 0) {\n this.enable_buttons_with_delay(this.params.enable_button_after);\n } else {\n this.enable_buttons_without_delay();\n }\n }\n\n // function to handle responses by the subject\n private after_response = (choice) => {\n // measure rt\n var endTime = performance.now();\n var rt = Math.round(endTime - this.startTime);\n if (this.context !== null) {\n endTime = this.context.currentTime;\n rt = Math.round((endTime - this.startTime) * 1000);\n }\n this.response.button = parseInt(choice);\n this.response.rt = rt;\n\n // disable all the buttons after a response\n this.disable_buttons();\n\n if (this.params.response_ends_trial) {\n this.end_trial();\n }\n };\n\n // method to end trial when it is time\n private end_trial = () => {\n // stop the audio file if it is playing\n this.audio.stop();\n\n // remove end event listeners if they exist\n this.audio.removeEventListener(\"ended\", this.end_trial);\n this.audio.removeEventListener(\"ended\", this.enable_buttons);\n\n // gather the data to store for the trial\n var trial_data = {\n rt: this.response.rt,\n stimulus: this.params.stimulus,\n response: this.response.button,\n };\n\n // move on to the next trial\n this.trial_complete(trial_data);\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 this.simulate_data_only(trial, simulation_options);\n }\n if (simulation_mode == \"visual\") {\n this.simulate_visual(trial, simulation_options, load_callback);\n }\n }\n\n private create_simulation_data(trial: TrialType<Info>, simulation_options) {\n const default_data = {\n stimulus: trial.stimulus,\n rt:\n this.jsPsych.randomization.sampleExGaussian(500, 50, 1 / 150, true) +\n trial.enable_button_after,\n response: this.jsPsych.randomization.randomInt(0, trial.choices.length - 1),\n };\n\n const data = this.jsPsych.pluginAPI.mergeSimulationData(default_data, simulation_options);\n\n this.jsPsych.pluginAPI.ensureSimulationDataConsistency(trial, data);\n\n return data;\n }\n\n private simulate_data_only(trial: TrialType<Info>, simulation_options) {\n const data = this.create_simulation_data(trial, simulation_options);\n\n this.jsPsych.finishTrial(data);\n }\n\n private simulate_visual(trial: TrialType<Info>, simulation_options, load_callback: () => void) {\n const data = this.create_simulation_data(trial, simulation_options);\n\n const display_element = this.jsPsych.getDisplayElement();\n\n const respond = () => {\n if (data.rt !== null) {\n this.jsPsych.pluginAPI.clickTarget(\n display_element.querySelector(\n `#jspsych-audio-button-response-btngroup [data-choice=\"${data.response}\"]`\n ),\n data.rt\n );\n }\n };\n\n this.trial(display_element, trial, () => {\n load_callback();\n if (!trial.response_allowed_while_playing) {\n this.audio.addEventListener(\"ended\", respond);\n } else {\n respond();\n }\n });\n }\n}\n\nexport default AudioButtonResponsePlugin;\n"],"names":[],"mappings":";;;AAEE,IAAW,OAAA,GAAA,OAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ECmHA,SAAA,EAAA;AAAA;;GAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jspsych/plugin-audio-button-response",
|
|
3
|
-
"version": "2.0
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "jsPsych plugin for playing an audio file and getting a button response",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.cjs",
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"jspsych": ">=7.1.0"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
|
-
"@jspsych/config": "^3.
|
|
40
|
+
"@jspsych/config": "^3.2.0",
|
|
41
41
|
"@jspsych/test-utils": "^1.2.0"
|
|
42
42
|
}
|
|
43
43
|
}
|
package/src/index.ts
CHANGED
|
@@ -100,6 +100,10 @@ const info = <const>{
|
|
|
100
100
|
},
|
|
101
101
|
},
|
|
102
102
|
data: {
|
|
103
|
+
/** The path of the audio file that was played. */
|
|
104
|
+
stimulus: {
|
|
105
|
+
type: ParameterType.STRING,
|
|
106
|
+
},
|
|
103
107
|
/** The response time in milliseconds for the participant to make a response. The time is measured from
|
|
104
108
|
* when the stimulus first began playing until the participant's response.*/
|
|
105
109
|
rt: {
|
|
@@ -110,6 +114,8 @@ const info = <const>{
|
|
|
110
114
|
type: ParameterType.INT,
|
|
111
115
|
},
|
|
112
116
|
},
|
|
117
|
+
// prettier-ignore
|
|
118
|
+
citations: '__CITATIONS__',
|
|
113
119
|
};
|
|
114
120
|
|
|
115
121
|
type Info = typeof info;
|