Difference between revisions of "Web-based perception experiments"
Keithjohnson (talk | contribs) |
Keithjohnson (talk | contribs) |
||
Line 61: | Line 61: | ||
4) A <form ...> element named "dataform" |
4) A <form ...> element named "dataform" |
||
*the order of the input items determines the column order in the output file. |
*the order of the input items determines the column order in the output file. |
||
− | <form method="POST" id="dataform" action="process.php? |
+ | <form method="POST" id="dataform" action="process.php?n=unique"> |
<input type="hidden" name="subject" value=<?php echo $subj; ?> /> |
<input type="hidden" name="subject" value=<?php echo $subj; ?> /> |
||
<input type="hidden" name="trial" /> |
<input type="hidden" name="trial" /> |
||
Line 84: | Line 84: | ||
Finally, in (4) above you may have noticed there was reference to a file: process.php. Here is that file. |
Finally, in (4) above you may have noticed there was reference to a file: process.php. Here is that file. |
||
− | |||
<?php |
<?php |
||
+ | function safe_post_param($p, $maxlen) { |
||
⚫ | |||
+ | if (isset($_POST[$p]) && preg_match("/^[a-zA-Z0-9 \.-_]{1,$maxlen}$/", $_POST[$p])) { |
||
− | $n = $_GET["n"]; |
||
+ | $val = $_POST[$p]; |
||
− | $datafile = $prefix . "_data" . $n . ".csv"; |
||
+ | } else { $val = '<invalid>'; } |
||
⚫ | |||
+ | return $val; |
||
− | $data = "empty"; |
||
− | foreach($_POST as $key=>$value) { // we don't have to know the keys |
||
− | if ($data == "empty") { $data = $value; } |
||
− | else {$data = $data . "," . $value;} |
||
⚫ | |||
⚫ | |||
⚫ | |||
⚫ | |||
− | echo "saved data"; |
||
⚫ | |||
− | echo "there was a problem writing the data"; |
||
⚫ | |||
− | } else { |
||
− | http_response_code(403); |
||
− | echo "invalid submission"; |
||
} |
} |
||
+ | |||
+ | if (isset($_GET['n']) && preg_match("/^\w+$/", $_GET['n'])) { |
||
⚫ | |||
+ | $incfile = 'ep_' . $n . '.inc'; |
||
+ | $success = include_once($incfile); |
||
+ | if (! $success) { |
||
+ | throw new Exception("didn't open the include file."); |
||
⚫ | |||
⚫ | |||
+ | throw new Exception("didn't find GET parameter."); |
||
+ | exit(); |
||
+ | } |
||
+ | |||
+ | $form_params['subject'] = 20; |
||
+ | $form_params['trial'] = 5; |
||
+ | $form_params['list'] = 5; |
||
+ | $form_params['file1'] = 20; |
||
+ | if ($type=='ax' || $type =='cr') {$form_params['file2'] = 20; } |
||
+ | $form_params['filedur'] = 10; |
||
+ | $form_params['loadtime'] = 10; |
||
+ | $form_params['mystatus'] = 20; |
||
+ | $form_params['response'] = 10; |
||
+ | $form_params['rt'] = 10; |
||
+ | |||
⚫ | |||
+ | $formdata = []; |
||
+ | foreach($form_params as $p=>$maxlen) { |
||
+ | array_push($formdata, safe_post_param($p, $maxlen)); |
||
⚫ | |||
⚫ | |||
⚫ | |||
⚫ | |||
+ | throw new Exception("error on file_put_contents()"); |
||
+ | } |
||
+ | } |
||
+ | |||
?> |
?> |
||
+ | *This script constructs a filename from the parameter that you pass to it. The parameter is ?n=xxx and the script constructs a file name "ep_xxx.inc" and then "includes" that file so the script now knows the value of a hidden variable called $datafile that contains the name of the file where you will save the experiment data. |
||
− | * |
+ | ** for example, if ep_xxx.inc contains $datafile="id_data1.csv" then your data will be stored, one line per button press response, into the .csv file. |
− | ** for example, "process.php?p=id&n=1" translates into the filename "id_data1.csv" |
||
*The <input..> elements of "dataform" (# 4 above) are now saved as columns in the data file. |
*The <input..> elements of "dataform" (# 4 above) are now saved as columns in the data file. |
||
+ | ** process.php checks the names of the input elements in the form, and only accepts data from these form elements. |
||
*The data file must already exist on the server |
*The data file must already exist on the server |
||
** This script adds lines, but does not create a new file |
** This script adds lines, but does not create a new file |
||
+ | ** The data file must have access privileges that let the php script write to the file (see Ronald Sprouse about how to set these privileges). |
||
− | ** The data file must be writable, for example: chmod a+w id_data1.csv |
||
Revision as of 09:01, 14 February 2018
Experiments can be hosted on the Department of Linguistics server, and then distributed to listeners by email link, or via Amazon Mechanical Turk.
An example is here: example experiment
A Javascript library audexp.js makes it realatively easy to implement the following four typical kinds of experiments:
- Identification (id) - a single audio file is played, and a two-alternative forced choice (2AFC) is given.
- Discrimination (ax) - two audio files are played, and a 2AFC is given.
- Rating (r) - a single audio file is played, and a rating number (from 1 to 7) is given.
- Contrast Rating (cr) - two audio files are played, and a rating number is given.
Your html code must have four features:
1) after loading audexp.js, load a javascript file that defines:
- an array (or two arrays) of filenames that will be presented.
- a variable called 'block'
- here's an example of such a file
- this .js file was created with a small perl script from a .csv spreadsheet.
- the html header contains the following two lines to get the audexp.js library and the experiment-specific list of sound files.
<script src="js/audexp.js"></script> <script src="js/blocktest_list.js"></script>
In the experiment linked above, the link is to a php script which dynamically constructs the html code that is delivered the user's web browser. The word 'test' is passed to the php script in the URL: http://linguistics.berkeley.edu/~kjohnson/rating_exp.php?list=test
The list variable is read from the php $_GET variable:
<?php $block = $GET["list"]; ?>
In the php script, the javascript lines above are generated with an embedded bit of php code (as below). This makes it possible for me to deploy different experiments (that just differ in the list of sound files that will be played), using the same php script. The script constructs different list file names on the fly from the "list" variable that is given in the URL.
<script src="js/audexp.js"></script> <script src="js/block<?php echo $block; ?>_list.js"></script>
2) a call to load the experiment when the page is loaded
- this call specifies the type of experiment (in the script below this is 'id')
- whether to randomize the order of presentation of the list of sound files
- the interstimulus interval for 'ax' and 'cr' types
- and the intertrial interval for all experiments.
<body onload="load('id',false,500,2000);">
3) Three or four span elements that will be used to give feedback to listeners.
- Show warnings to the listener
<span id="wr"></span>
- indicate that an audio file is playing
<span id="f1">sound 1</span>
- indicate that a second audio file is playing (for ax, and cr type experiments)
<span id="f2">sound 2</span>
- give feedback to the lister, showing which key they pressed.
<span id="key">#</span>
4) A <form ...> element named "dataform"
- the order of the input items determines the column order in the output file.
<form method="POST" id="dataform" action="process.php?n=unique"> <input type="hidden" name="subject" value=<?php echo $subj; ?> /> <input type="hidden" name="trial" /> <input type="hidden" name="list" /> <input type="hidden" name="file1" /> <input type="hidden" name="filedur" /> <input type="hidden" name="mystatus" /> <input type="hidden" name="loadtime" /> <input type="hidden" name="response" /> <input type="hidden" name="rt" /> </form>
The experiment php script linked at the top of this page generated a unique subject number for the user with a little php code:
<?php $block = $_GET["list"]; $subj = uniqid($block); ?>
This subject number will start with the word or number that you passed to the script as 'list' in the url (http:\\linguistics.berkeley.edu\~kjohnson\rate_exp.php?list=test).
Saving data to a server file
Finally, in (4) above you may have noticed there was reference to a file: process.php. Here is that file.
<?php function safe_post_param($p, $maxlen) { if (isset($_POST[$p]) && preg_match("/^[a-zA-Z0-9 \.-_]{1,$maxlen}$/", $_POST[$p])) { $val = $_POST[$p]; } else { $val = '<invalid>'; } return $val; }
if (isset($_GET['n']) && preg_match("/^\w+$/", $_GET['n'])) { $n = $_GET['n']; $incfile = 'ep_' . $n . '.inc'; $success = include_once($incfile); if (! $success) { throw new Exception("didn't open the include file."); } } else { throw new Exception("didn't find GET parameter."); exit(); }
$form_params['subject'] = 20; $form_params['trial'] = 5; $form_params['list'] = 5; $form_params['file1'] = 20; if ($type=='ax' || $type =='cr') {$form_params['file2'] = 20; } $form_params['filedur'] = 10; $form_params['loadtime'] = 10; $form_params['mystatus'] = 20; $form_params['response'] = 10; $form_params['rt'] = 10;
if ($_SERVER["REQUEST_METHOD"] == "POST") { $formdata = []; foreach($form_params as $p=>$maxlen) { array_push($formdata, safe_post_param($p, $maxlen)); } $data = join(",", $formdata) . "\n"; $ret = file_put_contents($datafile,$data,FILE_APPEND| LOCK_EX); if (! $ret) { throw new Exception("error on file_put_contents()"); } }
?>
- This script constructs a filename from the parameter that you pass to it. The parameter is ?n=xxx and the script constructs a file name "ep_xxx.inc" and then "includes" that file so the script now knows the value of a hidden variable called $datafile that contains the name of the file where you will save the experiment data.
- for example, if ep_xxx.inc contains $datafile="id_data1.csv" then your data will be stored, one line per button press response, into the .csv file.
- The <input..> elements of "dataform" (# 4 above) are now saved as columns in the data file.
- process.php checks the names of the input elements in the form, and only accepts data from these form elements.
- The data file must already exist on the server
- This script adds lines, but does not create a new file
- The data file must have access privileges that let the php script write to the file (see Ronald Sprouse about how to set these privileges).
- You can read the data file directly into R from your server address:
data <- read.csv("http://linguistics.berkeley.edu/~kjohnson/talkers/id_data.csv",header=TRUE);