Compare commits
2 Commits
jspsych@6.
...
jspsych@6.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2a130f79b8 | ||
|
|
1cd4dd8a95 |
@@ -10,6 +10,8 @@ The trial can end when the subject responds, when the audio file has finished pl
|
|||||||
|
|
||||||
Note that the buttons are disabled during playing so the subject cannot press any button during that time.
|
Note that the buttons are disabled during playing so the subject cannot press any button during that time.
|
||||||
|
|
||||||
|
Make sure to define a CSS style for `.jspsych-audio-sequence-button-response button.highlighted` to see the button light up, and you may have to use `!important` for it to show up.
|
||||||
|
|
||||||
## Parameters
|
## Parameters
|
||||||
|
|
||||||
Parameters with a default value of *undefined* must be specified. Other parameters can be left unspecified if the default value is acceptable.
|
Parameters with a default value of *undefined* must be specified. Other parameters can be left unspecified if the default value is acceptable.
|
||||||
@@ -72,6 +74,18 @@ The animation of the visual feedback can make use of [Semantic UI's transitions]
|
|||||||
|
|
||||||
#### Three alternative forced choice (3AFC)
|
#### Three alternative forced choice (3AFC)
|
||||||
|
|
||||||
|
CSS:
|
||||||
|
```css
|
||||||
|
.jspsych-audio-sequence-button-response button {
|
||||||
|
min-width: 5em;
|
||||||
|
min-height: 4em;
|
||||||
|
}
|
||||||
|
.jspsych-audio-sequence-button-response button.highlighted {
|
||||||
|
background-color: #ffff00 !important;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Javascript:
|
||||||
```javascript
|
```javascript
|
||||||
var trial = {
|
var trial = {
|
||||||
type: 'audio-button-response',
|
type: 'audio-button-response',
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
/**
|
/**
|
||||||
* jspsych-audio-sequence-button-response
|
* jspsych-audio-sequence-button-response for jsPsych v6.3
|
||||||
* Etienne Gaudrain <etienne.gaudrain@cnrs.fr>
|
* Etienne Gaudrain <etienne.gaudrain@cnrs.fr> 2021-10-15
|
||||||
*
|
*
|
||||||
* Plugin for playing a sequence of audio files and getting an HTML button response
|
* Plugin for playing a sequence of audio files and getting an HTML button response
|
||||||
*
|
*
|
||||||
* Based on jspsych-audio-button-response.
|
* Based on jspsych-audio-button-response.
|
||||||
|
*
|
||||||
|
* 2022-03-19: Fixed bug that ISI was applied also to last item.
|
||||||
**/
|
**/
|
||||||
|
|
||||||
jsPsych.plugins["audio-sequence-button-response"] = (function() {
|
jsPsych.plugins["audio-sequence-button-response"] = (function() {
|
||||||
@@ -103,11 +105,7 @@ jsPsych.plugins["audio-sequence-button-response"] = (function() {
|
|||||||
plugin.trial = function(display_element, trial) {
|
plugin.trial = function(display_element, trial) {
|
||||||
|
|
||||||
var context = jsPsych.pluginAPI.audioContext();
|
var context = jsPsych.pluginAPI.audioContext();
|
||||||
if(context !== null) {
|
var audio;
|
||||||
var source;
|
|
||||||
} else {
|
|
||||||
var audio;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(trial.visual_feedback===true && trial.i_correct===null)
|
if(trial.visual_feedback===true && trial.i_correct===null)
|
||||||
throw "'i_correct' has to be defined if visual feedback is requested.";
|
throw "'i_correct' has to be defined if visual feedback is requested.";
|
||||||
@@ -125,12 +123,6 @@ jsPsych.plugins["audio-sequence-button-response"] = (function() {
|
|||||||
play_next_audio.i = 0;
|
play_next_audio.i = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
// We un-highlight the previous button
|
|
||||||
if(play_next_audio.i>0)
|
|
||||||
$(display_element).find('#jspsych-audio-sequence-button-response-' + (play_next_audio.i-1) +' button').toggleClass('highlighted');
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Is it the last stimulus, do we need to end trial?
|
// Is it the last stimulus, do we need to end trial?
|
||||||
if(play_next_audio.i >= trial.stimuli.length) {
|
if(play_next_audio.i >= trial.stimuli.length) {
|
||||||
$(display_element).find(".jspsych-audio-sequence-button-response button").removeClass("disabled").prop('disabled', false);
|
$(display_element).find(".jspsych-audio-sequence-button-response button").removeClass("disabled").prop('disabled', false);
|
||||||
@@ -141,34 +133,37 @@ jsPsych.plugins["audio-sequence-button-response"] = (function() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Prepare the next sound to play
|
// Prepare the next sound to play
|
||||||
if(context !== null) {
|
jsPsych.pluginAPI.getAudioBuffer(trial.stimuli[play_next_audio.i]).then(function(buffer){
|
||||||
source = context.createBufferSource();
|
if(context !== null) {
|
||||||
source.buffer = jsPsych.pluginAPI.getAudioBuffer(trial.stimuli[play_next_audio.i]);
|
audio = context.createBufferSource();
|
||||||
source.connect(context.destination);
|
audio.buffer = buffer;
|
||||||
source.onended = function(){
|
audio.connect(context.destination);
|
||||||
|
} else {
|
||||||
|
audio = buffer;
|
||||||
|
audio.currentTime = 0;
|
||||||
|
}
|
||||||
|
audio.addEventListener('ended', function _audio_ended(){
|
||||||
$(display_element).find('.jspsych-audio-sequence-button-response button.highlighted').removeClass('highlighted');
|
$(display_element).find('.jspsych-audio-sequence-button-response button.highlighted').removeClass('highlighted');
|
||||||
setTimeout(play_next_audio, trial.isi);
|
if(play_next_audio.i<trial.stimuli.length){
|
||||||
};
|
setTimeout(play_next_audio, trial.isi);
|
||||||
} else {
|
} else {
|
||||||
audio = jsPsych.pluginAPI.getAudioBuffer(trial.stimuli[play_next_audio.i]);
|
setTimeout(play_next_audio, 0);
|
||||||
audio.currentTime = 0;
|
}
|
||||||
audio.addEventListener('ended', function(){
|
audio.removeEventListener('ended', _audio_ended);
|
||||||
$(display_element).find('.jspsych-audio-sequence-button-response button.highlighted').removeClass('highlighted');
|
|
||||||
setTimeout(play_next_audio, trial.isi);
|
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
// Highlight the current button
|
// Highlight the current button
|
||||||
$(display_element).find('#jspsych-audio-sequence-button-response-' + play_next_audio.i +' button').addClass('highlighted');
|
$(display_element).find('#jspsych-audio-sequence-button-response-' + play_next_audio.i +' button').addClass('highlighted');
|
||||||
|
|
||||||
if(context !== null) {
|
if(context !== null) {
|
||||||
startTime = context.currentTime;
|
startTime = context.currentTime;
|
||||||
source.start(startTime);
|
audio.start(startTime);
|
||||||
} else {
|
} else {
|
||||||
audio.play();
|
audio.play();
|
||||||
}
|
}
|
||||||
|
|
||||||
play_next_audio.i++;
|
play_next_audio.i++;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
//display buttons
|
//display buttons
|
||||||
@@ -288,12 +283,11 @@ jsPsych.plugins["audio-sequence-button-response"] = (function() {
|
|||||||
// stop the audio file if it is playing
|
// stop the audio file if it is playing
|
||||||
// remove end event listeners if they exist
|
// remove end event listeners if they exist
|
||||||
if(context !== null) {
|
if(context !== null) {
|
||||||
source.stop();
|
audio.stop();
|
||||||
source.onended = function() {}
|
|
||||||
} else {
|
} else {
|
||||||
audio.pause();
|
audio.pause();
|
||||||
audio.removeEventListener('ended', end_trial);
|
|
||||||
}
|
}
|
||||||
|
audio.removeEventListener('ended', end_trial);
|
||||||
|
|
||||||
// kill any remaining setTimeout handlers
|
// kill any remaining setTimeout handlers
|
||||||
jsPsych.pluginAPI.clearAllTimeouts();
|
jsPsych.pluginAPI.clearAllTimeouts();
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
* Etienne Gaudrain
|
* Etienne Gaudrain
|
||||||
*
|
*
|
||||||
* Based on:
|
* Based on:
|
||||||
* jspsych-image-keyboard-response @6.1.0
|
* jspsych-image-keyboard-response @6.2.0
|
||||||
* Josh de Leeuw
|
* Josh de Leeuw
|
||||||
*
|
*
|
||||||
* plugin for displaying a stimulus and getting a keyboard response or click on
|
* plugin for displaying a stimulus and getting a keyboard response or click on
|
||||||
@@ -21,7 +21,7 @@ jsPsych.plugins["image-keyboard-response-clickable"] = (function() {
|
|||||||
jsPsych.pluginAPI.registerPreload('image-keyboard-response-clickable', 'stimulus', 'image');
|
jsPsych.pluginAPI.registerPreload('image-keyboard-response-clickable', 'stimulus', 'image');
|
||||||
|
|
||||||
plugin.info = {
|
plugin.info = {
|
||||||
name: 'image-keyboard-response',
|
name: 'image-keyboard-response-clickable',
|
||||||
description: '',
|
description: '',
|
||||||
parameters: {
|
parameters: {
|
||||||
stimulus: {
|
stimulus: {
|
||||||
@@ -85,37 +85,102 @@ jsPsych.plugins["image-keyboard-response-clickable"] = (function() {
|
|||||||
default: true,
|
default: true,
|
||||||
description: 'If true, trial will end when subject makes a response.'
|
description: 'If true, trial will end when subject makes a response.'
|
||||||
},
|
},
|
||||||
|
render_on_canvas: {
|
||||||
|
type: jsPsych.plugins.parameterType.BOOL,
|
||||||
|
pretty_name: 'Render on canvas',
|
||||||
|
default: true,
|
||||||
|
description: 'If true, the image will be drawn onto a canvas element (prevents blank screen between consecutive images in some browsers).' +
|
||||||
|
'If false, the image will be shown via an img element.'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
plugin.trial = function(display_element, trial) {
|
plugin.trial = function(display_element, trial) {
|
||||||
|
|
||||||
var start_time;
|
var height, width, start_time;
|
||||||
|
if(trial.render_on_canvas) {
|
||||||
// display stimulus
|
// first clear the display element (because the render_on_canvas method appends to display_element instead of overwriting it with .innerHTML)
|
||||||
var html = '<img src="' + trial.stimulus + '" id="jspsych-image-keyboard-response-stimulus" style="';
|
if(display_element.hasChildNodes()) {
|
||||||
if(trial.stimulus_height !== null) {
|
// can't loop through child list because the list will be modified by .removeChild()
|
||||||
html += 'height:' + trial.stimulus_height + 'px; '
|
while(display_element.firstChild) {
|
||||||
if(trial.stimulus_width == null && trial.maintain_aspect_ratio) {
|
display_element.removeChild(display_element.firstChild);
|
||||||
html += 'width: auto; ';
|
}
|
||||||
}
|
}
|
||||||
}
|
// create canvas element and image
|
||||||
if(trial.stimulus_width !== null) {
|
var canvas = document.createElement("canvas");
|
||||||
html += 'width:' + trial.stimulus_width + 'px; '
|
canvas.id = "jspsych-image-keyboard-response-stimulus";
|
||||||
if(trial.stimulus_height == null && trial.maintain_aspect_ratio) {
|
canvas.style.margin = 0;
|
||||||
html += 'height: auto; ';
|
canvas.style.padding = 0;
|
||||||
|
var img = new Image();
|
||||||
|
img.src = trial.stimulus;
|
||||||
|
// determine image height and width
|
||||||
|
if(trial.stimulus_height !== null) {
|
||||||
|
height = trial.stimulus_height;
|
||||||
|
if(trial.stimulus_width == null && trial.maintain_aspect_ratio) {
|
||||||
|
width = img.naturalWidth * (trial.stimulus_height / img.naturalHeight);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
height = img.naturalHeight;
|
||||||
|
}
|
||||||
|
if(trial.stimulus_width !== null) {
|
||||||
|
width = trial.stimulus_width;
|
||||||
|
if(trial.stimulus_height == null && trial.maintain_aspect_ratio) {
|
||||||
|
height = img.naturalHeight * (trial.stimulus_width / img.naturalWidth);
|
||||||
|
}
|
||||||
|
} else if(!(trial.stimulus_height !== null & trial.maintain_aspect_ratio)) {
|
||||||
|
// if stimulus width is null, only use the image's natural width if the width value wasn't set
|
||||||
|
// in the if statement above, based on a specified height and maintain_aspect_ratio = true
|
||||||
|
width = img.naturalWidth;
|
||||||
|
}
|
||||||
|
canvas.height = height;
|
||||||
|
canvas.width = width;
|
||||||
|
// add canvas and draw image
|
||||||
|
display_element.insertBefore(canvas, null);
|
||||||
|
var ctx = canvas.getContext("2d");
|
||||||
|
ctx.drawImage(img, 0, 0, width, height);
|
||||||
|
// add prompt if there is one
|
||||||
|
if(trial.prompt !== null) {
|
||||||
|
display_element.insertAdjacentHTML('beforeend', trial.prompt);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
html += '"></img>';
|
|
||||||
|
|
||||||
// add prompt
|
start_time = performance.now();
|
||||||
if(trial.prompt !== null) {
|
|
||||||
html += trial.prompt;
|
|
||||||
}
|
|
||||||
|
|
||||||
// render
|
} else {
|
||||||
display_element.innerHTML = html;
|
|
||||||
start_time = performance.now();
|
// display stimulus as an image element
|
||||||
|
var html = '<img src="' + trial.stimulus + '" id="jspsych-image-keyboard-response-stimulus">';
|
||||||
|
// add prompt
|
||||||
|
if(trial.prompt !== null) {
|
||||||
|
html += trial.prompt;
|
||||||
|
}
|
||||||
|
// update the page content
|
||||||
|
display_element.innerHTML = html;
|
||||||
|
|
||||||
|
// set image dimensions after image has loaded (so that we have access to naturalHeight/naturalWidth)
|
||||||
|
var img = display_element.querySelector('#jspsych-image-keyboard-response-stimulus');
|
||||||
|
if(trial.stimulus_height !== null) {
|
||||||
|
height = trial.stimulus_height;
|
||||||
|
if(trial.stimulus_width == null && trial.maintain_aspect_ratio) {
|
||||||
|
width = img.naturalWidth * (trial.stimulus_height / img.naturalHeight);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
height = img.naturalHeight;
|
||||||
|
}
|
||||||
|
if(trial.stimulus_width !== null) {
|
||||||
|
width = trial.stimulus_width;
|
||||||
|
if(trial.stimulus_height == null && trial.maintain_aspect_ratio) {
|
||||||
|
height = img.naturalHeight * (trial.stimulus_width / img.naturalWidth);
|
||||||
|
}
|
||||||
|
} else if(!(trial.stimulus_height !== null & trial.maintain_aspect_ratio)) {
|
||||||
|
// if stimulus width is null, only use the image's natural width if the width value wasn't set
|
||||||
|
// in the if statement above, based on a specified height and maintain_aspect_ratio = true
|
||||||
|
width = img.naturalWidth;
|
||||||
|
}
|
||||||
|
img.style.height = height.toString() + "px";
|
||||||
|
img.style.width = width.toString() + "px";
|
||||||
|
|
||||||
|
start_time = performance.now();
|
||||||
|
}
|
||||||
|
|
||||||
// store response
|
// store response
|
||||||
var response = {
|
var response = {
|
||||||
@@ -165,17 +230,6 @@ jsPsych.plugins["image-keyboard-response-clickable"] = (function() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// start the response listener
|
|
||||||
if(trial.choices != jsPsych.NO_KEYS) {
|
|
||||||
var keyboardListener = jsPsych.pluginAPI.getKeyboardResponse({
|
|
||||||
callback_function: after_response,
|
|
||||||
valid_responses: trial.choices,
|
|
||||||
rt_method: 'performance',
|
|
||||||
persist: false,
|
|
||||||
allow_held_key: false
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if(trial.clickable){
|
if(trial.clickable){
|
||||||
display_element.querySelectorAll(".clickable").forEach(function(e){
|
display_element.querySelectorAll(".clickable").forEach(function(e){
|
||||||
var clickHandler = function(event){
|
var clickHandler = function(event){
|
||||||
@@ -188,6 +242,17 @@ jsPsych.plugins["image-keyboard-response-clickable"] = (function() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// start the response listener
|
||||||
|
if(trial.choices != jsPsych.NO_KEYS) {
|
||||||
|
var keyboardListener = jsPsych.pluginAPI.getKeyboardResponse({
|
||||||
|
callback_function: after_response,
|
||||||
|
valid_responses: trial.choices,
|
||||||
|
rt_method: 'performance',
|
||||||
|
persist: false,
|
||||||
|
allow_held_key: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// hide stimulus if stimulus_duration is set
|
// hide stimulus if stimulus_duration is set
|
||||||
if(trial.stimulus_duration !== null) {
|
if(trial.stimulus_duration !== null) {
|
||||||
jsPsych.pluginAPI.setTimeout(function() {
|
jsPsych.pluginAPI.setTimeout(function() {
|
||||||
@@ -200,8 +265,9 @@ jsPsych.plugins["image-keyboard-response-clickable"] = (function() {
|
|||||||
jsPsych.pluginAPI.setTimeout(function() {
|
jsPsych.pluginAPI.setTimeout(function() {
|
||||||
end_trial();
|
end_trial();
|
||||||
}, trial.trial_duration);
|
}, trial.trial_duration);
|
||||||
|
} else if(trial.response_ends_trial === false) {
|
||||||
|
console.warn("The experiment may be deadlocked. Try setting a trial duration or set response_ends_trial to true.");
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return plugin;
|
return plugin;
|
||||||
|
|||||||
BIN
tests/res/eg-syllables-bi01.wav
Normal file
BIN
tests/res/eg-syllables-bi01.wav
Normal file
Binary file not shown.
BIN
tests/res/eg-syllables-bu01.wav
Normal file
BIN
tests/res/eg-syllables-bu01.wav
Normal file
Binary file not shown.
BIN
tests/res/eg-syllables-di01.wav
Normal file
BIN
tests/res/eg-syllables-di01.wav
Normal file
Binary file not shown.
BIN
tests/res/eg-syllables-du01.wav
Normal file
BIN
tests/res/eg-syllables-du01.wav
Normal file
Binary file not shown.
64
tests/test_jspsych-audio-sequence-button-response.html
Normal file
64
tests/test_jspsych-audio-sequence-button-response.html
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<title>Test for jspsych-html-keyboard-response-clickable</title>
|
||||||
|
<script src="https://cdn.jsdelivr.net/gh/jquery/jquery/dist/jquery.min.js"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/gh/jspsych/jsPsych@6.3.1/jspsych.js"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/gh/jspsych/jsPsych@6.3.1/plugins/jspsych-html-button-response.js"></script>
|
||||||
|
<link href="https://cdn.jsdelivr.net/gh/jspsych/jsPsych@6.3.1/css/jspsych.css" rel="stylesheet" type="text/css">
|
||||||
|
<script src="../plugins/jspsych-audio-sequence-button-response.js"></script>
|
||||||
|
<style>
|
||||||
|
.jspsych-audio-sequence-button-response button {
|
||||||
|
min-width: 5em;
|
||||||
|
min-height: 4em;
|
||||||
|
}
|
||||||
|
.jspsych-audio-sequence-button-response button.highlighted {
|
||||||
|
background-color: #ffff00 !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', function(event) {
|
||||||
|
|
||||||
|
var timeline = [];
|
||||||
|
|
||||||
|
timeline.push({
|
||||||
|
type: 'html-button-response',
|
||||||
|
choices: ['start'],
|
||||||
|
stimulus: ""
|
||||||
|
});
|
||||||
|
|
||||||
|
timeline.push({
|
||||||
|
type: 'audio-sequence-button-response',
|
||||||
|
choices: [' ', ' ', ' '],
|
||||||
|
prompt: '<p>Prompt</p>',
|
||||||
|
stimuli: ['res/eg-syllables-bi01.wav', 'res/eg-syllables-bu01.wav', 'res/eg-syllables-di01.wav'],
|
||||||
|
trial_ends_after_audio: true,
|
||||||
|
on_finish: function(){
|
||||||
|
console.log("Trial is finished.");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
timeline.push({
|
||||||
|
type: 'audio-sequence-button-response',
|
||||||
|
choices: [' ', ' ', ' '],
|
||||||
|
prompt: '<p>Prompt</p>',
|
||||||
|
stimuli: ['res/eg-syllables-bi01.wav', 'res/eg-syllables-bu01.wav', 'res/eg-syllables-di01.wav'],
|
||||||
|
trial_ends_after_audio: true,
|
||||||
|
on_finish: function(){
|
||||||
|
console.log("Trial is finished.");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
jsPsych.init({
|
||||||
|
timeline: timeline
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
Reference in New Issue
Block a user