# HG changeset patch # User Nicholas Jillings # Date 1450881360 0 # Node ID 9c9fd68693b116d68926ba2ac22910eb7cfc6740 # Parent 1081368deed74d7f3fef1c8d0783fa88a4ff441a# Parent 4866152611e65f88725ac4ea8f29a2f3c9937202 Merge. Pull of revision info from dev_main. diff -r 1081368deed7 -r 9c9fd68693b1 CITING.txt --- a/CITING.txt Mon Dec 21 23:18:43 2015 +0000 +++ b/CITING.txt Wed Dec 23 14:36:00 2015 +0000 @@ -5,7 +5,7 @@ Feel free to let us know how you have used it! We highly welcome any kind of feedback, bug reports and feature requests. -n.g.r.jillings@se14.qmul.ac.uk +nicholas.jillings@mail.bcu.ac.uk b.deman@qmul.ac.uk d.j.moffat@qmul.ac.uk joshua.reiss@qmul.ac.uk \ No newline at end of file diff -r 1081368deed7 -r 9c9fd68693b1 README.txt --- a/README.txt Mon Dec 21 23:18:43 2015 +0000 +++ b/README.txt Wed Dec 23 14:36:00 2015 +0000 @@ -1,127 +1,11 @@ WEB AUDIO EVALUATION TOOL -This is not (yet) a fully fledged manual. - - AUTHORS Nicholas Jillings Brecht De Man David Moffat Joshua D. Reiss (supervisor) +INSTRUCTIONS FOR USE -PACKAGE CONTENTS - -- main folder (/) - - ape.css, core.css, graphics.css, structure.css: style files (edit to change appearance) - - ape.js: JavaScript file for APE-style interface [1] - - core.js: JavaScript file with core functionality - - index.html: webpage where interface should appear - - jquery-2.1.4.js: jQuery JavaScript Library - - pythonServer.py: webserver for running tests locally - - pythonServer-legacy.py: webserver with limited functionality (no storing of output XML files) -- Documentation (/docs/) - - Project Specification Document (LaTeX/PDF) - - Results Specification Document (LaTeX/PDF) - - SMC15: PDF and LaTeX source of corresponding SMC2015 publication -- Example project (/example_eval/) - An example of what the set up XML should look like, with example audio files 0.wav-10.wav which are short recordings at 44.1kHz, 16bit of a woman saying the corresponding number (useful for testing randomisation and general familiarisation with the interface). -- Output files (/saves/) - The output XML files of tests will be stored here by default by the pythonServer.py script. -- Auxiliary scripts (/scripts/) - Helpful Python scripts for extraction and visualisation of data. -- Test creation tool (/test_create/) - Webpage for easily setting up a test without having to delve into the XML. - - -QUICK START - -Using the example project: -1. Make sure your system sample rate corresponds with the sample rate of the audio files, if the input XML file enforces the given sample rate. -2. Run pythonServer.py (make sure you have Python installed). -3. Open a browser (anything but Internet Explorer). -4. Go to ‘localhost:8000’. -5. The test should open; complete it and look at the output XML file in /saves/. - - -LEGACY - -The APE interface and most of the functionality of the interface is inspired by the APE toolbox for MATLAB [1]. See https://code.soundsoftware.ac.uk/projects/ape for the source code and corresponding paper. - - -CITING - -We request that you acknowledge the authors and cite our work [2], see CITING.txt. - - -LICENSE - -See LICENSE.txt. This code is shared under the GNU General Public License v3.0 (http://choosealicense.com/licenses/gpl-3.0/). Generally speaking, this is a copyleft license that requires anyone who distributes our code or a derivative work to make the source available under the same terms. - - -FEATURE REQUESTS AND BUG REPORTS - -We continually develop this tool to fix issues and implement features useful to us or our user base. See https://code.soundsoftware.ac.uk/projects/webaudioevaluationtool/issues for a list of feature requests and bug reports, and their status. - -Please contact the authors if you experience any bugs, if you would like additional functionality, if you have questions about using the interface or if you would like to give any feedback (even positive!) about the interface. We look forward to learning how the tool has (not) been useful to you. - - -TROUBLESHOOTING - -Thanks to feedback from using the interface in experiments by the authors and others, many bugs have been caught and fatal crashes due to the interface (provided it is set up properly by the user) seem to be a thing of the past. -However, if things do go wrong or the test needs to be interrupted for whatever reason, all data is not lost. In a normal scenario, the test needs to be completed until the end (the final ‘Submit’), at which point the output XML is stored in ‘saves/‘. If this stage is not reached, open the JavaScript Console (see below for how to find it) and type ‘createProjectSave()’ (without the quotes) and hit enter. This will open a pop-up window with a hyperlink that reads ‘Save File’; click it and an XML file with results until that point should be stored in your download folder. -Alternatively, a lot of data can be read from the same console, in which the tool prints a lot of debug information. Specifically: - - the randomisation of pages and fragments are logged; - - any time a slider is played, its ID and the time stamp (in seconds since the start of the test) are displayed; - - any time a slider is dragged and dropped, the location where it is dropped including the time stamp are shown; - - any comments and pre- or post-test questions and their answers are logged as well. - -You can select all this and save into a text file, so that none of this data is lost. You may to choose to do this even when a test was successful as an extra precaution. - -In Google Chrome, the JavaScript Console can be found in View>Developer>JavaScript Console, or via the keyboard shortcut Cmd + Alt + J (Mac OS X). -In Safari, the JavaScript Console can be found in Develop>Show Error Console, or via the keyboard shortcut Cmd + Alt + C (Mac OS X). Note that for the Developer menu to be visible, you have to go to Preferences (Cmd + ,) and enable ‘Show Develop menu in menu bar’ in the ‘Advanced’ tab. -In Firefox, go to Tools>Web Developer>Web Console, or hit Cmd + Alt + K. - - -REMOTE TESTS - -As the test is browser-based, it can be run remotely from a web server without modification. To allow for remote storage of the output XML files (as opposed to saving them locally on the subject’s machine, which is the default if no ‘save’ path is specified or found), a PHP script on the server needs to accept the output XML files. An example of such script will be included in a future version. - - -SCRIPTS - -The tool comes with a few handy Python (2.7) scripts for easy extraction of ratings or comments, and visualisation of ratings and timelines. See below for a quick guide on how to use them. All scripts written for Python 2.7. Visualisation requires the free matplotlib toolbox (http://matplotlib.org), numpy and scipy. -By default, the scripts can be run from the ‘scripts’ folder, with the result files in the ‘saves’ folder (the default location where result XMLs are stored). Each script takes the XML file folder as an argument, along with other arguments in some cases. -Note: to avoid all kinds of problems, please avoid using spaces in file and folder names (this may work on some systems, but others don’t like it). - - comment_parser.py - Extracts comments from the output XML files corresponding with the different subjects found in ‘saves/’. It creates a folder per ‘audioholder’/page it finds, and stores a CSV file with comments for every ‘audioelement’/fragment within these respective ‘audioholders’/pages. In this CSV file, every line corresponds with a subject/output XML file. Depending on the settings, the first column containing the name of the corresponding XML file can be omitted (for anonymisation). - Beware of Excel: sometimes the UTF-8 is not properly imported, leading to problems with special characters in the comments (particularly cumbersome for foreign languages). - - evaluation_stats.py - Shows a few statistics of tests in the ‘saves/‘ folder so far, mainly for checking for errors. Shows the number of files that are there, the audioholder IDs that were tested (and how many of each separate ID), the duration of each page, the duration of each complete test, the average duration per page, and the average duration in function of the page number. - - generate_report.py - Similar to ‘evaluation_stats.py’, but generates a PDF report based on the output files in the ‘saves/‘ folder - or any folder specified as command line argument. Uses pdflatex to write a LaTeX document, then convert to a PDF. - - score_parser.py - Extracts rating values from the XML to CSV - necessary for running visualisation of ratings. Creates the folder ‘saves/ratings/‘ if not yet created, to which it writes a separate file for every ‘audioholder’/page in any of the output XMLs it finds in ‘saves/‘. Within each file, rows represent different subjects (output XML file names) and columns represent different ‘audioelements’/fragments. - - score_plot.py - Plots the ratings as stored in the CSVs created by score_parser.py - Depending on the settings, it displays and/or saves (in ‘saves/ratings/’) a boxplot, confidence interval plot, scatter plot, or a combination of the aforementioned. - Requires the free matplotlib library. - At this point, more than one subjects are needed for this script to work. - - timeline_view_movement.py - Creates a timeline for every subject, for every ‘audioholder’/page, corresponding with any of the output XML files found in ‘/saves’. It shows the marker movements of the different fragments, along with when each fragment was played (red regions). Automatically takes fragment names, rating axis title, rating axis labels, and audioholder name from the XML file (if available). - - timeline_view.py - Creates a timeline for every subject, for every ‘audioholder’/page, corresponding with any of the output XML files found in ‘/saves’. It shows when and for how long the subject listened to each of the fragments. - - - -REFERENCES -[1] B. De Man and Joshua D. Reiss, “APE: Audio Perceptual Evaluation toolbox for MATLAB,” 136th Convention of the Audio Engineering Society, 2014. - -[2] Nicholas Jillings, Brecht De Man, David Moffat and Joshua D. Reiss, "Web Audio Evaluation Tool: A Browser-Based Listening Test Environment," 12th Sound and Music Computing Conference, July 2015. +Please refer to ‘docs/Instructions/Instructions.pdf’ \ No newline at end of file diff -r 1081368deed7 -r 9c9fd68693b1 analyse.html --- a/analyse.html Mon Dec 21 23:18:43 2015 +0000 +++ b/analyse.html Wed Dec 23 14:36:00 2015 +0000 @@ -15,44 +15,18 @@ + - - -
-

HTML5 APE Tool

+

Web Audio Evaluation Tool

+

Start menu

+ + +
+ + + +
diff -r 1081368deed7 -r 9c9fd68693b1 loudness.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/loudness.js Wed Dec 23 14:36:00 2015 +0000 @@ -0,0 +1,181 @@ +/** + * loundess.js + * Loudness module for the Web Audio Evaluation Toolbox + * Allows for automatic calculation of loudness of Web Audio API Buffer objects, + * return gain values to correct for a target loudness or match loudness between + * multiple objects + */ + +var interval_cal_loudness_event = null; + +if (typeof OfflineAudioContext == "undefined"){ + var OfflineAudioContext = webkitOfflineAudioContext; +} + +function calculateLoudness(buffer, timescale, target, offlineContext) +{ + // This function returns the EBU R 128 specification loudness model and sets the linear gain required to match -23 LUFS + // buffer -> Web Audio API Buffer object + // timescale -> M or Momentary (returns Array), S or Short (returns Array), + // I or Integrated (default, returns number) + // target -> default is -23 LUFS but can be any LUFS measurement. + + if (buffer == undefined) + { + return 0; + } + if (timescale == undefined) + { + timescale = "I"; + } + if (target == undefined) + { + target = -23; + } + if (offlineContext == undefined) + { + offlineContext = new OfflineAudioContext(buffer.numberOfChannels, buffer.length, buffer.sampleRate); + } + // Create the required filters + var KFilter = offlineContext.createBiquadFilter(); + KFilter.type = "highshelf"; + KFilter.gain.value = 4; + KFilter.frequency.value = 1480; + + var HPFilter = offlineContext.createBiquadFilter(); + HPFilter.type = "highpass"; + HPFilter.Q.value = 0.707; + HPFilter.frequency.value = 60; + // copy Data into the process buffer + var processSource = offlineContext.createBufferSource(); + processSource.buffer = buffer; + + processSource.connect(KFilter); + KFilter.connect(HPFilter); + HPFilter.connect(offlineContext.destination); + processSource.start(); + offlineContext.oncomplete = function(renderedBuffer) { + // Have the renderedBuffer information, now continue processing + if (typeof renderedBuffer.renderedBuffer == 'object') { + renderedBuffer = renderedBuffer.renderedBuffer; + } + switch(timescale) + { + case "I": + var blockEnergy = calculateProcessedLoudness(renderedBuffer, 400, 0.75); + // Apply the absolute gate + var loudness = calculateLoudnessFromChannelBlocks(blockEnergy); + var absgatedEnergy = new Array(blockEnergy.length); + for (var c=0; c= -70) + { + for (var c=0; c= relGateLevel) + { + for (var c=0; c= 4) {G = 1.41;} + sigma += blockEnergy[channel][i]*G; + } + loudness[i] = -0.691 + 10*Math.log10(sigma); + } + return loudness; +} +function calculateOverallLoudnessFromChannelBlocks(blockEnergy) +{ + // Loudness + var summation = 0; + for (var channel = 0; channel < blockEnergy.length; channel++) + { + var G = 1.0; + if (channel >= 4) {G = 1.41;} + var sigma = 0; + for (var i=0; i 1) + { + console.log("WARNING - This interface only supports one node per page. Using first interface node"); + } + interfaceObj = interfaceObj[0]; + if(interfaceObj.title != null) + { + document.getElementById("pageTitle").textContent = interfaceObj.title; + } - var sliderBox = document.getElementById('slider'); + // Delete outside reference + var outsideReferenceHolder = document.getElementById('outside-reference'); + if (outsideReferenceHolder != null) { + document.getElementById('interface-buttons').removeChild(outsideReferenceHolder); + } + + var sliderBox = document.getElementById('slider-holder'); feedbackHolder.innerHTML = null; sliderBox.innerHTML = null; @@ -116,20 +132,8 @@ if (interfaceObj.commentBoxPrefix != undefined) { commentBoxPrefix = interfaceObj.commentBoxPrefix; } - - /// CHECK FOR SAMPLE RATE COMPATIBILITY - if (audioHolderObject.sampleRate != undefined) { - if (Number(audioHolderObject.sampleRate) != audioContext.sampleRate) { - var errStr = 'Sample rates do not match! Requested '+Number(audioHolderObject.sampleRate)+', got '+audioContext.sampleRate+'. Please set the sample rate to match before completing this test.'; - alert(errStr); - return; - } - } - var loopPlayback = audioHolderObject.loop; - audioEngineContext.loopPlayback = loopPlayback; - currentTestHolder = document.createElement('audioHolder'); currentTestHolder.id = audioHolderObject.id; currentTestHolder.repeatCount = audioHolderObject.repeatCount; @@ -144,8 +148,12 @@ // Find URL of track // In this jQuery loop, variable 'this' holds the current audioElement. - // Now load each audio sample. First create the new track by passing the full URL - var trackURL = audioHolderObject.hostURL + element.url; + // Check if an outside reference + if (index == audioHolderObject.outsideReference) + { + return; + } + var audioObject = audioEngineContext.newTrack(element); var node = interfaceContext.createCommentBox(audioObject); @@ -153,8 +161,14 @@ // Create a slider per track audioObject.interfaceDOM = new sliderObject(audioObject); - // Distribute it randomnly - audioObject.interfaceDOM.slider.value = Math.random(); + if (typeof audioHolderObject.initialPosition === "number") + { + // Set the values + audioObject.interfaceDOM.slider.value = audioHolderObject.initalPosition; + } else { + // Distribute it randomnly + audioObject.interfaceDOM.slider.value = Math.random(); + } sliderBox.appendChild(audioObject.interfaceDOM.holder); audioObject.metric.initialised(audioObject.interfaceDOM.slider.value); @@ -165,7 +179,33 @@ var numObj = audioHolderObject.audioElements.length; var totalWidth = (numObj-1)*150+100; var diff = (window.innerWidth - totalWidth)/2; - audioEngineContext.audioObjects[0].interfaceDOM.holder.style.marginLeft = diff + 'px'; + sliderBox.style.marginLeft = diff + 'px'; + + // Construct outside reference; + if (audioHolderObject.outsideReference != null) { + var outsideReferenceHolder = document.createElement('div'); + outsideReferenceHolder.id = 'outside-reference'; + outsideReferenceHolder.className = 'outside-reference'; + outsideReferenceHolderspan = document.createElement('span'); + outsideReferenceHolderspan.textContent = 'Reference'; + outsideReferenceHolder.appendChild(outsideReferenceHolderspan); + + var audioObject = audioEngineContext.newTrack(audioHolderObject.audioElements[audioHolderObject.outsideReference]); + + outsideReferenceHolder.onclick = function(event) + { + audioEngineContext.play(audioEngineContext.audioObjects.length-1); + $('.track-slider').removeClass('track-slider-playing'); + $('.comment-div').removeClass('comment-box-playing'); + if (event.currentTarget.nodeName == 'DIV') { + $(event.currentTarget).addClass('track-slider-playing'); + } else { + $(event.currentTarget.parentElement).addClass('track-slider-playing'); + } + }; + + document.getElementById('interface-buttons').appendChild(outsideReferenceHolder); + } } function sliderObject(audioObject) @@ -183,7 +223,10 @@ this.holder.appendChild(this.slider); this.holder.appendChild(this.play); this.holder.align = "center"; - this.holder.style.marginLeft = "50px"; + if (audioObject.id == 0) + { + this.holder.style.marginLeft = '0px'; + } this.holder.setAttribute('trackIndex',audioObject.id); this.title.textContent = audioObject.id; @@ -191,12 +234,11 @@ this.title.style.float = "left"; this.slider.type = "range"; + this.slider.className = "track-slider-range track-slider-not-moved"; this.slider.min = "0"; this.slider.max = "1"; this.slider.step = "0.01"; this.slider.setAttribute('orient','vertical'); - this.slider.style.float = "left"; - this.slider.style.width = "100%"; this.slider.style.height = window.innerHeight-250 + 'px'; this.slider.onchange = function() { @@ -204,27 +246,31 @@ var id = Number(this.parentNode.getAttribute('trackIndex')); audioEngineContext.audioObjects[id].metric.moved(time,this.value); console.log('slider '+id+' moved to '+this.value+' ('+time+')'); + $(this).removeClass('track-slider-not-moved'); }; - this.play.textContent = "Play"; + this.play.textContent = "Loading..."; this.play.value = audioObject.id; this.play.style.float = "left"; this.play.style.width = "100%"; - this.play.onclick = function() + this.play.disabled = true; + this.play.onclick = function(event) { - audioEngineContext.play(); - if (audioEngineContext.audioObjectsReady) { - var id = Number(event.srcElement.value); - //audioEngineContext.metric.sliderPlayed(id); - audioEngineContext.play(id); + var id = Number(event.currentTarget.value); + //audioEngineContext.metric.sliderPlayed(id); + audioEngineContext.play(id); + $(".track-slider").removeClass('track-slider-playing'); + $(event.currentTarget.parentElement).addClass('track-slider-playing'); + var outsideReference = document.getElementById('outside-reference'); + if (outsideReference != null) { + $(outsideReference).removeClass('track-slider-playing'); } }; this.enable = function() { - if (this.parent.state == 1) - { - $(this.slider).removeClass('track-slider-disabled'); - } + this.play.disabled = false; + this.play.textContent = "Play"; + $(this.slider).removeClass('track-slider-disabled'); }; this.exportXMLDOM = function(audioObject) { @@ -236,6 +282,41 @@ this.getValue = function() { return this.slider.value; }; + + this.resize = function(event) + { + this.holder.style.height = window.innerHeight-200 + 'px'; + this.slider.style.height = window.innerHeight-250 + 'px'; + }; + this.updateLoading = function(progress) + { + progress = String(progress); + progress = progress.substr(0,5); + this.play.textContent = "Loading: "+progress+"%"; + }; + + if (this.parent.state == 1) + { + this.enable(); + } +} + +function resizeWindow(event) +{ + // Function called when the window has been resized. + // MANDATORY FUNCTION + + // Auto-align + var numObj = audioEngineContext.audioObjects.length; + var totalWidth = (numObj-1)*150+100; + var diff = (window.innerWidth - totalWidth)/2; + document.getElementById('slider').style.height = window.innerHeight - 180 + 'px'; + if (diff <= 0){diff = 0;} + document.getElementById('slider-holder').style.marginLeft = diff + 'px'; + for (var i in audioEngineContext.audioObjects) + { + audioEngineContext.audioObjects[i].interfaceDOM.resize(event); + } } @@ -247,7 +328,7 @@ // Check that the anchor and reference objects are correctly placed if (interfaceContext.checkHiddenAnchor() == false) {return;} if (interfaceContext.checkHiddenReference() == false) {return;} - /* + for (var i=0; ipage_number: @@ -330,7 +334,7 @@ plt.xlim(.8, len(duration_order)+1) plt.xticks(np.arange(1,len(duration_order)+1)+.4, range(1,len(duration_order)+1)) plt.ylabel('Average time [minutes]') -plt.savefig(folder_name+"/time_per_page.pdf", bbox_inches='tight') +plt.savefig(folder_name+"time_per_page.pdf", bbox_inches='tight') plt.close() #TODO add error bars @@ -372,7 +376,7 @@ plt.xlim(.8, len(audioholder_names_ordered)+1) plt.xticks(np.arange(1,len(audioholder_names_ordered)+1)+.4, audioholder_names_ordered, rotation=90) plt.ylabel('Average time [minutes]') -plt.savefig(folder_name+"/time_per_audioholder.pdf", bbox_inches='tight') +plt.savefig(folder_name+"time_per_audioholder.pdf", bbox_inches='tight') plt.close() # SHOW bar plot of average time per page @@ -385,7 +389,7 @@ ylims = ax.get_ylim() yint = np.arange(int(np.floor(ylims[0])), int(np.ceil(ylims[1]))+1) plt.yticks(yint) -plt.savefig(folder_name+"/subjects_per_audioholder.pdf", bbox_inches='tight') +plt.savefig(folder_name+"subjects_per_audioholder.pdf", bbox_inches='tight') plt.close() # SHOW both figures @@ -393,7 +397,7 @@ \begin{figure}[htbp] \begin{center} \includegraphics[width=.65\textwidth]{'''+\ - folder_name+"/time_per_page.pdf"+\ + folder_name+'time_per_page.pdf'+\ r'''} \caption{Average time spent per page.} \label{fig:avgtimeperpage} @@ -404,7 +408,7 @@ body += r'''\begin{figure}[htbp] \begin{center} \includegraphics[width=.65\textwidth]{'''+\ - folder_name+"/time_per_audioholder.pdf"+\ + folder_name+'time_per_audioholder.pdf'+\ r'''} \caption{Average time spent per audioholder.} \label{fig:avgtimeperaudioholder} @@ -415,7 +419,7 @@ body += r'''\begin{figure}[htbp] \begin{center} \includegraphics[width=.65\textwidth]{'''+\ - folder_name+"/subjects_per_audioholder.pdf"+\ + folder_name+'subjects_per_audioholder.pdf'+\ r'''} \caption{Number of subjects per audioholder.} \label{fig:subjectsperaudioholder} @@ -430,11 +434,11 @@ #TODO order in decreasing order of participants for audioholder_name in page_names: # get each name # plot boxplot if exists (not so for the 'alt' names) - if os.path.isfile(folder_name+'/ratings/'+audioholder_name+'-ratings-box.pdf'): + if os.path.isfile(folder_name+'ratings/'+audioholder_name+'-ratings-box.pdf'): body += r'''\begin{figure}[htbp] \begin{center} \includegraphics[width=.65\textwidth]{'''+\ - folder_name+"/ratings/"+audioholder_name+'-ratings-box.pdf'+\ + folder_name+"ratings/"+audioholder_name+'-ratings-box.pdf'+\ r'''} \caption{Box plot of ratings for audioholder '''+\ audioholder_name+' ('+str(subject_count[real_page_names.index(audioholder_name)])+\ @@ -505,21 +509,23 @@ texfile = header+body+footer # add bits together +print 'pdflatex -output-directory="'+folder_name+'"" "'+ folder_name + 'Report.tex"' # DEBUG + # write TeX file -with open(folder_name + '/' + 'Report.tex','w') as f: +with open(folder_name + 'Report.tex','w') as f: f.write(texfile) -proc=subprocess.Popen(shlex.split('pdflatex -output-directory='+folder_name+' '+ folder_name + '/Report.tex')) +proc=subprocess.Popen(shlex.split('pdflatex -output-directory="'+folder_name+'" "'+ folder_name + 'Report.tex"')) proc.communicate() # run again -proc=subprocess.Popen(shlex.split('pdflatex -output-directory='+folder_name+' '+ folder_name + '/Report.tex')) +proc=subprocess.Popen(shlex.split('pdflatex -output-directory="'+folder_name+'" "'+ folder_name + 'Report.tex"')) proc.communicate() #TODO remove auxiliary LaTeX files try: - os.remove(folder_name + '/' + 'Report.aux') - os.remove(folder_name + '/' + 'Report.log') - os.remove(folder_name + '/' + 'Report.out') - os.remove(folder_name + '/' + 'Report.toc') + os.remove(folder_name + 'Report.aux') + os.remove(folder_name + 'Report.log') + os.remove(folder_name + 'Report.out') + os.remove(folder_name + 'Report.toc') except OSError: pass \ No newline at end of file diff -r 1081368deed7 -r 9c9fd68693b1 scripts/score_parser.py --- a/scripts/score_parser.py Mon Dec 21 23:18:43 2015 +0000 +++ b/scripts/score_parser.py Wed Dec 23 14:36:00 2015 +0000 @@ -97,7 +97,7 @@ filewriter.writerow(row + ['']*len(newfragments)) os.rename('temp.csv', file_name) # replace old file with temp file headerrow = headerrow + newfragments - + # if not, create file and make header else: headerrow = sorted(fragmentnamelist) # sort alphabetically diff -r 1081368deed7 -r 9c9fd68693b1 scripts/score_plot.py --- a/scripts/score_plot.py Mon Dec 21 23:18:43 2015 +0000 +++ b/scripts/score_plot.py Wed Dec 23 14:36:00 2015 +0000 @@ -115,7 +115,7 @@ # CODE # get every csv file in folder -for file in os.listdir(rating_folder): # You have to put this in folder where rating csv files are. +for file in os.listdir(rating_folder): if file.endswith(".csv"): page_name = file[:-4] # file name (without extension) is page ID diff -r 1081368deed7 -r 9c9fd68693b1 test_create/test_create.html --- a/test_create/test_create.html Mon Dec 21 23:18:43 2015 +0000 +++ b/test_create/test_create.html Wed Dec 23 14:36:00 2015 +0000 @@ -104,6 +104,38 @@ dnd.style.width = "100%"; dnd.style.height = "50px"; dnd.className = "dragndrop"; + dnd.ondragover = function(e) { + e.stopPropagation(); + e.preventDefault(); + }; + dnd.ondragenter = function(e) { + e.stopPropagation(); + e.preventDefault(); + this.style.backgroundColor = '#AAFFAA'; + }; + dnd.ondragleave = function(e) { + e.stopPropagation(); + e.preventDefault(); + this.style.backgroundColor = "#FFFFFF"; + }; + dnd.ondrop = function(e) { + e.stopPropagation(); + e.preventDefault(); + + var file = e.dataTransfer.files[0]; + + // Uses HTML5 FileAPI - https://w3c.github.io/FileAPI/#filereader-interface + var reader = new FileReader(); + reader.onload = function() { + var parse = new DOMParser(); + var xml = parse.parseFromString(reader.result,'text/xml'); + specificationNode.decode(xml); + popupInstance.hidePopup(); + SpecificationToHTML(); + }; + reader.readAsText(file); + + }; this.popupBody.appendChild(text); this.popupBody.appendChild(dnd); this.showPopup(); @@ -1511,7 +1543,7 @@ case 8: this.hidePopup(); this.state = 0; - SpecficationToHTML(); + SpecificationToHTML (); } this.state++; }; @@ -1585,6 +1617,34 @@ } }; }; + + this.randomiseOrder = function(input) + { + // This takes an array of information and randomises the order + var N = input.length; + + var inputSequence = []; // For safety purposes: keep track of randomisation + for (var counter = 0; counter < N; ++counter) + inputSequence.push(counter) // Fill array + var inputSequenceClone = inputSequence.slice(0); + + var holdArr = []; + var outputSequence = []; + for (var n=0; n 0) { metricCollection = metricCollection[0].getElementsByTagName('metricEnable'); @@ -1675,11 +1746,13 @@ var audioHolders = projectXML.getElementsByTagName('audioHolder'); for (var i=0; i tags for (var ahIndex = 0; ahIndex < this.audioHolders.length; ahIndex++) { - var AHObj = this.audioHolders[ahIndex]; - var AHNode = root.createElement("audioHolder"); - AHNode.id = AHObj.id; - AHNode.setAttribute("hostURL",AHObj.hostURL); - AHNode.setAttribute("sampleRate",AHObj.samplerate); - AHNode.setAttribute("randomiseOrder",AHObj.randomiseOrder); - AHNode.setAttribute("repeatCount",AHObj.repeatCount); - AHNode.setAttribute("loop",AHObj.loop); - AHNode.setAttribute("elementComments",AHObj.elementComments); - - // Create tag - for (var i=0; i - for (var aeIndex = 0; aeIndex < AHObj.audioElements.length; aeIndex++) - { - var AEObj = AHObj.audioElements[aeIndex]; - var AENode = root.createElement("audioElements"); - AENode.id = AEObj.id; - AENode.setAttribute("url",AEObj.url); - AENode.setAttribute("type",AEObj.type); - if (AEObj.marker != undefined && AEObj.enforce) - { - AENode.setAttribute("marker",AEObj.marker*100); - } - AHNode.appendChild(AENode); - } - - // Create - for (var i=0; i - var AHPreTest = document.createElement("PreTest"); - for (var i=0; i + for (var i=0; i + var AHPreTest = root.createElement("PreTest"); + for (var i=0; i 1) + { this.marker /= 100.0;} + if (this.marker >= 0 && this.marker <= 1) + { + this.enforce = true; + return; + } else { + console.log("ERROR - Marker of audioElement "+this.id+" is not between 0 and 1 (float) or 0 and 100 (integer)!"); + console.log("ERROR - Marker not enforced!"); + } + } else { + console.log("ERROR - Marker of audioElement "+this.id+" is not a number!"); + console.log("ERROR - Marker not enforced!"); + } + } + } + }; + this.encode = function(root) + { + var AENode = root.createElement("audioElements"); + AENode.id = this.id; + AENode.setAttribute("url",this.url); + AENode.setAttribute("type",this.type); + AENode.setAttribute("gain",linearToDecibel(this.gain)); + if (this.marker != false) + { + AENode.setAttribute("marker",this.marker*100); + } + return AENode; + }; }; this.commentQuestionNode = function(xml) { + this.id = null; + this.type = undefined; + this.question = undefined; + this.options = []; + this.statement = undefined; + + this.childOption = function() { + this.type = 'option'; + this.name = null; + this.text = null; + }; this.exportXML = function(root) { var CQNode = root.createElement("CommentQuestion"); @@ -1973,13 +2253,25 @@ case "text": CQNode.textContent = this.question; break; - case "radio" || "checkbox": - var statement = document.createElement("statement"); + case "radio": + var statement = root.createElement("statement"); statement.textContent = this.statement; CQNode.appendChild(statement); for (var i=0; i