@jspsych/plugin-audio-keyboard-response 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.browser.js +57 -17
- package/dist/index.browser.js.map +1 -1
- package/dist/index.browser.min.js +1 -1
- package/dist/index.browser.min.js.map +1 -1
- package/dist/index.cjs +57 -17
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +5 -0
- package/dist/index.js +57 -17
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
- package/src/index.spec.ts +55 -0
- package/src/index.ts +73 -17
package/dist/index.browser.js
CHANGED
|
@@ -65,7 +65,6 @@ var jsPsychAudioKeyboardResponse = (function (jspsych) {
|
|
|
65
65
|
let trial_complete;
|
|
66
66
|
// setup stimulus
|
|
67
67
|
var context = this.jsPsych.pluginAPI.audioContext();
|
|
68
|
-
var audio;
|
|
69
68
|
// store response
|
|
70
69
|
var response = {
|
|
71
70
|
rt: null,
|
|
@@ -76,26 +75,26 @@ var jsPsychAudioKeyboardResponse = (function (jspsych) {
|
|
|
76
75
|
// load audio file
|
|
77
76
|
this.jsPsych.pluginAPI
|
|
78
77
|
.getAudioBuffer(trial.stimulus)
|
|
79
|
-
.then(
|
|
78
|
+
.then((buffer) => {
|
|
80
79
|
if (context !== null) {
|
|
81
|
-
audio = context.createBufferSource();
|
|
82
|
-
audio.buffer = buffer;
|
|
83
|
-
audio.connect(context.destination);
|
|
80
|
+
this.audio = context.createBufferSource();
|
|
81
|
+
this.audio.buffer = buffer;
|
|
82
|
+
this.audio.connect(context.destination);
|
|
84
83
|
}
|
|
85
84
|
else {
|
|
86
|
-
audio = buffer;
|
|
87
|
-
audio.currentTime = 0;
|
|
85
|
+
this.audio = buffer;
|
|
86
|
+
this.audio.currentTime = 0;
|
|
88
87
|
}
|
|
89
88
|
setupTrial();
|
|
90
89
|
})
|
|
91
|
-
.catch(
|
|
90
|
+
.catch((err) => {
|
|
92
91
|
console.error(`Failed to load audio file "${trial.stimulus}". Try checking the file path. We recommend using the preload plugin to load audio files.`);
|
|
93
92
|
console.error(err);
|
|
94
93
|
});
|
|
95
94
|
const setupTrial = () => {
|
|
96
95
|
// set up end event if trial needs it
|
|
97
96
|
if (trial.trial_ends_after_audio) {
|
|
98
|
-
audio.addEventListener("ended", end_trial);
|
|
97
|
+
this.audio.addEventListener("ended", end_trial);
|
|
99
98
|
}
|
|
100
99
|
// show prompt if there is one
|
|
101
100
|
if (trial.prompt !== null) {
|
|
@@ -104,21 +103,21 @@ var jsPsychAudioKeyboardResponse = (function (jspsych) {
|
|
|
104
103
|
// start audio
|
|
105
104
|
if (context !== null) {
|
|
106
105
|
startTime = context.currentTime;
|
|
107
|
-
audio.start(startTime);
|
|
106
|
+
this.audio.start(startTime);
|
|
108
107
|
}
|
|
109
108
|
else {
|
|
110
|
-
audio.play();
|
|
109
|
+
this.audio.play();
|
|
111
110
|
}
|
|
112
111
|
// start keyboard listener when trial starts or sound ends
|
|
113
112
|
if (trial.response_allowed_while_playing) {
|
|
114
113
|
setup_keyboard_listener();
|
|
115
114
|
}
|
|
116
115
|
else if (!trial.trial_ends_after_audio) {
|
|
117
|
-
audio.addEventListener("ended", setup_keyboard_listener);
|
|
116
|
+
this.audio.addEventListener("ended", setup_keyboard_listener);
|
|
118
117
|
}
|
|
119
118
|
// end trial if time limit is set
|
|
120
119
|
if (trial.trial_duration !== null) {
|
|
121
|
-
this.jsPsych.pluginAPI.setTimeout(
|
|
120
|
+
this.jsPsych.pluginAPI.setTimeout(() => {
|
|
122
121
|
end_trial();
|
|
123
122
|
}, trial.trial_duration);
|
|
124
123
|
}
|
|
@@ -131,13 +130,13 @@ var jsPsychAudioKeyboardResponse = (function (jspsych) {
|
|
|
131
130
|
// stop the audio file if it is playing
|
|
132
131
|
// remove end event listeners if they exist
|
|
133
132
|
if (context !== null) {
|
|
134
|
-
audio.stop();
|
|
133
|
+
this.audio.stop();
|
|
135
134
|
}
|
|
136
135
|
else {
|
|
137
|
-
audio.pause();
|
|
136
|
+
this.audio.pause();
|
|
138
137
|
}
|
|
139
|
-
audio.removeEventListener("ended", end_trial);
|
|
140
|
-
audio.removeEventListener("ended", setup_keyboard_listener);
|
|
138
|
+
this.audio.removeEventListener("ended", end_trial);
|
|
139
|
+
this.audio.removeEventListener("ended", setup_keyboard_listener);
|
|
141
140
|
// kill keyboard listeners
|
|
142
141
|
this.jsPsych.pluginAPI.cancelAllKeyboardResponses();
|
|
143
142
|
// gather the data to store for the trial
|
|
@@ -189,6 +188,47 @@ var jsPsychAudioKeyboardResponse = (function (jspsych) {
|
|
|
189
188
|
trial_complete = resolve;
|
|
190
189
|
});
|
|
191
190
|
}
|
|
191
|
+
simulate(trial, simulation_mode, simulation_options, load_callback) {
|
|
192
|
+
if (simulation_mode == "data-only") {
|
|
193
|
+
load_callback();
|
|
194
|
+
this.simulate_data_only(trial, simulation_options);
|
|
195
|
+
}
|
|
196
|
+
if (simulation_mode == "visual") {
|
|
197
|
+
this.simulate_visual(trial, simulation_options, load_callback);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
simulate_data_only(trial, simulation_options) {
|
|
201
|
+
const data = this.create_simulation_data(trial, simulation_options);
|
|
202
|
+
this.jsPsych.finishTrial(data);
|
|
203
|
+
}
|
|
204
|
+
simulate_visual(trial, simulation_options, load_callback) {
|
|
205
|
+
const data = this.create_simulation_data(trial, simulation_options);
|
|
206
|
+
const display_element = this.jsPsych.getDisplayElement();
|
|
207
|
+
const respond = () => {
|
|
208
|
+
if (data.rt !== null) {
|
|
209
|
+
this.jsPsych.pluginAPI.pressKey(data.response, data.rt);
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
this.trial(display_element, trial, () => {
|
|
213
|
+
load_callback();
|
|
214
|
+
if (!trial.response_allowed_while_playing) {
|
|
215
|
+
this.audio.addEventListener("ended", respond);
|
|
216
|
+
}
|
|
217
|
+
else {
|
|
218
|
+
respond();
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
create_simulation_data(trial, simulation_options) {
|
|
223
|
+
const default_data = {
|
|
224
|
+
stimulus: trial.stimulus,
|
|
225
|
+
rt: this.jsPsych.randomization.sampleExGaussian(500, 50, 1 / 150, true),
|
|
226
|
+
response: this.jsPsych.pluginAPI.getValidKey(trial.choices),
|
|
227
|
+
};
|
|
228
|
+
const data = this.jsPsych.pluginAPI.mergeSimulationData(default_data, simulation_options);
|
|
229
|
+
this.jsPsych.pluginAPI.ensureSimulationDataConsistency(trial, data);
|
|
230
|
+
return data;
|
|
231
|
+
}
|
|
192
232
|
}
|
|
193
233
|
AudioKeyboardResponsePlugin.info = info;
|
|
194
234
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.browser.js","sources":["../src/index.ts"],"sourcesContent":["import { JsPsych, JsPsychPlugin, ParameterType, TrialType } from \"jspsych\";\n\nconst info = <const>{\n name: \"audio-keyboard-response\",\n parameters: {\n /** The audio file to be played. */\n stimulus: {\n type: ParameterType.AUDIO,\n pretty_name: \"Stimulus\",\n default: undefined,\n },\n /** Array containing the key(s) the subject is allowed to press to respond to the stimulus. */\n choices: {\n type: ParameterType.KEYS,\n pretty_name: \"Choices\",\n default: \"ALL_KEYS\",\n },\n /** Any content here will be displayed below the stimulus. */\n prompt: {\n type: ParameterType.HTML_STRING,\n pretty_name: \"Prompt\",\n default: null,\n },\n /** The maximum duration to wait for a response. */\n trial_duration: {\n type: ParameterType.INT,\n pretty_name: \"Trial duration\",\n default: null,\n },\n /** If true, the trial will end when user makes a response. */\n response_ends_trial: {\n type: ParameterType.BOOL,\n pretty_name: \"Response ends trial\",\n default: true,\n },\n /** If true, then the trial will end as soon as the audio file finishes playing. */\n trial_ends_after_audio: {\n type: ParameterType.BOOL,\n pretty_name: \"Trial ends after audio\",\n default: false,\n },\n /** If true, then responses are allowed while the audio is playing. If false, then the audio must finish playing before a response is accepted. */\n response_allowed_while_playing: {\n type: ParameterType.BOOL,\n pretty_name: \"Response allowed while playing\",\n default: true,\n },\n },\n};\n\ntype Info = typeof info;\n\n/**\n * **audio-keyboard-response**\n *\n * jsPsych plugin for playing an audio file and getting a keyboard response\n *\n * @author Josh de Leeuw\n * @see {@link https://www.jspsych.org/plugins/jspsych-audio-keyboard-response/ audio-keyboard-response plugin documentation on jspsych.org}\n */\nclass AudioKeyboardResponsePlugin implements JsPsychPlugin<Info> {\n static info = info;\n\n constructor(private jsPsych: JsPsych) {}\n\n trial(display_element: HTMLElement, trial: TrialType<Info>, on_load: () => void) {\n // hold the .resolve() function from the Promise that ends the trial\n let trial_complete;\n\n // setup stimulus\n var context = this.jsPsych.pluginAPI.audioContext();\n var audio;\n\n // store response\n var response = {\n rt: null,\n key: null,\n };\n\n // record webaudio context start time\n var startTime;\n\n // load audio file\n this.jsPsych.pluginAPI\n .getAudioBuffer(trial.stimulus)\n .then(function (buffer) {\n if (context !== null) {\n audio = context.createBufferSource();\n audio.buffer = buffer;\n audio.connect(context.destination);\n } else {\n audio = buffer;\n audio.currentTime = 0;\n }\n setupTrial();\n })\n .catch(function (err) {\n console.error(\n `Failed to load audio file \"${trial.stimulus}\". Try checking the file path. We recommend using the preload plugin to load audio files.`\n );\n console.error(err);\n });\n\n const setupTrial = () => {\n // set up end event if trial needs it\n if (trial.trial_ends_after_audio) {\n audio.addEventListener(\"ended\", end_trial);\n }\n\n // show prompt if there is one\n if (trial.prompt !== null) {\n display_element.innerHTML = trial.prompt;\n }\n\n // start audio\n if (context !== null) {\n startTime = context.currentTime;\n audio.start(startTime);\n } else {\n audio.play();\n }\n\n // start keyboard listener when trial starts or sound ends\n if (trial.response_allowed_while_playing) {\n setup_keyboard_listener();\n } else if (!trial.trial_ends_after_audio) {\n audio.addEventListener(\"ended\", setup_keyboard_listener);\n }\n\n // end trial if time limit is set\n if (trial.trial_duration !== null) {\n this.jsPsych.pluginAPI.setTimeout(function () {\n end_trial();\n }, trial.trial_duration);\n }\n\n on_load();\n };\n\n // function to end trial when it is time\n const end_trial = () => {\n // kill any remaining setTimeout handlers\n this.jsPsych.pluginAPI.clearAllTimeouts();\n\n // stop the audio file if it is playing\n // remove end event listeners if they exist\n if (context !== null) {\n audio.stop();\n } else {\n audio.pause();\n }\n\n audio.removeEventListener(\"ended\", end_trial);\n audio.removeEventListener(\"ended\", setup_keyboard_listener);\n\n // kill keyboard listeners\n this.jsPsych.pluginAPI.cancelAllKeyboardResponses();\n\n // gather the data to store for the trial\n var trial_data = {\n rt: response.rt,\n stimulus: trial.stimulus,\n response: response.key,\n };\n\n // clear the display\n display_element.innerHTML = \"\";\n\n // move on to the next trial\n this.jsPsych.finishTrial(trial_data);\n\n trial_complete();\n };\n\n // function to handle responses by the subject\n function after_response(info) {\n // only record the first response\n if (response.key == null) {\n response = info;\n }\n\n if (trial.response_ends_trial) {\n end_trial();\n }\n }\n\n const setup_keyboard_listener = () => {\n // start the response listener\n if (context !== null) {\n this.jsPsych.pluginAPI.getKeyboardResponse({\n callback_function: after_response,\n valid_responses: trial.choices,\n rt_method: \"audio\",\n persist: false,\n allow_held_key: false,\n audio_context: context,\n audio_context_start_time: startTime,\n });\n } else {\n this.jsPsych.pluginAPI.getKeyboardResponse({\n callback_function: after_response,\n valid_responses: trial.choices,\n rt_method: \"performance\",\n persist: false,\n allow_held_key: false,\n });\n }\n };\n\n return new Promise((resolve) => {\n trial_complete = resolve;\n });\n }\n}\n\nexport default AudioKeyboardResponsePlugin;\n"],"names":["ParameterType"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.browser.js","sources":["../src/index.ts"],"sourcesContent":["import { JsPsych, JsPsychPlugin, ParameterType, TrialType } from \"jspsych\";\n\nconst info = <const>{\n name: \"audio-keyboard-response\",\n parameters: {\n /** The audio file to be played. */\n stimulus: {\n type: ParameterType.AUDIO,\n pretty_name: \"Stimulus\",\n default: undefined,\n },\n /** Array containing the key(s) the subject is allowed to press to respond to the stimulus. */\n choices: {\n type: ParameterType.KEYS,\n pretty_name: \"Choices\",\n default: \"ALL_KEYS\",\n },\n /** Any content here will be displayed below the stimulus. */\n prompt: {\n type: ParameterType.HTML_STRING,\n pretty_name: \"Prompt\",\n default: null,\n },\n /** The maximum duration to wait for a response. */\n trial_duration: {\n type: ParameterType.INT,\n pretty_name: \"Trial duration\",\n default: null,\n },\n /** If true, the trial will end when user makes a response. */\n response_ends_trial: {\n type: ParameterType.BOOL,\n pretty_name: \"Response ends trial\",\n default: true,\n },\n /** If true, then the trial will end as soon as the audio file finishes playing. */\n trial_ends_after_audio: {\n type: ParameterType.BOOL,\n pretty_name: \"Trial ends after audio\",\n default: false,\n },\n /** If true, then responses are allowed while the audio is playing. If false, then the audio must finish playing before a response is accepted. */\n response_allowed_while_playing: {\n type: ParameterType.BOOL,\n pretty_name: \"Response allowed while playing\",\n default: true,\n },\n },\n};\n\ntype Info = typeof info;\n\n/**\n * **audio-keyboard-response**\n *\n * jsPsych plugin for playing an audio file and getting a keyboard response\n *\n * @author Josh de Leeuw\n * @see {@link https://www.jspsych.org/plugins/jspsych-audio-keyboard-response/ audio-keyboard-response plugin documentation on jspsych.org}\n */\nclass AudioKeyboardResponsePlugin implements JsPsychPlugin<Info> {\n static info = info;\n private audio;\n\n constructor(private jsPsych: JsPsych) {}\n\n trial(display_element: HTMLElement, trial: TrialType<Info>, on_load: () => void) {\n // hold the .resolve() function from the Promise that ends the trial\n let trial_complete;\n\n // setup stimulus\n var context = this.jsPsych.pluginAPI.audioContext();\n\n // store response\n var response = {\n rt: null,\n key: null,\n };\n\n // record webaudio context start time\n var startTime;\n\n // load audio file\n this.jsPsych.pluginAPI\n .getAudioBuffer(trial.stimulus)\n .then((buffer) => {\n if (context !== null) {\n this.audio = context.createBufferSource();\n this.audio.buffer = buffer;\n this.audio.connect(context.destination);\n } else {\n this.audio = buffer;\n this.audio.currentTime = 0;\n }\n setupTrial();\n })\n .catch((err) => {\n console.error(\n `Failed to load audio file \"${trial.stimulus}\". Try checking the file path. We recommend using the preload plugin to load audio files.`\n );\n console.error(err);\n });\n\n const setupTrial = () => {\n // set up end event if trial needs it\n if (trial.trial_ends_after_audio) {\n this.audio.addEventListener(\"ended\", end_trial);\n }\n\n // show prompt if there is one\n if (trial.prompt !== null) {\n display_element.innerHTML = trial.prompt;\n }\n\n // start audio\n if (context !== null) {\n startTime = context.currentTime;\n this.audio.start(startTime);\n } else {\n this.audio.play();\n }\n\n // start keyboard listener when trial starts or sound ends\n if (trial.response_allowed_while_playing) {\n setup_keyboard_listener();\n } else if (!trial.trial_ends_after_audio) {\n this.audio.addEventListener(\"ended\", setup_keyboard_listener);\n }\n\n // end trial if time limit is set\n if (trial.trial_duration !== null) {\n this.jsPsych.pluginAPI.setTimeout(() => {\n end_trial();\n }, trial.trial_duration);\n }\n\n on_load();\n };\n\n // function to end trial when it is time\n const end_trial = () => {\n // kill any remaining setTimeout handlers\n this.jsPsych.pluginAPI.clearAllTimeouts();\n\n // stop the audio file if it is playing\n // remove end event listeners if they exist\n if (context !== null) {\n this.audio.stop();\n } else {\n this.audio.pause();\n }\n\n this.audio.removeEventListener(\"ended\", end_trial);\n this.audio.removeEventListener(\"ended\", setup_keyboard_listener);\n\n // kill keyboard listeners\n this.jsPsych.pluginAPI.cancelAllKeyboardResponses();\n\n // gather the data to store for the trial\n var trial_data = {\n rt: response.rt,\n stimulus: trial.stimulus,\n response: response.key,\n };\n\n // clear the display\n display_element.innerHTML = \"\";\n\n // move on to the next trial\n this.jsPsych.finishTrial(trial_data);\n\n trial_complete();\n };\n\n // function to handle responses by the subject\n function after_response(info) {\n // only record the first response\n if (response.key == null) {\n response = info;\n }\n\n if (trial.response_ends_trial) {\n end_trial();\n }\n }\n\n const setup_keyboard_listener = () => {\n // start the response listener\n if (context !== null) {\n this.jsPsych.pluginAPI.getKeyboardResponse({\n callback_function: after_response,\n valid_responses: trial.choices,\n rt_method: \"audio\",\n persist: false,\n allow_held_key: false,\n audio_context: context,\n audio_context_start_time: startTime,\n });\n } else {\n this.jsPsych.pluginAPI.getKeyboardResponse({\n callback_function: after_response,\n valid_responses: trial.choices,\n rt_method: \"performance\",\n persist: false,\n allow_held_key: false,\n });\n }\n };\n\n return new Promise((resolve) => {\n trial_complete = resolve;\n });\n }\n\n simulate(\n trial: TrialType<Info>,\n simulation_mode,\n simulation_options: any,\n load_callback: () => void\n ) {\n if (simulation_mode == \"data-only\") {\n load_callback();\n this.simulate_data_only(trial, simulation_options);\n }\n if (simulation_mode == \"visual\") {\n this.simulate_visual(trial, simulation_options, load_callback);\n }\n }\n\n private simulate_data_only(trial: TrialType<Info>, simulation_options) {\n const data = this.create_simulation_data(trial, simulation_options);\n\n this.jsPsych.finishTrial(data);\n }\n\n private simulate_visual(trial: TrialType<Info>, simulation_options, load_callback: () => void) {\n const data = this.create_simulation_data(trial, simulation_options);\n\n const display_element = this.jsPsych.getDisplayElement();\n\n const respond = () => {\n if (data.rt !== null) {\n this.jsPsych.pluginAPI.pressKey(data.response, data.rt);\n }\n };\n\n this.trial(display_element, trial, () => {\n load_callback();\n if (!trial.response_allowed_while_playing) {\n this.audio.addEventListener(\"ended\", respond);\n } else {\n respond();\n }\n });\n }\n\n private create_simulation_data(trial: TrialType<Info>, simulation_options) {\n const default_data = {\n stimulus: trial.stimulus,\n rt: this.jsPsych.randomization.sampleExGaussian(500, 50, 1 / 150, true),\n response: this.jsPsych.pluginAPI.getValidKey(trial.choices),\n };\n\n const data = this.jsPsych.pluginAPI.mergeSimulationData(default_data, simulation_options);\n\n this.jsPsych.pluginAPI.ensureSimulationDataConsistency(trial, data);\n\n return data;\n }\n}\n\nexport default AudioKeyboardResponsePlugin;\n"],"names":["ParameterType"],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
var jsPsychAudioKeyboardResponse=function(e){"use strict";const t={name:"audio-keyboard-response",parameters:{stimulus:{type:e.ParameterType.AUDIO,pretty_name:"Stimulus",default:void 0},choices:{type:e.ParameterType.KEYS,pretty_name:"Choices",default:"ALL_KEYS"},prompt:{type:e.ParameterType.HTML_STRING,pretty_name:"Prompt",default:null},trial_duration:{type:e.ParameterType.INT,pretty_name:"Trial duration",default:null},response_ends_trial:{type:e.ParameterType.BOOL,pretty_name:"Response ends trial",default:!0},trial_ends_after_audio:{type:e.ParameterType.BOOL,pretty_name:"Trial ends after audio",default:!1},response_allowed_while_playing:{type:e.ParameterType.BOOL,pretty_name:"Response allowed while playing",default:!0}}};class s{constructor(e){this.jsPsych=e}trial(e,t,s){let
|
|
1
|
+
var jsPsychAudioKeyboardResponse=function(e){"use strict";const t={name:"audio-keyboard-response",parameters:{stimulus:{type:e.ParameterType.AUDIO,pretty_name:"Stimulus",default:void 0},choices:{type:e.ParameterType.KEYS,pretty_name:"Choices",default:"ALL_KEYS"},prompt:{type:e.ParameterType.HTML_STRING,pretty_name:"Prompt",default:null},trial_duration:{type:e.ParameterType.INT,pretty_name:"Trial duration",default:null},response_ends_trial:{type:e.ParameterType.BOOL,pretty_name:"Response ends trial",default:!0},trial_ends_after_audio:{type:e.ParameterType.BOOL,pretty_name:"Trial ends after audio",default:!1},response_allowed_while_playing:{type:e.ParameterType.BOOL,pretty_name:"Response allowed while playing",default:!0}}};class s{constructor(e){this.jsPsych=e}trial(e,t,s){let i;var a,n=this.jsPsych.pluginAPI.audioContext(),l={rt:null,key:null};this.jsPsych.pluginAPI.getAudioBuffer(t.stimulus).then((e=>{null!==n?(this.audio=n.createBufferSource(),this.audio.buffer=e,this.audio.connect(n.destination)):(this.audio=e,this.audio.currentTime=0),r()})).catch((e=>{console.error(`Failed to load audio file "${t.stimulus}". Try checking the file path. We recommend using the preload plugin to load audio files.`),console.error(e)}));const r=()=>{t.trial_ends_after_audio&&this.audio.addEventListener("ended",o),null!==t.prompt&&(e.innerHTML=t.prompt),null!==n?(a=n.currentTime,this.audio.start(a)):this.audio.play(),t.response_allowed_while_playing?d():t.trial_ends_after_audio||this.audio.addEventListener("ended",d),null!==t.trial_duration&&this.jsPsych.pluginAPI.setTimeout((()=>{o()}),t.trial_duration),s()},o=()=>{this.jsPsych.pluginAPI.clearAllTimeouts(),null!==n?this.audio.stop():this.audio.pause(),this.audio.removeEventListener("ended",o),this.audio.removeEventListener("ended",d),this.jsPsych.pluginAPI.cancelAllKeyboardResponses();var s={rt:l.rt,stimulus:t.stimulus,response:l.key};e.innerHTML="",this.jsPsych.finishTrial(s),i()};function u(e){null==l.key&&(l=e),t.response_ends_trial&&o()}const d=()=>{null!==n?this.jsPsych.pluginAPI.getKeyboardResponse({callback_function:u,valid_responses:t.choices,rt_method:"audio",persist:!1,allow_held_key:!1,audio_context:n,audio_context_start_time:a}):this.jsPsych.pluginAPI.getKeyboardResponse({callback_function:u,valid_responses:t.choices,rt_method:"performance",persist:!1,allow_held_key:!1})};return new Promise((e=>{i=e}))}simulate(e,t,s,i){"data-only"==t&&(i(),this.simulate_data_only(e,s)),"visual"==t&&this.simulate_visual(e,s,i)}simulate_data_only(e,t){const s=this.create_simulation_data(e,t);this.jsPsych.finishTrial(s)}simulate_visual(e,t,s){const i=this.create_simulation_data(e,t),a=this.jsPsych.getDisplayElement(),n=()=>{null!==i.rt&&this.jsPsych.pluginAPI.pressKey(i.response,i.rt)};this.trial(a,e,(()=>{s(),e.response_allowed_while_playing?n():this.audio.addEventListener("ended",n)}))}create_simulation_data(e,t){const s={stimulus:e.stimulus,rt:this.jsPsych.randomization.sampleExGaussian(500,50,1/150,!0),response:this.jsPsych.pluginAPI.getValidKey(e.choices)},i=this.jsPsych.pluginAPI.mergeSimulationData(s,t);return this.jsPsych.pluginAPI.ensureSimulationDataConsistency(e,i),i}}return s.info=t,s}(jsPsychModule);
|
|
2
2
|
//# sourceMappingURL=index.browser.min.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.browser.min.js","sources":["../src/index.ts"],"sourcesContent":["import { JsPsych, JsPsychPlugin, ParameterType, TrialType } from \"jspsych\";\n\nconst info = <const>{\n name: \"audio-keyboard-response\",\n parameters: {\n /** The audio file to be played. */\n stimulus: {\n type: ParameterType.AUDIO,\n pretty_name: \"Stimulus\",\n default: undefined,\n },\n /** Array containing the key(s) the subject is allowed to press to respond to the stimulus. */\n choices: {\n type: ParameterType.KEYS,\n pretty_name: \"Choices\",\n default: \"ALL_KEYS\",\n },\n /** Any content here will be displayed below the stimulus. */\n prompt: {\n type: ParameterType.HTML_STRING,\n pretty_name: \"Prompt\",\n default: null,\n },\n /** The maximum duration to wait for a response. */\n trial_duration: {\n type: ParameterType.INT,\n pretty_name: \"Trial duration\",\n default: null,\n },\n /** If true, the trial will end when user makes a response. */\n response_ends_trial: {\n type: ParameterType.BOOL,\n pretty_name: \"Response ends trial\",\n default: true,\n },\n /** If true, then the trial will end as soon as the audio file finishes playing. */\n trial_ends_after_audio: {\n type: ParameterType.BOOL,\n pretty_name: \"Trial ends after audio\",\n default: false,\n },\n /** If true, then responses are allowed while the audio is playing. If false, then the audio must finish playing before a response is accepted. */\n response_allowed_while_playing: {\n type: ParameterType.BOOL,\n pretty_name: \"Response allowed while playing\",\n default: true,\n },\n },\n};\n\ntype Info = typeof info;\n\n/**\n * **audio-keyboard-response**\n *\n * jsPsych plugin for playing an audio file and getting a keyboard response\n *\n * @author Josh de Leeuw\n * @see {@link https://www.jspsych.org/plugins/jspsych-audio-keyboard-response/ audio-keyboard-response plugin documentation on jspsych.org}\n */\nclass AudioKeyboardResponsePlugin implements JsPsychPlugin<Info> {\n static info = info;\n\n constructor(private jsPsych: JsPsych) {}\n\n trial(display_element: HTMLElement, trial: TrialType<Info>, on_load: () => void) {\n // hold the .resolve() function from the Promise that ends the trial\n let trial_complete;\n\n // setup stimulus\n var context = this.jsPsych.pluginAPI.audioContext();\n var audio;\n\n // store response\n var response = {\n rt: null,\n key: null,\n };\n\n // record webaudio context start time\n var startTime;\n\n // load audio file\n this.jsPsych.pluginAPI\n .getAudioBuffer(trial.stimulus)\n .then(function (buffer) {\n if (context !== null) {\n audio = context.createBufferSource();\n audio.buffer = buffer;\n audio.connect(context.destination);\n } else {\n audio = buffer;\n audio.currentTime = 0;\n }\n setupTrial();\n })\n .catch(function (err) {\n console.error(\n `Failed to load audio file \"${trial.stimulus}\". Try checking the file path. We recommend using the preload plugin to load audio files.`\n );\n console.error(err);\n });\n\n const setupTrial = () => {\n // set up end event if trial needs it\n if (trial.trial_ends_after_audio) {\n audio.addEventListener(\"ended\", end_trial);\n }\n\n // show prompt if there is one\n if (trial.prompt !== null) {\n display_element.innerHTML = trial.prompt;\n }\n\n // start audio\n if (context !== null) {\n startTime = context.currentTime;\n audio.start(startTime);\n } else {\n audio.play();\n }\n\n // start keyboard listener when trial starts or sound ends\n if (trial.response_allowed_while_playing) {\n setup_keyboard_listener();\n } else if (!trial.trial_ends_after_audio) {\n audio.addEventListener(\"ended\", setup_keyboard_listener);\n }\n\n // end trial if time limit is set\n if (trial.trial_duration !== null) {\n this.jsPsych.pluginAPI.setTimeout(function () {\n end_trial();\n }, trial.trial_duration);\n }\n\n on_load();\n };\n\n // function to end trial when it is time\n const end_trial = () => {\n // kill any remaining setTimeout handlers\n this.jsPsych.pluginAPI.clearAllTimeouts();\n\n // stop the audio file if it is playing\n // remove end event listeners if they exist\n if (context !== null) {\n audio.stop();\n } else {\n audio.pause();\n }\n\n audio.removeEventListener(\"ended\", end_trial);\n audio.removeEventListener(\"ended\", setup_keyboard_listener);\n\n // kill keyboard listeners\n this.jsPsych.pluginAPI.cancelAllKeyboardResponses();\n\n // gather the data to store for the trial\n var trial_data = {\n rt: response.rt,\n stimulus: trial.stimulus,\n response: response.key,\n };\n\n // clear the display\n display_element.innerHTML = \"\";\n\n // move on to the next trial\n this.jsPsych.finishTrial(trial_data);\n\n trial_complete();\n };\n\n // function to handle responses by the subject\n function after_response(info) {\n // only record the first response\n if (response.key == null) {\n response = info;\n }\n\n if (trial.response_ends_trial) {\n end_trial();\n }\n }\n\n const setup_keyboard_listener = () => {\n // start the response listener\n if (context !== null) {\n this.jsPsych.pluginAPI.getKeyboardResponse({\n callback_function: after_response,\n valid_responses: trial.choices,\n rt_method: \"audio\",\n persist: false,\n allow_held_key: false,\n audio_context: context,\n audio_context_start_time: startTime,\n });\n } else {\n this.jsPsych.pluginAPI.getKeyboardResponse({\n callback_function: after_response,\n valid_responses: trial.choices,\n rt_method: \"performance\",\n persist: false,\n allow_held_key: false,\n });\n }\n };\n\n return new Promise((resolve) => {\n trial_complete = resolve;\n });\n }\n}\n\nexport default AudioKeyboardResponsePlugin;\n"],"names":["info","name","parameters","stimulus","type","ParameterType","AUDIO","pretty_name","default","undefined","choices","KEYS","prompt","HTML_STRING","trial_duration","INT","response_ends_trial","BOOL","trial_ends_after_audio","response_allowed_while_playing","AudioKeyboardResponsePlugin","constructor","jsPsych","this","trial","display_element","on_load","trial_complete","audio","startTime","context","pluginAPI","audioContext","response","rt","key","getAudioBuffer","then","buffer","createBufferSource","connect","destination","currentTime","setupTrial","catch","err","console","error","addEventListener","end_trial","innerHTML","start","play","setup_keyboard_listener","setTimeout","clearAllTimeouts","stop","pause","removeEventListener","cancelAllKeyboardResponses","trial_data","finishTrial","after_response","getKeyboardResponse","callback_function","valid_responses","rt_method","persist","allow_held_key","audio_context","audio_context_start_time","Promise","resolve"],"mappings":"0DAEA,MAAMA,EAAc,CAClBC,KAAM,0BACNC,WAAY,CAEVC,SAAU,CACRC,KAAMC,gBAAcC,MACpBC,YAAa,WACbC,aAASC,GAGXC,QAAS,CACPN,KAAMC,gBAAcM,KACpBJ,YAAa,UACbC,QAAS,YAGXI,OAAQ,CACNR,KAAMC,gBAAcQ,YACpBN,YAAa,SACbC,QAAS,MAGXM,eAAgB,CACdV,KAAMC,gBAAcU,IACpBR,YAAa,iBACbC,QAAS,MAGXQ,oBAAqB,CACnBZ,KAAMC,gBAAcY,KACpBV,YAAa,sBACbC,SAAS,GAGXU,uBAAwB,CACtBd,KAAMC,gBAAcY,KACpBV,YAAa,yBACbC,SAAS,GAGXW,+BAAgC,CAC9Bf,KAAMC,gBAAcY,KACpBV,YAAa,iCACbC,SAAS,KAef,MAAMY,EAGJC,YAAoBC,GAAAC,aAAAD,EAEpBE,MAAMC,EAA8BD,EAAwBE,GAE1D,IAAIC,EAGJ,IACIC,EASAC,EAVAC,EAAUP,KAAKD,QAAQS,UAAUC,eAIjCC,EAAW,CACbC,GAAI,KACJC,IAAK,MAOPZ,KAAKD,QAAQS,UACVK,eAAeZ,EAAMrB,UACrBkC,MAAK,SAAUC,GACE,OAAZR,IACFF,EAAQE,EAAQS,sBACVD,OAASA,EACfV,EAAMY,QAAQV,EAAQW,eAEtBb,EAAQU,GACFI,YAAc,EAEtBC,OAEDC,OAAM,SAAUC,GACfC,QAAQC,MACN,8BAA8BvB,EAAMrB,qGAEtC2C,QAAQC,MAAMF,MAGlB,MAAMF,EAAa,KAEbnB,EAAMN,wBACRU,EAAMoB,iBAAiB,QAASC,GAIb,OAAjBzB,EAAMZ,SACRa,EAAgByB,UAAY1B,EAAMZ,QAIpB,OAAZkB,GACFD,EAAYC,EAAQY,YACpBd,EAAMuB,MAAMtB,IAEZD,EAAMwB,OAIJ5B,EAAML,+BACRkC,IACU7B,EAAMN,wBAChBU,EAAMoB,iBAAiB,QAASK,GAIL,OAAzB7B,EAAMV,gBACRS,KAAKD,QAAQS,UAAUuB,YAAW,WAChCL,MACCzB,EAAMV,gBAGXY,KAIIuB,EAAY,KAEhB1B,KAAKD,QAAQS,UAAUwB,mBAIP,OAAZzB,EACFF,EAAM4B,OAEN5B,EAAM6B,QAGR7B,EAAM8B,oBAAoB,QAAST,GACnCrB,EAAM8B,oBAAoB,QAASL,GAGnC9B,KAAKD,QAAQS,UAAU4B,6BAGvB,IAAIC,EAAa,CACf1B,GAAID,EAASC,GACb/B,SAAUqB,EAAMrB,SAChB8B,SAAUA,EAASE,KAIrBV,EAAgByB,UAAY,GAG5B3B,KAAKD,QAAQuC,YAAYD,GAEzBjC,KAIF,SAASmC,EAAe9D,GAEF,MAAhBiC,EAASE,MACXF,EAAWjC,GAGTwB,EAAMR,qBACRiC,IAIJ,MAAMI,EAA0B,KAEd,OAAZvB,EACFP,KAAKD,QAAQS,UAAUgC,oBAAoB,CACzCC,kBAAmBF,EACnBG,gBAAiBzC,EAAMd,QACvBwD,UAAW,QACXC,SAAS,EACTC,gBAAgB,EAChBC,cAAevC,EACfwC,yBAA0BzC,IAG5BN,KAAKD,QAAQS,UAAUgC,oBAAoB,CACzCC,kBAAmBF,EACnBG,gBAAiBzC,EAAMd,QACvBwD,UAAW,cACXC,SAAS,EACTC,gBAAgB,KAKtB,OAAO,IAAIG,SAASC,IAClB7C,EAAiB6C,aArJdpD,OAAOpB"}
|
|
1
|
+
{"version":3,"file":"index.browser.min.js","sources":["../src/index.ts"],"sourcesContent":["import { JsPsych, JsPsychPlugin, ParameterType, TrialType } from \"jspsych\";\n\nconst info = <const>{\n name: \"audio-keyboard-response\",\n parameters: {\n /** The audio file to be played. */\n stimulus: {\n type: ParameterType.AUDIO,\n pretty_name: \"Stimulus\",\n default: undefined,\n },\n /** Array containing the key(s) the subject is allowed to press to respond to the stimulus. */\n choices: {\n type: ParameterType.KEYS,\n pretty_name: \"Choices\",\n default: \"ALL_KEYS\",\n },\n /** Any content here will be displayed below the stimulus. */\n prompt: {\n type: ParameterType.HTML_STRING,\n pretty_name: \"Prompt\",\n default: null,\n },\n /** The maximum duration to wait for a response. */\n trial_duration: {\n type: ParameterType.INT,\n pretty_name: \"Trial duration\",\n default: null,\n },\n /** If true, the trial will end when user makes a response. */\n response_ends_trial: {\n type: ParameterType.BOOL,\n pretty_name: \"Response ends trial\",\n default: true,\n },\n /** If true, then the trial will end as soon as the audio file finishes playing. */\n trial_ends_after_audio: {\n type: ParameterType.BOOL,\n pretty_name: \"Trial ends after audio\",\n default: false,\n },\n /** If true, then responses are allowed while the audio is playing. If false, then the audio must finish playing before a response is accepted. */\n response_allowed_while_playing: {\n type: ParameterType.BOOL,\n pretty_name: \"Response allowed while playing\",\n default: true,\n },\n },\n};\n\ntype Info = typeof info;\n\n/**\n * **audio-keyboard-response**\n *\n * jsPsych plugin for playing an audio file and getting a keyboard response\n *\n * @author Josh de Leeuw\n * @see {@link https://www.jspsych.org/plugins/jspsych-audio-keyboard-response/ audio-keyboard-response plugin documentation on jspsych.org}\n */\nclass AudioKeyboardResponsePlugin implements JsPsychPlugin<Info> {\n static info = info;\n private audio;\n\n constructor(private jsPsych: JsPsych) {}\n\n trial(display_element: HTMLElement, trial: TrialType<Info>, on_load: () => void) {\n // hold the .resolve() function from the Promise that ends the trial\n let trial_complete;\n\n // setup stimulus\n var context = this.jsPsych.pluginAPI.audioContext();\n\n // store response\n var response = {\n rt: null,\n key: null,\n };\n\n // record webaudio context start time\n var startTime;\n\n // load audio file\n this.jsPsych.pluginAPI\n .getAudioBuffer(trial.stimulus)\n .then((buffer) => {\n if (context !== null) {\n this.audio = context.createBufferSource();\n this.audio.buffer = buffer;\n this.audio.connect(context.destination);\n } else {\n this.audio = buffer;\n this.audio.currentTime = 0;\n }\n setupTrial();\n })\n .catch((err) => {\n console.error(\n `Failed to load audio file \"${trial.stimulus}\". Try checking the file path. We recommend using the preload plugin to load audio files.`\n );\n console.error(err);\n });\n\n const setupTrial = () => {\n // set up end event if trial needs it\n if (trial.trial_ends_after_audio) {\n this.audio.addEventListener(\"ended\", end_trial);\n }\n\n // show prompt if there is one\n if (trial.prompt !== null) {\n display_element.innerHTML = trial.prompt;\n }\n\n // start audio\n if (context !== null) {\n startTime = context.currentTime;\n this.audio.start(startTime);\n } else {\n this.audio.play();\n }\n\n // start keyboard listener when trial starts or sound ends\n if (trial.response_allowed_while_playing) {\n setup_keyboard_listener();\n } else if (!trial.trial_ends_after_audio) {\n this.audio.addEventListener(\"ended\", setup_keyboard_listener);\n }\n\n // end trial if time limit is set\n if (trial.trial_duration !== null) {\n this.jsPsych.pluginAPI.setTimeout(() => {\n end_trial();\n }, trial.trial_duration);\n }\n\n on_load();\n };\n\n // function to end trial when it is time\n const end_trial = () => {\n // kill any remaining setTimeout handlers\n this.jsPsych.pluginAPI.clearAllTimeouts();\n\n // stop the audio file if it is playing\n // remove end event listeners if they exist\n if (context !== null) {\n this.audio.stop();\n } else {\n this.audio.pause();\n }\n\n this.audio.removeEventListener(\"ended\", end_trial);\n this.audio.removeEventListener(\"ended\", setup_keyboard_listener);\n\n // kill keyboard listeners\n this.jsPsych.pluginAPI.cancelAllKeyboardResponses();\n\n // gather the data to store for the trial\n var trial_data = {\n rt: response.rt,\n stimulus: trial.stimulus,\n response: response.key,\n };\n\n // clear the display\n display_element.innerHTML = \"\";\n\n // move on to the next trial\n this.jsPsych.finishTrial(trial_data);\n\n trial_complete();\n };\n\n // function to handle responses by the subject\n function after_response(info) {\n // only record the first response\n if (response.key == null) {\n response = info;\n }\n\n if (trial.response_ends_trial) {\n end_trial();\n }\n }\n\n const setup_keyboard_listener = () => {\n // start the response listener\n if (context !== null) {\n this.jsPsych.pluginAPI.getKeyboardResponse({\n callback_function: after_response,\n valid_responses: trial.choices,\n rt_method: \"audio\",\n persist: false,\n allow_held_key: false,\n audio_context: context,\n audio_context_start_time: startTime,\n });\n } else {\n this.jsPsych.pluginAPI.getKeyboardResponse({\n callback_function: after_response,\n valid_responses: trial.choices,\n rt_method: \"performance\",\n persist: false,\n allow_held_key: false,\n });\n }\n };\n\n return new Promise((resolve) => {\n trial_complete = resolve;\n });\n }\n\n simulate(\n trial: TrialType<Info>,\n simulation_mode,\n simulation_options: any,\n load_callback: () => void\n ) {\n if (simulation_mode == \"data-only\") {\n load_callback();\n this.simulate_data_only(trial, simulation_options);\n }\n if (simulation_mode == \"visual\") {\n this.simulate_visual(trial, simulation_options, load_callback);\n }\n }\n\n private simulate_data_only(trial: TrialType<Info>, simulation_options) {\n const data = this.create_simulation_data(trial, simulation_options);\n\n this.jsPsych.finishTrial(data);\n }\n\n private simulate_visual(trial: TrialType<Info>, simulation_options, load_callback: () => void) {\n const data = this.create_simulation_data(trial, simulation_options);\n\n const display_element = this.jsPsych.getDisplayElement();\n\n const respond = () => {\n if (data.rt !== null) {\n this.jsPsych.pluginAPI.pressKey(data.response, data.rt);\n }\n };\n\n this.trial(display_element, trial, () => {\n load_callback();\n if (!trial.response_allowed_while_playing) {\n this.audio.addEventListener(\"ended\", respond);\n } else {\n respond();\n }\n });\n }\n\n private create_simulation_data(trial: TrialType<Info>, simulation_options) {\n const default_data = {\n stimulus: trial.stimulus,\n rt: this.jsPsych.randomization.sampleExGaussian(500, 50, 1 / 150, true),\n response: this.jsPsych.pluginAPI.getValidKey(trial.choices),\n };\n\n const data = this.jsPsych.pluginAPI.mergeSimulationData(default_data, simulation_options);\n\n this.jsPsych.pluginAPI.ensureSimulationDataConsistency(trial, data);\n\n return data;\n }\n}\n\nexport default AudioKeyboardResponsePlugin;\n"],"names":["info","name","parameters","stimulus","type","ParameterType","AUDIO","pretty_name","default","undefined","choices","KEYS","prompt","HTML_STRING","trial_duration","INT","response_ends_trial","BOOL","trial_ends_after_audio","response_allowed_while_playing","AudioKeyboardResponsePlugin","constructor","jsPsych","this","trial","display_element","on_load","trial_complete","startTime","context","pluginAPI","audioContext","response","rt","key","getAudioBuffer","then","buffer","audio","createBufferSource","connect","destination","currentTime","setupTrial","catch","err","console","error","addEventListener","end_trial","innerHTML","start","play","setup_keyboard_listener","setTimeout","clearAllTimeouts","stop","pause","removeEventListener","cancelAllKeyboardResponses","trial_data","finishTrial","after_response","getKeyboardResponse","callback_function","valid_responses","rt_method","persist","allow_held_key","audio_context","audio_context_start_time","Promise","resolve","simulate","simulation_mode","simulation_options","load_callback","simulate_data_only","simulate_visual","data","create_simulation_data","getDisplayElement","respond","pressKey","default_data","randomization","sampleExGaussian","getValidKey","mergeSimulationData","ensureSimulationDataConsistency"],"mappings":"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"}
|
package/dist/index.cjs
CHANGED
|
@@ -66,7 +66,6 @@ class AudioKeyboardResponsePlugin {
|
|
|
66
66
|
let trial_complete;
|
|
67
67
|
// setup stimulus
|
|
68
68
|
var context = this.jsPsych.pluginAPI.audioContext();
|
|
69
|
-
var audio;
|
|
70
69
|
// store response
|
|
71
70
|
var response = {
|
|
72
71
|
rt: null,
|
|
@@ -77,26 +76,26 @@ class AudioKeyboardResponsePlugin {
|
|
|
77
76
|
// load audio file
|
|
78
77
|
this.jsPsych.pluginAPI
|
|
79
78
|
.getAudioBuffer(trial.stimulus)
|
|
80
|
-
.then(
|
|
79
|
+
.then((buffer) => {
|
|
81
80
|
if (context !== null) {
|
|
82
|
-
audio = context.createBufferSource();
|
|
83
|
-
audio.buffer = buffer;
|
|
84
|
-
audio.connect(context.destination);
|
|
81
|
+
this.audio = context.createBufferSource();
|
|
82
|
+
this.audio.buffer = buffer;
|
|
83
|
+
this.audio.connect(context.destination);
|
|
85
84
|
}
|
|
86
85
|
else {
|
|
87
|
-
audio = buffer;
|
|
88
|
-
audio.currentTime = 0;
|
|
86
|
+
this.audio = buffer;
|
|
87
|
+
this.audio.currentTime = 0;
|
|
89
88
|
}
|
|
90
89
|
setupTrial();
|
|
91
90
|
})
|
|
92
|
-
.catch(
|
|
91
|
+
.catch((err) => {
|
|
93
92
|
console.error(`Failed to load audio file "${trial.stimulus}". Try checking the file path. We recommend using the preload plugin to load audio files.`);
|
|
94
93
|
console.error(err);
|
|
95
94
|
});
|
|
96
95
|
const setupTrial = () => {
|
|
97
96
|
// set up end event if trial needs it
|
|
98
97
|
if (trial.trial_ends_after_audio) {
|
|
99
|
-
audio.addEventListener("ended", end_trial);
|
|
98
|
+
this.audio.addEventListener("ended", end_trial);
|
|
100
99
|
}
|
|
101
100
|
// show prompt if there is one
|
|
102
101
|
if (trial.prompt !== null) {
|
|
@@ -105,21 +104,21 @@ class AudioKeyboardResponsePlugin {
|
|
|
105
104
|
// start audio
|
|
106
105
|
if (context !== null) {
|
|
107
106
|
startTime = context.currentTime;
|
|
108
|
-
audio.start(startTime);
|
|
107
|
+
this.audio.start(startTime);
|
|
109
108
|
}
|
|
110
109
|
else {
|
|
111
|
-
audio.play();
|
|
110
|
+
this.audio.play();
|
|
112
111
|
}
|
|
113
112
|
// start keyboard listener when trial starts or sound ends
|
|
114
113
|
if (trial.response_allowed_while_playing) {
|
|
115
114
|
setup_keyboard_listener();
|
|
116
115
|
}
|
|
117
116
|
else if (!trial.trial_ends_after_audio) {
|
|
118
|
-
audio.addEventListener("ended", setup_keyboard_listener);
|
|
117
|
+
this.audio.addEventListener("ended", setup_keyboard_listener);
|
|
119
118
|
}
|
|
120
119
|
// end trial if time limit is set
|
|
121
120
|
if (trial.trial_duration !== null) {
|
|
122
|
-
this.jsPsych.pluginAPI.setTimeout(
|
|
121
|
+
this.jsPsych.pluginAPI.setTimeout(() => {
|
|
123
122
|
end_trial();
|
|
124
123
|
}, trial.trial_duration);
|
|
125
124
|
}
|
|
@@ -132,13 +131,13 @@ class AudioKeyboardResponsePlugin {
|
|
|
132
131
|
// stop the audio file if it is playing
|
|
133
132
|
// remove end event listeners if they exist
|
|
134
133
|
if (context !== null) {
|
|
135
|
-
audio.stop();
|
|
134
|
+
this.audio.stop();
|
|
136
135
|
}
|
|
137
136
|
else {
|
|
138
|
-
audio.pause();
|
|
137
|
+
this.audio.pause();
|
|
139
138
|
}
|
|
140
|
-
audio.removeEventListener("ended", end_trial);
|
|
141
|
-
audio.removeEventListener("ended", setup_keyboard_listener);
|
|
139
|
+
this.audio.removeEventListener("ended", end_trial);
|
|
140
|
+
this.audio.removeEventListener("ended", setup_keyboard_listener);
|
|
142
141
|
// kill keyboard listeners
|
|
143
142
|
this.jsPsych.pluginAPI.cancelAllKeyboardResponses();
|
|
144
143
|
// gather the data to store for the trial
|
|
@@ -190,6 +189,47 @@ class AudioKeyboardResponsePlugin {
|
|
|
190
189
|
trial_complete = resolve;
|
|
191
190
|
});
|
|
192
191
|
}
|
|
192
|
+
simulate(trial, simulation_mode, simulation_options, load_callback) {
|
|
193
|
+
if (simulation_mode == "data-only") {
|
|
194
|
+
load_callback();
|
|
195
|
+
this.simulate_data_only(trial, simulation_options);
|
|
196
|
+
}
|
|
197
|
+
if (simulation_mode == "visual") {
|
|
198
|
+
this.simulate_visual(trial, simulation_options, load_callback);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
simulate_data_only(trial, simulation_options) {
|
|
202
|
+
const data = this.create_simulation_data(trial, simulation_options);
|
|
203
|
+
this.jsPsych.finishTrial(data);
|
|
204
|
+
}
|
|
205
|
+
simulate_visual(trial, simulation_options, load_callback) {
|
|
206
|
+
const data = this.create_simulation_data(trial, simulation_options);
|
|
207
|
+
const display_element = this.jsPsych.getDisplayElement();
|
|
208
|
+
const respond = () => {
|
|
209
|
+
if (data.rt !== null) {
|
|
210
|
+
this.jsPsych.pluginAPI.pressKey(data.response, data.rt);
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
this.trial(display_element, trial, () => {
|
|
214
|
+
load_callback();
|
|
215
|
+
if (!trial.response_allowed_while_playing) {
|
|
216
|
+
this.audio.addEventListener("ended", respond);
|
|
217
|
+
}
|
|
218
|
+
else {
|
|
219
|
+
respond();
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
create_simulation_data(trial, simulation_options) {
|
|
224
|
+
const default_data = {
|
|
225
|
+
stimulus: trial.stimulus,
|
|
226
|
+
rt: this.jsPsych.randomization.sampleExGaussian(500, 50, 1 / 150, true),
|
|
227
|
+
response: this.jsPsych.pluginAPI.getValidKey(trial.choices),
|
|
228
|
+
};
|
|
229
|
+
const data = this.jsPsych.pluginAPI.mergeSimulationData(default_data, simulation_options);
|
|
230
|
+
this.jsPsych.pluginAPI.ensureSimulationDataConsistency(trial, data);
|
|
231
|
+
return data;
|
|
232
|
+
}
|
|
193
233
|
}
|
|
194
234
|
AudioKeyboardResponsePlugin.info = info;
|
|
195
235
|
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","sources":["../src/index.ts"],"sourcesContent":["import { JsPsych, JsPsychPlugin, ParameterType, TrialType } from \"jspsych\";\n\nconst info = <const>{\n name: \"audio-keyboard-response\",\n parameters: {\n /** The audio file to be played. */\n stimulus: {\n type: ParameterType.AUDIO,\n pretty_name: \"Stimulus\",\n default: undefined,\n },\n /** Array containing the key(s) the subject is allowed to press to respond to the stimulus. */\n choices: {\n type: ParameterType.KEYS,\n pretty_name: \"Choices\",\n default: \"ALL_KEYS\",\n },\n /** Any content here will be displayed below the stimulus. */\n prompt: {\n type: ParameterType.HTML_STRING,\n pretty_name: \"Prompt\",\n default: null,\n },\n /** The maximum duration to wait for a response. */\n trial_duration: {\n type: ParameterType.INT,\n pretty_name: \"Trial duration\",\n default: null,\n },\n /** If true, the trial will end when user makes a response. */\n response_ends_trial: {\n type: ParameterType.BOOL,\n pretty_name: \"Response ends trial\",\n default: true,\n },\n /** If true, then the trial will end as soon as the audio file finishes playing. */\n trial_ends_after_audio: {\n type: ParameterType.BOOL,\n pretty_name: \"Trial ends after audio\",\n default: false,\n },\n /** If true, then responses are allowed while the audio is playing. If false, then the audio must finish playing before a response is accepted. */\n response_allowed_while_playing: {\n type: ParameterType.BOOL,\n pretty_name: \"Response allowed while playing\",\n default: true,\n },\n },\n};\n\ntype Info = typeof info;\n\n/**\n * **audio-keyboard-response**\n *\n * jsPsych plugin for playing an audio file and getting a keyboard response\n *\n * @author Josh de Leeuw\n * @see {@link https://www.jspsych.org/plugins/jspsych-audio-keyboard-response/ audio-keyboard-response plugin documentation on jspsych.org}\n */\nclass AudioKeyboardResponsePlugin implements JsPsychPlugin<Info> {\n static info = info;\n\n constructor(private jsPsych: JsPsych) {}\n\n trial(display_element: HTMLElement, trial: TrialType<Info>, on_load: () => void) {\n // hold the .resolve() function from the Promise that ends the trial\n let trial_complete;\n\n // setup stimulus\n var context = this.jsPsych.pluginAPI.audioContext();\n var audio;\n\n // store response\n var response = {\n rt: null,\n key: null,\n };\n\n // record webaudio context start time\n var startTime;\n\n // load audio file\n this.jsPsych.pluginAPI\n .getAudioBuffer(trial.stimulus)\n .then(function (buffer) {\n if (context !== null) {\n audio = context.createBufferSource();\n audio.buffer = buffer;\n audio.connect(context.destination);\n } else {\n audio = buffer;\n audio.currentTime = 0;\n }\n setupTrial();\n })\n .catch(function (err) {\n console.error(\n `Failed to load audio file \"${trial.stimulus}\". Try checking the file path. We recommend using the preload plugin to load audio files.`\n );\n console.error(err);\n });\n\n const setupTrial = () => {\n // set up end event if trial needs it\n if (trial.trial_ends_after_audio) {\n audio.addEventListener(\"ended\", end_trial);\n }\n\n // show prompt if there is one\n if (trial.prompt !== null) {\n display_element.innerHTML = trial.prompt;\n }\n\n // start audio\n if (context !== null) {\n startTime = context.currentTime;\n audio.start(startTime);\n } else {\n audio.play();\n }\n\n // start keyboard listener when trial starts or sound ends\n if (trial.response_allowed_while_playing) {\n setup_keyboard_listener();\n } else if (!trial.trial_ends_after_audio) {\n audio.addEventListener(\"ended\", setup_keyboard_listener);\n }\n\n // end trial if time limit is set\n if (trial.trial_duration !== null) {\n this.jsPsych.pluginAPI.setTimeout(function () {\n end_trial();\n }, trial.trial_duration);\n }\n\n on_load();\n };\n\n // function to end trial when it is time\n const end_trial = () => {\n // kill any remaining setTimeout handlers\n this.jsPsych.pluginAPI.clearAllTimeouts();\n\n // stop the audio file if it is playing\n // remove end event listeners if they exist\n if (context !== null) {\n audio.stop();\n } else {\n audio.pause();\n }\n\n audio.removeEventListener(\"ended\", end_trial);\n audio.removeEventListener(\"ended\", setup_keyboard_listener);\n\n // kill keyboard listeners\n this.jsPsych.pluginAPI.cancelAllKeyboardResponses();\n\n // gather the data to store for the trial\n var trial_data = {\n rt: response.rt,\n stimulus: trial.stimulus,\n response: response.key,\n };\n\n // clear the display\n display_element.innerHTML = \"\";\n\n // move on to the next trial\n this.jsPsych.finishTrial(trial_data);\n\n trial_complete();\n };\n\n // function to handle responses by the subject\n function after_response(info) {\n // only record the first response\n if (response.key == null) {\n response = info;\n }\n\n if (trial.response_ends_trial) {\n end_trial();\n }\n }\n\n const setup_keyboard_listener = () => {\n // start the response listener\n if (context !== null) {\n this.jsPsych.pluginAPI.getKeyboardResponse({\n callback_function: after_response,\n valid_responses: trial.choices,\n rt_method: \"audio\",\n persist: false,\n allow_held_key: false,\n audio_context: context,\n audio_context_start_time: startTime,\n });\n } else {\n this.jsPsych.pluginAPI.getKeyboardResponse({\n callback_function: after_response,\n valid_responses: trial.choices,\n rt_method: \"performance\",\n persist: false,\n allow_held_key: false,\n });\n }\n };\n\n return new Promise((resolve) => {\n trial_complete = resolve;\n });\n }\n}\n\nexport default AudioKeyboardResponsePlugin;\n"],"names":["ParameterType"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs","sources":["../src/index.ts"],"sourcesContent":["import { JsPsych, JsPsychPlugin, ParameterType, TrialType } from \"jspsych\";\n\nconst info = <const>{\n name: \"audio-keyboard-response\",\n parameters: {\n /** The audio file to be played. */\n stimulus: {\n type: ParameterType.AUDIO,\n pretty_name: \"Stimulus\",\n default: undefined,\n },\n /** Array containing the key(s) the subject is allowed to press to respond to the stimulus. */\n choices: {\n type: ParameterType.KEYS,\n pretty_name: \"Choices\",\n default: \"ALL_KEYS\",\n },\n /** Any content here will be displayed below the stimulus. */\n prompt: {\n type: ParameterType.HTML_STRING,\n pretty_name: \"Prompt\",\n default: null,\n },\n /** The maximum duration to wait for a response. */\n trial_duration: {\n type: ParameterType.INT,\n pretty_name: \"Trial duration\",\n default: null,\n },\n /** If true, the trial will end when user makes a response. */\n response_ends_trial: {\n type: ParameterType.BOOL,\n pretty_name: \"Response ends trial\",\n default: true,\n },\n /** If true, then the trial will end as soon as the audio file finishes playing. */\n trial_ends_after_audio: {\n type: ParameterType.BOOL,\n pretty_name: \"Trial ends after audio\",\n default: false,\n },\n /** If true, then responses are allowed while the audio is playing. If false, then the audio must finish playing before a response is accepted. */\n response_allowed_while_playing: {\n type: ParameterType.BOOL,\n pretty_name: \"Response allowed while playing\",\n default: true,\n },\n },\n};\n\ntype Info = typeof info;\n\n/**\n * **audio-keyboard-response**\n *\n * jsPsych plugin for playing an audio file and getting a keyboard response\n *\n * @author Josh de Leeuw\n * @see {@link https://www.jspsych.org/plugins/jspsych-audio-keyboard-response/ audio-keyboard-response plugin documentation on jspsych.org}\n */\nclass AudioKeyboardResponsePlugin implements JsPsychPlugin<Info> {\n static info = info;\n private audio;\n\n constructor(private jsPsych: JsPsych) {}\n\n trial(display_element: HTMLElement, trial: TrialType<Info>, on_load: () => void) {\n // hold the .resolve() function from the Promise that ends the trial\n let trial_complete;\n\n // setup stimulus\n var context = this.jsPsych.pluginAPI.audioContext();\n\n // store response\n var response = {\n rt: null,\n key: null,\n };\n\n // record webaudio context start time\n var startTime;\n\n // load audio file\n this.jsPsych.pluginAPI\n .getAudioBuffer(trial.stimulus)\n .then((buffer) => {\n if (context !== null) {\n this.audio = context.createBufferSource();\n this.audio.buffer = buffer;\n this.audio.connect(context.destination);\n } else {\n this.audio = buffer;\n this.audio.currentTime = 0;\n }\n setupTrial();\n })\n .catch((err) => {\n console.error(\n `Failed to load audio file \"${trial.stimulus}\". Try checking the file path. We recommend using the preload plugin to load audio files.`\n );\n console.error(err);\n });\n\n const setupTrial = () => {\n // set up end event if trial needs it\n if (trial.trial_ends_after_audio) {\n this.audio.addEventListener(\"ended\", end_trial);\n }\n\n // show prompt if there is one\n if (trial.prompt !== null) {\n display_element.innerHTML = trial.prompt;\n }\n\n // start audio\n if (context !== null) {\n startTime = context.currentTime;\n this.audio.start(startTime);\n } else {\n this.audio.play();\n }\n\n // start keyboard listener when trial starts or sound ends\n if (trial.response_allowed_while_playing) {\n setup_keyboard_listener();\n } else if (!trial.trial_ends_after_audio) {\n this.audio.addEventListener(\"ended\", setup_keyboard_listener);\n }\n\n // end trial if time limit is set\n if (trial.trial_duration !== null) {\n this.jsPsych.pluginAPI.setTimeout(() => {\n end_trial();\n }, trial.trial_duration);\n }\n\n on_load();\n };\n\n // function to end trial when it is time\n const end_trial = () => {\n // kill any remaining setTimeout handlers\n this.jsPsych.pluginAPI.clearAllTimeouts();\n\n // stop the audio file if it is playing\n // remove end event listeners if they exist\n if (context !== null) {\n this.audio.stop();\n } else {\n this.audio.pause();\n }\n\n this.audio.removeEventListener(\"ended\", end_trial);\n this.audio.removeEventListener(\"ended\", setup_keyboard_listener);\n\n // kill keyboard listeners\n this.jsPsych.pluginAPI.cancelAllKeyboardResponses();\n\n // gather the data to store for the trial\n var trial_data = {\n rt: response.rt,\n stimulus: trial.stimulus,\n response: response.key,\n };\n\n // clear the display\n display_element.innerHTML = \"\";\n\n // move on to the next trial\n this.jsPsych.finishTrial(trial_data);\n\n trial_complete();\n };\n\n // function to handle responses by the subject\n function after_response(info) {\n // only record the first response\n if (response.key == null) {\n response = info;\n }\n\n if (trial.response_ends_trial) {\n end_trial();\n }\n }\n\n const setup_keyboard_listener = () => {\n // start the response listener\n if (context !== null) {\n this.jsPsych.pluginAPI.getKeyboardResponse({\n callback_function: after_response,\n valid_responses: trial.choices,\n rt_method: \"audio\",\n persist: false,\n allow_held_key: false,\n audio_context: context,\n audio_context_start_time: startTime,\n });\n } else {\n this.jsPsych.pluginAPI.getKeyboardResponse({\n callback_function: after_response,\n valid_responses: trial.choices,\n rt_method: \"performance\",\n persist: false,\n allow_held_key: false,\n });\n }\n };\n\n return new Promise((resolve) => {\n trial_complete = resolve;\n });\n }\n\n simulate(\n trial: TrialType<Info>,\n simulation_mode,\n simulation_options: any,\n load_callback: () => void\n ) {\n if (simulation_mode == \"data-only\") {\n load_callback();\n this.simulate_data_only(trial, simulation_options);\n }\n if (simulation_mode == \"visual\") {\n this.simulate_visual(trial, simulation_options, load_callback);\n }\n }\n\n private simulate_data_only(trial: TrialType<Info>, simulation_options) {\n const data = this.create_simulation_data(trial, simulation_options);\n\n this.jsPsych.finishTrial(data);\n }\n\n private simulate_visual(trial: TrialType<Info>, simulation_options, load_callback: () => void) {\n const data = this.create_simulation_data(trial, simulation_options);\n\n const display_element = this.jsPsych.getDisplayElement();\n\n const respond = () => {\n if (data.rt !== null) {\n this.jsPsych.pluginAPI.pressKey(data.response, data.rt);\n }\n };\n\n this.trial(display_element, trial, () => {\n load_callback();\n if (!trial.response_allowed_while_playing) {\n this.audio.addEventListener(\"ended\", respond);\n } else {\n respond();\n }\n });\n }\n\n private create_simulation_data(trial: TrialType<Info>, simulation_options) {\n const default_data = {\n stimulus: trial.stimulus,\n rt: this.jsPsych.randomization.sampleExGaussian(500, 50, 1 / 150, true),\n response: this.jsPsych.pluginAPI.getValidKey(trial.choices),\n };\n\n const data = this.jsPsych.pluginAPI.mergeSimulationData(default_data, simulation_options);\n\n this.jsPsych.pluginAPI.ensureSimulationDataConsistency(trial, data);\n\n return data;\n }\n}\n\nexport default AudioKeyboardResponsePlugin;\n"],"names":["ParameterType"],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -104,7 +104,12 @@ declare class AudioKeyboardResponsePlugin implements JsPsychPlugin<Info> {
|
|
|
104
104
|
};
|
|
105
105
|
};
|
|
106
106
|
};
|
|
107
|
+
private audio;
|
|
107
108
|
constructor(jsPsych: JsPsych);
|
|
108
109
|
trial(display_element: HTMLElement, trial: TrialType<Info>, on_load: () => void): Promise<unknown>;
|
|
110
|
+
simulate(trial: TrialType<Info>, simulation_mode: any, simulation_options: any, load_callback: () => void): void;
|
|
111
|
+
private simulate_data_only;
|
|
112
|
+
private simulate_visual;
|
|
113
|
+
private create_simulation_data;
|
|
109
114
|
}
|
|
110
115
|
export default AudioKeyboardResponsePlugin;
|
package/dist/index.js
CHANGED
|
@@ -64,7 +64,6 @@ class AudioKeyboardResponsePlugin {
|
|
|
64
64
|
let trial_complete;
|
|
65
65
|
// setup stimulus
|
|
66
66
|
var context = this.jsPsych.pluginAPI.audioContext();
|
|
67
|
-
var audio;
|
|
68
67
|
// store response
|
|
69
68
|
var response = {
|
|
70
69
|
rt: null,
|
|
@@ -75,26 +74,26 @@ class AudioKeyboardResponsePlugin {
|
|
|
75
74
|
// load audio file
|
|
76
75
|
this.jsPsych.pluginAPI
|
|
77
76
|
.getAudioBuffer(trial.stimulus)
|
|
78
|
-
.then(
|
|
77
|
+
.then((buffer) => {
|
|
79
78
|
if (context !== null) {
|
|
80
|
-
audio = context.createBufferSource();
|
|
81
|
-
audio.buffer = buffer;
|
|
82
|
-
audio.connect(context.destination);
|
|
79
|
+
this.audio = context.createBufferSource();
|
|
80
|
+
this.audio.buffer = buffer;
|
|
81
|
+
this.audio.connect(context.destination);
|
|
83
82
|
}
|
|
84
83
|
else {
|
|
85
|
-
audio = buffer;
|
|
86
|
-
audio.currentTime = 0;
|
|
84
|
+
this.audio = buffer;
|
|
85
|
+
this.audio.currentTime = 0;
|
|
87
86
|
}
|
|
88
87
|
setupTrial();
|
|
89
88
|
})
|
|
90
|
-
.catch(
|
|
89
|
+
.catch((err) => {
|
|
91
90
|
console.error(`Failed to load audio file "${trial.stimulus}". Try checking the file path. We recommend using the preload plugin to load audio files.`);
|
|
92
91
|
console.error(err);
|
|
93
92
|
});
|
|
94
93
|
const setupTrial = () => {
|
|
95
94
|
// set up end event if trial needs it
|
|
96
95
|
if (trial.trial_ends_after_audio) {
|
|
97
|
-
audio.addEventListener("ended", end_trial);
|
|
96
|
+
this.audio.addEventListener("ended", end_trial);
|
|
98
97
|
}
|
|
99
98
|
// show prompt if there is one
|
|
100
99
|
if (trial.prompt !== null) {
|
|
@@ -103,21 +102,21 @@ class AudioKeyboardResponsePlugin {
|
|
|
103
102
|
// start audio
|
|
104
103
|
if (context !== null) {
|
|
105
104
|
startTime = context.currentTime;
|
|
106
|
-
audio.start(startTime);
|
|
105
|
+
this.audio.start(startTime);
|
|
107
106
|
}
|
|
108
107
|
else {
|
|
109
|
-
audio.play();
|
|
108
|
+
this.audio.play();
|
|
110
109
|
}
|
|
111
110
|
// start keyboard listener when trial starts or sound ends
|
|
112
111
|
if (trial.response_allowed_while_playing) {
|
|
113
112
|
setup_keyboard_listener();
|
|
114
113
|
}
|
|
115
114
|
else if (!trial.trial_ends_after_audio) {
|
|
116
|
-
audio.addEventListener("ended", setup_keyboard_listener);
|
|
115
|
+
this.audio.addEventListener("ended", setup_keyboard_listener);
|
|
117
116
|
}
|
|
118
117
|
// end trial if time limit is set
|
|
119
118
|
if (trial.trial_duration !== null) {
|
|
120
|
-
this.jsPsych.pluginAPI.setTimeout(
|
|
119
|
+
this.jsPsych.pluginAPI.setTimeout(() => {
|
|
121
120
|
end_trial();
|
|
122
121
|
}, trial.trial_duration);
|
|
123
122
|
}
|
|
@@ -130,13 +129,13 @@ class AudioKeyboardResponsePlugin {
|
|
|
130
129
|
// stop the audio file if it is playing
|
|
131
130
|
// remove end event listeners if they exist
|
|
132
131
|
if (context !== null) {
|
|
133
|
-
audio.stop();
|
|
132
|
+
this.audio.stop();
|
|
134
133
|
}
|
|
135
134
|
else {
|
|
136
|
-
audio.pause();
|
|
135
|
+
this.audio.pause();
|
|
137
136
|
}
|
|
138
|
-
audio.removeEventListener("ended", end_trial);
|
|
139
|
-
audio.removeEventListener("ended", setup_keyboard_listener);
|
|
137
|
+
this.audio.removeEventListener("ended", end_trial);
|
|
138
|
+
this.audio.removeEventListener("ended", setup_keyboard_listener);
|
|
140
139
|
// kill keyboard listeners
|
|
141
140
|
this.jsPsych.pluginAPI.cancelAllKeyboardResponses();
|
|
142
141
|
// gather the data to store for the trial
|
|
@@ -188,6 +187,47 @@ class AudioKeyboardResponsePlugin {
|
|
|
188
187
|
trial_complete = resolve;
|
|
189
188
|
});
|
|
190
189
|
}
|
|
190
|
+
simulate(trial, simulation_mode, simulation_options, load_callback) {
|
|
191
|
+
if (simulation_mode == "data-only") {
|
|
192
|
+
load_callback();
|
|
193
|
+
this.simulate_data_only(trial, simulation_options);
|
|
194
|
+
}
|
|
195
|
+
if (simulation_mode == "visual") {
|
|
196
|
+
this.simulate_visual(trial, simulation_options, load_callback);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
simulate_data_only(trial, simulation_options) {
|
|
200
|
+
const data = this.create_simulation_data(trial, simulation_options);
|
|
201
|
+
this.jsPsych.finishTrial(data);
|
|
202
|
+
}
|
|
203
|
+
simulate_visual(trial, simulation_options, load_callback) {
|
|
204
|
+
const data = this.create_simulation_data(trial, simulation_options);
|
|
205
|
+
const display_element = this.jsPsych.getDisplayElement();
|
|
206
|
+
const respond = () => {
|
|
207
|
+
if (data.rt !== null) {
|
|
208
|
+
this.jsPsych.pluginAPI.pressKey(data.response, data.rt);
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
this.trial(display_element, trial, () => {
|
|
212
|
+
load_callback();
|
|
213
|
+
if (!trial.response_allowed_while_playing) {
|
|
214
|
+
this.audio.addEventListener("ended", respond);
|
|
215
|
+
}
|
|
216
|
+
else {
|
|
217
|
+
respond();
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
create_simulation_data(trial, simulation_options) {
|
|
222
|
+
const default_data = {
|
|
223
|
+
stimulus: trial.stimulus,
|
|
224
|
+
rt: this.jsPsych.randomization.sampleExGaussian(500, 50, 1 / 150, true),
|
|
225
|
+
response: this.jsPsych.pluginAPI.getValidKey(trial.choices),
|
|
226
|
+
};
|
|
227
|
+
const data = this.jsPsych.pluginAPI.mergeSimulationData(default_data, simulation_options);
|
|
228
|
+
this.jsPsych.pluginAPI.ensureSimulationDataConsistency(trial, data);
|
|
229
|
+
return data;
|
|
230
|
+
}
|
|
191
231
|
}
|
|
192
232
|
AudioKeyboardResponsePlugin.info = info;
|
|
193
233
|
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../src/index.ts"],"sourcesContent":["import { JsPsych, JsPsychPlugin, ParameterType, TrialType } from \"jspsych\";\n\nconst info = <const>{\n name: \"audio-keyboard-response\",\n parameters: {\n /** The audio file to be played. */\n stimulus: {\n type: ParameterType.AUDIO,\n pretty_name: \"Stimulus\",\n default: undefined,\n },\n /** Array containing the key(s) the subject is allowed to press to respond to the stimulus. */\n choices: {\n type: ParameterType.KEYS,\n pretty_name: \"Choices\",\n default: \"ALL_KEYS\",\n },\n /** Any content here will be displayed below the stimulus. */\n prompt: {\n type: ParameterType.HTML_STRING,\n pretty_name: \"Prompt\",\n default: null,\n },\n /** The maximum duration to wait for a response. */\n trial_duration: {\n type: ParameterType.INT,\n pretty_name: \"Trial duration\",\n default: null,\n },\n /** If true, the trial will end when user makes a response. */\n response_ends_trial: {\n type: ParameterType.BOOL,\n pretty_name: \"Response ends trial\",\n default: true,\n },\n /** If true, then the trial will end as soon as the audio file finishes playing. */\n trial_ends_after_audio: {\n type: ParameterType.BOOL,\n pretty_name: \"Trial ends after audio\",\n default: false,\n },\n /** If true, then responses are allowed while the audio is playing. If false, then the audio must finish playing before a response is accepted. */\n response_allowed_while_playing: {\n type: ParameterType.BOOL,\n pretty_name: \"Response allowed while playing\",\n default: true,\n },\n },\n};\n\ntype Info = typeof info;\n\n/**\n * **audio-keyboard-response**\n *\n * jsPsych plugin for playing an audio file and getting a keyboard response\n *\n * @author Josh de Leeuw\n * @see {@link https://www.jspsych.org/plugins/jspsych-audio-keyboard-response/ audio-keyboard-response plugin documentation on jspsych.org}\n */\nclass AudioKeyboardResponsePlugin implements JsPsychPlugin<Info> {\n static info = info;\n\n constructor(private jsPsych: JsPsych) {}\n\n trial(display_element: HTMLElement, trial: TrialType<Info>, on_load: () => void) {\n // hold the .resolve() function from the Promise that ends the trial\n let trial_complete;\n\n // setup stimulus\n var context = this.jsPsych.pluginAPI.audioContext();\n var audio;\n\n // store response\n var response = {\n rt: null,\n key: null,\n };\n\n // record webaudio context start time\n var startTime;\n\n // load audio file\n this.jsPsych.pluginAPI\n .getAudioBuffer(trial.stimulus)\n .then(function (buffer) {\n if (context !== null) {\n audio = context.createBufferSource();\n audio.buffer = buffer;\n audio.connect(context.destination);\n } else {\n audio = buffer;\n audio.currentTime = 0;\n }\n setupTrial();\n })\n .catch(function (err) {\n console.error(\n `Failed to load audio file \"${trial.stimulus}\". Try checking the file path. We recommend using the preload plugin to load audio files.`\n );\n console.error(err);\n });\n\n const setupTrial = () => {\n // set up end event if trial needs it\n if (trial.trial_ends_after_audio) {\n audio.addEventListener(\"ended\", end_trial);\n }\n\n // show prompt if there is one\n if (trial.prompt !== null) {\n display_element.innerHTML = trial.prompt;\n }\n\n // start audio\n if (context !== null) {\n startTime = context.currentTime;\n audio.start(startTime);\n } else {\n audio.play();\n }\n\n // start keyboard listener when trial starts or sound ends\n if (trial.response_allowed_while_playing) {\n setup_keyboard_listener();\n } else if (!trial.trial_ends_after_audio) {\n audio.addEventListener(\"ended\", setup_keyboard_listener);\n }\n\n // end trial if time limit is set\n if (trial.trial_duration !== null) {\n this.jsPsych.pluginAPI.setTimeout(function () {\n end_trial();\n }, trial.trial_duration);\n }\n\n on_load();\n };\n\n // function to end trial when it is time\n const end_trial = () => {\n // kill any remaining setTimeout handlers\n this.jsPsych.pluginAPI.clearAllTimeouts();\n\n // stop the audio file if it is playing\n // remove end event listeners if they exist\n if (context !== null) {\n audio.stop();\n } else {\n audio.pause();\n }\n\n audio.removeEventListener(\"ended\", end_trial);\n audio.removeEventListener(\"ended\", setup_keyboard_listener);\n\n // kill keyboard listeners\n this.jsPsych.pluginAPI.cancelAllKeyboardResponses();\n\n // gather the data to store for the trial\n var trial_data = {\n rt: response.rt,\n stimulus: trial.stimulus,\n response: response.key,\n };\n\n // clear the display\n display_element.innerHTML = \"\";\n\n // move on to the next trial\n this.jsPsych.finishTrial(trial_data);\n\n trial_complete();\n };\n\n // function to handle responses by the subject\n function after_response(info) {\n // only record the first response\n if (response.key == null) {\n response = info;\n }\n\n if (trial.response_ends_trial) {\n end_trial();\n }\n }\n\n const setup_keyboard_listener = () => {\n // start the response listener\n if (context !== null) {\n this.jsPsych.pluginAPI.getKeyboardResponse({\n callback_function: after_response,\n valid_responses: trial.choices,\n rt_method: \"audio\",\n persist: false,\n allow_held_key: false,\n audio_context: context,\n audio_context_start_time: startTime,\n });\n } else {\n this.jsPsych.pluginAPI.getKeyboardResponse({\n callback_function: after_response,\n valid_responses: trial.choices,\n rt_method: \"performance\",\n persist: false,\n allow_held_key: false,\n });\n }\n };\n\n return new Promise((resolve) => {\n trial_complete = resolve;\n });\n }\n}\n\nexport default AudioKeyboardResponsePlugin;\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../src/index.ts"],"sourcesContent":["import { JsPsych, JsPsychPlugin, ParameterType, TrialType } from \"jspsych\";\n\nconst info = <const>{\n name: \"audio-keyboard-response\",\n parameters: {\n /** The audio file to be played. */\n stimulus: {\n type: ParameterType.AUDIO,\n pretty_name: \"Stimulus\",\n default: undefined,\n },\n /** Array containing the key(s) the subject is allowed to press to respond to the stimulus. */\n choices: {\n type: ParameterType.KEYS,\n pretty_name: \"Choices\",\n default: \"ALL_KEYS\",\n },\n /** Any content here will be displayed below the stimulus. */\n prompt: {\n type: ParameterType.HTML_STRING,\n pretty_name: \"Prompt\",\n default: null,\n },\n /** The maximum duration to wait for a response. */\n trial_duration: {\n type: ParameterType.INT,\n pretty_name: \"Trial duration\",\n default: null,\n },\n /** If true, the trial will end when user makes a response. */\n response_ends_trial: {\n type: ParameterType.BOOL,\n pretty_name: \"Response ends trial\",\n default: true,\n },\n /** If true, then the trial will end as soon as the audio file finishes playing. */\n trial_ends_after_audio: {\n type: ParameterType.BOOL,\n pretty_name: \"Trial ends after audio\",\n default: false,\n },\n /** If true, then responses are allowed while the audio is playing. If false, then the audio must finish playing before a response is accepted. */\n response_allowed_while_playing: {\n type: ParameterType.BOOL,\n pretty_name: \"Response allowed while playing\",\n default: true,\n },\n },\n};\n\ntype Info = typeof info;\n\n/**\n * **audio-keyboard-response**\n *\n * jsPsych plugin for playing an audio file and getting a keyboard response\n *\n * @author Josh de Leeuw\n * @see {@link https://www.jspsych.org/plugins/jspsych-audio-keyboard-response/ audio-keyboard-response plugin documentation on jspsych.org}\n */\nclass AudioKeyboardResponsePlugin implements JsPsychPlugin<Info> {\n static info = info;\n private audio;\n\n constructor(private jsPsych: JsPsych) {}\n\n trial(display_element: HTMLElement, trial: TrialType<Info>, on_load: () => void) {\n // hold the .resolve() function from the Promise that ends the trial\n let trial_complete;\n\n // setup stimulus\n var context = this.jsPsych.pluginAPI.audioContext();\n\n // store response\n var response = {\n rt: null,\n key: null,\n };\n\n // record webaudio context start time\n var startTime;\n\n // load audio file\n this.jsPsych.pluginAPI\n .getAudioBuffer(trial.stimulus)\n .then((buffer) => {\n if (context !== null) {\n this.audio = context.createBufferSource();\n this.audio.buffer = buffer;\n this.audio.connect(context.destination);\n } else {\n this.audio = buffer;\n this.audio.currentTime = 0;\n }\n setupTrial();\n })\n .catch((err) => {\n console.error(\n `Failed to load audio file \"${trial.stimulus}\". Try checking the file path. We recommend using the preload plugin to load audio files.`\n );\n console.error(err);\n });\n\n const setupTrial = () => {\n // set up end event if trial needs it\n if (trial.trial_ends_after_audio) {\n this.audio.addEventListener(\"ended\", end_trial);\n }\n\n // show prompt if there is one\n if (trial.prompt !== null) {\n display_element.innerHTML = trial.prompt;\n }\n\n // start audio\n if (context !== null) {\n startTime = context.currentTime;\n this.audio.start(startTime);\n } else {\n this.audio.play();\n }\n\n // start keyboard listener when trial starts or sound ends\n if (trial.response_allowed_while_playing) {\n setup_keyboard_listener();\n } else if (!trial.trial_ends_after_audio) {\n this.audio.addEventListener(\"ended\", setup_keyboard_listener);\n }\n\n // end trial if time limit is set\n if (trial.trial_duration !== null) {\n this.jsPsych.pluginAPI.setTimeout(() => {\n end_trial();\n }, trial.trial_duration);\n }\n\n on_load();\n };\n\n // function to end trial when it is time\n const end_trial = () => {\n // kill any remaining setTimeout handlers\n this.jsPsych.pluginAPI.clearAllTimeouts();\n\n // stop the audio file if it is playing\n // remove end event listeners if they exist\n if (context !== null) {\n this.audio.stop();\n } else {\n this.audio.pause();\n }\n\n this.audio.removeEventListener(\"ended\", end_trial);\n this.audio.removeEventListener(\"ended\", setup_keyboard_listener);\n\n // kill keyboard listeners\n this.jsPsych.pluginAPI.cancelAllKeyboardResponses();\n\n // gather the data to store for the trial\n var trial_data = {\n rt: response.rt,\n stimulus: trial.stimulus,\n response: response.key,\n };\n\n // clear the display\n display_element.innerHTML = \"\";\n\n // move on to the next trial\n this.jsPsych.finishTrial(trial_data);\n\n trial_complete();\n };\n\n // function to handle responses by the subject\n function after_response(info) {\n // only record the first response\n if (response.key == null) {\n response = info;\n }\n\n if (trial.response_ends_trial) {\n end_trial();\n }\n }\n\n const setup_keyboard_listener = () => {\n // start the response listener\n if (context !== null) {\n this.jsPsych.pluginAPI.getKeyboardResponse({\n callback_function: after_response,\n valid_responses: trial.choices,\n rt_method: \"audio\",\n persist: false,\n allow_held_key: false,\n audio_context: context,\n audio_context_start_time: startTime,\n });\n } else {\n this.jsPsych.pluginAPI.getKeyboardResponse({\n callback_function: after_response,\n valid_responses: trial.choices,\n rt_method: \"performance\",\n persist: false,\n allow_held_key: false,\n });\n }\n };\n\n return new Promise((resolve) => {\n trial_complete = resolve;\n });\n }\n\n simulate(\n trial: TrialType<Info>,\n simulation_mode,\n simulation_options: any,\n load_callback: () => void\n ) {\n if (simulation_mode == \"data-only\") {\n load_callback();\n this.simulate_data_only(trial, simulation_options);\n }\n if (simulation_mode == \"visual\") {\n this.simulate_visual(trial, simulation_options, load_callback);\n }\n }\n\n private simulate_data_only(trial: TrialType<Info>, simulation_options) {\n const data = this.create_simulation_data(trial, simulation_options);\n\n this.jsPsych.finishTrial(data);\n }\n\n private simulate_visual(trial: TrialType<Info>, simulation_options, load_callback: () => void) {\n const data = this.create_simulation_data(trial, simulation_options);\n\n const display_element = this.jsPsych.getDisplayElement();\n\n const respond = () => {\n if (data.rt !== null) {\n this.jsPsych.pluginAPI.pressKey(data.response, data.rt);\n }\n };\n\n this.trial(display_element, trial, () => {\n load_callback();\n if (!trial.response_allowed_while_playing) {\n this.audio.addEventListener(\"ended\", respond);\n } else {\n respond();\n }\n });\n }\n\n private create_simulation_data(trial: TrialType<Info>, simulation_options) {\n const default_data = {\n stimulus: trial.stimulus,\n rt: this.jsPsych.randomization.sampleExGaussian(500, 50, 1 / 150, true),\n response: this.jsPsych.pluginAPI.getValidKey(trial.choices),\n };\n\n const data = this.jsPsych.pluginAPI.mergeSimulationData(default_data, simulation_options);\n\n this.jsPsych.pluginAPI.ensureSimulationDataConsistency(trial, data);\n\n return data;\n }\n}\n\nexport default AudioKeyboardResponsePlugin;\n"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jspsych/plugin-audio-keyboard-response",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "jsPsych plugin for playing an audio file and getting a keyboard response",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.cjs",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
],
|
|
17
17
|
"source": "src/index.ts",
|
|
18
18
|
"scripts": {
|
|
19
|
-
"test": "jest
|
|
19
|
+
"test": "jest",
|
|
20
20
|
"test:watch": "npm test -- --watch",
|
|
21
21
|
"tsc": "tsc",
|
|
22
22
|
"build": "rollup --config",
|
|
@@ -34,10 +34,10 @@
|
|
|
34
34
|
},
|
|
35
35
|
"homepage": "https://www.jspsych.org/latest/plugins/audio-keyboard-response",
|
|
36
36
|
"peerDependencies": {
|
|
37
|
-
"jspsych": ">=7.
|
|
37
|
+
"jspsych": ">=7.1.0"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
|
-
"@jspsych/config": "^1.
|
|
41
|
-
"@jspsych/test-utils": "^1.
|
|
40
|
+
"@jspsych/config": "^1.1.0",
|
|
41
|
+
"@jspsych/test-utils": "^1.1.0"
|
|
42
42
|
}
|
|
43
43
|
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { pressKey, simulateTimeline, startTimeline } from "@jspsych/test-utils";
|
|
2
|
+
import { initJsPsych } from "jspsych";
|
|
3
|
+
|
|
4
|
+
import audioKeyboardResponse from ".";
|
|
5
|
+
|
|
6
|
+
jest.useFakeTimers();
|
|
7
|
+
|
|
8
|
+
describe("audio-keyboard-response simulation", () => {
|
|
9
|
+
test("data mode works", async () => {
|
|
10
|
+
const timeline = [
|
|
11
|
+
{
|
|
12
|
+
type: audioKeyboardResponse,
|
|
13
|
+
stimulus: "foo.mp3",
|
|
14
|
+
},
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
const { expectFinished, getData } = await simulateTimeline(timeline);
|
|
18
|
+
|
|
19
|
+
await expectFinished();
|
|
20
|
+
|
|
21
|
+
expect(getData().values()[0].rt).toBeGreaterThan(0);
|
|
22
|
+
expect(typeof getData().values()[0].response).toBe("string");
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
// can't run this until we mock Audio elements.
|
|
26
|
+
test.skip("visual mode works", async () => {
|
|
27
|
+
const jsPsych = initJsPsych({ use_webaudio: false });
|
|
28
|
+
|
|
29
|
+
const timeline = [
|
|
30
|
+
{
|
|
31
|
+
type: audioKeyboardResponse,
|
|
32
|
+
stimulus: "foo.mp3",
|
|
33
|
+
prompt: "foo",
|
|
34
|
+
},
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
const { expectFinished, expectRunning, getHTML, getData } = await simulateTimeline(
|
|
38
|
+
timeline,
|
|
39
|
+
"visual",
|
|
40
|
+
{},
|
|
41
|
+
jsPsych
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
await expectRunning();
|
|
45
|
+
|
|
46
|
+
expect(getHTML()).toContain("foo");
|
|
47
|
+
|
|
48
|
+
jest.runAllTimers();
|
|
49
|
+
|
|
50
|
+
await expectFinished();
|
|
51
|
+
|
|
52
|
+
expect(getData().values()[0].rt).toBeGreaterThan(0);
|
|
53
|
+
expect(typeof getData().values()[0].response).toBe("string");
|
|
54
|
+
});
|
|
55
|
+
});
|
package/src/index.ts
CHANGED
|
@@ -60,6 +60,7 @@ type Info = typeof info;
|
|
|
60
60
|
*/
|
|
61
61
|
class AudioKeyboardResponsePlugin implements JsPsychPlugin<Info> {
|
|
62
62
|
static info = info;
|
|
63
|
+
private audio;
|
|
63
64
|
|
|
64
65
|
constructor(private jsPsych: JsPsych) {}
|
|
65
66
|
|
|
@@ -69,7 +70,6 @@ class AudioKeyboardResponsePlugin implements JsPsychPlugin<Info> {
|
|
|
69
70
|
|
|
70
71
|
// setup stimulus
|
|
71
72
|
var context = this.jsPsych.pluginAPI.audioContext();
|
|
72
|
-
var audio;
|
|
73
73
|
|
|
74
74
|
// store response
|
|
75
75
|
var response = {
|
|
@@ -83,18 +83,18 @@ class AudioKeyboardResponsePlugin implements JsPsychPlugin<Info> {
|
|
|
83
83
|
// load audio file
|
|
84
84
|
this.jsPsych.pluginAPI
|
|
85
85
|
.getAudioBuffer(trial.stimulus)
|
|
86
|
-
.then(
|
|
86
|
+
.then((buffer) => {
|
|
87
87
|
if (context !== null) {
|
|
88
|
-
audio = context.createBufferSource();
|
|
89
|
-
audio.buffer = buffer;
|
|
90
|
-
audio.connect(context.destination);
|
|
88
|
+
this.audio = context.createBufferSource();
|
|
89
|
+
this.audio.buffer = buffer;
|
|
90
|
+
this.audio.connect(context.destination);
|
|
91
91
|
} else {
|
|
92
|
-
audio = buffer;
|
|
93
|
-
audio.currentTime = 0;
|
|
92
|
+
this.audio = buffer;
|
|
93
|
+
this.audio.currentTime = 0;
|
|
94
94
|
}
|
|
95
95
|
setupTrial();
|
|
96
96
|
})
|
|
97
|
-
.catch(
|
|
97
|
+
.catch((err) => {
|
|
98
98
|
console.error(
|
|
99
99
|
`Failed to load audio file "${trial.stimulus}". Try checking the file path. We recommend using the preload plugin to load audio files.`
|
|
100
100
|
);
|
|
@@ -104,7 +104,7 @@ class AudioKeyboardResponsePlugin implements JsPsychPlugin<Info> {
|
|
|
104
104
|
const setupTrial = () => {
|
|
105
105
|
// set up end event if trial needs it
|
|
106
106
|
if (trial.trial_ends_after_audio) {
|
|
107
|
-
audio.addEventListener("ended", end_trial);
|
|
107
|
+
this.audio.addEventListener("ended", end_trial);
|
|
108
108
|
}
|
|
109
109
|
|
|
110
110
|
// show prompt if there is one
|
|
@@ -115,21 +115,21 @@ class AudioKeyboardResponsePlugin implements JsPsychPlugin<Info> {
|
|
|
115
115
|
// start audio
|
|
116
116
|
if (context !== null) {
|
|
117
117
|
startTime = context.currentTime;
|
|
118
|
-
audio.start(startTime);
|
|
118
|
+
this.audio.start(startTime);
|
|
119
119
|
} else {
|
|
120
|
-
audio.play();
|
|
120
|
+
this.audio.play();
|
|
121
121
|
}
|
|
122
122
|
|
|
123
123
|
// start keyboard listener when trial starts or sound ends
|
|
124
124
|
if (trial.response_allowed_while_playing) {
|
|
125
125
|
setup_keyboard_listener();
|
|
126
126
|
} else if (!trial.trial_ends_after_audio) {
|
|
127
|
-
audio.addEventListener("ended", setup_keyboard_listener);
|
|
127
|
+
this.audio.addEventListener("ended", setup_keyboard_listener);
|
|
128
128
|
}
|
|
129
129
|
|
|
130
130
|
// end trial if time limit is set
|
|
131
131
|
if (trial.trial_duration !== null) {
|
|
132
|
-
this.jsPsych.pluginAPI.setTimeout(
|
|
132
|
+
this.jsPsych.pluginAPI.setTimeout(() => {
|
|
133
133
|
end_trial();
|
|
134
134
|
}, trial.trial_duration);
|
|
135
135
|
}
|
|
@@ -145,13 +145,13 @@ class AudioKeyboardResponsePlugin implements JsPsychPlugin<Info> {
|
|
|
145
145
|
// stop the audio file if it is playing
|
|
146
146
|
// remove end event listeners if they exist
|
|
147
147
|
if (context !== null) {
|
|
148
|
-
audio.stop();
|
|
148
|
+
this.audio.stop();
|
|
149
149
|
} else {
|
|
150
|
-
audio.pause();
|
|
150
|
+
this.audio.pause();
|
|
151
151
|
}
|
|
152
152
|
|
|
153
|
-
audio.removeEventListener("ended", end_trial);
|
|
154
|
-
audio.removeEventListener("ended", setup_keyboard_listener);
|
|
153
|
+
this.audio.removeEventListener("ended", end_trial);
|
|
154
|
+
this.audio.removeEventListener("ended", setup_keyboard_listener);
|
|
155
155
|
|
|
156
156
|
// kill keyboard listeners
|
|
157
157
|
this.jsPsych.pluginAPI.cancelAllKeyboardResponses();
|
|
@@ -211,6 +211,62 @@ class AudioKeyboardResponsePlugin implements JsPsychPlugin<Info> {
|
|
|
211
211
|
trial_complete = resolve;
|
|
212
212
|
});
|
|
213
213
|
}
|
|
214
|
+
|
|
215
|
+
simulate(
|
|
216
|
+
trial: TrialType<Info>,
|
|
217
|
+
simulation_mode,
|
|
218
|
+
simulation_options: any,
|
|
219
|
+
load_callback: () => void
|
|
220
|
+
) {
|
|
221
|
+
if (simulation_mode == "data-only") {
|
|
222
|
+
load_callback();
|
|
223
|
+
this.simulate_data_only(trial, simulation_options);
|
|
224
|
+
}
|
|
225
|
+
if (simulation_mode == "visual") {
|
|
226
|
+
this.simulate_visual(trial, simulation_options, load_callback);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
private simulate_data_only(trial: TrialType<Info>, simulation_options) {
|
|
231
|
+
const data = this.create_simulation_data(trial, simulation_options);
|
|
232
|
+
|
|
233
|
+
this.jsPsych.finishTrial(data);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
private simulate_visual(trial: TrialType<Info>, simulation_options, load_callback: () => void) {
|
|
237
|
+
const data = this.create_simulation_data(trial, simulation_options);
|
|
238
|
+
|
|
239
|
+
const display_element = this.jsPsych.getDisplayElement();
|
|
240
|
+
|
|
241
|
+
const respond = () => {
|
|
242
|
+
if (data.rt !== null) {
|
|
243
|
+
this.jsPsych.pluginAPI.pressKey(data.response, data.rt);
|
|
244
|
+
}
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
this.trial(display_element, trial, () => {
|
|
248
|
+
load_callback();
|
|
249
|
+
if (!trial.response_allowed_while_playing) {
|
|
250
|
+
this.audio.addEventListener("ended", respond);
|
|
251
|
+
} else {
|
|
252
|
+
respond();
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
private create_simulation_data(trial: TrialType<Info>, simulation_options) {
|
|
258
|
+
const default_data = {
|
|
259
|
+
stimulus: trial.stimulus,
|
|
260
|
+
rt: this.jsPsych.randomization.sampleExGaussian(500, 50, 1 / 150, true),
|
|
261
|
+
response: this.jsPsych.pluginAPI.getValidKey(trial.choices),
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
const data = this.jsPsych.pluginAPI.mergeSimulationData(default_data, simulation_options);
|
|
265
|
+
|
|
266
|
+
this.jsPsych.pluginAPI.ensureSimulationDataConsistency(trial, data);
|
|
267
|
+
|
|
268
|
+
return data;
|
|
269
|
+
}
|
|
214
270
|
}
|
|
215
271
|
|
|
216
272
|
export default AudioKeyboardResponsePlugin;
|