changeset 2472:582be5fd8884

Merge branch 'master' of https://github.com/BrechtDeMan/WebAudioEvaluationTool
author www-data <www-data@sucuk.dcs.qmul.ac.uk>
date Tue, 02 Aug 2016 13:20:49 +0100
parents 7875d043f8ba (current diff) 2a1f42b5614a (diff)
children 32335c0f5a08
files
diffstat 10 files changed, 307 insertions(+), 29 deletions(-) [+]
line wrap: on
line diff
--- a/interfaces/AB.js	Tue Aug 02 12:20:52 2016 +0100
+++ b/interfaces/AB.js	Tue Aug 02 13:20:49 2016 +0100
@@ -45,6 +45,7 @@
 	title.className = "title";
 	title.align = "center";
 	var titleSpan = document.createElement('span');
+    titleSpan.id = "test-title";
 	
 	// Set title to that defined in XML, else set to default
 	if (titleAttr != undefined) {
@@ -136,6 +137,11 @@
     // Delete outside reference
 	var outsideReferenceHolder = document.getElementById("outside-reference-holder");
     outsideReferenceHolder.innerHTML = "";
+    
+    // Set the page title
+    if (typeof audioHolderObject.title == "string" && audioHolderObject.title.length > 0) {
+        document.getElementById("test-title").textContent = audioHolderObject.title
+    }
 	
 	if(interfaceObj.title != null)
 	{
@@ -207,7 +213,7 @@
     
     $(audioHolderObject.commentQuestions).each(function(index,element) {
 		var node = interfaceContext.createCommentQuestion(element);
-		commentHolder.appendChild(node.holder);
+		document.getElementById('testContent').appendChild(node.holder);
 	});
     
 	resizeWindow(null);
--- a/interfaces/ABX.js	Tue Aug 02 12:20:52 2016 +0100
+++ b/interfaces/ABX.js	Tue Aug 02 13:20:49 2016 +0100
@@ -50,6 +50,7 @@
 	title.className = "title";
 	title.align = "center";
 	var titleSpan = document.createElement('span');
+    titleSpan.id = "test-title";
     
     // Set title to that defined in XML, else set to default
 	if (titleAttr != undefined) {
@@ -134,12 +135,19 @@
 		console.log("WARNING - This interface only supports one <interface> node per page. Using first interface node");
 	}
 	interfaceObj = interfaceObj[0];
+    
+    // Set the page title
+    if (typeof page.title == "string" && page.title.length > 0) {
+        document.getElementById("test-title").textContent = page.title
+    }
 	
 	if(interfaceObj.title != null)
 	{
 		document.getElementById("pageTitle").textContent = interfaceObj.title;
 	}
     
+    interfaceContext.comparator = new comparator(page);
+    
     var interfaceOptions = specification.interfaces.options.concat(interfaceObj.options);
     for (var option of interfaceOptions)
     {
@@ -175,11 +183,26 @@
                         feedbackHolder.appendChild(interfaceContext.volume.object);
                     }
                     break;
+                case "comments":
+                    var commentHolder = document.createElement('div');
+                    commentHolder.id = 'commentHolder';
+                    document.getElementById('testContent').appendChild(commentHolder);
+                    // Generate one comment box per presented page
+                    for (var element of audioEngineContext.audioObjects)
+                    {
+                        interfaceContext.commentBoxes.createCommentBox(element);
+                    }
+                    interfaceContext.commentBoxes.showCommentBoxes(commentHolder,true);
+                    break;
             }
         }
     }
     
-    interfaceContext.comparator = new comparator(page);
+    $(page.commentQuestions).each(function(index,element) {
+		var node = interfaceContext.createCommentQuestion(element);
+		document.getElementById('testContent').appendChild(node.holder);
+	});
+    
     resizeWindow(null);
 }
 
--- a/interfaces/ape.js	Tue Aug 02 12:20:52 2016 +0100
+++ b/interfaces/ape.js	Tue Aug 02 13:20:49 2016 +0100
@@ -220,6 +220,7 @@
 	title.className = "title";
 	title.align = "center";
 	var titleSpan = document.createElement('span');
+    titleSpan.id = "test-title";
 	
 	// Set title to that defined in XML, else set to default
 	if (titleAttr != undefined) {
@@ -297,6 +298,12 @@
 	var sliderHolder = document.getElementById('slider-holder');
 	feedbackHolder.innerHTML = "";
 	sliderHolder.innerHTML = "";
+    
+    // Set the page title
+    if (typeof audioHolderObject.title == "string" && audioHolderObject.title.length > 0) {
+        document.getElementById("test-title").textContent = audioHolderObject.title
+    }
+	
 	
 	// Delete outside reference
 	document.getElementById("outside-reference-holder").innerHTML = "";
@@ -395,7 +402,11 @@
 		event.preventDefault();
 		var obj = interfaceContext.getSelectedObject();
 		if (obj == null) {return;}
-		$(obj).css("left",event.clientX-6 + "px");
+        var move = event.clientX-6;
+        var w = $(event.currentTarget).width();
+        move = Math.max(50,move);
+        move = Math.min(w+50,move);
+		$(obj).css("left",move + "px");
 		interfaceContext.moveObject();
 	});
 	
@@ -404,6 +415,9 @@
 		var obj = interfaceContext.getSelectedObject();
 		if (obj == null) {return;}
 		var move = event.originalEvent.targetTouches[0].clientX - 6;
+        var w = $(event.currentTarget).width();
+        move = Math.max(50,move);
+        move = Math.min(w+50,move);
 		$(obj).css("left",move + "px");
 		interfaceContext.moveObject();
 	});
--- a/interfaces/discrete.js	Tue Aug 02 12:20:52 2016 +0100
+++ b/interfaces/discrete.js	Tue Aug 02 13:20:49 2016 +0100
@@ -16,6 +16,7 @@
 	title.className = "title";
 	title.align = "center";
 	var titleSpan = document.createElement('span');
+    titleSpan.id = "test-title";
 	
 	// Set title to that defined in XML, else set to default
 	if (titleAttr != undefined) {
@@ -126,6 +127,12 @@
 		console.log("WARNING - This interface only supports one <interface> node per page. Using first interface node");
 	}
 	interfaceObj = interfaceObj[0];
+    
+    // Set the page title
+    if (typeof page.title == "string" && page.title.length > 0) {
+        document.getElementById("test-title").textContent = page.title
+    }
+    
 	if(interfaceObj.title != null)
 	{
 		document.getElementById("pageTitle").textContent = interfaceObj.title;
@@ -143,11 +150,6 @@
 	}
 	var loopPlayback = page.loop;
 	
-	$(page.commentQuestions).each(function(index,element) {
-		var node = interfaceContext.createCommentQuestion(element);
-		feedbackHolder.appendChild(node.holder);
-	});
-	
 	// Find all the audioElements from the audioHolder
 	var index = 0;
 	var interfaceScales = testState.currentStateMap.interfaces[0].scales;
@@ -229,7 +231,7 @@
     
     $(page.commentQuestions).each(function(index,element) {
 		var node = interfaceContext.createCommentQuestion(element);
-		commentHolder.appendChild(node.holder);
+		feedbackHolder.appendChild(node.holder);
 	});
     
 	// Auto-align
--- a/interfaces/horizontal-sliders.js	Tue Aug 02 12:20:52 2016 +0100
+++ b/interfaces/horizontal-sliders.js	Tue Aug 02 13:20:49 2016 +0100
@@ -16,6 +16,7 @@
 	title.className = "title";
 	title.align = "center";
 	var titleSpan = document.createElement('span');
+    titleSpan.id="test-title";
 	
 	// Set title to that defined in XML, else set to default
 	if (titleAttr != undefined) {
@@ -126,6 +127,12 @@
 		console.log("WARNING - This interface only supports one <interface> node per page. Using first interface node");
 	}
 	interfaceObj = interfaceObj[0];
+    
+    // Set the page title
+    if (typeof page.title == "string" && page.title.length > 0) {
+        document.getElementById("test-title").textContent = page.title
+    }
+    
 	if(interfaceObj.title != null)
 	{
 		document.getElementById("pageTitle").textContent = interfaceObj.title;
--- a/interfaces/mushra.js	Tue Aug 02 12:20:52 2016 +0100
+++ b/interfaces/mushra.js	Tue Aug 02 13:20:49 2016 +0100
@@ -22,6 +22,7 @@
 	title.className = "title";
 	title.align = "center";
 	var titleSpan = document.createElement('span');
+    titleSpan.id = "test-title";
 	
 	// Set title to that defined in XML, else set to default
 	if (titleAttr != undefined) {
@@ -127,6 +128,12 @@
 		console.log("WARNING - This interface only supports one <interface> node per page. Using first interface node");
 	}
 	interfaceObj = interfaceObj[0];
+    
+    // Set the page title
+    if (typeof audioHolderObject.title == "string" && audioHolderObject.title.length > 0) {
+        document.getElementById("test-title").textContent = audioHolderObject.title
+    }
+    
 	if(interfaceObj.title != null)
 	{
 		document.getElementById("pageTitle").textContent = interfaceObj.title;
--- a/js/core.js	Tue Aug 02 12:20:52 2016 +0100
+++ b/js/core.js	Tue Aug 02 13:20:49 2016 +0100
@@ -859,6 +859,39 @@
 				console.log("Question Response: "+ textArea.value);
 				node.response = textArea.value;
 			}
+            // Perform the conditional
+            for (var condition of node.specification.conditions) {
+                var pass = false;
+                switch(condition.check) {
+                    case "equals":
+                        if (textArea.value == condition.value) {
+                            pass = true;
+                        }
+                        break;
+                    case "greaterThan":
+                    case "lessThan":
+                        console.log("Survey Element of type 'question' cannot interpret greaterThan/lessThan conditions. IGNORING");
+                        break;
+                    case "contains":
+                        if (textArea.value.includes(condition.value)) {
+                            pass = true;
+                        }
+                        break;
+                }
+                var jumpID;
+                if (pass) {
+                    jumpID = condition.jumpToOnPass;
+                } else {
+                    jumpID = condition.jumpToOnFail;
+                }
+                if (jumpID != undefined) {
+                    var index = this.popupOptions.findIndex(function(item,index,element){
+                        if (item.specification.id == jumpID) {return true;} else {return false;}
+                    },this);
+                    this.currentIndex = index-1;
+                    break;
+                }
+            }
 		} else if (node.specification.type == 'checkbox') {
 			// Must extract checkbox data
 			console.log("Checkbox: "+ node.specification.statement);
@@ -872,6 +905,38 @@
 				});
 				console.log(node.specification.options[i].name+": "+ inputs[i].checked);
 			}
+            // Perform the conditional
+            for (var condition of node.specification.conditions) {
+                var pass = false;
+                switch(condition.check) {
+                    case "equals":
+                    case "greaterThan":
+                    case "lessThan":
+                        console.log("Survey Element of type 'checkbox' cannot interpret equals/greaterThan/lessThan conditions. IGNORING");
+                        break;
+                    case "contains":
+                        for (var response of node.response) {
+                            if (response.name == condition.value && response.checked) {
+                                pass = true;
+                                break;
+                            }
+                        }
+                        break;
+                }
+                var jumpID;
+                if (pass) {
+                    jumpID = condition.jumpToOnPass;
+                } else {
+                    jumpID = condition.jumpToOnFail;
+                }
+                if (jumpID != undefined) {
+                    var index = this.popupOptions.findIndex(function(item,index,element){
+                        if (item.specification.id == jumpID) {return true;} else {return false;}
+                    },this);
+                    this.currentIndex = index-1;
+                    break;
+                }
+            }
 		} else if (node.specification.type == "radio") {
 			var optHold = this.popupResponse;
 			console.log("Radio: "+ node.specification.statement);
@@ -894,6 +959,35 @@
 				}
 				i++;
 			}
+            // Perform the conditional
+            for (var condition of node.specification.conditions) {
+                var pass = false;
+                switch(condition.check) {
+                    case "contains":
+                    case "greaterThan":
+                    case "lessThan":
+                        console.log("Survey Element of type 'radio' cannot interpret contains/greaterThan/lessThan conditions. IGNORING");
+                        break;
+                    case "equals":
+                        if (node.response == condition.value) {
+                            pass = true;
+                        }
+                        break;
+                }
+                var jumpID;
+                if (pass) {
+                    jumpID = condition.jumpToOnPass;
+                } else {
+                    jumpID = condition.jumpToOnFail;
+                }
+                if (jumpID != undefined) {
+                    var index = this.popupOptions.findIndex(function(item,index,element){
+                        if (item.specification.id == jumpID) {return true;} else {return false;}
+                    },this);
+                    this.currentIndex = index-1;
+                    break;
+                }
+            }
 		} else if (node.specification.type == "number") {
 			var input = this.popupContent.getElementsByTagName('input')[0];
 			if (node.mandatory == true && input.value.length == 0) {
@@ -914,6 +1008,43 @@
 				return;
 			}
 			node.response = input.value;
+            // Perform the conditional
+            for (var condition of node.specification.conditions) {
+                var pass = false;
+                switch(condition.check) {
+                    case "contains":
+                        console.log("Survey Element of type 'number' cannot interpret contains conditions. IGNORING");
+                        break;
+                    case "greaterThan":
+                        if (node.response > Number(condition.value)) {
+                            pass = true;
+                        }
+                        break;
+                    case "lessThan":
+                        if (node.response < Number(condition.value)) {
+                            pass = true;
+                        }
+                        break;
+                    case "equals":
+                        if (node.response == condition.value) {
+                            pass = true;
+                        }
+                        break;
+                }
+                var jumpID;
+                if (pass) {
+                    jumpID = condition.jumpToOnPass;
+                } else {
+                    jumpID = condition.jumpToOnFail;
+                }
+                if (jumpID != undefined) {
+                    var index = this.popupOptions.findIndex(function(item,index,element){
+                        if (item.specification.id == jumpID) {return true;} else {return false;}
+                    },this);
+                    this.currentIndex = index-1;
+                    break;
+                }
+            }
 		}
 		this.currentIndex++;
 		if (this.currentIndex < this.popupOptions.length) {
@@ -1355,29 +1486,48 @@
             // Copies the entire bufferObj.
             if (preSilenceTime == undefined) {preSilenceTime = 0;}
             if (postSilenceTime == undefined) {postSilenceTime = 0;}
-            var copy = new this.constructor();
-            copy.url = this.url;
             var preSilenceSamples = secondsToSamples(preSilenceTime,this.buffer.sampleRate);
             var postSilenceSamples = secondsToSamples(postSilenceTime,this.buffer.sampleRate);
             var newLength = this.buffer.length+preSilenceSamples+postSilenceSamples;
-            copy.buffer = audioContext.createBuffer(this.buffer.numberOfChannels, newLength, this.buffer.sampleRate);
+            var copybuffer = audioContext.createBuffer(this.buffer.numberOfChannels, newLength, this.buffer.sampleRate);
             // Now we can use some efficient background copy schemes if we are just padding the end
-            if (preSilenceSamples == 0 && typeof copy.buffer.copyToChannel == "function") {
+            if (preSilenceSamples == 0 && typeof copybuffer.copyToChannel == "function") {
                 for (var c=0; c<this.buffer.numberOfChannels; c++) {
-                    copy.buffer.copyToChannel(this.buffer.getChannelData(c),c);
+                    copybuffer.copyToChannel(this.buffer.getChannelData(c),c);
                 }
             } else {
                 for (var c=0; c<this.buffer.numberOfChannels; c++) {
                     var src = this.buffer.getChannelData(c);
-                    var dst = copy.buffer.getChannelData(c);
+                    var dst = copybuffer.getChannelData(c);
                     for (var n=0; n<src.length; n++)
                         dst[n+preSilenceSamples] = src[n];
                 }
             }
             // Copy in the rest of the buffer information
-            copy.buffer.lufs = this.buffer.lufs;
-            copy.buffer.playbackGain = this.buffer.playbackGain;
-            return copy;
+            copybuffer.lufs = this.buffer.lufs;
+            copybuffer.playbackGain = this.buffer.playbackGain;
+            return copybuffer;
+        }
+        
+        this.cropBuffer = function(startTime, stopTime) {
+            // Copy and return the cropped buffer
+            var start_sample = Math.floor(startTime*this.buffer.sampleRate);
+            var stop_sample = Math.floor(stopTime*this.buffer.sampleRate);
+            var newLength = stop_sample - start_sample;
+            var copybuffer = audioContext.createBuffer(this.buffer.numberOfChannels, newLength, this.buffer.sampleRate);
+            // Now we can use some efficient background copy schemes if we are just padding the end
+            for (var c=0; c<this.buffer.numberOfChannels; c++) {
+                var buffer = this.buffer.getChannelData(c);
+                var sub_frame = buffer.subarray(start_sample,stop_sample);
+                if (typeof copybuffer.copyToChannel == "function") {
+                    copybuffer.copyToChannel(sub_frame,c);
+                } else {
+                    var dst = copybuffer.getChannelData(c);
+                    for (var n=0; n<newLength; n++)
+                        dst[n] = src[n+start_sample];
+                }
+            }
+            return copybuffer;
         }
 	};
     
@@ -1402,13 +1552,9 @@
 	
 	this.play = function(id) {
 		// Start the timer and set the audioEngine state to playing (1)
-		if (this.status == 0 && this.loopPlayback) {
+		if (this.status == 0) {
 			// Check if all audioObjects are ready
-			if(this.checkAllReady())
-			{
-				this.status = 1;
-				this.setSynchronousLoop();
-			}
+			this.bufferReady(id);
 		}
 		else
 		{
@@ -1563,6 +1709,15 @@
 		}
 	};
     
+    this.bufferReady = function(id) {
+        if(this.checkAllReady()) {
+            if (this.synchPlayback){this.setSynchronousLoop();}
+            this.status = 1;
+            return true;
+        }
+        return false;
+    }
+    
     this.exportXML = function()
     {
         
@@ -1611,12 +1766,22 @@
         this.buffer = callee;
         var preSilenceTime = this.specification.preSilence || this.specification.parent.preSilence || specification.preSilence || 0.0;
         var postSilenceTime = this.specification.postSilence || this.specification.parent.postSilence || specification.postSilence || 0.0;
+        var startTime = this.specification.startTime;
+        var stopTime = this.specification.stopTime;
+        var copybuffer = new callee.constructor();
+        if (isFinite(startTime) || isFinite(stopTime)) {
+            copybuffer.buffer = callee.cropBuffer(startTime,stopTime);
+        }
         if (preSilenceTime != 0 || postSilenceTime != 0) {
-            this.buffer = callee.copyBuffer(preSilenceTime,postSilenceTime);    
+            if (copybuffer.buffer == undefined) {
+                copybuffer.buffer = callee.copyBuffer(preSilenceTime,postSilenceTime);
+            } else {
+                copybuffer.buffer = copybuffer.copyBuffer(preSilenceTime,postSilenceTime);
+            }
         }
-		this.state = 1;
+        
 		var targetLUFS = this.specification.parent.loudness || specification.loudness;
-		if (typeof targetLUFS === "number")
+		if (typeof targetLUFS === "number" && isFinite(targetLUFS))
 		{
 			this.buffer.buffer.playbackGain = decibelToLinear(targetLUFS - this.buffer.buffer.lufs);
 		} else {
@@ -1627,6 +1792,8 @@
 		}
 		this.onplayGain = decibelToLinear(this.specification.gain)*(this.buffer.buffer.playbackGain||1.0);
 		this.storeDOM.setAttribute('playGain',linearToDecibel(this.onplayGain));
+        this.state = 1;
+        audioEngineContext.bufferReady(id);
 	};
 	
 	this.bindInterface = function(interfaceObject)
@@ -3154,6 +3321,10 @@
 				surveyresult.appendChild(child);
 				break;
 			case "checkbox":
+                if (node.response == undefined) {
+                    surveyresult.appendChild(this.parent.document.createElement('response'));
+                    break;
+                }
 				for (var i=0; i<node.response.length; i++)
 				{
 					var checkNode = this.parent.document.createElement('response');
--- a/js/specification.js	Tue Aug 02 12:20:52 2016 +0100
+++ b/js/specification.js	Tue Aug 02 13:20:49 2016 +0100
@@ -214,6 +214,7 @@
 			this.min = undefined;
 			this.max = undefined;
 			this.step = undefined;
+            this.conditions = [];
 			
 			this.decode = function(parent,child)
 			{
@@ -252,6 +253,17 @@
 						}
 					}
 				}
+                var conditionElements = child.getElementsByTagName("conditional");
+                for (var i=0; i<conditionElements.length; i++) {
+                    var condition = conditionElements[i];
+                    var obj = {
+                        check: condition.getAttribute("check"),
+                        value: condition.getAttribute("value"),
+                        jumpToOnPass: condition.getAttribute("jumpToOnPass"),
+                        jumpToOnFail: condition.getAttribute("jumpToOnFail")
+                    }
+                    this.conditions.push(obj);
+                }
 			};
 			
 			this.exportXML = function(doc)
@@ -287,6 +299,14 @@
                     default:
                         break;
                 }
+                for (var condition of this.conditions) {
+                    var conditionDOM = doc.createElement("conditional");
+                    conditionDOM.setAttribute("check",condition.check);
+                    conditionDOM.setAttribute("value",condition.value);
+                    conditionDOM.setAttribute("jumpToOnPass",condition.jumpToOnPass);
+                    conditionDOM.setAttribute("jumpToOnFail",condition.jumpToOnFail);
+                    node.appendChild(conditionDOM);
+                }
 				return node;
 			};
 		};
@@ -434,6 +454,7 @@
 	this.page = function(specification) {
 		this.presentedId = undefined;
 		this.id = undefined;
+        this.title = undefined;
 		this.hostURL = undefined;
 		this.randomiseOrder = undefined;
 		this.loop = undefined;
@@ -471,6 +492,12 @@
 					break;
 				}
 			}
+            
+            // Get the title
+            var title = xml.getElementsByTagName('title');
+            if (title.length != 0) {
+                this.title = title[0].textContent;
+            }
 			
 			// Get the Comment Box Prefix
 			var CBP = xml.getElementsByTagName('commentboxprefix');
@@ -622,8 +649,8 @@
 			this.enforce = false;
 			this.gain = 0.0;
             this.label = null;
-            this.startTime = null;
-            this.stopTime = null;
+            this.startTime = undefined;
+            this.stopTime = undefined;
 			this.schema = specification.schema.getAllElementsByName('audioelement')[0];;
 			this.parent = null;
 			this.decode = function(parent,xml)
--- a/tests/examples/project.xml	Tue Aug 02 12:20:52 2016 +0100
+++ b/tests/examples/project.xml	Tue Aug 02 13:20:49 2016 +0100
@@ -4,6 +4,7 @@
 		<survey location="before">
 			<surveyentry type="question" id="sessionId" mandatory="true">
 				<statement>Please enter your name.</statement>
+                <conditional check="equals" value="John" jumpToOnPass="test-intro" jumpToOnFail="checkboxtest"/>
 			</surveyentry>
 			<surveyentry type="checkbox" id="checkboxtest" mandatory="true">
 				<statement>Please select with which activities you have any experience (example checkbox question)</statement>
@@ -13,6 +14,9 @@
 				<option name="hwdesigner">Designing or building audio hardware</option>
 				<option name="researcher">Research in the field of audio</option>
 			</surveyentry>
+            <surveyentry type="question" id="instrument" mandatory="false">
+                <statement>What instrument did you play</statement>
+            </surveyentry>
 			<surveyentry type="statement" id="test-intro">
 				<statement>This is an example of an 'APE'-style test, with two pages, using the test stimuli in 'example_eval/'.</statement>
 			</surveyentry>
--- a/xml/test-schema.xsd	Tue Aug 02 12:20:52 2016 +0100
+++ b/xml/test-schema.xsd	Tue Aug 02 13:20:49 2016 +0100
@@ -249,6 +249,23 @@
                                         </xs:simpleContent>
                                     </xs:complexType>
                                 </xs:element>
+                                <xs:element name="conditional" minOccurs="0" maxOccurs="unbounded">
+                                    <xs:complexType>
+                                        <xs:attribute name="check" use="required">
+                                            <xs:simpleType>
+                                                <xs:restriction base="xs:string">
+                                                    <xs:enumeration value="equals"/>
+                                                    <xs:enumeration value="lessThan"/>
+                                                    <xs:enumeration value="greaterThan"/>
+                                                    <xs:enumeration value="stringContains"/>
+                                                </xs:restriction>
+                                            </xs:simpleType>
+                                        </xs:attribute>
+                                        <xs:attribute name="value" type="xs:string" use="optional"/>
+                                        <xs:attribute name="jumpToOnPass" type="xs:string" use="optional"/>
+                                        <xs:attribute name="jumpToOnFail" type="xs:string" use="optional"/>
+                                    </xs:complexType>
+                                </xs:element>
                             </xs:sequence>
                             <xs:attribute ref="id" use="required" />
                             <xs:attribute ref="name" />