changeset 2467:221688a8be4f

Merge branch 'master' into Dev_main
author Nicholas Jillings <nicholas.jillings@mail.bcu.ac.uk>
date Tue, 02 Aug 2016 12:26:19 +0100
parents 496fde335890 (diff) d26623bd65e0 (current diff)
children 8273da734d07
files js/core.js
diffstat 5 files changed, 240 insertions(+), 21 deletions(-) [+]
line wrap: on
line diff
--- a/interfaces/ape.js	Tue Aug 02 11:22:51 2016 +0100
+++ b/interfaces/ape.js	Tue Aug 02 12:26:19 2016 +0100
@@ -395,7 +395,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 +408,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/js/core.js	Tue Aug 02 11:22:51 2016 +0100
+++ b/js/core.js	Tue Aug 02 12:26:19 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 11:22:51 2016 +0100
+++ b/js/specification.js	Tue Aug 02 12:26:19 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;
 			};
 		};
@@ -622,8 +642,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 11:22:51 2016 +0100
+++ b/tests/examples/project.xml	Tue Aug 02 12:26:19 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 11:22:51 2016 +0100
+++ b/xml/test-schema.xsd	Tue Aug 02 12:26:19 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" />