@jspsych/plugin-audio-button-response 2.0.2 → 2.1.1
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 +96 -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 +56 -46
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +56 -46
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/index.spec.ts +2 -2
- package/src/index.ts +5 -3
package/dist/index.browser.js
CHANGED
|
@@ -49,132 +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.2",
|
|
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.1";
|
|
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. */
|
|
153
151
|
stimulus: {
|
|
154
152
|
type: jspsych.ParameterType.STRING
|
|
155
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.*/
|
|
156
156
|
rt: {
|
|
157
157
|
type: jspsych.ParameterType.INT
|
|
158
158
|
},
|
|
159
|
+
/** Indicates which button the participant pressed. The first button in the `choices` array is 0, the second is 1, and so on. */
|
|
159
160
|
response: {
|
|
160
161
|
type: jspsych.ParameterType.INT
|
|
161
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}, } '
|
|
162
168
|
}
|
|
163
169
|
};
|
|
164
170
|
class AudioButtonResponsePlugin {
|
|
165
171
|
constructor(jsPsych) {
|
|
166
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.removeEventListener("ended", this.end_trial);
|
|
206
|
+
this.audio.removeEventListener("ended", this.enable_buttons);
|
|
207
|
+
this.audio.stop();
|
|
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
|
+
};
|
|
167
215
|
autoBind$1(this);
|
|
168
216
|
}
|
|
169
|
-
static
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
buttonElements = [];
|
|
173
|
-
display;
|
|
174
|
-
response = { rt: null, button: null };
|
|
175
|
-
context;
|
|
176
|
-
startTime;
|
|
177
|
-
trial_complete;
|
|
217
|
+
static {
|
|
218
|
+
this.info = info;
|
|
219
|
+
}
|
|
178
220
|
async trial(display_element, trial, on_load) {
|
|
179
221
|
this.params = trial;
|
|
180
222
|
this.display = display_element;
|
|
@@ -238,19 +280,6 @@ var jsPsychAudioButtonResponse = (function (jspsych) {
|
|
|
238
280
|
this.trial_complete = resolve;
|
|
239
281
|
});
|
|
240
282
|
}
|
|
241
|
-
disable_buttons = () => {
|
|
242
|
-
for (const button of this.buttonElements) {
|
|
243
|
-
button.setAttribute("disabled", "disabled");
|
|
244
|
-
}
|
|
245
|
-
};
|
|
246
|
-
enable_buttons_without_delay = () => {
|
|
247
|
-
for (const button of this.buttonElements) {
|
|
248
|
-
button.removeAttribute("disabled");
|
|
249
|
-
}
|
|
250
|
-
};
|
|
251
|
-
enable_buttons_with_delay = (delay) => {
|
|
252
|
-
this.jsPsych.pluginAPI.setTimeout(this.enable_buttons_without_delay, delay);
|
|
253
|
-
};
|
|
254
283
|
enable_buttons() {
|
|
255
284
|
if (this.params.enable_button_after > 0) {
|
|
256
285
|
this.enable_buttons_with_delay(this.params.enable_button_after);
|
|
@@ -258,31 +287,6 @@ var jsPsychAudioButtonResponse = (function (jspsych) {
|
|
|
258
287
|
this.enable_buttons_without_delay();
|
|
259
288
|
}
|
|
260
289
|
}
|
|
261
|
-
after_response = (choice) => {
|
|
262
|
-
var endTime = performance.now();
|
|
263
|
-
var rt = Math.round(endTime - this.startTime);
|
|
264
|
-
if (this.context !== null) {
|
|
265
|
-
endTime = this.context.currentTime;
|
|
266
|
-
rt = Math.round((endTime - this.startTime) * 1e3);
|
|
267
|
-
}
|
|
268
|
-
this.response.button = parseInt(choice);
|
|
269
|
-
this.response.rt = rt;
|
|
270
|
-
this.disable_buttons();
|
|
271
|
-
if (this.params.response_ends_trial) {
|
|
272
|
-
this.end_trial();
|
|
273
|
-
}
|
|
274
|
-
};
|
|
275
|
-
end_trial = () => {
|
|
276
|
-
this.audio.stop();
|
|
277
|
-
this.audio.removeEventListener("ended", this.end_trial);
|
|
278
|
-
this.audio.removeEventListener("ended", this.enable_buttons);
|
|
279
|
-
var trial_data = {
|
|
280
|
-
rt: this.response.rt,
|
|
281
|
-
stimulus: this.params.stimulus,
|
|
282
|
-
response: this.response.button
|
|
283
|
-
};
|
|
284
|
-
this.trial_complete(trial_data);
|
|
285
|
-
};
|
|
286
290
|
async simulate(trial, simulation_mode, simulation_options, load_callback) {
|
|
287
291
|
if (simulation_mode == "data-only") {
|
|
288
292
|
load_callback();
|
|
@@ -333,4 +337,4 @@ var jsPsychAudioButtonResponse = (function (jspsych) {
|
|
|
333
337
|
return AudioButtonResponsePlugin;
|
|
334
338
|
|
|
335
339
|
})(jsPsychModule);
|
|
336
|
-
//# sourceMappingURL=https://unpkg.com/@jspsych/plugin-audio-button-response@2.
|
|
340
|
+
//# sourceMappingURL=https://unpkg.com/@jspsych/plugin-audio-button-response@2.1.1/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 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};\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.1\",\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 // 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 // stop the audio file if it is playing\n this.audio.stop();\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.
|
|
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.1",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.removeEventListener("ended",this.end_trial),this.audio.removeEventListener("ended",this.enable_buttons),this.audio.stop();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.1/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 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};\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","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":"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,GAAOE,EAAKF,GAAK,KAAKE,CAAI,EAEhC,CAED,OAAOA,CACR,23BC1CA,EAAA,CAAAO,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,EAAA,EAAA,GAAA,CAAA,GAAA,CAAAE,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,EAAA,CAAA,EAAAC,GAAA,EAAA,EAAA,MAAAL,EAAAC,CAAA,GAAA,MAAA,CAAA,CAAA,EAMA,MAAMK,EAAc,CAClB,KAAM,wBACN,QAASC,EAAAA,QACT,WAAY,CAEV,SAAU,CACR,KAAMC,gBAAc,MACpB,QAAS,MACX,EAEA,QAAS,CACP,KAAMA,EAAAA,cAAc,OACpB,QAAS,OACT,MAAO,EACT,EAOA,YAAa,CACX,KAAMA,gBAAc,SACpB,QAAS,SAAUC,EAAgBC,EAAsB,CACvD,MAAO,+BAA+BD,YACxC,CACF,EAIA,OAAQ,CACN,KAAMD,EAAAA,cAAc,YACpB,QAAS,IACX,EAKA,eAAgB,CACd,KAAMA,EAAAA,cAAc,IACpB,QAAS,IACX,EAKA,cAAe,CACb,KAAMA,EAAAA,cAAc,OACpB,QAAS,MACX,EAIA,UAAW,CACT,KAAMA,EAAAA,cAAc,IACpB,QAAS,CACX,EAKA,aAAc,CACZ,KAAMA,EAAAA,cAAc,IACpB,QAAS,IACX,EAKA,oBAAqB,CACnB,KAAMA,EAAAA,cAAc,KACpB,QAAS,EACX,EAEA,uBAAwB,CACtB,KAAMA,gBAAc,KACpB,QAAS,EACX,EAOA,+BAAgC,CAC9B,KAAMA,EAAAA,cAAc,KACpB,QAAS,EACX,EAGA,oBAAqB,CACnB,KAAMA,gBAAc,IACpB,QAAS,CACX,CACF,EACA,KAAM,CAEJ,SAAU,CACR,KAAMA,EAAAA,cAAc,MACtB,EAGA,GAAI,CACF,KAAMA,EAAAA,cAAc,GACtB,EAEA,SAAU,CACR,KAAMA,gBAAc,GACtB,CACF,CACF,EAqBA,MAAMG,CAAyD,CAW7D,YAAoBC,EAAkB,CAAlB,KAAA,QAAAA,EAPpB,KAAQ,eAAgC,CAAA,EAExC,KAAQ,SAA2C,CAAE,GAAI,KAAM,OAAQ,IAAK,EAsG5E,KAAQ,gBAAkB,IAAM,CAC9B,UAAWC,KAAU,KAAK,eACxBA,EAAO,aAAa,WAAY,UAAU,CAE9C,EAEA,KAAQ,6BAA+B,IAAM,CAC3C,UAAWA,KAAU,KAAK,eACxBA,EAAO,gBAAgB,UAAU,CAErC,EAEA,KAAQ,0BAA6BC,GAAkB,CACrD,KAAK,QAAQ,UAAU,WAAW,KAAK,6BAA8BA,CAAK,CAC5E,EAWA,KAAQ,eAAkBL,GAAW,CAEnC,IAAIM,EAAU,YAAY,IAAI,EAC1BC,EAAK,KAAK,MAAMD,EAAU,KAAK,SAAS,EACxC,KAAK,UAAY,OACnBA,EAAU,KAAK,QAAQ,YACvBC,EAAK,KAAK,OAAOD,EAAU,KAAK,WAAa,GAAI,GAEnD,KAAK,SAAS,OAAS,SAASN,CAAM,EACtC,KAAK,SAAS,GAAKO,EAGnB,KAAK,gBAAA,EAED,KAAK,OAAO,qBACd,KAAK,UAAA,CAET,EAGA,KAAQ,UAAY,IAAM,CAExB,KAAK,MAAM,KAAA,EAGX,KAAK,MAAM,oBAAoB,QAAS,KAAK,SAAS,EACtD,KAAK,MAAM,oBAAoB,QAAS,KAAK,cAAc,EAG3D,IAAIC,EAAa,CACf,GAAI,KAAK,SAAS,GAClB,SAAU,KAAK,OAAO,SACtB,SAAU,KAAK,SAAS,MAC1B,EAGA,KAAK,eAAeA,CAAU,CAChC,EA9JEzB,EAAS,IAAI,CACf,CAEM,MAAM0B,EAA8BC,EAAwBC,EAAqB,CAAAC,OAAAA,EAAA,sBACrF,KAAK,OAASF,EACd,KAAK,QAAUD,EAEf,KAAK,QAAU,KAAK,QAAQ,UAAU,aAAa,EAGnD,KAAK,MAAQ,MAAM,KAAK,QAAQ,UAAU,eAAeC,EAAM,QAAQ,EAGnEA,EAAM,wBACR,KAAK,MAAM,iBAAiB,QAAS,KAAK,SAAS,EAIjD,CAACA,EAAM,gCAAkC,CAACA,EAAM,wBAClD,KAAK,MAAM,iBAAiB,QAAS,KAAK,cAAc,EAI1D,MAAMG,EAAqB,SAAS,cAAc,KAAK,EAEvD,GADAA,EAAmB,GAAK,yCACpBH,EAAM,gBAAkB,OAAQ,CAElC,GADAG,EAAmB,UAAU,IAAI,wBAAwB,EACrDH,EAAM,YAAc,MAAQA,EAAM,eAAiB,KACrD,MAAM,IAAI,MACR,oFACF,EAEF,MAAMI,EACJJ,EAAM,eAAiB,KACnB,KAAK,KAAKA,EAAM,QAAQ,OAASA,EAAM,SAAS,EAChDA,EAAM,aACNK,EACJL,EAAM,YAAc,KAChB,KAAK,KAAKA,EAAM,QAAQ,OAASA,EAAM,YAAY,EACnDA,EAAM,UACZG,EAAmB,MAAM,oBAAsB,UAAUC,UACzDD,EAAmB,MAAM,iBAAmB,UAAUE,SACxD,MAAWL,EAAM,gBAAkB,QACjCG,EAAmB,UAAU,IAAI,wBAAwB,EAG3D,SAAW,CAACG,EAAahB,CAAM,IAAKU,EAAM,QAAQ,QAAQ,EAAG,CAC3DG,EAAmB,mBAAmB,YAAaH,EAAM,YAAYV,EAAQgB,CAAW,CAAC,EACzF,MAAMC,EAAgBJ,EAAmB,UACzCI,EAAc,QAAQ,OAASD,EAAY,SAAS,EACpDC,EAAc,iBAAiB,QAAS,IAAM,CAC5C,KAAK,eAAeD,CAAW,CACjC,CAAC,EACD,KAAK,eAAe,KAAKC,CAAa,CACxC,CAEA,OAAAR,EAAgB,YAAYI,CAAkB,EAG1CH,EAAM,SAAW,MACnBD,EAAgB,mBAAmB,YAAaC,EAAM,MAAM,EAG1DA,EAAM,+BACJA,EAAM,oBAAsB,IAC9B,KAAK,kBACL,KAAK,kBAGP,KAAK,kBAIHA,EAAM,iBAAmB,MAC3B,KAAK,QAAQ,UAAU,WAAW,IAAM,CACtC,KAAK,WACP,EAAGA,EAAM,cAAc,EAGzBC,EAAQ,EAGR,KAAK,UAAY,YAAY,MACzB,KAAK,UAAY,OACnB,KAAK,UAAY,KAAK,QAAQ,aAIhC,KAAK,MAAM,OAEJ,IAAI,QAASO,GAAY,CAE9B,KAAK,eAAiBA,CACxB,CAAC,CACH,GAkBQ,gBAAiB,CACnB,KAAK,OAAO,oBAAsB,EACpC,KAAK,0BAA0B,KAAK,OAAO,mBAAmB,EAE9D,KAAK,6BAET,CAAA,CA0CM,SACJR,EACAS,EACAC,EACAC,EACA,QAAAT,EAAA,KAAA,KAAA,WAAA,CACIO,GAAmB,cACrBE,EAAAA,EACA,KAAK,mBAAmBX,EAAOU,CAAkB,GAE/CD,GAAmB,UACrB,KAAK,gBAAgBT,EAAOU,EAAoBC,CAAa,CAEjE,GAEQ,uBAAuBX,EAAwBU,EAAoB,CACzE,MAAME,EAAe,CACnB,SAAUZ,EAAM,SAChB,GACE,KAAK,QAAQ,cAAc,iBAAiB,IAAK,GAAI,oBAAS,EAAI,EAClEA,EAAM,oBACR,SAAU,KAAK,QAAQ,cAAc,UAAU,EAAGA,EAAM,QAAQ,OAAS,CAAC,CAC5E,EAEMa,EAAO,KAAK,QAAQ,UAAU,oBAAoBD,EAAcF,CAAkB,EAExF,YAAK,QAAQ,UAAU,gCAAgCV,EAAOa,CAAI,EAE3DA,CACT,CAEQ,mBAAmBb,EAAwBU,EAAoB,CACrE,MAAMG,EAAO,KAAK,uBAAuBb,EAAOU,CAAkB,EAElE,KAAK,QAAQ,YAAYG,CAAI,CAC/B,CAEQ,gBAAgBb,EAAwBU,EAAoBC,EAA2B,CAC7F,MAAME,EAAO,KAAK,uBAAuBb,EAAOU,CAAkB,EAE5DX,EAAkB,KAAK,QAAQ,oBAE/Be,EAAU,IAAM,CAChBD,EAAK,KAAO,MACd,KAAK,QAAQ,UAAU,YACrBd,EAAgB,cACd,yDAAyDc,EAAK,YAChE,EACAA,EAAK,EACP,CAEJ,EAEA,KAAK,MAAMd,EAAiBC,EAAO,IAAM,CACvCW,IACKX,EAAM,+BAGTc,EAAQ,EAFR,KAAK,MAAM,iBAAiB,QAASA,CAAO,CAIhD,CAAC,CACH,CACF,CA1OMtB,SACG,KAAOL"}
|
|
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.1\",\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 // 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 // stop the audio file if it is playing\n this.audio.stop();\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,116 +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.2",
|
|
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.1";
|
|
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. */
|
|
107
105
|
stimulus: {
|
|
108
106
|
type: jspsych.ParameterType.STRING
|
|
109
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.*/
|
|
110
110
|
rt: {
|
|
111
111
|
type: jspsych.ParameterType.INT
|
|
112
112
|
},
|
|
113
|
+
/** Indicates which button the participant pressed. The first button in the `choices` array is 0, the second is 1, and so on. */
|
|
113
114
|
response: {
|
|
114
115
|
type: jspsych.ParameterType.INT
|
|
115
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}, } '
|
|
116
122
|
}
|
|
117
123
|
};
|
|
118
124
|
class AudioButtonResponsePlugin {
|
|
@@ -133,6 +139,7 @@ class AudioButtonResponsePlugin {
|
|
|
133
139
|
this.enable_buttons_with_delay = (delay) => {
|
|
134
140
|
this.jsPsych.pluginAPI.setTimeout(this.enable_buttons_without_delay, delay);
|
|
135
141
|
};
|
|
142
|
+
// function to handle responses by the subject
|
|
136
143
|
this.after_response = (choice) => {
|
|
137
144
|
var endTime = performance.now();
|
|
138
145
|
var rt = Math.round(endTime - this.startTime);
|
|
@@ -147,10 +154,11 @@ class AudioButtonResponsePlugin {
|
|
|
147
154
|
this.end_trial();
|
|
148
155
|
}
|
|
149
156
|
};
|
|
157
|
+
// method to end trial when it is time
|
|
150
158
|
this.end_trial = () => {
|
|
151
|
-
this.audio.stop();
|
|
152
159
|
this.audio.removeEventListener("ended", this.end_trial);
|
|
153
160
|
this.audio.removeEventListener("ended", this.enable_buttons);
|
|
161
|
+
this.audio.stop();
|
|
154
162
|
var trial_data = {
|
|
155
163
|
rt: this.response.rt,
|
|
156
164
|
stimulus: this.params.stimulus,
|
|
@@ -160,6 +168,9 @@ class AudioButtonResponsePlugin {
|
|
|
160
168
|
};
|
|
161
169
|
autoBind(this);
|
|
162
170
|
}
|
|
171
|
+
static {
|
|
172
|
+
this.info = info;
|
|
173
|
+
}
|
|
163
174
|
async trial(display_element, trial, on_load) {
|
|
164
175
|
this.params = trial;
|
|
165
176
|
this.display = display_element;
|
|
@@ -276,7 +287,6 @@ class AudioButtonResponsePlugin {
|
|
|
276
287
|
});
|
|
277
288
|
}
|
|
278
289
|
}
|
|
279
|
-
AudioButtonResponsePlugin.info = info;
|
|
280
290
|
|
|
281
291
|
module.exports = AudioButtonResponsePlugin;
|
|
282
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 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};\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.1\",\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 // 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 // stop the audio file if it is playing\n this.audio.stop();\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
|
@@ -108,6 +108,7 @@ declare const info: {
|
|
|
108
108
|
readonly type: ParameterType.INT;
|
|
109
109
|
};
|
|
110
110
|
};
|
|
111
|
+
readonly citations: "__CITATIONS__";
|
|
111
112
|
};
|
|
112
113
|
type Info = typeof info;
|
|
113
114
|
/**
|
|
@@ -237,6 +238,7 @@ declare class AudioButtonResponsePlugin implements JsPsychPlugin<Info> {
|
|
|
237
238
|
readonly type: ParameterType.INT;
|
|
238
239
|
};
|
|
239
240
|
};
|
|
241
|
+
readonly citations: "__CITATIONS__";
|
|
240
242
|
};
|
|
241
243
|
private audio;
|
|
242
244
|
private params;
|
package/dist/index.js
CHANGED
|
@@ -1,116 +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.2",
|
|
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.1";
|
|
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. */
|
|
105
103
|
stimulus: {
|
|
106
104
|
type: ParameterType.STRING
|
|
107
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.*/
|
|
108
108
|
rt: {
|
|
109
109
|
type: ParameterType.INT
|
|
110
110
|
},
|
|
111
|
+
/** Indicates which button the participant pressed. The first button in the `choices` array is 0, the second is 1, and so on. */
|
|
111
112
|
response: {
|
|
112
113
|
type: ParameterType.INT
|
|
113
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}, } '
|
|
114
120
|
}
|
|
115
121
|
};
|
|
116
122
|
class AudioButtonResponsePlugin {
|
|
@@ -131,6 +137,7 @@ class AudioButtonResponsePlugin {
|
|
|
131
137
|
this.enable_buttons_with_delay = (delay) => {
|
|
132
138
|
this.jsPsych.pluginAPI.setTimeout(this.enable_buttons_without_delay, delay);
|
|
133
139
|
};
|
|
140
|
+
// function to handle responses by the subject
|
|
134
141
|
this.after_response = (choice) => {
|
|
135
142
|
var endTime = performance.now();
|
|
136
143
|
var rt = Math.round(endTime - this.startTime);
|
|
@@ -145,10 +152,11 @@ class AudioButtonResponsePlugin {
|
|
|
145
152
|
this.end_trial();
|
|
146
153
|
}
|
|
147
154
|
};
|
|
155
|
+
// method to end trial when it is time
|
|
148
156
|
this.end_trial = () => {
|
|
149
|
-
this.audio.stop();
|
|
150
157
|
this.audio.removeEventListener("ended", this.end_trial);
|
|
151
158
|
this.audio.removeEventListener("ended", this.enable_buttons);
|
|
159
|
+
this.audio.stop();
|
|
152
160
|
var trial_data = {
|
|
153
161
|
rt: this.response.rt,
|
|
154
162
|
stimulus: this.params.stimulus,
|
|
@@ -158,6 +166,9 @@ class AudioButtonResponsePlugin {
|
|
|
158
166
|
};
|
|
159
167
|
autoBind(this);
|
|
160
168
|
}
|
|
169
|
+
static {
|
|
170
|
+
this.info = info;
|
|
171
|
+
}
|
|
161
172
|
async trial(display_element, trial, on_load) {
|
|
162
173
|
this.params = trial;
|
|
163
174
|
this.display = display_element;
|
|
@@ -274,7 +285,6 @@ class AudioButtonResponsePlugin {
|
|
|
274
285
|
});
|
|
275
286
|
}
|
|
276
287
|
}
|
|
277
|
-
AudioButtonResponsePlugin.info = info;
|
|
278
288
|
|
|
279
289
|
export { AudioButtonResponsePlugin as default };
|
|
280
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 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};\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.1\",\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 // 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 // stop the audio file if it is playing\n this.audio.stop();\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.
|
|
3
|
+
"version": "2.1.1",
|
|
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.spec.ts
CHANGED
|
@@ -273,9 +273,9 @@ describe("audio-button-response", () => {
|
|
|
273
273
|
use_webaudio: false,
|
|
274
274
|
});
|
|
275
275
|
|
|
276
|
-
await startTimeline(timeline, jsPsych);
|
|
276
|
+
const { displayElement } = await startTimeline(timeline, jsPsych);
|
|
277
277
|
|
|
278
|
-
const btns =
|
|
278
|
+
const btns = displayElement.querySelectorAll(".jspsych-html-button-response-button button");
|
|
279
279
|
|
|
280
280
|
for (let i = 0; i < btns.length; i++) {
|
|
281
281
|
expect(btns[i].getAttribute("disabled")).toBe(true);
|
package/src/index.ts
CHANGED
|
@@ -114,6 +114,8 @@ const info = <const>{
|
|
|
114
114
|
type: ParameterType.INT,
|
|
115
115
|
},
|
|
116
116
|
},
|
|
117
|
+
// prettier-ignore
|
|
118
|
+
citations: '__CITATIONS__',
|
|
117
119
|
};
|
|
118
120
|
|
|
119
121
|
type Info = typeof info;
|
|
@@ -289,13 +291,13 @@ class AudioButtonResponsePlugin implements JsPsychPlugin<Info> {
|
|
|
289
291
|
|
|
290
292
|
// method to end trial when it is time
|
|
291
293
|
private end_trial = () => {
|
|
292
|
-
// stop the audio file if it is playing
|
|
293
|
-
this.audio.stop();
|
|
294
|
-
|
|
295
294
|
// remove end event listeners if they exist
|
|
296
295
|
this.audio.removeEventListener("ended", this.end_trial);
|
|
297
296
|
this.audio.removeEventListener("ended", this.enable_buttons);
|
|
298
297
|
|
|
298
|
+
// stop the audio file if it is playing
|
|
299
|
+
this.audio.stop();
|
|
300
|
+
|
|
299
301
|
// gather the data to store for the trial
|
|
300
302
|
var trial_data = {
|
|
301
303
|
rt: this.response.rt,
|