@jspsych/plugin-audio-button-response 1.2.0 → 2.0.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 +328 -319
- 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 +260 -304
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +151 -69
- package/dist/index.js +260 -304
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/index.spec.ts +233 -9
- package/src/index.ts +223 -224
package/src/index.ts
CHANGED
|
@@ -1,312 +1,309 @@
|
|
|
1
|
+
import autoBind from "auto-bind";
|
|
1
2
|
import { JsPsych, JsPsychPlugin, ParameterType, TrialType } from "jspsych";
|
|
2
3
|
|
|
4
|
+
import { AudioPlayerInterface } from "../../jspsych/src/modules/plugin-api/AudioPlayer";
|
|
5
|
+
import { version } from "../package.json";
|
|
6
|
+
|
|
3
7
|
const info = <const>{
|
|
4
8
|
name: "audio-button-response",
|
|
9
|
+
version: version,
|
|
5
10
|
parameters: {
|
|
6
|
-
/**
|
|
11
|
+
/** Path to audio file to be played. */
|
|
7
12
|
stimulus: {
|
|
8
13
|
type: ParameterType.AUDIO,
|
|
9
|
-
pretty_name: "Stimulus",
|
|
10
14
|
default: undefined,
|
|
11
15
|
},
|
|
12
|
-
/**
|
|
16
|
+
/** Labels for the buttons. Each different string in the array will generate a different button. */
|
|
13
17
|
choices: {
|
|
14
18
|
type: ParameterType.STRING,
|
|
15
|
-
pretty_name: "Choices",
|
|
16
19
|
default: undefined,
|
|
17
20
|
array: true,
|
|
18
21
|
},
|
|
19
|
-
/**
|
|
22
|
+
/**
|
|
23
|
+
* A function that generates the HTML for each button in the `choices` array. The function gets the string
|
|
24
|
+
* and index of the item in the `choices` array and should return valid HTML. If you want to use different
|
|
25
|
+
* markup for each button, you can do that by using a conditional on either parameter. The default parameter
|
|
26
|
+
* returns a button element with the text label of the choice.
|
|
27
|
+
*/
|
|
20
28
|
button_html: {
|
|
21
|
-
type: ParameterType.
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
29
|
+
type: ParameterType.FUNCTION,
|
|
30
|
+
default: function (choice: string, choice_index: number) {
|
|
31
|
+
return `<button class="jspsych-btn">${choice}</button>`;
|
|
32
|
+
},
|
|
25
33
|
},
|
|
26
|
-
/** Any content here will be displayed below the stimulus.
|
|
34
|
+
/** This string can contain HTML markup. Any content here will be displayed below the stimulus. The intention
|
|
35
|
+
* is that it can be used to provide a reminder about the action the participant is supposed to take
|
|
36
|
+
* (e.g., which key to press). */
|
|
27
37
|
prompt: {
|
|
28
38
|
type: ParameterType.HTML_STRING,
|
|
29
|
-
pretty_name: "Prompt",
|
|
30
39
|
default: null,
|
|
31
40
|
},
|
|
32
|
-
/**
|
|
41
|
+
/** How long to wait for the participant to make a response before ending the trial in milliseconds. If the
|
|
42
|
+
* participant fails to make a response before this timer is reached, the participant's response will be
|
|
43
|
+
* recorded as null for the trial and the trial will end. If the value of this parameter is null, the trial
|
|
44
|
+
* will wait for a response indefinitely */
|
|
33
45
|
trial_duration: {
|
|
34
46
|
type: ParameterType.INT,
|
|
35
|
-
pretty_name: "Trial duration",
|
|
36
47
|
default: null,
|
|
37
48
|
},
|
|
38
|
-
/**
|
|
39
|
-
|
|
49
|
+
/** Setting to `'grid'` will make the container element have the CSS property `display: grid` and enable the
|
|
50
|
+
* use of `grid_rows` and `grid_columns`. Setting to `'flex'` will make the container element have the CSS
|
|
51
|
+
* property `display: flex`. You can customize how the buttons are laid out by adding inline CSS in the `button_html` parameter.
|
|
52
|
+
*/
|
|
53
|
+
button_layout: {
|
|
40
54
|
type: ParameterType.STRING,
|
|
41
|
-
|
|
42
|
-
default: "0px",
|
|
55
|
+
default: "grid",
|
|
43
56
|
},
|
|
44
|
-
/**
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
57
|
+
/** The number of rows in the button grid. Only applicable when `button_layout` is set to `'grid'`. If null, the
|
|
58
|
+
* number of rows will be determined automatically based on the number of buttons and the number of columns.
|
|
59
|
+
*/
|
|
60
|
+
grid_rows: {
|
|
61
|
+
type: ParameterType.INT,
|
|
62
|
+
default: 1,
|
|
49
63
|
},
|
|
50
|
-
/**
|
|
64
|
+
/** The number of columns in the button grid. Only applicable when `button_layout` is set to `'grid'`.
|
|
65
|
+
* If null, the number of columns will be determined automatically based on the number of buttons and the
|
|
66
|
+
* number of rows.
|
|
67
|
+
*/
|
|
68
|
+
grid_columns: {
|
|
69
|
+
type: ParameterType.INT,
|
|
70
|
+
default: null,
|
|
71
|
+
},
|
|
72
|
+
/** If true, then the trial will end whenever the participant makes a response (assuming they make their
|
|
73
|
+
* response before the cutoff specified by the `trial_duration` parameter). If false, then the trial will
|
|
74
|
+
* continue until the value for `trial_duration` is reached. You can set this parameter to `false` to force
|
|
75
|
+
* the participant to listen to the stimulus for a fixed amount of time, even if they respond before the time is complete. */
|
|
51
76
|
response_ends_trial: {
|
|
52
77
|
type: ParameterType.BOOL,
|
|
53
|
-
pretty_name: "Response ends trial",
|
|
54
78
|
default: true,
|
|
55
79
|
},
|
|
56
|
-
/** If true, then the trial will end as soon as the audio file finishes playing.
|
|
80
|
+
/** If true, then the trial will end as soon as the audio file finishes playing. */
|
|
57
81
|
trial_ends_after_audio: {
|
|
58
82
|
type: ParameterType.BOOL,
|
|
59
|
-
pretty_name: "Trial ends after audio",
|
|
60
83
|
default: false,
|
|
61
84
|
},
|
|
62
85
|
/**
|
|
63
|
-
* If true, then responses are allowed while the audio is playing.
|
|
64
|
-
*
|
|
86
|
+
* If true, then responses are allowed while the audio is playing. If false, then the audio must finish
|
|
87
|
+
* playing before the button choices are enabled and a response is accepted. Once the audio has played
|
|
88
|
+
* all the way through, the buttons are enabled and a response is allowed (including while the audio is
|
|
89
|
+
* being re-played via on-screen playback controls).
|
|
65
90
|
*/
|
|
66
91
|
response_allowed_while_playing: {
|
|
67
92
|
type: ParameterType.BOOL,
|
|
68
|
-
pretty_name: "Response allowed while playing",
|
|
69
93
|
default: true,
|
|
70
94
|
},
|
|
71
|
-
/**
|
|
95
|
+
/** How long the button will delay enabling in milliseconds. If `response_allowed_while_playing` is `true`,
|
|
96
|
+
* the timer will start immediately. If it is `false`, the timer will start at the end of the audio. */
|
|
72
97
|
enable_button_after: {
|
|
73
98
|
type: ParameterType.INT,
|
|
74
|
-
pretty_name: "Enable button after",
|
|
75
99
|
default: 0,
|
|
76
100
|
},
|
|
77
101
|
},
|
|
102
|
+
data: {
|
|
103
|
+
/** The response time in milliseconds for the participant to make a response. The time is measured from
|
|
104
|
+
* when the stimulus first began playing until the participant's response.*/
|
|
105
|
+
rt: {
|
|
106
|
+
type: ParameterType.INT,
|
|
107
|
+
},
|
|
108
|
+
/** Indicates which button the participant pressed. The first button in the `choices` array is 0, the second is 1, and so on. */
|
|
109
|
+
response: {
|
|
110
|
+
type: ParameterType.INT,
|
|
111
|
+
},
|
|
112
|
+
},
|
|
78
113
|
};
|
|
79
114
|
|
|
80
115
|
type Info = typeof info;
|
|
81
116
|
|
|
82
117
|
/**
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
118
|
+
* If the browser supports it, audio files are played using the WebAudio API. This allows for reasonably precise
|
|
119
|
+
* timing of the playback. The timing of responses generated is measured against the WebAudio specific clock,
|
|
120
|
+
* improving the measurement of response times. If the browser does not support the WebAudio API, then the audio file is
|
|
121
|
+
* played with HTML5 audio.
|
|
122
|
+
|
|
123
|
+
* Audio files can be automatically preloaded by jsPsych using the [`preload` plugin](preload.md). However, if
|
|
124
|
+
* you are using timeline variables or another dynamic method to specify the audio stimulus, you will need
|
|
125
|
+
* to [manually preload](../overview/media-preloading.md#manual-preloading) the audio.
|
|
126
|
+
|
|
127
|
+
* The trial can end when the participant responds, when the audio file has finished playing, or if the participant
|
|
128
|
+
* has failed to respond within a fixed length of time. You can also prevent a button response from being made before the
|
|
129
|
+
* audio has finished playing.
|
|
130
|
+
*
|
|
87
131
|
* @author Kristin Diep
|
|
88
|
-
* @see {@link https://www.jspsych.org/plugins/
|
|
132
|
+
* @see {@link https://www.jspsych.org/latest/plugins/audio-button-response/ audio-button-response plugin documentation on jspsych.org}
|
|
89
133
|
*/
|
|
90
134
|
class AudioButtonResponsePlugin implements JsPsychPlugin<Info> {
|
|
91
135
|
static info = info;
|
|
92
|
-
private audio;
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
136
|
+
private audio: AudioPlayerInterface;
|
|
137
|
+
private params: TrialType<Info>;
|
|
138
|
+
private buttonElements: HTMLElement[] = [];
|
|
139
|
+
private display: HTMLElement;
|
|
140
|
+
private response: { rt: number; button: number } = { rt: null, button: null };
|
|
141
|
+
private context: AudioContext;
|
|
142
|
+
private startTime: number;
|
|
143
|
+
private trial_complete: (trial_data: { rt: number; stimulus: string; response: number }) => void;
|
|
144
|
+
|
|
145
|
+
constructor(private jsPsych: JsPsych) {
|
|
146
|
+
autoBind(this);
|
|
147
|
+
}
|
|
99
148
|
|
|
149
|
+
async trial(display_element: HTMLElement, trial: TrialType<Info>, on_load: () => void) {
|
|
150
|
+
this.params = trial;
|
|
151
|
+
this.display = display_element;
|
|
100
152
|
// setup stimulus
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
// store response
|
|
104
|
-
var response = {
|
|
105
|
-
rt: null,
|
|
106
|
-
button: null,
|
|
107
|
-
};
|
|
108
|
-
|
|
109
|
-
// record webaudio context start time
|
|
110
|
-
var startTime;
|
|
153
|
+
this.context = this.jsPsych.pluginAPI.audioContext();
|
|
111
154
|
|
|
112
155
|
// load audio file
|
|
113
|
-
this.jsPsych.pluginAPI
|
|
114
|
-
.getAudioBuffer(trial.stimulus)
|
|
115
|
-
.then((buffer) => {
|
|
116
|
-
if (context !== null) {
|
|
117
|
-
this.audio = context.createBufferSource();
|
|
118
|
-
this.audio.buffer = buffer;
|
|
119
|
-
this.audio.connect(context.destination);
|
|
120
|
-
} else {
|
|
121
|
-
this.audio = buffer;
|
|
122
|
-
this.audio.currentTime = 0;
|
|
123
|
-
}
|
|
124
|
-
setupTrial();
|
|
125
|
-
})
|
|
126
|
-
.catch((err) => {
|
|
127
|
-
console.error(
|
|
128
|
-
`Failed to load audio file "${trial.stimulus}". Try checking the file path. We recommend using the preload plugin to load audio files.`
|
|
129
|
-
);
|
|
130
|
-
console.error(err);
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
const setupTrial = () => {
|
|
134
|
-
// set up end event if trial needs it
|
|
135
|
-
if (trial.trial_ends_after_audio) {
|
|
136
|
-
this.audio.addEventListener("ended", end_trial);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
// enable buttons after audio ends if necessary
|
|
140
|
-
if (!trial.response_allowed_while_playing && !trial.trial_ends_after_audio) {
|
|
141
|
-
this.audio.addEventListener("ended", enable_buttons);
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
//display buttons
|
|
145
|
-
var buttons = [];
|
|
146
|
-
if (Array.isArray(trial.button_html)) {
|
|
147
|
-
if (trial.button_html.length == trial.choices.length) {
|
|
148
|
-
buttons = trial.button_html;
|
|
149
|
-
} else {
|
|
150
|
-
console.error(
|
|
151
|
-
"Error in audio-button-response plugin. The length of the button_html array does not equal the length of the choices array"
|
|
152
|
-
);
|
|
153
|
-
}
|
|
154
|
-
} else {
|
|
155
|
-
for (var i = 0; i < trial.choices.length; i++) {
|
|
156
|
-
buttons.push(trial.button_html);
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
var html = '<div id="jspsych-audio-button-response-btngroup">';
|
|
161
|
-
for (var i = 0; i < trial.choices.length; i++) {
|
|
162
|
-
var str = buttons[i].replace(/%choice%/g, trial.choices[i]);
|
|
163
|
-
html +=
|
|
164
|
-
'<div class="jspsych-audio-button-response-button" style="cursor: pointer; display: inline-block; margin:' +
|
|
165
|
-
trial.margin_vertical +
|
|
166
|
-
" " +
|
|
167
|
-
trial.margin_horizontal +
|
|
168
|
-
'" id="jspsych-audio-button-response-button-' +
|
|
169
|
-
i +
|
|
170
|
-
'" data-choice="' +
|
|
171
|
-
i +
|
|
172
|
-
'">' +
|
|
173
|
-
str +
|
|
174
|
-
"</div>";
|
|
175
|
-
}
|
|
176
|
-
html += "</div>";
|
|
177
|
-
|
|
178
|
-
//show prompt if there is one
|
|
179
|
-
if (trial.prompt !== null) {
|
|
180
|
-
html += trial.prompt;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
display_element.innerHTML = html;
|
|
156
|
+
this.audio = await this.jsPsych.pluginAPI.getAudioPlayer(trial.stimulus);
|
|
184
157
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
disable_buttons();
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
// start time
|
|
193
|
-
startTime = performance.now();
|
|
158
|
+
// set up end event if trial needs it
|
|
159
|
+
if (trial.trial_ends_after_audio) {
|
|
160
|
+
this.audio.addEventListener("ended", this.end_trial);
|
|
161
|
+
}
|
|
194
162
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
} else {
|
|
200
|
-
this.audio.play();
|
|
201
|
-
}
|
|
163
|
+
// enable buttons after audio ends if necessary
|
|
164
|
+
if (!trial.response_allowed_while_playing && !trial.trial_ends_after_audio) {
|
|
165
|
+
this.audio.addEventListener("ended", this.enable_buttons);
|
|
166
|
+
}
|
|
202
167
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
168
|
+
// Display buttons
|
|
169
|
+
const buttonGroupElement = document.createElement("div");
|
|
170
|
+
buttonGroupElement.id = "jspsych-audio-button-response-btngroup";
|
|
171
|
+
if (trial.button_layout === "grid") {
|
|
172
|
+
buttonGroupElement.classList.add("jspsych-btn-group-grid");
|
|
173
|
+
if (trial.grid_rows === null && trial.grid_columns === null) {
|
|
174
|
+
throw new Error(
|
|
175
|
+
"You cannot set `grid_rows` to `null` without providing a value for `grid_columns`."
|
|
176
|
+
);
|
|
208
177
|
}
|
|
178
|
+
const n_cols =
|
|
179
|
+
trial.grid_columns === null
|
|
180
|
+
? Math.ceil(trial.choices.length / trial.grid_rows)
|
|
181
|
+
: trial.grid_columns;
|
|
182
|
+
const n_rows =
|
|
183
|
+
trial.grid_rows === null
|
|
184
|
+
? Math.ceil(trial.choices.length / trial.grid_columns)
|
|
185
|
+
: trial.grid_rows;
|
|
186
|
+
buttonGroupElement.style.gridTemplateColumns = `repeat(${n_cols}, 1fr)`;
|
|
187
|
+
buttonGroupElement.style.gridTemplateRows = `repeat(${n_rows}, 1fr)`;
|
|
188
|
+
} else if (trial.button_layout === "flex") {
|
|
189
|
+
buttonGroupElement.classList.add("jspsych-btn-group-flex");
|
|
190
|
+
}
|
|
209
191
|
|
|
210
|
-
|
|
211
|
-
|
|
192
|
+
for (const [choiceIndex, choice] of trial.choices.entries()) {
|
|
193
|
+
buttonGroupElement.insertAdjacentHTML("beforeend", trial.button_html(choice, choiceIndex));
|
|
194
|
+
const buttonElement = buttonGroupElement.lastChild as HTMLElement;
|
|
195
|
+
buttonElement.dataset.choice = choiceIndex.toString();
|
|
196
|
+
buttonElement.addEventListener("click", () => {
|
|
197
|
+
this.after_response(choiceIndex);
|
|
198
|
+
});
|
|
199
|
+
this.buttonElements.push(buttonElement);
|
|
200
|
+
}
|
|
212
201
|
|
|
213
|
-
|
|
214
|
-
function after_response(choice) {
|
|
215
|
-
// measure rt
|
|
216
|
-
var endTime = performance.now();
|
|
217
|
-
var rt = Math.round(endTime - startTime);
|
|
218
|
-
if (context !== null) {
|
|
219
|
-
endTime = context.currentTime;
|
|
220
|
-
rt = Math.round((endTime - startTime) * 1000);
|
|
221
|
-
}
|
|
222
|
-
response.button = parseInt(choice);
|
|
223
|
-
response.rt = rt;
|
|
202
|
+
display_element.appendChild(buttonGroupElement);
|
|
224
203
|
|
|
225
|
-
|
|
226
|
-
|
|
204
|
+
// Show prompt if there is one
|
|
205
|
+
if (trial.prompt !== null) {
|
|
206
|
+
display_element.insertAdjacentHTML("beforeend", trial.prompt);
|
|
207
|
+
}
|
|
227
208
|
|
|
228
|
-
|
|
229
|
-
|
|
209
|
+
if (trial.response_allowed_while_playing) {
|
|
210
|
+
if (trial.enable_button_after > 0) {
|
|
211
|
+
this.disable_buttons();
|
|
212
|
+
this.enable_buttons();
|
|
230
213
|
}
|
|
214
|
+
} else {
|
|
215
|
+
this.disable_buttons();
|
|
231
216
|
}
|
|
232
217
|
|
|
233
|
-
//
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
218
|
+
// end trial if time limit is set
|
|
219
|
+
if (trial.trial_duration !== null) {
|
|
220
|
+
this.jsPsych.pluginAPI.setTimeout(() => {
|
|
221
|
+
this.end_trial();
|
|
222
|
+
}, trial.trial_duration);
|
|
223
|
+
}
|
|
237
224
|
|
|
238
|
-
|
|
239
|
-
// remove end event listeners if they exist
|
|
240
|
-
if (context !== null) {
|
|
241
|
-
this.audio.stop();
|
|
242
|
-
} else {
|
|
243
|
-
this.audio.pause();
|
|
244
|
-
}
|
|
225
|
+
on_load();
|
|
245
226
|
|
|
246
|
-
|
|
247
|
-
|
|
227
|
+
// start time
|
|
228
|
+
this.startTime = performance.now();
|
|
229
|
+
if (this.context !== null) {
|
|
230
|
+
this.startTime = this.context.currentTime;
|
|
231
|
+
}
|
|
248
232
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
rt: response.rt,
|
|
252
|
-
stimulus: trial.stimulus,
|
|
253
|
-
response: response.button,
|
|
254
|
-
};
|
|
233
|
+
// start audio
|
|
234
|
+
this.audio.play();
|
|
255
235
|
|
|
256
|
-
|
|
257
|
-
|
|
236
|
+
return new Promise((resolve) => {
|
|
237
|
+
// hold the .resolve() function from the Promise that ends the trial
|
|
238
|
+
this.trial_complete = resolve;
|
|
239
|
+
});
|
|
240
|
+
}
|
|
258
241
|
|
|
259
|
-
|
|
260
|
-
|
|
242
|
+
private disable_buttons = () => {
|
|
243
|
+
for (const button of this.buttonElements) {
|
|
244
|
+
button.setAttribute("disabled", "disabled");
|
|
245
|
+
}
|
|
246
|
+
};
|
|
261
247
|
|
|
262
|
-
|
|
263
|
-
|
|
248
|
+
private enable_buttons_without_delay = () => {
|
|
249
|
+
for (const button of this.buttonElements) {
|
|
250
|
+
button.removeAttribute("disabled");
|
|
251
|
+
}
|
|
252
|
+
};
|
|
264
253
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
254
|
+
private enable_buttons_with_delay = (delay: number) => {
|
|
255
|
+
this.jsPsych.pluginAPI.setTimeout(this.enable_buttons_without_delay, delay);
|
|
256
|
+
};
|
|
268
257
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
258
|
+
private enable_buttons() {
|
|
259
|
+
if (this.params.enable_button_after > 0) {
|
|
260
|
+
this.enable_buttons_with_delay(this.params.enable_button_after);
|
|
261
|
+
} else {
|
|
262
|
+
this.enable_buttons_without_delay();
|
|
272
263
|
}
|
|
264
|
+
}
|
|
273
265
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
}
|
|
266
|
+
// function to handle responses by the subject
|
|
267
|
+
private after_response = (choice) => {
|
|
268
|
+
// measure rt
|
|
269
|
+
var endTime = performance.now();
|
|
270
|
+
var rt = Math.round(endTime - this.startTime);
|
|
271
|
+
if (this.context !== null) {
|
|
272
|
+
endTime = this.context.currentTime;
|
|
273
|
+
rt = Math.round((endTime - this.startTime) * 1000);
|
|
283
274
|
}
|
|
275
|
+
this.response.button = parseInt(choice);
|
|
276
|
+
this.response.rt = rt;
|
|
284
277
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
for (var i = 0; i < btns.length; i++) {
|
|
288
|
-
var btn_el = btns[i].querySelector("button");
|
|
289
|
-
if (btn_el) {
|
|
290
|
-
btn_el.disabled = false;
|
|
291
|
-
}
|
|
292
|
-
btns[i].addEventListener("click", button_response);
|
|
293
|
-
}
|
|
294
|
-
}
|
|
278
|
+
// disable all the buttons after a response
|
|
279
|
+
this.disable_buttons();
|
|
295
280
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
enable_buttons_with_delay(trial.enable_button_after);
|
|
299
|
-
} else {
|
|
300
|
-
enable_buttons_without_delay();
|
|
301
|
-
}
|
|
281
|
+
if (this.params.response_ends_trial) {
|
|
282
|
+
this.end_trial();
|
|
302
283
|
}
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
// method to end trial when it is time
|
|
287
|
+
private end_trial = () => {
|
|
288
|
+
// stop the audio file if it is playing
|
|
289
|
+
this.audio.stop();
|
|
290
|
+
|
|
291
|
+
// remove end event listeners if they exist
|
|
292
|
+
this.audio.removeEventListener("ended", this.end_trial);
|
|
293
|
+
this.audio.removeEventListener("ended", this.enable_buttons);
|
|
294
|
+
|
|
295
|
+
// gather the data to store for the trial
|
|
296
|
+
var trial_data = {
|
|
297
|
+
rt: this.response.rt,
|
|
298
|
+
stimulus: this.params.stimulus,
|
|
299
|
+
response: this.response.button,
|
|
300
|
+
};
|
|
303
301
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
}
|
|
302
|
+
// move on to the next trial
|
|
303
|
+
this.trial_complete(trial_data);
|
|
304
|
+
};
|
|
308
305
|
|
|
309
|
-
simulate(
|
|
306
|
+
async simulate(
|
|
310
307
|
trial: TrialType<Info>,
|
|
311
308
|
simulation_mode,
|
|
312
309
|
simulation_options: any,
|
|
@@ -351,7 +348,9 @@ class AudioButtonResponsePlugin implements JsPsychPlugin<Info> {
|
|
|
351
348
|
const respond = () => {
|
|
352
349
|
if (data.rt !== null) {
|
|
353
350
|
this.jsPsych.pluginAPI.clickTarget(
|
|
354
|
-
display_element.querySelector(
|
|
351
|
+
display_element.querySelector(
|
|
352
|
+
`#jspsych-audio-button-response-btngroup [data-choice="${data.response}"]`
|
|
353
|
+
),
|
|
355
354
|
data.rt
|
|
356
355
|
);
|
|
357
356
|
}
|