分享下一个图片描述范式的运用 jsPsych 的JavaScript, 因为有录音部分,实验需要用Chrome 或者 Firefox 进行浏览。 小白一枚,欢迎补充和指导。
实验流程是:看fixation -》图片-》听录音-》判断对错(对d,错k)-》哔音 - 》看图片描述(并录音)(如图)
window.alert('Hello, you will be audio recorded in this experiment. '); //随便打个招呼
//ID assignment (给被试分配ID)
var subject_id = Math.floor(Math.random()*100000); //give participant a random id
jsPsych.data.addProperties({
subject: subject_id
});
//提示被试的浏览器有问题,并且被试需要同意被录音的对话框。 这个部分是关于允许录音和储存
var wrong_browser_message = "Sorry, it's not possible to run the experiment on your web browser. Please try using Chrome or Firefox instead.";
var declined_audio_message = "You must allow audio recording to take part in the experiment. Please allow access to your microphone and reload the page to proceed.";
//声音生成variables
var audio_chunks = []; //empty array for blob content
var rec; // MediaRecorder
var next_audio_filename; //holder for complete audio file name
var trial_counter = 0; //increments to differentiate audio file names, without different names files may overwrite
//欢迎部分
var welcome_block = {
data:{
screen_id:'Welcome',
},
type:'html-button-response',
stimulus: '<p> Welcome to this experiment! </p>'+
'<p>This experiment requires you to listen to the audio and then click the correpsonding pictures,</p>'+
'<p>after the choice, you need to finish a question,</p>'+
'<p>You will be recorded when you answer questions,</p>'+
'<p>Clicking the button below counts as an interaction.If you understand the information above </p>'+
'<p>please click the <b>[Continue]</b> botton</p>',
choices: ['Continue']
};
// 同意意向书
var consent = {
data:{
screen_id:'consent',
},
type:'instructions',
pages:[
If you understand above, please click [Next]"],
stimulus:'',
show_clickable_nav:true
};
//Randomize trial number(一共有8个trials,所以排序从0开始到7, 然后是随机选择)
var trial_num = [0,1,2,3,4,5,6,7]
var trial_num = jsPsych.randomization.shuffle(trial_num);
console.log(trial_num) // randomize trial numbers
//Create stimuli array 创建实验刺激串 imgs指的是文件夹,E01PA...指得图片png文件, xxx.wav是音频文件。
prime_stimuli = [
'imgs/E01PA.png',
'imgs/E04PA.png',
'imgs/E03PA.png',
'imgs/E07PA.png',
'imgs/E05PA.png',
'imgs/E02PA.png',
'imgs/E06PA.png',
'imgs/E08PA.png',
]
//用html图片使用在图片播放的同时获取录音,方便利用后面的audio-button-response 的plugin
html_prime_stimuli = [
'<img src = "imgs/E01PA.png" width = 100% height = 100%"/>',
'<img src = "imgs/E04PA.png" width = 100% height = 100%"/>',
'<img src = "imgs/E03PA.png" width = 100% height = 100%"/>',
'<img src = "imgs/E07PA.png" width = 100% height = 100%"/>',
'<img src = "imgs/E05PA.png" width = 100% height = 100%"/>',
'<img src = "imgs/E02PA.png" width = 100% height = 100%"/>',
'<img src = "imgs/E06PA.png" width = 100% height = 100%"/>',
'<img src = "imgs/E08PA.png" width = 100% height = 100%"/>',
]
//同上
prime_audio = [
'sound/E01A.wav',
'sound/E04A.wav',
'sound/E03A.wav',
'sound/E07A.wav',
'sound/E05A.wav',
'sound/E02A.wav',
'sound/E06A.wav',
'sound/E08A.wav',
]
target_stimuli = [
'imgs/E01TA.png',
'imgs/E04TA.png',
'imgs/E03TA.png',
'imgs/E07TA.png',
'imgs/E05TA.png',
'imgs/E02TA.png',
'imgs/E06TA.png',
'imgs/E08TA.png',
]
//从这里开始开始图片描述的任务
var experiment_blocks = [welcome_block, consent];
for (i in trial_num){
jsPsych.data.addProperties({
trial:trial_num
}); // 当 i是0-7的时候
//这里被试听看到的是一个类似与fixation的图片。listen.png是图片中耳机的图片
listening_sign = {
type: 'image-keyboard-response',
stimulus: 'imgs/listen.png',
choices: jsPsych.NO_KEYS,
trial_duration:1500,
prompt:'<p> Please listen it carefully </p>'
};
//这里被试看到了图片并且听到了相匹配的录音, 这里在一个block中
experiment_blocks.push(listening_sign);
priming_sound_block = {
// creates trial-internal timeline
timeline: [{
// audio exposure trial with associated image displayed as button
type: 'audio-button-response',
stimulus: prime_audio[trial_num[i]],
choices: [prime_stimuli[trial_num[i]]],
button_html:'<img src="%choice%" />', // html image to overlay button
trial_name: 'priming_audio_trial',
response_ends_trial: false, //prevents response from ending trial
trial_ends_after_audio: true,
},
{
type: 'image-keyboard-response',
stimulus: prime_stimuli[trial_num[i]], // calls image stimuli
choices: ['d','j'], // only allows progression using spacebar
trial_name: 'noun_image_following_trial',
post_trial_gap: 1500
}, //这里需要被试判断对错
],
};
experiment_blocks.push(priming_sound_block);
//这里是一个fixation 并提示被试描述接下来的图片
speaking_sign = {
type:'image-keyboard-response',
stimulus:'imgs/speak.png',
choices:jsPsych.NO_KEYS,
trial_duration:1500,
prompt:'<p> Please describe the following picture </p>'
};
experiment_blocks.push(speaking_sign);
//这里是一个哔声,哔声后被试开始作答
beep_sound ={
type:'audio-keyboard-response',
choices:jsPsych.NO_KEYS,
stimulus:'sound/beep.mp3',
trial_ends_after_audio:true,
response_ends_trial:false,
on_finish:function(){
jsPsych.data.addDataToLastTrial();
}
};
experiment_blocks.push(beep_sound);
// 这里是目标图片,被试描述并且在描述的同时,录音
target_block = {
type:'image-keyboard-response',
choices:['spacebar'],
stimulus:target_stimuli[trial_num[i]],
response_ends_trial:true,
on_finish:function(){
jsPsych.data.addDataToLastTrial();
}
};
//这里选择性有或者没有,有的话,被试在录音后可以听到自己的录音,可以选择没有这部分
replay_trial = {
type:'html-button-response',
stimulus:'<p>Use the controls below to hear the answer you gave. Then press
continue to progress.</p>',
choices:['Continue']
};
// 录音
audio_recording_trial = { //internal timeline defines the order in which the abover trials happen and when recording functions are called
timeline: [
start_recording(subject_id),
target_block,
stop_recording(),
replay_trial
],
timeline_variables: target_block
};
experiment_blocks.push(audio_recording_trial);
//开始录音的function
function start_recording(filename) {
return {
type: "call-function", //make sure you load this as a plugin in your html file just like you would with something like audio-keyboard-response
func: function() {
audio_chunks = []; //clears global audio_chunks of previous blob content, needed for recording multipe trials in seperate files
next_audio_filename = filename + '_test_' + trial_counter; //name of audio file will be something like rxe32hl37h_test_0.webm
rec.start(); // starts audio recording
trial_counter += 1; //adds 1 to trial counter
}
}
};
// 停止录音的function
function stop_recording() {
return {
type: "call-function",
func: function() {
rec.stop(); // stops recording and triggers onstop event below
}
}
};
}
//在重新播放的部分的音频的设置的 function
function generate_replay(item) {
var div = document.getElementById("jspsych-content");//makes div key to access jspsych content part of window
var audio = document.createElement("audio");//creates an audio element
audioUrl = URL.createObjectURL(item); //constructs URL that references the blob passed into the function
audio.src = audioUrl;//sets file url as src of audio
audio.style.cssFloat = "right";//sets where the controls appear
audio.controls=true;//activates browser internal audio controls
div.appendChild(audio);//appends audio element as child of "jspsych-content"
};
//保存data
function saveData(name, data){
var url = 'record_result.php'; // external .php file
var data = {filename: name, filedata: data};
data = JSON.stringify(data);
fetch(url, {
method: 'POST',
body: data,
headers: new Headers({
'Content-Type': 'application/json'
})
});
};
//想要保存的data 的部分
function saveDataLine(data) {
// choose the data we want to save
var data_to_save = [
data.rt, data.stimulus, data.key_press, data.name, data.trial_type,
data.trial_index, data.time_elapsed, data.internal_node_id,
data.Worker_ID, data.choices, data.answer, data.button_pressed,
data.value, data.responses
];
//在存储csv的时候在数据中加入逗号
var line = data_to_save.join(',')+"n";
saveData(participant_id + ".csv", line);
}
// 存储音频的data
function saveAudio(name, audio_data) {
var url = 'record_audio.php'; // external .php file that should be in same folder as your experiment
form_data = new FormData();
form_data.append("filename", name);
form_data.append("filedata", audio_data);
fetch(url, {
method: 'POST',
body: form_data
});
};
// 如果浏览器不支持MediaRecorder,或者麦克风没有被允许。会给出提示。
function errorQuit(message) {
var body = document.getElementsByTagName('body')[0];
body.innerHTML = '<p style="color: #FF0000">'+message+'</p>'+body.innerHTML;//defines the style of error messages
throw error;
};
//如果浏览器或者音频介入有错误,会退出
navigator.mediaDevices.getUserMedia({audio:true})
.then(stream => {handlerFunction(stream)})
.catch(error => {errorQuit(declined_audio_message)});
//还是录音的一个function,这里有点没懂,抄来的一段。
function handlerFunction(stream) {
try {
rec = new MediaRecorder(stream);
} catch(error) {
errorQuit(wrong_browser_message);
};
rec.ondataavailable = e => {
audio_chunks.push(e.data);//pushes blob to "audio_chunks" variable above
};
rec.onstop = e => { //handles the stop event, everything below happens when stop_recording() is called
//note that this audio needs to be converted after it has been saved to the server
let blob = new Blob(audio_chunks,{type:'audio/webm'});
generate_replay(blob);//call to function that generates replay for next trial
saveAudio(next_audio_filename, blob); //calls saveAudio, one parameter for the name of audio file and one for audio content
};
};
//通用结尾
jsPsych.init({
timeline: experiment_blocks,
use_webaudio: false,
on_finish: function(data){
var alldata = jsPsych.data.get();
SaveData("test.csv",experiment_data.csv());
}
});
//}).call(this);
-------------------
补充一条遇到过的bug的解决方法,如果这一行出现如下的提示,把网址 http 改成https。
navigat or.mediaDevices.getUserMedia({audio:true})这一行如果出现如下的提示
Uncaught TypeError: Cannot read property 'getUserMedia' of undefined
--------------------
补充第二条,录音需要收集录音的php,格式如下:
<?php
//build filename from workerId and day
function cleanInput($data) {
$data = trim($data);
$data = stripslashes($data);
$data = str_replace('/','',$data);
$data = str_replace('.','',$data);
return $data;
}
$filename = cleanInput($_REQUEST["filename"]);
copy($_FILES['filedata']['tmp_name'], '/home/domainname/server_data/audio_data/' . $filename . ".webm");
?> // 存储在与实验文件夹相并列的文件夹中,注意:不是实验的子文件夹。把其中的domainname换成自己的domainname。