# HG changeset patch # User www-data # Date 1464873655 -3600 # Node ID 25c1436706af653dba4a240b5c627763fcc7562b # Parent d479fdc7221ce71ee464440c1c2b5e38bf7d691c# Parent a78ebb1bdd8ecd69decb1eeb06ad1af521449e2d Merge branch 'master' of https://github.com/BrechtDeMan/WebAudioEvaluationTool diff -r d479fdc7221c -r 25c1436706af docs/Instructions/Instructions.pdf Binary file docs/Instructions/Instructions.pdf has changed diff -r d479fdc7221c -r 25c1436706af docs/Instructions/Instructions.tex --- a/docs/Instructions/Instructions.tex Fri May 27 15:20:59 2016 +0100 +++ b/docs/Instructions/Instructions.tex Thu Jun 02 14:20:55 2016 +0100 @@ -35,6 +35,8 @@ The SoundSoftware project page, including a Mercurial repository, is \url{https://code.soundsoftware.ac.uk/projects/webaudioevaluationtool/}. +\textbf{The most current version of these instructions can be found on \url{https://github.com/BrechtDeMan/WebAudioEvaluationTool/wiki}.} + \tableofcontents @@ -431,7 +433,6 @@ \item \texttt{randomiseOrder}: Boolean, optional. If true the audio fragments are presented randomly rather than the order specified. See Section~\ref{sec:randomisation}. Default is false. \item \texttt{repeatCount}: non-negative integer, optional. Specify the number of times to repeat the test page (re-present). Each presentation will appear as an individual page in the results. Default is 0. \item \texttt{loop}: Boolean, optional. If true, the audio elements will loop synchronously with each other. See \ref{sec:looping}. Default is false. - \item \texttt{showElementComments}: Boolean, optional. If true then there will be a comment box on the test page for each audio element presented, see Section~\ref{sec:commentboxes}. \item \texttt{loudness}: non-positive integer, optional. Set the LUFS target value for this page. Supersedes the \texttt{} loudness attribute for this page. See Section~\ref{sec:loudness} for more. \item \texttt{label}: enumeration, optional. Set the label to one of the following \begin{itemize} @@ -488,7 +489,7 @@ \begin{itemize} \item \texttt{title}: Min 0, max 1 occurence. The text content specifies the name of the axis as shown to the user. \item \texttt{interfaceoption}: Min 0, max unbounded. Specifies the interface options. See Section~\ref{sec:interfaceoption}. - \item \texttt{scales}: Min 0, max 1 occurence. Contains \texttt{} nodes which define the displayed scales. See Section~\ref{sec:scales}. + \item \texttt{scales}: Min 0, max 1 occurence. Contains \texttt{} nodes which define the displayed scales. See Section~\ref{sec:multiscale}. \end{itemize} \subsection{Audio Element} @@ -539,6 +540,7 @@ \end{itemize} \subsubsection{Multiple scales} + \label{sec:multiscale} In the case of multiple rating scales, e.g. when the stimuli are to be rated in terms of attributes `timbre' and `spatial impression', multiple interface nodes will have to be added, each specifying the title and annotations. This is where the \texttt{interface}'s \texttt{name} attribute is particularly important: use this to retrieve the rating values, comments and metrics associated with the specified interface. @@ -556,7 +558,7 @@ % Needs to be implemented in PHP and automated better, will complete soon - \subsubsection{Randomsation of page order} + \subsubsection{Randomisation of page order} The page order randomisation is set by the \texttt{} node attribute \texttt{randomise-order}, for example \texttt{...} will randomise the test page order. When not set, the default is to \textbf{not} randomise the test page order. \subsubsection{Randomisation of axis order} @@ -580,13 +582,13 @@ \subsection{Sample rate} \label{sec:samplerate} - If you require the test to be conducted at a certain sample rate (i.e. you do not tolerate resampling of the elements to correspond with the system's sample rate), add \texttt{sampleRate="96000"} - where ``96000'' can be any support sample rate (in Hz) - so that a warning message is shown alerting the subject that their system's sample rate is different from this enforced sample rate. This is checked immediately after parsing and stops the page loading any other elements if this check has failed. + If you require the test to be conducted at a certain sample rate (i.e. you do not tolerate resampling of the elements to correspond with the system's sample rate), add \texttt{sampleRate="96000"} - where ``96000'' can be any supported sample rate (in Hz) - so that a warning message is shown alerting the subject that their system's sample rate is different from this enforced sample rate. This is checked immediately after parsing and stops the page loading any other elements if this check has failed. \subsection{Metrics} - The \texttt{Metric} node, which contains the metrics to be tracked during the complete test, is a child of the \texttt{setup} node, and it could look as follows. + The \texttt{metric} node, which contains the metrics to be tracked during the complete test, is a child of the \texttt{setup} node, and it could look as follows. \begin{lstlisting} - + testTimer elementTimer elementInitialPosition @@ -594,14 +596,14 @@ elementFlagListenedTo elementFlagMoved elementListenTracker - + \end{lstlisting} When in doubt, err on the inclusive side, as one never knows which information is needed in the future. Most of these metrics are necessary for post-processing scripts such as timeline\_view\_movement.py. % Brecht: should perhaps list somewhere what metrics are required for which analysis scripts. \subsubsection{Time test duration} \texttt{testTimer}\\ - One per test page. Presents the total test time from the first playback on the test page to the submission of the test page (exculding test time of the pre-/post- test surveys). This is presented in the results as \texttt{ 8.60299319727892 }. The time is in seconds. + One per test page. Presents the total test time from the first playback on the test page to the submission of the test page (excluding test time of the pre-/post- test surveys). This is presented in the results as \texttt{ 8.60299319727892 }. The time is in seconds. \subsubsection{Time fragment playback} \texttt{elementTimer}\\ @@ -631,7 +633,7 @@ \subsubsection{Outside Reference} Set type to `outside-reference'. This will place the object in a separate playback element clearly labelled as an outside reference. This is exempt of any movement checks but will still be included in any listening checks. \subsubsection{Hidden reference} - Set type to `reference'. The element will still be randomised as normal (if selected) and presented to the user. However the element will have the `reference' type in the results to quickly find it. The reference can be forced to be below a value before completing the test page by setting the attribute `marker' to be a value between 0 and 100 representing the integer value position it must be equal to or above. + Set type to `reference'. The element will still be randomised as normal (if selected) and presented to the user. However the element will have the `reference' type in the results to quickly find it. The reference can be forced to be above a value before completing the test page by setting the attribute `marker' to be a value between 0 and 100 representing the integer value position it must be equal to or above. \subsubsection{Hidden anchor} Set type to `anchor'. The element will still be randomised as normal (if selected) and presented to the user. However the element will have the `anchor' type in the results to quickly find it. The anchor can be forced to be below a value before completing the test page by setting the attribute `marker' to be a value between 0 and 100 representing the integer value position it must be equal to or below. @@ -701,11 +703,11 @@ \label{sec:loudness} % automatic loudness equalisation % guide to loudness.js - Each audio fragment on loading has its loudness calculated. The tool uses the EBU R 128 recommendation following the ITU-R BS.1770-4 loduness calculations to return the integreated LUFS loudness. The attribute \texttt{loudness} will set the loudness from the scope it is applied in. Applying it in the \texttt{} node will set the loudness for all test pages. Applying it in the \texttt{} node will set the loudness for that page. Applying it in the \texttt{} node will set the loudness for that fragment. The scope is set locally, so if there is a loudness on both the \texttt{} and \texttt{} nodes, that test page will take the value associated with the \texttt{}. The loudness attribute is set in LUFS + Each audio fragment on loading has its loudness calculated. The tool uses the EBU R 128 recommendation following the ITU-R BS.1770-4 loudness calculations to return the integrated LUFS loudness. The attribute \texttt{loudness} will set the loudness from the scope it is applied in. Applying it in the \texttt{} node will set the loudness for all test pages. Applying it in the \texttt{} node will set the loudness for that page. Applying it in the \texttt{} node will set the loudness for that fragment. The scope is set locally, so if there is a loudness on both the \texttt{} and \texttt{} nodes, that test page will take the value associated with the \texttt{}. The loudness attribute is set in LUFS \subsection{Comment Boxes} \label{sec:commentboxes} - There are two types of comment boxes which can be presented, those linked to the audio fragments on the page and those which pose a general question. The audio fragment boxes are shown by setting the attribute \texttt{showElementComments} to true of the page in question. This will then show a comment box below the main interface for every fragment on the page. There is some customisation around the text that accompanies the box, by default the text will read ``Comment on fragment'' followed by the fragment identifier (the number / letter shown by the interface). This `prefix' can be modified using the page node \texttt{}, see Section~\ref{sec:page} for where to place this node in the document. The comment box prefix node takes no attribute and the text contained by the node represents to the prefix. For instance if we have a node \texttt{ Describe fragment }, then the interface will show ``Describe fragment'' followed by the identifier. + There are two types of comment boxes which can be presented, those linked to the audio fragments on the page and those which pose a general question. When enabled, there is a comment box below the main interface for every fragment on the page. There is some customisation around the text that accompanies the box, by default the text will read ``Comment on fragment'' followed by the fragment identifier (the number / letter shown by the interface). This `prefix' can be modified using the page node \texttt{}, see Section~\ref{sec:page} for where to place this node in the document. The comment box prefix node takes no attribute and the text contained by the node represents to the prefix. For instance if we have a node \texttt{ Describe fragment }, then the interface will show ``Describe fragment'' followed by the identifier. The second type of comment box is slightly more complex because it can handle different types of response data. These are called comment questions because they are located in the comment section of the test but pose a specific question. diff -r d479fdc7221c -r 25c1436706af interfaces/AB.js --- a/interfaces/AB.js Fri May 27 15:20:59 2016 +0100 +++ b/interfaces/AB.js Thu Jun 02 14:20:55 2016 +0100 @@ -315,14 +315,23 @@ } this.startPlayback = function() { - $('.comparator-button').text('Listen'); + if (this.parent.specification.parent.playOne || specification.playOne) { + $('.comparator-button').text('Wait'); + $('.comparator-button').attr("disabled","true"); + $(this.playback).css("disabled","false"); + } else { + $('.comparator-button').text('Listen'); + } $(this.playback).text('Stop'); this.playback.setAttribute("playstate","playing"); }; this.stopPlayback = function() { - $(this.playback).text('Listen'); - this.playback.setAttribute("playstate","ready"); + if (this.playback.getAttribute("playstate") == "playing") { + $('.comparator-button').text('Listen'); + $('.comparator-button').removeAttr("disabled"); + this.playback.setAttribute("playstate","ready"); + } }; this.exportXMLDOM = function(audioObject) { diff -r d479fdc7221c -r 25c1436706af interfaces/ABX.js --- a/interfaces/ABX.js Fri May 27 15:20:59 2016 +0100 +++ b/interfaces/ABX.js Thu Jun 02 14:20:55 2016 +0100 @@ -292,16 +292,23 @@ }; this.startPlayback = function() { - // Called when playback has begun - $('.comparator-button').text('Listen'); + if (this.parent.specification.parent.playOne || specification.playOne) { + $('.comparator-button').text('Wait'); + $('.comparator-button').attr("disabled","true"); + $(this.playback).css("disabled","false"); + } else { + $('.comparator-button').text('Listen'); + } $(this.playback).text('Stop'); this.playback.setAttribute("playstate","playing"); }; this.stopPlayback = function() { - // Called when playback has stopped. This gets called even if playback never started! - $(this.playback).text('Listen'); - this.playback.setAttribute("playstate","ready"); + if (this.playback.getAttribute("playstate") == "playing") { + $('.comparator-button').text('Listen'); + $('.comparator-button').removeAttr("disabled"); + this.playback.setAttribute("playstate","ready"); + } }; this.getValue = function() { diff -r d479fdc7221c -r 25c1436706af interfaces/ape.js --- a/interfaces/ape.js Fri May 27 15:20:59 2016 +0100 +++ b/interfaces/ape.js Thu Jun 02 14:20:55 2016 +0100 @@ -621,6 +621,7 @@ this.parent = audioObject; this.trackSliderObjects = []; this.label = null; + this.playing = false; switch(audioObject.specification.parent.label) { case "letter": this.label = String.fromCharCode(97 + index); @@ -669,15 +670,24 @@ $(name).addClass('track-slider-playing'); $('.comment-div').removeClass('comment-box-playing'); $('#comment-div-'+this.parent.id).addClass('comment-box-playing'); - var outsideReference = document.getElementById('outside-reference'); - if (outsideReference != undefined) - $(outsideReference).removeClass('track-slider-playing'); + $('.outside-reference').removeClass('track-slider-playing'); + this.playing = true; + + if (this.parent.specification.parent.playOne || specification.playOne) { + $('.track-slider').addClass('track-slider-disabled'); + $('.outside-reference').addClass('track-slider-disabled'); + } }; this.stopPlayback = function() { - var name = ".track-slider-"+this.parent.id; - $(name).removeClass('track-slider-playing'); - $('#comment-div-'+this.parent.id).removeClass('comment-box-playing'); + if (this.playing) { + this.playing = false; + var name = ".track-slider-"+this.parent.id; + $(name).removeClass('track-slider-playing'); + $('#comment-div-'+this.parent.id).removeClass('comment-box-playing'); + $('.track-slider').removeClass('track-slider-disabled'); + $('.outside-reference').removeClass('track-slider-disabled'); + } }; this.exportXMLDOM = function(audioObject) { // Called by the audioObject holding this element. Must be present diff -r d479fdc7221c -r 25c1436706af interfaces/discrete.js --- a/interfaces/discrete.js Fri May 27 15:20:59 2016 +0100 +++ b/interfaces/discrete.js Thu Jun 02 14:20:55 2016 +0100 @@ -355,13 +355,21 @@ if (outsideReference != null) { $(outsideReference).removeClass('track-slider-playing'); } + if (this.parent.specification.parent.playOne || specification.playOne) { + $('.track-slider-button').text = "Wait"; + $('.track-slider-button').attr("disabled","true"); + } } this.stopPlayback = function() { // Called by audioObject when playback stops - this.play.setAttribute("playstate","ready"); - $(this.holder).removeClass('track-slider-playing'); - this.play.textContent = "Play"; + if (this.play.getAttribute("playstate") == "playing") { + this.play.setAttribute("playstate","ready"); + $(this.holder).removeClass('track-slider-playing'); + $('.track-slider-button').text = "Play"; + this.play.textContent = "Play"; + $('.track-slider-button').removeAttr("disabled"); + } } this.getValue = function() diff -r d479fdc7221c -r 25c1436706af js/core.js --- a/js/core.js Fri May 27 15:20:59 2016 +0100 +++ b/js/core.js Thu Jun 02 14:20:55 2016 +0100 @@ -1309,6 +1309,7 @@ this.parent.users[i].bufferLoaded(this); } } + interfaceContext.lightbox.post("Error","Could not load resource "+this.parent.url); } this.progress = 0; @@ -1623,7 +1624,7 @@ if (this.interfaceDOM != null) { this.interfaceDOM.enable(); } - this.onplayGain = decibelToLinear(this.specification.gain)*this.buffer.buffer.playbackGain; + this.onplayGain = decibelToLinear(this.specification.gain)*(this.buffer.buffer.playbackGain||1.0); this.storeDOM.setAttribute('playGain',linearToDecibel(this.onplayGain)); }; @@ -1672,7 +1673,7 @@ }; if (!audioEngineContext.loopPlayback || !audioEngineContext.synchPlayback) { this.metric.startListening(audioEngineContext.timer.getTestTime()); - this.outputGain.gain.setValueAtTime(this.onplayGain,startTime); + this.outputGain.gain.setValueAtTime(this.onplayGain,0.0); this.interfaceDOM.startPlayback(); } else { this.outputGain.gain.setValueAtTime(0.0,startTime); diff -r d479fdc7221c -r 25c1436706af js/loudness.js --- a/js/loudness.js Fri May 27 15:20:59 2016 +0100 +++ b/js/loudness.js Thu Jun 02 14:20:55 2016 +0100 @@ -19,7 +19,9 @@ // 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(navigator.platform == 'iPad' || navigator.platform == 'iPhone') { + buffer.ready(); + } if (buffer == undefined) { return 0; diff -r d479fdc7221c -r 25c1436706af js/specification.js --- a/js/specification.js Fri May 27 15:20:59 2016 +0100 +++ b/js/specification.js Thu Jun 02 14:20:55 2016 +0100 @@ -13,6 +13,7 @@ this.crossFade = null; this.preSilence = null; this.postSilence = null; + this.playOne = null; // nodes this.metrics = null; @@ -120,7 +121,6 @@ { case 'pre': case 'before': - if (this.preTest != null){console.log("Already a pre/before test survey defined! Ignoring second!!");} else { this.preTest = new this.surveyNode(this); this.preTest.decode(this,survey[i]); @@ -128,7 +128,6 @@ break; case 'post': case 'after': - if (this.postTest != null){console.log("Already a post/after test survey defined! Ignoring second!!");} else { this.postTest = new this.surveyNode(this); this.postTest.decode(this,survey[i]); @@ -448,6 +447,7 @@ this.preTest = null; this.postTest = null; this.interfaces = []; + this.playOne = null; this.commentBoxPrefix = "Comment on track"; this.audioElements = []; this.commentQuestions = []; diff -r d479fdc7221c -r 25c1436706af python/pythonServer.py --- a/python/pythonServer.py Fri May 27 15:20:59 2016 +0100 +++ b/python/pythonServer.py Thu Jun 02 14:20:55 2016 +0100 @@ -4,13 +4,13 @@ # http://stackoverflow.com/questions/9079036/detect-python-version-at-runtime import sys -from os import walk -from os import path -from os import listdir import inspect import os import pickle import datetime +import operator +import xml.etree.ElementTree as ET +import copy if sys.version_info[0] == 2: # Version 2.x @@ -28,14 +28,14 @@ PSEUDO_PATH = '../tests/' pseudo_files = [] -for filename in listdir(PSEUDO_PATH): +for filename in os.listdir(PSEUDO_PATH): print(filename) if filename.endswith('.xml'): pseudo_files.append(filename) curSaveIndex = 0; curFileName = 'test-0.xml' -while(path.isfile('../saves/'+curFileName)): +while(os.path.isfile('../saves/'+curFileName)): curSaveIndex += 1; curFileName = 'test-'+str(curSaveIndex)+'.xml' @@ -60,7 +60,7 @@ lenSt = len(st) fmt = st[lenSt-1].rsplit('.') fpath = "../"+urllib2.unquote(s.path) - size = path.getsize(fpath) + size = os.path.getsize(fpath) fileDump = open(fpath) s.send_response(200) @@ -160,6 +160,61 @@ curSaveIndex += 1 curFileName = 'test-'+str(curSaveIndex)+'.xml' +def poolXML(s): + pool = ET.parse('../tests/pool.xml') + root = pool.getroot() + setupNode = root.find("setup"); + poolSize = setupNode.get("poolSize",0); + if (poolSize == 0): + s.path = s.path.split("/php",1)[0]+"/tests/pool/xml" + processFile(s) + return + poolSize = int(poolSize) + # Set up the store will all the test page key nodes + pages = {}; + for page in root.iter("page"): + id = page.get("id") + pages[id] = 0 + # Read the saves and determine the completed pages + for filename in os.listdir("../saves/"): + if filename.endswith(".xml"): + save = ET.parse("../saves/"+filename) + save_root = save.getroot(); + if (save_root.find("waet").get("url") == "http://localhost:8000/php/pool.php"): + for page in save_root.findall("./page"): + id = page.get("ref") + pages[id] = pages[id] + 1 + + # Sort the dictionary + rot_pages = {} + for key, value in pages.items(): + if (value in rot_pages): + rot_pages[value].append(key) + else: + rot_pages[value] = [key] + + Keys = list(rot_pages) + print ("Current pool state:") + print (rot_pages) + + return_node = ET.fromstring(''); + return_node.append(copy.deepcopy(root.find("setup"))) + page_elements = root.findall("page") + + # Now append the pages + i = 0 + while(len(return_node.findall("page")) < poolSize): + if (i > 0): + for page in return_node.iter("page"): + page.set("alwaysInclude","true") + for id in rot_pages[Keys[i]]: + return_node.append(copy.deepcopy(root.find('./page[@id="'+id+'"]'))) + i=i+1 + s.send_response(200) + s.send_header("Content-type", "text/xml") + s.end_headers() + s.wfile.write(ET.tostring(return_node)) + def http_do_HEAD(s): s.send_response(200) s.send_header("Content-type", "text/html") @@ -171,6 +226,8 @@ send404(request) elif (request.path.split('?',1)[0] == "/php/keygen.php"): keygen(request); + elif (request.path.split('?',1)[0] == "/php/pool.php"): + poolXML(request); else: request.path = request.path.split('?',1)[0] if (request.path == '/'): diff -r d479fdc7221c -r 25c1436706af tests/examples/AB_example.xml --- a/tests/examples/AB_example.xml Fri May 27 15:20:59 2016 +0100 +++ b/tests/examples/AB_example.xml Thu Jun 02 14:20:55 2016 +0100 @@ -1,6 +1,6 @@ - + Please enter your name. diff -r d479fdc7221c -r 25c1436706af tests/pool.xml --- a/tests/pool.xml Fri May 27 15:20:59 2016 +0100 +++ b/tests/pool.xml Thu Jun 02 14:20:55 2016 +0100 @@ -1,69 +1,69 @@ - - - - testTimer - elementTimer - elementInitialPosition - elementTracker - elementFlagListenedTo - elementFlagMoved - elementListenTracker - - - - - - - - - (1) Very Annoying - (2) Annoying - (3) Slightly Annoying - (4) Audible but not Annoying - (5) Inaudible - - - - - - - - - (1) Very Annoying - (2) Annoying - (3) Slightly Annoying - (4) Audible but not Annoying - (5) Inaudible - - - - - - - - - (1) Very Annoying - (2) Annoying - (3) Slightly Annoying - (4) Audible but not Annoying - (5) Inaudible - - - - - - - - - (1) Very Annoying - (2) Annoying - (3) Slightly Annoying - (4) Audible but not Annoying - (5) Inaudible - - - - - - + + + + testTimer + elementTimer + elementInitialPosition + elementTracker + elementFlagListenedTo + elementFlagMoved + elementListenTracker + + + + + + + + + (1) Very Annoying + (2) Annoying + (3) Slightly Annoying + (4) Audible but not Annoying + (5) Inaudible + + + + + + + + + (1) Very Annoying + (2) Annoying + (3) Slightly Annoying + (4) Audible but not Annoying + (5) Inaudible + + + + + + + + + (1) Very Annoying + (2) Annoying + (3) Slightly Annoying + (4) Audible but not Annoying + (5) Inaudible + + + + + + + + + (1) Very Annoying + (2) Annoying + (3) Slightly Annoying + (4) Audible but not Annoying + (5) Inaudible + + + + + + diff -r d479fdc7221c -r 25c1436706af xml/test-schema.xsd --- a/xml/test-schema.xsd Fri May 27 15:20:59 2016 +0100 +++ b/xml/test-schema.xsd Thu Jun 02 14:20:55 2016 +0100 @@ -26,6 +26,8 @@ + + @@ -62,6 +64,7 @@ + @@ -97,6 +100,7 @@ +