Mercurial > hg > webaudioevaluationtool
view analyse.html @ 1116:c44fbf72f7f2
All interfaces support comment boxes. Comment box identification matches presented tag (for instance, AB will be Comment on fragment A, rather than 1). Tighter buffer loading protocol, audioObjects register with the buffer rather than checking for buffer existence (which can be buggy depending on the buffer state). Buffers now have a state to ensure exact location in loading chain (downloading, decoding, LUFS, ready).
author | Nicholas Jillings <n.g.r.jillings@se14.qmul.ac.uk> |
---|---|
date | Fri, 29 Jan 2016 11:11:57 +0000 |
parents | |
children | b5bf2f57187c 9ee921c8cdd3 eef2d4ea18fb |
line wrap: on
line source
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <!-- Always force latest IE rendering engine (even in intranet) & Chrome Frame Remove this if you use the .htaccess --> <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> <title>Analysis</title> <meta name="description" content="Show results from subjective evaluation"> <meta name="author" content="Brecht De Man"> <script type="text/javascript" src="https://www.google.com/jsapi"></script> <script type="text/javascript"> // To aid 'one-page set-up' all scripts and CSS must be included directly in this file! google.load("visualization", "1", {packages:["corechart"]}); /************* * SETUP * *************/ // folder where to find the XML files xmlFileFolder = "saves"; // array of XML files // THIS IS WHERE YOU SPECIFY RESULT XML FILES TO ANALYSE var xmlFiles = ['test-0.xml','test-1.xml','test-2.xml','test-3.xml']; //TODO: make retrieval of file names automatic / drag files on here /**************** * VARIABLES * ****************/ // Counters // How many files, audioholders, audioelementes and statements annotated (don't count current one) var numberOfFiles = -1; var numberOfaudioholders = -1; var numberOfaudioelementes = -1; var numberOfStatements = -1; var numberOfSkippedComments = 0; // Object arrays var fileNameArray = []; var subjectArray = []; var audioholderArray = []; var audioelementArray = []; // End of (file, audioholder, audioelement) flags var newFile = true; var newAudioHolder = true; var newAudioElement = true; var fileCounter = 0; // file index var audioholderCounter=0; // audioholder index (current XML file) var audioelementCounter=0; // audioelement index (current audioholder) var statementNumber=0; // total number of statements var root; // root of XML file var commentInFull = ''; // full comment var playAudio = true; // whether corresponding audio should be played back // // Measuring time // var lastTimeMeasured = -1; // // var durationLastAnnotation = -1; // duration of last annotation // var timeArray = []; // var MIN_TIME = 1.0; // minimum time counted as significant // var measurementPaused = false; // whether time measurement is paused // var timeInBuffer = 0; // var topLevel; window.onload = function() { // Initialise page topLevel = document.getElementById('topLevelBody'); var setup = document.createElement('div'); setup.id = 'setupTagDiv'; loadAllFiles(); makePlots(); printSurveyData() // measure time at this point: lastTimeMeasured = new Date().getTime(); // in milliseconds }; // Assert function function assert(condition, message) { if (!condition) { message = message || "Assertion failed"; if (typeof Error !== "undefined") { throw new Error(message); } throw message; // Fallback } } function median(values) { // TODO: replace code by '50th percentile' - should be the same? values.sort( function(a,b) {return a - b;} ); var half = Math.floor(values.length/2); if(values.length % 2) return values[half]; else return (values[half-1] + values[half]) / 2.0; } function percentile(values, n) { values.sort( function(a,b) {return a - b;} ); // get ordinal rank var rank = Math.min(Math.floor(values.length*n/100), values.length-1); return values[rank]; } /*********************** * TIME MEASUREMENT * ************************/ // measure time since last time this function was called function timeSinceLastCall() { // current time var currentTime = new Date().getTime(); // calculate time difference var timeDifference = currentTime - lastTimeMeasured + timeInBuffer; // clear buffer (for pausing) timeInBuffer = 0; // remember last measured time lastTimeMeasured = currentTime; return timeDifference; } // pause time measurement function pauseTimeMeasurement() { // UN-PAUSE if (measurementPaused) { // already paused // button shows 'pause' again document.getElementById('pauseButton').innerHTML = 'Pause'; // toggle state measurementPaused = false; // resume time measurement lastTimeMeasured = new Date().getTime(); // reset time, discard time while paused } else { // PAUSE // button shows 'resume' document.getElementById('pauseButton').innerHTML = 'Resume'; // toggle state measurementPaused = true; // pause time measurement timeInBuffer = timeSinceLastCall(); } } // show elapsed time on interface function showTimeElapsedInSeconds() { // if paused: un-pause if (measurementPaused) { pauseTimeMeasurement(); } // time of last annotation var lastAnnotationTime = timeSinceLastCall()/1000; document.getElementById('timeDisplay').innerHTML = lastAnnotationTime.toFixed(2); // average time over last ... annotations var avgAnnotationTime; var numberOfElementsToAverage = document.getElementById('numberOfTimeAverages').value; if (isPositiveInteger(numberOfElementsToAverage)) { avgAnnotationTime = calculateAverageTime(lastAnnotationTime, Number(numberOfElementsToAverage)); } else { // change text field content to 'ALL' document.getElementById('numberOfTimeAverages').value = 'ALL'; avgAnnotationTime = calculateAverageTime(lastAnnotationTime, -1); } document.getElementById('timeAverageDisplay').innerHTML = avgAnnotationTime.toFixed(2); } // auxiliary function: is string a positive integer? // http://stackoverflow.com/questions/10834796/... // validate-that-a-string-is-a-positive-integer function isPositiveInteger(str) { var n = ~~Number(str); return String(n) === str && n >= 0; } // calculate average time function calculateAverageTime(newTimeMeasurementInSeconds,numberOfPoints) { // append last measurement time to time array, if significant if (newTimeMeasurementInSeconds > MIN_TIME) { timeArray.push(newTimeMeasurementInSeconds); } // average over last N elements of this array if (numberOfPoints < 0 || numberOfPoints>=timeArray.length) { // calculate average over all var sum = 0; for (var i = 0; i < timeArray.length; i++) { sum += timeArray[i]; } averageOfTimes = sum/timeArray.length; } else { // calculate average over specified number of times measured last var sum = 0; for (var i = timeArray.length-numberOfPoints; i < timeArray.length; i++) { sum += timeArray[i]; } averageOfTimes = sum/numberOfPoints; } return averageOfTimes; } /******************************** * PLAYBACK OF AUDIO * ********************************/ //PLAYaudioelement // Keep track of whether audio should be played function playFlagChanged(){ playAudio = playFlag.checked; // global variable if (!playAudio){ // if audio needs to stop audio.pause(); // stop audio - if anything is playing currently_playing = ''; // back to empty string so playaudioelement knows nothing's playing } } // audioholder that's currently playing var currently_playing_audioholder = ''; // at first: empty string var currently_playing_audioelement = ''; var audio; // Play audioelement of audioholder if available, from start or from same position function playaudioelement(audioholderName, audioelementerName){ if (playAudio) { // if enabled // get corresponding file from folder var file_location = 'audio/'+audioholderName + '/' + audioelementerName + '.mp3'; // fixed path and file name format // if not available, show error/warning message //TODO ... // if nothing playing yet, start playing if (currently_playing_audioholder == ''){ // signal that nothing is playing //playSound(audioBuffer); audio = new Audio(file_location); audio.loop = true; // loop when end is reached audio.play(); currently_playing_audioholder = audioholderName; currently_playing_audioelement = audioelementerName; } else if (currently_playing_audioholder != audioholderName) { // if different audioholder playing, stop that and start playing audio.pause(); // stop audio audio = new Audio(file_location); // load new file audio.loop = true; // loop when end is reached audio.play(); // play audio from the start currently_playing_audioholder = audioholderName; currently_playing_audioelement = audioelementerName; } else if (currently_playing_audioelement != audioelementerName) { // if same audioholder playing, start playing from where it left off skipTime = audio.currentTime; // time to skip to audio.pause(); // stop audio audio = new Audio(file_location); audio.addEventListener('loadedmetadata', function() { this.currentTime = skipTime; console.log('Loaded '+audioholderName+'-'+audioelementerName+', playing from '+skipTime); }, false); // skip to same time when audio is loaded! audio.loop = true; // loop when end is reached audio.play(); // play from that time audio.currentTime = skipTime; currently_playing_audioholder = audioholderName; currently_playing_audioelement = audioelementerName; } // if same audioelement playing: keep on playing (i.e. do nothing) } } /******************** * READING FILES * ********************/ // Read necessary data from XML file function readXML(xmlFileName){ if (window.XMLHttpRequest) {// code for IE7+, Firefox, Chrome, Opera, Safari xmlhttp=new XMLHttpRequest(); } else {// code for IE6, IE5 xmlhttp=new ActiveXObject("Microsoft.XMLHTTP"); } xmlhttp.open("GET",xmlFileName,false); xmlhttp.send(); return xmlhttp.responseXML; } // go over all files and compute relevant statistics function loadAllFiles() { // retrieve information from XMLs for (fileIndex = 0; fileIndex < xmlFiles.length; fileIndex++) { xmlFileName = xmlFileFolder+"/"+xmlFiles[fileIndex]; xml = readXML(xmlFileName); if (xml != null) { // if file exists // append file name to array of file names fileNameArray.push(xmlFiles[fileIndex]); // get root of XML file root = xml.getElementsByTagName('browserevaluationresult')[0]; // get subject ID, add to array if not already there pretest = root.getElementsByTagName('pretest')[0]; subjectID = pretest.getElementsByTagName('comment')[0]; if (subjectID){ if (subjectID.getAttribute('id')!='sessionId') { // warning in console when not available console.log(xmlFiles[fileIndex]+': no SessionID available'); } if (subjectArray.indexOf(subjectID.textContent) == -1) { // if not already in array subjectArray.push(subjectID.textContent); // append to array } } // go over all audioholders, add to array if not already there audioholderNodes = root.getElementsByTagName('audioholder'); // go over audioholderNodes and append audioholder name when not present yet for (audioholderIndex = 0; audioholderIndex < audioholderNodes.length; audioholderIndex++) { audioholderName = audioholderNodes[audioholderIndex].getAttribute('id'); if (audioholderArray.indexOf(audioholderName) == -1) { // if not already in array audioholderArray.push(audioholderName); // append to array } // within each audioholder, go over all audioelement IDs, add to array if not already there audioelementNodes = audioholderNodes[audioholderIndex].getElementsByTagName('audioelement'); for (audioelementIndex = 0; audioelementIndex < audioelementNodes.length; audioelementIndex++) { audioelementName = audioelementNodes[audioelementIndex].getAttribute('id'); if (audioelementArray.indexOf(audioelementName) == -1) { // if not already in array audioelementArray.push(audioelementName); // append to array } } } // count occurrences of each audioholder // ... } else { console.log('XML file '+xmlFileName+' not found.'); } } // sort alphabetically fileNameArray.sort(); subjectArray.sort(); audioholderArray.sort(); audioelementArray.sort(); // display all information in HTML // show XML file folder document.getElementById('xmlFileFolder_span').innerHTML = "\""+xmlFileFolder+"/\""; // show number of files document.getElementById('numberOfFiles_span').innerHTML = fileNameArray.length; // show list of subject names document.getElementById('subjectArray_span').innerHTML = subjectArray.toString(); // show list of audioholders document.getElementById('audioholderArray_span').innerHTML = audioholderArray.toString(); // show list of audioelementes document.getElementById('audioelementArray_span').innerHTML = audioelementArray.toString(); } function printSurveyData() { // print some fields from the survey for different people // go over all XML files for (fileIndex = 0; fileIndex < xmlFiles.length; fileIndex++) { xmlFileName = xmlFileFolder+"/"+xmlFiles[fileIndex]; xml = readXML(xmlFileName); // make a div var div = document.createElement('div'); document.body.appendChild(div); div.id = 'div_survey_'+xmlFileName; div.style.width = '1100px'; //div.style.height = '350px'; // title for that div (subject id) document.getElementById('div_survey_'+xmlFileName).innerHTML = '<h2>'+xmlFileName+'</h2>'; // which songs did they do if (xml != null) { // if file exists // get root of XML file root = xml.getElementsByTagName('browserevaluationresult')[0]; // go over all audioholders // document.getElementById('div_survey_'+xmlFileName).innerHTML += '<strong>Audioholders: </strong>'; // audioholderNodes = root.getElementsByTagName('audioholder'); // for (audioholderIndex = 0; audioholderIndex < audioholderNodes.length-1; audioholderIndex++) { // document.getElementById('div_survey_'+xmlFileName).innerHTML += audioholderNodes[audioholderIndex].getAttribute('id')+', '; // } // document.getElementById('div_survey_'+xmlFileName).innerHTML += audioholderNodes[audioholderNodes.length-1].getAttribute('id'); // survey responses (each if available) // get posttest node for total test childNodes = root.childNodes; posttestnode = null; for (idx = 0; idx < childNodes.length; idx++){ if (childNodes[childNodes.length-idx-1].tagName == 'posttest') { posttestnode = childNodes[childNodes.length-idx-1]; break; } } // post-test info if (posttestnode) { posttestcomments = posttestnode.getElementsByTagName('comment'); for (idx=0; idx < posttestcomments.length; idx++){ commentsToPrint = ['age', 'location']; // CHANGE WHAT TO PRINT idAttribute = posttestcomments[idx].getAttribute('id'); if (commentsToPrint.indexOf(idAttribute) >= 0) { // if exists? document.getElementById('div_survey_'+xmlFileName).innerHTML += '<br><strong>'+idAttribute+': </strong>'+posttestcomments[idx].textContent; } } } } } } function makePlots() { //TODO: split into different functions // TEMPORARY makeTimeline(xmlFileFolder+"/"+xmlFiles[7]); // create value array var ratings = []; // 3D matrix of ratings (audioholder, audioelement, subject) for (audioholderIndex = 0; audioholderIndex < audioholderArray.length; audioholderIndex++) { ratings.push([]); for (audioelementIndex = 0; audioelementIndex < audioelementArray.length; audioelementIndex++) { ratings[audioholderIndex].push([]); } } // go over all XML files for (fileIndex = 0; fileIndex < xmlFiles.length; fileIndex++) { xmlFileName = xmlFileFolder+"/"+xmlFiles[fileIndex]; xml = readXML(xmlFileName); if (xml != null) { // if file exists // get root of XML file root = xml.getElementsByTagName('browserevaluationresult')[0]; // go over all audioholders audioholderNodes = root.getElementsByTagName('audioholder'); for (audioholderIndex = 0; audioholderIndex < audioholderNodes.length; audioholderIndex++) { audioholderName = audioholderNodes[audioholderIndex].getAttribute('id'); audioelementNodes = audioholderNodes[audioholderIndex].getElementsByTagName('audioelement'); // go over all audioelements for (audioelementIndex = 0; audioelementIndex < audioelementNodes.length; audioelementIndex++) { audioelementName = audioelementNodes[audioelementIndex].getAttribute('id'); // get value var value = audioelementNodes[audioelementIndex].getElementsByTagName("value")[0].textContent; if (value) { // if not empty, null, undefined... ratingValue = parseFloat(value); // add to matrix at proper position aHidx = audioholderArray.indexOf(audioholderName); aEidx = audioelementArray.indexOf(audioelementName); ratings[aHidx][aEidx].push(ratingValue); } } } // go over all audioholders // go over all audioelements within audioholder, see if present in idMatrix, add if not // add corresponding rating to 'ratings', at position corresponding with position in idMatrix } } for (audioholderIndex = 0; audioholderIndex < audioholderArray.length; audioholderIndex++) { audioholderName = audioholderArray[audioholderIndex]; // for this song tickArray = [] raw_data = [['SubjectID', 'Rating']]; audioElIdx = 0; for (audioelementIndex = 0; audioelementIndex<ratings[audioholderIndex].length; audioelementIndex++){ if (ratings[audioholderIndex][audioelementIndex].length>0) { audioElIdx++; // increase if not empty // make tick label tickArray.push({v:audioElIdx, f: audioelementArray[audioelementIndex]}); } for (subject = 0; subject<ratings[audioholderIndex][audioelementIndex].length; subject++){ // add subject-value pair for each subject raw_data.push([audioElIdx, ratings[audioholderIndex][audioelementIndex][subject]]); } } // create plot (one per song) var data = google.visualization.arrayToDataTable(raw_data); var options = { title: audioholderName, hAxis: {title: 'audioelement ID', minValue: 0, maxValue: audioElIdx+1, ticks: tickArray}, vAxis: {title: 'Rating', minValue: 0, maxValue: 1}, seriesType: 'scatter', legend: 'none' }; var div = document.createElement('div'); document.body.appendChild(div); div.id = 'div_'+audioholderName; div.style.width = '1100px'; div.style.height = '350px'; var chart = new google.visualization.ComboChart(document.getElementById('div_'+audioholderName)); chart.draw(data, options); // box plots var div = document.createElement('div'); document.body.appendChild(div); div.id = 'div_box_'+audioholderName; div.style.width = '1100px'; div.style.height = '350px'; // Get median, percentiles, maximum and minimum; outliers. pctl25 = []; pctl75 = []; med = []; min = []; max = []; outlierArray = []; max_n_outliers = 0; // maximum number of outliers for one audioelement for (audioelementIndex = 0; audioelementIndex<ratings[audioholderIndex].length; audioelementIndex++){ med.push(median(ratings[audioholderIndex][audioelementIndex])); // median pctl25.push(percentile(ratings[audioholderIndex][audioelementIndex], 25)); // 25th percentile pctl75.push(percentile(ratings[audioholderIndex][audioelementIndex], 75)); // 75th percentile IQR = pctl75[pctl75.length-1]-pctl25[pctl25.length-1]; // outliers: range of values which is above pctl75+1.5*IQR or below pctl25-1.5*IQR outliers = []; rest = []; for (idx = 0; idx<ratings[audioholderIndex][audioelementIndex].length; idx++){ if (ratings[audioholderIndex][audioelementIndex][idx] > pctl75[pctl75.length-1]+1.5*IQR || ratings[audioholderIndex][audioelementIndex][idx] < pctl25[pctl25.length-1]-1.5*IQR){ outliers.push(ratings[audioholderIndex][audioelementIndex][idx]); } else { rest.push(ratings[audioholderIndex][audioelementIndex][idx]); } } outlierArray.push(outliers); max_n_outliers = Math.max(max_n_outliers, outliers.length); // update max mber // max: maximum value which is not outlier max.push(Math.max.apply(null, rest)); // min: minimum value which is not outlier min.push(Math.min.apply(null, rest)); } // Build data array boxplot_data = [['ID', 'Span', '', '', '', 'Median']]; for (idx = 0; idx < max_n_outliers; idx++) { boxplot_data[0].push('Outlier'); } for (audioelementIndex = 0; audioelementIndex<ratings[audioholderIndex].length; audioelementIndex++){ if (ratings[audioholderIndex][audioelementIndex].length>0) { // if rating array not empty for this audioelement data_array = [ audioelementArray[audioelementIndex], // name min[audioelementIndex], // minimum pctl75[audioelementIndex], pctl25[audioelementIndex], max[audioelementIndex], // maximum med[audioelementIndex] ]; for (idx = 0; idx < max_n_outliers; idx++) { if (idx<outlierArray[audioelementIndex].length){ data_array.push(outlierArray[audioelementIndex][idx]); } else { data_array.push(null); } } boxplot_data.push(data_array); } } // Create and populate the data table. var data = google.visualization.arrayToDataTable(boxplot_data); // Create and draw the visualization. var ac = new google.visualization.ComboChart(document.getElementById('div_box_'+audioholderName)); ac.draw(data, { title : audioholderName, //width: 600, //height: 400, vAxis: {title: "Rating"}, hAxis: {title: "audioelement ID"}, seriesType: "line", pointSize: 5, lineWidth: 0, colors: ['black'], series: { 0: {type: "candlesticks", color: 'blue'}, // box plot shape 1: {type: "line", pointSize: 10, lineWidth: 0, color: 'red' } }, // median legend: 'none' }); } } function makeTimeline(xmlFileName){ // WIP // Based on the XML file name, take time data and plot playback and marker movements // read XML file and check if exists xml = readXML(xmlFileName); if (!xml) { // if file does not exist console.log('XML file '+xml+'does not exist. ('+xmlFileName+')') return; // do nothing; exit function } // get root of XML file root = xml.getElementsByTagName('browserevaluationresult')[0]; audioholder_time = 0; previous_audioholder_time = 0; // time spent before current audioholder time_offset = 0; // test starts at zero // go over all audioholders audioholderNodes = root.getElementsByTagName('audioholder'); for (audioholderIndex = 0; audioholderIndex < audioholderNodes.length; audioholderIndex++) { audioholderName = audioholderNodes[audioholderIndex].getAttribute('id'); if (!audioholderName) { console.log('audioholder name is empty; go to next one. ('+xmlFileName+')'); break; } // subtract total audioholder length from subsequent audioholder event times audioholder_children = audioholderNodes[audioholderIndex].childNodes; foundIt = false; console.log(audioholder_children[2].getElementsByTagName("metricResult")) // not working! for (idx = 0; idx<audioholder_children.length; idx++) { // go over children if (audioholder_children[idx].getElementsByTagName('metricResult').length) { console.log(audioholder_children[idx].getElementsByTagName('metricResult')[0]); if (audioholder_children[idx].getElementsByTagName('metricResult')[0].getAttribute('id') == "testTime"){ audioholder_time = parseFloat(audioholder_children[idx].getElementsByTagName('metricResult')[0].textContent); console.log(audioholder_time); foundIt = true; } } } if (!foundIt) { console.log("Skipping audioholder without total time specified from "+xmlFileName+"."); // always hitting this break; } audioelementNodes = audioholderNodes[audioholderIndex].getElementsByTagName('audioelement'); // make div // draw chart // legend with audioelement names } } </script> <style> div { padding: 2px; margin-top: 2px; margin-bottom: 2px; } div.head{ margin-left: 10px; border: black; border-width: 2px; border-style: solid; } div.attrib{ margin-left:25px; border: black; border-width: 2px; border-style: dashed; margin-bottom: 10px; } div#headerMatter{ background-color: #FFFFCC; } div#currentStatement{ font-size:3.0em; font-weight: bold; } div#debugDisplay { color: #CCCCCC; font-size:0.3em; } span#scoreDisplay { font-weight: bold; } div#wrapper { width: 780px; border: 1px solid black; overflow: hidden; /* add this to contain floated children */ } div#instrumentSection { width: 250px; border: 1px solid red; display: inline-block; } div#featureSection { width: 250px; border: 1px solid green; display: inline-block; } div#valenceSection { width: 250px; border: 1px solid blue; display: inline-block; } button#previousComment{ width: 120px; height: 150px; font-size:1.5em; } button#nextComment{ width: 666px; height: 150px; font-size:1.5em; } ul { list-style-type: none; /* no bullet points */ margin-left: -20px; /* less indent */ margin-top: 0px; margin-bottom: 5px; } </style> </head> <body> <h1>Subjective evaluation results</h1> <div id="debugDisplay"> XML file folder: <span id="xmlFileFolder_span"></span> </div> <div id="headerMatter"> <div> <strong>Result XML files:</strong> <span id="numberOfFiles_span"></span> </div> <div> <strong>Audioholders in dataset:</strong> <span id="audioholderArray_span"></span> </div> <div> <strong>Subjects in dataset:</strong> <span id="subjectArray_span"></span> </div> <div> <strong>Audioelements in dataset:</strong> <span id="audioelementArray_span"></span> </div> <br> </div> <br> <!-- Show time elapsed The last annotation took <strong><span id="timeDisplay">(N/A)</span></strong> seconds. <br>--> </body> </html>