changeset 2363:95dc0833eb0a

Merge branch 'master' of https://github.com/BrechtDeMan/WebAudioEvaluationTool
author www-data <www-data@sucuk.dcs.qmul.ac.uk>
date Mon, 16 May 2016 16:20:59 +0100
parents 1ab42e36b6c8 (current diff) a3099bdb056c (diff)
children 5586b1687088
files
diffstat 10 files changed, 291 insertions(+), 52 deletions(-) [+]
line wrap: on
line diff
--- a/css/core.css	Mon May 16 14:20:50 2016 +0100
+++ b/css/core.css	Mon May 16 16:20:59 2016 +0100
@@ -133,6 +133,46 @@
 	top: 0px;
 }
 
+div#lightbox-root {
+    visibility: hidden;
+    z-index: 20;
+    top: 25px;
+    min-height: 50px;
+    max-height: 250px;
+}
+
+div.lightbox-error {
+    margin: 25px;
+    margin-bottom: 50px;
+    padding: 5px;
+    border-radius: 5px;
+    background-color: rgb(255,220,220);
+    border: 2px rgb(200,0,0) solid;
+}
+
+div.lightbox-warning {
+    margin: 25px;
+    margin-bottom: 50px;
+    padding: 5px;
+    border-radius: 5px;
+    background-color: rgb(255,255,220);
+    border: 2px rgb(255,250,0) solid;
+}
+
+div.lightbox-message {
+    margin: 25px;
+    margin-bottom: 50px;
+    padding: 5px;
+    border-radius: 5px;
+    background-color: rgb(200,220,255);
+    border: 2px rgb(50,100,250) solid;
+}
+
+div#lightbox-blanker {
+    visibility: hidden;
+    z-index: 19;
+}
+
 button.outside-reference {
 	width:120px;
 	height:20px;
@@ -169,12 +209,25 @@
 	background-color: #000;
 }
 
-div#master-volume-holder {
-    width: 250px;
-    float: left;
+div.master-volume-holder-inline {
+    width: 100%;
+    padding: 5px;
+}
+
+div.master-volume-holder-float {
+    position: absolute;
+    top: 20px;
+    left: 50px;
+    width: 250px%;
+    padding: 5px;
+}
+
+div#master-volume-root {
+    margin:auto;
     border: black 1px solid;
     border-radius: 5px;
-    padding: 5px;
+    width: 250px;
+    height: 40px;
 }
 
 input#master-volume-control {
--- a/interfaces/AB.js	Mon May 16 14:20:50 2016 +0100
+++ b/interfaces/AB.js	Mon May 16 16:20:59 2016 +0100
@@ -9,6 +9,30 @@
 	
 	// Custom comparator Object
 	Interface.prototype.comparator = null;
+    
+    Interface.prototype.checkScaleRange = function(min, max) {
+        var page = testState.getCurrentTestPage();
+        var audioObjects = audioEngineContext.audioObjects;
+        var state = true;
+        var str = "Please keep listening. ";
+        var minRanking = Infinity;
+        var maxRanking = -Infinity;
+        for (var ao of audioObjects) {
+            var rank = ao.interfaceDOM.getValue();
+            if (rank < minRanking) {minRanking = rank;}
+            if (rank > maxRanking) {maxRanking = rank;}
+        }
+        if (maxRanking*100 < max) {
+            str += "At least one fragment must be selected."
+            state = false;
+        }
+        if (!state) {
+            console.log(str);
+            this.storeErrorNode(str);
+            interfaceContext.lightbox.post("Message",str);
+        }
+        return state;
+    }
 	
 	// The injection point into the HTML page
 	interfaceContext.insertPoint = document.getElementById("topLevelBody");
@@ -163,6 +187,11 @@
         interfaceContext.commentBoxes.showCommentBoxes(commentHolder,true);
     }
 	resizeWindow(null);
+    
+    $(audioHolderObject.commentQuestions).each(function(index,element) {
+		var node = interfaceContext.createCommentQuestion(element);
+		commentHolder.appendChild(node.holder);
+	});
 }
 
 function comparator(audioHolderObject)
@@ -198,7 +227,7 @@
 			}
 			if (audioEngineContext.status == 0)
 			{
-				alert("Please listen to the samples before making a selection");
+				interfaceContext.lightbox.post("Message","Please listen to the samples before making a selection");
 				console.log("Please listen to the samples before making a selection");
 				return;
             }
@@ -411,7 +440,7 @@
 	    {
 	        if (audioEngineContext.timer.testStarted == false)
 	        {
-	            alert('You have not started the test! Please press start to begin the test!');
+	            interfaceContext.lightbox.post("Warning",'You have not started the test! Please click play on a sample to begin the test!');
 	            return;
 	        }
 	    }
--- a/interfaces/ABX.js	Mon May 16 14:20:50 2016 +0100
+++ b/interfaces/ABX.js	Mon May 16 16:20:59 2016 +0100
@@ -12,6 +12,30 @@
     
     interfaceContext.insertPoint.innerHTML = null; // Clear the current schema
     
+    Interface.prototype.checkScaleRange = function(min, max) {
+        var page = testState.getCurrentTestPage();
+        var audioObjects = audioEngineContext.audioObjects;
+        var state = true;
+        var str = "Please keep listening. ";
+        var minRanking = Infinity;
+        var maxRanking = -Infinity;
+        for (var ao of audioObjects) {
+            var rank = ao.interfaceDOM.getValue();
+            if (rank < minRanking) {minRanking = rank;}
+            if (rank > maxRanking) {maxRanking = rank;}
+        }
+        if (maxRanking*100 < max) {
+            str += "At least one fragment must be selected."
+            state = false;
+        }
+        if (!state) {
+            console.log(str);
+            this.storeErrorNode(str);
+            interfaceContext.lightbox.post("Message",str);
+        }
+        return state;
+    }
+    
     // Custom comparator Object
 	Interface.prototype.comparator = null;
     
@@ -198,7 +222,7 @@
 			}
 			if (audioEngineContext.status == 0)
 			{
-				alert("Please listen to the samples before making a selection");
+				interfaceContext.lightbox.post("Message", "Please listen to the samples before making a selection");
 				console.log("Please listen to the samples before making a selection");
 				return;
             }
@@ -453,7 +477,7 @@
 	    {
 	        if (audioEngineContext.timer.testStarted == false)
 	        {
-	            alert('You have not started the test! Please press start to begin the test!');
+	            interfaceContext.lightbox.post("Warning",'You have not started the test! Please listen to a sample to begin the test!');
 	            return;
 	        }
 	    }
--- a/interfaces/ape.js	Mon May 16 14:20:50 2016 +0100
+++ b/interfaces/ape.js	Mon May 16 16:20:59 2016 +0100
@@ -41,7 +41,7 @@
                str = 'You have not played fragment ' + (audioEngineContext.audioObjects[hasBeenPlayed[0]].interfaceDOM.getPresentedId()) + ' yet. Please listen, rate and comment all samples before submitting.';
            }
             this.storeErrorNode(str);
-            alert(str);
+            interfaceContext.lightbox.post("Message",str);
 	        return false;
 	    }
 	    return true;
@@ -87,7 +87,7 @@
 		if (state != true)
 		{
             this.storeErrorNode(str);
-			alert(str);
+			interfaceContext.lightbox.post("Message",str);
 			console.log(str);
 		}
 		return state;
@@ -124,7 +124,7 @@
                    str = 'You have not commented on fragment ' + (audioEngineContext.audioObjects[strNums[0]].interfaceDOM.getPresentedId()) + ' yet. Please listen, rate and comment all samples before submitting.';
 		       }
                 this.storeErrorNode(str);
-                alert(str);
+                interfaceContext.lightbox.post("Message",str);
                 console.log(str);
 			}
 		}
@@ -172,7 +172,7 @@
 		if (state != true)
 		{
             this.storeErrorNode(str);
-			alert(str);
+			interfaceContext.lightbox.post("Message",str);
 			console.log(str);
 		}
 		return state;
@@ -811,7 +811,7 @@
 	    {
 	        if (audioEngineContext.timer.testStarted == false)
 	        {
-	            alert('You have not started the test! Please click a fragment to begin the test!');
+	            interfaceContext.lightbox.post("Warning",'You have not started the test! Please click a fragment to begin the test!');
 	            return;
 	        }
 	    }
--- a/interfaces/discrete.js	Mon May 16 14:20:50 2016 +0100
+++ b/interfaces/discrete.js	Mon May 16 16:20:59 2016 +0100
@@ -515,7 +515,7 @@
 	    {
 	        if (audioEngineContext.timer.testStarted == false)
 	        {
-	            alert('You have not started the test! Please press start to begin the test!');
+	            interfaceContext.lightbox.post("Warning",'You have not started the test! Please press start to begin the test!');
 	            return;
 	        }
 	    }
--- a/interfaces/horizontal-sliders.js	Mon May 16 14:20:50 2016 +0100
+++ b/interfaces/horizontal-sliders.js	Mon May 16 16:20:59 2016 +0100
@@ -468,7 +468,7 @@
 	    {
 	        if (audioEngineContext.timer.testStarted == false)
 	        {
-	            alert('You have not started the test! Please press start to begin the test!');
+	            interfaceContext.lightbox.post("Warning",'You have not started the test! Please press start to begin the test!');
 	            return;
 	        }
 	    }
--- a/interfaces/mushra.js	Mon May 16 14:20:50 2016 +0100
+++ b/interfaces/mushra.js	Mon May 16 16:20:59 2016 +0100
@@ -492,7 +492,7 @@
 	    {
 	        if (audioEngineContext.timer.testStarted == false)
 	        {
-	            alert('You have not started the test! Please press start to begin the test!');
+	            interfaceContext.lightbox.post("Message",'You have not started the test! Please press start to begin the test!');
 	            return;
 	        }
 	    }
--- a/js/core.js	Mon May 16 14:20:50 2016 +0100
+++ b/js/core.js	Mon May 16 16:20:59 2016 +0100
@@ -171,6 +171,7 @@
             return "Please only leave this page once you have completed the tests. Are you sure you have completed all testing?";
         };
     }
+    interfaceContext.lightbox.resize();
 };
 
 function loadProjectSpec(url) {
@@ -321,7 +322,7 @@
 	if (specification.sampleRate != undefined) {
 		if (Number(specification.sampleRate) != audioContext.sampleRate) {
 			var errStr = 'Sample rates do not match! Requested '+Number(specification.sampleRate)+', got '+audioContext.sampleRate+'. Please set the sample rate to match before completing this test.';
-			alert(errStr);
+            interfaceContext.lightbox.post("Error",errStr);
 			return;
 		}
 	}
@@ -834,7 +835,7 @@
 			// Must extract the question data
 			var textArea = $(popup.popupContent).find('textarea')[0];
 			if (node.specification.mandatory == true && textArea.value.length == 0) {
-				alert('This question is mandatory');
+                interfaceContext.lightbox.post("Error","This Question is mandatory");
 				return;
 			} else {
 				// Save the text content
@@ -866,7 +867,7 @@
 				{
 					if (node.specification.mandatory == true)
 					{
-						alert("This radio is mandatory");
+                        interfaceContext.lightbox.post("Error","Please select one option");
                         return;
 					}
                     break;
@@ -880,20 +881,20 @@
 		} else if (node.specification.type == "number") {
 			var input = this.popupContent.getElementsByTagName('input')[0];
 			if (node.mandatory == true && input.value.length == 0) {
-				alert('This question is mandatory. Please enter a number');
+				interfaceContext.lightbox.post("Error",'This question is mandatory. Please enter a number');
 				return;
 			}
 			var enteredNumber = Number(input.value);
 			if (isNaN(enteredNumber)) {
-				alert('Please enter a valid number');
+				interfaceContext.lightbox.post("Error",'Please enter a valid number');
 				return;
 			}
 			if (enteredNumber < node.min && node.min != null) {
-				alert('Number is below the minimum value of '+node.min);
+				interfaceContext.lightbox.post("Error",'Number is below the minimum value of '+node.min);
 				return;
 			}
 			if (enteredNumber > node.max && node.max != null) {
-				alert('Number is above the maximum value of '+node.max);
+				interfaceContext.lightbox.post("Error",'Number is above the maximum value of '+node.max);
 				return;
 			}
 			node.response = input.value;
@@ -1044,7 +1045,9 @@
 		if (this.stateIndex == null) {
 			this.initialise();
 		}
-        storage.update();
+        if (this.stateIndex > -2) {
+            storage.update();
+        }
 		if (this.stateIndex == -2) {
             this.stateIndex++;
 			if (this.preTestSurvey != null)
@@ -1202,6 +1205,8 @@
 	this.metric = new sessionMetrics(this,specification);
 	
 	this.loopPlayback = false;
+    this.synchPlayback = false;
+    this.pageSpecification = null;
 	
 	this.pageStore = null;
 	
@@ -1409,7 +1414,8 @@
 			} else {
 				interfaceContext.playhead.setTimePerPixel(this.audioObjects[id]);
 			}
-			if (this.loopPlayback) {
+			if (this.synchPlayback && this.loopPlayback) {
+                // Traditional looped playback
                 var setTime = audioContext.currentTime+specification.crossFade;
 				for (var i=0; i<this.audioObjects.length; i++)
 				{
@@ -1420,7 +1426,7 @@
 						this.audioObjects[i].loopStop(setTime);
 					}
 				}
-			} else {
+            } else {
                 var setTime = audioContext.currentTime+specification.crossFade;
 				for (var i=0; i<this.audioObjects.length; i++)
 				{
@@ -1491,6 +1497,7 @@
 	
 	this.newTestPage = function(audioHolderObject,store) {
 		this.pageStore = store;
+        this.pageSpecification = audioHolderObject;
 		this.status = 0;
 		this.audioObjectsReady = false;
 		this.metric.reset();
@@ -1501,6 +1508,7 @@
 		this.audioObjects = [];
         this.timer = new timer();
         this.loopPlayback = audioHolderObject.loop;
+        this.synchPlayback = audioHolderObject.synchronous;
 	};
 	
 	this.checkAllPlayed = function() {
@@ -1655,7 +1663,7 @@
 				    event.currentTarget.owner.stop(audioContext.currentTime+1);
                 }
 			};
-			if (this.bufferNode.loop == false) {
+			if (!audioEngineContext.loopPlayback || !audioEngineContext.synchPlayback) {
 				this.metric.startListening(audioEngineContext.timer.getTestTime());
                 this.outputGain.gain.setValueAtTime(this.onplayGain,startTime);
                 this.interfaceDOM.startPlayback();
@@ -1977,6 +1985,8 @@
 	this.resizeWindow = function(event)
 	{
 		popup.resize(event);
+        this.volume.resize();
+        this.lightbox.resize();
 		for(var i=0; i<this.commentBoxes.length; i++)
 		{this.commentBoxes[i].resize();}
 		for(var i=0; i<this.commentQuestions.length; i++)
@@ -2035,6 +2045,62 @@
         return hold;
 
     }
+    
+    this.lightbox = {
+        parent: this,
+        root: document.createElement("div"),
+        content: document.createElement("div"),
+        accept: document.createElement("button"),
+        blanker: document.createElement("div"),
+        post: function(type,message) {
+            switch(type) {
+                case "Error":
+                    this.content.className = "lightbox-error";
+                    break;
+                case "Warning":
+                    this.content.className = "lightbox-warning";
+                    break;
+                default:
+                    this.content.className = "lightbox-message";
+                    break;
+            }
+            var msg = document.createElement("p");
+            msg.textContent = message;
+            this.content.appendChild(msg);
+            this.show();
+        },
+        show: function() {
+            this.root.style.visibility = "visible";
+            this.blanker.style.visibility = "visible";
+        },
+        clear: function() {
+            this.root.style.visibility = "";
+            this.blanker.style.visibility = "";
+            this.content.textContent = "";
+        },
+        handleEvent: function(event) {
+            if (event.currentTarget == this.accept) {
+                this.clear();
+            }
+        },
+        resize: function(event) {
+            this.root.style.left = (window.innerWidth/2)-250 + 'px';
+        }
+    }
+    
+    this.lightbox.root.appendChild(this.lightbox.content);
+    this.lightbox.root.appendChild(this.lightbox.accept);
+    this.lightbox.root.className = "popupHolder";
+    this.lightbox.root.id = "lightbox-root";
+    this.lightbox.accept.className = "popupButton";
+    this.lightbox.accept.style.bottom = "10px";
+    this.lightbox.accept.textContent = "OK";
+    this.lightbox.accept.style.left = "237.5px";
+    this.lightbox.accept.addEventListener("click",this.lightbox);
+    this.lightbox.blanker.className = "testHalt";
+    this.lightbox.blanker.id = "lightbox-blanker";
+    document.getElementsByTagName("body")[0].appendChild(this.lightbox.root);
+    document.getElementsByTagName("body")[0].appendChild(this.lightbox.blanker);
 	
 	this.commentBoxes = new function() {
         this.boxes = [];
@@ -2574,8 +2640,11 @@
         // Volume does NOT reset to 0dB on each page load
         this.valueLin = 1.0;
         this.valueDB = 0.0;
+        this.root = document.createElement('div');
+        this.root.id = 'master-volume-root';
         this.object = document.createElement('div');
-        this.object.id = 'master-volume-holder';
+        this.object.className = 'master-volume-holder-float';
+        this.object.appendChild(this.root);
         this.slider = document.createElement('input');
         this.slider.id = 'master-volume-control';
         this.slider.type = 'range';
@@ -2618,10 +2687,18 @@
         title.style.fontSize = '0.75em';
         title.style.width = "100%";
         title.align = 'center';
-        this.object.appendChild(title);
+        this.root.appendChild(title);
         
-        this.object.appendChild(this.slider);
-        this.object.appendChild(this.valueText);
+        this.root.appendChild(this.slider);
+        this.root.appendChild(this.valueText);
+        
+        this.resize = function(event) {
+            if (window.innerWidth < 1000) {
+                this.object.className = "master-volume-holder-inline"
+            } else {
+                this.object.className = 'master-volume-holder-float';
+            }
+        }
     }
     
     this.calibrationModuleObject = null;
@@ -2723,7 +2800,7 @@
 				if (ao.interfaceDOM.getValue() > (ao.specification.marker/100) && ao.specification.marker > 0) {
 					// Anchor is not set below
 					console.log('Anchor node not below marker value');
-					alert('Please keep listening');
+					interfaceContext.lightbox.post("Message",'Please keep listening');
                     this.storeErrorNode('Anchor node not below marker value');
 					return false;
 				}
@@ -2742,7 +2819,7 @@
 					// Anchor is not set below
 					console.log('Reference node not above marker value');
                     this.storeErrorNode('Reference node not above marker value');
-					alert('Please keep listening');
+					interfaceContext.lightbox.post("Message",'Please keep listening');
 					return false;
 				}
 			}
@@ -2800,7 +2877,7 @@
 			str_start += ". Please keep listening";
 			console.log("[ALERT]: "+str_start);
             this.storeErrorNode("[ALERT]: "+str_start);
-			alert(str_start);
+			interfaceContext.lightbox.post("Error",str_start);
 		}
 	};
 	this.checkAllMoved = function()
@@ -2829,7 +2906,7 @@
 			str += 'and '+failed[i];
 		}
 		str +='.';
-		alert(str);
+		interfaceContext.lightbox.post("Error",str);
 		console.log(str);
         this.storeErrorNode(str);
 		return false;
@@ -2860,7 +2937,7 @@
 			str += 'and '+failed[i];
 		}
 		str +='.';
-		alert(str);
+		interfaceContext.lightbox.post("Error",str);
 		console.log(str);
         this.storeErrorNode(str);
 		return false;
@@ -2872,8 +2949,6 @@
         var str = "Please keep listening. ";
         var minRanking = Infinity;
         var maxRanking = -Infinity;
-        var interface = page.specification.interface;
-        var isAb = interface === "AB" || interface === "ABX";
         for (var ao of audioObjects) {
             var rank = ao.interfaceDOM.getValue();
             if (rank < minRanking) {minRanking = rank;}
@@ -2883,21 +2958,18 @@
             str += "At least one fragment must be below the "+min+" mark.";
             state = false;
         }
-	if (maxRanking*100 < max) {
-	    if(isAb){ // if it is AB or ABX let's phrase it differently
-                str += "You must select a fragment before continuing";
-            } else{
-                str += "At least one fragment must be above the "+max+" mark."
-            }
+        if (maxRanking*100 < max) {
+            str += "At least one fragment must be above the "+max+" mark."
             state = false;
         }
         if (!state) {
             console.log(str);
             this.storeErrorNode(str);
-            alert(str);
+            interfaceContext.lightbox.post("Error",str);
         }
         return state;
     }
+    
     this.storeErrorNode = function(errorMessage)
     {
         var time = audioEngineContext.timer.getTestTime();
@@ -2973,6 +3045,10 @@
             this.request.send();
         },
         update: function() {
+            if (this.key == null) {
+                console.log("Cannot save as key == null");
+                return;
+            }
             this.parent.root.setAttribute("state","update");
             var xmlhttp = new XMLHttpRequest();
             var returnURL = "";
--- a/test_create/interface-specs.xml	Mon May 16 14:20:50 2016 +0100
+++ b/test_create/interface-specs.xml	Mon May 16 16:20:59 2016 +0100
@@ -262,12 +262,35 @@
         </scale>
     </scaledefinitions>
     <tests>
-        <test name="APE" interface="APE"/>
-        <test name="vertical-sliders" interface="MUSHRA"/>
-        <test name="horizontal-sliders" interface="horizontal"/>
-        <test name="discrete" interface="discrete"/>
-        <test name="Comparison" interface="AB"/>
+        <test name="APE" interface="APE">
+            <descriptions>
+                <description lang="en">Audio Perceptual Evaluation. A multi-stimulus test where each audio fragment is shown on one continuous slider. Fragments are randomnly positioned along the slider. The user clicks a fragment to play and drags to move.</description>
+            </descriptions>
+        </test>
+        <test name="vertical-sliders" interface="MUSHRA">
+            <descriptions>
+                <description lang="en">Each element is given its own vertical slider with user defined scale markers.</description>
+            </descriptions>
+        </test>
+        <test name="horizontal-sliders" interface="horizontal">
+            <descriptions>
+                <description lang="en">Each element is given its own horizontal slider with user defined scale markers.</description>
+            </descriptions>
+        </test>
+        <test name="discrete" interface="discrete">
+            <descriptions>
+                <description lang="en">Each element is given a horizontal scale broken into a number of discrete choices. The number of choices is defined by the scale markers.</description>
+            </descriptions>
+        </test>
+        <test name="Comparison" interface="AB">
+            <descriptions>
+                <description lang="en">An N-way comparison test. Each element is given its own selector box. The user can select one element per page for submission.</description>
+            </descriptions>
+        </test>
         <test name="MUSHRA" interface="MUSHRA">
+            <descriptions>
+                <description lang="en">Multi-stimulus with hidden reference and anchor. Each fragment is shown on its own vertical slider. One fragment must be labelled as a reference and another labelled as an anchor. One external reference must also be shown.</description>
+            </descriptions>
             <checks>
                 <entry name="fragmentMoved" support="none"/>
                 <entry name="fragmentPlayed" support="none"/>
@@ -289,6 +312,9 @@
             <scale name="ACR"/>
         </test>
         <test name="Rank" interface="discrete">
+            <descriptions>
+                <description lang="en">Each stimulus is placed on a discrete scale equalling the number of fragments. The fragments are then ranked based on the question posed. Only one element can occupy a rank position</description>
+            </descriptions>
             <checks>
                 <entry name="fragmentPlayed" support="none"/>
                 <entry name="fragmentFullPlayback" support="none"/>
@@ -303,6 +329,9 @@
             <scale name="undefined"/>
         </test>
         <test name="Likert" interface="discrete">
+            <descriptions>
+                <description lang="en">Each stimulus is placed on a discrete scale. The scale is fixed to the Likert scale options of 'Strongly Disagree', 'Disagree', 'Neutral', 'Agree' and 'Strongly Agree'</description>
+            </descriptions>
             <checks>
                 <entry name="fragmentPlayed" support="none"/>
                 <entry name="fragmentFullPlayback" support="none"/>
@@ -317,6 +346,9 @@
             <scale name="Likert"/>
         </test>
         <test name="ABC/HR" interface="MUSHRA">
+            <descriptions>
+                <description lang="en">Each stimulus is placed on a vertical slider. The scale is fixed with the labels 'Imperceptible' to 'Very Annoying'</description>
+            </descriptions>
             <checks>
                 <entry name="fragmentMoved" support="none"/>
                 <entry name="fragmentPlayed" support="none"/>
@@ -332,6 +364,9 @@
             <scale name="ABC"/>
         </test>
         <test name="Bipolar" interface="horizontal">
+            <descriptions>
+                <description lang="en">Each stimulus is placed on a horizontal slider and initialised to the value '0'. The scale operates from -50 to +5-. In the results this is normalised, like all other interfaces, from 0 (-50) to 1 (+50).</description>
+            </descriptions>
             <checks>
                 <entry name="fragmentMoved" support="mandatory"/>
                 <entry name="fragmentPlayed" support="none"/>
@@ -350,7 +385,11 @@
             <scale name="Bipolar"/>
         </test>
         <test name="ACR" interface="discrete">
+            <descriptions>
+                <description lang="en">Absolute Category Rating. Each element is on a discrete scale of 'Bad', 'Poor', 'Fair', 'Good' and 'Excellent'. Each element must be given a rating.</description>
+            </descriptions>
             <checks>
+                <entry name="fragmentMoved" support="mandatory"/>
                 <entry name="fragmentPlayed" support="none"/>
                 <entry name="fragmentFullPlayback" support="none"/>
                 <entry name="fragmentComments" support="none"/>
@@ -428,6 +467,9 @@
             <scale name="ABC"/>
         </test>
         <test name="AB" interface="AB">
+            <descriptions>
+                <description lang="en">Each page has only two audio fragments. The user must select one of the two fragments to proceed. There can be one hidden reference.</description>
+            </descriptions>
             <checks>
                 <entry name="fragmentPlayed" support="none"/>
                 <entry name="fragmentFullPlayback" support="none"/>
@@ -441,7 +483,7 @@
             </show>
             <elements>
                 <number min="2" max="2"/>
-                <outsidereference min="0" max="0"/>
+                <outsidereference min="0" max="1"/>
             </elements>
         </test>
         <test name="ABX" interface="ABX"/>
--- a/test_create/test_core.js	Mon May 16 14:20:50 2016 +0100
+++ b/test_create/test_core.js	Mon May 16 16:20:59 2016 +0100
@@ -5,6 +5,7 @@
 var specification;
 var convert;
 var attributeText;
+var page_lang = "en";
 
 // Firefox does not have an XMLDocument.prototype.getElementsByName
 // and there is no searchAll style command, this custom function will
@@ -278,6 +279,9 @@
             spnH.appendChild(span);
             this.content.appendChild(spnH);
             this.select = document.createElement("select");
+            this.content.appendChild(this.select);
+            this.description = document.createElement("p");
+            this.content.appendChild(this.description);
             this.testsXML = interfaceSpecs.getElementsByTagName('tests')[0].children;
             for (var i=0; i<this.testsXML.length; i++)
             {
@@ -286,7 +290,18 @@
                 option.textContent = this.testsXML[i].getAttribute('name');
                 this.select.appendChild(option);
             }
-            this.content.appendChild(this.select);
+            this.handleEvent = function(event) {
+                var testXML = interfaceSpecs.getElementsByTagName("tests")[0].getAllElementsByName(this.select.value)[0];
+                var descriptors = testXML.getAllElementsByTagName("description");
+                this.description.textContent = "";
+                for (var i=0; i<descriptors.length; i++) {
+                    if (descriptors[i].getAttribute("lang") == page_lang) {
+                        this.description.textContent = descriptors[i].textContent;
+                    }
+                }
+            }
+            this.select.addEventListener("change",this);
+            this.handleEvent();
             this.continue = function()
             {
                 var testXML = interfaceSpecs.getElementsByTagName("tests")[0].getAllElementsByName(this.select.value)[0];